Onboarding flow
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user