Files
Alireza HassaniandCursor 7c4b59c6f8 Initial commit of the Dr Asadian Next.js site.
Persian RTL clinic site with Meshkee blog, auth, sitemap config, and on-page SEO.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 09:14:52 +03:30

8.8 KiB
Raw Permalink Blame History

Dr Asadian — AI / Project Context

Persian RTL rebuild of drasadian.com (مطب پوست، مو و زیبایی دکتر محمد اسدیان، قم). Keep this file current — other AI tools and later prompts read it first.

Source of truth

Stack

  • Next.js 15 (App Router) + React 19 + TypeScript
  • Tailwind CSS v4 (@import "tailwindcss" in src/app/globals.css) + custom CSS
  • Language: fa, direction: rtl
  • Fonts (downloaded from Meshkee CDN into public/fonts/):
    • kalameh (Persian UI: thin / regular / bold / black)
    • a-glitch-in-time (English kickers)

Color palette (from live yoga3 theme CSS)

  • Primary / headings / body: #17543E
  • Theme gold (links, buttons, accents): #D19C1D (hover #E9B948)
  • Mint button: #D5E8E0 on #17543E text
  • Footer: #195C46 + public/images/footer/footer-bg.jpg
  • Page gray: #F2F2F2
  • Cards: #F8F8F8
  • Doctor band overlay: #1F6C54
  • Muted: #A8A8A8

Routes

Route Purpose
/ Homepage
/services خدمات ما (original /services) — hair / skin / other
/about درباره ما (original /aboutus)
/contact تماس با ما (original /contactus)
/gallery گالری تصاویر مطب (original /gallery)
/blog مقالات — Meshkee GET /tenants/{domain}/blogs
/blog/{id}/{titleSlug} مقاله — GET /tenants/{domain}/blogs/by-id/{id}
/blog/{id} Redirects to canonical {id}/{titleSlug}

