web: the QR card hands out the look that made the photo

A photo's landing section can now be the QR card, and that section is the
only one that hands something out: the server writes the photo's own stored
look back as the app's .recipe file, at
GET /api/photos/:id/preset.recipe, for any row the curator ticked into the
qr slot. Nothing new is stored — the file is built from the recipe the
upload already carried, so it works for a photo uploaded by the phone too.

The admin pane grows a fourth checkbox and a fourth row (QR card); the
row draws the download link as a scannable code, and the box is dead for a
photo with no stored look. The landing's QR card now encodes the curated
photo's own link instead of a mock address. The listing exposes
hasPreset, never the recipe itself.
This commit is contained in:
2026-09-18 16:57:46 +07:00
parent a35ecf4f1c
commit 8a889db069
11 changed files with 233 additions and 18 deletions
+23
View File
@@ -0,0 +1,23 @@
import { useState } from 'react';
// A QR code, drawn by the one public image service the landing already used:
// no QR library and no extra bytes in the bundle. `url` is the whole payload —
// whatever the phone reads on a scan is exactly this string.
// ponytail: a remote image means a third-party request and a blank frame while
// it loads. Render the matrix in-bundle only if the QR must work offline.
export function QrCode({ url, size = 180, className }: { url: string; size?: number; className?: string }) {
const [ok, setOk] = useState(true);
if (!ok) return null;
return (
<img
className={className}
width={size}
height={size}
alt=""
loading="lazy"
referrerPolicy="no-referrer"
onError={() => setOk(false)}
src={`https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(url)}`}
/>
);
}