import { useRef, useState } from 'react'; import { Check, CircleAlert, Hammer, Loader2, Upload } from 'lucide-react'; import { useClient } from 'hooks/useClient'; import { Button } from '@/components/ui/button'; import { readableError } from './errors'; import { useFlash } from './useFlash'; import { useReady } from './useReady'; /** * Publish and Build, from wherever you happen to be. * * ── Why they are in the header and not only in Administration ── * * Publishing is the last step of every edit — write something on Home, choose a carousel, add a drop, * then make it public. It lived in one card at the bottom of one section, so finishing a change meant * leaving the thing you were working on to go and find the button. A person who does not know the * panel simply does not find it, and concludes their edit went live when it did not. * * They are the same actions Administration's card offers. Two entry points to one thing is right here: * that card belongs to a row about the production server and reports what a build produced, while * these are the verbs at the end of a sentence somebody has just written. * * ── Why both, when Publish already builds ── * * They answer different questions, and the difference is not obvious from the words: * * Publish copies what you edited in DEVELOPMENT over production, then renders the site. * Build renders production as it already is. Nothing moves. * * Publish is the one to press after editing anything. Build is for when the content is already * promoted and only the code changed — a template fix, a new rendition ladder, a failed build to * retry. Offering only Publish would mean every retry of a build also re-copied a database and took * another backup, which is a lot of machinery for "try that again". * * ── One busy state, one failure panel ── * * Only one of these can be running, so they share both. A header is one line, and a failure here is * usually a build error — long, and worth reading — so it opens a panel underneath rather than * replacing a label. Positioned absolutely so nothing shifts, and it stays until dismissed: a message * that vanishes on a timer is one somebody was still reading. */ type Action = 'publish' | 'build'; const ROUTES: Record = { publish: '/fofos-admin/_config/static/publish', build: '/fofos-admin/_config/static/build', }; const LABELS: Record = { publish: { idle: 'Publish', busy: 'Publishing…', done: 'Published', title: 'Copy what you have edited into production, then rebuild the site', }, build: { idle: 'Build', busy: 'Building…', done: 'Built', title: 'Rebuild the site from what production already holds. Nothing is copied.', }, }; export const PublishButton = () => { const client = useClient(); const clientRef = useRef(client); clientRef.current = client; const { ready } = useReady(); const [busy, setBusy] = useState(null); const [failure, setFailure] = useState(null); const [done, flashDone] = useFlash(6000); const [finished, setFinished] = useState(null); const run = async (action: Action) => { setBusy(action); setFailure(null); try { const result = await clientRef.current.post<{ ok: boolean; output: string }>(ROUTES[action], {}); // `ok: false` is a REFUSAL the server explains — a backup that could not be taken, a build that // failed. It answers 400 with the reason, which the client throws on, so this is the belt to // that braces rather than the usual path. if (!result.ok) setFailure(result.output || 'It did not say why.'); else { setFinished(action); flashDone(); } } catch (caught) { setFailure(readableError(caught)); } finally { setBusy(null); } }; const label = (action: Action) => { const words = LABELS[action]; if (busy === action) return words.busy; if (done && finished === action) return words.done; return words.idle; }; const icon = (action: Action) => { if (busy === action) return ; if (done && finished === action) return ; if (action === 'publish') return ; return ; }; return (
{failure && ( )} {/* Build first and quieter. Publish is the one to press, so it reads last and solid — the row runs left to right towards the action somebody actually came for. */} {failure && ( /* Absolute, so the header does not grow and push the panel down while somebody reads it. */

That did not work

            {failure}
          
)}
); };