feat: auto-register walk-in attendees during check-in
- Automatically register users who scan QR code but aren't pre-registered - Mark walk-in registrations with wasWalkIn flag - Show different success messages for walk-ins vs pre-registered attendees - Check in walk-ins immediately upon registration - Remove 'not registered' error - now handles walk-ins gracefully - Update both QR scan and manual check-in to support walk-ins
This commit is contained in:
@@ -582,21 +582,13 @@ export const eventController = {
|
|||||||
// Find or create the attendee user
|
// Find or create the attendee user
|
||||||
const attendeeUserId = await findOrCreateUser(clerkUserId);
|
const attendeeUserId = await findOrCreateUser(clerkUserId);
|
||||||
|
|
||||||
// Check in the attendee
|
// Check in the attendee (will auto-register if not registered)
|
||||||
const result = await eventService.checkInAttendee(id, attendeeUserId);
|
const result = await eventService.checkInAttendee(id, attendeeUserId);
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error checking in attendee:', error);
|
console.error('Error checking in attendee:', error);
|
||||||
|
|
||||||
if (error.message.includes('not registered')) {
|
|
||||||
return res.status(404).json({
|
|
||||||
code: 'NOT_REGISTERED',
|
|
||||||
message: error.message,
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.message.includes('already checked in')) {
|
if (error.message.includes('already checked in')) {
|
||||||
return res.status(409).json({
|
return res.status(409).json({
|
||||||
code: 'ALREADY_CHECKED_IN',
|
code: 'ALREADY_CHECKED_IN',
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ export const eventService = {
|
|||||||
|
|
||||||
async checkInAttendee(eventId: string, userId: string) {
|
async checkInAttendee(eventId: string, userId: string) {
|
||||||
// Check if user is registered
|
// Check if user is registered
|
||||||
const registration = await prisma.eventRegistration.findUnique({
|
let registration = await prisma.eventRegistration.findUnique({
|
||||||
where: {
|
where: {
|
||||||
userId_eventId: {
|
userId_eventId: {
|
||||||
userId,
|
userId,
|
||||||
@@ -515,15 +515,49 @@ export const eventService = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If not registered, register them now (walk-in)
|
||||||
if (!registration) {
|
if (!registration) {
|
||||||
throw new Error('User is not registered for this event');
|
registration = await prisma.eventRegistration.create({
|
||||||
|
data: {
|
||||||
|
userId,
|
||||||
|
eventId,
|
||||||
|
attended: true, // Check them in immediately
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
username: registration.user.username,
|
||||||
|
firstName: registration.user.firstName,
|
||||||
|
lastName: registration.user.lastName,
|
||||||
|
wasWalkIn: true, // Flag to indicate this was a walk-in registration
|
||||||
|
attendee: {
|
||||||
|
id: registration.id,
|
||||||
|
userId: registration.userId,
|
||||||
|
eventId: registration.eventId,
|
||||||
|
registeredAt: registration.registeredAt || new Date(),
|
||||||
|
attended: registration.attended || false,
|
||||||
|
user: registration.user,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registration.attended) {
|
if (registration.attended) {
|
||||||
throw new Error('User is already checked in');
|
throw new Error('User is already checked in');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update attendance
|
// Update attendance for pre-registered user
|
||||||
const updated = await prisma.eventRegistration.update({
|
const updated = await prisma.eventRegistration.update({
|
||||||
where: {
|
where: {
|
||||||
userId_eventId: {
|
userId_eventId: {
|
||||||
@@ -552,6 +586,7 @@ export const eventService = {
|
|||||||
username: updated.user.username,
|
username: updated.user.username,
|
||||||
firstName: updated.user.firstName,
|
firstName: updated.user.firstName,
|
||||||
lastName: updated.user.lastName,
|
lastName: updated.user.lastName,
|
||||||
|
wasWalkIn: false,
|
||||||
attendee: {
|
attendee: {
|
||||||
id: updated.id,
|
id: updated.id,
|
||||||
userId: updated.userId,
|
userId: updated.userId,
|
||||||
|
|||||||
@@ -55,7 +55,13 @@ export function EventCheckInPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Send the Clerk ID to backend and let it handle the lookup
|
// Send the Clerk ID to backend and let it handle the lookup
|
||||||
const response = await apiClient.post<{ success: boolean; username?: string; firstName?: string; lastName?: string }>(
|
const response = await apiClient.post<{
|
||||||
|
success: boolean;
|
||||||
|
username?: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
wasWalkIn?: boolean;
|
||||||
|
}>(
|
||||||
`/events/${eventId}/checkin`,
|
`/events/${eventId}/checkin`,
|
||||||
{ clerkUserId }
|
{ clerkUserId }
|
||||||
);
|
);
|
||||||
@@ -64,7 +70,11 @@ export function EventCheckInPage() {
|
|||||||
? `${response.firstName} ${response.lastName}`
|
? `${response.firstName} ${response.lastName}`
|
||||||
: response.username || 'user';
|
: response.username || 'user';
|
||||||
|
|
||||||
setSuccessMessage(`Successfully checked in ${displayName}`);
|
if (response.wasWalkIn) {
|
||||||
|
setSuccessMessage(`✨ ${displayName} registered and checked in as walk-in!`);
|
||||||
|
} else {
|
||||||
|
setSuccessMessage(`✓ Successfully checked in ${displayName}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Reload attendees to get updated status
|
// Reload attendees to get updated status
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -255,7 +265,13 @@ export function EventCheckInPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.post<{ success: boolean; username?: string; firstName?: string; lastName?: string }>(
|
const response = await apiClient.post<{
|
||||||
|
success: boolean;
|
||||||
|
username?: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
wasWalkIn?: boolean;
|
||||||
|
}>(
|
||||||
`/events/${eventId}/checkin`,
|
`/events/${eventId}/checkin`,
|
||||||
{ clerkUserId: attendee.user.clerkId }
|
{ clerkUserId: attendee.user.clerkId }
|
||||||
);
|
);
|
||||||
@@ -264,7 +280,11 @@ export function EventCheckInPage() {
|
|||||||
? `${response.firstName} ${response.lastName}`
|
? `${response.firstName} ${response.lastName}`
|
||||||
: response.username || attendee.user.username;
|
: response.username || attendee.user.username;
|
||||||
|
|
||||||
setSuccessMessage(`Successfully checked in ${displayName}`);
|
if (response.wasWalkIn) {
|
||||||
|
setSuccessMessage(`✨ ${displayName} registered and checked in as walk-in!`);
|
||||||
|
} else {
|
||||||
|
setSuccessMessage(`✓ Successfully checked in ${displayName}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Reload attendees to get updated status
|
// Reload attendees to get updated status
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -98,7 +98,9 @@ export function UserProfilePage() {
|
|||||||
areasOfInterest: profileData.areasOfInterest || [],
|
areasOfInterest: profileData.areasOfInterest || [],
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load profile');
|
console.error('Error loading profile data:', err);
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Failed to load profile';
|
||||||
|
setError(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user