feat: implement QR code check-in system for event attendees

- Add UserQRCode component to display unique QR code on user profile
- Add QRCodeScanner component for mobile camera-based scanning
- Create EventCheckInPage for organizers to scan attendee QR codes
- Add backend endpoint POST /api/events/:id/checkin for check-in
- Add check-in button to EventDetailModal for organizers
- Install qrcode.react and html5-qrcode dependencies
- Implement real-time attendee list updates
- Add organizer permission validation
- Prevent duplicate check-ins
- Mobile-optimized with back camera support
This commit is contained in:
2025-11-20 18:52:04 +00:00
parent cbafe85694
commit e793a8e3ea
13 changed files with 870 additions and 235 deletions

View File

@@ -536,4 +536,81 @@ export const eventController = {
});
}
},
// QR Code Check-in
async checkInAttendee(req: AuthRequest, res: Response) {
try {
const organizerClerkId = req.userId;
if (!organizerClerkId) {
return res.status(401).json({
code: 'UNAUTHORIZED',
message: 'User not authenticated',
timestamp: new Date().toISOString(),
});
}
const organizerId = await findOrCreateUser(organizerClerkId);
const { id } = req.params;
const { clerkUserId } = req.body;
if (!clerkUserId) {
return res.status(400).json({
code: 'VALIDATION_ERROR',
message: 'clerkUserId is required',
timestamp: new Date().toISOString(),
});
}
// Verify event exists and user is organizer
const event = await eventService.getEventById(id);
if (!event) {
return res.status(404).json({
code: 'EVENT_NOT_FOUND',
message: 'Event not found',
timestamp: new Date().toISOString(),
});
}
if (event.organizerId !== organizerId) {
return res.status(403).json({
code: 'FORBIDDEN',
message: 'Only the event organizer can check in attendees',
timestamp: new Date().toISOString(),
});
}
// Find or create the attendee user
const attendeeUserId = await findOrCreateUser(clerkUserId);
// Check in the attendee
const result = await eventService.checkInAttendee(id, attendeeUserId);
res.json(result);
} catch (error: any) {
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')) {
return res.status(409).json({
code: 'ALREADY_CHECKED_IN',
message: error.message,
timestamp: new Date().toISOString(),
});
}
res.status(500).json({
code: 'CHECKIN_ERROR',
message: 'Failed to check in attendee',
details: error.message,
timestamp: new Date().toISOString(),
});
}
},
};