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:
@@ -241,10 +241,12 @@ const getEventsAll = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const includePast = req.query.includePast === 'true';
|
const includePast = req.query.includePast === 'true';
|
||||||
const includeInactive = req.query.includeInactive === 'true';
|
const includeInactive = req.query.includeInactive === 'true';
|
||||||
|
const excludeClosed = req.query.excludeClosed === 'true';
|
||||||
|
|
||||||
const where = {};
|
const where = {};
|
||||||
if (!includeInactive) where.isActive = true;
|
if (!includeInactive) where.isActive = true;
|
||||||
if (!includePast) where.endDate = { gte: new Date() };
|
if (!includePast) where.endDate = { gte: new Date() };
|
||||||
|
if (excludeClosed) where.cashupStatus = { not: 'closed' };
|
||||||
|
|
||||||
const canIncludeTiers = !!(prisma && prisma.earlyBirdTier && typeof prisma.earlyBirdTier.findMany === 'function');
|
const canIncludeTiers = !!(prisma && prisma.earlyBirdTier && typeof prisma.earlyBirdTier.findMany === 'function');
|
||||||
const canIncludeVariants = !!(prisma && prisma.optionVariant && typeof prisma.optionVariant.findMany === 'function');
|
const canIncludeVariants = !!(prisma && prisma.optionVariant && typeof prisma.optionVariant.findMany === 'function');
|
||||||
|
|||||||
@@ -482,13 +482,24 @@ const checkUserExists = async (req, res) => {
|
|||||||
|
|
||||||
const existingUser = await prisma.user.findFirst({
|
const existingUser = await prisma.user.findFirst({
|
||||||
where: { OR: searchClauses },
|
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({
|
res.json({
|
||||||
exists: !!existingUser,
|
exists: !!existingUser,
|
||||||
hasEmail: !!existingUser?.email && !existingUser.email.endsWith('@guest.local'),
|
hasEmail,
|
||||||
hasPhone: !!existingUser?.phoneNumber,
|
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) {
|
} catch (error) {
|
||||||
res.status(res.statusCode === 200 ? 400 : res.statusCode).json({ message: safeErrorMessage(error) });
|
res.status(res.statusCode === 200 ? 400 : res.statusCode).json({ message: safeErrorMessage(error) });
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export default function SelfServicePage() {
|
|||||||
setEventsLoading(true);
|
setEventsLoading(true);
|
||||||
try {
|
try {
|
||||||
const data: KioskEvent[] = await apiFetch(
|
const data: KioskEvent[] = await apiFetch(
|
||||||
"/api/events/all?includePast=false&includeInactive=false",
|
"/api/events/all?includePast=false&includeInactive=false&excludeClosed=true",
|
||||||
{ authToken: token }
|
{ authToken: token }
|
||||||
);
|
);
|
||||||
setEvents(data);
|
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(() => {
|
useEffect(() => {
|
||||||
const email = visitorEmail.trim();
|
const email = visitorEmail.trim();
|
||||||
const phone = visitorPhone.trim();
|
const phone = visitorPhone.trim();
|
||||||
@@ -445,11 +449,18 @@ export default function SelfServicePage() {
|
|||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (email) params.set("email", email);
|
if (email) params.set("email", email);
|
||||||
if (phone) params.set("phone", phone);
|
if (phone) params.set("phone", phone);
|
||||||
const data = await apiFetch<{ exists: boolean }>(
|
const data = await apiFetch<{
|
||||||
`/api/users/check-exists?${params.toString()}`,
|
exists: boolean;
|
||||||
{ authToken: supervisorToken }
|
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);
|
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 {
|
} catch {
|
||||||
setAccountExists(false);
|
setAccountExists(false);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -795,7 +806,7 @@ export default function SelfServicePage() {
|
|||||||
{/* Account creation toggle — hidden once we know an account already exists */}
|
{/* Account creation toggle — hidden once we know an account already exists */}
|
||||||
{accountExists ? (
|
{accountExists ? (
|
||||||
<div className="border border-green-200 bg-green-50 rounded-xl px-4 py-3 text-sm text-green-700">
|
<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'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.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="border border-gray-200 rounded-xl px-4 py-4">
|
<div className="border border-gray-200 rounded-xl px-4 py-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user