Simpl-Resultat/src/shared
le king fu b89074e6c6 feat(gating): multi-profile gate (Base+), non-destructive
A Free user keeps full access to their active profile; profiles beyond
it show a lock in ProfileSwitcher and open an upsell dialog instead of
switching. Creating a profile beyond the first is locked at the single
creation point, ProfileFormModal (reached from both ProfileSwitcher and
ProfileSelectionPage), with a race guard in handleSave covering the
license boot window. Both creation entries stay visible with a lock
(locked-not-hidden). Nothing is ever removed from profiles.json — an
upgrade to Base/Premium makes every profile reappear untouched.

- New pure predicates in src/shared/profileGate.ts
  (isProfileSwitchLocked, isProfileCreationLocked) + 10 vitest
- ProfileFormModal upsell panel reuses upsell.* keys WITHOUT UpsellGate:
  the modal also opens from ProfileSelectionPage, which renders outside
  BrowserRouter, where UpsellGate's useNavigate would throw
- UpsellGate gains an optional onNavigate callback so the
  ProfileSwitcher upsell dialog can close itself after navigation
- No lock while the license is loading (anti-flash, ready guard);
  zero new i18n keys; no DB migration

Resolves #300

Generated autonomously by /autopilot run of 2026-07-20

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 22:24:23 -04:00
..
constants feat(gating): gate routes and Sidebar for budget, advanced reports, balance 2026-07-20 22:12:51 -04:00
types feat(gating): gate routes and Sidebar for budget, advanced reports, balance 2026-07-20 22:12:51 -04:00
entitlements.ts feat(gating): UI guard — RequireFeature + UpsellGate + i18n 2026-07-20 22:05:04 -04:00
profileGate.test.ts feat(gating): multi-profile gate (Base+), non-destructive 2026-07-20 22:24:23 -04:00
profileGate.ts feat(gating): multi-profile gate (Base+), non-destructive 2026-07-20 22:24:23 -04:00