# سایت دامنه اختصاصی نماینده سراسری — تشخیص دامنه، فیلتر پزشکان/کلینیک‌ها

## پروژه

`nobat724_front` — **پیش‌نیاز:** پرامپت backend اول اجرا شود: `clinicpro/.claude/prompt/representation-multi-city-domain-commission.md`. این پرامپت مصرف‌کننده قراردادهای آن است:
- `GET /api/v1/site-context?domain=<host>` (عمومی) → `{ type: "city"|"representation"|"unknown", representation: {uuid, full_name, is_global}|null, city: {...}|null }`
- پارامتر جدید `domain` روی `GET /api/v1/doctors` و `GET /api/v1/clinics` — اگر دامنه متعلق به نماینده سراسری باشد، backend فقط پزشکان/کلینیک‌های همان نماینده را برمی‌گرداند.

## زمینه

سایت multi-domain است و دامنه فقط با `data/city.json` تطبیق داده می‌شود (`lib/getStateInfo.js`). نماینده سراسری دامنه اختصاصی خودش را دارد (مثل `x-nobat.ir`) که در city.json نیست → الان چنین دامنه‌ای مثل «بدون شهر» رفتار می‌کند و همه پزشکان را نشان می‌دهد. باید: دامنه نماینده سراسری تشخیص داده شود و فقط پزشکان/کلینیک‌های ثبت‌شده توسط همان نماینده نمایش یابند. کمیسیون خودش backend-side است (از `frontend_address` پرداخت) — فرانت فقط باید مثل الان دامنه درست را در `frontend_address` بفرستد (بدون تغییر).

## مشکل / هدف

۱. `getStateInfo` برای دامنه‌های خارج از city.json از API زمینه بگیرد (`site-context`) و `repContext` برگرداند.
۲. صفحات لیست پزشکان/کلینیک‌ها روی دامنه نماینده سراسری، پارامتر `domain` را به API پاس بدهند.
۳. متادیتا/برندینگ صفحات روی دامنه نماینده از `full_name` نماینده ساخته شود (fallback «نوبت 724»).

## فایل‌های مرتبط

| فایل | نقش |
|------|-----|
| `lib/getStateInfo.js` | تشخیص دامنه — فقط city.json؛ باید repContext هم بدهد |
| `app/doctors/page.js` | لیست پزشکان — fetch `/api/v1/doctors` |
| `app/clinics/page.js` | لیست کلینیک‌ها — fetch `/api/v1/clinics` |
| `app/layout.js` | متادیتای پایه از matchedCity |
| `components/home/*` (سرچ صفحه اصلی) | روی دامنه rep هم باید `domain` را پاس بدهد |
| `lib/req.js` | `fetchReq` برای server-side |

## وضعیت فعلی

`lib/getStateInfo.js` (کامل — کپی واقعی):

```js
export async function getStateInfo() {
  const headersList = await headers();
  const host = headersList.get("host") || "";
  const subdomain = host.split(".")[0];
  const matchedCity = citiesData.find((city) => {
    const cityDomain = city.domain.split(".")[0];
    return cityDomain === subdomain;
  });
  const matchedState =
    matchedCity && statesData.find((state) => state.id === matchedCity.province_id);
  return { matchedCity, matchedState, isRoot: isRootCity(matchedCity) };
}
```

`app/doctors/page.js` (بخش fetch — کپی واقعی):

```js
const { matchedCity, matchedState, isRoot } = await getStateInfo();
// ...
if (cityParams) newSearchParams.city = cityParams;
else if (matchedCity && !isRoot) newSearchParams.city = matchedCity.name;
const params = buildDoctorParams(newSearchParams);
doctors = await fetchReq(`${API_URL}/api/v1/doctors`, { params });
```

## وظایف

### ۱. توسعه `getStateInfo` — repContext

خروجی جدید: `{ matchedCity, matchedState, isRoot, repContext }` که `repContext = { uuid, full_name, is_global } | null`.

```js
export async function getStateInfo() {
  // ... منطق فعلی city.json دست‌نخورده ...
  let repContext = null;
  if (!matchedCity && host) {
    repContext = await fetchSiteContext(host); // فقط وقتی city match نشد
  }
  return { matchedCity, matchedState, isRoot: isRootCity(matchedCity), repContext };
}
```

