import { useCallback, useEffect, useRef, useState } from 'react'; import { CircleAlert, CircleCheck, CircleDashed, FolderPlus, Loader2 } from 'lucide-react'; import { useClient } from 'hooks/useClient'; import { Button } from '@/components/ui/button'; import { readableError } from './errors'; /** * `/media` — where the images and the files live. * * Same row shape as the checkout above it, because they are the same kind of fact: a thing that either * is or is not at a known path under the root, with one button that puts it there. * * No console. Making a directory either works or gives one line about why not, and a black transcript * box for a single sentence would make a small thing look like a large one. * * ── The children are listed, and the button repairs ── * * `originals/` and `derived/` are shown whether or not they are there, so a structure that is missing * one reads as INCOMPLETE rather than as fine. The button follows: "Create" when there is nothing, "Create * missing" when part of it exists. It is `mkdir -p` on every path either way, so pressing it can only * add and can never fail on something already correct. */ type Child = { name: string; hint: string; present: boolean; entries: number | null }; type Status = { path: string; present: boolean; blocked: boolean; children: Child[]; complete: boolean }; const count = (entries: number | null) => entries === null ? '' : entries === 0 ? 'empty' : `${entries} item${entries === 1 ? '' : 's'}`; export const MediaFolder = ({ rootPath }: { rootPath: string }) => { const client = useClient(); const clientRef = useRef(client); clientRef.current = client; const [status, setStatus] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const load = useCallback(async () => { try { setStatus(await clientRef.current.get('/fofos-admin/_config/media')); } catch { // Owner-only. The block above already says so once, and twice is two refusals for one cause. setStatus(null); } }, []); // Re-read whenever the root moves — this describes `/media`, and a stale answer is about a // directory nobody is looking at any more. useEffect(() => { void load(); }, [load, rootPath]); const create = async () => { setBusy(true); setError(null); try { const result = await clientRef.current.post<{ ok: boolean; error?: string; status: Status }>( '/fofos-admin/_config/media/create', {}, ); setStatus(result.status); if (!result.ok && result.error) setError(result.error); } catch (failure) { setError(readableError(failure)); void load(); } finally { setBusy(false); } }; if (!status) return null; // Three states and not two: complete, partly there, and absent. A `media/` with one folder missing // looked identical to a finished one under `present`, which is the state most worth seeing. const Icon = status.blocked ? CircleAlert : status.complete ? CircleCheck : CircleDashed; const tone = status.blocked ? 'text-destructive' : status.complete ? 'text-success' : 'text-muted-foreground'; return (

{status.path}

{status.blocked ? (

Something is already here, and it is not a directory.

) : status.complete ? (

Ready.

) : status.present ? (

Partly there.

) : (

Not created yet.

)} {/* The structure, always — the missing folders are the point of showing it. The hint beside each name carries the one distinction that matters: which of the two can be rebuilt. */}
    {status.children.map((child) => (
  • {child.name}/ {child.hint} {child.present && count(child.entries) ? ` · ${count(child.entries)}` : ''}
  • ))}
{!status.blocked && !status.complete && ( )}
{error &&

{error}

}
); };