From de40c78f47815725dc54a1e3b79e029b3f12dd68 Mon Sep 17 00:00:00 2001 From: Brian Nelson Date: Sat, 5 Jul 2025 20:29:09 -0400 Subject: [PATCH] event rsvp popup --- app/(auth)/events/[eventId]/page.tsx | 16 ++- app/layout.tsx | 2 + components/events/EventDashboard.tsx | 1 - components/events/EventRsvpModal.tsx | 97 ++++++++++++++++ components/events/EventRsvpTracking.tsx | 26 ++++- components/ui/dialog.tsx | 143 ++++++++++++++++++++++++ types.d.ts | 11 ++ 7 files changed, 282 insertions(+), 14 deletions(-) create mode 100644 components/events/EventRsvpModal.tsx create mode 100644 components/ui/dialog.tsx diff --git a/app/(auth)/events/[eventId]/page.tsx b/app/(auth)/events/[eventId]/page.tsx index c6d2f28..b7eaeba 100644 --- a/app/(auth)/events/[eventId]/page.tsx +++ b/app/(auth)/events/[eventId]/page.tsx @@ -6,19 +6,17 @@ export default async function SingleEventPage({ params }: { params: { eventId: s const data = await queries.singleEvent(params.eventId) return ( - <> - - {/*
+
{data ? ( - // @ts-ignore - + ) : (

Event not found.

)} -
*/} - +
) } diff --git a/app/layout.tsx b/app/layout.tsx index 9aac048..639ad46 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import "./globals.css"; +import { Toaster } from "@/components/ui/sonner"; export const metadata: Metadata = { title: "Wedding Planner", @@ -18,6 +19,7 @@ export default async function RootLayout({ className="bg-brand-background text-brand-text" > {children} + ); diff --git a/components/events/EventDashboard.tsx b/components/events/EventDashboard.tsx index 08ba43d..7697779 100644 --- a/components/events/EventDashboard.tsx +++ b/components/events/EventDashboard.tsx @@ -3,7 +3,6 @@ import React, { useState } from 'react' import EventInfo from './EventInfo' import EventRsvpTracking from './EventRsvpTracking' -import EventToDoList from './EventToDoList' import ToDoList from '../ToDoList' import { fetchEventTodos } from '@/lib/helper/fetchTodos' diff --git a/components/events/EventRsvpModal.tsx b/components/events/EventRsvpModal.tsx new file mode 100644 index 0000000..3d8a5fe --- /dev/null +++ b/components/events/EventRsvpModal.tsx @@ -0,0 +1,97 @@ +'use client' +import React from 'react' +import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog' +import { toast } from 'sonner' +import { Button } from '../ui/button' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select' + +interface GuestBookEntry { + id: string + fName: string + lName: string +} + +interface EventGuest { + id: string + rsvp: 'YES' | 'NO' | 'PENDING' + eventId: string + guestBookEntryId: string + guestBookEntry: GuestBookEntry +} + +interface Props { + eventGuests: EventGuest[] +} + +export default function EventRsvpModal({ eventGuests }: Props) { + + const handleRsvpChange = async ( + guest: EventGuest, + rsvp: 'YES' | 'NO' | 'PENDING' + ) => { + try { + const res = await fetch( + `/api/events/${guest.eventId}/guests/${guest.guestBookEntryId}/rsvp`, + { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ rsvp }) + } + ) + + if (!res.ok) throw new Error('Failed to update RSVP') + toast.success(`RSVP updated for ${guest.guestBookEntry.fName} ${guest.guestBookEntry.lName}`) + } catch (err) { + console.error('RSVP update error:', err) + toast.error('Failed to update RSVP') + } + } + + + return ( + + + + + + + Manage Guest RSVPs + + Update RSVP statuses for each guest attending this event. + + + +
+ {eventGuests.length && eventGuests.map(guest => ( +
+
+

{guest.guestBookEntry.fName + " " + guest.guestBookEntry.lName}

+
+ +
+ ))} +
+ + + + + + +
+
+ ) +} diff --git a/components/events/EventRsvpTracking.tsx b/components/events/EventRsvpTracking.tsx index 2ad71a0..824a6a6 100644 --- a/components/events/EventRsvpTracking.tsx +++ b/components/events/EventRsvpTracking.tsx @@ -1,12 +1,30 @@ -'use client' import React from 'react' import { Card, CardContent } from '../ui/card' -import { Button } from '../ui/button' +import EventRsvpModal from './EventRsvpModal' -export default function EventRsvpTracking({ eventGuests }: EventData) { +interface GuestBookEntry { + id: string + fName: string + lName: string +} + +interface EventGuest { + id: string + rsvp: 'YES' | 'NO' | 'PENDING' + eventId: string + guestBookEntryId: string + guestBookEntry: GuestBookEntry +} + +interface Props { + eventGuests: EventGuest[] +} + +export default function EventRsvpTracking({ eventGuests }: Props) { const attendingGuests = eventGuests.filter((g) => g.rsvp === 'YES'); const notAttendingGuests = eventGuests.filter((g) => g.rsvp === 'NO'); const pendingGuests = eventGuests.filter((g) => g.rsvp === 'PENDING'); + return ( @@ -29,7 +47,7 @@ export default function EventRsvpTracking({ eventGuests }: EventData) {

{pendingGuests.length}

- +
) diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000..d9ccec9 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,143 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/types.d.ts b/types.d.ts index 0fac8da..ea2e4bf 100644 --- a/types.d.ts +++ b/types.d.ts @@ -53,3 +53,14 @@ interface EventData { eventGuests: any[] todos: Todo[] } + +interface EventGuest { + id: string + guestId: string + rsvp: 'YES' | 'NO' | 'PENDING' + guest: { + fName: string + lName: string + email?: string | null + } +} \ No newline at end of file