- Updated `getStateInfo` to fetch site context for domains not in city.json, returning `repContext` with representative details. - Implemented caching for site context requests to optimize performance. - Modified doctor and clinic listing pages to pass the `domain` parameter when fetching data for global representatives. - Adjusted metadata generation in layout and pages to reflect representative branding based on `repContext`. - Added documentation for the new functionality in `.claude/prompt/global-rep-domain-site.md`.
8.5 KiB
سایت دامنه اختصاصی نماینده سراسری — تشخیص دامنه، فیلتر پزشکان/کلینیکها
پروژه
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 (کامل — کپی واقعی):
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 — کپی واقعی):
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.
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-memoryMapدر سطح ماژول +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:
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) — در گزارش نهایی یادآوری کن.