Files
nobat724_front/.claude/prompt/global-rep-domain-site.md
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

117 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# سایت دامنه اختصاصی نماینده سراسری — تشخیص دامنه، فیلتر پزشکان/کلینیک‌ها
## پروژه
`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` (کامل — کپی واقعی):
```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 — کپی واقعی):
```js
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`.
```js
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`:
```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) — در گزارش نهایی یادآوری کن.