From d67a10adbb0eca0e079fa10f9353b243625a0fb4 Mon Sep 17 00:00:00 2001 From: marker-sss Date: Mon, 16 Feb 2026 20:18:59 -0500 Subject: [PATCH 1/5] slots remaining logic implemented --- src/actions/attend-event.ts | 33 ++++++++++++++++++++++++++++++++- src/actions/leave-event.ts | 21 ++++++++++++++++++++- src/app/HomePageClient.tsx | 2 ++ src/app/VolunteerEventCard.tsx | 17 +++++++++++++++-- src/app/api/events/route.ts | 2 ++ src/db/schema/events.ts | 1 + 6 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/actions/attend-event.ts b/src/actions/attend-event.ts index 4111ec3..fa55339 100644 --- a/src/actions/attend-event.ts +++ b/src/actions/attend-event.ts @@ -1,7 +1,9 @@ "use server"; +import { count, eq } from "drizzle-orm"; + import db from "@/db"; -import { eventAttendees } from "@/db/schema"; +import { eventAttendees, events } from "@/db/schema"; import { auth } from "@/lib/auth"; export async function attendEvent(eventId: string): Promise { @@ -10,6 +12,35 @@ export async function attendEvent(eventId: string): Promise { if (!session?.user?.id) { throw new Error("Unauthorized"); } + + const event = await db + .select() + .from(events) + .where(eq(events.id, eventId)) + .then((res) => res[0]); + + if (!event) { + throw new Error("Event not found"); + } + + const capacity = event.capacity ?? 0; + + const attendeeCount = await db + .select({ count: count() }) + .from(eventAttendees) + .where(eq(eventAttendees.eventId, eventId)); + + if (attendeeCount[0].count >= capacity) { + throw new Error("Event capacity reached"); + } + + const newCount = attendeeCount[0].count + 1; + + await db + .update(events) + .set({ registeredUsers: newCount }) + .where(eq(events.id, eventId)); + await db .insert(eventAttendees) .values({ diff --git a/src/actions/leave-event.ts b/src/actions/leave-event.ts index 3186db2..d20056b 100644 --- a/src/actions/leave-event.ts +++ b/src/actions/leave-event.ts @@ -3,7 +3,7 @@ import { and, eq } from "drizzle-orm"; import db from "@/db"; -import { eventAttendees } from "@/db/schema"; +import { eventAttendees, events } from "@/db/schema"; import { auth } from "@/lib/auth"; export async function leaveEvent(eventId: string): Promise { @@ -13,6 +13,25 @@ export async function leaveEvent(eventId: string): Promise { throw new Error("Unauthorized"); } + const event = await db + .select() + .from(events) + .where(eq(events.id, eventId)) + .then((res) => res[0]); + + if (!event) { + throw new Error("Event not found"); + } + + let newCount = event.registeredUsers ?? 0; + + newCount--; + + await db + .update(events) + .set({ registeredUsers: newCount }) + .where(eq(events.id, eventId)); + // ✅ Remove the attendee row for this user + event await db .delete(eventAttendees) diff --git a/src/app/HomePageClient.tsx b/src/app/HomePageClient.tsx index d9535b0..4bc7d76 100644 --- a/src/app/HomePageClient.tsx +++ b/src/app/HomePageClient.tsx @@ -14,6 +14,7 @@ type HomePageClientProps = { startTime: string; endTime: string; capacity: number | null; + registeredUsers: number | null; streetLine: string; description: string; }[]; @@ -34,6 +35,7 @@ export default function HomePageClient({ startTime={event.startTime} endTime={event.endTime} capacity={event.capacity} + registeredUsers={event.registeredUsers} streetLine={event.streetLine} description={event.description} /> diff --git a/src/app/VolunteerEventCard.tsx b/src/app/VolunteerEventCard.tsx index 6c53984..c5d39db 100644 --- a/src/app/VolunteerEventCard.tsx +++ b/src/app/VolunteerEventCard.tsx @@ -20,6 +20,7 @@ type VolunteerEventCardProps = { startTime: string; endTime: string; capacity: number | null; + registeredUsers: number | null; streetLine: string; description: string; isRegistered?: boolean; @@ -40,6 +41,10 @@ export default function VolunteerEventCard( event.isRegistered ?? false, ); + const [registeredUsers, setRegisteredUsers] = React.useState( + event.registeredUsers ?? 0, + ); + const [isPending, setIsPending] = React.useState(false); return ( @@ -68,12 +73,20 @@ export default function VolunteerEventCard( if (isRegistered) { await leaveEvent(event.id); setIsRegistered(false); + setRegisteredUsers((prev) => Math.max(prev - 1, 0)); } else { await attendEvent(event.id); setIsRegistered(true); + setRegisteredUsers((prev) => prev + 1); } } catch (error) { - console.error(error); + if (error instanceof Error) { + if (error.message === "Event capacity reached") { + alert("Sorry, this event is full! You cannot register."); + } else { + console.error(error.message); + } + } } finally { setIsPending(false); } @@ -101,7 +114,7 @@ export default function VolunteerEventCard( - {event.capacity} slots remaining + {(event.capacity ?? 0) - registeredUsers} slots remaining diff --git a/src/app/api/events/route.ts b/src/app/api/events/route.ts index eb5304e..dab1230 100644 --- a/src/app/api/events/route.ts +++ b/src/app/api/events/route.ts @@ -56,6 +56,7 @@ export async function POST(req: Request): Promise { const firstResult = Array.isArray(result) ? result[0] : undefined; const latitude = firstResult?.lat ?? null; const longitude = firstResult?.lon ?? null; + const registeredUsers = 0; if (endTime <= startTime) { return NextResponse.json( @@ -70,6 +71,7 @@ export async function POST(req: Request): Promise { startTime, endTime, capacity: capacity ?? null, + registeredUsers, streetLine, city, state, diff --git a/src/db/schema/events.ts b/src/db/schema/events.ts index 5437b4d..0642dfa 100644 --- a/src/db/schema/events.ts +++ b/src/db/schema/events.ts @@ -20,6 +20,7 @@ export const events = pgTable("events", { endTime: time("end_time").notNull(), capacity: integer("capacity"), + registeredUsers: integer("registered_users"), streetLine: text("street_line").notNull(), city: text("city").notNull(), From 8d01f9d455e00645bdcb28cde630f6dc5d20b2df Mon Sep 17 00:00:00 2001 From: marker-sss Date: Mon, 16 Feb 2026 20:30:32 -0500 Subject: [PATCH 2/5] changed how registeredUsers is updated in leave event --- src/actions/leave-event.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/actions/leave-event.ts b/src/actions/leave-event.ts index d20056b..5aaf9a0 100644 --- a/src/actions/leave-event.ts +++ b/src/actions/leave-event.ts @@ -1,6 +1,6 @@ "use server"; -import { and, eq } from "drizzle-orm"; +import { and, count, eq } from "drizzle-orm"; import db from "@/db"; import { eventAttendees, events } from "@/db/schema"; @@ -22,10 +22,12 @@ export async function leaveEvent(eventId: string): Promise { if (!event) { throw new Error("Event not found"); } + const attendeeCount = await db + .select({ count: count() }) + .from(eventAttendees) + .where(eq(eventAttendees.eventId, eventId)); - let newCount = event.registeredUsers ?? 0; - - newCount--; + const newCount = attendeeCount[0].count - 1; await db .update(events) From 88a3dcb210e2da7c1c2b089108dabc44481b46d6 Mon Sep 17 00:00:00 2001 From: marker-sss Date: Mon, 16 Feb 2026 20:39:48 -0500 Subject: [PATCH 3/5] off by one error ): --- src/actions/attend-event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/attend-event.ts b/src/actions/attend-event.ts index fa55339..57cccad 100644 --- a/src/actions/attend-event.ts +++ b/src/actions/attend-event.ts @@ -30,7 +30,7 @@ export async function attendEvent(eventId: string): Promise { .from(eventAttendees) .where(eq(eventAttendees.eventId, eventId)); - if (attendeeCount[0].count >= capacity) { + if (attendeeCount[0].count > capacity) { throw new Error("Event capacity reached"); } From ddb3e30495b6a60e51c2b7426dddb50f2b75d454 Mon Sep 17 00:00:00 2001 From: AgustinSV Date: Tue, 24 Feb 2026 14:38:53 -0500 Subject: [PATCH 4/5] pulled from main branch --- src/app/HomePageClient.tsx | 3 + src/app/VolunteerEventCard.tsx | 134 +++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/app/VolunteerEventCard.tsx diff --git a/src/app/HomePageClient.tsx b/src/app/HomePageClient.tsx index bf49175..d7086a1 100644 --- a/src/app/HomePageClient.tsx +++ b/src/app/HomePageClient.tsx @@ -5,6 +5,8 @@ import { Fragment } from "react"; import { DefaultButton } from "@/components/Button/DefaultButton"; +import VolunteerEventCard from "./VolunteerEventCard"; + type HomePageClientProps = { events: { id: string; @@ -76,6 +78,7 @@ export default function HomePageClient({ startTime={event.startTime} endTime={event.endTime} capacity={event.capacity} + registeredUsers={event.registeredUsers} streetLine={event.streetLine} description={event.description} isRegistered={event.isRegistered} diff --git a/src/app/VolunteerEventCard.tsx b/src/app/VolunteerEventCard.tsx new file mode 100644 index 0000000..c5d39db --- /dev/null +++ b/src/app/VolunteerEventCard.tsx @@ -0,0 +1,134 @@ +"use client"; + +import LocationPinIcon from "@mui/icons-material/LocationPin"; +import PersonIcon from "@mui/icons-material/Person"; +import QueryBuilderIcon from "@mui/icons-material/QueryBuilder"; +import { Box, Card, CardContent, Typography } from "@mui/material"; +import { useSession } from "next-auth/react"; +import * as React from "react"; + +import { attendEvent } from "@/actions/attend-event"; +import { leaveEvent } from "@/actions/leave-event"; +import { DefaultButton } from "@/components/Button"; + +import getTimeRange from "./helpers"; + +type VolunteerEventCardProps = { + id: string; + title: string; + eventDate: string; + startTime: string; + endTime: string; + capacity: number | null; + registeredUsers: number | null; + streetLine: string; + description: string; + isRegistered?: boolean; +}; + +export default function VolunteerEventCard( + event: VolunteerEventCardProps, +): React.ReactElement { + const { status } = useSession(); + + const timeRange = getTimeRange( + event.eventDate, + event.startTime, + event.endTime, + ); + + const [isRegistered, setIsRegistered] = React.useState( + event.isRegistered ?? false, + ); + + const [registeredUsers, setRegisteredUsers] = React.useState( + event.registeredUsers ?? 0, + ); + + const [isPending, setIsPending] = React.useState(false); + + return ( + + + {/* Header */} + + {event.title} + + {status === "authenticated" && ( + { + if (isPending) return; + + setIsPending(true); + try { + if (isRegistered) { + await leaveEvent(event.id); + setIsRegistered(false); + setRegisteredUsers((prev) => Math.max(prev - 1, 0)); + } else { + await attendEvent(event.id); + setIsRegistered(true); + setRegisteredUsers((prev) => prev + 1); + } + } catch (error) { + if (error instanceof Error) { + if (error.message === "Event capacity reached") { + alert("Sorry, this event is full! You cannot register."); + } else { + console.error(error.message); + } + } + } finally { + setIsPending(false); + } + }} + bgcolor={isRegistered ? "grey.400" : "primary.main"} + color={isRegistered ? "text.primary" : "white"} + /> + )} + + + {/* Meta info */} + + + + {timeRange} + + + + + + {(event.capacity ?? 0) - registeredUsers} slots remaining + + + + + + {event.streetLine} + + + + {/* Description */} + + {event.description} + + + + ); +} From 101a7768b4237d26d94779c4a967aeb1182bd7ba Mon Sep 17 00:00:00 2001 From: marker-sss Date: Tue, 24 Feb 2026 16:55:00 -0500 Subject: [PATCH 5/5] Capacity = 0 case fixed --- src/actions/attend-event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/attend-event.ts b/src/actions/attend-event.ts index 57cccad..190baa1 100644 --- a/src/actions/attend-event.ts +++ b/src/actions/attend-event.ts @@ -30,7 +30,7 @@ export async function attendEvent(eventId: string): Promise { .from(eventAttendees) .where(eq(eventAttendees.eventId, eventId)); - if (attendeeCount[0].count > capacity) { + if (attendeeCount[0].count >= capacity || capacity == 0) { throw new Error("Event capacity reached"); }