Add shared customer auth, cart handoff, and account dropdown.
Mirror Meshkee customer sessions onto the storefront, continue checkout on the customer dashboard, and show profile/orders/admin links under the header name. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
c2552af079
commit
c53fb99330
@@ -0,0 +1,119 @@
|
||||
import type { CartItem } from "@/lib/cart-types";
|
||||
|
||||
/** Must match customer dashboard `GUEST_CART_STORAGE_KEY`. */
|
||||
export const MESHKEE_GUEST_CART_KEY = "meshkee-guest-cart";
|
||||
export const LEGACY_GUEST_CART_KEY = "sanihome-guest-cart";
|
||||
export const GUEST_CART_URL_PARAM = "guestCart";
|
||||
|
||||
export type MeshkeeGuestCartItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug?: string;
|
||||
price: number;
|
||||
originalPrice?: number;
|
||||
image: string | null;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
function cookieDomain(): string | undefined {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
const host = window.location.hostname.toLowerCase();
|
||||
if (host === "localhost" || host === "127.0.0.1") return undefined;
|
||||
const parts = host.split(".").filter(Boolean);
|
||||
if (parts.length < 2) return undefined;
|
||||
return `.${parts.slice(-2).join(".")}`;
|
||||
}
|
||||
|
||||
function parseItems(raw: string | null): CartItem[] {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as CartItem[];
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Map storefront cart lines to the shared guest-cart shape (id = variant id). */
|
||||
export function toMeshkeeGuestItems(items: CartItem[]): MeshkeeGuestCartItem[] {
|
||||
return items
|
||||
.map((item) => {
|
||||
const variantId = (item.storeItemVariantId || item.id || "").trim();
|
||||
if (!variantId) return null;
|
||||
return {
|
||||
id: variantId,
|
||||
name: item.name || "محصول",
|
||||
slug: item.slug || undefined,
|
||||
price: item.price,
|
||||
image: item.image ?? null,
|
||||
quantity: Math.max(1, Math.floor(item.quantity) || 1),
|
||||
};
|
||||
})
|
||||
.filter((item): item is MeshkeeGuestCartItem => item != null);
|
||||
}
|
||||
|
||||
export function encodeGuestCartForUrl(items: CartItem[]): string {
|
||||
const payload = toMeshkeeGuestItems(items);
|
||||
const json = JSON.stringify(payload);
|
||||
return btoa(unescape(encodeURIComponent(json)))
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=+$/, "");
|
||||
}
|
||||
|
||||
function writeSharedCookie(items: MeshkeeGuestCartItem[]) {
|
||||
if (typeof document === "undefined") return;
|
||||
const domain = cookieDomain();
|
||||
const maxAge = 60 * 60 * 24 * 30;
|
||||
const encoded = encodeURIComponent(
|
||||
JSON.stringify(
|
||||
items.map(({ id, name, slug, price, originalPrice, quantity }) => ({
|
||||
id,
|
||||
name,
|
||||
slug,
|
||||
price,
|
||||
originalPrice,
|
||||
quantity,
|
||||
image: null as string | null,
|
||||
})),
|
||||
),
|
||||
);
|
||||
if (encoded.length > 3500) return;
|
||||
let cookie = `${MESHKEE_GUEST_CART_KEY}=${encoded}; path=/; max-age=${maxAge}; SameSite=Lax`;
|
||||
if (domain) cookie += `; domain=${domain}`;
|
||||
document.cookie = cookie;
|
||||
}
|
||||
|
||||
export function readGuestCart(): CartItem[] {
|
||||
if (typeof window === "undefined") return [];
|
||||
const primary = parseItems(localStorage.getItem(MESHKEE_GUEST_CART_KEY));
|
||||
if (primary.length > 0) return primary;
|
||||
const legacy = parseItems(localStorage.getItem(LEGACY_GUEST_CART_KEY));
|
||||
if (legacy.length > 0) {
|
||||
writeGuestCart(legacy);
|
||||
return legacy;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function writeGuestCart(items: CartItem[]) {
|
||||
if (typeof window === "undefined") return;
|
||||
const raw = JSON.stringify(items);
|
||||
localStorage.setItem(MESHKEE_GUEST_CART_KEY, raw);
|
||||
localStorage.removeItem(LEGACY_GUEST_CART_KEY);
|
||||
try {
|
||||
writeSharedCookie(toMeshkeeGuestItems(items));
|
||||
} catch {
|
||||
// ignore cookie failures
|
||||
}
|
||||
}
|
||||
|
||||
export function clearGuestCart() {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.removeItem(MESHKEE_GUEST_CART_KEY);
|
||||
localStorage.removeItem(LEGACY_GUEST_CART_KEY);
|
||||
const domain = cookieDomain();
|
||||
let cookie = `${MESHKEE_GUEST_CART_KEY}=; path=/; max-age=0; SameSite=Lax`;
|
||||
if (domain) cookie += `; domain=${domain}`;
|
||||
document.cookie = cookie;
|
||||
}
|
||||
Reference in New Issue
Block a user