The backend now answers 503 with code MAINTENANCE_MODE while maintenance is on. Without this change a visitor got a red error toast over a broken page client-side, and a silently empty page server-side, because fetchReq discards the status and returns null on any failure. - lib/maintenance.js detects the state by BOTH status 503 and the error code; a bare 503 can come from a reverse proxy and is not maintenance - The axios interceptor checks it before the 401 branch, so a maintenance response never triggers the refresh-token path or logs the user out - fetchReq redirects to /maintenance, with a silentMaintenance opt-out used by getStateInfo: that one runs inside generateMetadata and while rendering the maintenance page itself, where a redirect is either ineffective or loops - redirect() works by throwing, so the try/catch blocks in the doctors, clinics and specialties pages now rethrow NEXT_REDIRECT instead of swallowing it - clinicApi.js handles 503 too; it previously rendered maintenance as a clinic with zero doctors - The page reuses the existing 404 design and is marked noindex Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
73 lines
2.6 KiB
JavaScript
73 lines
2.6 KiB
JavaScript
import { headers } from "next/headers";
|
|
|
|
// Data
|
|
import citiesData from "@/data/city.json";
|
|
import statesData from "@/data/state.json";
|
|
import { fetchReq } from "@/lib/req";
|
|
|
|
import { ROOT_CITY_ID, isRootCity } from "@/lib/rootCity";
|
|
export { ROOT_CITY_ID, isRootCity };
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
|
|
// دامنههای خارج از city.json (دامنه اختصاصی نمایندگان سراسری) از backend پرسیده میشوند
|
|
// (GET /api/v1/site-context). cache ماژولسطح تا هر render یک درخواست نزند؛
|
|
// خطای شبکه هرگز صفحه را نمیشکند (null = مثل دامنه ناشناخته).
|
|
const SITE_CONTEXT_TTL_MS = 5 * 60 * 1000;
|
|
const siteContextCache = new Map();
|
|
|
|
async function fetchSiteContext(host) {
|
|
if (!host || host === "localhost" || host === "127.0.0.1" || !API_URL) return null;
|
|
|
|
const cached = siteContextCache.get(host);
|
|
if (cached && cached.expires > Date.now()) return cached.value;
|
|
|
|
let value = null;
|
|
try {
|
|
// این تابع از generateMetadata و از خودِ صفحهی تعمیرات هم صدا زده میشود؛
|
|
// ریدایرکت اینجا یا بیاثر است یا حلقه میسازد.
|
|
const json = await fetchReq(
|
|
`${API_URL}/api/v1/site-context?domain=${encodeURIComponent(host)}`,
|
|
undefined,
|
|
{ silentMaintenance: true }
|
|
);
|
|
if (json?.data?.type === "representation") {
|
|
value = json.data.representation; // { uuid, full_name, is_global }
|
|
}
|
|
} catch {
|
|
value = null;
|
|
}
|
|
|
|
siteContextCache.set(host, { value, expires: Date.now() + SITE_CONTEXT_TTL_MS });
|
|
return value;
|
|
}
|
|
|
|
export async function getStateInfo() {
|
|
const headersList = await headers();
|
|
|
|
const rawHost = headersList.get("host") || "";
|
|
const host = rawHost.split(":")[0];
|
|
const subdomain = host.split(".")[0];
|
|
|
|
// Match city by domain (supports both full domain and subdomain)
|
|
const matchedCity = citiesData.find((city) => {
|
|
const cityDomain = city.domain.split(".")[0]; // Extract subdomain from domain (e.g., "yasuj-nobat" from "yasuj-nobat.ir")
|
|
return cityDomain === subdomain;
|
|
});
|
|
|
|
const matchedState =
|
|
matchedCity &&
|
|
statesData.find((state) => state.id === matchedCity.province_id);
|
|
|
|
// فقط وقتی هیچ شهری match نشد سراغ backend میرویم — رفتار دامنههای شهری دستنخورده میماند.
|
|
const repContext = matchedCity ? null : await fetchSiteContext(host);
|
|
|
|
return {
|
|
matchedCity,
|
|
matchedState,
|
|
isRoot: isRootCity(matchedCity),
|
|
repContext,
|
|
host,
|
|
};
|
|
}
|