import { useCallback, useEffect, useRef, useState } from "react"; import { Copy, ImageUp, Star, Trash2 } from "lucide-react"; import { api } from "../api.js"; import { useAuth } from "../App.jsx"; import { Badge, Button, Dialog, ErrorNote, Panel, Spinner, timeAgo } from "../ui.jsx"; const monthLabel = (m) => new Date(`${m}-15`).toLocaleDateString("en-CA", { month: "long", year: "numeric" }); /** Client page tab: the monthly screenshot checklist the team fills and the audit reads. */ export default function Screenshots({ slug, clientName }) { const { isAdmin } = useAuth(); const [month, setMonth] = useState(null); const [data, setData] = useState(null); const [error, setError] = useState(null); const [busy, setBusy] = useState(null); const [view, setView] = useState(null); // { title, src } const [copied, setCopied] = useState(false); const load = useCallback(() => api(`/api/clients/${slug}/screenshots${month ? `?month=${month}` : ""}`).then((d) => { setData(d); setError(null); }).catch(setError), [slug, month]); useEffect(() => { load(); }, [load]); const act = async (id, fn) => { setBusy(id); setError(null); try { setData(await fn()); } catch (e) { setError(e); } setBusy(null); }; const q = `?month=${data?.month}`; const upload = (shot, file) => act(shot.id, async () => { const res = await fetch(`/api/clients/${slug}/screenshots/${shot.id}${q}`, { method: "POST", credentials: "same-origin", headers: { "X-Filename": encodeURIComponent(file.name), "Content-Type": "application/octet-stream" }, body: file }); const out = await res.json().catch(() => ({})); if (!res.ok) throw new Error(out.error || `Upload failed (${res.status})`); return out; }); const copyChecklist = async () => { const lines = data.shots.map((s, i) => `${i + 1}. ${s.platform}, ${s.label}${s.required ? "" : " (if it exists)"}: ${s.how}`); await navigator.clipboard.writeText(`Screenshots for ${clientName}, ${monthLabel(data.month)}\nUpload them in Quorla → ${clientName} → Screenshots.\n\n${lines.join("\n")}`); setCopied(true); setTimeout(() => setCopied(false), 2000); }; if (!data) return error ? : ; const required = data.shots.filter((s) => s.required); const done = required.filter((s) => s.file).length; return (
} >

{done} of {required.length} required screenshots uploaded for {monthLabel(data.month)}. {done === required.length && " Ready for the audit."}

{data.shots.map((s) => ( upload(s, f)} onRemove={() => act(s.id, () => api(`/api/clients/${slug}/screenshots/${s.id}${q}`, { method: "DELETE" }))} onExample={() => act(s.id, () => api(`/api/clients/${slug}/screenshots/${s.id}/example${q}`, { method: "POST" }))} onView={setView} /> ))}
setView(null)} title={view?.title} wide> {view && {view.title}} ); } function ShotCard({ shot, slug, q, busy, isAdmin, onUpload, onRemove, onExample, onView }) { const input = useRef(null); const [drag, setDrag] = useState(false); const title = `${shot.platform} · ${shot.label}`; const stamp = shot.file?.at ? `&v=${encodeURIComponent(shot.file.at)}` : ""; const src = `/api/clients/${slug}/screenshots/${shot.id}/image${q}${stamp}`; const exampleSrc = `/api/screenshot-examples/${shot.id}`; const pick = (files) => files?.[0] && onUpload(files[0]); return (

{shot.platform}

{shot.label}

{shot.file ? Uploaded : shot.required ? Required : Optional}

{shot.how}

{shot.file ? ( ) : (
{ e.preventDefault(); setDrag(true); }} onDragLeave={() => setDrag(false)} onDrop={(e) => { e.preventDefault(); setDrag(false); pick(e.dataTransfer.files); }} className={`h-56 rounded-lg border border-dashed flex flex-col items-center justify-center gap-2 text-center px-3 ${drag ? "border-brand bg-brand/5" : "border-line bg-canvas"}`} > {busy ? : ( <>
)} { pick(e.target.files); e.target.value = ""; }} />
{shot.file && {shot.file.by ? `${shot.file.by}, ` : ""}{timeAgo(shot.file.at)}} {shot.example && } {shot.file && ( {isAdmin && } )}
); }