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:
co-authored by
Claude Sonnet 5
parent
053a9ba361
commit
5152b16d61
+70
-80
@@ -51,81 +51,72 @@ before touching App Router APIs you're not 100% sure of.
|
||||
because different endpoints shape the "same" concept differently (see
|
||||
Data quirks below). Prefer extending an existing `normalize*`/type over
|
||||
adding a parallel one.
|
||||
- **`auth.ts`** — client-only (uses `localStorage`) Meshkee customer auth:
|
||||
register/login/OTP/refresh. Access tokens last **~15 minutes** — this is
|
||||
short enough that you *will* hit expiry during normal use, so every
|
||||
authenticated call must self-heal (see Auth below), not just fail.
|
||||
- **`cart.ts`** — cart/checkout, needs a Bearer token + `businessId`
|
||||
(`GET /tenants/{domain}` and `.../website/business-info` both return the
|
||||
same numeric id as their `id` field — `getBusinessInfo().id` is enough,
|
||||
no separate tenant-resolve call needed).
|
||||
- **`guestCart.ts`** — the storefront's entire cart story (see next
|
||||
section). No token, no backend call, no `businessId` — just
|
||||
`localStorage` + a shared cookie.
|
||||
|
||||
## Auth
|
||||
## Auth, cart & checkout: this site does none of it
|
||||
|
||||
Real in-site login (not a redirect to `customer.sanihome.ir` — that link
|
||||
still exists in the header/footer as a legacy "ورود" pointer but is
|
||||
independent of this system). `AuthProvider` (`src/context/AuthContext.tsx`)
|
||||
+ `LoginModal` (`src/components/auth/LoginModal.tsx`), mounted once in
|
||||
`src/context/Providers.tsx` inside the root layout.
|
||||
**This was built wrong once already** — an earlier pass added real in-site
|
||||
login (password/OTP), a real backend cart (`/businesses/{businessId}/cart`),
|
||||
and a `/checkout` page. That's explicitly **not** how Meshkee storefronts
|
||||
work, per `https://api.meshkee.com/docs/website/AI_PROMPT.md`'s hard rules:
|
||||
login, register, OTP, the real cart, addresses, and payment all belong to
|
||||
**`https://customer.<domain>`** (the customer dashboard) — a *separate app*,
|
||||
not a page on this site. If you're about to add a `/login`, `/register`,
|
||||
`/checkout`, `/cart`, or `/account` route, or call `/auth/*` or
|
||||
`/businesses/{id}/cart*` from this codebase: **stop, re-read the AI_PROMPT.md
|
||||
"Shopping cart on the storefront" and "Shared login" sections first.**
|
||||
|
||||
- Login methods: password (`POST /auth/login`) and SMS OTP
|
||||
(`POST /auth/send-otp` → `POST /auth/login-otp`). Register:
|
||||
`POST /auth/register` (requires `domain: "sanihome.ir"`).
|
||||
- **Confirmed live response shape** (undocumented in OpenAPI, verified by
|
||||
actually registering a test account): `{ accessToken, refreshToken, user }`
|
||||
from register/login/login-otp/refresh alike. `GET /auth/me` returns `{ user }`.
|
||||
- **Password login requires a verified phone number**
|
||||
(`cellNumber is not verified` 401) — a freshly-registered account can still
|
||||
use the tokens *from that register response* immediately, but if the
|
||||
session is lost before verifying, password login is blocked until OTP
|
||||
verification. There's no verify-OTP UI built yet (see Known gaps).
|
||||
- **Token refresh race**: `AuthContext` and `CartContext` each independently
|
||||
notice a 401 and try to refresh. Without coordination this fires two
|
||||
concurrent `/auth/refresh` calls and the second one fails (likely
|
||||
refresh-token rotation server-side) — observed as 500/400 errors. Fixed
|
||||
with an in-flight-promise singleton in `auth.ts`'s `refreshAccessToken()`
|
||||
— **do not** add another independent refresh call anywhere; always go
|
||||
through that function.
|
||||
- `cart.ts` functions take no token parameter — they read the freshest token
|
||||
from storage internally and self-refresh-and-retry once on 401. Gate UI on
|
||||
`useAuth().user` (session identity), not on a captured `accessToken` value.
|
||||
|
||||
## Cart & checkout
|
||||
|
||||
`CartProvider` (`src/context/CartContext.tsx`) fetches the real backend cart
|
||||
whenever `user` changes and exposes `addItem/updateItem/removeItem/refresh`.
|
||||
`CartButton` + `CartPopup` (`src/components/cart/`) live in the header via
|
||||
`Providers`. `/checkout` (`src/app/checkout/page.tsx`) posts to
|
||||
`POST /businesses/{businessId}/cart/checkout` with `payment.type` of `cash`
|
||||
or `transfer` only — no online payment gateway (Zarinpal/Mellat/etc.) is
|
||||
wired up, by explicit scope decision (see Known gaps).
|
||||
|
||||
**Important:** a successful checkout clears the cart server-side. Any flow
|
||||
that calls `checkout()` must also call `useCart().refresh()` afterward, or
|
||||
the header badge and cart state go stale until next reload.
|
||||
|
||||
**Which store-item variant does "add to cart" target?**
|
||||
- On the product/store-item **detail** pages, `ProductPurchasePanel` always
|
||||
resolves a `matchedVariant` from `storeVariants` (even for a single-SKU
|
||||
product with no selectable variations — it just uses the one variant) and
|
||||
adds `matchedVariant.id`.
|
||||
- On **cards** (`ProductCard`), only sources that already carry a specific
|
||||
variant id can quick-add: `Product.variantId`, populated in
|
||||
`normalizeProduct` from `raw.variants[0].id` (store-specials, brand-groups).
|
||||
Cards from the plain `/products` list endpoint have no variant info, so the
|
||||
button renders as a link to the detail page instead of a cart action —
|
||||
never fabricate a variant id to force a quick-add.
|
||||
- **Detecting a logged-in shopper**: read the `meshkee_customer_access_token`
|
||||
cookie (written by the customer dashboard with `Domain=.sanihome.ir`, so
|
||||
it's readable here too) — see `isCustomerLoggedIn()` in `guestCart.ts`.
|
||||
Never build a login UI or call `/auth/login` from this site.
|
||||
- **Guest cart** (`guestCart.ts`): lines are
|
||||
`{ id, name, slug, price, originalPrice?, image, quantity }` where `id` is
|
||||
always a `storeItemVariantId` — never a product id or store-item id.
|
||||
Persisted to `localStorage` (key `meshkee-guest-cart`) **and** a cookie of
|
||||
the same name (`Domain=.sanihome.ir`, skipped if the encoded JSON is
|
||||
>~3500 chars — rely on the URL param instead). `CartProvider`
|
||||
(`src/context/CartContext.tsx`) just wraps this in React state; there is
|
||||
no server round-trip, so `addItem`/`updateItem`/`removeItem` are
|
||||
synchronous.
|
||||
- **"Continue" in the mini-cart** (`CartPopup`) builds a URL with
|
||||
`continueToCheckoutUrl()`: the guest cart, base64url-encoded (exact
|
||||
algorithm from the spec) into a `guestCart` query param, pointed at
|
||||
`https://customer.sanihome.ir/checkout/cart?guestCart=...` if the shopper
|
||||
looks logged in, or `.../login?redirect=...` (wrapping that same cart URL)
|
||||
if not. The dashboard is responsible for decoding it and syncing it into
|
||||
the real server cart after login — this site's job ends at building that
|
||||
URL correctly.
|
||||
- **Which variant does "add to cart" target?**
|
||||
- On the product/store-item **detail** pages, `ProductPurchasePanel`
|
||||
resolves a `matchedVariant` from `storeVariants` (even for a single-SKU
|
||||
product with no selectable variations — it just uses the one variant)
|
||||
and adds `matchedVariant.id`.
|
||||
- On **cards** (`ProductCard`), only sources that already carry a specific
|
||||
variant id can quick-add: `Product.variantId`, populated in
|
||||
`normalizeProduct` from `raw.variants[0].id` (store-specials,
|
||||
brand-groups). Cards from the plain `/products` list endpoint have no
|
||||
variant info, so the button links to the detail page instead — never
|
||||
fabricate a variant id to force a quick-add.
|
||||
- The spec's fuller flow (`GET /store-items/by-product/{productId}`,
|
||||
then: hide the button if no in-stock variant exists, force a picker
|
||||
when more than one is in stock) isn't separately re-implemented per
|
||||
card — the existing variation-picker UI on the detail page already
|
||||
satisfies "shopper must choose before adding."
|
||||
|
||||
## Comments
|
||||
|
||||
`GET/POST /tenants/{domain}/comments` needs no auth on Meshkee's side
|
||||
(`authorName`/`authorEmail` are plain body fields) — the login requirement is
|
||||
a **product decision by this site**, not an API constraint. Gate the comment
|
||||
form in `ProductTabs`' comments panel on `useAuth().user`; when posting, use
|
||||
the logged-in user's name/email. `GET /comments` only returns **approved**
|
||||
comments, so a freshly-posted comment won't reappear on refetch until an
|
||||
admin approves it — the UI appends it optimistically to local state instead
|
||||
of re-fetching, which is correct, not a bug.
|
||||
`GET/POST /tenants/{domain}/comments` needs no auth on Meshkee's side —
|
||||
`authorName`/`authorEmail` are plain body fields, same as the contact form.
|
||||
`ProductTabs`' comments panel is a guest name+email(optional)+text form, no
|
||||
login involved (this also isn't gated on the dashboard cookie — keep it
|
||||
that way; there's no reason to require a customer-dashboard session just to
|
||||
leave a comment). `GET /comments` only returns **approved** comments, so a
|
||||
freshly-posted comment won't reappear on refetch until an admin approves it
|
||||
— the UI appends it optimistically to local state instead of re-fetching,
|
||||
which is correct, not a bug.
|
||||
|
||||
## Data quirks (verified against the live catalog)
|
||||
|
||||
@@ -213,18 +204,17 @@ else the pre-existing UI unchanged).
|
||||
| `/products/id/[id]` | Redirect-only resolver, see "Group-source id vs productId" above |
|
||||
| `/store-items`, `/store-items/[id]` | Variant-level browsing/detail, see above |
|
||||
| `/blog`, `/blog/[slug]` | Blog listing/detail |
|
||||
| `/checkout` | Cart → order, cash/transfer only |
|
||||
| `/contact` | Contact form → `POST /contact-submissions`, toast + reset on success |
|
||||
|
||||
No `/login`, `/register`, `/checkout`, `/cart`, or `/account` route exists
|
||||
here on purpose — see "Auth, cart & checkout" above.
|
||||
|
||||
## Known gaps / explicit scope decisions
|
||||
|
||||
- No OTP-verification UI for a registered-but-unverified phone number — if a
|
||||
returning shopper's session is lost pre-verification, password login fails
|
||||
with a clear error but there's no in-app way to complete verification yet
|
||||
(OTP *login* still works as a workaround).
|
||||
- No online payment gateway integration (Zarinpal/Mellat/etc.) — checkout
|
||||
only supports cash-on-delivery and bank transfer, by explicit user choice
|
||||
over building the bank-redirect + return-URL callback flow.
|
||||
- A throwaway test customer account exists in the live business database
|
||||
from building/verifying this integration: `+989120000001` / "Test Dev".
|
||||
Safe to delete from the Meshkee admin panel.
|
||||
- Login, registration, OTP, the real cart, addresses, and payment are all
|
||||
out of scope for this codebase by design — they live on
|
||||
`customer.sanihome.ir`. Don't reintroduce them here.
|
||||
- A throwaway test customer account was created against the live business
|
||||
database while an earlier (since-reverted) pass tested a real backend
|
||||
cart: `+989120000001` / "Test Dev". Safe to delete from the Meshkee admin
|
||||
panel — it's not used by anything in this codebase anymore.
|
||||
|
||||
Reference in New Issue
Block a user