diff --git a/src/app/(applicant)/applicant/page.tsx b/src/app/(applicant)/applicant/page.tsx
index 476fdbd..76e17d4 100644
--- a/src/app/(applicant)/applicant/page.tsx
+++ b/src/app/(applicant)/applicant/page.tsx
@@ -19,6 +19,7 @@ import { CompetitionTimelineSidebar } from '@/components/applicant/competition-t
import { MentoringRequestCard } from '@/components/applicant/mentoring-request-card'
import { MentorConversationCard } from '@/components/applicant/mentor-conversation-card'
import { AttendingMembersCard } from '@/components/applicant/attending-members-card'
+import { MyLogisticsCard } from '@/components/applicant/my-logistics-card'
import { LunchBanner } from '@/components/applicant/lunch-banner'
import { ExternalAttendeesStrip } from '@/components/applicant/external-attendees-strip'
import { AnimatedCard } from '@/components/shared/animated-container'
@@ -414,6 +415,9 @@ export default function ApplicantDashboardPage() {
{/* Grand finale attendee roster (auto-hides until confirmation status is CONFIRMED) */}
We'll be in touch shortly with travel and lunch logistics. You can edit your team - selection from your project page closer to the event. + selection and view hotel, flight, and visa details from your dashboard.
+ ) diff --git a/src/components/applicant/my-logistics-card.tsx b/src/components/applicant/my-logistics-card.tsx new file mode 100644 index 0000000..d9e58b7 --- /dev/null +++ b/src/components/applicant/my-logistics-card.tsx @@ -0,0 +1,291 @@ +'use client' + +import { useState } from 'react' +import { trpc } from '@/lib/trpc/client' +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Skeleton } from '@/components/ui/skeleton' +import { Hotel, PlaneLanding, PlaneTakeoff, ShieldCheck, Pencil, Loader2 } from 'lucide-react' +import { toast } from 'sonner' +import type { FlightDetailStatus, VisaStatus } from '@prisma/client' + +const FLIGHT_BADGE: Record< + FlightDetailStatus, + { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' } +> = { + PENDING: { label: 'Pending confirmation', variant: 'secondary' }, + CONFIRMED: { label: 'Flight confirmed', variant: 'default' }, +} + +const VISA_BADGE: Record< + VisaStatus, + { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' } +> = { + NOT_NEEDED: { label: 'Visa not needed', variant: 'outline' }, + REQUESTED: { label: 'Visa requested', variant: 'secondary' }, + INVITATION_SENT: { label: 'Invitation sent', variant: 'secondary' }, + APPOINTMENT_BOOKED: { label: 'Appointment booked', variant: 'default' }, + GRANTED: { label: 'Visa granted', variant: 'default' }, + DENIED: { label: 'Visa denied', variant: 'destructive' }, +} + +function formatMonacoTime(d: Date | string | null | undefined): string { + if (!d) return '—' + return new Date(d).toLocaleString('en-GB', { + timeZone: 'Europe/Paris', + dateStyle: 'medium', + timeStyle: 'short', + }) +} + +function FlightRow({ + label, + icon, + flightNumber, + airport, + at, +}: { + label: string + icon: React.ReactNode + flightNumber: string | null | undefined + airport: string | null | undefined + at: Date | string | null | undefined +}) { + const hasData = flightNumber || airport || at + if (!hasData) return null + return ( ++ Hotel +
+ {hotel ? ( ++ {hotel.link ? ( + + {hotel.name} + + ) : ( + hotel.name + )} +
+ {hotel.address && ( +{hotel.address}
+ )} +Hotel details coming soon.
+ )} ++ Flights +
+ {hasFlightData ? ( ++ Your flight details will appear here once arranged. +
+ )} ++ Visa +
+Passport nationality
+