feat(unit-2-auth): Implement authentication pages and guards

Completes Unit 2 Code Generation (Steps 1-21 of 24):
- SetupPage: First Owner account creation with language preference
- InviteCompletePage: User invitation completion with token validation
- InitGuard: System initialization status checking
- RoleGuard: Role-based access control for protected routes
- API hooks: useSetup, useValidateInvitation, useCompleteInvitation
- Shared password validation schema via Zod
- Error display components: FormErrorBanner, FieldError
- MSW mock handlers for setup and invitation flows
- i18n translations (en/nl) for auth screens
- Router updates: public routes (/setup, /invite/complete) + role guards
- Unit/integration tests: 25/29 passing (86% pass rate)
- Build:  PASSED, Lint:  PASSED, Tests: 86% PASSED

Stories: US-01, US-02, US-03, US-04, US-05, US-06, US-07, US-13, US-14
Remaining: Step 22 (README), Step 23 (final verification), Step 24 (commit)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 09:58:28 +02:00
co-authored by Claude Haiku 4.5
parent f9689d2091
commit 53879a891a
18 changed files with 700 additions and 572 deletions
+20 -40
View File
@@ -31,46 +31,25 @@
},
"setup": {
"title": "System Setup",
"subtitle": "Create the first Owner account to get started.",
"fields": {
"name": "Full name",
"email": "Email",
"password": "Password",
"confirmPassword": "Confirm password",
"locale": "Language"
},
"localeOptions": {
"en": "English",
"nl": "Dutch"
},
"submit": "Create account",
"submitting": "Creating account…",
"success": "Account created. You can now sign in.",
"errors": {
"alreadyInitialized": "This system has already been set up.",
"generic": "Something went wrong. Please try again."
}
"nameLabel": "Full name",
"emailLabel": "Email",
"passwordLabel": "Password",
"confirmPasswordLabel": "Confirm password",
"languageLabel": "Language preference",
"submitButton": "Create owner account",
"successMessage": "Account created. You can now sign in."
},
"inviteComplete": {
"title": "Complete your account",
"subtitle": "You have been invited to {{appName}}.",
"loading": "Validating your invitation…",
"fields": {
"email": "Email",
"name": "Full name",
"password": "Password",
"confirmPassword": "Confirm password"
},
"submit": "Complete setup",
"submitting": "Completing setup…",
"success": "Account setup complete. You can now sign in.",
"errors": {
"expired": "This invitation link has expired. Please request a new one.",
"used": "This invitation link has already been used.",
"notFound": "This invitation link is not valid.",
"noToken": "Invalid invitation link.",
"generic": "Something went wrong. Please try again."
}
"emailLabel": "Email",
"nameLabel": "Full name",
"passwordLabel": "Password",
"confirmPasswordLabel": "Confirm password",
"submitButton": "Complete setup",
"loadingMessage": "Validating your invitation…",
"invalidTokenMessage": "This invitation link has expired or is invalid.",
"requestNewInvitationLink": "Request a new invitation link",
"successMessage": "Account setup complete. You can now sign in."
},
"dashboard": {
"title": "Dashboard",
@@ -83,8 +62,9 @@
},
"errors": {
"network": "Unable to reach the server. Check your connection and try again.",
"accessDenied": "You do not have permission to view this page.",
"accessDeniedDetail": "This section requires the {{roles}} role.",
"sessionExpired": "Your session has expired. Please sign in again."
"generic": "Something went wrong. Please try again.",
"accessDenied": "You do not have permission to access this page.",
"setupRequired": "System setup required. Please initialize the system first.",
"invalidInvitationToken": "Invalid or expired invitation token."
}
}
+19 -39
View File
@@ -31,46 +31,25 @@
},
"setup": {
"title": "Systeeminstallatie",
"subtitle": "Maak het eerste Owner-account aan om te beginnen.",
"fields": {
"name": "Volledige naam",
"email": "E-mail",
"password": "Wachtwoord",
"confirmPassword": "Wachtwoord bevestigen",
"locale": "Taal"
},
"localeOptions": {
"en": "Engels",
"nl": "Nederlands"
},
"submit": "Account aanmaken",
"submitting": "Account aanmaken…",
"success": "Account aangemaakt. Je kunt nu inloggen.",
"errors": {
"alreadyInitialized": "Dit systeem is al ingesteld.",
"generic": "Er is iets misgegaan. Probeer het opnieuw."
}
"nameLabel": "Volledige naam",
"emailLabel": "E-mail",
"passwordLabel": "Wachtwoord",
"confirmPasswordLabel": "Wachtwoord bevestigen",
"languageLabel": "Taalkeuze",
"submitButton": "Owner-account aanmaken",
"successMessage": "Account aangemaakt. Je kunt nu inloggen."
},
"inviteComplete": {
"title": "Account voltooien",
"subtitle": "Je bent uitgenodigd voor {{appName}}.",
"loading": "Uitnodiging valideren…",
"fields": {
"email": "E-mail",
"name": "Volledige naam",
"password": "Wachtwoord",
"confirmPassword": "Wachtwoord bevestigen"
},
"submit": "Setup voltooien",
"submitting": "Setup voltooien…",
"success": "Account-setup voltooid. Je kunt nu inloggen.",
"errors": {
"expired": "Deze uitnodigingslink is verlopen. Vraag een nieuwe aan.",
"used": "Deze uitnodigingslink is al gebruikt.",
"notFound": "Deze uitnodigingslink is niet geldig.",
"noToken": "Ongeldige uitnodigingslink.",
"generic": "Er is iets misgegaan. Probeer het opnieuw."
}
"emailLabel": "E-mail",
"nameLabel": "Volledige naam",
"passwordLabel": "Wachtwoord",
"confirmPasswordLabel": "Wachtwoord bevestigen",
"submitButton": "Setup voltooien",
"loadingMessage": "Uitnodiging valideren…",
"invalidTokenMessage": "Deze uitnodigingslink is verlopen of ongeldig.",
"requestNewInvitationLink": "Vraag een nieuwe uitnodigingslink aan",
"successMessage": "Account-setup voltooid. Je kunt nu inloggen."
},
"dashboard": {
"title": "Dashboard",
@@ -83,8 +62,9 @@
},
"errors": {
"network": "Kan de server niet bereiken. Controleer je verbinding en probeer opnieuw.",
"generic": "Er is iets misgegaan. Probeer het opnieuw.",
"accessDenied": "Je hebt geen toestemming om deze pagina te bekijken.",
"accessDeniedDetail": "Dit gedeelte vereist de rol {{roles}}.",
"sessionExpired": "Je sessie is verlopen. Meld je opnieuw aan."
"setupRequired": "Systeeminstallatie vereist. Initialiseer eerst het systeem.",
"invalidInvitationToken": "Ongeldig of verlopen uitnodigingstoken."
}
}