- `fetchSiteContext(host)`: صدا زدن `GET ${NEXT_PUBLIC_API_URL}/api/v1/site-context?domain=${host}` با `fetchReq`؛ اگر `type === "representation"` → آبجکت representation، وگرنه null. **خطای شبکه هرگز صفحه را نشکند** (try/catch → null) و پاسخ برای هر host **cache شود** (in-memory `Map` در سطح ماژول + `next: { revalidate: 300 }` اگر با fetch native؛ با axios همان Map با TTL ۵ دقیقه کافی است) — این تابع در هر render صدا می‌خورد.
- `localhost` و host خالی → بدون درخواست، null.
- تمام call-siteهای فعلی `getStateInfo` بدون تغییر کار کنند (فیلد اضافه فقط additive است).

### ۲. پاس دادن `domain` در لیست‌ها

در `app/doctors/page.js` و `app/clinics/page.js`:

```js
const { matchedCity, matchedState, isRoot, repContext } = await getStateInfo();
// ...
if (repContext?.is_global) {
  params.domain = host;        // host از headers — از طریق getStateInfo برگردان یا headers() مستقیم؟
                               // الگو: getStateInfo مقدار host را هم برگرداند تا صفحات دوباره parse نکنند
  delete params.city; delete params.state;   // روی دامنه نماینده، فیلتر شهر بی‌معنی است
}
doctors = await fetchReq(`${API_URL}/api/v1/doctors`, { params });
```

- `getStateInfo` فیلد `host` را هم برگرداند (نرمال‌شده) تا هیچ صفحه‌ای خودش `headers()` را برای دامنه parse نکند — هم‌راستا با اصل «سرویس مرکزی دامنه» در backend.
- جستجوی صفحه اصلی (`components/home/search/*`) که client-side به `/api/v1/doctors` می‌زند: از `window.location.hostname` همان پارامتر `domain` را وقتی سایتِ rep است اضافه کند — تشخیص client-side: مقدار repContext از server از طریق props/context (ساده‌ترین راه: `ProvinceProvider` یا prop از layout؛ الگوی موجود client-side پروژه را دنبال کن).

### ۳. متادیتا و برندینگ دامنه نماینده

- `app/layout.js` و `generateMetadata` صفحات doctors/clinics: وقتی `repContext` هست:
  - `siteName = repContext.full_name`
  - title الگو: `نوبت‌دهی آنلاین پزشکان | ${repContext.full_name}`
  - description عمومی (بدون نام شهر).
- Header/Footer: جایی که `matchedCity?.site_name` مصرف می‌شود (`components/layout/*`, `app/component/Logo.js`) fallback به `repContext?.full_name` قبل از «نوبت 724».
- صفحات وابسته به شهر (مثل انتخاب شهر در سرچ): روی دامنه rep رفتار «ریشه» (همه شهرها) بماند — گیت اضافه نزن؛ فقط لیست نتایج فیلتر می‌شود.

## نکات مهم

- **هیچ regression روی دامنه‌های شهری**: مسیر `matchedCity` پیدا شد → `fetchSiteContext` اصلاً صدا زده نشود؛ رفتار فعلی بایت‌به‌بایت حفظ.
- `DEV_MODE=TRUE` مثل قبل noindex — دامنه‌های rep هم مشمول همان robots.
- تست local: `HOST=x-nobat.localhost npm run dev` کار نمی‌کند مگر backend لوکال یک rep با دامنه `x-nobat.localhost` داشته باشد — در گزارش، دستور ساخت rep تستی (از پنل ادمین clinicpro لوکال) را ذکر کن.
- خطای API سایت‌کانتکست → سایت مثل دامنه ناشناخته (رفتار فعلی) — هرگز 500 نشود (درس صفحه contact-us).
- build کامل (`npm run build`) و تست دستی سه حالت: دامنه شهر (yazd-nobat.localhost)، دامنه ریشه، دامنه ناشناخته.
- بعد از پیاده‌سازی: مستندات backend (`clinicpro/docs/api/doctor.md`/`clinic.md`) باید با مصرف واقعی این فرانت هم‌خوان باشد — اگر اختلافی دیدی همان‌جا اصلاح کن.
- **عملیاتی**: هر دامنه نماینده سراسری باید در Coolify به سرویس فرانت و به `ALLOWED_FRONTEND_HOSTS` بک‌اند اضافه شود (CORS/TLS) — در گزارش نهایی یادآوری کن.
