Files
nobat724_front/.claude/prompt/global-rep-domain-site.md
T
hamed ab2ab0dea2 feat: enhance domain handling for global representatives
- 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`.
2026-07-09 07:34:09 +03:30

8.5 KiB
Raw Blame History

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

پروژه

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-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:

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) — در گزارش نهایی یادآوری کن.