# دامنهٔ ریشهٔ nobat724.com نباید مثل «شهر» رفتار کند

## پروژه

`nobat724_front` (سایت عمومی — Next.js 15، multi-domain)

## زمینه

هر شهر یک دامنهٔ اختصاصی دارد (`data/city.json`؛ مثل `yasuj-nobat.ir`) و `lib/getStateInfo.js` شهر را از روی subdomain تشخیص می‌دهد. اما `nobat724.com` یک رکورد **استثنا** در `city.json` است: `{ id: 600, name: "نوبت 724", domain: "nobat724.com", province_id: null }`. این دامنه **شهر نیست** — دامنهٔ ریشه/مخزنِ همهٔ شهرهاست.

الان به‌خاطر این رکورد، وقتی `nobat724.com` باز می‌شود سیستم آن را مثل یک «شهر» با `city_id=600` می‌بیند:
- درخواست لیست پزشکان: `/api/v1/doctors?city_id=600&page=1&limit=12` (باید بدون `city_id` = سراسری باشد).
- هدر انتخاب شهر: `استان / نوبت 724` (باید placeholder `استان / شهر` باشد چون شهری انتخاب نشده).

علت فنی: در چند جا محافظ `matchedCity.id !== "600"` نوشته شده ولی `id` **عدد `600`** است و `"600"` **رشته** → `600 !== "600"` همیشه `true` → استثنا هرگز اعمال نمی‌شود.

## مشکل / هدف

`nobat724.com` (id 600) به‌عنوان **دامنهٔ ریشه** شناخته شود:
- هیچ `city_id`/`state_id` به API تزریق نشود (لیست پزشکان سراسری).
- شهر/استان به‌صورت پیش‌فرض «انتخاب‌نشده» باشد (هدر: `استان / شهر`).
- برندینگ (`site_name` = «نوبت 724»، slogan) حفظ شود.

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

| فایل | نقش |
|------|-----|
| `nobat724_front/lib/getStateInfo.js` | افزودن فلگ `isRoot` |
| `nobat724_front/app/doctors/page.js` | حذف تزریق city روی ریشه (باگ `!== "600"`) |
| `nobat724_front/components/doctors/index.js` | filter پیش‌فرض بدون شهر روی ریشه (باگ `!== "600"`) |
| `nobat724_front/context/ProvinceProvider.js` | `cityId=null` روی ریشه (client) |
| `nobat724_front/app/specialties/page.js` | `cityId` روی ریشه = null |
| (در صورت وجود) هر جای دیگری که `matchedCity.id` → `city_id` می‌شود |

## وضعیت فعلی

### `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) => city.domain.split(".")[0] === subdomain);
  const matchedState = matchedCity && statesData.find((s) => s.id === matchedCity.province_id);
  return { matchedCity, matchedState };
}
```

### `app/doctors/page.js` (باگ نوع)
```js
// Conditional City
if (cityParams) newSearchParams.city = cityParams;
else if (matchedCity && matchedCity.id !== "600")   // ← 600 عدد است، "600" رشته → همیشه true
  newSearchParams.city = matchedCity.name;
```

### `components/doctors/index.js` (باگ نوع در defaultFilter)
```js
const defaultCity =
  (matchedCity?.name && matchedCity?.id !== "600"     // ← همان باگ
    ? matchedCity?.name
    : "") /* ... */;
```

### `context/ProvinceProvider.js`
```js
const isProvinceInclude = citiesData.find((city) => hostName.includes(city.domain.split(".")[0]));
// ...
<ProvinceContext.Provider value={{ isProvinceInclude, cityId: isProvinceInclude?.id ?? null }}>
// روی nobat724.com → cityId = 600
```

### `app/specialties/page.js`
```js
<... cityId={matchedCity?.id ?? null} ... />   // روی ریشه → 600
```

### `services/response.js` (بدون تغییر — فقط برای مرجع)
```js
getDoctors: (params) => api.get("api/v1/doctors", { params, ... }),
getSpecialtyDoctorCounts: (cityId) =>
  api.get("api/v1/specialties/doctor-counts", { params: cityId ? { city_id: cityId } : {}, ... }),
```
`helper/index.js` → `buildDoctorParams` فقط وقتی `searchParams.city` مقدار دارد `city_id` می‌سازد؛ پس اگر روی ریشه city ست نشود، `city_id` هم نمی‌رود.

## وظایف

### ۱. تعریف مرکزی ریشه + `isRoot` در `getStateInfo`

یک ثابت مشترک بساز و در `getStateInfo` فلگ برگردان:

```js
// lib/getStateInfo.js
export const ROOT_CITY_ID = 600;                 // رکورد nobat724.com در city.json (شهر نیست)
export const isRootCity = (c) => c != null && Number(c.id) === ROOT_CITY_ID;

