Account and paywall
Onboarding, sign-in, entitlements and account deletion — the surface where two backends meet.
| Route | What it is |
|---|---|
(onboarding)/onboarding | First run |
paywall | RevenueCat's paywall |
customer-center | Subscription management |
(settings)/(profile)/* | Account, filter defaults |
legal/[document] | The legal documents |
Two backends meet here
Match data comes from the API. Accounts do not: Supabase holds the session and the user record, the same project the web apps sign in against.
There is no profile table. auth.users is the whole account model — Supabase
keeps the display name and avatar in user_metadata, whose keys belong to
whichever identity provider wrote them — and everything else about a person is
their entitlement, which RevenueCat owns.
lib/api/auth/auth-client is the only place a client is constructed. Tokens go
to the keychain rather than AsyncStorage: SecureStore rejects an entry over
2048 bytes and a session is bigger than that, so the storage adapter chunks it.
Entitlements
src/lib/core/paid-access.tsx, paid-access-model.ts and
feature-access-model.ts — what is unlocked, derived from RevenueCat's customer
info. Both models have tests beside them, because an entitlement bug either
gives away the product or locks out someone who paid.
revenuecat-key.ts picks the platform's API key, and is also tested — a wrong
key is an empty offerings list, which looks identical to a network failure.
The paywall is a route
Like everything else. purchases-ui.ts presents RevenueCat's own paywall, so
the design lives in their dashboard rather than in this repo — which is what
makes changing a price or a trial a non-deploy.
Account deletion
deleteAccountAndEndSession in lib/core/session calls the
delete_current_user database function, then signs out, then clears the
device. The order is the point: clearing first and failing the server call
leaves an account nobody can reach and Apple's reviewers can find — see
App Store.
The delete is a database function because there is no client call that can do
it. Removing a row from auth.users needs the service role, which cannot ship
in a bundle, so the privilege lives in a SECURITY DEFINER function that takes
no arguments and deletes exactly auth.uid() —
supabase/migrations/20260902_delete_current_user.sql. That migration has to
be applied to the project before a build ships, or deletion fails at the
point Apple tests it.