Add guest cart drawer, Continue handoff, and shared customer login.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
ad05241a77
commit
16340c3b48
@@ -0,0 +1,181 @@
|
||||
"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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user