feat: lunch banner on applicant dashboard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-04-29 02:46:02 +02:00
parent 618def6174
commit ec24d404c5
2 changed files with 58 additions and 0 deletions

View File

@@ -19,6 +19,7 @@ import { CompetitionTimelineSidebar } from '@/components/applicant/competition-t
import { MentoringRequestCard } from '@/components/applicant/mentoring-request-card' import { MentoringRequestCard } from '@/components/applicant/mentoring-request-card'
import { MentorConversationCard } from '@/components/applicant/mentor-conversation-card' import { MentorConversationCard } from '@/components/applicant/mentor-conversation-card'
import { AttendingMembersCard } from '@/components/applicant/attending-members-card' import { AttendingMembersCard } from '@/components/applicant/attending-members-card'
import { LunchBanner } from '@/components/applicant/lunch-banner'
import { AnimatedCard } from '@/components/shared/animated-container' import { AnimatedCard } from '@/components/shared/animated-container'
import { ProjectLogoUpload } from '@/components/shared/project-logo-upload' import { ProjectLogoUpload } from '@/components/shared/project-logo-upload'
import { Progress } from '@/components/ui/progress' import { Progress } from '@/components/ui/progress'
@@ -403,6 +404,9 @@ export default function ApplicantDashboardPage() {
</AnimatedCard> </AnimatedCard>
))} ))}
{/* Lunch banner (auto-hides when lunch event disabled or unconfigured) */}
<LunchBanner programId={project.programId} />
{/* Grand finale attendee roster (auto-hides until confirmation status is CONFIRMED) */} {/* Grand finale attendee roster (auto-hides until confirmation status is CONFIRMED) */}
<AttendingMembersCard /> <AttendingMembersCard />

View File

@@ -0,0 +1,54 @@
'use client'
import { trpc } from '@/lib/trpc/client'
import { Card, CardContent } from '@/components/ui/card'
import { Calendar, MapPin, Salad, Clock } from 'lucide-react'
export function LunchBanner({ programId }: { programId: string }) {
const { data: event } = trpc.lunch.getEventForMember.useQuery({ programId })
if (!event) return null
const fmt = new Intl.DateTimeFormat(undefined, {
timeZone: 'Europe/Monaco',
dateStyle: 'long',
timeStyle: 'short',
})
const eventAt = event.eventAt ? new Date(event.eventAt) : null
const deadline = event.changeDeadline ? new Date(event.changeDeadline) : null
const deadlinePassed = deadline ? new Date() > deadline : false
return (
<Card>
<CardContent className="flex flex-wrap items-center gap-4 py-3 text-sm">
<Salad className="h-4 w-4 text-emerald-500" />
<span className="font-medium">Lunch event</span>
{eventAt && (
<span className="flex items-center gap-1.5">
<Calendar className="h-4 w-4" /> {fmt.format(eventAt)}{' '}
<span className="text-muted-foreground text-xs">(Monaco time)</span>
</span>
)}
{event.venue && (
<span className="flex items-center gap-1.5">
<MapPin className="h-4 w-4" /> {event.venue}
</span>
)}
{deadline && (
<span
className={`text-muted-foreground ml-auto flex items-center gap-1.5 ${deadlinePassed ? 'text-destructive' : ''}`}
>
<Clock className="h-4 w-4" />
{deadlinePassed ? 'Picks closed' : 'Picks close'}: {fmt.format(deadline)}
</span>
)}
{event.notes && (
<details className="basis-full">
<summary className="text-muted-foreground cursor-pointer text-xs">
Notes from organizers
</summary>
<p className="mt-1 text-sm whitespace-pre-wrap">{event.notes}</p>
</details>
)}
</CardContent>
</Card>
)
}