Bedrijfslogica
Authenticatiestroom
Authenticatie werkt uitsluitend via e-mail + wachtwoord. OAuth2 en gebruikersnaamauthenticatie zijn uitgeschakeld op PocketBase-niveau.
Stap-voor-stap toelichting
- App start —
RootLayoutrendertAuthProviderals buitenste React-tree-node. - Token herstellen —
AuthProviderroeptinitPocketBase()aan, die hetpb_auth-sleutel leest uitAsyncStorage. Als een token bestaat wordt het geladen enauthRefresh()aangeroepen om het te valideren. - Laadslot — zolang
isLoadingtrueis doetRootLayoutNavniets, waardoor vroegtijdige redirects worden voorkomen. - Bewaker-evaluatie — zodra
isLoadingfalsewordt:user === nullen niet in(auth)→ redirect naar/(auth)/user !== nullen in(auth)→ redirect naar/(app)/home
- Inloggen —
login(email, password)roeptpb.collection('users').authWithPassword(email, pass)aan. Bij succes slaat deonChange-luisteraar het token op inAsyncStorageen werkt de React-state bij. - Inlogfout —
authWithPasswordgooit een uitzondering; de catch-blok in het inlogscherm stelt een gelokaliseerde foutmelding in zonderAuthContextte muteren. - Uitloggen —
logout()roeptpb.authStore.clear()aan, waarna de bewaker de gebruiker omleidt naar het inlogscherm en het token uitAsyncStorageverwijdert.
Rolgebaseerde Toegangscontrole (RBAC)
RBAC wordt afgedwongen op twee onafhankelijke lagen. Beide moeten worden voldaan voor een bewerking om te slagen.
Twee lagen
| Laag | Mechanisme | Doel |
|---|---|---|
| Client-side | Tabbar rendert rolafhankelijke tabbladen | UX-gemak, verbergt niet-relevante functies |
| Server-side | PocketBase-toegangsregels per collectie | Werkelijke beveiliging, niet te omzeilen |
De server-side laag is doorslaggevend: een mislukte toegangsregel resulteert in een 403 Forbidden-respons, ongeacht wat de client-UI toont.
Rollen en tabbladen
De eigenaar deelt dezelfde tabbladen als een lid maar beschikt over schrijftoegang die niet beschikbaar is in de lid-UI. Schermen renderen beheerknoppen conditioneel op basis van user.role.
Server-side Regelpatronen
| Patroon | Expressie | Voorbeeld |
|---|---|---|
| Elke ingelogde gebruiker | @request.auth.id != '' | Lezen van trainingen, evenementen, nieuws |
| Eigen record | @request.auth.id = user.id | Eigen reserveringen en facturen lezen/bewerken |
| Eigenaar-only | @request.auth.role = 'eigenaar' | Producten, galerij, nieuws aanmaken/verwijderen |
| Trainer of eigenaar | @request.auth.role = 'trainer' || @request.auth.role = 'eigenaar' | Trainingen aanmaken en bewerken |
| Zelf of eigenaar | @request.auth.id = trainer.id || @request.auth.role = 'eigenaar' | Trainerbeschikbaarheid beheren |
Inschrijving Trainingen met Capaciteitscontrole
De trainings-collectie slaat een capacity-veld op (minimaal 1) dat het maximale aantal gelijktijdige inschrijvingen vertegenwoordigt. De training_enrollments-collectie koppelt gebruikers aan trainingen via een unieke (training, user)-index.
Inschrijvingsstroom
- Het Trainingen-scherm toont elk training met een
spotsLeft-waarde berekend uitcapacity - enrollmentCount. - Als
spotsLeft > 0is de knop “Inschrijven” actief. - Als
spotsLeft === 0verandert de knop naar “Wachtlijst”. - Als de gebruiker al ingeschreven is toont de knop “Ingeschreven” en is uitgeschakeld.
- Bij klikken roept het scherm
pb.collection('training_enrollments').create({ training, user })aan. - De unieke index
(training, user)voorkomt dubbele inschrijving op databaseniveau — een tweede aanmaaakpoging retourneert een400met een unique-constraint-fout. - Lokale state (
enrolledIds-set) wordt direct bijgewerkt bij succes, zodat de UI de wijziging weerspiegelt zonder een her-fetch.
Capaciteitshandhaving
De huidige implementatie berekent spotsLeft vanuit client-gehouden data. Het productiepatroon is om het inschrijvingstelling server-side op te halen via PocketBase-expand of een aparte teltquery en dit te vergelijken met capacity. De PocketBase-unieke index garandeert correctheid op de datalaag, zelfs als meerdere clients gelijktijdig proberen in te schrijven.
Reserveringsstatus-overgangen
Reserveringen bijhouden de levenscyclus van een baanboekingsverzoek van aanvraag tot afronding.
Statusoverzicht
| Status | Label in UI | Badge-kleur | Geactiveerd door |
|---|---|---|---|
pending | In behandeling | Goud | Lid maakt reservering aan |
confirmed | Bevestigd | Groen | Eigenaar keurt goed |
cancelled | Geannuleerd | Rood | Lid of eigenaar annuleert |
Stroom
- Lid selecteert datum, tijdslot, baan en duur op het Reservaties-scherm.
- Lid tikt op “Bevestigen” in het modale bottomsheet.
handleConfirm()roeptpb.collection('reservations').create(...)aan metstatus: 'pending'.- De reservering verschijnt in de lijst van het lid met een goud “In behandeling”-badge.
- De eigenaar bekijkt openstaande reserveringen en werkt de status bij naar
confirmedofcancelled.
Opmerking: Geen Unieke Constraint op Baanconflicten
Het schema dwingt nog geen unieke constraint af op (date, court, startTime). Baanconflictdetectie vindt plaats in de UI door “bezet”-sloten te tonen. Een productie-implementatie zou een PocketBase-validatieregel of unieke index toevoegen om dubbele boekingen op databaseniveau te voorkomen.
Factuurlevenscyclus
Facturen worden uitsluitend door de eigenaar aangemaakt en vertegenwoordigen verschuldigd geld van een lid aan de club.
Statusoverzicht
| Status | Label in UI | Badge-kleur | Betekenis |
|---|---|---|---|
open | Open | Amber | Factuur uitgesteld, betaling nog niet ontvangen |
paid | Betaald | Groen | Betaling ontvangen en bevestigd |
overdue | Verlopen | Rood | Vervaldatum verstreken zonder betaling |
Levenscyclus
- Aanmaken — eigenaar maakt een factuurrecord aan met
status: 'open', eendueDate, eenamounten een beschrijvendename. Hetnumber-veld is een mensleesbare identifier (bijv.INV-2025-042). Hettype-veld categoriseert de kostenpost. - Open — de factuur is zichtbaar voor het lid in het Facturen-scherm met een amber “Open”-badge. Het
paidAt-veld is leeg. - Betaling ontvangen — de eigenaar werkt
statusbij naarpaiden vultpaidAtin met de betalingsdatum. - Verlopen — als de
dueDateverstrijkt zonder statuswijziging naarpaid, werkt de eigenaar handmatigstatusbij naaroverdue. Deze overgang is handmatig — er is geen geautomatiseerde cron-job geimplementeerd. Een toekomstige verbetering kan een geplande PocketBase-hook uitvoeren om de status automatisch op verlopen te zetten. - Toegang — leden kunnen alleen hun eigen facturen lezen. Alle mutaties zijn beperkt tot de eigenaar-rol.