web: PRO needs a proven address — email verification gates the studio

A signed-in account is served exactly like a guest until it opens the
verification link: watermarked 2048px export, no saving, no PRO frames,
GPS stamp or HDF. SMTP is declared in .env; with SMTP_HOST unset the link
goes to the container log. Allowlisted admins count as verified.
This commit is contained in:
2026-09-20 07:39:03 +07:00
parent efe578f61c
commit 52b672deec
16 changed files with 633 additions and 74 deletions
+6
View File
@@ -16,6 +16,9 @@ export interface User {
// True when the account is on the API's ADMIN_EMAILS allowlist. The server
// re-checks it on every admin route; this only drives what the UI offers.
admin?: boolean;
// Signed-in accounts only unlock the PRO tier once their address is proven;
// an unverified one is served exactly like a guest. Admins count as verified.
verified?: boolean;
// A ready-to-use picture URL (`/api/users/<id>/avatar?v=<file>`), or null.
// The version segment is the file's own name, so a replacement is never
// served from cache.
@@ -197,6 +200,9 @@ export const api = {
login: (email: string, password: string) =>
call<{ user: User }>('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) }),
logout: () => call<void>('/auth/logout', { method: 'POST' }),
// Mail the verification link to the signed-in address again. Works while
// unverified (that is the whole point); 429 once the hourly cap is spent.
resendVerification: () => call<{ ok: boolean; verified?: boolean }>('/auth/resend-verification', { method: 'POST' }),
listRecipes: () => call<{ recipes: SavedRecipe[] }>('/recipes'),
createRecipe: (name: string, recipe: Recipe) =>