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>
This commit is contained in:
Alireza Hassani
2026-09-01 09:14:52 +03:30
co-authored by Cursor
commit 7c4b59c6f8
84 changed files with 10342 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
# 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
- Live site being cloned: https://www.drasadian.com/
- Prompt: Meshkee `basic-clone` then `backend` (`/api/v1/public/ai-prompts/dr-asadian/...`)
- Backend / Meshkee Website API: https://api.meshkee.com/docs/website
- OpenAPI: https://api.meshkee.com/docs/website/openapi.json
- Apex domain: `drasadian.com` (no `www` / `api` / `customer` / `business`)
- Tenant: `GET /tenants/drasadian.com` → `businessId` `31`
- Enabled modules: `products`, `blog`, `finance`
## 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`)
## Footer Meshkee credit
- 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
```bash
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
```bash
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)
## Gallery
- 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