165 lines
9.1 KiB
Markdown
165 lines
9.1 KiB
Markdown
# 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 `<title>` + meta description on every crawlable page (`src/lib/seo.ts` → `pageMetadata`); paginated list pages include the page number
|
||
- Prefer CMS `nameFa` / `title` / `abstract` / `summary` / `descriptionHtml`; never leave Next.js defaults
|
||
- Exactly one `<h1>` per page; section headings use `<h2>` / `<h3>`
|
||
- Content images have meaningful `alt` (CMS title when available)
|
||
- Slugify: Farsi letters kept, spaces → `-`, other punctuation stripped (`src/lib/slug.ts`)
|