Files
nobat724_front/app/clinics/page.js
T
hamedandClaude Fable 5 daf38c8631 feat(maintenance): show maintenance page when the API is in maintenance
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>
2026-07-19 22:01:45 +03:30

81 lines
3.5 KiB
JavaScript

import { fetchReq } from "@/lib/req";
import { isNextRedirectError } from "@/lib/maintenance";
import ClinicsPage from "@/components/clinics";
import Layout from "@/components/layout/StLayout";
import { getStateInfo } from "@/lib/getStateInfo";
import { buildClinicParams } from "@/helper";
import { listingRobots } from "@/lib/listingRobots";
export async function generateMetadata({ searchParams }) {
const awaitedParams = await searchParams;
const { matchedCity, matchedState, isRoot, repContext } = await getStateInfo();
const siteName = matchedCity?.site_name || repContext?.full_name || "نوبت 724";
// روی دامنهٔ ریشه نام رکورد («نوبت 724») برند است نه شهر — نباید در Title بنشیند.
const cityName = isRoot ? "" : matchedCity?.name || matchedState?.name || "";
const title = cityName
? `کلینیک‌های ${cityName} | جستجو و رزرو نوبت | ${siteName}`
: `جستجوی کلینیک و مراکز درمانی | ${siteName}`;
const description = cityName
? `لیست کلینیک‌ها و مراکز درمانی در ${cityName}. رزرو آنلاین نوبت از بهترین مراکز درمانی ${cityName}.`
: `جستجوی کلینیک‌ها و مراکز درمانی در سراسر کشور. رزرو آنلاین نوبت سریع و آسان.`;
const image = "/assets/images/og-image.png";
return {
title,
description,
...listingRobots(awaitedParams, { matchedCity, matchedState }),
openGraph: { title, description, images: [image] },
twitter: { card: "summary_large_image", title, description, images: [image] },
};
}
export default async function Clinics({ searchParams }) {
const awaitedSearchParams = await searchParams;
const { matchedCity, matchedState, isRoot, repContext, host } = await getStateInfo();
const API_URL = process.env.NEXT_PUBLIC_API_URL;
const stateParams = awaitedSearchParams.state;
const cityParams = awaitedSearchParams.city;
let clinics;
try {
// Params
// کپی — awaitedSearchParams همان شیئی است که generateMetadata هم می‌خواند؛
// تزریق city/state روی خودش، سیاست robots را به noindex می‌برد.
const newSearchParams = { ...awaitedSearchParams };
// Conditional State — روی دامنهٔ ریشه (nobat724) فیلتر اعمال نمی‌شود.
if (stateParams) newSearchParams.state = stateParams;
else if (matchedState && !isRoot) newSearchParams.state = matchedState.name;
// Conditional City
if (cityParams) newSearchParams.city = cityParams;
else if (matchedCity && !isRoot) newSearchParams.city = matchedCity.name;
const params = buildClinicParams(newSearchParams);
// دامنه‌ی نماینده سراسری: backend فقط کلینیک‌های همان نماینده را برمی‌گرداند؛ فیلتر شهر بی‌معنی است.
if (repContext?.is_global) {
delete params.city;
delete params.state;
params.domain = host;
}
// Req
clinics = await fetchReq(`${API_URL}/api/v1/clinics`, {
params,
});
} catch (err) {
// ریدایرکت نکست با throw کار می‌کند؛ بدون این rethrow، ریدایرکت حالت تعمیرات بلعیده می‌شود.
if (isNextRedirectError(err)) throw err;
}
return (
<Layout name="/clinics">
<ClinicsPage
clinics={clinics}
matchedCity={matchedCity}
matchedState={matchedState}
/>
</Layout>
);
}