feat: rich text editor, image crop component, empty DB resilience

- RichTextarea with toolbar (Bold, Italic, List, Heading) + Ctrl+B/I
  hotkeys (layout-independent), active state highlighting, preview mode
- Shared ImageCropField component (replaces duplicate in news/classes)
  with drag-to-reposition, Ctrl+scroll zoom, compact layout
- SectionEditor defaultData prop — all admin pages handle empty DB
- Team: section title editable, toast notifications, unsaved data warning
  on navigation (back button, sidebar links, browser close)
- Carousel: continuous card wrapping during drag, edge fade for small teams
- Markup renderer: **bold**, *italic*, ## headings, 🤍 bullet points
- Empty DB guards on all public site sections
- Fix: upload error handling, contact phone field, "team" section key
This commit is contained in:
2026-03-30 00:40:08 +03:00
parent e56a6a1608
commit 22bd117dae
25 changed files with 698 additions and 241 deletions
+3 -1
View File
@@ -7,6 +7,7 @@ import { adminFetch } from "@/lib/csrf";
interface SectionEditorProps<T> {
sectionKey: string;
title: string;
defaultData?: Partial<T>;
children: (data: T, update: (data: T) => void) => React.ReactNode;
}
@@ -15,6 +16,7 @@ const DEBOUNCE_MS = 800;
export function SectionEditor<T>({
sectionKey,
title,
defaultData,
children,
}: SectionEditorProps<T>) {
const [data, setData] = useState<T | null>(null);
@@ -30,7 +32,7 @@ export function SectionEditor<T>({
if (!r.ok) throw new Error("Failed to load");
return r.json();
})
.then(setData)
.then((loaded) => setData(defaultData ? { ...defaultData, ...loaded } as T : loaded))
.catch(() => setError("Не удалось загрузить данные"))
.finally(() => setLoading(false));
}, [sectionKey]);