From 5c215ece1beb8e50ec7866c94bc5a48ebde127f6 Mon Sep 17 00:00:00 2001 From: "PC-20260115JRSN\\Administrator" Date: Thu, 19 Mar 2026 15:04:17 +0800 Subject: [PATCH] no message --- src/app/error.tsx | 64 ++++++++++++++++++++++++++++++++++------ src/app/global-error.tsx | 59 ++++++++++++++++++++++++++++++++---- 2 files changed, 108 insertions(+), 15 deletions(-) diff --git a/src/app/error.tsx b/src/app/error.tsx index c365aa8..4c570c9 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -1,11 +1,19 @@ "use client"; -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; +import { useEffect, useMemo, useState } from "react"; + +const STORAGE_KEY = "fpsms_server_wait_until_ms"; +const WAIT_SECONDS = 30; +const RELOAD_INTERVAL_SECONDS = 5; + +function formatSeconds(s: number) { + const v = Math.max(0, Math.floor(s)); + return `${v} 秒`; +} /** * When a server-side exception occurs (e.g. backend down during deploy), - * redirect to login instead of showing the default error page. + * show a reconnect countdown and retry instead of immediate redirect. */ export default function Error({ error, @@ -14,17 +22,55 @@ export default function Error({ error: Error & { digest?: string }; reset: () => void; }) { - const router = useRouter(); + const [remaining, setRemaining] = useState(WAIT_SECONDS); + + const waitUntilMs = useMemo(() => { + if (typeof window === "undefined") return Date.now() + WAIT_SECONDS * 1000; + const existing = Number(window.localStorage.getItem(STORAGE_KEY) || "0"); + const now = Date.now(); + if (existing && existing > now) return existing; + const next = now + WAIT_SECONDS * 1000; + window.localStorage.setItem(STORAGE_KEY, String(next)); + return next; + }, []); useEffect(() => { - // Redirect to login so user can re-authenticate after backend restart - router.replace("/login"); - }, [router]); + const start = Date.now(); + const tick = () => { + const now = Date.now(); + const msLeft = Math.max(0, waitUntilMs - now); + setRemaining(msLeft / 1000); + + // When waiting time ends, go to login. + if (msLeft <= 0) { + window.localStorage.removeItem(STORAGE_KEY); + window.location.href = "/login"; + } + }; + + tick(); + const interval = window.setInterval(tick, 250); + + // Reload periodically to give backend time to come back. + const reloadTimer = window.setInterval(() => { + const elapsedSec = (Date.now() - start) / 1000; + if (elapsedSec >= WAIT_SECONDS) return; + window.location.reload(); + }, RELOAD_INTERVAL_SECONDS * 1000); + + return () => { + window.clearInterval(interval); + window.clearInterval(reloadTimer); + }; + }, [waitUntilMs]); return ( -
+
+

+ 連線異常,伺服器暫停中 +

- 連線異常,正在導向登入頁… + 系統會在後台恢復後自動重試。倒數中:{formatSeconds(remaining)}

); diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index cce15b9..c5ba3d7 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -1,10 +1,19 @@ "use client"; -import { useEffect } from "react"; +import { useEffect, useMemo, useState } from "react"; + +const STORAGE_KEY = "fpsms_server_wait_until_ms"; +const WAIT_SECONDS = 30; +const RELOAD_INTERVAL_SECONDS = 5; + +function formatSeconds(s: number) { + const v = Math.max(0, Math.floor(s)); + return `${v} 秒`; +} /** * Catches root-level errors (e.g. backend down during deploy). - * Redirects to login instead of showing the default "Application error" page. + * Shows a reconnect countdown and retries, then forwards to login if still failing. * Must define and because this replaces the root layout. */ export default function GlobalError({ @@ -14,10 +23,45 @@ export default function GlobalError({ error: Error & { digest?: string }; reset: () => void; }) { - useEffect(() => { - window.location.href = "/login"; + const [remaining, setRemaining] = useState(WAIT_SECONDS); + + const waitUntilMs = useMemo(() => { + const existing = Number(window.localStorage.getItem(STORAGE_KEY) || "0"); + const now = Date.now(); + if (existing && existing > now) return existing; + const next = now + WAIT_SECONDS * 1000; + window.localStorage.setItem(STORAGE_KEY, String(next)); + return next; }, []); + useEffect(() => { + const start = Date.now(); + const tick = () => { + const now = Date.now(); + const msLeft = Math.max(0, waitUntilMs - now); + setRemaining(msLeft / 1000); + + if (msLeft <= 0) { + window.localStorage.removeItem(STORAGE_KEY); + window.location.href = "/login"; + } + }; + + tick(); + const interval = window.setInterval(tick, 250); + + const reloadTimer = window.setInterval(() => { + const elapsedSec = (Date.now() - start) / 1000; + if (elapsedSec >= WAIT_SECONDS) return; + window.location.reload(); + }, RELOAD_INTERVAL_SECONDS * 1000); + + return () => { + window.clearInterval(interval); + window.clearInterval(reloadTimer); + }; + }, [waitUntilMs]); + return ( @@ -31,8 +75,11 @@ export default function GlobalError({ padding: "1rem", }} > -

- 連線異常,正在導向登入頁… +

+ 連線異常,伺服器暫停中 +

+

+ 系統會在後台恢復後自動重試。倒數中:{formatSeconds(remaining)}