ソースを参照

no message

MergeProblem1
PC-20260115JRSN\Administrator 2日前
コミット
5c215ece1b
2個のファイルの変更108行の追加15行の削除
  1. +55
    -9
      src/app/error.tsx
  2. +53
    -6
      src/app/global-error.tsx

+ 55
- 9
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 (
<div className="flex min-h-[200px] flex-col items-center justify-center gap-2 p-4 text-center">
<div className="flex min-h-[200px] flex-col items-center justify-center gap-2 p-6 text-center">
<p className="text-sm font-semibold text-slate-700 dark:text-slate-200">
連線異常,伺服器暫停中
</p>
<p className="text-sm text-slate-600 dark:text-slate-400">
連線異常,正在導向登入頁…
系統會在後台恢復後自動重試。倒數中:{formatSeconds(remaining)}
</p>
</div>
);


+ 53
- 6
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 <html> and <body> 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 (
<html lang="zh-TW">
<body>
@@ -31,8 +75,11 @@ export default function GlobalError({
padding: "1rem",
}}
>
<p style={{ color: "#64748b", fontSize: "0.875rem" }}>
連線異常,正在導向登入頁…
<p style={{ color: "#334155", fontSize: "0.95rem", fontWeight: 700 }}>
連線異常,伺服器暫停中
</p>
<p style={{ color: "#64748b", fontSize: "0.875rem", marginTop: 8 }}>
系統會在後台恢復後自動重試。倒數中:{formatSeconds(remaining)}
</p>
</div>
</body>


読み込み中…
キャンセル
保存