From 16340c3b4832e85f988270a6496bbc4760a20f0d Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Tue, 25 Aug 2026 17:33:39 +0330 Subject: [PATCH] Add guest cart drawer, Continue handoff, and shared customer login. Co-authored-by: Cursor --- Ai-data/prompt-cart.txt | 36 +++++++ app/api/auth/handoff/route.ts | 17 ++++ app/api/auth/me/route.ts | 10 ++ app/layout.tsx | 16 ++- components/AccountMenu.tsx | 138 +++++++++++++++++++++++++ components/AppProviders.tsx | 18 ++++ components/CartPopup.tsx | 181 ++++++++++++++++++++++++++++++++ components/Header.tsx | 77 +++++++++----- components/ProductTabs.tsx | 24 ++--- components/ProductView.tsx | 9 +- lib/api.ts | 16 +++ lib/cart.ts | 39 ------- lib/cart.tsx | 187 ++++++++++++++++++++++++++++++++++ lib/customer.ts | 59 +++++++++++ lib/meshkee-server.ts | 42 ++++++++ lib/session.tsx | 175 +++++++++++++++++++++++++++++++ lib/shared-auth-cookie.ts | 123 ++++++++++++++++++++++ lib/site.ts | 12 ++- 18 files changed, 1082 insertions(+), 97 deletions(-) create mode 100644 Ai-data/prompt-cart.txt create mode 100644 app/api/auth/handoff/route.ts create mode 100644 app/api/auth/me/route.ts create mode 100644 components/AccountMenu.tsx create mode 100644 components/AppProviders.tsx create mode 100644 components/CartPopup.tsx delete mode 100644 lib/cart.ts create mode 100644 lib/cart.tsx create mode 100644 lib/customer.ts create mode 100644 lib/meshkee-server.ts create mode 100644 lib/session.tsx create mode 100644 lib/shared-auth-cookie.ts diff --git a/Ai-data/prompt-cart.txt b/Ai-data/prompt-cart.txt new file mode 100644 index 0000000..f059c73 --- /dev/null +++ b/Ai-data/prompt-cart.txt @@ -0,0 +1,36 @@ +Build storefront cart UI + Continue handoff + shared customer login for this Meshkee website. +## Remote only +- Production Meshkee API and live site only — no localhost / local ports. +- Customer app: https://customer. +- Business app: https://business. +Production env: +NEXT_PUBLIC_API_BASE_URL=https://api.meshkee.com/api/v1 +NEXT_PUBLIC_DOMAIN= +NEXT_PUBLIC_CUSTOMER_ORIGIN=https://customer. +NEXT_PUBLIC_BUSINESS_ORIGIN=https://business. + + +## Scope +Website only. Do not rebuild customer checkout, payment, addresses, or account pages — those are global on customer.. + + +## Cart UI +- Header cart icon + count badge +- Popup/drawer: lines, quantity, remove, total, Continue +- Cart lines use store item variant id (not product id) +- Guest cart: localStorage key meshkee-guest-cart + cookie on . +- Guest line: { id, name, slug?, price, quantity, image? } (id = variant id) + + +## Continue +- Logged in → customer./checkout/cart#guestCart= +- Guest → customer./login?redirect=/checkout/cart#guestCart= +- If no guest lines left, open /checkout/cart without the hash + + +## Shared session +- Cookies: meshkee_customer_access_token / meshkee_customer_refresh_token on . (SameSite=Lax; chunk large tokens) +- Hydrate site session from those cookies +- ورود → customer login with redirect=/ +- Logged in: "{firstName} عزیز" menu → profile, orders, logout; “مدیریت وبسایت” only for site admins → business app (handoff when available) +Match existing site design. Keep the change limited to cart popup, Continue handoff, and header auth. \ No newline at end of file diff --git a/app/api/auth/handoff/route.ts b/app/api/auth/handoff/route.ts new file mode 100644 index 0000000..93365f5 --- /dev/null +++ b/app/api/auth/handoff/route.ts @@ -0,0 +1,17 @@ +import { getBearerToken, jsonResponse, meshkeeFetch, unauthorizedResponse } from "@/lib/meshkee-server"; + +export async function POST(request: Request) { + const token = getBearerToken(request); + if (!token) return unauthorizedResponse(); + + const res = await meshkeeFetch("/auth/handoff", { + method: "POST", + token, + }); + + if (!res.ok) { + return jsonResponse({ error: "امکان باز کردن پنل مدیریت نیست." }, res.status); + } + + return jsonResponse(res.data); +} diff --git a/app/api/auth/me/route.ts b/app/api/auth/me/route.ts new file mode 100644 index 0000000..53435fb --- /dev/null +++ b/app/api/auth/me/route.ts @@ -0,0 +1,10 @@ +import { getBearerToken, jsonResponse, meshkeeFetch, unauthorizedResponse } from "@/lib/meshkee-server"; + +export async function GET(request: Request) { + const token = getBearerToken(request); + if (!token) return unauthorizedResponse(); + + const res = await meshkeeFetch("/auth/me", { token }); + if (!res.ok) return jsonResponse({ error: "Unauthorized" }, res.status); + return jsonResponse(res.data); +} diff --git a/app/layout.tsx b/app/layout.tsx index b3d04bc..df9a501 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,8 +1,10 @@ import type { Metadata } from "next"; import { Carter_One, Oswald, Vazirmatn } from "next/font/google"; +import { AppProviders } from "@/components/AppProviders"; import { Footer } from "@/components/Footer"; import { Header } from "@/components/Header"; import { TopHeader } from "@/components/TopHeader"; +import { getTenant } from "@/lib/api"; import { SITE } from "@/lib/site"; import "./globals.css"; @@ -30,14 +32,18 @@ export const metadata: Metadata = { description: SITE.aboutShort, }; -export default function RootLayout({ children }: LayoutProps<"/">) { +export default async function RootLayout({ children }: LayoutProps<"/">) { + const tenant = await getTenant(); + return ( - -
-
{children}
-