StatsHub Docs

Account and paywall

Onboarding, sign-in, entitlements and account deletion — the surface where two backends meet.

RouteWhat it is
(onboarding)/onboardingFirst run
paywallRevenueCat's paywall
customer-centerSubscription 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.

On this page