182 lines
7.1 KiB
TypeScript
182 lines
7.1 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { useEffect, useState } from "react";
|
||
import { createPortal } from "react-dom";
|
||
import { customerContinueCheckoutUrl } from "@/lib/customer";
|
||
import { useCart } from "@/lib/cart";
|
||
import { formatPrice } from "@/lib/format";
|
||
import { PLACEHOLDER } from "@/lib/data";
|
||
import { useSession } from "@/lib/session";
|
||
import { IconClose } from "./Icons";
|
||
import { Media } from "./Media";
|
||
|
||
type Props = {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
};
|
||
|
||
export function CartPopup({ open, onClose }: Props) {
|
||
const { isAuthenticated } = useSession();
|
||
const { items, itemCount, total, removeItem, setQuantity } = useCart();
|
||
const [mounted, setMounted] = useState(false);
|
||
|
||
useEffect(() => {
|
||
setMounted(true);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
const onKeyDown = (event: KeyboardEvent) => {
|
||
if (event.key === "Escape") onClose();
|
||
};
|
||
document.addEventListener("keydown", onKeyDown);
|
||
document.body.style.overflow = "hidden";
|
||
return () => {
|
||
document.removeEventListener("keydown", onKeyDown);
|
||
document.body.style.overflow = "";
|
||
};
|
||
}, [open, onClose]);
|
||
|
||
if (!mounted || !open) return null;
|
||
|
||
const continueHref = customerContinueCheckoutUrl(isAuthenticated, items);
|
||
|
||
return createPortal(
|
||
<div>
|
||
<button type="button" className="fixed inset-0 z-[120] bg-black/40" aria-label="بستن" onClick={onClose} />
|
||
<div
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-label="سبد خرید"
|
||
className="fixed inset-y-0 left-0 z-[130] flex h-dvh w-full max-w-md flex-col bg-white shadow-2xl"
|
||
>
|
||
<div className="flex items-center justify-between border-b border-black/5 px-5 py-4">
|
||
<div>
|
||
<h2 className="text-base font-extrabold">سبد خرید</h2>
|
||
{itemCount > 0 && (
|
||
<p className="mt-0.5 text-xs text-zinc-400">
|
||
{itemCount.toLocaleString("fa-IR")} کالا
|
||
</p>
|
||
)}
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
aria-label="بستن"
|
||
className="grid size-9 place-items-center rounded-full hover:bg-zinc-100"
|
||
>
|
||
<IconClose className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="flex-1 overflow-y-auto px-5 py-4">
|
||
{items.length === 0 ? (
|
||
<div className="flex h-full flex-col items-center justify-center text-center">
|
||
<p className="text-sm text-zinc-500">سبد خرید شما خالی است</p>
|
||
</div>
|
||
) : (
|
||
<ul className="space-y-4">
|
||
{items.map((item) => (
|
||
<li key={item.id} className="flex gap-3 border-b border-black/5 pb-4 last:border-0">
|
||
<Link
|
||
href={item.slug ? `/products/${item.slug}` : "/products"}
|
||
onClick={onClose}
|
||
className="size-20 shrink-0 overflow-hidden rounded-xl border border-black/5"
|
||
>
|
||
<Media src={item.image || PLACEHOLDER.product} alt="" className="size-full" sizes="80px" />
|
||
</Link>
|
||
<div className="min-w-0 flex-1">
|
||
<Link
|
||
href={item.slug ? `/products/${item.slug}` : "/products"}
|
||
onClick={onClose}
|
||
className="line-clamp-2 text-sm font-medium leading-6 hover:text-brand"
|
||
>
|
||
{item.name}
|
||
</Link>
|
||
<div className="mt-2 flex items-center justify-between gap-2">
|
||
<div className="flex items-center overflow-hidden rounded-lg border border-black/10">
|
||
<button
|
||
type="button"
|
||
aria-label="افزایش"
|
||
onClick={() => setQuantity(item.id, item.quantity + 1)}
|
||
className="grid size-8 place-items-center text-sm hover:bg-zinc-50"
|
||
>
|
||
+
|
||
</button>
|
||
<span className="min-w-7 text-center text-xs font-bold">
|
||
{item.quantity.toLocaleString("fa-IR")}
|
||
</span>
|
||
<button
|
||
type="button"
|
||
aria-label="کاهش"
|
||
onClick={() => setQuantity(item.id, item.quantity - 1)}
|
||
className="grid size-8 place-items-center text-sm hover:bg-zinc-50"
|
||
>
|
||
−
|
||
</button>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-sm font-bold">
|
||
{formatPrice(item.price * item.quantity)}
|
||
<span className="mr-0.5 text-[11px] font-normal text-zinc-400">تومان</span>
|
||
</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => removeItem(item.id)}
|
||
aria-label="حذف"
|
||
className="grid size-8 place-items-center rounded-lg text-zinc-400 hover:bg-brand-soft hover:text-brand"
|
||
>
|
||
<svg viewBox="0 0 24 24" fill="none" className="h-4 w-4" aria-hidden>
|
||
<path
|
||
d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2m-8 0v12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V7M10 11v5M14 11v5"
|
||
stroke="currentColor"
|
||
strokeWidth="1.75"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
|
||
<div className="border-t border-black/5 px-5 py-4">
|
||
{items.length > 0 && (
|
||
<div className="mb-3 flex items-center justify-between text-sm">
|
||
<span className="text-zinc-500">جمع کل</span>
|
||
<span className="font-extrabold">
|
||
{formatPrice(total)}
|
||
<span className="mr-0.5 text-xs font-normal text-zinc-400">تومان</span>
|
||
</span>
|
||
</div>
|
||
)}
|
||
{items.length > 0 ? (
|
||
<a
|
||
href={continueHref}
|
||
onClick={onClose}
|
||
className="flex w-full items-center justify-center rounded-xl bg-brand py-3 text-sm text-white hover:bg-[#e51f1f]"
|
||
>
|
||
ادامه
|
||
</a>
|
||
) : (
|
||
<button
|
||
type="button"
|
||
onClick={onClose}
|
||
className="flex w-full items-center justify-center rounded-xl bg-brand-soft py-3 text-sm font-medium text-brand"
|
||
>
|
||
ادامه خرید
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>,
|
||
document.body,
|
||
);
|
||
}
|