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>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
import { DEFAULT_MAINTENANCE_MESSAGE } from "@/lib/maintenance";
|
||||
|
||||
const RECHECK_INTERVAL_MS = 60_000;
|
||||
|
||||
function MaintenancePage() {
|
||||
const [message, setMessage] = useState(DEFAULT_MAINTENANCE_MESSAGE);
|
||||
|
||||
// پیام واقعی را interceptor هنگام دریافت ۵۰۳ ذخیره کرده است.
|
||||
useEffect(() => {
|
||||
try {
|
||||
const stored = sessionStorage.getItem("maintenance_message");
|
||||
if (stored) setMessage(stored);
|
||||
} catch { }
|
||||
}, []);
|
||||
|
||||
// اگر تعمیرات تمام شد کاربر نباید تا رفرش دستی پشت این صفحه بماند.
|
||||
useEffect(() => {
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch("/", { method: "HEAD", cache: "no-store" });
|
||||
if (response.status !== 503) window.location.href = "/";
|
||||
} catch { }
|
||||
}, RECHECK_INTERVAL_MS);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="py-[105px] padding-responsive sm:py-[135px] md:py-[165px] lg:py-[201px] min-h-screen flex flex-col md:flex-row items-center justify-between gap-[50px]">
|
||||
<div className="flex w-full md:w-fit flex-col justify-center items-center">
|
||||
<Image
|
||||
className=" flex md:hidden mt-[20px] w-[200px] sm:w-[270px] md:w-[340px] lg:w-[411px] h-[200px] sm:h-[270px] md:h-[340px] lg:h-[405px] "
|
||||
src="/assets/images/notfound-cover.png"
|
||||
alt="maintenance-cover"
|
||||
height={405}
|
||||
width={411}
|
||||
/>
|
||||
<p className="text-[#3B3B3B] text-[20px] sm:text-[24px] md:text-[28px] lg:text-[32px] font-bold mt-[24px] sm:mt-[30px] md:mt-[35px] lg:mt-[40px] mb-[12px] sm:mb-[16px] md:mb-[20px] lg:mb-[24px] text-center">
|
||||
در حال بهروزرسانی سیستم
|
||||
</p>
|
||||
<p className="text-[#525252] text-[14px] md:text-[16px] font-normal text-center max-w-[520px]">
|
||||
{message}
|
||||
</p>
|
||||
<Button
|
||||
className="!h-[43px] md:!h-[45px] lg:!h-[46px] !mt-[20px] sm:!mt-[29px] md:!mt-[39px] lg:!mt-[48px] !px-[12px] !text-[#EFEFEF] !text-[14px] md:!text-[16px] !font-medium"
|
||||
variant="contained"
|
||||
onClick={() => { window.location.href = "/"; }}
|
||||
>
|
||||
تلاش مجدد
|
||||
</Button>
|
||||
</div>
|
||||
<Image
|
||||
className=" hidden md:flex w-[200px] sm:w-[270px] md:w-[340px] lg:w-[411px] h-[200px] sm:h-[270px] md:h-[340px] lg:h-[405px] "
|
||||
src="/assets/images/notfound-cover.png"
|
||||
alt="maintenance-cover"
|
||||
height={405}
|
||||
width={411}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MaintenancePage;
|
||||
Reference in New Issue
Block a user