8.7 KiB
دامنهٔ ریشهٔ 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
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 (باگ نوع)
// 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)
const defaultCity =
(matchedCity?.name && matchedCity?.id !== "600" // ← همان باگ
? matchedCity?.name
: "") /* ... */;
context/ProvinceProvider.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
<... cityId={matchedCity?.id ?? null} ... /> // روی ریشه → 600
services/response.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 فلگ برگردان:
// 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 تزریق نشود
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 استفاده کن:
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
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/شمارش مبتنی بر شهر)
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 یکدست کن:
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بدون خطا؛ سپس با hostnobat724.com(یا mock header) بررسی کن کهcity_idنمیرود و هدراستان / شهراست، و با یک دامنهٔ شهری بررسی کنcity_idهمچنان میرود.