Files
simode/src/components/MiniCart.tsx
T
amirhosein.ashourlooandClaude Sonnet 5 12d39bc020 Add guest mini-cart with header icon, popup, and customer dashboard handoff
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>
2026-09-11 08:50:38 +03:30

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>
);
}