Skip to content

Fase 0: Fundament

Fase 0 legt het fundament: authenticatie, role-based routing, de basisstructuur van beide portalen, taalwissel en de technische onderlaag (monorepo, database, CI/CD). Hier beschrijven we de flows die een gebruiker doorloopt.


Route: /register

Een formulier met drie velden:

VeldTypeValidatie
NaamtextVerplicht, min. 2 karakters
EmailemailVerplicht, geldig emailadres
WachtwoordpasswordVerplicht, min. 8 karakters

Onderaan het formulier:

  • Knop “Account aanmaken”
  • Link “Al een account? Inloggen” naar /login
  1. Client-side validatie: zijn alle velden correct ingevuld? Zo niet, inline foutmeldingen per veld.
  2. API call naar Supabase Auth via PKCE flow: supabase.auth.signUp({ email, password }).
  3. Er wordt een rij aangemaakt in de clients tabel met onboarding_completed = false.
  4. Supabase stuurt een verificatiemail (standaard Supabase flow).
  5. Na succesvolle registratie: redirect naar /onboarding.
SituatieMelding
Email al in gebruik”Dit emailadres is al geregistreerd. Probeer in te loggen.”
Ongeldig emailformaat”Voer een geldig emailadres in.”
Wachtwoord te kort”Wachtwoord moet minimaal 8 karakters zijn.”
Server error”Er ging iets mis. Probeer het later opnieuw.”

De gebruiker komt op /onboarding (beschreven in Fase 1a). Zolang onboarding_completed = false is, wordt de gebruiker bij elke login teruggestuurd naar onboarding.


Route: /login

Een formulier met twee velden:

VeldTypeValidatie
EmailemailVerplicht
WachtwoordpasswordVerplicht

Daaronder:

  • Knop “Inloggen”
  • Link “Wachtwoord vergeten?” naar /forgot-password
  • Link “Nog geen account? Registreren” naar /register

Admins en medewerkers loggen in via dezelfde route. Naast email + wachtwoord ondersteunen admins ook magic link login:

  1. Admin klikt op “Inloggen met magic link”.
  2. Vult emailadres in.
  3. Ontvangt een eenmalige inloglink per email.
  4. Klikt op de link en wordt direct ingelogd.

De magic link verloopt na 1 uur.

  1. Client-side validatie op ingevulde velden.
  2. API call: supabase.auth.signInWithPassword({ email, password }) of signInWithOtp({ email }) voor magic link.
  3. Supabase retourneert een sessie met JWT.
  4. De app checkt de rol van de gebruiker (zie sectie 4: Role-based redirect).
SituatieMelding
Verkeerde combinatie”Email of wachtwoord is onjuist.”
Account niet geverifieerd”Bevestig eerst je emailadres via de link in je inbox.”
Te veel pogingen”Te veel inlogpogingen. Probeer het over een paar minuten.”
Server error”Er ging iets mis. Probeer het later opnieuw.”

Foutmeldingen geven nooit prijs of een emailadres wel of niet bestaat.

Redirect op basis van rol, zie sectie 4.


Route: /forgot-password

Wat de gebruiker ziet:

  • Eenvoudig formulier met 1 veld: Email
  • Knop “Verstuur resetlink”
  • Link “Terug naar inloggen” naar /login

Wat er gebeurt:

  1. API call: supabase.auth.resetPasswordForEmail(email).
  2. Supabase stuurt een email met een resetlink.
  3. De app toont altijd de melding: “Als dit emailadres bij ons bekend is, ontvang je een resetlink.” Dit voorkomt dat iemand kan testen of een emailadres geregistreerd is.

De resetlink verloopt na 1 uur.

Route: /reset-password

De gebruiker komt hier via de link uit de email. Supabase voegt een token toe aan de URL.

Wat de gebruiker ziet:

  • Veld “Nieuw wachtwoord” (min. 8 karakters)
  • Veld “Bevestig wachtwoord”
  • Knop “Wachtwoord opslaan”

Wat er gebeurt:

  1. De app leest het token uit de URL.
  2. Bij bevestigen: supabase.auth.updateUser({ password }).
  3. Na succes: redirect naar /login met een succesmelding “Wachtwoord gewijzigd. Je kunt nu inloggen.”

Foutmeldingen:

SituatieMelding
Wachtwoorden komen niet overeen”Wachtwoorden komen niet overeen.”
Wachtwoord te kort”Wachtwoord moet minimaal 8 karakters zijn.”
Token verlopen”Deze resetlink is verlopen. Vraag een nieuwe aan.”
Ongeldig token”Ongeldige link. Vraag een nieuwe resetlink aan.”

Na een succesvolle login bepaalt de app waar de gebruiker naartoe gaat. Dit werkt op basis van het role veld in de users tabel.

