From ac08f4490b435b8561e55ef018c2eae83a5dc8f8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 24 Jul 2026 19:39:45 -0400 Subject: [PATCH] x --- .../page-builder/ArticleContentForms.tsx | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 apps/admin/src/components/page-builder/ArticleContentForms.tsx diff --git a/apps/admin/src/components/page-builder/ArticleContentForms.tsx b/apps/admin/src/components/page-builder/ArticleContentForms.tsx new file mode 100644 index 0000000..5f11189 --- /dev/null +++ b/apps/admin/src/components/page-builder/ArticleContentForms.tsx @@ -0,0 +1,198 @@ +import type React from 'react'; +import { Image as ImageIcon, Plus, Trash2, UploadCloud } from 'lucide-react'; +import { type PageSection } from '@homaroma/types'; + +type EditingSetter = React.Dispatch | null>>; +type UploadTarget = { type: 'main' | 'slide' | 'gallery'; index?: number }; + +interface BaseProps { + editingSection: Partial; + setEditingSection: EditingSetter; + editingLang: string; + getLocalizedValue: (payload: unknown, field: string) => string; + updateLocalizedValue: (field: string, value: string) => void; + updatePayloadValue: (field: string, value: unknown) => void; +} + +interface MediaProps { + setUploadTarget: (target: UploadTarget | null) => void; + setIsMediaModalOpen: (open: boolean) => void; +} + +function localizedValue(record: Record, field: string, lang: string) { + const value = record[field]; + if (typeof value === 'string') return lang === 'th' ? value : ''; + if (value && typeof value === 'object') return String((value as Record)[lang] || ''); + return ''; +} + +function updateArrayItem( + setEditingSection: EditingSetter, + field: string, + index: number, + updater: (item: Record) => Record, +) { + setEditingSection((previous) => { + if (!previous) return null; + const payload = previous.payload || {}; + const items = Array.isArray(payload[field]) ? [...payload[field]] : []; + items[index] = updater((items[index] || {}) as Record); + return { ...previous, payload: { ...payload, [field]: items } }; + }); +} + +function updateItemLocalized( + setEditingSection: EditingSetter, + field: string, + index: number, + itemField: string, + lang: string, + value: string, +) { + updateArrayItem(setEditingSection, field, index, (item) => ({ + ...item, + [itemField]: { + ...(typeof item[itemField] === 'object' && item[itemField] ? item[itemField] as Record : {}), + [lang]: value, + }, + })); +} + +function removeArrayItem(setEditingSection: EditingSetter, field: string, index: number) { + setEditingSection((previous) => { + if (!previous) return null; + const payload = previous.payload || {}; + const items = Array.isArray(payload[field]) ? payload[field].filter((_, itemIndex) => itemIndex !== index) : []; + return { ...previous, payload: { ...payload, [field]: items } }; + }); +} + +export function ArticleHeaderForm({ + editingSection, + getLocalizedValue, + updateLocalizedValue, + updatePayloadValue, + setUploadTarget, + setIsMediaModalOpen, +}: BaseProps & MediaProps) { + const payload = editingSection.payload || {}; + const imageUrl = typeof payload.imageUrl === 'string' ? payload.imageUrl : ''; + return ( +
+ {['title', 'subtitle', 'category', 'author'].map((field) => ( + + ))} + + +
+ Cover image + +
+
+ ); +} + +export function ArticleGalleryForm({ editingSection, setEditingSection, editingLang, updatePayloadValue, setUploadTarget, setIsMediaModalOpen }: BaseProps & MediaProps) { + const payload = editingSection.payload || {}; + const images = Array.isArray(payload.images) ? payload.images as Record[] : []; + return ( +
+ + {images.map((image, index) => ( +
+
+ +
+ + + + +
+ ))} + +
+ ); +} + +export function ArticleFactsForm({ editingSection, setEditingSection, editingLang, getLocalizedValue, updateLocalizedValue, updatePayloadValue }: BaseProps) { + const payload = editingSection.payload || {}; + const items = Array.isArray(payload.items) ? payload.items as Record[] : []; + return ( +
+ + {items.map((item, index) => ( +
+ updateItemLocalized(setEditingSection, 'items', index, 'title', editingLang, event.target.value)} className="admin-input w-full" placeholder="Fact title" /> + updateItemLocalized(setEditingSection, 'items', index, 'description', editingLang, event.target.value)} className="admin-input w-full" placeholder="Description" /> + +
+ ))} + +
+ ); +} + +export function ArticleVideoForm({ editingSection, getLocalizedValue, updateLocalizedValue, updatePayloadValue }: BaseProps) { + const payload = editingSection.payload || {}; + return ( +
+ + + + +
+ ); +} + +export function ArticleReferencesForm({ editingSection, setEditingSection, editingLang, getLocalizedValue, updateLocalizedValue, updatePayloadValue }: BaseProps) { + const payload = editingSection.payload || {}; + const items = Array.isArray(payload.items) ? payload.items as Record[] : []; + return ( +
+ + {items.map((item, index) => ( +
+ updateItemLocalized(setEditingSection, 'items', index, 'title', editingLang, event.target.value)} className="admin-input w-full" placeholder="Reference title" /> + updateArrayItem(setEditingSection, 'items', index, (record) => ({ ...record, url: event.target.value || undefined }))} className="admin-input w-full" placeholder="https://..." /> + updateItemLocalized(setEditingSection, 'items', index, 'note', editingLang, event.target.value)} className="admin-input w-full md:col-span-2" placeholder="Optional note" /> + +
+ ))} + +
+ ); +}