Tier model: registered=basic (recipes, 12 photos, QR), PRO=verified or admin-activated (HSL, tools/gradient mask, RAW, export > source longest edge); admin users table gets Activated Pro column

This commit is contained in:
2026-09-30 11:50:28 +07:00
parent 35d8d57984
commit 74c0b2747f
10 changed files with 264 additions and 122 deletions
+17 -9
View File
@@ -24,9 +24,14 @@ export function ExportModal({
const { t } = useI18n();
const [custom, setCustom] = useState(String(Math.min(CUSTOM_MAX, Math.max(1024, longest * 2))));
// `maxDim` is whatever the account's tier allows: none for PRO, the photo's
// own long edge for a basic account, 2048 for a guest. The field's own `max`
// says so, so a number past it is refused where it is typed rather than
// silently clamped on the way out.
const customMax = Math.min(CUSTOM_MAX, maxDim ?? CUSTOM_MAX);
const pick = (n: number | null) => onPick(n !== null && maxDim !== null ? Math.min(n, maxDim) : n);
const customValue = Number(custom);
const customOk = Number.isFinite(customValue) && customValue >= 1 && customValue <= CUSTOM_MAX;
const customOk = Number.isFinite(customValue) && customValue >= 1 && customValue <= customMax;
return (
<div className="modal-backdrop" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
@@ -38,12 +43,14 @@ export function ExportModal({
<button type="button" className="btn" data-key="export-same" onClick={() => pick(null)}>
{t('export.same')}
</button>
<button type="button" className="btn" data-key="export-2k" onClick={() => pick(2048)}>
{t('export.2k')}
</button>
{/* A capped account cannot be handed a size its tier refuses, so the two
sizes over the ceiling are not offered at all. */}
{maxDim === null || maxDim > 2048 ? (
{(maxDim === null || maxDim >= 2048) && (
<button type="button" className="btn" data-key="export-2k" onClick={() => pick(2048)}>
{t('export.2k')}
</button>
)}
{/* A size the ceiling refuses is not offered at all — picking it would
only hand back the cap. The note below names that cap. */}
{(maxDim === null || maxDim >= 4096) && (
<>
<button type="button" className="btn" data-key="export-4k" onClick={() => pick(4096)}>
{t('export.4k')}
@@ -57,7 +64,7 @@ export function ExportModal({
className="field"
type="number"
min={1}
max={CUSTOM_MAX}
max={customMax}
value={custom}
onChange={(e) => setCustom(e.target.value)}
/>
@@ -72,7 +79,8 @@ export function ExportModal({
</button>
</div>
</>
) : (
)}
{maxDim !== null && (
<p className="hint" data-key="export-cap">
{t('export.cap', { px: maxDim })}
</p>