← back to Trademarks Copyright
src/components/DropEditor.tsx
112 lines
"use client";
import { useEffect, useState } from "react";
type Item = {
id: number; position: number;
headline: string; blurb: string;
pro_extra: string | null; cta_text: string | null; cta_url: string | null;
source_kind: string; source_id: number;
};
export default function DropEditor({ dropId, onClose }: { dropId: number; onClose: () => void }) {
const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState(true);
const [savingId, setSavingId] = useState<number | null>(null);
const [saved, setSaved] = useState<Record<number, boolean>>({});
useEffect(() => {
fetch(`/api/drops/drop-items/${dropId}`)
.then((r) => r.json())
.then((j) => { setItems(j.items || []); setLoading(false); });
}, [dropId]);
function update(id: number, field: keyof Item, value: string) {
setItems((L) => L.map((it) => (it.id === id ? { ...it, [field]: value } : it)));
setSaved((s) => ({ ...s, [id]: false }));
}
async function save(it: Item) {
setSavingId(it.id);
try {
const r = await fetch(`/api/drops/drop-items/${it.id}`, {
method: "PATCH",
headers: { "content-type": "application/json" },
body: JSON.stringify({
headline: it.headline,
blurb: it.blurb,
pro_extra: it.pro_extra,
cta_text: it.cta_text,
cta_url: it.cta_url,
}),
});
if (r.ok) setSaved((s) => ({ ...s, [it.id]: true }));
} finally { setSavingId(null); }
}
return (
<div className="fixed inset-0 z-50 flex items-start justify-center bg-black/40 p-4 overflow-auto" onClick={onClose}>
<div className="card w-full max-w-4xl bg-parchment my-10" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between border-b border-ink/10 px-5 py-3">
<h2 className="text-lg font-semibold">Edit drop #{dropId}</h2>
<button onClick={onClose} className="btn-ghost">Close</button>
</div>
{loading ? (
<div className="p-6 text-ink/50">Loading…</div>
) : items.length === 0 ? (
<div className="p-6 text-ink/50">No items.</div>
) : (
<div className="divide-y divide-ink/10">
{items.map((it) => (
<div key={it.id} className="p-5 space-y-2">
<div className="flex items-center gap-3">
<span className="text-xs text-ink/50">#{it.position}</span>
<span className="tag">{it.source_kind}:{it.source_id}</span>
<div className="ml-auto flex items-center gap-2">
{saved[it.id] && <span className="text-xs text-emerald-700">✓ saved</span>}
<button className="btn" disabled={savingId !== null} onClick={() => save(it)}>
{savingId === it.id ? "Saving…" : "Save"}
</button>
</div>
</div>
<input
value={it.headline}
onChange={(e) => update(it.id, "headline", e.target.value)}
className="w-full font-semibold text-lg"
/>
<textarea
value={it.blurb}
onChange={(e) => update(it.id, "blurb", e.target.value)}
rows={3}
className="w-full text-sm"
/>
<textarea
value={it.pro_extra ?? ""}
onChange={(e) => update(it.id, "pro_extra", e.target.value)}
rows={2}
placeholder="PRO-only extra (tactical hint, leave blank for none)"
className="w-full text-sm bg-brass/10 border-brass/30"
/>
<div className="flex gap-2">
<input
value={it.cta_text ?? ""}
onChange={(e) => update(it.id, "cta_text", e.target.value)}
placeholder="CTA label"
className="w-40"
/>
<input
value={it.cta_url ?? ""}
onChange={(e) => update(it.id, "cta_url", e.target.value)}
placeholder="CTA URL"
className="flex-1"
/>
</div>
</div>
))}
</div>
)}
</div>
</div>
);
}