Connect the storefront to the Meshkee Website API.

Products, blog, search, mega menu, and home carousels now read live tenant data, and login goes to the customer portal.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-18 11:22:31 +03:30
co-authored by Cursor
parent 1a2b62463e
commit c0a95a826a
34 changed files with 2458 additions and 401 deletions
+107 -67
View File
@@ -2,11 +2,17 @@
import Link from "next/link";
import { useState } from "react";
import { categories, navLinks } from "@/data/content";
import { toFaDigits } from "@/lib/format";
import { IconBag, IconChevronDown, IconClose, IconHeart, IconMenu, IconSearch, IconUser } from "./Icons";
import { SearchBox } from "@/components/SearchBox";
import { navLinks } from "@/data/content";
import type { MegaColumn } from "@/lib/catalog";
import { CUSTOMER_URL } from "@/lib/site";
import { IconBag, IconChevronDown, IconClose, IconHeart, IconMenu, IconUser } from "./Icons";
export function Header() {
type Props = {
categories?: MegaColumn[];
};
export function Header({ categories = [] }: Props) {
const [open, setOpen] = useState(false);
const [mega, setMega] = useState(false);
const [mobileCats, setMobileCats] = useState(false);
@@ -40,49 +46,50 @@ export function Header() {
</span>
</Link>
<form className="relative mx-auto hidden min-w-0 flex-1 max-w-xl md:block">
<input
type="search"
placeholder="جستجوی لباس نوزاد، دخترانه، یگانه..."
className="h-11 w-full rounded-full border border-line bg-purple-50/60 pr-4 pl-11 text-sm outline-none transition focus:border-purple-500 focus:bg-white"
/>
<span className="pointer-events-none absolute top-1/2 left-3.5 -translate-y-1/2 text-muted">
<IconSearch />
</span>
</form>
<SearchBox
className="mx-auto hidden min-w-0 flex-1 max-w-xl md:block"
inputClassName="h-11 w-full rounded-full border border-line bg-purple-50/60 pr-4 pl-11 text-sm outline-none transition focus:border-purple-500 focus:bg-white"
/>
<div className="ms-auto flex items-center gap-1 sm:gap-2">
<a href="#" className="hidden items-center gap-1.5 rounded-full px-3 py-2 text-sm text-ink hover:bg-purple-50 lg:inline-flex">
<a
href={CUSTOMER_URL}
className="grid size-10 place-items-center rounded-full text-ink hover:bg-purple-50 lg:hidden"
aria-label="ورود"
>
<IconUser />
</a>
<a
href={CUSTOMER_URL}
className="hidden items-center gap-1.5 rounded-full px-3 py-2 text-sm text-ink hover:bg-purple-50 lg:inline-flex"
>
<IconUser />
ورود
</a>
<a href="#" className="relative grid size-10 place-items-center rounded-full text-ink hover:bg-purple-50" aria-label="علاقه‌مندی">
<a
href={CUSTOMER_URL}
className="relative grid size-10 place-items-center rounded-full text-ink hover:bg-purple-50"
aria-label="علاقه‌مندی"
>
<IconHeart />
<span className="absolute top-1 left-1 grid size-4 place-items-center rounded-full bg-rose-500 text-[10px] text-white">
۲
</span>
</a>
<a href="#" className="relative grid size-10 place-items-center rounded-full text-ink hover:bg-purple-50" aria-label="سبد خرید">
<a
href={CUSTOMER_URL}
className="relative grid size-10 place-items-center rounded-full text-ink hover:bg-purple-50"
aria-label="سبد خرید"
>
<IconBag />
<span className="absolute top-1 left-1 grid size-4 place-items-center rounded-full bg-cyan-500 text-[10px] text-white">
۳
</span>
</a>
</div>
</div>
<form className="pb-3 md:hidden">
<div className="relative">
<input
type="search"
placeholder="جستجو در نینی نادری"
className="h-10 w-full rounded-full border border-line bg-purple-50/60 pr-4 pl-10 text-sm outline-none"
/>
<span className="pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-muted">
<IconSearch className="size-4" />
</span>
</div>
</form>
<div className="pb-3 md:hidden">
<SearchBox
placeholder="جستجو در نینی نادری"
inputClassName="h-10 w-full rounded-full border border-line bg-purple-50/60 pr-4 pl-10 text-sm outline-none"
onNavigate={closeAll}
/>
</div>
<nav className="relative hidden h-12 items-center gap-6 text-sm font-medium text-ink lg:flex">
<div
@@ -99,16 +106,16 @@ export function Header() {
دسته‌بندی‌ها
<IconChevronDown className={`size-4 transition ${mega ? "rotate-180" : ""}`} />
</button>
{mega ? <MegaMenu onPick={closeAll} /> : null}
{mega ? <MegaMenu categories={categories} onPick={closeAll} /> : null}
</div>
{navLinks.map((link) => (
<a
<Link
key={link.label}
href={link.href}
className="relative py-3 transition hover:text-purple-700 after:absolute after:right-0 after:bottom-1 after:h-0.5 after:w-0 after:bg-linear-to-l after:from-purple-600 after:to-cyan-500 after:transition-all hover:after:w-full"
>
{link.label}
</a>
</Link>
))}
</nav>
</div>
@@ -125,29 +132,43 @@ export function Header() {
<IconChevronDown className={`size-4 transition ${mobileCats ? "rotate-180" : ""}`} />
</button>
{mobileCats ? (
<div className="grid grid-cols-2 gap-2 border-b border-line/70 py-3">
{categories.map((category) => (
<a
key={category.id}
href="#categories"
onClick={closeAll}
className="rounded-2xl bg-purple-50/70 px-3 py-2 text-xs font-bold"
>
{category.title}
</a>
<div className="space-y-3 border-b border-line/70 py-3">
{categories.map((column) => (
<div key={column.id}>
<Link href={column.href} onClick={closeAll} className="block text-sm font-bold text-purple-700">
{column.name}
</Link>
{column.children.length ? (
<div className="mt-2 grid grid-cols-2 gap-2">
{column.children.map((child) => (
<Link
key={child.id}
href={child.href}
onClick={closeAll}
className="rounded-2xl bg-purple-50/70 px-3 py-2 text-xs font-bold"
>
{child.name}
</Link>
))}
</div>
) : null}
</div>
))}
</div>
) : null}
{navLinks.map((link) => (
<a
<Link
key={link.label}
href={link.href}
onClick={closeAll}
className="border-b border-line/70 py-3 text-ink"
>
{link.label}
</a>
</Link>
))}
<a href={CUSTOMER_URL} onClick={closeAll} className="py-3 text-ink">
ورود / ثبت نام
</a>
</nav>
</div>
) : null}
@@ -155,32 +176,51 @@ export function Header() {
);
}
function MegaMenu({ onPick }: { onPick: () => void }) {
function MegaMenu({ categories, onPick }: { categories: MegaColumn[]; onPick: () => void }) {
return (
<div className="absolute top-full right-0 z-50 w-[min(40rem,calc(100vw-2rem))] pt-2">
<div className="absolute top-full right-0 z-50 w-[min(52rem,calc(100vw-2rem))] pt-2">
<div className="rounded-3xl border border-white/80 bg-white/95 p-5 shadow-card backdrop-blur-[80px]">
<div className="mb-4 flex items-end justify-between">
<div>
<p className="font-display text-xs text-cyan-600">Categories</p>
<p className="text-base font-bold">خرید بر اساس دسته</p>
</div>
<a href="#categories" onClick={onPick} className="text-xs font-medium text-purple-700">
<Link href="/products" onClick={onPick} className="text-xs font-medium text-purple-700">
مشاهده همه
</a>
</div>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
{categories.map((category) => (
<a
key={category.id}
href="#categories"
onClick={onPick}
className="rounded-2xl px-3 py-3 text-right transition hover:bg-white/45"
>
<p className="text-sm font-bold text-ink">{category.title}</p>
<p className="mt-1 text-[11px] text-muted">{toFaDigits(category.count)} مدل</p>
</a>
))}
</Link>
</div>
{categories.length ? (
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{categories.map((column) => (
<div key={column.id}>
<Link
href={column.href}
onClick={onPick}
className="block text-sm font-bold text-purple-700 hover:text-purple-800"
>
{column.name}
</Link>
{column.children.length ? (
<ul className="mt-2 space-y-1.5">
{column.children.map((child) => (
<li key={child.id}>
<Link
href={child.href}
onClick={onPick}
className="block rounded-xl px-2 py-1 text-sm text-muted transition hover:bg-purple-50 hover:text-ink"
>
{child.name}
</Link>
</li>
))}
</ul>
) : null}
</div>
))}
</div>
) : (
<p className="text-sm text-muted">دسته‌بندی‌ها به‌زودی اینجا می‌آیند.</p>
)}
</div>
</div>
);