# context.md — Negin Dental Clinic (کلینیک دندانپزشکی نگین) > Interoperability context for AI systems and platforms working on this repository. > Keep this file updated as the project evolves. ## 1. What this project is A **Next.js (App Router)** re-implementation ("basic clone") of the Negin Dental Clinic website, now **wired to the real Meshkee Website API backend** for this business. The clinic is a real dental practice in **Qom, Iran**. - **Language:** Persian (`fa`) - **Direction:** RTL (`dir="rtl"` on ``) - **Reference site cloned:** `https://www.negindentalclinic.ir` (built on Meshkee's web-builder; canonical host is the `.ir` domain). - **Note on the brief:** the original prompt referenced `negindentalclinic.com`. That `.com` domain is a **parked GoDaddy lander** with no content. The live site is the `.ir` domain, and all content/assets/palette were taken from there. - **Meshkee tenant:** this domain is registered as **businessId `36`** ("Negin Dental Clinic") on `api.meshkee.com`, with modules `blog`, `videos`, `finance` enabled. See §9. - **Virtual tour:** the original `/assets/virtualtour.html` (old Flash-era krpano export) is **gone** — 404 on the live origin, and only 6 non-essential files were ever archived by the Wayback Machine (no tour.xml, no panorama tiles, no viewer engine). It is **not recoverable**; `/virtual-tour` still iframes the dead URL pending a decision (new tour, placeholder, or drop the nav item — ask before changing). ## 2. Tech stack | Concern | Choice | | -------------- | ----------------------------------------------------------- | | Framework | Next.js `15.5.25` (App Router, RSC) | | UI library | React `19` | | Styling | Plain CSS + CSS Modules (no Tailwind / CSS-in-JS) | | Fonts | `next/font/google` — **Vazirmatn** (Persian body), **Cinzel** (Latin/English titles) | | Images | `next/image` (local files under `public/assets`, plus remote Meshkee-hosted uploads — see `next.config.mjs`) | | Language/JS | JavaScript (no TypeScript) | | Path alias | `@/*` → repo root (see `jsconfig.json`) | | Backend | **Meshkee Website API** (`https://api.meshkee.com/api/v1`) — see §9 | ## 3. Directory map ``` app/ layout.js # , font vars, Header + Footer, dynamic favicon globals.css # design tokens + shared component classes page.js # Home (+ "latest blog posts" section, hidden when empty) page.module.css about-us/page.js # درباره ما contact-us/page.js # ارتباط با ما (+ ContactForm.js — posts to the Contact API) services/page.js # خدمات کلینیک blog/page.js # مقالات — list, backed by GET /tenants/{domain}/blogs blog/[id]/[slug]/page.js # /blog/{id}/{titleSlug} detail page (canonical Meshkee URL shape) virtual-tour/page.js # تور مجازی — iframes the (dead) original virtualtour.html meshkee/sitemap-config.json/route.js # static-page list for the Meshkee dashboard's sitemap importer components/ Header.js / .module.css # sticky nav + mobile burger + phone + account menu (client component) Footer.js / .module.css # 4-col footer + Meshkee-style bottom copyright row PageHero.js / .module.css # bilingual sub-header band for inner pages lib/ meshkee.js # Meshkee Website API client (tenant fetches, blog helpers, contact POST) auth-client.js # client-side session bridge for the customer account (see §9.3) public/assets/ img/… # banners, section photos, service icons (from the original site) img/Header/header.png # primary logo (کلینیک دندانپزشکی نگین / NEGIN DENTAL CLINIC) files/… # footer logo + misc images from the original site ``` ## 4. Design system (tokens live in `app/globals.css` `:root`) Palette was sampled from the live site's computed styles. | Token | Value | Use | | ----------------- | ---------- | ------------------------------------ | | `--brand` | `#3b559a` | primary blue — buttons, headings, table header | | `--brand-dark` | `#2c4380` | gradients, overlays | | `--cyan` | `#29abe2` | logo cyan — accents, borders | | `--cyan-deep` | `#1c7ba6` | darker blue for the light-blue buttons | | `--en-accent` | `#22356b` | dark navy for English/Latin captions & titles | | `--text` | `#5c6873` | body copy | | `--muted` | `#818695` | secondary copy | | `--heading` | `#333232` | dark headings | | `--bg-tint` | `#eff6fa` | pale-blue section backgrounds | | `--bg-grey` | `#f7f7f7` | alternate section background | Typography convention (matches the original's bilingual headings and the brief's "Cinzel for English titles"): every section shows a small **uppercase Cinzel** English label above a **Vazirmatn** Persian heading. Helper classes: `.sec-head`, `.sec-head__en`, `.sec-head__fa`, `.en-title`, plus `.btn`, `.card`. Body copy (`
`) is **justified** by default (`text-align: justify` + `text-align-last: right`) sitewide. Any paragraph that must stay **centered** (section leads, card blurbs, empty-state text, …) needs **both** `text-align: center` **and** `text-align-last: center` set explicitly on that element/class — `text-align-last` alone governs a single-line paragraph, so `text-align: center` by itself silently loses to the sitewide default. Grep for `text-align-last: center` in the CSS to see every guarded spot before adding a new centered `
`. ## 5. Content / data Static/hardcoded (not from the API — see §9 for what *is* live): - **Clinic phones:** `025-32945077`, `025-32945088`, `025-32945350` - **Address:** قم، بلوار امین، کوچه ۲۱، شماره ۵ (روبروی صدا و سیما) - **Social:** Instagram `neginclinicc` · Aparat `aparat.com/v/3zqwp` · Telegram `negin_clinic` - **Insurances (طرف قرارداد):** دانا، نوین، ایران، بانک ملت، معلم، کوثر، بانک تجارت، دی، سامان - **Doctors (weekly schedule on Home):** شجری، صانعیپور، دیناروند، کریمی، حسینی مطلق، رسولی، صبری - The original site's schedule table markup was partially malformed; the day→doctor mapping here is a best-effort reconstruction. **Verify with the clinic before treating it as authoritative.** - **Services (12):** پروتز، خدمات تخصصی، خدمات عمومی، درمان ریشه، ارتودنسی، پریو، ترمیم تخصصی (ونیر/کامپوزیت)، کودکان، جراحی ایمپلنت، مشاوره، خدمات حضوری - **Email:** `info@negindentalclinic.ir` (the original site literally showed a placeholder `noreply@envato.com`; replaced with a sensible clinic address). `GET /tenants/{domain}/website/business-info` (about/emails/phones/addresses/ social) is wired in `lib/meshkee.js` but **not yet used** — the client's dashboard has it all empty right now, and the static content above is more complete. Switch the footer/contact page over to it (with the static content as fallback) once the dashboard is filled in. ## 6. Known gaps / TODO 1. **Appointment booking** — buttons currently link to `/contact-us`. Decide on a real booking flow (customer portal? a dedicated form?). 2. **Doctors page** — no dedicated route yet; "معرفی پزشکان" points to `/about-us`. 3. **Virtual tour** — unrecoverable (see §1). Needs a product decision: record a new tour, or replace `/virtual-tour` + the nav link with something else. 4. **Fonts** — using Vazirmatn as a free stand-in for the original's IRANYekan (proprietary). Swap if a licensed IRANYekan is available. 5. **SEO — mostly done** (see §10): unique title/description per page, single H1 with clean H1→H2→H3 nesting (incl. a couple of `visually-hidden` H2s added purely to fix skipped levels — no visual change), all images have real/intentionally-empty alt text, `/meshkee/sitemap-config.json` is published. Still open: JSON-LD (`Dentist` schema). Do **not** add `app/sitemap.ts` / `app/robots.ts` — Meshkee's nginx proxies `/sitemap.xml` and `/robots.txt` for the live domain (see AI_PROMPT.md rule in §9); shipping local copies would conflict with those routes once this is deployed behind the Meshkee proxy. 6. **`meshkee/static-image-slots`** — the Meshkee AI brief also asks for a catalog-of-image-slots route. Not implemented — this site doesn't use static-image slots at all yet; add it if that changes. 7. **Business-info** — see §5; not wired into the UI yet. ## 7. Commands ```bash npm install npm run dev # http://localhost:3000 npm run build # production build npm run start # serve the production build ``` **Don't run `npm run build` while `npm run dev` is pointed at the same `.next/` folder** — dev and production write incompatible `.next` output and the dev server will crash with `Cannot find module './NNN.js'`. If that happens: stop both, `rm -rf .next node_modules/.cache`, restart dev. ## 8. Conventions for contributors / agents - Keep everything **RTL-first** and in Persian. Latin text only for the Cinzel accent labels and brand name. - Use logical CSS properties (`margin-inline`, `inset-inline-*`, `padding-inline`). - New shared styles → `globals.css` tokens/classes; page-specific → co-located `*.module.css`. - Prefer server components; add `"use client"` only for interactivity (Header menu/account, ContactForm). - Images: local site assets go in `public/assets/…`; anything fetched from the Meshkee API (blog covers, favicon, logos) is a remote URL — both go through `next/image` (remote hosts are wildcarded in `next.config.mjs` since the upload CDN host isn't fixed). - Numbers (phone numbers, prices, dates in Latin digits) need `dir="ltr"` (see the `.ltr` / `[dir="ltr"]` rule in `globals.css`) so they don't get bidi-reordered inside RTL text. ## 9. Meshkee Website API integration **Docs:** hub `https://api.meshkee.com/docs/website` · OpenAPI `https://api.meshkee.com/docs/website/openapi.json` · AI brief `https://api.meshkee.com/docs/website/AI_PROMPT.md`. Base URL `https://api.meshkee.com/api/v1`. This site's apex domain (`negindentalclinic.ir`, configurable via `NEXT_PUBLIC_WEBSITE_DOMAIN`) → businessId **36**. All client code goes through `lib/meshkee.js` — add new endpoints there rather than calling `fetch` ad hoc from a page. ### 9.1 What's live right now | Feature | Endpoint | Where | |---|---|---| | Favicon (fallback to local logo if none uploaded) | `GET /tenants/{domain}/website/favicon` | `app/layout.js` (`generateMetadata`) | | Blog list | `GET /tenants/{domain}/blogs` | `app/blog/page.js` | | Blog detail | `GET /tenants/{domain}/blogs/by-id/{id}` | `app/blog/[id]/[slug]/page.js` | | Latest posts on home | `GET /tenants/{domain}/blogs?pageSize=3` | `app/page.js` (hidden when 0 posts) | | Contact form | `POST /tenants/{domain}/contact-submissions` | `app/contact-us/ContactForm.js` — shows a success/error toast and resets the form | As of this writing the tenant has **0 published blogs** and an **empty business-info** (no logo/favicon/emails/phones/addresses uploaded in the dashboard yet) — `lib/meshkee.js` fetchers return `null`/empty and every consumer has an explicit empty state, so none of this crashes; it'll just start showing real content the moment the dashboard is filled in. The contact form was tested end-to-end against the live API (real `201` response). ### 9.2 Blog field names — unverified The public API doesn't publish a formal response schema for blog items (no `components.schemas` in the OpenAPI doc, and there are 0 posts to inspect a real payload from). `lib/meshkee.js`'s `blogTitle()` / `blogExcerpt()` / `blogCover()` / `blogDate()` / `blogContentHtml()` helpers read defensively across the likely field-name variants (`titleFa`/`title`, `coverUrl`/ `coverImage`/`image.url`, `content`/`body`/`html`, …). **Once the client publishes a real post, check the actual response shape and simplify/fix these helpers** — they're written to degrade gracefully, not to be the final word on field names. ### 9.3 Header auth state — best-effort, needs confirmation The brief: user icon → `customer.{domain}` when signed out; when signed in, show "**{name} عزیز**" with a **پروفایل من / خروج** dropdown, plus **مدیریت وبسایت** (→ `app.{domain}`, the admin dashboard — matches the `app.negindentalclinic.ir/auth/login` link found on the original live site) for admins. Auth on this API is **Bearer JWT** (`POST /auth/login` returns `{ user, accessToken, refreshToken }` in the body — not a shared cookie), and this storefront **has no login form of its own** (by design — login happens on the separate customer portal). The public docs don't say how a session on `customer.{domain}` is supposed to reach this site. `lib/auth-client.js` implements the standard fallback for that gap: the customer portal is assumed to redirect back here with `?token=&refreshToken=` in the URL, which gets captured into `localStorage` and used to call `GET /auth/me`. **This hand-back mechanism is an assumption, not a documented contract.** If Meshkee's customer portal actually bridges sessions differently (shared cookie on the apex domain, postMessage, etc.), update `lib/auth-client.js` — every consumer of auth state goes through `useMeshkeeSession()` / `isAdminUser()`, so the header and any future account-aware UI won't need to change. Also unverified: the exact field name for "is this user an admin" on the `/auth/me` response (`isAdminUser()` checks `isAdmin`/`role`/`type` defensively). ### 9.4 Hard rules from the Meshkee AI brief (apply if/when this grows) - Never invent endpoints outside `/tenants/{domain}/...` (public) and `/businesses/{businessId}/...` + Bearer JWT (authenticated cart/orders/ favorites) — this site currently uses neither the cart nor any auth-write endpoints. - Don't add `app/sitemap.ts` / `app/robots.ts` or ship `public/sitemap.xml` — Meshkee's proxy serves those from the API once this site is live on a Meshkee-managed domain. - Don't call `/torob_api/...` from Next.js — it's an nginx-proxied path, not a storefront fetch. ## 10. SEO Per the Meshkee SEO brief (unique title/description, one clean H1→H2→H3 outline per page, real `alt` text, sitemap-config publishing): - Every route has its own `metadata` (or `generateMetadata` for the blog detail page, pulling from CMS `title`/excerpt) — no page relies on the root layout's default title/description except `/` itself. - Exactly one `