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.
This commit is contained in:
2026-07-28 14:21:47 +02:00
parent 1815f78c85
commit 047f61b627
3 changed files with 34 additions and 10 deletions
@@ -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');
+14 -3
View File
@@ -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) });
+18 -7
View File
@@ -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 ? (
<div className="border border-green-200 bg-green-50 rounded-xl px-4 py-3 text-sm text-green-700">
An account with this email/number already exists you&apos;ll be registered using that account.
An account with this email/number already exists we&apos;ve filled in their details below, and you&apos;ll be registered using that account.
</div>
) : (
<div className="border border-gray-200 rounded-xl px-4 py-4">