import { useEffect, useState } from 'react'; import { useClient } from 'hooks/useClient'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { useAutosave } from '../useAutosave'; import type { SubjectRecord, SubjectSection } from './subjects'; /** * What somebody says about a collection or a piece, in the editor column. * * ── One form, two sections ── * * The fields below the description come from the section — see subjects.ts. Everything above them is * shared because it is the same in both: what it is called, and where it lives. * * The carousel is not here. It is chosen beside the photographs it puts on screen, in the panel, where * the consequence of choosing it is visible — a dropdown in a column of text fields would be the one * control whose effect you cannot see. * * ── One draft, one save ── * * Every field writes into one draft object and `useAutosave` sends it whole. Per-field saves would be * eight requests for somebody filling in a form, and the last one to land would win in an order nobody * controls. */ export const SubjectFields = ({ section, record, onSaved, }: { section: SubjectSection; record: T; onSaved: () => void; }) => { const client = useClient(); const [draft, setDraft] = useState>>({}); // Keyed on the ID, not the values: keyed on the values it would fight the typing, pulling every // keystroke back to the last saved one until the save landed. useEffect(() => setDraft({}), [record.id]); const value = (key: string): string => draft[key] ?? ((record as Record)[key] as string | null) ?? ''; const dirty = Object.keys(draft).length > 0; useAutosave(dirty ? JSON.stringify(draft) : null, false, async () => { await client.patch(`${section.endpoint}/${record.id}`, draft); setDraft({}); onSaved(); }); const set = (key: string) => (next: string) => setDraft((was) => ({ ...was, [key]: next })); return (
set('title')(event.target.value)} />
set('slug')(event.target.value)} />

{section.base}/{value('slug')}