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,138 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState, type MouseEvent } from "react";
|
||||
import { businessDashboardUrl, customerDashboardUrl } from "@/lib/customer";
|
||||
import { useSession } from "@/lib/session";
|
||||
import { IconChevron, IconUser } from "./Icons";
|
||||
|
||||
const itemClass =
|
||||
"block w-full px-3 py-2.5 text-right text-sm text-ink transition-colors hover:bg-brand-soft hover:text-brand";
|
||||
|
||||
export function AccountMenu({
|
||||
displayName,
|
||||
isWebsiteAdmin,
|
||||
onLogout,
|
||||
compact = false,
|
||||
}: {
|
||||
displayName: string;
|
||||
isWebsiteAdmin: boolean;
|
||||
onLogout: () => void;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [handoffBusy, setHandoffBusy] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const { getAccessToken } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onClickOutside = (event: Event) => {
|
||||
if (menuRef.current && event.target instanceof Node && !menuRef.current.contains(event.target)) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") setOpen(false);
|
||||
};
|
||||
document.addEventListener("mousedown", onClickOutside);
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", onClickOutside);
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
async function openBusinessDashboard(event: MouseEvent<HTMLAnchorElement>) {
|
||||
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
if (handoffBusy) return;
|
||||
|
||||
const baseUrl = businessDashboardUrl();
|
||||
const popup = window.open("about:blank", "_blank");
|
||||
setHandoffBusy(true);
|
||||
try {
|
||||
const token = getAccessToken();
|
||||
const res = await fetch("/api/auth/handoff", {
|
||||
method: "POST",
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
||||
});
|
||||
if (!res.ok) throw new Error("handoff failed");
|
||||
const data = (await res.json()) as { ticket?: string };
|
||||
const url = new URL(baseUrl);
|
||||
if (data.ticket) url.searchParams.set("handoff", data.ticket);
|
||||
if (popup) {
|
||||
popup.opener = null;
|
||||
popup.location.replace(url.toString());
|
||||
} else {
|
||||
window.open(url.toString(), "_blank", "noopener,noreferrer");
|
||||
}
|
||||
} catch {
|
||||
if (popup) popup.location.replace(baseUrl);
|
||||
else window.open(baseUrl, "_blank", "noopener,noreferrer");
|
||||
} finally {
|
||||
setHandoffBusy(false);
|
||||
setOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={menuRef} className="relative">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((value) => !value)}
|
||||
aria-expanded={open}
|
||||
aria-haspopup="menu"
|
||||
className={`flex max-w-[12rem] items-center gap-2 rounded-full px-3 py-2 text-sm hover:bg-zinc-100 ${
|
||||
compact ? "w-full justify-between px-0 hover:bg-transparent" : ""
|
||||
}`}
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<IconUser />
|
||||
<span className="truncate font-medium">
|
||||
{displayName} عزیز
|
||||
</span>
|
||||
</span>
|
||||
<IconChevron className={`w-3.5 h-3.5 shrink-0 transition-transform ${open ? "rotate-90" : "-rotate-90"}`} />
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div
|
||||
role="menu"
|
||||
className={`absolute z-50 mt-2 min-w-[11rem] overflow-hidden rounded-2xl border border-black/5 bg-white py-1 shadow-xl ${
|
||||
compact ? "start-0 end-0" : "start-0"
|
||||
}`}
|
||||
>
|
||||
<a role="menuitem" href={customerDashboardUrl("/profile")} className={itemClass} onClick={() => setOpen(false)}>
|
||||
پروفایل من
|
||||
</a>
|
||||
<a role="menuitem" href={customerDashboardUrl("/orders")} className={itemClass} onClick={() => setOpen(false)}>
|
||||
سفارشات من
|
||||
</a>
|
||||
{isWebsiteAdmin && (
|
||||
<a
|
||||
role="menuitem"
|
||||
href={businessDashboardUrl()}
|
||||
className={itemClass}
|
||||
onClick={openBusinessDashboard}
|
||||
>
|
||||
{handoffBusy ? "در حال انتقال…" : "مدیریت وبسایت"}
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className={`${itemClass} text-brand hover:text-[#e51f1f]`}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
onLogout();
|
||||
}}
|
||||
>
|
||||
خروج
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user