Wires a localStorage/cookie-backed guest cart (per AI_PROMPT.md contract) into the header cart icon and popup, with add-to-cart in the product card and buy panel resolved via the dedicated by-product store-items endpoint. Continue always hands off to the Meshkee customer dashboard for auth/checkout — no login, register, or payment UI is built on the storefront. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
127 lines
5.4 KiB
TypeScript
127 lines
5.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { useCart } from "./CartProvider";
|
|
import { getContinueUrl } from "@/lib/cart";
|
|
import { formatToman } from "@/lib/format";
|
|
import { BagIcon, CloseIcon, MinusIcon, PlusIcon, TrashIcon } from "./icons";
|
|
|
|
export default function MiniCart() {
|
|
const { items, count, total, updateQuantity, removeItem } = useCart();
|
|
const [open, setOpen] = useState(false);
|
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
function onClickAway(e: MouseEvent) {
|
|
if (rootRef.current && !rootRef.current.contains(e.target as Node)) {
|
|
setOpen(false);
|
|
}
|
|
}
|
|
document.addEventListener("mousedown", onClickAway);
|
|
return () => document.removeEventListener("mousedown", onClickAway);
|
|
}, [open]);
|
|
|
|
return (
|
|
<div className="relative" ref={rootRef}>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen((v) => !v)}
|
|
className="relative flex items-center justify-center size-10 md:size-11 rounded-lg border border-line text-ink hover:border-brand hover:text-brand transition-colors"
|
|
aria-label="سبد خرید"
|
|
>
|
|
<BagIcon className="size-5" />
|
|
{count > 0 && (
|
|
<span className="absolute -top-1.5 -end-1.5 flex items-center justify-center size-4.5 rounded-full bg-gold text-[10px] font-bold text-ink">
|
|
{count > 99 ? "99+" : count}
|
|
</span>
|
|
)}
|
|
</button>
|
|
|
|
{open && (
|
|
<div className="absolute end-0 top-full mt-2 w-80 sm:w-96 bg-white border border-line rounded-xl shadow-xl z-50 flex flex-col max-h-[70vh]">
|
|
<div className="flex items-center justify-between px-4 h-12 border-b border-line shrink-0">
|
|
<h3 className="text-sm font-bold text-ink">سبد خرید</h3>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen(false)}
|
|
aria-label="بستن"
|
|
className="p-1 text-body hover:text-ink"
|
|
>
|
|
<CloseIcon className="size-4" />
|
|
</button>
|
|
</div>
|
|
|
|
{items.length === 0 ? (
|
|
<div className="px-4 py-8 text-center text-sm text-body">سبد خرید شما خالی است</div>
|
|
) : (
|
|
<div className="flex-1 overflow-y-auto divide-y divide-line">
|
|
{items.map((line) => (
|
|
<div key={line.id} className="flex gap-3 px-4 py-3">
|
|
<img
|
|
src={line.image}
|
|
alt={line.name}
|
|
className="size-16 shrink-0 rounded-lg object-cover border border-line"
|
|
/>
|
|
<div className="flex-1 min-w-0 flex flex-col">
|
|
<p className="text-[13px] text-ink text-right line-clamp-2">{line.name}</p>
|
|
<div className="mt-auto flex items-center justify-between gap-2">
|
|
<div className="flex items-center gap-1.5 rounded-full border border-line px-1 h-7">
|
|
<button
|
|
type="button"
|
|
onClick={() => updateQuantity(line.id, line.quantity + 1)}
|
|
aria-label="افزایش تعداد"
|
|
className="flex items-center justify-center size-5 text-body hover:text-brand"
|
|
>
|
|
<PlusIcon className="size-3.5" />
|
|
</button>
|
|
<span className="w-5 text-center text-xs font-bold text-ink">
|
|
{line.quantity}
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => updateQuantity(line.id, line.quantity - 1)}
|
|
aria-label="کاهش تعداد"
|
|
className="flex items-center justify-center size-5 text-body hover:text-brand"
|
|
>
|
|
<MinusIcon className="size-3.5" />
|
|
</button>
|
|
</div>
|
|
<span className="text-xs font-bold text-brand whitespace-nowrap">
|
|
{formatToman(line.price * line.quantity)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => removeItem(line.id)}
|
|
aria-label="حذف از سبد خرید"
|
|
className="self-start p-1 text-body hover:text-red-600"
|
|
>
|
|
<TrashIcon className="size-4" />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{items.length > 0 && (
|
|
<div className="px-4 py-3 border-t border-line shrink-0">
|
|
<div className="flex items-center justify-between mb-3">
|
|
<span className="text-sm text-body">جمع کل</span>
|
|
<span className="text-sm font-bold text-ink">{formatToman(total)}</span>
|
|
</div>
|
|
<a
|
|
href={getContinueUrl(items)}
|
|
className="flex items-center justify-center w-full rounded-full bg-brand text-white text-sm font-bold h-11 hover:bg-brand-dark transition-colors"
|
|
>
|
|
ادامه فرآیند خرید
|
|
</a>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|