/** * @page Homepage & Services Layout Section Manager (`app/(admin)/storefront-sections/page.tsx`) * @purpose Admin CRM console to edit, reorder, upload images, configure nested arrays, and persist dynamic storefront configurations. */ 'use client'; import { useState, useEffect, useRef } from 'react'; import { Save, ChevronDown, ArrowUp, ArrowDown, Eye, EyeOff, RefreshCw, Plus, Trash2, Upload, X, Calendar, } from 'lucide-react'; import { toast } from 'sonner'; import { getAccessToken } from '@/services/api/client'; import { CustomSelect } from '@/components/ui/CustomSelect'; import ConfirmDeleteModal from '@/components/ui/ConfirmDeleteModal'; interface HomepageSection { id: string; section_key: string; name: string; type: string; page: 'home' | 'services'; is_active: boolean; display_order: number; title: string; subtitle: string; metadata_json: Record; } const INITIAL_SECTIONS: HomepageSection[] = [ // Storefront Homepage Sections { id: 'sec_hero', section_key: 'hero_slider', name: 'Hero Carousel Slider', type: 'hero', page: 'home', is_active: true, display_order: 1, title: 'Next-Gen Electronics & OEM Repair Hub', subtitle: 'Up to 40% Off on Top Smartphones, Laptops & Certified Spare Parts', metadata_json: { slides: [ { id: '1', badge: 'Limited Time Offer', title: 'Ultimate Virtual Reality\nExperience Box', subtitle: 'Experience the Future of Entertainment Today', image: '', buttonText: 'Shop Now', buttonUrl: '/products', gradient: 'from-blue-600 via-indigo-600 to-purple-650' }, { id: '2', badge: 'Exclusive Launch', title: 'Next-Gen Performance\nPro Gaming Gear', subtitle: 'Unleash Your Full Creative Power Everywhere', image: '', buttonText: 'Shop Now', buttonUrl: '/products', gradient: 'from-slate-900 via-blue-900 to-indigo-950' } ] } }, { id: 'sec_promo_cards', section_key: 'promo_cards', name: 'Promotional Feature Cards', type: 'promo_cards', page: 'home', is_active: true, display_order: 2, title: 'Top Category Promos', subtitle: 'Featured discounts on VR boxes, audio gear, and accessories', metadata_json: { cards: [ { id: 'promo-1', badge: 'Weekend Discount', title: 'iPhone Vision', subtitle: 'Only for this week...', priceText: 'from $ 239.99', image: '', bgColor: 'bg-[#F2EDFD]', textColor: 'text-[#4A154B]', badgeBg: 'bg-[#7B2CBF] text-white', link: '/products' }, { id: 'promo-2', badge: 'Amazing Deal', title: 'Vibe Your Sound', subtitle: 'This Week Exclusively...', priceText: 'from $ 399.99', image: '', bgColor: 'bg-[#E3F2FD]', textColor: 'text-primary-dark', badgeBg: 'bg-primary text-white', link: '/products' }, { id: 'promo-3', badge: 'Flat 30% OFF', title: 'True Harmony', subtitle: 'Limited Time Only...', priceText: 'from $ 189.99', image: '', bgColor: 'bg-[#FCE4EC]', textColor: 'text-[#880E4F]', badgeBg: 'bg-[#EC407A] text-white', link: '/products' } ] } }, { id: 'sec_categories', section_key: 'featured_categories', name: 'Shop By Categories Grid', type: 'categories', page: 'home', is_active: true, display_order: 3, title: 'Shop By Categories', subtitle: 'Explore high-performance electronics and replacement hardware', metadata_json: { categories: [ { id: 'cat-1', name: 'TV & Speaker', slug: 'tv-speaker', iconName: 'Speaker', image: '', link: '/shop?category=tv-speaker' }, { id: 'cat-2', name: 'Party Speakers', slug: 'party-speakers', iconName: 'Volume2', image: '', link: '/shop?category=party-speakers' }, { id: 'cat-3', name: 'Cameras', slug: 'cameras', iconName: 'Camera', image: '', link: '/shop?category=cameras' }, { id: 'cat-4', name: 'Phones', slug: 'phones', iconName: 'Smartphone', image: '', link: '/shop?category=phones' }, { id: 'cat-5', name: 'Chargers & Cables', slug: 'chargers-cables', iconName: 'Cable', image: '', link: '/shop?category=chargers-cables' }, { id: 'cat-6', name: 'Smart Watches', slug: 'smart-watches', iconName: 'Watch', image: '', link: '/shop?category=smart-watches' } ] } }, { id: 'sec_editorial', section_key: 'editorial_story', name: 'New Arrivals Products', type: 'editorial', page: 'home', is_active: true, display_order: 4, title: 'New Arrivals Products', subtitle: 'Explore the latest smartphones, accessories, and certified OEM spare parts', metadata_json: { items: [] } }, { id: 'sec_promo_banners', section_key: 'promo_banners', name: 'Dual Promotional Banner Row', type: 'promo_banners', page: 'home', is_active: true, display_order: 5, title: 'Featured Banners', subtitle: 'Spotlight on sound systems and power banks', metadata_json: { banners: [ { title: 'Premium Sound Systems', subtitle: 'Up to 50% off on premium bluetooth speakers & audio decks', image: '', buttonText: 'Browse Audio', buttonUrl: '/shop?category=tv-speaker' }, { title: 'Super-Charged Battery Packs', subtitle: 'Stay connected with 20000mAh Power Delivery power banks', image: '', buttonText: 'Shop Chargers', buttonUrl: '/shop?category=chargers-cables' } ] } }, { id: 'sec_deals', section_key: 'deal_of_the_day', name: 'Deals Of The Day & Countdown', type: 'deal', page: 'home', is_active: true, display_order: 6, title: 'Deals Of The Day', subtitle: 'Limited-time discounts on certified OEM screens & batteries', metadata_json: { countdown_end: '2026-12-31T23:59:59Z', products: [ { id: 'deal-1', title: 'HP LaserJet Mono Single Function Laser Printer', image: '', originalPrice: 89, dealPrice: 85, link: '/products/hp-laserjet' }, { id: 'deal-2', title: 'Jajot J2 - Feature Phone, Keypad Mobile phone', image: '', originalPrice: 2000, dealPrice: 1500, link: '/products/jajot-j2' } ] } }, { id: 'sec_recently', section_key: 'recently_launched', name: 'Recently Launched Hardware', type: 'launched', page: 'home', is_active: true, display_order: 7, title: 'Recently Launched', subtitle: 'Latest smartphone accessories & travel gear', metadata_json: { items: [ { id: 'rl-1', title: 'Apple Watch Series 9 GPS + Cellular', price: 669, image: '', link: '/products/apple-watch-s9' }, { id: 'rl-2', title: 'Ceptics Multi Travel Adapter Plug', price: 300, image: '', link: '/products/ceptics-adapter' }, { id: 'rl-3', title: "D'Wild USB Type C 100W PD Cable", price: 9, image: '', link: '/products/dwild-cable' } ] } }, { id: 'sec_brands', section_key: 'official_brands', name: 'Official Tech Brands Showcase', type: 'brands', page: 'home', is_active: true, display_order: 8, title: 'Official Tech Brands', subtitle: 'Authorized brand partners', metadata_json: { brands: [ { name: 'Apple', logo: 'apple', link: '/shop?brand=apple' }, { name: 'Samsung', logo: 'samsung', link: '/shop?brand=samsung' }, { name: 'Sony', logo: 'sony', link: '/shop?brand=sony' }, { name: 'Dell', logo: 'dell', link: '/shop?brand=dell' }, { name: 'Bose', logo: 'bose', link: '/shop?brand=bose' } ] } }, { id: 'sec_ultimate', section_key: 'ultimate_tech', name: 'Ultimate Tech Gadgets & Banner Grid', type: 'ultimate_tech', page: 'home', is_active: true, display_order: 9, title: 'Ultimate Tech Gadgets', subtitle: 'High-end smart home appliances, wearables, and audio gear', metadata_json: { banners: [ { title: 'Smart Watches', image: '', buttonText: 'Explore Watches', buttonUrl: '/shop?category=smart-watches' }, { title: 'VR Gaming Headsets', image: '', buttonText: 'VR Gear', buttonUrl: '/shop?category=accessories' } ] } }, { id: 'sec_blog', section_key: 'tech_blog', name: 'Tech Blog & Featured Articles', type: 'blog', page: 'home', is_active: true, display_order: 10, title: 'From Our Articles', subtitle: 'Latest tech news, repair guides, and trends', metadata_json: { articles: [ { id: 'b-1', title: 'The Future of Wireless Audio: True Stereo Earbuds', excerpt: 'Explore how audio drivers and high bitrates are redefining our daily listening routines...', date: 'July 10, 2026', author: 'Sarah Connor', readTime: '5 min read', image: '', link: '/blog/wireless-audio' }, { id: 'b-2', title: 'Top 5 Tech Gadgets Every Student Needs in 2026', excerpt: 'From digital note-taking tablets to portable power delivery adapters...', date: 'July 05, 2026', author: 'James Smith', readTime: '8 min read', image: '', link: '/blog/student-gadgets' } ] } }, { id: 'sec_trust', section_key: 'trust_badges', name: 'Service Guarantee & Trust Badges Bar', type: 'badges', page: 'home', is_active: true, display_order: 11, title: 'Certified Quality Guarantee', subtitle: 'Free Shipping, 1-Year Warranty & 24/7 Technical Support', metadata_json: { features: [ { title: '6-Month Spare Parts Warranty', desc: 'Secure protective repair warranties', icon: 'ShieldCheck' }, { title: 'Skilled Tech Diagnostics', desc: 'Certified technician repairs', icon: 'Wrench' }, { title: 'Fast Doorstep Repairs', desc: 'In-home diagnostic booking', icon: 'Clock' } ] } }, // Header & Navigation Dynamic Sections { id: 'sec_announcement_bar', section_key: 'announcement_bar', name: 'Announcement Bar Links (Top Bar)', type: 'announcement_bar', page: 'home', is_active: true, display_order: 0, title: 'Top Navigation Links', subtitle: 'Links displayed in the announcement bar above the header', metadata_json: { links: [ { label: 'Track Order', url: '/track-order', icon: 'package' }, { label: 'About Us', url: '/about' }, { label: 'Blog', url: '/blog' }, { label: 'Contact Us', url: '/contact' }, { label: 'FAQs', url: '/faqs' } ] } }, { id: 'sec_nav_links', section_key: 'nav_links', name: 'Main Navigation Links (Navbar)', type: 'nav_links', page: 'home', is_active: true, display_order: 0, title: 'Main Navigation Menu', subtitle: 'Links displayed in the primary navigation bar', metadata_json: { links: [ { label: 'Home', url: '/' }, { label: 'Shop', url: '/shop', hasDropdown: true }, { label: 'Service', url: '/repair-services' }, { label: 'Top Deals', url: '/shop?tag=deals' }, { label: 'Featured Products', url: '/shop?featured=true' } ] } }, // Service E-commerce Booking Page Sections { id: 'sec_srv_hero', section_key: 'service_hero', name: 'Service Hero Banner', type: 'service_hero', page: 'services', is_active: true, display_order: 12, title: 'Mobile phone repair at your doorstep', subtitle: 'Select your device, get an instant quote, and book skilled technicians to fix your phone screen, battery, or hardware issues at your home or nearest iFixKart store.', metadata_json: { badge_tag: 'iFixKart Trusted Repair Partner', button_text: 'Book Repair Now', gradient_from: '#00b574', gradient_to: '#009b62' } }, { id: 'sec_srv_steps', section_key: 'service_how_it_works', name: 'Service How It Works (Steps)', type: 'service_how_it_works', page: 'services', is_active: true, display_order: 13, title: 'How it works', subtitle: 'Get your device restored in 3 simple steps', metadata_json: { steps: [ { number: '1', title: 'Check Price', desc: 'Select your phone model and choice of repairs to see your custom instant pricing quote.' }, { number: '2', title: 'Schedule Service', desc: 'Choose a convenient date and location - your home, office, or our local service workshop.' }, { number: '3', title: 'Get Device Repaired', desc: 'Our certified technician handles the repair on-site. Test it, pay, and receive your 6-month warranty.' } ] } }, { id: 'sec_srv_why', section_key: 'service_why_us', name: 'Service Why Choose Us (Cards)', type: 'service_why_us', page: 'services', is_active: true, display_order: 14, title: 'Why Choose iFixKart?', subtitle: 'Top tier hardware repair guarantees', metadata_json: { cards: [ { title: 'Premium Quality Parts', desc: 'We source only certified top-grade components to guarantee perfect compatibility and longevity.', icon: 'ShieldCheck' }, { title: 'Instant On-Site Repair', desc: 'No need to leave your phone at a shop for days. Most repairs completed in under 45 minutes.', icon: 'Clock' }, { title: '6 Months Warranty', desc: 'Every screen and spare part replacement includes our worry-free protection warranty cover.', icon: 'ThumbsUp' } ] } }, { id: 'sec_srv_faq', section_key: 'service_faq', name: 'Service FAQ Q&A pairs', type: 'service_faq', page: 'services', is_active: true, display_order: 15, title: 'Frequently Asked Questions', subtitle: 'Got questions? We have answers.', metadata_json: { faqs: [ { question: 'What happens when I place my booking?', answer: 'Our support rep confirms the time slot. A technician is assigned and visits your address to handle the repair on-site.' }, { question: 'How do I pay for the service?', answer: 'You pay only after the repair is complete and verified. We accept UPI, Cards, Cash, and online payments.' }, { question: 'Is there a warranty on parts replaced?', answer: 'Yes! All screens and battery replacements include a worry-free 6-month replacement warranty cover.' }, { question: 'Will my data remain secure?', answer: 'We strictly ensure data security. Repair is conducted in front of you, ensuring zero access to storage components.' } ] } } ]; interface SectionImageUploaderProps { value: string; onChange: (url: string) => void; recommendedSize?: string; } const SectionImageUploader: React.FC = ({ value, onChange, recommendedSize }) => { const [dragActive, setDragActive] = useState(false); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); const handleDrag = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (e.type === "dragenter" || e.type === "dragover") { setDragActive(true); } else if (e.type === "dragleave") { setDragActive(false); } }; const handleDrop = async (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { await uploadFile(e.dataTransfer.files[0]); } }; const handleChange = async (e: React.ChangeEvent) => { e.preventDefault(); if (e.target.files && e.target.files[0]) { await uploadFile(e.target.files[0]); } }; const uploadFile = async (file: File) => { const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; if (!allowedTypes.includes(file.type)) { toast.error('Only JPG, JPEG, PNG, or WebP images are allowed.'); return; } const maxSize = 20 * 1024 * 1024; if (file.size > maxSize) { toast.error('Image is larger than 20MB.'); return; } setUploading(true); const formData = new FormData(); formData.append('file', file); const loaderId = toast.loading('Uploading media...'); try { const token = getAccessToken(); const backendUrl = ''; const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${backendUrl}/api/v1/admin/storefront/upload-image`, { method: 'POST', headers, body: formData, }); if (res.ok) { const data = await res.json(); onChange(data.image_url); toast.success('Section image uploaded', { id: loaderId }); } else { const errData = await res.json().catch(() => ({})); const msg = errData.detail || 'Image upload failed.'; toast.error(`Upload failed: ${msg}`, { id: loaderId }); } } catch (err: any) { toast.error('Network error or CORS issue. Please check console for details.', { id: loaderId }); console.error('File upload error:', err); } finally { setUploading(false); } }; const handleRemove = (e: React.MouseEvent) => { e.stopPropagation(); onChange(''); if (fileInputRef.current) fileInputRef.current.value = ''; }; const backendUrl = process.env.NEXT_PUBLIC_API_URL || ''; const displayUrl = value ? value.startsWith('http://') || value.startsWith('https://') || value.startsWith('data:') ? value : `${backendUrl}${value.startsWith('/') ? value : `/${value}`}` : ''; return (
e.stopPropagation()}>
fileInputRef.current?.click()} className={`relative w-full h-32 crm-radius-card border border-dashed flex flex-col items-center justify-center p-3 text-center cursor-pointer transition-colors ${ dragActive ? 'border-primary bg-primary/5' : 'border-border bg-muted/20 hover:bg-muted/40 hover:border-primary/40' }`} > {uploading ? (
Uploading...
) : displayUrl ? (
Preview
Change
) : (
Click to upload or drag
{recommendedSize && (
{recommendedSize}
)}
)}
); }; interface UppercaseButtonProps { value: string; onChange: (val: string) => void; } const UppercaseButton: React.FC = ({ value, onChange }) => { return ( ); }; // ─── ProductPicker ──────────────────────────────────────────────────────────── interface ProductPickerProps { sectionKey: string; pinnedSlugs: string[]; onUpdate: (slugs: string[]) => void; label?: string; } const ProductPicker: React.FC = ({ pinnedSlugs, onUpdate, label }) => { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [searching, setSearching] = useState(false); const [allProducts, setAllProducts] = useState([]); const [loaded, setLoaded] = useState(false); // Lazy-load full product list on first focus const loadProducts = async () => { if (loaded) return; setSearching(true); try { const backendUrl = ''; const res = await fetch(`${backendUrl}/api/v1/catalog/products/all?page=1&limit=200`); if (res.ok) { const data = await res.json(); const list = Array.isArray(data) ? data : data?.products || []; setAllProducts(list); setResults(list.slice(0, 12)); } } catch {} setSearching(false); setLoaded(true); }; useEffect(() => { if (!loaded || allProducts.length === 0) { setResults([]); return; } const q = query.trim().toLowerCase(); if (!q) { setResults(allProducts.slice(0, 12)); return; } setResults(allProducts.filter((p: any) => (p.name || p.title || '').toLowerCase().includes(q) || (p.slug || '').toLowerCase().includes(q) ).slice(0, 20)); }, [query, allProducts, loaded]); const addProduct = (p: any) => { const slug = p.slug || p.product_id || p.id || p.name; if (!slug || pinnedSlugs.includes(slug)) return; onUpdate([...pinnedSlugs, slug]); }; const removeProduct = (slug: string) => { onUpdate(pinnedSlugs.filter((s) => s !== slug)); }; const getDisplayName = (p: any) => p.name || p.title || p.slug || p.product_id || 'Unknown'; const getSlug = (p: any) => p.slug || p.product_id || p.id || p.name; return (
e.stopPropagation()}>
{label || 'Pinned products'}
{pinnedSlugs.length > 0 && (
{pinnedSlugs.map((slug) => { const match = allProducts.find((p: any) => getSlug(p) === slug); const name = match ? getDisplayName(match) : slug; return ( {name} ); })}
)}
setQuery(e.target.value)} className="w-full h-9 px-3 bg-card border border-border crm-radius-control text-[13px] text-foreground focus:outline-none focus:border-primary pr-8" /> {searching && ( )}
{loaded && results.length > 0 && (
{results.map((p: any) => { const slug = getSlug(p); const isPinned = pinnedSlugs.includes(slug); return ( ); })}
)} {pinnedSlugs.length === 0 && (

No products pinned. This section will use catalog products automatically.

)}
); }; // ────────────────────────────────────────────────────────────────────────────── export default function StorefrontSectionsPage() { const [sections, setSections] = useState(INITIAL_SECTIONS); const [activeTab, setActiveTab] = useState<'home' | 'services'>('home'); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [activeEditingId, setActiveEditingId] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; message: string; onConfirm: () => void; }>({ isOpen: false, message: '', onConfirm: () => {}, }); const requestDeleteConfirm = (message: string, onConfirm: () => void) => { setDeleteConfirm({ isOpen: true, message, onConfirm, }); }; const fetchSections = async () => { setLoading(true); try { const token = getAccessToken(); const backendUrl = ''; const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${backendUrl}/api/v1/admin/storefront/content/all`, { headers }); if (res.ok) { const data = await res.json(); if (data && data.length > 0) { const dbMap = new Map(); data.forEach((d: any) => { const key = d.metadata?.section_key || d.region; dbMap.set(key, d); }); const merged = INITIAL_SECTIONS.map((initSec, idx) => { const dbSec = dbMap.get(initSec.section_key); if (dbSec) { return { id: dbSec.content_id, section_key: initSec.section_key, name: initSec.name, type: dbSec.type, page: (initSec.page || (initSec.section_key.startsWith('service_') ? 'services' : 'home')) as any, is_active: typeof dbSec.metadata_json?.is_active === 'boolean' ? dbSec.metadata_json.is_active : true, display_order: dbSec.display_order || (idx + 1), title: dbSec.title || initSec.title, subtitle: dbSec.subtitle || initSec.subtitle, metadata_json: { ...initSec.metadata_json, ...dbSec.metadata_json, } }; } return { ...initSec, display_order: idx + 1 }; }); // Sort by display order merged.sort((a, b) => a.display_order - b.display_order); setSections(merged); } } } catch { toast.error('Failed to load live configurations. Using default templates.'); } finally { setLoading(false); } }; useEffect(() => { fetchSections(); }, []); const moveSection = (index: number, direction: 'up' | 'down') => { const filtered = sections.filter((s) => (s.page || (s.section_key.startsWith('service_') ? 'services' : 'home')) === activeTab); const targetIndex = direction === 'up' ? index - 1 : index + 1; if (targetIndex < 0 || targetIndex >= filtered.length) return; const item1 = filtered[index]; const item2 = filtered[targetIndex]; const updated = sections.map((s) => { if (s.id === item1.id) return { ...s, display_order: item2.display_order }; if (s.id === item2.id) return { ...s, display_order: item1.display_order }; return s; }); updated.sort((a, b) => a.display_order - b.display_order); setSections(updated); toast.success('Section order updated. Click Save & publish to apply.'); }; const toggleActive = async (id: string) => { const updated = sections.map((s) => { if (s.id === id) { const next = !s.is_active; toast.success(`${s.name} is now ${next ? 'visible on the storefront' : 'hidden on the storefront'}`); return { ...s, is_active: next }; } return s; }); setSections(updated); await saveSectionsToBackend(updated); }; const saveSectionsToBackend = async (sectionsToSave: HomepageSection[]) => { setSaving(true); try { const payload = { items: sectionsToSave.map((s) => ({ content_id: s.id.startsWith('sec_') ? null : s.id, page: s.page || (s.section_key.startsWith('service_') ? 'services' : 'home'), region: s.section_key, type: s.type, title: s.title, subtitle: s.subtitle, display_order: s.display_order, metadata_json: { ...s.metadata_json, section_key: s.section_key, is_active: s.is_active } })) }; const token = getAccessToken(); const backendUrl = ''; const headers: Record = { 'Content-Type': 'application/json' }; if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${backendUrl}/api/v1/admin/storefront/content/bulk-save`, { method: 'POST', headers, body: JSON.stringify(payload) }); if (res.ok) { toast.success('Layout published to the storefront'); await fetchSections(); } else { toast.error('Could not save the layout'); } } catch { toast.error('Could not reach the server to save the layout'); } finally { setSaving(false); } }; const handleImageUpload = async (e: React.ChangeEvent, callback: (url: string) => void) => { if (!e.target.files?.[0]) return; const file = e.target.files[0]; const formData = new FormData(); formData.append('file', file); const loaderId = toast.loading('Uploading image...'); try { const token = getAccessToken(); const backendUrl = ''; const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${backendUrl}/api/v1/admin/storefront/upload-image`, { method: 'POST', headers, body: formData, }); if (res.ok) { const data = await res.json(); callback(data.image_url); toast.success('Section image uploaded', { id: loaderId }); } else { const errData = await res.json().catch(() => ({})); const msg = errData.detail || 'Image upload failed.'; toast.error(`Upload failed: ${msg}`, { id: loaderId }); } } catch { toast.error('Network error uploading image.', { id: loaderId }); } }; const updateMetadataField = (sectionKey: string, field: string, value: any) => { setSections((prev) => prev.map((s) => { if (s.section_key === sectionKey) { return { ...s, metadata_json: { ...s.metadata_json, [field]: value } }; } return s; }) ); }; // Render Section Field Editors dynamically based on layout type const renderSectionFields = (sec: HomepageSection) => { const meta = sec.metadata_json || {}; switch (sec.section_key) { case 'hero_slider': { const slides = meta.slides || []; return (
Swiper Slide Cards
{slides.map((slide: any, idx: number) => (
Slide {idx + 1}
{ const updated = [...slides]; updated[idx].badge = e.target.value; updateMetadataField(sec.section_key, 'slides', updated); }} className="w-full h-9 px-3 bg-card border border-border crm-radius-control text-[13px] text-foreground focus:outline-none focus:border-primary pr-10" /> { const updated = [...slides]; updated[idx].badge = val; updateMetadataField(sec.section_key, 'slides', updated); }} />
{ const updated = [...slides]; updated[idx].buttonText = e.target.value; updateMetadataField(sec.section_key, 'slides', updated); }} className="w-full px-3 py-2 bg-card border border-border crm-radius-control text-[13px] text-foreground focus:outline-none focus:border-primary resize-none" />