diff --git a/package-lock.json b/package-lock.json index e0fbce9..a4cba35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@tailwindcss/typography": "^0.5.20", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -1605,6 +1606,19 @@ "tailwindcss": "4.3.3" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.20.tgz", + "integrity": "sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || >=4.0.0 || insiders" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", @@ -2801,6 +2815,19 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5557,6 +5584,20 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6639,6 +6680,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 9dbf527..7215258 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@tailwindcss/typography": "^0.5.20", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/prompt/connect-backend-prompt.txt b/prompt/connect-backend-prompt.txt new file mode 100644 index 0000000..91f58e2 --- /dev/null +++ b/prompt/connect-backend-prompt.txt @@ -0,0 +1,15 @@ +Domain: sanihome.ir +Documentation for backend is:https://api.meshkee.com/docs/website + + +Login or user icon should go to customer.sanihome.ir.com + +Create product page and details >> connect to store items +Create blog page and details >> connect to blog + +Product Menu should be a mega menu with level 2 and 3 of categories - skip level 1 + +Search input should be instant search - activate when type more than 2 char + +First carousel >> special-sale +Second carousel >> top-selling diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts new file mode 100644 index 0000000..ce17375 --- /dev/null +++ b/src/app/api/search/route.ts @@ -0,0 +1,12 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getProducts } from "@/lib/meshkee"; + +export async function GET(request: NextRequest) { + const query = request.nextUrl.searchParams.get("q")?.trim() ?? ""; + if (query.length <= 2) { + return NextResponse.json({ items: [] }); + } + + const { items } = await getProducts({ name: query, pageSize: 6 }); + return NextResponse.json({ items }); +} diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..3b41633 --- /dev/null +++ b/src/app/blog/[slug]/page.tsx @@ -0,0 +1,64 @@ +import { notFound } from "next/navigation"; +import { Placeholder } from "@/components/ui/Placeholder"; +import { getBlog } from "@/lib/meshkee"; + +function formatDate(iso?: string) { + if (!iso) return ""; + return new Intl.DateTimeFormat("fa-IR", { dateStyle: "long" }).format(new Date(iso)); +} + +export default async function BlogDetailPage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + const post = await getBlog(slug); + + if (!post) notFound(); + + return ( +
+
+ {post.categoryName && {post.categoryName}} + {post.publishedAt && ( + <> + • + {formatDate(post.publishedAt)} + + )} + {post.author && ( + <> + • + {post.author} + + )} +
+ +

+ {post.title} +

+ + {post.image && ( + + )} + +
+ +
+ + → بازگشت به وبلاگ + +
+
+ ); +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx new file mode 100644 index 0000000..c6d2dff --- /dev/null +++ b/src/app/blog/page.tsx @@ -0,0 +1,97 @@ +import { Placeholder } from "@/components/ui/Placeholder"; +import { EmptyState } from "@/components/ui/EmptyState"; +import { getBlogs } from "@/lib/meshkee"; + +const PAGE_SIZE = 12; + +function formatDate(iso?: string) { + if (!iso) return ""; + return new Intl.DateTimeFormat("fa-IR", { dateStyle: "long" }).format(new Date(iso)); +} + +export default async function BlogListPage({ + searchParams, +}: { + searchParams: Promise<{ page?: string }>; +}) { + const params = await searchParams; + const page = Math.max(1, Number(params.page ?? 1) || 1); + const { items, total } = await getBlogs({ page, pageSize: PAGE_SIZE }); + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + + return ( +
+

وبلاگ

+ + {items.length === 0 ? ( + + ) : ( + <> +
+ {items.map((post) => ( + + +
+
+ {post.categoryName && {post.categoryName}} + {post.publishedAt && {formatDate(post.publishedAt)}} +
+

+ {post.title} +

+ {post.abstract && ( +

+ {post.abstract} +

+ )} +
+
+ ))} +
+ + {totalPages > 1 && ( +
+ {page > 1 ? ( + + قبلی + + ) : ( + + قبلی + + )} + + صفحه {page} از {totalPages} + + {page < totalPages ? ( + + بعدی + + ) : ( + + بعدی + + )} +
+ )} + + )} +
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index 481eab9..323febe 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@plugin "@tailwindcss/typography"; :root { --background: #fafafa; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f9056df..1e94339 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,10 @@ import type { Metadata } from "next"; import { Vazirmatn, Syne } from "next/font/google"; import "./globals.css"; +import { TopBar } from "@/components/home/TopBar"; +import { Header } from "@/components/home/Header"; +import { Footer } from "@/components/home/Footer"; +import { getBusinessInfo, getCategories, getCategoryTree } from "@/lib/meshkee"; const vazirmatn = Vazirmatn({ variable: "--font-vazirmatn", @@ -19,14 +23,25 @@ export const metadata: Metadata = { description: "فروشگاه آنلاین لوازم خانگی — یخچال، لباسشویی، تلویزیون و بیشتر", }; -export default function RootLayout({ children }: LayoutProps<"/">) { +export default async function RootLayout({ children }: LayoutProps<"/">) { + const [categoryTree, categories, businessInfo] = await Promise.all([ + getCategoryTree(), + getCategories(), + getBusinessInfo(), + ]); + return ( - {children} + + +
+
{children}
+