From 22bd117dae1733d7497a41704d5081faacb41a89 Mon Sep 17 00:00:00 2001 From: "diana.dolgolyova" Date: Mon, 30 Mar 2026 00:40:08 +0300 Subject: [PATCH] feat: rich text editor, image crop component, empty DB resilience MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/app/admin/_components/ArrayEditor.tsx | 2 +- src/app/admin/_components/FormField.tsx | 254 +++++++++++++++++- src/app/admin/_components/ImageCropField.tsx | 172 ++++++++++++ src/app/admin/_components/SectionEditor.tsx | 4 +- src/app/admin/about/page.tsx | 2 +- src/app/admin/classes/page.tsx | 18 +- src/app/admin/contact/page.tsx | 4 +- src/app/admin/faq/page.tsx | 2 +- src/app/admin/master-classes/page.tsx | 1 + src/app/admin/news/page.tsx | 181 +------------ src/app/admin/pricing/page.tsx | 2 +- src/app/admin/schedule/page.tsx | 2 +- src/app/admin/team/[id]/page.tsx | 57 +++- src/app/admin/team/page.tsx | 82 ++++-- src/components/sections/About.tsx | 1 + src/components/sections/Classes.tsx | 10 +- src/components/sections/FAQ.tsx | 1 + src/components/sections/MasterClasses.tsx | 1 + src/components/sections/Pricing.tsx | 1 + src/components/sections/Schedule.tsx | 1 + src/components/sections/team/TeamCarousel.tsx | 33 ++- src/lib/content.ts | 21 +- src/lib/db.ts | 1 + src/lib/markup.tsx | 83 ++++++ src/types/content.ts | 3 + 25 files changed, 698 insertions(+), 241 deletions(-) create mode 100644 src/app/admin/_components/ImageCropField.tsx create mode 100644 src/lib/markup.tsx diff --git a/src/app/admin/_components/ArrayEditor.tsx b/src/app/admin/_components/ArrayEditor.tsx index 77c8a1a..28fc1a7 100644 --- a/src/app/admin/_components/ArrayEditor.tsx +++ b/src/app/admin/_components/ArrayEditor.tsx @@ -24,7 +24,7 @@ interface ArrayEditorProps { } export function ArrayEditor({ - items, + items = [] as unknown as T[], onChange, renderItem, createItem, diff --git a/src/app/admin/_components/FormField.tsx b/src/app/admin/_components/FormField.tsx index 61880d6..9457f3e 100644 --- a/src/app/admin/_components/FormField.tsx +++ b/src/app/admin/_components/FormField.tsx @@ -1,5 +1,6 @@ -import { useRef, useEffect, useState, useMemo } from "react"; -import { Plus, X, Upload, Loader2, Link, ImageIcon, AlertCircle } from "lucide-react"; +import { useRef, useEffect, useState, useMemo, useCallback } from "react"; +import { Plus, X, Upload, Loader2, Link, ImageIcon, AlertCircle, Bold, Italic, List, Heading2, Pencil } from "lucide-react"; +import { formatMarkup } from "@/lib/markup"; import { adminFetch } from "@/lib/csrf"; import type { RichListItem } from "@/types/content"; @@ -29,7 +30,7 @@ export function InputField({ onChange(e.target.value)} placeholder={placeholder} className={inputCls} @@ -147,7 +148,7 @@ export function TextareaField({