export async function getStateInfo() {
  // ... مثل قبل ...
  const isRoot = isRootCity(matchedCity);
  return { matchedCity, matchedState, isRoot };
}
```

> `matchedCity` را برای برندینگ (site_name/slogan/description) نگه دار؛ فقط منطقِ **فیلتر شهر** باید روی ریشه غیرفعال شود.

### ۲. `app/doctors/page.js` — روی ریشه city تزریق نشود

```js
const { matchedCity, matchedState, isRoot } = await getStateInfo();
// ...
// Conditional City
if (cityParams) newSearchParams.city = cityParams;
else if (matchedCity && !isRoot) newSearchParams.city = matchedCity.name;
```
(اگر `matchedState` روی ریشه هم null است مشکلی نیست؛ چون province_id=null. ولی برای اطمینان می‌توانی `else if (matchedState && !isRoot)` بگذاری.)

### ۳. `components/doctors/index.js` — defaultFilter بدون شهر روی ریشه

`isRoot` را از صفحهٔ سرور به‌عنوان prop بده یا محلی محاسبه کن (`Number(matchedCity?.id) === 600`)، و در `defaultCity`/`defaultFilter` استفاده کن:

```js
const isRoot = Number(matchedCity?.id) === 600;
const defaultCity = (matchedCity?.name && !isRoot) ? matchedCity.name : "";
const defaultState = (matchedState?.name && !isRoot) ? matchedState.name : "";
```
با این کار `filter.city`/`filter.state` روی ریشه خالی می‌مانند و `FilterLocate` هدر را `استان / شهر` نشان می‌دهد (نه «نوبت 724»).

### ۴. `context/ProvinceProvider.js` — cityId روی ریشه null

```js
const isRoot = Number(isProvinceInclude?.id) === 600;
const cityId = isRoot ? null : (isProvinceInclude?.id ?? null);
// اگر مصرف‌کننده‌ها به isProvinceInclude برای «شهر فعلی» تکیه دارند، روی ریشه آن را هم null بده:
const currentCity = isRoot ? null : isProvinceInclude;
// value={{ isProvinceInclude: currentCity, cityId }}
```
(بررسی کن مصرف‌کننده‌های `useProvince()` با `null` درست کار کنند — یعنی «شهری انتخاب نشده».)

### ۵. `app/specialties/page.js` (و هر `getSpecialtyDoctorCounts`/شمارش مبتنی بر شهر)

```js
const { matchedCity, matchedState, isRoot } = await getStateInfo();
// ...
cityId={isRoot ? null : (matchedCity?.id ?? null)}
```

### ۶. جستجوی باقی‌ماندهٔ الگوی باگ

کل `nobat724_front` را برای `!== "600"` / `=== "600"` / `id === 600`/`city_id` مبتنی بر `matchedCity`/`isProvinceInclude` بگرد و همه را با `isRoot`/`ROOT_CITY_ID` یکدست کن:
```bash
grep -rn '"600"\|=== 600\|!== 600\|matchedCity?.id\|isProvinceInclude?.id' app components context lib services helper
```

## نکات مهم

- **type-safety:** id در `city.json` عدد است؛ هرگز با رشتهٔ `"600"` مقایسه نکن. از `Number(x) === ROOT_CITY_ID` یا `isRootCity()` استفاده کن.
- **برندینگ حفظ شود:** روی ریشه، `matchedCity.site_name`/`slogan`/عنوان متا («نوبت 724») درست است؛ فقط فیلتر شهر و هدر انتخاب‌شهر نباید ۶۰۰ را به‌عنوان شهرِ انتخاب‌شده نشان دهند.
- **canonical:** در `app/layout.js` از قبل `matchedCity.domain !== 'nobat724.com'` هست؛ دست نزن، فقط اگر خواستی با `isRoot` یکدست کن.
- **رفتار موردانتظار روی nobat724.com:** لیست پزشکان بدون `city_id` (سراسری)، هدر `استان / شهر`، انتخاب شهر توسط کاربر باعث ست‌شدن فیلتر و رفتن `city_id` واقعی می‌شود.
- **رفتار دامنه‌های شهری تغییر نکند:** روی `yasuj-nobat.ir` و بقیه باید دقیقاً مثل قبل `city_id` شهر برود.
- backend تغییری ندارد؛ فقط رفتار کلاینت. بعد از تغییر: `npm run build` و `npm run lint` بدون خطا؛ سپس با host `nobat724.com` (یا mock header) بررسی کن که `city_id` نمی‌رود و هدر `استان / شهر` است، و با یک دامنهٔ شهری بررسی کن `city_id` همچنان می‌رود.
