Next.js clone of the clinic site with Meshkee-backed articles and portfolio pages. Co-authored-by: Cursor <cursoragent@cursor.com>
6.4 KiB
Project context — dr-mousavi.com clone
Next.js rebuild of the ENT clinic site dr-mousavi.com (Dr Sayed Shahab Mousavipour). Static marketing pages are a faithful clone of the original theme; articles and portfolio entries come from the Meshkee Website API.
Locale is Farsi (fa) and the layout is RTL. Keep this file current when the
project changes — other AI tools and systems read it first.
Stack
- Next.js 15 (App Router, Turbopack) + React 19 + TypeScript
- No UI framework: the original theme's CSS is mirrored verbatim and reused
- Dev server:
npm run dev(this workspace usually runs it on port 3001)
Routes
| Route | Source of content |
|---|---|
/ |
src/components/generated/HomeSections.tsx (mirrored markup) |
/about-us |
AboutSections.tsx (mirrored markup) |
/contact-us |
ContactSections.tsx (mirrored markup) |
/ourservices |
ServicesSections.tsx (mirrored markup) — nav label «تخصص ها» |
/surgeries |
Meshkee API portfolios — nav label «نمونه عمل ها» |
/surgeries/[slug] |
portfolios/{slug} + lightbox gallery + comments |
/articles |
Meshkee API blogs — nav label «مجله سلامت» |
/articles/[slug] |
blogs/{slug} + comments |
Header and footer live in SiteHeader.tsx / SiteFooter.tsx (also generated).
The account icon in the header points at the customer portal.
Cloned theme (static pages)
public/theme/**— the original CSS, fonts, icon font (Fontello), images andaos.js, mirrored at their original paths so the theme CSS resolves.src/components/generated/**— JSX converted from the original HTML bytools/clone_page.py. Do not hand-edit these files; they are overwritten. Put fixes in the converter or insrc/app/globals.css.src/app/globals.css— fonts, theme variables, and the overrides the clone needs (each one carries a comment explaining what it outranks and why).
Regenerate everything (downloads source HTML into tools/.cache, mirrors any new
asset into public/theme, rewrites the components):
python3 tools/clone_page.py # all pages
python3 tools/clone_page.py services # one page
Delete tools/.cache to pull fresh markup from the live site.
Conversion rules worth knowing
- Internal links become
next/link; the account icon becomes{CUSTOMER_URL}. data-aosattributes are preserved:ScrollAnimations.tsxloads the theme's ownaos.jsand callsAOS.init(), exactly like the original'smain.js.- React style objects cannot express
!important, so inlinedirection: ltr !important(how the theme alternates image/text sides) becomes thewb-force-ltrclass defined inglobals.css. - The theme's
.wb-convert-datespans keep their timestamp asdata-date;SiteBehaviour.tsxformats them as Jalali dates. - Forms rendered by React components carry
data-react-formso the theme's generic form handler inSiteBehaviour.tsxleaves them alone.
Meshkee Website API
Docs: https://api.meshkee.com/docs/website · OpenAPI: https://api.meshkee.com/docs/website/openapi.json
Only public website endpoints are used — never dashboard/CMS routes.
Client: src/lib/meshkee.ts.
| Purpose | Endpoint |
|---|---|
Tenant (businessId 20, modules: portfolio, blog, videos, instructions) |
GET /tenants/{domain} |
| Article list | GET /tenants/{domain}/blogs?page&pageSize&categoryId&title |
| Article detail | GET /tenants/{domain}/blogs/{slug} → { blog } |
| Portfolio list | GET /tenants/{domain}/portfolios?page&pageSize&categoryId&title |
| Portfolio detail | GET /tenants/{domain}/portfolios/{slug} → { portfolio } |
| Farsi category names | GET /tenants/{domain}/categories?entityType=blog|portfolio |
| Approved comments | GET /tenants/{domain}/comments?entityType&entityId |
| Submit comment | POST /tenants/{domain}/comments { entityType, entityId, authorName, authorEmail, text } |
Notes on the payloads:
- Lists return
{ items, total, page, pageSize }; details are wrapped in a single key (blog/portfolio). - Portfolios add
titleFa/titleEnand agalleryarray; the Farsi title is preferred.galleryis empty for every current entry, so detail pages fall back totitleImageUrl. - Blog and portfolio items expose only the English
categoryName, hence the separate categories lookup for Farsi labels. mainTextHtmlis authored in the business dashboard and rendered withdangerouslySetInnerHTML, styled by.content-bodyinglobals.css.- Requests are cached for 300s (
revalidate). A failed request logs and returns an empty result so the page shows its empty state instead of erroring.
Comments and sign-in
- Comments are gated: signed-in visitors get the form, everyone else gets a link
to the customer portal (
CommentForm.tsx). useCustomerSession.tslooks for ameshkee_access_tokencookie (the portal shares the apex domain) or the same key inlocalStorage, then confirms it withGET /auth/mebefore trusting it.- Submitted comments are held for approval in the dashboard, so they do not appear immediately; the UI says so.
- The API itself accepts anonymous comments — the sign-in requirement is this site's rule, not the API's.
Environment
Defaults are in src/lib/meshkee.ts; override in .env.local (see .env.example).
| Variable | Default |
|---|---|
NEXT_PUBLIC_MESHKEE_API_BASE |
https://api.meshkee.com/api/v1 |
NEXT_PUBLIC_SITE_DOMAIN |
dr-mousavi.com (apex only — no www) |
NEXT_PUBLIC_CUSTOMER_URL |
https://customer.<site domain> |
Pointing NEXT_PUBLIC_SITE_DOMAIN at another Meshkee tenant is a quick way to
test the API-driven pages against a different content set.
Known gaps
- The homepage «مقالات داغ و جذاب» carousel still shows the articles that were
hardcoded in the original markup; its cards link to
/articles. Wiring it to the API needs a slot mechanism in the converter. - Comment item fields (
id,authorName,text,createdAt) are inferred: no tenant has an approved comment yet to confirm them. - Portfolio galleries are empty upstream, so the lightbox has only ever been exercised with the single title image.
www.dr-mousavi.comcurrently serves a certificate for an unrelated domain and its HTML now belongs to another site, so re-running the converter against the live site will fail until that hosting issue is fixed. The cached HTML intools/.cacheis the last good copy.