4.1 KiB
4.1 KiB
Unit 5 – Code Generation Summary
What Was Built
Unit 5 implements the User Management feature: listing users, inviting new users, and changing roles.
Backend Changes
Bug Fixes
- UsersController: Fixed invite link URL from
/setup/complete?token=…to/invite/complete?token=…
New Interface Method
IInvitationService.GetPendingInvitationByEmailAsync(string email)— returns(bool IsPending, string? Token)
New Service Implementation
InvitationService.GetPendingInvitationByEmailAsync— queriesInvitationsfor a non-accepted, non-expired record by email
New Models (IdentityRequests.cs)
UserDto— maps a user to the API response includingInvitationPendingandInviteLinkChangeRoleRequest— carriesNewRole
Updated Controller (UsersController)
- Now injects
UserManager<ApplicationUser>alongsideIInvitationService GET /api/v1/Users— returns all users with role, status, and pending invite infoPUT /api/v1/Users/{userId}/role— enforces RBAC rules (Owner constraint, Admin can only promote User→Admin)
Database Migration
- No migration needed — all tables (
Users,Roles,UserRoles,Invitations) already exist
Frontend Changes
New UI Components (frontend/src/components/ui/)
| File | Component |
|---|---|
dialog.tsx |
Dialog, DialogContent, DialogHeader, DialogTitle, etc. |
select.tsx |
Select, SelectTrigger, SelectContent, SelectItem, etc. |
table.tsx |
Table, TableHeader, TableBody, TableRow, TableHead, TableCell |
badge.tsx |
Badge with default / secondary / destructive / outline variants |
New API Hook (useUsers.ts)
useUsers()—GET /api/v1/Users, staleTime 30s, queryKey['users']useInviteUser()—POST /api/v1/Users/invite, invalidates['users']useChangeRole()—PUT /api/v1/Users/{userId}/role, invalidates['users']
Migrated Hook (useInvitation.ts)
- Converted from
useState/useEffectto TanStack Query - Uses
InvitationValidationfromtypes.ts(fieldisValid, not the old localvalid)
Updated Files
| File | Change |
|---|---|
types.ts |
Added UserListItem, InviteUserPayload, InviteUserResponse, ChangeRolePayload |
InviteCompletePage.tsx |
validationQuery.data?.valid → validationQuery.data?.isValid (2 places) |
mocks/invitation/handlers.ts |
Response shape updated to { isValid, email, name, errorCode } |
mocks/users/handlers.ts |
Full user list + invite + change-role mock handlers |
New Page & Component
UsersPage.tsx— table with role/status badges, per-row dropdown (copy link, change role)components/users/InviteUserDialog.tsx— 2-step dialog: email+role form → invite link display
i18n
en/translation.jsonandnl/translation.json—users.*key section added (title, table columns, status labels, role labels, invite dialog, action labels)
Tests
| File | Coverage |
|---|---|
useUsers.test.ts |
useUsers, useInviteUser, useChangeRole — happy path + error |
UsersPage.test.tsx |
Render, table rows, pending badge, copy link action, API error, dialog open |
InviteUserDialog.test.tsx |
Form validation, successful flow (step 2), API error, dialog reset |
InviteCompletePage.test.tsx |
No changes needed — mock now returns correct isValid shape |
Key Design Decisions
isValidvsvalid: The existingInvitationValidationtype intypes.tsalready usesisValid. The olduseInvitation.tshad a local interface withvalid. Migration aligns the hook to the canonical type.- No server-side invite email: Invite flow is link-only (per functional design). The link is shown in the dialog step 2 and copied to clipboard.
- Role RBAC in frontend:
availableRolesFor()inUsersPagerestricts the dropdown options; backend enforces the same rules server-side. - Pending invite for accepted users:
GetPendingInvitationByEmailAsynconly returns a token if!IsAccepted && !IsExpired— accepted users correctly show no pending invite.