import { useEffect, useRef, useState } from 'react'; type Props = { section: string; initial: unknown; }; type Status = | { kind: 'idle' } | { kind: 'saving' } | { kind: 'saved' } | { kind: 'error'; message: string } | { kind: 'invalid' }; export function RawJsonEditor({ section, initial }: Props) { const [value, setValue] = useState(() => JSON.stringify(initial, null, 2)); const [status, setStatus] = useState({ kind: 'idle' }); const taRef = useRef(null); const format = () => { try { const parsed = JSON.parse(value); setValue(JSON.stringify(parsed, null, 2)); setStatus({ kind: 'idle' }); } catch { setStatus({ kind: 'invalid' }); } }; const save = async () => { let body: unknown; try { body = JSON.parse(value); } catch { setStatus({ kind: 'invalid' }); return; } setStatus({ kind: 'saving' }); try { const res = await fetch(`/api/content/${section}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const data = await res.json(); if (!res.ok) { setStatus({ kind: 'error', message: data.message || data.error || 'erreur' }); return; } setStatus({ kind: 'saved' }); setTimeout(() => setStatus({ kind: 'idle' }), 2500); } catch (e) { setStatus({ kind: 'error', message: (e as Error).message }); } }; useEffect(() => { const onKey = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 's') { e.preventDefault(); save(); } }; const ta = taRef.current; ta?.addEventListener('keydown', onKey as EventListener); return () => ta?.removeEventListener('keydown', onKey as EventListener); // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]); return (

Mode avancé — JSON brut, validé Zod côté serveur.