'use client'; import { useRef, useState } from 'react'; export type JsonValue = | string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue }; const IMAGE_KEYS = new Set(['cover', 'image', 'avatar', 'gallery', 'logo', 'icon']); const IMAGE_RE = /\.(png|jpe?g|svg|webp|gif|avif)$/i; function looksLikeImage(key: string | undefined, value: string): boolean { if (key && IMAGE_KEYS.has(key)) return true; return IMAGE_RE.test(value) || value.startsWith('/api/uploads/') || value.startsWith('/portfolio/'); } /** Produce an "empty" clone of a sample value, for new array entries. */ function emptyLike(sample: JsonValue | undefined): JsonValue { if (sample === undefined || sample === null) return ''; if (typeof sample === 'string') return ''; if (typeof sample === 'number') return 0; if (typeof sample === 'boolean') return false; if (Array.isArray(sample)) return []; const out: Record = {}; for (const [k, v] of Object.entries(sample)) out[k] = emptyLike(v); return out; } function humanize(key: string): string { return key .replace(/[_-]/g, ' ') .replace(/([a-z])([A-Z])/g, '$1 $2') .replace(/^\w/, (c) => c.toUpperCase()); } export function JsonForm({ value, onChange, fieldKey, depth = 0, }: { value: JsonValue; onChange: (v: JsonValue) => void; fieldKey?: string; depth?: number; }) { // ---- Primitive: string ---- if (typeof value === 'string') { if (looksLikeImage(fieldKey, value)) { return ; } const multiline = value.length > 64 || value.includes('\n'); return multiline ? (