From ec536bfc327dab7c9ada74f6e708219a45cfea29 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Fri, 5 Jun 2026 21:47:42 +0330 Subject: [PATCH] fix: resolve sitemap build error caused by invalid Date values - Replace new Date(b.created) with toSafeDate() helper to guard against invalid date strings from blog API responses - Use a single NOW constant instead of new Date() per entry to avoid serialization issues during static generation - Remove unused imports (cityData, DOMAIN_CONFIG, etc.) - Add CLAUDE.md with project architecture and development guidance Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 124 +++++++++++++++++++++++++++++++++++++++++++++++++ app/sitemap.js | 50 +++++++++----------- 2 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4bfad16 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,124 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +npm run dev # Dev server at http://yazd-nobat.localhost:3000 (sets HOST via cross-env) +npm run build # Production build +npm run start # Start production server +npm run lint # ESLint +``` + +The dev script forces `HOST=yazd-nobat.localhost` so multi-domain detection works locally. To test a different city subdomain, temporarily change `HOST` in the script. + +## Environment Variables + +```env +NEXT_PUBLIC_API_URL=https://api.clinic-pro.ir # Backend API base URL +NEXT_PUBLIC_CLIENT_ID=... # OAuth client ID +NEXT_PUBLIC_CLIENT_SECRET=... # OAuth client secret +DEV_MODE=TRUE # TRUE → blocks all crawlers + noindex +``` + +`DEV_MODE=TRUE` disables robots indexing and adds `noindex` metadata. Set to `FALSE` in production. + +## Architecture Overview + +**Next.js 15 App Router** with MUI v5 + Tailwind CSS, RTL (Persian/Farsi), Jalali calendar. + +### Multi-Domain System (Core Concept) + +Each city has its own domain (e.g. `yazd-nobat.ir`, `tabriz-nobat.ir`). The codebase serves all cities from one deployment, detecting which city to show via subdomain. + +- **`data/city.json`** — source of truth: every city's `domain`, `title`, `description`, `keywords`, `site_name`, `socialMedia`, etc. +- **`lib/getStateInfo.js`** — server-side: reads `host` header, extracts subdomain, matches against `city.json`. Use in Server Components and `generateMetadata`. +- **`context/ProvinceProvider.js`** — client-side equivalent using `window.location.hostname`. Exposes `useProvince()` → `{ isProvinceInclude }`. +- **`lib/getCanonicalUrl.js`** — reads `x-pathname` header set by middleware for canonical URL generation. +- **`middleware.js`** — injects `x-pathname` header into every response so `getCanonicalUrl` can read it. + +### Page Metadata Pattern + +Every page must export `generateMetadata`. Layout-level metadata is the fallback: + +```js +// app/layout.js — sets title/description/OG/Twitter from matchedCity +export async function generateMetadata() { + const { matchedCity } = await getStateInfo(); + // ... +} +``` + +Page-level `generateMetadata` overrides layout for dynamic pages (doctor, blog, clinic): + +```js +export async function generateMetadata({ params }) { + const { slug } = await params; // Always await params in Next.js 15 + // fetch data, build title/description, return metadata object +} +``` + +### Data Fetching + +**Server Components** use: +- `lib/req.js` → `fetchReq(url)` — axios with SSL verification disabled (needed for dev backend) +- `axios` directly for cases needing more control + +**Client Components** use: +- `services/api.js` — axios instance with `baseURL = NEXT_PUBLIC_API_URL` +- `services/response.js` → `request.*` — all API call wrappers. Pass `{ requireAuth: true }` to attach the `access_token` cookie as `Authorization: Bearer`. + +### Authentication & Authorization + +Auth uses JWT stored in cookies: `access_token`, `refresh_token`, `uuid`, `userInfo`. + +- **`lib/auth.js`** → `getUser()` — reads cookies server-side +- **`lib/ability.js`** → `defineAbilitiesFor(user)` — CASL rules. Roles: `"representation"` → Panel access +- Protected pages call `getUser()` + `defineAbilitiesFor()` and redirect if unauthorized + +Login flow: POST `/api/v1/user/send-code` (OTP) → POST `oauth/token` → set cookies. + +### Styling + +- **Tailwind CSS** with `darkMode: "class"` — public pages use `data-theme` attribute, panel uses `class` +- **MUI theme** configured in `mui/index.js` with RTL direction and Vazir font +- **Font**: Vazir only — defined in `app/globals.css` via `@font-face` with `font-display: swap`. No other fonts. +- Custom CSS classes in `globals.css`: `.bg-banner-home`, `.bg-banner-footer`, `.padding-responsive`, etc. +- Dark mode toggled by `next-themes` in `app/Providers.js`: public = `attribute="data-"`, panel = `attribute="class"` + +### Routing Structure + +``` +app/ + layout.js # Root layout: metadata, ThemeRegistry, ProvinceProvider + page.js # Home → components/home/ + robots.js # Blocks all when DEV_MODE=TRUE + sitemap.js # Fetches doctors/clinics/blogs from API at runtime + doctor/[slug]/page.js # generateMetadata + JSON-LD (Physician schema) + clinic/[slug]/page.js # generateMetadata + JSON-LD (MedicalClinic schema) + blog/[slug]/page.js # generateMetadata + JSON-LD (Article schema) + doctors/page.js # generateMetadata using matchedCity + clinics/page.js # generateMetadata using matchedCity + panel/(layout)/ # Route group — requires "representation" role +``` + +All public pages wrap content in `` from `components/layout/StLayout.js` (header + footer). Panel pages use `components/layoutPanel/`. + +### Key Data Files + +- `data/city.json` — city configs including domain, SEO fields, social media links +- `data/state.json` — province/state data, joined to city via `province_id` +- `data/specialties.json` — medical specialties; items with `parent` field are sub-specialties shown in FrequentSearches + +### Doctor & Clinic Slugs + +Both use `uuid` as the URL slug: `/doctor/${doctor.uuid}` and `/clinic/${clinic.uuid}`. + +### JSON-LD Structured Data + +Added directly in page JSX (not via metadata API): +```jsx +