Original also had /hair, /skin, /otherservices — not cloned as separate pages. Category cards on /services use in-page anchors (#hair, #skin, #other). Header: خدمات → /services, گالری → /gallery, مقالات → /blog.

Homepage order

  1. Hero slider (2 banners)
  2. Visible H1 intro (clinic name + description)
  3. Specialized service icons
  4. Latest blogs (8 from Meshkee). If the API has no posts yet, the original three cloned teasers are shown and link to /blog
  5. Testimonials (microneedling / microderm / e-light)
  6. Skin repair + about CTA band

Meshkee auth (customer portal)

  • Logged-out user icon → https://customer.drasadian.com (customerPortalUrl in src/lib/config.ts)
  • Logged-in: {firstName lastName} عزیز with dropdown:
    • پروفایل من → https://customer.drasadian.com/profile
    • خروج (clears shared tokens)
    • مدیریت وبسایت (admins/owners only) → https://business.drasadian.com via POST /api/auth/handoff
  • Session: src/lib/session.tsx + cookies meshkee_customer_access_token / meshkee_customer_refresh_token (shared with customer portal on .drasadian.com)
  • Current user: website GET /api/auth/me → Meshkee GET /auth/me
  • Header: src/components/AccountMenu.tsx

Blog (Meshkee)

  • List: GET /tenants/drasadian.com/blogs?page=&pageSize=
  • Detail: GET /tenants/drasadian.com/blogs/by-id/{id} (preferred). Slug route exists but is not used for public URLs.
  • Helpers: src/lib/blog.ts (blogHref, blogImage, fetchBlogs, fetchBlogById)
  • Canonical URL: /blog/{id}/{titleSlug} — Farsi title slug (spaces → -, keep Persian letters)
  • Homepage fetches pageSize=8
  • Listing paginates 12 per page
  • Do not invent blog copy. Empty API → empty listing + homepage fallback teasers only
  • Blog CMS HTML is rendered as mainTextHtml (rich text)

Motion

  • Header fades down on load
  • Sections use Reveal (src/components/Reveal.tsx) — Intersection Observer, one-shot
  • Children with .stagger-child fade up in sequence (--i delay, 130ms steps)
  • Slider slides crossfade (1.15s)
  • prefers-reduced-motion: reduce disables the above

Inner page title

  • PageHero sits in .page-hero-row with a repeating minimal botanical-dot pattern (public/images/patterns/title-pattern.svg)

Original business data (do not invent replacements)

  • Phone: 025-32907661
  • Hours (contact): شنبه تا پنجشنبه، ۱۷ الی ۲۱
  • Address: قم، میدان سپاه (جمهوری)، خیابان شهید اخلاقی، جنب مسجد امام حسن مجتبی، مطب دکترمحمد اسدیان
  • Login icon → https://customer.drasadian.com
  • Business dashboard → https://business.drasadian.com
  • Instagram/Telegram URLs on the live site pointed to / — no public handles cloned
  • Map coordinates were empty in site settings; contact page uses a Google Maps embed of the address

Content & assets

  • Copy, hours table, testimonials, homepage teaser fallbacks: src/data/site.ts
  • Logo: public/images/logo.png (live theme PMlhUjbrcj.png)
  • Favicon: public/favicon.png (from logo; live favicon was empty)
  • Banners / icons / photos / gallery: public/images/
  • Fonts: public/fonts/
  • Remote CMS images: meshkee-storage…, cdn.meshkee.com, c804387.parspack.net (see next.config.ts)
  • Bottom row matches https://safeteb.com/ (site name + framed hover credit)
  • Component: src/components/Footer.tsx → .meshkee-credit
  • Styles: src/app/globals.css
  • Links to https://meshkee.com
  • Logo: public/images/footer/MeshkeeLogo-White.png

Important paths

  • Layout / shell: src/app/layout.tsx (wraps Providers + session)
  • Header: src/components/Header.tsx (split logo, RTL)
  • Account menu: src/components/AccountMenu.tsx
  • Footer: src/components/Footer.tsx
  • Home: src/app/page.tsx, src/components/HeroSlider.tsx
  • Blog: src/app/blog/page.tsx, src/app/blog/[id]/[slug]/page.tsx
  • Meshkee client: src/lib/meshkee.ts, src/lib/meshkee-server.ts, src/lib/config.ts
  • Data: src/data/site.ts

Env

NEXT_PUBLIC_WEBSITE_DOMAIN=drasadian.com
NEXT_PUBLIC_MESHKEE_API_BASE=https://api.meshkee.com/api/v1
# Optional overrides:
# NEXT_PUBLIC_CUSTOMER_PORTAL_URL=https://customer.drasadian.com
# NEXT_PUBLIC_BUSINESS_ORIGIN=https://business.drasadian.com

Commands

npm install
npm run dev
npm run build
npm run generate:sitemap-config

Sitemap + robots

  • Do not add public/sitemap.xml or public/robots.txt (nginx proxies to Meshkee API)
  • Live sitemap is an index: /sitemap.xml → /sitemap-main.xml + /sitemap-products.xml
  • Static pages config: GET /meshkee/sitemap-config.json (generated at build from app routes)
  • Generator: scripts/generate-sitemap-config.mjs → src/lib/sitemap-config.generated.json
  • Excludes api, meshkee, admin, auth, login, checkout, cart, account
  • Blog / product / portfolio details are not listed in sitemap-config (Meshkee XML covers CMS URLs)
  • After deploy: business dashboard → Website → Settings → Sync sitemap config (needed for static pages)

Canonical URL shapes (Meshkee defaults — do not add templates unless customized)

  • Blog: /blog/{id}/{titleSlug} → GET /tenants/{domain}/blogs/by-id/{id}
  • Product: /products/{id}/{nameFaSlug} → GET /tenants/{domain}/products/by-id/{id} (not built yet; catalog empty)
  • Category: /products/category/{id}/{nameFaSlug} → GET /tenants/{domain}/categories/by-id/{id}
  • Portfolio: /portfolios/{id}/{titleFaSlug} → GET /tenants/{domain}/portfolios/by-id/{id} (not built yet)
  • Slugify (src/lib/seo-slug.ts): keep Farsi letters, spaces → -, strip other punctuation. Do not use legacy DB slug in public URLs.

On-page SEO

  • Helper: src/lib/seo.ts (pageMetadata, unique absolute <title>, clipped meta description ~158 chars, canonical, OG)
  • Inner-page titles append | دکتر محمد اسدیان. Homepage title is unique and does not use the Next.js default
  • Exactly one visible H1 per page (never .sr-only / CSS-hidden). Homepage H1 is .home-intro
  • Section headings default to h2 (SectionHeading). Service cards / article cards use h3 under a section h2
  • Prefer CMS title / abstract / image for blog metadata. Fallback teasers only when the API is empty
  • Content images have meaningful alt (CMS title when available). Decorative deco.png uses alt=""
  • src/app/not-found.tsx: unique title + one H1, noindex
  • Favicon: public/favicon.png (tenant favicon API currently returns null)
  • 12 photos from the live /gallery page in public/images/gallery/01.jpg–12.jpg
  • Lightbox: src/components/GalleryGrid.tsx (click to enlarge, arrows / Escape)
  • Header گالری → /gallery

Next planned prompts

  1. Products / portfolio pages if the clinic starts publishing through Meshkee
  2. Image slots if a later prompt provides them