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:
+168
@@ -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
|
||||
Reference in New Issue
Block a user