Skip to content

Bedrijfslogica

Authenticatiestroom

Authenticatie werkt uitsluitend via e-mail + wachtwoord. OAuth2 en gebruikersnaamauthenticatie zijn uitgeschakeld op PocketBase-niveau.

Ja Ja Nee Nee Ja Nee App start AuthProvider mount AS AsyncStorage authRefresh aanroepen Geldig? Rol bepalen Tab-set tonen op basis van rol authStore.clear LS E-mail + wachtwoord invoeren authWithPassword aanroepen Succes? Token opslaan in AsyncStorage Redirect naar home Foutmelding tonen

Stap-voor-stap toelichting

  1. App startRootLayout rendert AuthProvider als buitenste React-tree-node.
  2. Token herstellenAuthProvider roept initPocketBase() aan, die het pb_auth-sleutel leest uit AsyncStorage. Als een token bestaat wordt het geladen en authRefresh() aangeroepen om het te valideren.
  3. Laadslot — zolang isLoading true is doet RootLayoutNav niets, waardoor vroegtijdige redirects worden voorkomen.
  4. Bewaker-evaluatie — zodra isLoading false wordt:
    • user === null en niet in (auth) → redirect naar /(auth)/
    • user !== null en in (auth) → redirect naar /(app)/home
  5. Inloggenlogin(email, password) roept pb.collection('users').authWithPassword(email, pass) aan. Bij succes slaat de onChange-luisteraar het token op in AsyncStorage en werkt de React-state bij.
  6. InlogfoutauthWithPassword gooit een uitzondering; de catch-blok in het inlogscherm stelt een gelokaliseerde foutmelding in zonder AuthContext te muteren.
  7. Uitloggenlogout() roept pb.authStore.clear() aan, waarna de bewaker de gebruiker omleidt naar het inlogscherm en het token uit AsyncStorage verwijdert.

Rolgebaseerde Toegangscontrole (RBAC)

RBAC wordt afgedwongen op twee onafhankelijke lagen. Beide moeten worden voldaan voor een bewerking om te slagen.

Twee lagen

LaagMechanismeDoel
Client-sideTabbar rendert rolafhankelijke tabbladenUX-gemak, verbergt niet-relevante functies
Server-sidePocketBase-toegangsregels per collectieWerkelijke 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

Lid — 8 tabbladen Trainer — 4 tabbladen Eigenaar — 8 tabbladen + beheerfuncties rol = lid rol = trainer rol = eigenaar Gebruiker Home Reservaties Trainingen Events Winkel Foto's Socials Facturen Home Mijn Lessen Beschikbaarheid Mijn Uren Home Reservaties Trainingen Events Winkel Foto's Socials Facturen

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

PatroonExpressieVoorbeeld
Elke ingelogde gebruiker@request.auth.id != ''Lezen van trainingen, evenementen, nieuws
Eigen record@request.auth.id = user.idEigen 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.

Ja Nee Ja Nee: dubbele inschrijving Training ophalen SC capacity AV spotsLeft Inschrijven knop tonen Wachtlijst knop tonen POST Succes? LS enrolledIds ERR foutmelding

Inschrijvingsstroom

  1. Het Trainingen-scherm toont elk training met een spotsLeft-waarde berekend uit capacity - enrollmentCount.
  2. Als spotsLeft > 0 is de knop “Inschrijven” actief.
  3. Als spotsLeft === 0 verandert de knop naar “Wachtlijst”.
  4. Als de gebruiker al ingeschreven is toont de knop “Ingeschreven” en is uitgeschakeld.
  5. Bij klikken roept het scherm pb.collection('training_enrollments').create({ training, user }) aan.
  6. De unieke index (training, user) voorkomt dubbele inschrijving op databaseniveau — een tweede aanmaaakpoging retourneert een 400 met een unique-constraint-fout.
  7. 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.

lid maakt reservering eigenaar keurt goed lid of eigenaar annuleert lid of eigenaar annuleert pending confirmed cancelled

Statusoverzicht

StatusLabel in UIBadge-kleurGeactiveerd door
pendingIn behandelingGoudLid maakt reservering aan
confirmedBevestigdGroenEigenaar keurt goed
cancelledGeannuleerdRoodLid of eigenaar annuleert

Stroom

  1. Lid selecteert datum, tijdslot, baan en duur op het Reservaties-scherm.
  2. Lid tikt op “Bevestigen” in het modale bottomsheet.
  3. handleConfirm() roept pb.collection('reservations').create(...) aan met status: 'pending'.
  4. De reservering verschijnt in de lijst van het lid met een goud “In behandeling”-badge.
  5. De eigenaar bekijkt openstaande reserveringen en werkt de status bij naar confirmed of cancelled.

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.

eigenaar maakt factuur aan eigenaar markeert als betaald vervaldatum verstreken alsnog betaald open paid overdue

Statusoverzicht

StatusLabel in UIBadge-kleurBetekenis
openOpenAmberFactuur uitgesteld, betaling nog niet ontvangen
paidBetaaldGroenBetaling ontvangen en bevestigd
overdueVerlopenRoodVervaldatum verstreken zonder betaling

Levenscyclus

  1. Aanmaken — eigenaar maakt een factuurrecord aan met status: 'open', een dueDate, een amount en een beschrijvende name. Het number-veld is een mensleesbare identifier (bijv. INV-2025-042). Het type-veld categoriseert de kostenpost.
  2. Open — de factuur is zichtbaar voor het lid in het Facturen-scherm met een amber “Open”-badge. Het paidAt-veld is leeg.
  3. Betaling ontvangen — de eigenaar werkt status bij naar paid en vult paidAt in met de betalingsdatum.
  4. Verlopen — als de dueDate verstrijkt zonder statuswijziging naar paid, werkt de eigenaar handmatig status bij naar overdue. 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.
  5. Toegang — leden kunnen alleen hun eigen facturen lezen. Alle mutaties zijn beperkt tot de eigenaar-rol.