Initial commit of the Dr Shahab Mousavi website.
Next.js clone of the clinic site with Meshkee-backed articles and portfolio pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+135
@@ -0,0 +1,135 @@
|
||||
# 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 and
|
||||
`aos.js`, mirrored at their original paths so the theme CSS resolves.
|
||||
- `src/components/generated/**` — JSX converted from the original HTML by
|
||||
`tools/clone_page.py`. **Do not hand-edit these files**; they are overwritten.
|
||||
Put fixes in the converter or in `src/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):
|
||||
|
||||
```bash
|
||||
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-aos` attributes are preserved: `ScrollAnimations.tsx` loads the theme's
|
||||
own `aos.js` and calls `AOS.init()`, exactly like the original's `main.js`.
|
||||
- React style objects cannot express `!important`, so inline
|
||||
`direction: ltr !important` (how the theme alternates image/text sides) becomes
|
||||
the `wb-force-ltr` class defined in `globals.css`.
|
||||
- The theme's `.wb-convert-date` spans keep their timestamp as `data-date`;
|
||||
`SiteBehaviour.tsx` formats them as Jalali dates.
|
||||
- Forms rendered by React components carry `data-react-form` so the theme's
|
||||
generic form handler in `SiteBehaviour.tsx` leaves 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` / `titleEn` and a `gallery` array; the Farsi title is
|
||||
preferred. `gallery` is empty for every current entry, so detail pages fall
|
||||
back to `titleImageUrl`.
|
||||
- Blog and portfolio items expose only the English `categoryName`, hence the
|
||||
separate categories lookup for Farsi labels.
|
||||
- `mainTextHtml` is authored in the business dashboard and rendered with
|
||||
`dangerouslySetInnerHTML`, styled by `.content-body` in `globals.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.ts` looks for a `meshkee_access_token` cookie (the portal
|
||||
shares the apex domain) or the same key in `localStorage`, then confirms it
|
||||
with `GET /auth/me` before 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.com` currently 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 in
|
||||
`tools/.cache` is the last good copy.
|
||||
Reference in New Issue
Block a user