Revert to guest-cart architecture, drop in-site auth/checkout

Per api.meshkee.com/docs/website/AI_PROMPT.md's hard rules (which an
earlier pass on this branch missed): login, register, OTP, the real
cart, addresses, and payment are the customer dashboard's job
(customer.sanihome.ir), not this storefront's. This site should only
ever run a local guest mini-cart and hand off to the dashboard.

- Remove: AuthContext, LoginModal, lib/auth.ts, lib/cart.ts (the real
  /businesses/{id}/cart integration), and the /checkout page entirely.
- Add lib/guestCart.ts: localStorage + shared cookie
  (meshkee-guest-cart, Domain=.sanihome.ir) cart, storeItemVariantId
  keyed, matching the spec's line shape and ~3500-char cookie-size
  cutoff.
- CartContext is now a thin React wrapper over the guest cart (no
  server round-trip, so add/update/remove are synchronous); CartPopup's
  "Continue" builds the exact customer.sanihome.ir/checkout/cart
  (or /login?redirect=...) URL from the spec, base64url-encoding the
  cart and detecting an existing dashboard session via the shared
  meshkee_customer_access_token cookie.
- ProductCard and ProductPurchasePanel add to the guest cart directly;
  no login gate on add-to-cart anymore.
- Comments panel is a plain guest name/email/text form again (the
  comments API never required auth — gating it on the now-removed
  login system was wrong regardless).
- CONTEXT.md rewritten to document the guest-cart contract explicitly
  and flag this exact mistake so it doesn't get reintroduced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
amirhosein.ashourloo
2026-09-06 15:01:22 +03:30
co-authored by Claude Sonnet 5
parent 053a9ba361
commit 5152b16d61
23 changed files with 351 additions and 1266 deletions
+1 -4
View File
@@ -1,17 +1,14 @@
"use client";
import { useAuth } from "@/context/AuthContext";
import { useCart } from "@/context/CartContext";
export function CartButton() {
const { user } = useAuth();
const { itemCount, openPopup } = useCart();
const { openLoginModal } = useAuth();
return (
<button
type="button"
onClick={user ? openPopup : openLoginModal}
onClick={openPopup}
className="relative flex items-center gap-1.5 rounded-lg border border-border px-3 py-2 transition-colors hover:border-red hover:text-red"
>
<span aria-hidden>🛒</span>
+21 -27
View File
@@ -4,12 +4,12 @@ import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { formatPrice } from "@/data/sample";
import { useCart } from "@/context/CartContext";
import { continueToCheckoutUrl } from "@/lib/guestCart";
import { EmptyState } from "@/components/ui/EmptyState";
export function CartPopup() {
const { cart, popupOpen, closePopup, updateItem, removeItem } = useCart();
const { items, subtotal, popupOpen, closePopup, updateItem, removeItem } = useCart();
const [mounted, setMounted] = useState(false);
const [busyId, setBusyId] = useState<string | null>(null);
useEffect(() => setMounted(true), []);
@@ -22,15 +22,6 @@ export function CartPopup() {
if (!mounted) return null;
const items = cart?.items ?? [];
const changeQty = async (itemId: string, quantity: number) => {
setBusyId(itemId);
if (quantity <= 0) await removeItem(itemId);
else await updateItem(itemId, quantity);
setBusyId(null);
};
return createPortal(
<div aria-hidden={!popupOpen}>
<div
@@ -65,22 +56,18 @@ export function CartPopup() {
<li key={item.id} className="flex gap-3 border-b border-border pb-4">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={item.productImage ?? "/images/placeholder.svg"}
alt={item.productNameFa ?? item.productTitle}
src={item.image || "/images/placeholder.svg"}
alt={item.name}
className="h-16 w-16 shrink-0 rounded-lg border border-border object-contain p-1"
/>
<div className="min-w-0 flex-1">
<p className="line-clamp-2 text-sm font-medium">
{item.productNameFa ?? item.productTitle}
</p>
{item.label && <p className="mt-0.5 text-xs text-muted">{item.label}</p>}
<p className="line-clamp-2 text-sm font-medium">{item.name}</p>
<div className="mt-2 flex items-center justify-between">
<div className="flex items-center rounded-lg border border-border">
<button
type="button"
disabled={busyId === item.id}
onClick={() => changeQty(item.id, item.quantity + 1)}
className="px-2 py-1 text-sm disabled:opacity-40"
onClick={() => updateItem(item.id, item.quantity + 1)}
className="px-2 py-1 text-sm"
aria-label="افزایش تعداد"
>
+
@@ -88,37 +75,44 @@ export function CartPopup() {
<span className="min-w-6 text-center text-xs font-medium">{item.quantity}</span>
<button
type="button"
disabled={busyId === item.id}
onClick={() => changeQty(item.id, item.quantity - 1)}
className="px-2 py-1 text-sm disabled:opacity-40"
onClick={() => updateItem(item.id, item.quantity - 1)}
className="px-2 py-1 text-sm"
aria-label="کاهش تعداد"
>
−
</button>
</div>
<span className="text-sm font-bold text-red-dark">
{formatPrice(item.lineTotal)}
{formatPrice(item.price * item.quantity)}
<span className="mr-0.5 text-[11px] font-normal text-muted">تومان</span>
</span>
</div>
</div>
<button
type="button"
onClick={() => removeItem(item.id)}
aria-label="حذف از سبد"
className="shrink-0 self-start text-muted transition-colors hover:text-red-dark"
>
✕
</button>
</li>
))}
</ul>
)}
</div>
{items.length > 0 && cart && (
{items.length > 0 && (
<div className="border-t border-border p-5">
<div className="mb-4 flex items-center justify-between text-sm">
<span className="text-muted">جمع کل</span>
<span className="text-base font-bold text-red-dark">
{formatPrice(cart.subtotal)}
{formatPrice(subtotal)}
<span className="mr-0.5 text-xs font-normal text-muted">تومان</span>
</span>
</div>
<a
href="/checkout"
href={continueToCheckoutUrl(items)}
className="block w-full rounded-xl bg-red py-3 text-center text-sm font-medium text-white transition-colors hover:bg-red-dark"
>
ادامه فرآیند خرید