Onboarding flow

This commit is contained in:
2026-07-06 22:25:52 +02:00
parent 99cd885833
commit 6b57a2acfc
37 changed files with 1503 additions and 507 deletions

View File

@@ -22,12 +22,12 @@ Note: `onboarding/customize.tsx` stays out of the chain (as today). The spec lis
**Key backend facts:**
- `server/lib/billing.js`: `FREE_MONTHLY_CREDITS = 0` (line 3), `getAvailableCredits` returns 0 for non-pro (line 257), `consumeCredits` throws for non-pro (line 573), `alignAccountToCurrentCycle` self-heals allowance via `isAllowedMonthlyAllowance` (line 76) — legacy free accounts with stored allowance 0 auto-migrate once `FREE_MONTHLY_CREDITS` changes.
- Costs (`server/index.js:84-87`): scan primary 1, scan review 0, semantic search 2, health check 2.
- Guests: `isGuest(userId)` = `userId === 'guest'`. Guests currently never reach credit consumption because `ensureActiveProEntitlement` throws first. **When removing it, guests must still be blocked server-side** — otherwise all guests share one global `'guest'` billing account and health checks become free for guests (`server/index.js:1005` skips charging guests).
- Guests: `isGuest(userId)` = `userId === 'guest'`. Guests may run the limited pre-auth demo identification scan, but the demo scan must use the same AI identification path as a normal scan. Guests must still be blocked from health checks and semantic search because those would otherwise use one shared `'guest'` billing account.
- Scan model per plan: `server/lib/openai.js:33` `getScanModelChain(plan)` — free uses the cheap chain. Decision: free gets the pro chain (same quality).
- Billing summary shape (`buildBillingSummary`): `credits.cycleEndsAt` is the free-credit renewal date.
**Key app facts:**
- `app/scanner.tsx`: `isDemoMode = !hasActiveEntitlement` (line 170) — ALL non-pro users currently get client-side mock scans (`getMockPlantByImage`), limited to 5/device via `guestScanCount`. New rule: demo mode = guests only (`!session`); signed-in free users do real scans with credits.
- `app/scanner.tsx`: demo mode is guests only (`!session`), limited to 5/device via `guestScanCount`. Demo scans must call the normal identification service, not `getMockPlantByImage`; signed-in free users do real scans with credits.
- `app/profile/billing.tsx` (1779 lines): already contains the full paywall branch `showPaywallPlans` (line 360: `!session || (!isLoadingBilling && planId !== 'pro')`), purchase/restore/sync/Expo-Go-simulation logic, per-language copy via `getBillingCopy(language)`. We reuse ALL purchase logic — only the paywall trigger and the paywall JSX change.
- Theme: `useColors(isDarkMode, colorPalette)` from `constants/Colors.ts` (tokens like `colors.primary`, `colors.surface`, `colors.text`, `colors.primarySoft`, `colors.border`, `colors.onPrimary`, `colors.textSecondary`, `colors.textMuted`, `colors.surfaceMuted`). New screens must support dark mode via these tokens (Stitch dark variants exist as reference).
- New-screen copy: follow the `getBillingCopy(language)`-style local copy object pattern (de/es/en) — do NOT add keys to `utils/translations.ts` unless a screen already uses `t.` keys you're keeping.
@@ -235,19 +235,19 @@ git commit -m "feat(server): free tier with 3 monthly credits"
---
### Task 3: Remove the server hard paywall (guests stay blocked)
### Task 3: Remove the server hard paywall (guest demo scan allowed)
**Files:**
- Modify: `server/index.js:206-218` (helpers), `:734` (scan), `:742` (scan model), `:906` (semantic search), `:946` (health check)
- [ ] **Step 1: Replace the pro-gate helper with a guest gate**
- [ ] **Step 1: Replace the pro-gate helper with a guest gate for non-demo endpoints**
Replace lines 206-218 (`createHardPaywallError` + `ensureActiveProEntitlement`) with:
```js
const ensureNotGuest = (userId, requiredCredits) => {
// Guests use the client-side demo scan; the shared 'guest' billing account
// must never consume real credits or run free AI analyses.
// Guests may use the limited pre-auth demo scan, but the shared 'guest'
// billing account must never consume credits for non-demo endpoints.
if (isGuest(userId)) {
const error = new Error('Sign in to use scan credits.');
error.code = 'INSUFFICIENT_CREDITS';
@@ -262,7 +262,7 @@ Note: `isGuest` is defined at line 353, *after* this helper — that's fine (fun
- [ ] **Step 2: Swap the three call sites**
- `server/index.js:734`: `ensureActiveProEntitlement(accountSnapshot, SCAN_PRIMARY_COST);``ensureNotGuest(userId, SCAN_PRIMARY_COST);`
- `server/index.js:734`: remove the guest gate from `/v1/scan`; guest demo scans may run primary AI identification without consuming account credits.
- `server/index.js:906`: `ensureActiveProEntitlement(accountSnapshot, SEMANTIC_SEARCH_COST);``ensureNotGuest(userId, SEMANTIC_SEARCH_COST);`
- `server/index.js:946`: `ensureActiveProEntitlement(accountSnapshot, HEALTH_CHECK_COST);``ensureNotGuest(userId, HEALTH_CHECK_COST);`
@@ -507,7 +507,7 @@ git commit -m "feat(app): out-of-credits bottom sheet (Stitch design)"
---
### Task 6: Scanner — demo mode for guests only, credits + sheet for free users
### Task 6: Scanner — AI demo mode for guests only, credits + sheet for free users
**Files:**
- Modify: `app/scanner.tsx:168-172` (mode flags), `:269-297` (pre-checks), `:414-425` (402 handler)
@@ -519,7 +519,7 @@ Replace lines 168-172:
```tsx
const hasActiveEntitlement = billingSummary?.entitlement?.plan === 'pro'
&& billingSummary?.entitlement?.status === 'active';
const isDemoMode = !session; // guests get the local demo scan; signed-in users burn real credits
const isDemoMode = !session; // guests get limited AI demo scans; signed-in users burn real credits
const availableCredits = billingSummary?.credits.available ?? 0;
const demoScansRemaining = Math.max(0, DEMO_SCAN_LIMIT - guestScanCount);
```
@@ -1481,7 +1481,7 @@ Run: `npx expo export --platform android` → completes without errors.
1. Fresh install → welcome (social proof) → Let's Go → 3 slides → 4 question steps with progress bar → personalizing (auto) → paywall with trial toggle → ✕ → sign-up → tabs.
2. New account has 3 credits; scan works and decrements; after 3 scans the out-of-credits sheet appears; "See Pro Plans" opens the paywall; ✕ returns to the scanner (app still usable).
3. Guest demo scan from welcome still works (5 local demo scans), server still 402s direct guest API calls.
3. Guest demo scan from welcome still works (5 AI demo scans), direct guest health-check and semantic-search API calls still 402.
4. Existing pro account: no paywall anywhere, unchanged manage view under Profile → billing.
5. Login as existing free user: lands in tabs (no hard-paywall redirect), sees credit badge in scanner.
6. Dark mode: slides, questions, personalizing, paywall, sheet all render with dark tokens (compare `_dark_mode` mockups).
@@ -1501,5 +1501,5 @@ git commit -m "docs: mark onboarding/soft-paywall/free-tier spec as implemented"
## Self-review notes (already applied)
- Spec coverage: welcome ✓(T10) slides ✓(T11) questions ✓(T12) personalizing ✓(T13) paywall ✓(T7/8) sign-up/login ✓(T14) out-of-credits ✓(T5/6) free tier ✓(T1-3) soft gating ✓(T4/6) credits badge ✓(T6 step 6) analytics ✓(inline). Deviations from spec, both agreed-level "Kleinigkeiten": `customize.tsx` stays out of the chain (health-check is step 4), and sign-up shows no personal name (no name is collected).
- Guest safety: T3 keeps guests blocked server-side (`ensureNotGuest`) — required because `getOrCreateAccount(db, 'guest')` would otherwise mint a shared free account.
- Guest safety: T3 keeps guests blocked server-side for non-demo endpoints (`ensureNotGuest`) — required because `getOrCreateAccount(db, 'guest')` would otherwise mint a shared free account.
- Type consistency: `PreAuthOnboardingService` keys (`acquisitionSource`/`primaryGoal`/`experienceLevel`) match `OnboardingProgressService` setters; `OutOfCreditsSheet` props match the T6 call site; paywall param names (`view`, `context`) consistent across T6/T7/T13.

View File

@@ -20,7 +20,7 @@ Welcome (social proof)
→ App (tabs), free plan with 3 credits/month
```
- The guest **demo scan stays** available from the welcome screen (existing `isGuest` server handling unchanged).
- The guest **demo scan stays** available from the welcome screen, but it must use the same AI identification path as a normal scan. Demo scans must never return local hash/mock plant results or fake confidence values.
- Existing users: "Log in" link on the welcome screen → login.
- Paywall ✕ during onboarding → continues to sign-up. Paywall ✕ in-app → simply closes.
@@ -46,10 +46,11 @@ Known mockup fixes (agreed): replace AI-artifact photos (garbled phone UI on sca
## 3. Free tier (backend, `server/lib/billing.js` + `server/index.js`)
- `FREE_MONTHLY_CREDITS: 0 → 3`. Existing monthly-allowance reset logic already covers free accounts; verify reset works for plan `free`.
- **Remove server hard paywall:** drop `ensureActiveProEntitlement` from scan and health-check endpoints (`server/index.js`). Credit consumption becomes the only gate; 0 credits → existing 402 `INSUFFICIENT_CREDITS` error.
- **Same scan model for free and pro** (`getScanModel` returns the pro model for both plans). Cost accepted (~cents/user/month at 3 scans).
- **Low-confidence AI review pass stays pro-only** (unchanged) so a free scan never burns 2 credits, and Pro keeps a quality edge.
- Trial handling unchanged (yearly plan carries 7-day trial, 30 credits during trial).
- **Remove server hard paywall:** drop `ensureActiveProEntitlement` from scan and health-check endpoints (`server/index.js`). Credit consumption becomes the only gate; 0 credits → existing 402 `INSUFFICIENT_CREDITS` error.
- **Same scan model for free and pro** (`getScanModel` returns the pro model for both plans). Cost accepted (~cents/user/month at 3 scans).
- **Low-confidence AI review pass stays pro-only** (unchanged) so a free scan never burns 2 credits, and Pro keeps a quality edge.
- **Guest demo scans use AI identification without consuming account credits.** Guests remain blocked from health checks and semantic search, but `/v1/scan` may run the primary identification model for the limited pre-auth demo experience.
- Trial handling unchanged (yearly plan carries 7-day trial, 30 credits during trial).
## 4. Soft paywall (app)