Files

9.1 KiB
Raw Permalink Blame History

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

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.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)