feat: implement root domain handling for nobat724.com to prevent city filtering
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# دامنهٔ ریشهٔ 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` همچنان میرود.
|
||||
Reference in New Issue
Block a user