'use client';
import { useState, useEffect, useCallback } from 'react';
import {
Store, Globe, Image as ImageIcon, Phone, Mail, MapPin, Link,
Settings, Menu, SlidersHorizontal, ShieldCheck, RefreshCw,
Save, Plus, Trash2, ChevronDown, CreditCard, Star, MessageSquare, Upload,
} from 'lucide-react';
import { toast } from 'sonner';
import { adminService } from '@/services/api/adminService';
// ─────────────────────────────────────────────────────────────────────────────
// Tabs
// ─────────────────────────────────────────────────────────────────────────────
type Tab = 'branding' | 'footer' | 'megamenu' | 'filters';
const TABS: { id: Tab; label: string; icon: React.ReactNode }[] = [
{ id: 'branding', label: 'Branding & Settings', icon: },
{ id: 'footer', label: 'Footer', icon: },
{ id: 'megamenu', label: 'Mega Menu', icon:
},
{ id: 'filters', label: 'Catalog Filters', icon: },
];
// ─────────────────────────────────────────────────────────────────────────────
// Branding Tab
// ─────────────────────────────────────────────────────────────────────────────
function BrandingTab() {
const [form, setForm] = useState>({
store_name: '', logo_url: '', primary_wordmark_url: '', secondary_wordmark_url: '',
favicon_url: '', support_phone: '', currency_code: 'INR', advance_percent: 20, theme_color: '#6D28D9',
});
const [sizeChart, setSizeChart] = useState<{ size: string; width: string; height: string; depth: string }[]>([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
adminService.getCmsSettings().then((data: Record) => {
const { size_chart, ...rest } = data;
setForm((prev) => ({ ...prev, ...rest }));
if (Array.isArray(size_chart)) setSizeChart(size_chart);
setLoading(false);
}).catch(() => { setLoading(false); toast.error('Failed to load settings'); });
}, []);
const set = (k: string, v: string | number) => setForm((p) => ({ ...p, [k]: v }));
const addSizeRow = () => setSizeChart((rows) => [...rows, { size: '', width: '', height: '', depth: '' }]);
const updateSizeRow = (i: number, field: string, v: string) =>
setSizeChart((rows) => rows.map((r, j) => j === i ? { ...r, [field]: v } : r));
const deleteSizeRow = (i: number) => setSizeChart((rows) => rows.filter((_, j) => j !== i));
const save = async () => {
setSaving(true);
try {
await adminService.updateCmsSettings({ ...form, size_chart: sizeChart } as Record);
toast.success('Store settings saved');
} catch { toast.error('Failed to save'); } finally { setSaving(false); }
};
if (loading) return ;
return (
}>
set('store_name', v)} />
set('support_phone', v)} icon={} />
set('currency_code', v)} />
set('theme_color', v)} />
{/* ── Size Chart ── */}
}>
Configure the global size chart shown on every product detail page under the "Size Chart" tab.
{/* Header */}
{sizeChart.length > 0 && (
{['Size', 'Width', 'Height', 'Depth', ''].map((h) => (
{h}
))}
)}
{/* Rows */}
{sizeChart.map((row, i) => (
updateSizeRow(i, 'size', v)} placeholder="e.g. S / M / L" />
updateSizeRow(i, 'width', v)} placeholder="e.g. 70 mm" />
updateSizeRow(i, 'height', v)} placeholder="e.g. 140 mm" />
updateSizeRow(i, 'depth', v)} placeholder="e.g. 20 mm" />
))}
{sizeChart.length === 0 && (
No size chart rows yet. Add a row to get started.
)}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Footer Tab
// ─────────────────────────────────────────────────────────────────────────────
interface SocialLink { platform: string; url: string; icon: string; }
interface NavLink { label: string; href: string; }
interface FooterColumn { title: string; links: NavLink[]; }
interface PaymentMethod { name: string; icon_url: string; }
function FooterTab() {
const [phone, setPhone] = useState('');
const [email, setEmail] = useState('');
const [address, setAddress] = useState('');
const [copyright, setCopyright] = useState('');
const [socials, setSocials] = useState([]);
const [columns, setColumns] = useState([]);
const [payments, setPayments] = useState([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
adminService.getCmsFooterInfo().then((d: any) => {
setPhone(d.phone || ''); setEmail(d.email || '');
setAddress(d.address || ''); setCopyright(d.copyright || '');
setSocials(d.social_links || []); setColumns(d.columns || []);
setPayments(d.payment_methods || []); setLoading(false);
}).catch(() => { setLoading(false); toast.error('Failed to load footer info'); });
}, []);
const save = async () => {
setSaving(true);
try {
await adminService.updateCmsFooterInfo({ phone, email, address, copyright, social_links: socials, columns, payment_methods: payments });
toast.success('Footer info saved');
} catch { toast.error('Failed to save'); } finally { setSaving(false); }
};
if (loading) return ;
return (
}>
}>
{socials.map((s, i) => (
setSocials(socials.map((x, j) => j === i ? { ...x, platform: v } : x))} placeholder="Platform" />
setSocials(socials.map((x, j) => j === i ? { ...x, url: v } : x))} placeholder="URL" />
setSocials(socials.map((x, j) => j === i ? { ...x, icon: v } : x))} placeholder="Icon key" />
))}
}>
{columns.map((col, ci) => (
setColumns(columns.map((c, j) => j === ci ? { ...c, title: v } : c))} />
{col.links.map((lnk, li) => (
setColumns(columns.map((c, j) => j !== ci ? c : { ...c, links: c.links.map((l, k) => k === li ? { ...l, label: v } : l) }))} placeholder="Label" />
setColumns(columns.map((c, j) => j !== ci ? c : { ...c, links: c.links.map((l, k) => k === li ? { ...l, href: v } : l) }))} placeholder="/path" />
))}
))}
}>
{payments.map((p, i) => (
setPayments(payments.map((x, j) => j === i ? { ...x, name: v } : x))} placeholder="Visa / MasterCard / UPI" />
setPayments(payments.map((x, j) => j === i ? { ...x, icon_url: v } : x))} placeholder="/images/payments/visa.svg" />
))}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Mega Menu Tab
// ─────────────────────────────────────────────────────────────────────────────
const MENU_KEYS = ['shop', 'deals', 'products'] as const;
type MenuKey = typeof MENU_KEYS[number];
interface MenuGroup { title: string; links: NavLink[]; }
interface MenuPromo { image_url: string; badge: string; title: string; href: string; }
interface MenuData { nav_key: MenuKey; groups: MenuGroup[]; promo: MenuPromo | null; featured_category_ids: string[]; }
function MegaMenuTab() {
const [activeKey, setActiveKey] = useState('shop');
const [menus, setMenus] = useState>({
shop: { nav_key: 'shop', groups: [], promo: null, featured_category_ids: [] },
deals: { nav_key: 'deals', groups: [], promo: null, featured_category_ids: [] },
products: { nav_key: 'products', groups: [], promo: null, featured_category_ids: [] },
});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
adminService.getCmsMegaMenu().then((data: any) => {
setMenus({
shop: data.shop || menus.shop,
deals: data.deals || menus.deals,
products: data.products || menus.products,
});
setLoading(false);
}).catch(() => { setLoading(false); toast.error('Failed to load mega menu'); });
}, []);
const menu = menus[activeKey];
const setMenu = (patch: Partial) => setMenus((p) => ({ ...p, [activeKey]: { ...p[activeKey], ...patch } }));
const save = async () => {
setSaving(true);
try {
await adminService.updateCmsMegaMenu({
nav_key: activeKey,
groups: menu.groups,
promo: menu.promo ?? undefined,
featured_category_ids: menu.featured_category_ids,
});
toast.success(`"${activeKey}" menu saved`);
} catch { toast.error('Failed to save'); } finally { setSaving(false); }
};
if (loading) return ;
return (
{/* Nav key selector */}
{MENU_KEYS.map((k) => (
))}
}>
{menu.groups.map((grp, gi) => (
setMenu({ groups: menu.groups.map((g, j) => j === gi ? { ...g, title: v } : g) })} />
{grp.links.map((lnk, li) => (
setMenu({ groups: menu.groups.map((g, j) => j !== gi ? g : { ...g, links: g.links.map((l, k) => k === li ? { ...l, label: v } : l) }) })} placeholder="Label" />
setMenu({ groups: menu.groups.map((g, j) => j !== gi ? g : { ...g, links: g.links.map((l, k) => k === li ? { ...l, href: v } : l) }) })} placeholder="/path" />
))}
))}
}>
setMenu({ promo: { ...(menu.promo || { badge: '', title: '', href: '' }), image_url: v } })} placeholder="/uploads/storefront/promo.webp" />
setMenu({ promo: { ...(menu.promo || { image_url: '', title: '', href: '' }), badge: v } })} placeholder="e.g. HOT SALE" />
setMenu({ promo: { ...(menu.promo || { image_url: '', badge: '', href: '' }), title: v } })} />
setMenu({ promo: { ...(menu.promo || { image_url: '', badge: '', title: '' }), href: v } })} placeholder="/deals" />
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Catalog Filters Tab
// ─────────────────────────────────────────────────────────────────────────────
interface HighlightFilter { label: string; value: string; icon: string; }
interface PriceRange { label: string; min: number | null; max: number | null; }
function CatalogFiltersTab() {
const [highlights, setHighlights] = useState([]);
const [priceRanges, setPriceRanges] = useState([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
adminService.getCmsCatalogFilters().then((d: any) => {
setHighlights(d.highlights || []); setPriceRanges(d.price_ranges || []);
setLoading(false);
}).catch(() => { setLoading(false); toast.error('Failed to load filters'); });
}, []);
const save = async () => {
setSaving(true);
try {
await adminService.updateCmsCatalogFilters({ highlights, price_ranges: priceRanges });
toast.success('Catalog filters saved');
} catch { toast.error('Failed to save'); } finally { setSaving(false); }
};
if (loading) return ;
return (
}>
{highlights.map((h, i) => (
setHighlights(highlights.map((x, j) => j === i ? { ...x, label: v } : x))} placeholder="Label" />
setHighlights(highlights.map((x, j) => j === i ? { ...x, value: v } : x))} placeholder="Value key" />
setHighlights(highlights.map((x, j) => j === i ? { ...x, icon: v } : x))} placeholder="Icon key" />
))}
}>
{priceRanges.map((r, i) => (
))}
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Shared UI primitives
// ─────────────────────────────────────────────────────────────────────────────
function ImageUploadField({ label, value, onChange, placeholder }: {
label: string; value: string; onChange: (v: string) => void; placeholder?: string;
}) {
const [uploading, setUploading] = useState(false);
const handleFileChange = async (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
toast.error('Please select an image file (PNG, JPG, WEBP, SVG)');
return;
}
setUploading(true);
try {
const res = await adminService.uploadStorefrontImage(file);
onChange(res.image_url);
toast.success('Image uploaded successfully');
} catch (err: any) {
toast.error(err?.message || 'Failed to upload image');
} finally {
setUploading(false);
e.target.value = '';
}
};
const previewUrl = value
? (value.startsWith('http') ? value : `https://ifixkartdev.trionixsolution.com${value.startsWith('/') ? '' : '/'}${value}`)
: '';
return (
);
}
function Field({ label, value, onChange, placeholder, icon }: {
label: string; value: string; onChange: (v: string) => void; placeholder?: string; icon?: React.ReactNode;
}) {
return (
{label &&
}
{icon && {icon}}
onChange(e.target.value)}
placeholder={placeholder}
className={`w-full h-9 ${icon ? 'pl-9' : 'px-3'} pr-3 crm-radius-control border border-border bg-card text-[13px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors`}
/>
);
}
function SectionCard({ title, icon, children }: { title: string; icon: React.ReactNode; children: React.ReactNode }) {
return (
{icon}
{title}
{children}
);
}
function SaveBtn({ saving, onClick }: { saving: boolean; onClick: () => void }) {
return (
);
}
function Spinner() {
return (
);
}
// ─────────────────────────────────────────────────────────────────────────────
// Page
// ─────────────────────────────────────────────────────────────────────────────
export default function StorefrontCmsPage() {
const [tab, setTab] = useState('branding');
return (
{/* Header */}
Storefront CMS
Live
Configure store branding, footer, mega-menu, and catalog filters from one place.
{/* Tab Bar */}
{TABS.map((t) => (
))}
{tab === 'branding' && }
{tab === 'footer' && }
{tab === 'megamenu' && }
{tab === 'filters' && }
);
}