211 lines
6.3 KiB
TypeScript
211 lines
6.3 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import Link from 'next/link';
|
|
import { getImageUrl } from '@/lib/utils';
|
|
import { requireLoginForCheckout } from '@/lib/requireLoginForCheckout';
|
|
import type { MegaMenuBadge, MegaMenuData } from '@/services/api/storefrontService';
|
|
|
|
type BadgeKind = MegaMenuBadge;
|
|
|
|
type MegaLink = {
|
|
label: string;
|
|
href: string;
|
|
badge?: BadgeKind;
|
|
accent?: boolean;
|
|
};
|
|
|
|
type MegaGroup = {
|
|
title: string;
|
|
links: MegaLink[];
|
|
};
|
|
|
|
const BADGE_STYLES: Record<BadgeKind, string> = {
|
|
new: 'bg-[#e8f5e9] text-[#43a047]',
|
|
hot: 'bg-[#ffebee] text-[#e57373]',
|
|
trending: 'bg-[#e3f2fd] text-[#1976d2]',
|
|
featured: 'bg-[#e0f7fa] text-[#00acc1]',
|
|
};
|
|
|
|
const BADGE_LABEL: Record<BadgeKind, string> = {
|
|
new: 'NEW',
|
|
hot: 'HOT',
|
|
trending: 'TRENDING',
|
|
featured: 'FEATURED',
|
|
};
|
|
|
|
function parseBadge(value: unknown): BadgeKind | undefined {
|
|
const v = String(value || '')
|
|
.toLowerCase()
|
|
.trim();
|
|
if (v === 'hot' || v === 'new' || v === 'trending' || v === 'featured') {
|
|
return v;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function hasCmsMegaGroups(menu?: MegaMenuData | null): boolean {
|
|
return Boolean(
|
|
menu?.groups?.some(
|
|
(group) =>
|
|
Array.isArray(group?.links) &&
|
|
group.links.some((link) => link?.label && link?.href)
|
|
)
|
|
);
|
|
}
|
|
|
|
export function normalizeCmsMegaMenu(menu: MegaMenuData): {
|
|
groups: MegaGroup[];
|
|
promo: NonNullable<MegaMenuData['promo']> | null;
|
|
} {
|
|
const groups: MegaGroup[] = (menu.groups || [])
|
|
.map((group) => ({
|
|
title: String(group.title || '').trim(),
|
|
links: (group.links || [])
|
|
.filter((link) => link?.label && link?.href)
|
|
.map((link) => {
|
|
const href = String(link.href).trim();
|
|
const label = String(link.label).trim();
|
|
return {
|
|
label,
|
|
href,
|
|
badge: parseBadge(link.badge),
|
|
accent: Boolean(link.accent),
|
|
};
|
|
}),
|
|
}))
|
|
.filter((group) => group.links.length > 0);
|
|
|
|
const promo = menu.promo;
|
|
const hasPromo = Boolean(
|
|
promo && (promo.image_url || promo.title || promo.badge || promo.href)
|
|
);
|
|
|
|
return { groups, promo: (hasPromo && promo) ? promo : null };
|
|
}
|
|
|
|
function MegaBadge({ kind }: { kind: BadgeKind }) {
|
|
return (
|
|
<span
|
|
className={`inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-bold uppercase tracking-wide leading-none ${BADGE_STYLES[kind]}`}
|
|
>
|
|
{BADGE_LABEL[kind]}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
function MegaColumn({ title, links }: MegaGroup) {
|
|
return (
|
|
<div className="min-w-0">
|
|
{title ? (
|
|
<h4 className="text-[15px] font-bold text-[#2d2d2d] mb-4">{title}</h4>
|
|
) : null}
|
|
<ul className="space-y-2.5">
|
|
{links.map((item) => (
|
|
<li key={`${title}-${item.label}-${item.href}`}>
|
|
{item.href === '/checkout' ? (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
if (!requireLoginForCheckout('/checkout')) return;
|
|
window.location.assign('/checkout');
|
|
}}
|
|
className={`inline-flex items-center gap-2 text-[13px] leading-snug transition-colors hover:text-primary cursor-pointer bg-transparent border-0 p-0 ${
|
|
item.accent
|
|
? 'text-primary font-medium'
|
|
: 'text-[#555] font-normal'
|
|
}`}
|
|
>
|
|
<span>{item.label}</span>
|
|
{item.badge ? <MegaBadge kind={item.badge} /> : null}
|
|
</button>
|
|
) : (
|
|
<Link
|
|
href={item.href}
|
|
className={`inline-flex items-center gap-2 text-[13px] leading-snug transition-colors hover:text-primary ${
|
|
item.accent
|
|
? 'text-primary font-medium'
|
|
: 'text-[#555] font-normal'
|
|
}`}
|
|
>
|
|
<span>{item.label}</span>
|
|
{item.badge ? <MegaBadge kind={item.badge} /> : null}
|
|
</Link>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PromoCard({
|
|
promo,
|
|
}: {
|
|
promo: NonNullable<MegaMenuData['promo']>;
|
|
}) {
|
|
const href = promo.href || '/shop';
|
|
const image = getImageUrl(promo.image_url);
|
|
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className="relative block min-h-[220px] lg:min-h-[260px] rounded-xl overflow-hidden group"
|
|
>
|
|
<div
|
|
className="absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-105 bg-[#ecf0f5]"
|
|
style={
|
|
image
|
|
? {
|
|
backgroundImage: `linear-gradient(105deg, rgba(236,240,245,0.92) 0%, rgba(236,240,245,0.55) 42%, rgba(236,240,245,0.15) 100%), url(${image})`,
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
<div className="relative z-10 p-5 md:p-6 flex flex-col justify-center h-full min-h-[220px] lg:min-h-[260px]">
|
|
{promo.badge ? (
|
|
<span className="inline-flex self-start bg-white text-[#222] text-[11px] font-bold tracking-wide px-2.5 py-1 rounded-sm shadow-sm mb-2">
|
|
{promo.badge}
|
|
</span>
|
|
) : null}
|
|
{promo.title ? (
|
|
<p className="text-[22px] md:text-[24px] font-extrabold text-[#1a1a1a] leading-tight max-w-[140px]">
|
|
{promo.title}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export function CmsMegaMenuPanel({ menu }: { menu: MegaMenuData }) {
|
|
const { groups, promo } = normalizeCmsMegaMenu(menu);
|
|
if (groups.length === 0 && !promo) return null;
|
|
|
|
const colCount = groups.length + (promo ? 1 : 0);
|
|
const gridClass =
|
|
colCount >= 4
|
|
? 'lg:grid-cols-[repeat(3,minmax(0,1fr))_minmax(200px,240px)]'
|
|
: colCount === 3
|
|
? 'lg:grid-cols-3'
|
|
: colCount === 2
|
|
? 'lg:grid-cols-2'
|
|
: 'lg:grid-cols-1';
|
|
|
|
return (
|
|
<div className="w-full max-w-full bg-white border border-t-0 border-gray-200 shadow-[0_16px_40px_rgba(0,0,0,0.12)] rounded-b-md overflow-hidden">
|
|
<div
|
|
className={`grid grid-cols-1 sm:grid-cols-2 ${gridClass} gap-6 lg:gap-8 px-5 py-6 md:px-7 md:py-7`}
|
|
>
|
|
{groups.map((group) => (
|
|
<MegaColumn
|
|
key={group.title || group.links[0]?.href}
|
|
title={group.title}
|
|
links={group.links}
|
|
/>
|
|
))}
|
|
{promo ? <PromoCard promo={promo} /> : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|