fix(console): fix stuck authentication check and add reliable redirect to /console/login

- Add immediate redirection to /console/login when unauthenticated in AdminLayout
- Add timeout (4000ms) with AbortController in adminApi.ts to prevent infinite pending fetch
- Provide explicit sign-in link in layout fallback view
This commit is contained in:
2026-08-25 14:42:26 +09:00
parent 9bdc9bdd9a
commit 54fe425b3c
2 changed files with 55 additions and 26 deletions
+31 -11
View File
@@ -13,14 +13,17 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
useEffect(() => { useEffect(() => {
let isMounted = true; let isMounted = true;
async function checkAuth() { async function checkAuth() {
if (pathname === "/console/login") {
if (isMounted) setIsAuthenticated(false);
return;
}
const token = getClientToken(); const token = getClientToken();
if (!token) { if (!token) {
if (pathname !== "/console/login") { if (isMounted) setIsAuthenticated(false);
router.push("/console/login"); router.replace("/console/login");
} else {
setIsAuthenticated(false);
}
return; return;
} }
@@ -29,11 +32,8 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
if (!isValid) { if (!isValid) {
removeClientToken(); removeClientToken();
if (pathname !== "/console/login") { setIsAuthenticated(false);
router.push("/console/login"); router.replace("/console/login");
} else {
setIsAuthenticated(false);
}
} else { } else {
setIsAuthenticated(true); setIsAuthenticated(true);
} }
@@ -51,8 +51,28 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
if (isAuthenticated === null) { if (isAuthenticated === null) {
return ( return (
<div className="min-h-screen bg-ivory text-ink flex items-center justify-center"> <div className="min-h-screen bg-ivory text-ink flex flex-col items-center justify-center space-y-4">
<div className="text-sm text-ink/70">Checking authentication...</div> <div className="text-sm text-ink/70">Checking authentication...</div>
<Link
href="/console/login"
className="text-xs text-cobalt underline hover:text-cobalt/80 font-mono"
>
Click here to sign in
</Link>
</div>
);
}
if (isAuthenticated === false) {
return (
<div className="min-h-screen bg-ivory text-ink flex flex-col items-center justify-center space-y-4">
<div className="text-sm text-ink/70">Redirecting to login...</div>
<Link
href="/console/login"
className="px-4 py-2 bg-cobalt text-white rounded text-sm font-medium hover:bg-cobalt/90"
>
Go to Sign In Page
</Link>
</div> </div>
); );
} }
+24 -15
View File
@@ -153,23 +153,32 @@ async function request<T>(
const baseUrl = getAdminApiBaseUrl(); const baseUrl = getAdminApiBaseUrl();
const url = `${baseUrl}${endpoint}`; const url = `${baseUrl}${endpoint}`;
const response = await fetch(url, {
...options, const controller = new AbortController();
headers, const timeoutId = setTimeout(() => controller.abort(), 4000);
cache: "no-store",
});
if (response.status === 204) { try {
return {} as T; const response = await fetch(url, {
...options,
headers,
signal: controller.signal,
cache: "no-store",
});
if (response.status === 204) {
return {} as T;
}
const json = await response.json().catch(() => ({}));
if (!response.ok) {
const errorMsg = json?.error?.message || `Request failed with status ${response.status}`;
throw new Error(errorMsg);
}
return json.data;
} finally {
clearTimeout(timeoutId);
} }
const json = await response.json().catch(() => ({}));
if (!response.ok) {
const errorMsg = json?.error?.message || `Request failed with status ${response.status}`;
throw new Error(errorMsg);
}
return json.data;
} }
// 0. Auth Verification // 0. Auth Verification