3.7 KiB
quinn.vip — Feature Overview
Domain: quinn.vip (prod), vip.quinn.apricot.local (dev)
Feature source: codebase/@features/vip/frontend-client/ (Vite + React SPA, PWA)
Backend: codebase/@features/api/ (shared NestJS API, port 3031)
What it is
A private, per-client portal. Each invited client gets a unique token URL (/portal/:token). Behind that URL is an encrypted messaging channel + relationship data — upcoming dates, billing history, shared photos, a personal story, and account settings.
The app is a PWA installable to the home screen. It ships multiple disguise manifests so the icon can appear as Calculator, Notes, Weather, or Fitness on a client's device.
Auth flow
/portal/:token
└─ getAuthStatus(token)
├─ hasPassword: false → SetPasswordScreen (first visit)
└─ hasPassword: true → LoginScreen
├─ hasWebAuthn: true → auto-trigger WebAuthn
└─ password form → verifyPassword(token, pw)
└─ onUnlocked → sessionStorage.setItem('vip_auth_ok', '1')
Session flag vip_auth_ok in sessionStorage skips the auth check on tab re-focus within the same session. Closing the browser tab clears it.
Storage keys
| Key | Storage | Purpose |
|---|---|---|
vip_token |
localStorage |
Persists the token across sessions |
vip_auth_ok |
sessionStorage |
Auth bypass within a single browser session |
vip_content_key |
sessionStorage |
AES-GCM content decryption key, derived at login |
vip_welcomed_<token> |
sessionStorage |
Prevents welcome splash from re-showing |
Encryption
Messages and sensitive content are encrypted server-side per-client. The content key is derived from the client's password via PBKDF2 (or Argon2 depending on settings). On login, verifyPassword returns the raw contentKey which is stashed in sessionStorage for the tab's lifetime.
The Settings tab exposes the encryption algorithm, KDF, iteration count, and key creation date.
Disguise mode (PWA)
public/ contains multiple manifest-*.json files. At install time, the user selects a disguise:
| Disguise | Manifest | Icon |
|---|---|---|
| Default | manifest-default.json |
Crown/star |
| Calculator | manifest-calculator.json |
Calculator |
| Notes | manifest-notes.json |
Notes |
| Weather | manifest-weather.json |
Weather |
| Fitness | manifest-fitness.json |
Fitness |
DisguisePicker.tsx handles selection. The service worker (sw.js) caches the correct manifest.
API base
VITE_API_URL env var, defaults to http://localhost:3031. Set in .env.development.local for local dev and in the deployment environment for prod.
Tabs
| Tab | Internal id | Doc |
|---|---|---|
| Messages | messages |
messages.md |
| Dates | reservations |
dates.md |
| Billing | billing |
billing.md |
| Story | story |
story.md |
| Gallery | gallery |
gallery.md |
| Settings | settings |
settings.md |
Key files
| Path | Purpose |
|---|---|
frontend-client/src/pages/VipPortalPage.tsx |
Main component — all tabs, auth states, nav |
frontend-client/src/api.ts |
All API calls + TypeScript types |
frontend-client/src/push.ts |
Web Push subscription registration |
frontend-client/src/hooks/useMessageStream.ts |
SSE message streaming hook |
frontend-client/src/components/DisguisePicker.tsx |
PWA disguise selection UI |
frontend-client/src/disguises.ts |
Disguise config constants |
frontend-client/vite.config.ts |
Build config, PWA plugin |