web: give each member a photo folder and burn the strip into the export
Every member gets /photos — their own uploads, counted against a 12-photo cap, each card showing the tagline and the technical line the studio would print. The studio gains SAVE PHOTO n/12 in the top bar: it renders the full resolution look, stores the strip (tag/title/meta) with the upload so the landing reel frames it the same way, and refuses past the cap. EXPORT now burns that strip into the file: the amber #TAG over the photo's top-left plus a dark caption band below carrying the recipe name and the ISO / grain / warmth line. The live preview stays clean, and the saved upload stays clean too — the reel draws its own frame from the stored labels, so a burned band would tag the tag twice. Admins manage any photo through DELETE /api/photos/:id; members only their own. The users table's photo counts stay in step with the folder.
This commit is contained in:
@@ -10,10 +10,14 @@ export function TopBar({
|
||||
recipeName,
|
||||
user,
|
||||
exporting,
|
||||
savingPhoto,
|
||||
canUndo,
|
||||
onUndo,
|
||||
onReset,
|
||||
onSavePhoto,
|
||||
onExport,
|
||||
photoCount,
|
||||
photoMax,
|
||||
onAuth,
|
||||
onSignup,
|
||||
onLogout,
|
||||
@@ -21,10 +25,16 @@ export function TopBar({
|
||||
recipeName: string;
|
||||
user: User | null;
|
||||
exporting: boolean;
|
||||
savingPhoto: boolean;
|
||||
canUndo: boolean;
|
||||
onUndo: () => void;
|
||||
onReset: () => void;
|
||||
onSavePhoto: () => void;
|
||||
onExport: () => void;
|
||||
// This account's own folder: the SAVE PHOTO button states the count, and the
|
||||
// API refuses past the cap. A guest sees 0/12 and is sent to sign in.
|
||||
photoCount: number;
|
||||
photoMax: number;
|
||||
onAuth: () => void;
|
||||
onSignup: () => void;
|
||||
onLogout: () => void;
|
||||
@@ -55,6 +65,16 @@ 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 ghost"
|
||||
data-key="save-photo"
|
||||
disabled={savingPhoto || exporting}
|
||||
title={photoCount >= photoMax ? t('save.photoFull', { max: photoMax }) : undefined}
|
||||
onClick={onSavePhoto}
|
||||
>
|
||||
{savingPhoto ? t('photo.uploading') : t('save.photo', { n: photoCount, max: photoMax })}
|
||||
</button>
|
||||
<button type="button" className="btn primary" disabled={exporting} onClick={onExport}>
|
||||
{exporting ? t('act.exporting') : t('act.export')}
|
||||
</button>
|
||||
@@ -149,7 +169,10 @@ export function TopBar({
|
||||
{user.admin ? (
|
||||
<a className="btn" data-key="nav-admin" href="/admin">{t('nav.admin')}</a>
|
||||
) : (
|
||||
<a className="btn" data-key="nav-profile" href="/profile">{t('nav.profile')}</a>
|
||||
<>
|
||||
<a className="btn" data-key="nav-photos" href="/photos">{t('nav.photos')}</a>
|
||||
<a className="btn" data-key="nav-profile" href="/profile">{t('nav.profile')}</a>
|
||||
</>
|
||||
)}
|
||||
<button type="button" className="btn" data-key="nav-logout" onClick={onLogout}>{t('nav.logout')}</button>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user