# Kaman Cable — AI / Project Context ## Source of truth - Live reference: https://www.kamancable.ir/ - Backend / Meshkee Website API: https://api.meshkee.com/docs/website - Domain: `kamancable.ir` (`NEXT_PUBLIC_WEBSITE_DOMAIN`) - Tenant business id: `24` (`GET /tenants/kamancable.ir`) - Customer portal: `https://customer.kamancable.ir` - Business dashboard: `https://business.kamancable.ir` - Catalog and blog content come from Meshkee (`enabledModules`: products, blog). Business-info / logos in the API may be empty — brand copy and some homepage photos still live in `src/data/site.ts` and `public/images/`. ## Stack - Next.js 15 (App Router) + React 19 + TypeScript - Tailwind CSS v4 (utility import) + custom CSS in `src/app/globals.css` - Language: `fa` (default, RTL), `en` (LTR), `ar` (RTL) - Static copy lives in `src/i18n/fa.ts`, `src/i18n/en.ts`, `src/i18n/ar.ts` - Locale cookie: `kaman-locale` (also mirrored in localStorage) - Products and blog are **FA-only** for now (hidden in EN/AR nav, homepage, and redirected away) - Fonts: - `iran-yekan` from Meshkee CDN (Persian UI / body) - `Rajdhani` via `next/font/google` (English UI, class `.en`) - `Oswald` via `next/font/google` (`--font-en-display` for section titles / phone) ## Color palette - Dark navy: `#1C1E40` - White: `#FFFFFF` - Gold accent: `#E1A34C` - Highlight yellow: `#FFDE30` - Muted text: `#C8CCE0` ## Pages | Route | Purpose | |-------|---------| | `/` | Homepage (hero, app CTA, about teaser, contact bar, category cards, blog carousel, clients) | | `/products` | Product catalog from `GET /tenants/{domain}/products` | | `/products/{id}/{nameFaSlug}` | Product detail (`GET .../products/by-id/{id}`) | | `/products/category/{id}/{nameFaSlug}` | Category (`GET .../categories/by-id/{id}`, then products with `categoryId`) | | `/blog` | Blog index from `GET /tenants/{domain}/blogs` | | `/blog/{id}/{titleSlug}` | Blog detail (`GET .../blogs/by-id/{id}`) | | `/about` | درباره ما | | `/contact` | تماس با ما | Header (RTL for FA/AR, LTR for EN): logo + menu on the inline-start; search, language (FA/EN/AR), and account on the inline-end. - Signed out: icon links to `https://customer.kamancable.ir` - Signed in: `{firstName} عزیز` dropdown — پروفایل من, خروج, and مدیریت وبسایت if the user is a website admin - Session: shared cookies `meshkee_customer_access_token` / refresh, hydrated into localStorage; `/api/auth/me` proxies `GET /auth/me` Homepage product cards and blog carousel are FA-only. EN and AR hide products/blog (nav, homepage, and `/products` `/blog` redirect home). The full catalog remains `/products` in FA. Homepage blog carousel prefers Meshkee blogs, with `src/data/site.ts` as fallback. ## Meshkee API integration Base: `{NEXT_PUBLIC_MESHKEE_API_BASE}/tenants/{domain}/...` Helpers: `src/lib/meshkee.ts` → `meshkeeGet`, `meshkeeGetOrNull`, `tenantPath`, `getTenant`, `getBusinessId` ### Products - List: `GET /tenants/{domain}/products?page&pageSize&categoryId` - Detail: `GET /tenants/{domain}/products/by-id/{id}` → `{ product, relatedProducts }` - Category: `GET /tenants/{domain}/categories?entityType=product` and `GET .../categories/by-id/{id}` - Specs: `GET /tenants/{domain}/products/by-id/{id}/technical-info` — join `form.fields[].id` ↔ `values[].fieldId`. Do not invent field labels from the detail payload. - Helpers: `src/lib/products.ts`, `src/lib/categories.ts`, `src/lib/technical.ts` - Detail UI: lightbox gallery, hover zoom, tabs (توضیحات / مشخصات فنی / نظرات), similar products row - Public slugs are built from `nameFa` / title (Farsi kept, spaces → `-`). Never use the legacy DB `slug` in the URL. ### Blog - List: `GET /tenants/{domain}/blogs?page&pageSize` - Detail: `GET /tenants/{domain}/blogs/by-id/{id}` → `{ blog }` - Helpers: `src/lib/blog.ts` - Empty API → local posts in `src/data/site.ts` ### Comments - List: `GET /tenants/{domain}/comments?entityType=product|blog&entityId=` - Submit: website proxy `POST /api/comments` → Meshkee comments - UI: authenticated users get a form; guests see a login box to the customer portal (`src/components/CommentBox.tsx`) ### Contact - Public POST: `/tenants/kamancable.ir/contact-submissions` - Website proxy: `POST /api/contact` - Body: `{ title, name, text, email?, cellNumber? }` - Homepage callback strip also posts through this route ### Auth proxies - `GET /api/auth/me` → `GET /auth/me` - `POST /api/auth/handoff` → `POST /auth/handoff` (admin dashboard) ## Static image slots API: `GET /tenants/{domain}/website/static-images?pageKey=home|about|contact|products|blog` Docs: https://api.meshkee.com/docs/website (not http://meshkee.com/docs/website) Catalog: `GET /meshkee/static-image-slots` → `STATIC_IMAGE_SLOTS` in `src/lib/static-images.ts` Helpers: `fetchStaticImageSlots`, `pickSlot` (aliases for renamed keys), `overlaySlotImages`, `slotRatioStyle` Homepage placeholders (match Meshkee dashboard keys exactly): | key | kind | itemCount | aspectRatio | recommendedWidth | section | | --- | --- | --- | --- | --- | --- | | `slider` | list (duplicatable) | null | `1920:830` | 1440 | Homepage hero (72vh cover crop) | | `about-us-image` | single | 1 | `440:609` | 480 | Homepage about photo | | `categories` | list (duplicatable) | null | `3:4` | 480 | Homepage product cards (`.product-card .thumb`) | | `partners` | list (duplicatable) | null | `3:2` | 280 | Homepage partner logos | | `home-blog` | list | 6 | `16:10` | 720 | Homepage blog carousel | | `about-hero` | single | 1 | `1920:640` | 1440 | About hero | | `about-gallery` | list | 6 | `4:3` | 720 | About gallery | | `contact-hero` | single | 1 | `1920:480` | 1440 | Contact hero | | `products-hero` | single | 1 | `1920:480` | 1440 | Products page hero | | `blog-hero` | single | 1 | `1920:480` | 1440 | Blog page hero | Legacy aliases (still resolved by `pickSlot` if the API has not been re-synced): `home-about-image` → `about-us-image`, `home-products` → `categories`, `home-clients` → `partners`. Rules: - Use `slot.images` (all if `kind=list`, first if `kind=single`) - `url` → image src; `linkUrl` → wrap in `SlotLink` - Empty / fail → keep local fallbacks from `src/data/site.ts` / `public/images/` - Apply `slot.aspectRatio` as CSS `--slot-ratio` (`slotRatioStyle`); do not invent upload APIs - **If a slot’s on-page size or aspect ratio changes in CSS, update `STATIC_IMAGE_SLOTS` in the same change** (Meshkee’s static-image reader uses this catalog) ## Content & assets - Site copy, phones, address, category blurbs: `src/data/site.ts` - Logo: `public/images/logo/logo.png` - Hero banners: `public/images/slider/bg1.jpg` … `bg3.jpg` - Favicon: `/favicon.png` - Contact: `021-242624` · دفتر تهران، خیابان کریمخان زند · کارخانه شهر صنعتی شماره یک زنجان - App: https://kamancable.com/applanding/ ## Important paths - Layout / shell: `src/app/layout.tsx`, `src/components/Header.tsx`, `src/components/AccountMenu.tsx`, `src/components/Footer.tsx` - Home: `src/app/page.tsx`, `src/components/HeroSlider.tsx`, `src/components/ContactBar.tsx` - i18n: `src/i18n/config.ts`, `src/i18n/fa.ts`, `src/i18n/en.ts`, `src/i18n/ar.ts`, `src/i18n/locale-context.tsx` - Products: `src/app/products/page.tsx`, `src/app/products/[id]/[slug]/page.tsx` - Blog: `src/app/blog/page.tsx`, `src/app/blog/[id]/[slug]/page.tsx` - Config: `src/lib/config.ts` ## Env ```bash NEXT_PUBLIC_WEBSITE_DOMAIN=kamancable.ir NEXT_PUBLIC_MESHKEE_API_BASE=https://api.meshkee.com/api/v1 ``` ## Commands ```bash npm install npm run dev npm run build ``` ## Sitemap + robots - **Do not** add `public/sitemap.xml` or `public/robots.txt` (nginx proxies both to the Meshkee API) - Live sitemap is an index: `/sitemap.xml` → `/sitemap-main.xml` + `/sitemap-products.xml` - Publish `GET /meshkee/sitemap-config.json` (generated at build from app routes) - Includes public static routes automatically; excludes `api` / `meshkee` / `auth` / `checkout` / `cart` / `admin` / `account` / `login` - Generated at build: `scripts/generate-sitemap-config.mjs` → `src/lib/sitemap-config.generated.json` - Run manually: `npm run generate:sitemap-config` - After deploy: business dashboard → Website → Settings → Sync sitemap config (needed for static pages) ### Canonical CMS URLs (id is canonical; slug is decorative and redirects if stale) - Product: `/products/{id}/{nameFaSlug}` → `GET /tenants/{domain}/products/by-id/{id}` - Category: `/products/category/{id}/{nameFaSlug}` → `GET /tenants/{domain}/categories/by-id/{id}` then list products with `categoryId` - Blog: `/blog/{id}/{titleSlug}` → `GET /tenants/{domain}/blogs/by-id/{id}` - Portfolio is not enabled for this tenant (`enabledModules`: products, blog, finance) ### On-page SEO rules - Unique `