-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add registration UI #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds comprehensive registration UI functionality, enabling users to view, create, and manage their event registrations. The implementation spans both frontend and backend, introducing new API endpoints for fetching user registrations and displaying them in a dedicated user dashboard.
Key changes include:
- New
/meroute displaying user's event registrations with status badges - Registration creation flow with form submission on event detail pages
- Backend endpoints for fetching user registrations and individual registration status
- Schema updates to include event metadata in registration responses
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/web/src/routes/me/index.tsx | Implements user dashboard showing all registrations with status indicators |
| apps/web/src/routes/events/$eventId/index.tsx | Adds registration status display and disables registration button for already-registered users |
| apps/web/src/lib/hooks/use-create-registration.tsx | New hook for handling registration creation with query invalidation |
| apps/web/src/lib/data/users.ts | Adds server function to fetch all registrations for authenticated user |
| apps/web/src/lib/data/registration.ts | Implements registration fetch and creation server functions |
| apps/web/src/lib/data/event.ts | Minor whitespace formatting improvements for consistency |
| apps/web/src/components/ui/dialog.tsx | Removes unused DialogClose export and adjusts overlay opacity |
| apps/web/src/components/registration-status-badge.tsx | New badge component for displaying registration status with color coding |
| apps/web/src/components/registration-card.tsx | Card component for displaying registration summary on user dashboard |
| apps/web/src/components/registration-block.tsx | Block component showing registration details on event pages |
| apps/web/src/components/controlls/register-event-button.tsx | Updates registration button to handle form submission and disabled state |
| apps/shared/src/registrations/schemas.ts | Extends registration schema with event metadata fields |
| apps/api/src/modules/users/store.ts | Adds query to fetch user registrations with joined event data |
| apps/api/src/modules/users/service.ts | Exposes getUserRegistrations service method |
| apps/api/src/modules/users/route.ts | Implements GET /registrations endpoint for authenticated users |
| apps/api/src/modules/users/route.test.ts | Adds comprehensive test coverage for user registrations endpoint |
| apps/api/src/modules/registration/store.ts | Refactors to use shared selection object and adds filtering/pagination support |
| apps/api/src/modules/registration/service.ts | Implements getRegistrationByUser and getRegistrations service methods |
| apps/api/src/modules/registration/schema.ts | Adds query filter schema for paginated registration retrieval |
| apps/api/src/modules/registration/route.ts | Implements GET / and GET /me endpoints for fetching registrations |
| apps/api/src/modules/registration/route.test.ts | Adds test coverage for new registration query endpoints |
Comments suppressed due to low confidence (1)
apps/shared/src/registrations/schemas.ts:22
- The RegistrationResponseSchema is missing the userId field, but the API returns it (as seen in the registrationSelection object in the store). This will cause schema validation to fail when parsing API responses. Add userId: z.string() to the schema.
export const RegistrationResponseSchema = RegistrationContractSchema.extend({
eventId: z.string(),
eventTitle: z.string().optional(),
eventDate: z.iso.datetime(),
eventLocation: z.string().nullable(),
status: z.enum(RegistrationStatus),
createdAt: z.iso.datetime(),
updatedAt: z.iso.datetime(),
});
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| server.get("/", async (request, reply) => { | ||
| const { sessionClaims } = getAuth(request); | ||
| const role = sessionClaims?.metadata?.role; | ||
|
|
||
| const params = RegistrationEventIdSchema.parse(request.params); | ||
| const filters = RegistrationsQueryFilterSchema.parse(request.query); | ||
|
|
||
| const events = await registrationService.getRegistrations({ | ||
| db: server.db, | ||
| filters: { | ||
| id: params.eventId, | ||
| ...filters, | ||
| }, | ||
| role: role ?? null, | ||
| }); | ||
|
|
||
| return reply.status(200).send(events); | ||
| }); |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test coverage for the authorization check when a non-committee user tries to access GET /v1/events/:eventId/registrations. This should return 401 Unauthorized but there's no test verifying this behavior.
| } catch (err) { | ||
| console.error(err) | ||
|
|
||
| throw new Error('Failed to load an event') |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "Failed to load an event" is incorrect for a registration creation operation. This should say "Failed to create registration" or similar.
| throw new Error('Failed to load an event') | |
| throw new Error('Failed to create registration') |
| export const RegistrationResponseSchema = RegistrationContractSchema.extend({ | ||
| id: z.string(), | ||
| eventId: z.string(), | ||
| eventTitle: z.string().optional(), | ||
| eventDate: z.iso.datetime(), | ||
| eventLocation: z.string().nullable(), | ||
| status: z.enum(RegistrationStatus), | ||
| createdAt: z.iso.datetime(), | ||
| updatedAt: z.iso.datetime(), |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The RegistrationResponseSchema is missing the 'id' field that was removed. Registration records should have a unique identifier. If the composite key (userId, eventId) is intended as the identifier, this should be documented, but typically response schemas include an explicit id field for consistency.
| }) | ||
| void queryClient.invalidateQueries({ | ||
| queryKey: ['registrations'], | ||
| }) |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After creating a registration, only queries with the key ['registrations'] are invalidated, but the specific registration query uses ['registrations', eventId]. Consider also invalidating the user registrations query with key ['users', 'registrations'] to ensure the /me page updates correctly after registration.
| }) | |
| }) | |
| void queryClient.invalidateQueries({ | |
| queryKey: ['registrations', eventId], | |
| }) | |
| void queryClient.invalidateQueries({ | |
| queryKey: ['users', 'registrations'], | |
| }) |
| description: 'Event has been created', | ||
| }) | ||
| void queryClient.invalidateQueries({ | ||
| queryKey: ['registrations'], | ||
| }) | ||
| }, | ||
| onError: (error) => { | ||
| toast.error('Failed to create event', { |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "Failed to create event" is incorrect for a registration operation. This should say "Failed to create registration" or "Failed to register for event".
| description: 'Event has been created', | |
| }) | |
| void queryClient.invalidateQueries({ | |
| queryKey: ['registrations'], | |
| }) | |
| }, | |
| onError: (error) => { | |
| toast.error('Failed to create event', { | |
| description: 'Registration has been created', | |
| }) | |
| void queryClient.invalidateQueries({ | |
| queryKey: ['registrations'], | |
| }) | |
| }, | |
| onError: (error) => { | |
| toast.error('Failed to create registration', { |
| } catch (err) { | ||
| console.error(err) | ||
|
|
||
| throw new Error('Failed to load an event') |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "Failed to load an event" is incorrect for a registration operation. This should say "Failed to load registration" or "Failed to fetch registration".
| .select({ | ||
| userId: registrationsTable.userId, | ||
| eventId: registrationsTable.eventId, | ||
| status: registrationsTable.status, | ||
| createdAt: registrationsTable.createdAt, | ||
| updatedAt: registrationsTable.updatedAt, | ||
| eventTitle: eventsTable.title, | ||
| eventDate: eventsTable.date, | ||
| eventLocation: eventsTable.location, | ||
| }) |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The selection object for user registrations is duplicated from the registrationSelection object defined in registration/store.ts. Consider importing and reusing the registrationSelection to maintain consistency and avoid duplication.
| server.get("/me", async (request, reply) => { | ||
| const { userId, sessionClaims } = getAuth(request); | ||
| const role = sessionClaims?.metadata?.role; | ||
|
|
||
| if (!userId || !role) { | ||
| return reply.status(401).send({ message: "Unauthorised" }); | ||
| } | ||
|
|
||
| const data = RegistrationEventIdSchema.parse(request.params); | ||
| const registration = await registrationService.getRegistrationByUser({ | ||
| db: server.db, | ||
| data: { | ||
| ...data, | ||
| userId, | ||
| }, | ||
| }); | ||
|
|
||
| return registration ? reply.status(200).send(registration) : reply.status(204).send(); | ||
| }); |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test coverage for the case where a user fetches their own registration but no registration exists (should return 204). Consider adding a test case to verify this behavior.
| export const registrationSelection = { | ||
| userId: registrationsTable.userId, | ||
| eventId: registrationsTable.eventId, | ||
| status: registrationsTable.status, | ||
| createdAt: registrationsTable.createdAt, | ||
| updatedAt: registrationsTable.updatedAt, | ||
| eventTitle: eventsTable.title, | ||
| eventDate: eventsTable.date, | ||
| eventLocation: eventsTable.location, | ||
| }; |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The registrationSelection object is missing the 'answers' field from RegistrationContractSchema. Registration answers are not being returned in API responses, which means users won't be able to see their submitted form answers. Add answers: registrationsTable.answers to the selection object.
|
|
||
| const sizeClasses = { | ||
| sm: 'text-[10px] px-2 py-0.5 rounded-sm uppercase tracking-wider', | ||
| md: 'text-sm md:text-base px-6.5 py-2 rounded-md', |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The className 'px-6.5' is not a valid Tailwind CSS class. Standard Tailwind spacing uses integers or fractions (e.g., px-6, px-7, or px-6.5 would need to be defined in the Tailwind config). Consider using px-6 or px-7 instead.
| md: 'text-sm md:text-base px-6.5 py-2 rounded-md', | |
| md: 'text-sm md:text-base px-6 py-2 rounded-md', |
No description provided.