import Link from "next/link" import { notFound } from "next/navigation" import { CopyInviteButton } from "@/components/admin/copy-invite-button" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { getInvitationInteractions, updateToastRequestStatus, } from "@/lib/db/queries/interactions" import { getInvitationDetail } from "@/lib/db/queries/invitations" import { getPhotoUploadByInvitationId } from "@/lib/db/queries/uploads" import { getLanguageLabel } from "@/lib/i18n/config" import { createPresignedDownloadUrl } from "@/lib/storage/uploads" import { toastStatusSchema } from "@/lib/validation/interactions" export const dynamic = "force-dynamic" function getInviteUrl(token: string) { return new URL(`/invite/${token}`, process.env.APP_URL ?? "http://localhost:3000").toString() } function formatDate(date: Date | null | undefined) { if (!date) return "—" return new Intl.DateTimeFormat("en", { dateStyle: "medium", timeStyle: "short", }).format(date) } function formatBoolean(value: boolean | null) { if (value === true) return "Yes" if (value === false) return "No" return "Pending" } function formatDateOnly(value: Date | string | null | undefined) { if (!value) return "—" if (value instanceof Date) { return new Intl.DateTimeFormat("en", { dateStyle: "medium" }).format(value) } return value } async function updateToastStatusAction(formData: FormData) { "use server" const toastId = formData.get("toastId")?.toString() const status = toastStatusSchema.safeParse(formData.get("status")) if (!toastId || !status.success) return await updateToastRequestStatus(toastId, status.data) } export default async function GuestDetailPage({ params, searchParams, }: { params: Promise<{ id: string }> searchParams: Promise<{ created?: string }> }) { const [{ id }, { created }] = await Promise.all([params, searchParams]) const [detail, interactions, photoUpload] = await Promise.all([ getInvitationDetail(id), getInvitationInteractions(id), getPhotoUploadByInvitationId(id), ]) if (!detail) notFound() const inviteUrl = getInviteUrl(detail.invitation.token) const photoUrl = photoUpload ? await createPresignedDownloadUrl(photoUpload.object_key) : null return (
Invitation details and member list.
{inviteUrl}
Submitted
{formatDate(detail.response.submitted_at)}
Needs transport help
{formatBoolean(detail.response.needs_transport_help)}
Arrival date
{formatDateOnly(detail.response.arrival_date)}
Departure date
{formatDateOnly(detail.response.departure_date)}
Staying at
{detail.response.stay_location ?? "—"}
Message to the couple
{detail.response.message_to_couple ?? "—"}
No RSVP response submitted yet.
)}{detail.invitation.notes ?? "No notes yet."}
Filename
{photoUpload.original_filename ?? "—"}
Content type
{photoUpload.content_type ?? "—"}
Size
{photoUpload.size_bytes ? `${(photoUpload.size_bytes / 1024 / 1024).toFixed(2)} MB` : "—"}
No photo uploaded yet.
)}{toast.speaker_name}
Estimated time: {toast.estimated_minutes ?? "—"} minutes
No toast requests submitted yet.
)}