RolRedirectVoorwaarde
klant/onboardingAls onboarding_completed = false
klant/dashboardAls onboarding voltooid
admin/adminAltijd
medewerker/adminAltijd
  1. Na login haalt de app het gebruikersprofiel op via supabase.auth.getUser().
  2. De rol wordt gecheckt in de users tabel.
  3. Een auth guard op de router stuurt klanten weg van /admin/* routes en admins weg van /dashboard/* routes.
  4. Niet-ingelogde gebruikers worden altijd naar /login gestuurd.
  5. Klanten die de onboarding niet hebben afgerond worden naar /onboarding gestuurd, ongeacht welke route ze proberen te bezoeken.
  • Een gebruiker probeert /admin te bezoeken zonder admin-rol: redirect naar /dashboard (of /onboarding).
  • Een admin probeert /dashboard te bezoeken: redirect naar /admin.
  • Een verlopen sessie: redirect naar /login, de oorspronkelijke URL wordt bewaard zodat de gebruiker na login terugkomt.

Na inloggen (en voltooide onboarding) ziet de klant een responsive layout met een zijnavigatie (desktop) of hamburger menu (mobiel).

LabelRouteIcoonOmschrijving
Dashboard/dashboardHomeStartpagina met overzicht
Mijn honden/dogsPawHondenprofielen
Boekingen/bookingsCalendarBoekingenoverzicht
Facturen/invoicesReceiptFactuuroverzicht
Account/accountUserPersoonlijke instellingen
  • Header: logo Dog Hotel Aruba, taalwissel (NL/EN), gebruikersnaam met dropdown (account, uitloggen).
  • Sidebar (desktop): navigatie-items met iconen en labels.
  • Mobiel: hamburger menu dat de sidebar opent als overlay.
  • Content area: de pagina-inhoud.
  • De actieve route is visueel gemarkeerd in de navigatie.

Admins en medewerkers komen na login in de admin layout. Dit is een volledig apart deel van de app met eigen navigatie.

LabelRouteIcoonOmschrijving
Dashboard/adminLayoutDashboardDagelijks overzicht
Klanten/admin/clientsUsersKlantbeheer
Honden/admin/dogsDogHondenoverzicht
Boekingen/admin/bookingsCalendarBoekingsbeheer
Facturen/admin/invoicesReceiptFacturatiebeheer
Instellingen/admin/settingsSettingsSysteeminstellingen
  • Header: logo, taalwissel, naam ingelogde medewerker met dropdown (account, uitloggen).
  • Sidebar (desktop): navigatie-items. Bij bredere schermen altijd zichtbaar, bij smallere schermen inklapbaar.
  • Mobiel: hamburger menu.
  • De actieve route en actieve sectie zijn visueel gemarkeerd.
  • Ander kleurenschema of accent om duidelijk te maken dat je in het admin paneel zit.
  • Geen overlap in routes: /admin/* is strikt gescheiden van klantroutes.

De app ondersteunt twee talen in Fase 0: Nederlands (NL) en Engels (EN).

De taalwissel staat in de header van zowel het klantportaal als het adminpaneel. Het is een simpele toggle of dropdown met de twee taalopties.

  1. De gebruiker klikt op de taalswitch en kiest NL of EN.
  2. Alle UI-teksten worden direct bijgewerkt via react-i18next. Er is geen pagina-reload nodig.
  3. De gekozen taal wordt opgeslagen in localStorage voor niet-ingelogde gebruikers.
  4. Voor ingelogde klanten wordt de voorkeurstaal ook opgeslagen in het gebruikersprofiel (preferred_language in de clients tabel), zodat het bij een volgende sessie automatisch goed staat.
  5. Emails worden verstuurd in de voorkeurstaal van de gebruiker.
  • Niet ingelogd: de browser-taal wordt gedetecteerd. Als die NL of EN is, wordt die gebruikt. Anders valt het terug op EN.
  • Ingelogd: de opgeslagen voorkeurstaal uit het profiel.

Naast de gebruikersflows bevat Fase 0 ook het technische fundament. Geen schermen, maar wel relevant om te noemen:

pnpm monorepo met de volgende structuur:

  • apps/web met React, Vite, Tailwind CSS, shadcn/ui
  • apps/api met Hono op Cloudflare Workers
  • apps/docs met Starlight (deze documentatie)
  • packages/shared met gedeelde TypeScript types en Zod schemas

Supabase met de eerste migratie: tabellen clients, dogs, vaccinations, users, services. RLS policies op alle tabellen. De services tabel wordt gevuld met seed data (alle diensten en prijzen).

Hono API met:

  • Auth middleware op alle routes (behalve /health en /payments/webhook)
  • CORS beperkt tot APP_ORIGIN
  • Zod input validatie op alle endpoints
  • /health endpoint zonder auth, retourneert { status: "ok" }

GitHub Actions workflows voor automatische deploy:

  • apps/web naar Cloudflare Pages
  • apps/api naar Cloudflare Workers
  • apps/docs naar Cloudflare Pages
  • Typecheck en lint als quality gate voor elke PR