From cfabf3e3273a3e7df9dab824801da0a4aa11164b Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Fri, 4 Sep 2026 15:00:41 +0330 Subject: [PATCH] Initial commit: Hoomat storefront with Meshkee API and static-image slots. Co-authored-by: Cursor --- .cursor/rules/static-image-slots.mdc | 14 + .gitignore | 8 + AGENTS.md | 9 + CLAUDE.md | 1 + CONTEXT.md | 109 + app/about-us/page.tsx | 107 + app/blog/[id]/[slug]/page.tsx | 67 + app/blog/page.tsx | 74 + app/components/article-carousel.tsx | 79 + app/components/cart-drawer.tsx | 112 + app/components/contact-form.tsx | 69 + app/components/footer.tsx | 107 + app/components/header.tsx | 111 + app/components/hero-slider.tsx | 95 + app/components/product-carousel.tsx | 129 + app/components/products-mega-menu.tsx | 83 + app/components/site-shell.tsx | 24 + app/components/slot-media.tsx | 93 + app/components/store-detail-client.tsx | 339 + app/components/user-menu.tsx | 132 + app/contactus/page.tsx | 132 + app/globals.css | 2267 ++++++ app/layout.tsx | 37 + app/meshkee/static-image-slots/route.ts | 8 + app/page.tsx | 344 + app/providers/auth-provider.tsx | 169 + app/providers/cart-provider.tsx | 243 + app/providers/index.tsx | 15 + app/providers/toast-provider.tsx | 56 + app/store/[variantId]/[[...slug]]/page.tsx | 73 + app/store/page.tsx | 110 + eslint.config.mjs | 9 + .../alona-gross-eH2YpdHO_7I-unsplash (1).jpg | Bin 0 -> 1287735 bytes images sample/categories/bed.png | Bin 0 -> 1174620 bytes images sample/categories/chair.png | Bin 0 -> 1513088 bytes images sample/categories/classic.png | Bin 0 -> 1255962 bytes images sample/categories/console.png | Bin 0 -> 1309060 bytes images sample/categories/dining-table.png | Bin 0 -> 1191616 bytes images sample/categories/sofa.png | Bin 0 -> 1123879 bytes .../charlesdeluvio-CKbBn7ZhmyM-unsplash.jpg | Bin 0 -> 195391 bytes .../charlesdeluvio-CdTH3UwWlR4-unsplash.jpg | Bin 0 -> 187059 bytes ...hilippe-delberghe-feijc-nqWKM-unsplash.jpg | Bin 0 -> 47660 bytes .../lucas-kepner-o_-snE5cDTI-unsplash.jpg | Bin 0 -> 111085 bytes ...mium_photo-1670950411934-e21abf39191e.avif | Bin 0 -> 155739 bytes ...mium_photo-1682259448839-2ea3bc7f9e73.avif | Bin 0 -> 272836 bytes ...mium_photo-1682410455752-1007e8cea553.avif | Bin 0 -> 166886 bytes .../ambo-ampeng-KEGGJPKxcSc-unsplash.jpg | Bin 0 -> 27543 bytes .../kevin-shek-kcxZ-YUZ8VE-unsplash.jpg | Bin 0 -> 29575 bytes ...mium_photo-1682410454254-809412bf04bd.avif | Bin 0 -> 54260 bytes ...mium_photo-1683120739323-0a920bdb15a4.avif | Bin 0 -> 85958 bytes .../sam-moghadam-kvmdsTrGOBM-unsplash.jpg | Bin 0 -> 70143 bytes .../sam-moghadam-xej3GOtAQ-o-unsplash.jpg | Bin 0 -> 20779 bytes .../scopic-ltd-NLlWwR4d3qU-unsplash.jpg | Bin 0 -> 95841 bytes images sample/sofa-real.png | Bin 0 -> 1521187 bytes .../spacejoy-c0JoR_-2x3E-unsplash.jpg | Bin 0 -> 541859 bytes lib/meshkee/api.ts | 335 + lib/meshkee/cart.ts | 117 + lib/meshkee/config.ts | 9 + lib/meshkee/format.ts | 102 + lib/meshkee/guest-cart.ts | 49 + lib/meshkee/static-slots.ts | 127 + lib/meshkee/types.ts | 187 + lib/meshkee/urls.ts | 55 + lib/site-data.ts | 209 + next-env.d.ts | 7 + next.config.ts | 14 + package-lock.json | 6159 +++++++++++++++++ package.json | 29 + public/assets/hoomat-logo.png | Bin 0 -> 48165 bytes public/assets/meshkee-logo-white.png | Bin 0 -> 33035 bytes public/assets/sample/categories/bed.png | Bin 0 -> 1174620 bytes public/assets/sample/categories/chair.png | Bin 0 -> 1513088 bytes public/assets/sample/categories/classic.png | Bin 0 -> 1255962 bytes public/assets/sample/categories/console.png | Bin 0 -> 1309060 bytes .../assets/sample/categories/dining-table.png | Bin 0 -> 1191616 bytes public/assets/sample/categories/sofa.png | Bin 0 -> 1123879 bytes public/assets/sample/categories/sofaBed.png | Bin 0 -> 1332334 bytes .../charlesdeluvio-CKbBn7ZhmyM-unsplash.jpg | Bin 0 -> 195391 bytes .../charlesdeluvio-CdTH3UwWlR4-unsplash.jpg | Bin 0 -> 187059 bytes ...hilippe-delberghe-feijc-nqWKM-unsplash.jpg | Bin 0 -> 47660 bytes .../lucas-kepner-o_-snE5cDTI-unsplash.jpg | Bin 0 -> 111085 bytes ...mium_photo-1670950411934-e21abf39191e.avif | Bin 0 -> 155739 bytes ...mium_photo-1682259448839-2ea3bc7f9e73.avif | Bin 0 -> 272836 bytes ...mium_photo-1682410455752-1007e8cea553.avif | Bin 0 -> 166886 bytes .../ambo-ampeng-KEGGJPKxcSc-unsplash.jpg | Bin 0 -> 27543 bytes .../kevin-shek-kcxZ-YUZ8VE-unsplash.jpg | Bin 0 -> 29575 bytes ...mium_photo-1682410454254-809412bf04bd.avif | Bin 0 -> 54260 bytes ...mium_photo-1683120739323-0a920bdb15a4.avif | Bin 0 -> 85958 bytes .../sam-moghadam-kvmdsTrGOBM-unsplash.jpg | Bin 0 -> 70143 bytes .../sam-moghadam-xej3GOtAQ-o-unsplash.jpg | Bin 0 -> 20779 bytes .../scopic-ltd-NLlWwR4d3qU-unsplash.jpg | Bin 0 -> 95841 bytes public/assets/sample/sofa-real-v2.png | Bin 0 -> 1521187 bytes public/assets/sample/sofa-real.png | Bin 0 -> 1521187 bytes .../sample/spacejoy-c0JoR_-2x3E-unsplash.jpg | Bin 0 -> 541859 bytes tsconfig.json | 27 + 95 files changed, 12651 insertions(+) create mode 100644 .cursor/rules/static-image-slots.mdc create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 CONTEXT.md create mode 100644 app/about-us/page.tsx create mode 100644 app/blog/[id]/[slug]/page.tsx create mode 100644 app/blog/page.tsx create mode 100644 app/components/article-carousel.tsx create mode 100644 app/components/cart-drawer.tsx create mode 100644 app/components/contact-form.tsx create mode 100644 app/components/footer.tsx create mode 100644 app/components/header.tsx create mode 100644 app/components/hero-slider.tsx create mode 100644 app/components/product-carousel.tsx create mode 100644 app/components/products-mega-menu.tsx create mode 100644 app/components/site-shell.tsx create mode 100644 app/components/slot-media.tsx create mode 100644 app/components/store-detail-client.tsx create mode 100644 app/components/user-menu.tsx create mode 100644 app/contactus/page.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/meshkee/static-image-slots/route.ts create mode 100644 app/page.tsx create mode 100644 app/providers/auth-provider.tsx create mode 100644 app/providers/cart-provider.tsx create mode 100644 app/providers/index.tsx create mode 100644 app/providers/toast-provider.tsx create mode 100644 app/store/[variantId]/[[...slug]]/page.tsx create mode 100644 app/store/page.tsx create mode 100644 eslint.config.mjs create mode 100644 images sample/alona-gross-eH2YpdHO_7I-unsplash (1).jpg create mode 100644 images sample/categories/bed.png create mode 100644 images sample/categories/chair.png create mode 100644 images sample/categories/classic.png create mode 100644 images sample/categories/console.png create mode 100644 images sample/categories/dining-table.png create mode 100644 images sample/categories/sofa.png create mode 100644 images sample/charlesdeluvio-CKbBn7ZhmyM-unsplash.jpg create mode 100644 images sample/charlesdeluvio-CdTH3UwWlR4-unsplash.jpg create mode 100644 images sample/jean-philippe-delberghe-feijc-nqWKM-unsplash.jpg create mode 100644 images sample/lucas-kepner-o_-snE5cDTI-unsplash.jpg create mode 100644 images sample/premium_photo-1670950411934-e21abf39191e.avif create mode 100644 images sample/premium_photo-1682259448839-2ea3bc7f9e73.avif create mode 100644 images sample/premium_photo-1682410455752-1007e8cea553.avif create mode 100644 images sample/products/ambo-ampeng-KEGGJPKxcSc-unsplash.jpg create mode 100644 images sample/products/kevin-shek-kcxZ-YUZ8VE-unsplash.jpg create mode 100644 images sample/products/premium_photo-1682410454254-809412bf04bd.avif create mode 100644 images sample/products/premium_photo-1683120739323-0a920bdb15a4.avif create mode 100644 images sample/products/sam-moghadam-kvmdsTrGOBM-unsplash.jpg create mode 100644 images sample/products/sam-moghadam-xej3GOtAQ-o-unsplash.jpg create mode 100644 images sample/products/scopic-ltd-NLlWwR4d3qU-unsplash.jpg create mode 100644 images sample/sofa-real.png create mode 100644 images sample/spacejoy-c0JoR_-2x3E-unsplash.jpg create mode 100644 lib/meshkee/api.ts create mode 100644 lib/meshkee/cart.ts create mode 100644 lib/meshkee/config.ts create mode 100644 lib/meshkee/format.ts create mode 100644 lib/meshkee/guest-cart.ts create mode 100644 lib/meshkee/static-slots.ts create mode 100644 lib/meshkee/types.ts create mode 100644 lib/meshkee/urls.ts create mode 100644 lib/site-data.ts create mode 100644 next-env.d.ts create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/assets/hoomat-logo.png create mode 100644 public/assets/meshkee-logo-white.png create mode 100644 public/assets/sample/categories/bed.png create mode 100644 public/assets/sample/categories/chair.png create mode 100644 public/assets/sample/categories/classic.png create mode 100644 public/assets/sample/categories/console.png create mode 100644 public/assets/sample/categories/dining-table.png create mode 100644 public/assets/sample/categories/sofa.png create mode 100644 public/assets/sample/categories/sofaBed.png create mode 100644 public/assets/sample/charlesdeluvio-CKbBn7ZhmyM-unsplash.jpg create mode 100644 public/assets/sample/charlesdeluvio-CdTH3UwWlR4-unsplash.jpg create mode 100644 public/assets/sample/jean-philippe-delberghe-feijc-nqWKM-unsplash.jpg create mode 100644 public/assets/sample/lucas-kepner-o_-snE5cDTI-unsplash.jpg create mode 100644 public/assets/sample/premium_photo-1670950411934-e21abf39191e.avif create mode 100644 public/assets/sample/premium_photo-1682259448839-2ea3bc7f9e73.avif create mode 100644 public/assets/sample/premium_photo-1682410455752-1007e8cea553.avif create mode 100644 public/assets/sample/products/ambo-ampeng-KEGGJPKxcSc-unsplash.jpg create mode 100644 public/assets/sample/products/kevin-shek-kcxZ-YUZ8VE-unsplash.jpg create mode 100644 public/assets/sample/products/premium_photo-1682410454254-809412bf04bd.avif create mode 100644 public/assets/sample/products/premium_photo-1683120739323-0a920bdb15a4.avif create mode 100644 public/assets/sample/products/sam-moghadam-kvmdsTrGOBM-unsplash.jpg create mode 100644 public/assets/sample/products/sam-moghadam-xej3GOtAQ-o-unsplash.jpg create mode 100644 public/assets/sample/products/scopic-ltd-NLlWwR4d3qU-unsplash.jpg create mode 100644 public/assets/sample/sofa-real-v2.png create mode 100644 public/assets/sample/sofa-real.png create mode 100644 public/assets/sample/spacejoy-c0JoR_-2x3E-unsplash.jpg create mode 100644 tsconfig.json diff --git a/.cursor/rules/static-image-slots.mdc b/.cursor/rules/static-image-slots.mdc new file mode 100644 index 0000000..c787775 --- /dev/null +++ b/.cursor/rules/static-image-slots.mdc @@ -0,0 +1,14 @@ +--- +description: Keep Meshkee static-image slot catalog aspect ratios in sync with UI +globs: lib/meshkee/static-slots.ts,app/meshkee/static-image-slots/**,app/page.tsx,app/about-us/**,app/components/hero-slider.tsx,app/components/slot-media.tsx,app/components/product-carousel.tsx +alwaysApply: false +--- + +# Static image slots + +When homepage/about (or other) UI image sizes or aspect ratios change during development, update `STATIC_IMAGE_SLOT_CATALOG` in `lib/meshkee/static-slots.ts` (and thus `GET /meshkee/static-image-slots`) so Meshkee’s static-image reader stays accurate. + +- Prefer API `slot.aspectRatio` / catalog values; do not invent upload or CMS write APIs. +- Empty or failed slot fetches must keep local placeholders. +- List slots use all `slot.images`; single slots use the first image with a `url`. +- Wrap with `` when `linkUrl` is present; map `titleFa` / `titleEn` / `subtext` to the existing UI copy fields. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f8e1a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.next +node_modules +out +dist +.env* +*.log +coverage +.DS_Store diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..643577d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,9 @@ + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..2fcc31f --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,109 @@ +# Hoomat Store Context + +## Current Prompt + +Source prompt: `https://api.meshkee.com/api/v1/public/ai-prompts/hoomat-store/images-placeholder/28488577c9163eaa8fdb8e86` + +Goal: wire homepage/about static-image slots from Meshkee (`GET /tenants/hoomatstore.com/website/static-images`) and publish the slot catalog at `/meshkee/static-image-slots`. Empty/fail → keep local placeholders. + +Previous prompts: +- Backend: `…/backend/114f70b804d97953f91ad26a` (API auth/cart/store/blog/contact) +- Basic create: `…/basic-create/d3ca3d1229f0e5c8e532b073` (Persian RTL flat-design clone) + +## Meshkee Backend + +- Docs: `https://api.meshkee.com/docs/website` +- OpenAPI: `https://api.meshkee.com/docs/website/openapi.json` +- API base: `https://api.meshkee.com/api/v1` +- Apex domain: `hoomatstore.com` (business id currently `35`) +- Customer app: `https://customer.hoomatstore.com` +- Business admin: `https://business.hoomatstore.com` +- Tenant resolve: `GET /tenants/hoomatstore.com` +- Public content: `/tenants/hoomatstore.com/...` +- Cart/orders: `/businesses/{businessId}/...` with Bearer JWT +- Auth token keys (localStorage): `meshkee_customer_access_token`, `meshkee_customer_refresh_token` +- Guest cart key: `meshkee-guest-cart` +- Cart continue: authenticated users go to `customer.hoomatstore.com/checkout/cart`; guests go to customer login with `redirect=/checkout/cart` and optional `#guestCart=` payload (Meshkee customer dashboard handoff) + +## Static image slots + +Catalog source: `lib/meshkee/static-slots.ts` → `GET /meshkee/static-image-slots` + +| key | kind | pageKey | count | UI | +|-----|------|---------|-------|----| +| `slider` | list | home | n | homepage hero | +| `categories-icon` | list | home | 6 | quick category icons | +| `special-sale-banner` | single | home | 1 | sale promo beside product carousel | +| `product-features-image` | single | home | 1 | features sofa | +| `contactus-image` | single | home | 1 | homepage contact guarantee panel | +| `two-banner-row` | list | home | 2 | dual promo banners | +| `aboutus-image` | single | home | 1 | homepage about panel | +| `three-banner-row` | list | home | 3 | triple promo banners | +| `about-us-image` | list | about | 2 | about page panels | + +Rules: use `slot.images` (all if list, first if single); `url` → img/bg; `linkUrl` → wrap link; map `titleFa` / `titleEn` / `subtext` to UI; if UI aspect/size changes, update the catalog. Cursor rule: `.cursor/rules/static-image-slots.mdc`. + +## Source Website Data + +- Main site: `https://www.hoomatstore.com/` +- About page: `https://www.hoomatstore.com/about-us` +- Contact page: `https://www.hoomatstore.com/contactus` +- Footer inspiration for bottom Meshkee row: `https://safeteb.com/` + +## Design Requirements + +- Language: Persian (`fa`) +- Direction: RTL +- Framework: Next.js app router +- Style: flat design, furniture-store feel, responsive layout +- Header: fixed with blurry/translucent background; stronger glass after scroll +- Header now includes user menu, cart icon/badge + drawer, and products mega menu (1st + 2nd category levels) +- Palette: + - Primary: `#00234B` + - Secondary: `#CC9966` + - Tertiary: `#883F3F` +- Fonts: Iran Sans (FA), Josefin Sans (EN titles), Montserrat fallback +- Logo local fallback: `public/assets/hoomat-logo.png` +- Favicon: from `GET /tenants/{domain}/website/favicon` when available; otherwise local logo + +## Implemented Routes + +- `/` homepage (static-image slots + local fallbacks; product carousels still sample) +- `/about-us` (about-us-image ×2 + local fallbacks) +- `/contactus` (contact form posts to Meshkee contact-submissions) +- `/store` store-items listing +- `/store/[variantId]/[[...slug]]` store item detail +- `/blog` blog listing +- `/blog/[id]/[slug]` blog detail +- `/meshkee/static-image-slots` slot catalog JSON for Meshkee dashboard + +## Auth / Cart UX + +- Logged out user icon → customer login +- Logged in: `{firstName} عزیز` with dropdown: پروفایل من, خروج, and مدیریت وبسایت (admins only, handoff to business app) +- Cart badge shows total quantity; drawer continue follows Meshkee customer checkout flow above + +## Key Code Locations + +- API client: `lib/meshkee/api.ts` (`getStaticImages`, `getStaticImageSlot`) +- Slot catalog/helpers: `lib/meshkee/static-slots.ts` +- Slot UI helpers: `app/components/slot-media.tsx` +- Config/domain: `lib/meshkee/config.ts` +- Auth/cart/toast providers: `app/providers/*` +- Header + cart drawer + mega menu: `app/components/header.tsx` and related components +- Shared static homepage content still in `lib/site-data.ts` +- Global styles: `app/globals.css` + +## Data Notes + +- Store-items for this tenant may be empty until inventory is published; blogs/categories are live. +- Static-images for this tenant may be empty until CMS slots are filled; UI keeps local placeholders. +- Product description/gallery/technical/comments for a store variant come from the linked catalog product (`productId`). +- Homepage carousels still use local sample products until a later prompt wires them to API. + +## Maintenance Notes + +- Update this file on every Meshkee prompt. +- Prefer Meshkee Website API docs over inventing CMS/admin endpoints. +- Keep RTL/FA patterns and existing visual language when adding pages. +- When UI image size/aspect changes, update `STATIC_IMAGE_SLOT_CATALOG` so `/meshkee/static-image-slots` stays correct. diff --git a/app/about-us/page.tsx b/app/about-us/page.tsx new file mode 100644 index 0000000..531c85a --- /dev/null +++ b/app/about-us/page.tsx @@ -0,0 +1,107 @@ +import Link from "next/link"; +import { SlotLinkedImage } from "@/app/components/slot-media"; +import { getStaticImageSlot, getStaticImages } from "@/lib/meshkee/api"; +import { slotImages } from "@/lib/meshkee/static-slots"; +import type { StaticImageItem, StaticImageSlot } from "@/lib/meshkee/types"; +import { aboutCopy, features, site } from "@/lib/site-data"; + +export const metadata = { + title: "درباره ما | داستان فروشگاه اینترنتی مبلمان هومت", + description: "با داستان، دیدگاه و خدمات مبلمان هومت بیشتر آشنا شوید.", +}; + +async function loadAboutImages(): Promise { + const aboutSlots = await getStaticImages("about").catch( + () => [] as StaticImageSlot[], + ); + const fromPage = slotImages(aboutSlots, "about-us-image"); + if (fromPage.length) return fromPage; + + const slot = await getStaticImageSlot("about-us-image"); + return slot ? slotImages([slot], "about-us-image") : []; +} + +export default async function AboutPage() { + const aboutImages = await loadAboutImages(); + const primaryImage = aboutImages[0]; + const secondaryImage = aboutImages[1]; + + return ( +
+
+

{aboutCopy.eyebrow}

+

{aboutCopy.title}

+

{aboutCopy.quote}

+
+ +
+
+

Story

+

{aboutCopy.storyTitle}

+

{aboutCopy.story}

+

+ هومت با تکیه بر تولید مستقیم، تلاش می‌کند تجربه خرید مبلمان را برای + خانواده‌ها ساده‌تر کند؛ از انتخاب محصول تا مشاوره، سفارش و تحویل. +

+
+ +
+

Vision

+

{aboutCopy.visionTitle}

+

{aboutCopy.vision}

+
    + {features.map((feature) => ( +
  • + {feature.title} - {feature.subtitle} +
  • + ))} +
+ + تماس با هومت + +
+
+ +
+
+ +
+

ONLINE FURNITURE SHOP

+

فروشگاه آنلاین خرید مبلمان منزل با تنوع بالا

+

+ مجموعه هومت محصولات اصلی مورد نیاز دکوراسیون منزل را پوشش می‌دهد: + مبلمان راحتی، کلاسیک، مینیمال، مبل تخت‌خواب‌شو، میز ناهارخوری، + صندلی، سرویس خواب و میزهای کاربردی. +

+
+
+
+ +
+
+ +
+

SHOWROOM

+

از انتخاب مدل تا چیدمان نهایی

+

+ در نمایشگاه و کارخانه هومت می‌توانید پارچه، رنگ و مدل را از نزدیک + بررسی کنید و با راهنمایی مشاوران، ترکیبی هماهنگ برای منزل خود بسازید. +

+
+
+
+
+ ); +} diff --git a/app/blog/[id]/[slug]/page.tsx b/app/blog/[id]/[slug]/page.tsx new file mode 100644 index 0000000..9789977 --- /dev/null +++ b/app/blog/[id]/[slug]/page.tsx @@ -0,0 +1,67 @@ +import type { Metadata } from "next"; +import Image from "next/image"; +import { notFound } from "next/navigation"; +import { getBlogById } from "@/lib/meshkee/api"; +import { formatDate } from "@/lib/meshkee/format"; + +type PageProps = { + params: Promise<{ id: string; slug: string }>; +}; + +export async function generateMetadata({ params }: PageProps): Promise { + const { id } = await params; + try { + const post = await getBlogById(id); + return { + title: `${post.title} | مبلمان هومت`, + description: post.abstract || post.title, + }; + } catch { + return { title: "مقاله | مبلمان هومت" }; + } +} + +export default async function BlogDetailPage({ params }: PageProps) { + const { id } = await params; + let post; + try { + post = await getBlogById(id); + } catch { + notFound(); + } + + return ( +
+
+
+

{post.categoryName || "Article"}

+

{post.title}

+ +
+ + {post.titleImageUrl ? ( +
+ {post.title} +
+ ) : null} + + {post.abstract ?

{post.abstract}

: null} + + {post.mainTextHtml ? ( +
+ ) : ( +

متن مقاله در دسترس نیست.

+ )} +
+
+ ); +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx new file mode 100644 index 0000000..b9bfc10 --- /dev/null +++ b/app/blog/page.tsx @@ -0,0 +1,74 @@ +import Image from "next/image"; +import Link from "next/link"; +import { listBlogs } from "@/lib/meshkee/api"; +import { formatDate } from "@/lib/meshkee/format"; +import { blogHref } from "@/lib/meshkee/urls"; + +type SearchParams = Promise<{ page?: string }>; + +export const metadata = { + title: "مجله دکوراسیون | مبلمان هومت", + description: "مقالات و راهنمای خرید مبلمان و دکوراسیون از مبلمان هومت", +}; + +export default async function BlogPage({ + searchParams, +}: { + searchParams: SearchParams; +}) { + const params = await searchParams; + const page = Math.max(1, Number(params.page) || 1); + const data = await listBlogs({ page, pageSize: 12 }); + + return ( +
+
+

Blog

+

مجله دکوراســـــــــیون

+

راهنماها و مقالات تخصصی برای انتخاب بهتر مبلمان و چیدمان خانه.

+
+ +
+ {data.items.length === 0 ? ( +

مقاله‌ای برای نمایش وجود ندارد.

+ ) : ( +
+ {data.items.map((post) => ( + +
+ {post.titleImageUrl ? ( + {post.title} + ) : ( +
+ )} +
+
+ +

{post.title}

+ {post.abstract ?

{post.abstract}

: null} +
+ + ))} +
+ )} + + {data.total > data.pageSize ? ( +
+ {page > 1 ? قبلی : null} + + {page} + + {page * data.pageSize < data.total ? ( + بعدی + ) : null} +
+ ) : null} +
+
+ ); +} diff --git a/app/components/article-carousel.tsx b/app/components/article-carousel.tsx new file mode 100644 index 0000000..dcb2969 --- /dev/null +++ b/app/components/article-carousel.tsx @@ -0,0 +1,79 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { useRef } from "react"; + +type Article = { + title: string; + date: string; + image: string; + href?: string; +}; + +export function ArticleCarousel({ + articles, + ariaLabel, +}: { + articles: Article[]; + ariaLabel: string; +}) { + const carouselRef = useRef(null); + + const scrollCarousel = (direction: "next" | "previous") => { + const amount = carouselRef.current + ? carouselRef.current.clientWidth * 0.7 + : 320; + carouselRef.current?.scrollBy({ + left: direction === "next" ? -amount : amount, + behavior: "smooth", + }); + }; + + return ( +
+ +
+ {articles.map((article) => ( + +
+ {article.title} +
+
+ +

{article.title}

+

ادامه

+
+ + ))} +
+ +
+ ); +} diff --git a/app/components/cart-drawer.tsx b/app/components/cart-drawer.tsx new file mode 100644 index 0000000..b43ee7d --- /dev/null +++ b/app/components/cart-drawer.tsx @@ -0,0 +1,112 @@ +"use client"; + +import Image from "next/image"; +import { useCart } from "@/app/providers/cart-provider"; +import { useAuth } from "@/app/providers/auth-provider"; +import { formatPrice } from "@/lib/meshkee/format"; +import { customerContinueCheckoutUrl } from "@/lib/meshkee/urls"; + +export function CartDrawer() { + const { cart, open, setOpen, updateQuantity, removeItem, guestItems, loading } = + useCart(); + const { isAuthenticated } = useAuth(); + + if (!open) return null; + + const continueUrl = customerContinueCheckoutUrl( + isAuthenticated, + isAuthenticated ? [] : guestItems, + ); + + return ( +
+ +
+ +
+ {cart.items.length === 0 ? ( +

هنوز محصولی به سبد اضافه نشده است.

+ ) : ( + cart.items.map((item) => ( +
+
+ {item.image ? ( + {item.name} + ) : ( +
+ )} +
+
+

{item.name}

+ {item.optionLabel ?

{item.optionLabel}

: null} + + {formatPrice(item.price)} + +
+ + + {item.quantity} + + + +
+
+
+ )) + )} +
+ +
+
+ جمع + + {formatPrice(cart.total)} + +
+
+ ادامه + +
+ + + ); +} diff --git a/app/components/contact-form.tsx b/app/components/contact-form.tsx new file mode 100644 index 0000000..54655ac --- /dev/null +++ b/app/components/contact-form.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { useState } from "react"; +import { submitContact } from "@/lib/meshkee/api"; +import { useToast } from "@/app/providers/toast-provider"; + +export function ContactForm() { + const { showToast } = useToast(); + const [name, setName] = useState(""); + const [cellNumber, setCellNumber] = useState(""); + const [text, setText] = useState(""); + const [successText, setSuccessText] = useState(null); + const [submitting, setSubmitting] = useState(false); + + async function onSubmit(event: React.FormEvent) { + event.preventDefault(); + setSubmitting(true); + setSuccessText(null); + try { + await submitContact({ + title: "تماس از وبسایت هومت", + name: name.trim(), + cellNumber: cellNumber.trim() || undefined, + text: text.trim(), + }); + setName(""); + setCellNumber(""); + setText(""); + setSuccessText("پیام شما با موفقیت ارسال شد. به زودی با شما تماس می‌گیریم."); + showToast("پیام با موفقیت ارسال شد"); + } catch (error) { + showToast(error instanceof Error ? error.message : "ارسال پیام ناموفق بود"); + } finally { + setSubmitting(false); + } + } + + return ( +
void onSubmit(event)}> + setName(event.target.value)} + /> + setCellNumber(event.target.value)} + /> +