web: moderate accounts, accept 12MB uploads, put SAVE under CREATE

- /admin User account rows gain BLOCK/UNBLOCK, REMOVE/RESTORE and DELETE.
  Blocked = cannot sign in (sessions swept), removed = hidden from the strip
  and cannot sign in, both reversible; DELETE drops the account with its
  photos and recipes and unlinks the files. An allowlisted account is never
  a target, so an admin cannot moderate or delete itself.
- Photo uploads move from a 3MB API cap / 4m nginx cap to 12MB / 16m, and
  the browser shrinks an oversized still before sending it (2048px JPEG,
  avatars 512px) so the declared type still matches the sniffed bytes.
- The studio SAVE leaves the top bar and sits under the CREATE RECIPES tab,
  labelled SAVE RECIPES.
This commit is contained in:
2026-09-18 10:33:35 +07:00
parent 8e6c1493e8
commit 43d86b4b6f
12 changed files with 386 additions and 45 deletions
-3
View File
@@ -14,7 +14,6 @@ export function TopBar({
onUndo,
onReset,
onExport,
onSave,
onAuth,
onSignup,
onLogout,
@@ -26,7 +25,6 @@ export function TopBar({
onUndo: () => void;
onReset: () => void;
onExport: () => void;
onSave: () => void;
onAuth: () => void;
onSignup: () => void;
onLogout: () => void;
@@ -57,7 +55,6 @@ export function TopBar({
<button type="button" className="btn ghost" disabled={!canUndo} onClick={onUndo}>{t('act.undo')}</button>
<button type="button" className="btn ghost" onClick={onReset}>{t('act.reset')}</button>
<button type="button" className="btn" onClick={onSave}>{t('save.save')}</button>
<button type="button" className="btn primary" disabled={exporting} onClick={onExport}>
{exporting ? t('act.exporting') : t('act.export')}
</button>