9.1 KiB
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 insrc/data/site.tsandpublic/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-yekanfrom Meshkee CDN (Persian UI / body)Rajdhanivianext/font/google(English UI, class.en)Oswaldvianext/font/google(--font-en-displayfor 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/meproxiesGET /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=productandGET .../categories/by-id/{id} - Specs:
GET /tenants/{domain}/products/by-id/{id}/technical-info— joinform.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 DBslugin 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/mePOST /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 ifkind=list, first ifkind=single) url→ image src;linkUrl→ wrap inSlotLink- Empty / fail → keep local fallbacks from
src/data/site.ts/public/images/ - Apply
slot.aspectRatioas 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_SLOTSin 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
NEXT_PUBLIC_WEBSITE_DOMAIN=kamancable.ir
NEXT_PUBLIC_MESHKEE_API_BASE=https://api.meshkee.com/api/v1
Commands
npm install
npm run dev
npm run build
Sitemap + robots
- Do not add
public/sitemap.xmlorpublic/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 withcategoryId - 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)