From 047f61b6272775dbe8af21cdb45451d9d98c7653 Mon Sep 17 00:00:00 2001 From: joshua Date: Tue, 28 Jul 2026 14:21:47 +0200 Subject: [PATCH] Fix self-service kiosk: hide closed events and autofill known visitors - GET /api/events/all gains an opt-in excludeClosed=true param, used only by the kiosk, so closed events no longer show as selectable there while other admin/supervisor screens that still need to see closed events are unaffected. - GET /api/users/check-exists now also returns the matched account's name, email, phone, and notification preference (safe fields only). The kiosk's existing debounced lookup uses this to autofill whichever fields are still blank when a visitor enters an email or phone that matches an existing account, without overwriting anything already typed. --- backend/src/controllers/eventController.js | 2 ++ backend/src/controllers/userController.js | 17 ++++++++++++--- frontend/src/app/self-service/page.tsx | 25 ++++++++++++++++------ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/backend/src/controllers/eventController.js b/backend/src/controllers/eventController.js index b6ed3ce..42eff26 100644 --- a/backend/src/controllers/eventController.js +++ b/backend/src/controllers/eventController.js @@ -241,10 +241,12 @@ const getEventsAll = async (req, res) => { try { const includePast = req.query.includePast === 'true'; const includeInactive = req.query.includeInactive === 'true'; + const excludeClosed = req.query.excludeClosed === 'true'; const where = {}; if (!includeInactive) where.isActive = true; if (!includePast) where.endDate = { gte: new Date() }; + if (excludeClosed) where.cashupStatus = { not: 'closed' }; const canIncludeTiers = !!(prisma && prisma.earlyBirdTier && typeof prisma.earlyBirdTier.findMany === 'function'); const canIncludeVariants = !!(prisma && prisma.optionVariant && typeof prisma.optionVariant.findMany === 'function'); diff --git a/backend/src/controllers/userController.js b/backend/src/controllers/userController.js index a4dc3e3..9e96e7d 100644 --- a/backend/src/controllers/userController.js +++ b/backend/src/controllers/userController.js @@ -482,13 +482,24 @@ const checkUserExists = async (req, res) => { const existingUser = await prisma.user.findFirst({ where: { OR: searchClauses }, - select: { email: true, phoneNumber: true }, + select: { name: true, email: true, phoneNumber: true, notificationPreference: true }, }); + const hasEmail = !!existingUser?.email && !existingUser.email.endsWith('@guest.local'); + const hasPhone = !!existingUser?.phoneNumber; + res.json({ exists: !!existingUser, - hasEmail: !!existingUser?.email && !existingUser.email.endsWith('@guest.local'), - hasPhone: !!existingUser?.phoneNumber, + hasEmail, + hasPhone, + // Safe-to-display fields only, for autofilling a lookup form — never the password. + // Guest placeholder emails are withheld the same way hasEmail already treats them. + user: existingUser ? { + name: existingUser.name, + email: hasEmail ? existingUser.email : null, + phoneNumber: hasPhone ? existingUser.phoneNumber : null, + notificationPreference: existingUser.notificationPreference, + } : null, }); } catch (error) { res.status(res.statusCode === 200 ? 400 : res.statusCode).json({ message: safeErrorMessage(error) }); diff --git a/frontend/src/app/self-service/page.tsx b/frontend/src/app/self-service/page.tsx index d56e5cd..32a5a27 100644 --- a/frontend/src/app/self-service/page.tsx +++ b/frontend/src/app/self-service/page.tsx @@ -180,7 +180,7 @@ export default function SelfServicePage() { setEventsLoading(true); try { const data: KioskEvent[] = await apiFetch( - "/api/events/all?includePast=false&includeInactive=false", + "/api/events/all?includePast=false&includeInactive=false&excludeClosed=true", { authToken: token } ); setEvents(data); @@ -431,7 +431,11 @@ export default function SelfServicePage() { }; }, []); - // ─── Check whether an account already exists for the entered email/phone ── + // ─── Check whether an account already exists for the entered email/phone, and + // autofill the rest of the visitor's details from it (name, the other contact + // channel, notification preference) so staff don't have to re-ask for info + // already on file. Only fills fields that are still blank — never overwrites + // whatever the operator is actively typing. ── useEffect(() => { const email = visitorEmail.trim(); const phone = visitorPhone.trim(); @@ -445,11 +449,18 @@ export default function SelfServicePage() { const params = new URLSearchParams(); if (email) params.set("email", email); if (phone) params.set("phone", phone); - const data = await apiFetch<{ exists: boolean }>( - `/api/users/check-exists?${params.toString()}`, - { authToken: supervisorToken } - ); + const data = await apiFetch<{ + exists: boolean; + user?: { name: string; email: string | null; phoneNumber: string | null; notificationPreference: "email" | "whatsapp" | "both" } | null; + }>(`/api/users/check-exists?${params.toString()}`, { authToken: supervisorToken }); setAccountExists(!!data?.exists); + if (data?.user) { + const found = data.user; + setVisitorName((prev) => (prev.trim() ? prev : found.name)); + if (found.email && !email) setVisitorEmail(found.email); + if (found.phoneNumber && !phone) setVisitorPhone(found.phoneNumber); + setNotificationPref(found.notificationPreference); + } } catch { setAccountExists(false); } finally { @@ -795,7 +806,7 @@ export default function SelfServicePage() { {/* Account creation toggle — hidden once we know an account already exists */} {accountExists ? (
- An account with this email/number already exists — you'll be registered using that account. + An account with this email/number already exists — we've filled in their details below, and you'll be registered using that account.
) : (