- Real in-site login/register (password + SMS OTP) against Meshkee's /auth API, with self-healing access-token refresh (15 min lifetime) and an in-flight-refresh guard so AuthContext and CartContext don't race each other into a failed concurrent refresh. - Real cart: header cart button/badge, a cart popup, and a working "add to cart" on both product cards (where a specific variant id is known) and the product/store-item detail buy-box (always resolves a purchasable variant, even for single-SKU products with no selectable variation). - /checkout: address + cash/bank-transfer payment, posts to the real checkout API and creates an order; refreshes the cart afterward so the header badge doesn't go stale. - New /store-items and /store-items/[id] pages (variant-level browsing, separate from the product-level /products catalog), cross-referencing the parent product for images/description/technical-info. - Product gallery: click-to-open fullscreen lightbox alongside the existing hover-zoom. - Comments tab wired to the real comments API, gated to logged-in users (a product decision — the API itself doesn't require auth) with a login prompt otherwise. - Similar-products row on product/store-item detail pages. - Contact page wired to the real contact-submissions API, with a toast and inline confirmation on success, and the form resetting. - CONTEXT.md: a maintained architecture/conventions doc for this repo, now auto-loaded via CLAUDE.md, covering the Meshkee integration quirks and UI conventions established across this project. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
1.8 KiB
TypeScript
59 lines
1.8 KiB
TypeScript
import type { Metadata } from "next";
|
|
import { Syne } from "next/font/google";
|
|
import localFont from "next/font/local";
|
|
import "./globals.css";
|
|
import { TopBar } from "@/components/home/TopBar";
|
|
import { Header } from "@/components/home/Header";
|
|
import { Footer } from "@/components/home/Footer";
|
|
import { Providers } from "@/context/Providers";
|
|
import { getBusinessInfo, getCategories, getCategoryTree } from "@/lib/meshkee";
|
|
|
|
// Body text — headings use the bold weight instead, see globals.css.
|
|
const iranyekan = localFont({
|
|
variable: "--font-iranyekan",
|
|
src: "../../public/fonts/iranyekanweblightfanum.woff",
|
|
weight: "400",
|
|
});
|
|
|
|
const iranyekanBold = localFont({
|
|
variable: "--font-iranyekan-bold",
|
|
src: "../../public/fonts/iranyekanwebboldfanum.woff",
|
|
weight: "700",
|
|
});
|
|
|
|
const syne = Syne({
|
|
variable: "--font-syne",
|
|
subsets: ["latin"],
|
|
weight: ["600", "700", "800"],
|
|
});
|
|
|
|
export const metadata: Metadata = {
|
|
title: "سانیهوم | فروشگاه لوازم خانگی",
|
|
description: "فروشگاه آنلاین لوازم خانگی — یخچال، لباسشویی، تلویزیون و بیشتر",
|
|
};
|
|
|
|
export default async function RootLayout({ children }: LayoutProps<"/">) {
|
|
const [categoryTree, categories, businessInfo] = await Promise.all([
|
|
getCategoryTree(),
|
|
getCategories(),
|
|
getBusinessInfo(),
|
|
]);
|
|
|
|
return (
|
|
<html
|
|
lang="fa"
|
|
dir="rtl"
|
|
className={`${iranyekan.variable} ${iranyekanBold.variable} ${syne.variable} h-full antialiased`}
|
|
>
|
|
<body className="min-h-full flex flex-col">
|
|
<Providers businessId={businessInfo?.id ?? "1"}>
|
|
<TopBar />
|
|
<Header categoryTree={categoryTree} categories={categories} />
|
|
<main>{children}</main>
|
|
<Footer businessInfo={businessInfo} categories={categories} />
|
|
</Providers>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|