/** * Shared helpers for storefront layout CMS payloads. * Public API returns `metadata` (object); admin saves `metadata_json`. */ import { DEFAULT_SECTION_ACTIVE, DEFAULT_SECTION_TITLES, HOMEPAGE_SECTION_KEYS, } from './homepageDefaults'; export type LayoutItem = { content_id?: string; region?: string; type?: string; page?: string; title?: string; subtitle?: string; display_order?: number; is_active?: boolean | string; metadata?: any; metadata_json?: any; [key: string]: any; }; function asObject(raw: unknown): Record { if (!raw) return {}; if (typeof raw === 'string') { try { const parsed = JSON.parse(raw); return parsed && typeof parsed === 'object' ? parsed : {}; } catch { return {}; } } return typeof raw === 'object' ? (raw as Record) : {}; } /** Normalize layout/home (or admin content/all) payload into an array. */ export function normalizeLayoutPayload(value: unknown): LayoutItem[] { if (Array.isArray(value)) return value as LayoutItem[]; if (!value || typeof value !== 'object') return []; const obj = value as Record; for (const key of ['items', 'data', 'results', 'sections', 'content']) { if (Array.isArray(obj[key])) return obj[key] as LayoutItem[]; } return []; } export function parseMetadata(item: LayoutItem | null | undefined): Record { if (!item) return {}; const fromJson = asObject(item.metadata_json); const fromMeta = asObject(item.metadata); // Admin writes metadata_json; public API mirrors into metadata — prefer json when both set const merged = { ...fromMeta, ...fromJson }; if (typeof fromJson.is_active === 'boolean') { merged.is_active = fromJson.is_active; } else if (typeof fromMeta.is_active === 'boolean') { merged.is_active = fromMeta.is_active; } return merged; } /** * Resolve Visible/Hidden from CMS. * Explicit `false` always wins (hide). Unspecified defaults to visible. */ export function resolveIsActive(item: LayoutItem | null | undefined): boolean { if (!item) return false; const meta = parseMetadata(item); const candidates = [ meta.is_active, asObject(item.metadata_json).is_active, asObject(item.metadata).is_active, item.is_active, ]; // Prefer any explicit false first so hide is never missed when sources diverge for (const v of candidates) { if (v === false || v === 'false' || v === 0 || v === '0') return false; } for (const v of candidates) { if (v === true || v === 'true' || v === 1 || v === '1') return true; } return true; } export function normalizeLayoutItem(item: LayoutItem): LayoutItem { const metadata = parseMetadata(item); return { ...item, metadata, metadata_json: metadata, }; } /** * Build active flags + section payloads from layout/home response. * Only `page: home` rows drive the storefront homepage. */ export function buildHomepageSections(layoutData: LayoutItem[] | unknown): { activeSections: Record; sectionsData: Record; layoutLoaded: boolean; } { const activeSections: Record = {}; const sectionsData: Record = {}; const raw = normalizeLayoutPayload(layoutData); const homeRows = raw.filter((item) => { const page = String(item.page || 'home').toLowerCase(); return page === 'home'; }); const layoutLoaded = homeRows.length > 0; if (!layoutLoaded) { for (const key of HOMEPAGE_SECTION_KEYS) { activeSections[key] = true; const titles = DEFAULT_SECTION_TITLES[key]; sectionsData[key] = { region: key, type: key, page: 'home', title: titles?.title || key, subtitle: titles?.subtitle || '', metadata: { section_key: key, is_active: true }, metadata_json: { section_key: key, is_active: true }, }; } return { activeSections, sectionsData, layoutLoaded: false }; } const groups: Record = {}; for (const item of homeRows) { const meta = parseMetadata(item); const key = item.region || meta.section_key || item.type; if (!key) continue; if (!groups[key]) groups[key] = []; groups[key].push(item); } for (const key of Object.keys(groups)) { const items = [...groups[key]].sort( (a, b) => (a.display_order || 0) - (b.display_order || 0) ); // Use the latest row for this region (do not skip inactive — inactivity must apply) const chosen = normalizeLayoutItem(items[items.length - 1] || items[0]); activeSections[key] = resolveIsActive(chosen); sectionsData[key] = chosen; } // Ensure every homepage key exists so Visible sections never vanish from a missing row for (const key of HOMEPAGE_SECTION_KEYS) { if (sectionsData[key]) continue; const titles = DEFAULT_SECTION_TITLES[key]; activeSections[key] = DEFAULT_SECTION_ACTIVE[key] !== false; sectionsData[key] = { region: key, type: key, page: 'home', title: titles?.title || key, subtitle: titles?.subtitle || '', metadata: { section_key: key, is_active: true }, metadata_json: { section_key: key, is_active: true }, }; } return { activeSections, sectionsData, layoutLoaded: true }; } /** True when section should render on the storefront. */ export function isSectionVisible( activeSections: Record, key: string, layoutLoaded: boolean ): boolean { if (!layoutLoaded) { return DEFAULT_SECTION_ACTIVE[key] !== false; } return activeSections[key] === true; }