132 lines
4.9 KiB
TypeScript
132 lines
4.9 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { onAuthStateChanged } from 'firebase/auth';
|
|
import { firebaseAuth } from '../../lib/firebase-client';
|
|
|
|
interface OperationsErrorStateProps {
|
|
status: 401 | 403 | 404 | 503 | 'disabled' | 'network' | number;
|
|
message?: string;
|
|
onRetry?: () => void;
|
|
}
|
|
|
|
export function OperationsErrorState({
|
|
status,
|
|
message,
|
|
onRetry,
|
|
}: OperationsErrorStateProps) {
|
|
const [authenticating, setAuthenticating] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (status !== 401) return;
|
|
const unsubscribe = onAuthStateChanged(firebaseAuth, async (user) => {
|
|
if (user) {
|
|
setAuthenticating(true);
|
|
try {
|
|
const idToken = await user.getIdToken();
|
|
const res = await fetch('/api/auth/firebase-login', {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ id_token: idToken }),
|
|
});
|
|
if (res.ok) {
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
} catch {
|
|
// Ignore and remain on error state
|
|
} finally {
|
|
setAuthenticating(false);
|
|
}
|
|
}
|
|
});
|
|
return () => unsubscribe();
|
|
}, [status]);
|
|
|
|
let title = 'Operations Error';
|
|
let description = authenticating
|
|
? 'Verifying operator credentials and activating session...'
|
|
: (message ?? 'An unexpected error occurred in the Operations console.');
|
|
let icon = authenticating ? 'sync' : 'error';
|
|
let showSignIn = false;
|
|
let showRetry = true;
|
|
|
|
if (status === 401) {
|
|
title = authenticating ? 'Authenticating Operator...' : 'Authentication Required';
|
|
description = authenticating
|
|
? 'Verifying operator credentials and establishing secure session...'
|
|
: (message ?? 'Your Operations session has expired or is unauthenticated.');
|
|
icon = authenticating ? 'sync' : 'lock';
|
|
showSignIn = !authenticating;
|
|
showRetry = false;
|
|
} else if (status === 403) {
|
|
title = 'Access Restricted';
|
|
description = message ?? 'You do not have sufficient authority to access Operations.';
|
|
icon = 'gavel';
|
|
showSignIn = false;
|
|
showRetry = false;
|
|
} else if (status === 404) {
|
|
title = 'Resource Not Found';
|
|
description = message ?? 'The requested Operations resource does not exist.';
|
|
icon = 'search_off';
|
|
} else if (status === 503 || status === 'network') {
|
|
title = 'Operations Temporarily Unavailable';
|
|
description = message ?? 'Unable to communicate with the Operations API. Try again in a few moments.';
|
|
icon = 'cloud_off';
|
|
} else if (status === 'disabled') {
|
|
title = 'Operations Disabled';
|
|
description = message ?? 'The Operations platform is currently disabled by system policy.';
|
|
icon = 'block';
|
|
showRetry = false;
|
|
}
|
|
|
|
return (
|
|
<div className="p-8 rounded-xl bg-surface-container-low border border-outline-variant/30 text-center max-w-lg mx-auto my-12 space-y-4 shadow-sm">
|
|
<div className="w-12 h-12 rounded-full bg-surface-container-high border border-outline-variant/40 flex items-center justify-center text-outline mx-auto">
|
|
<span className="material-symbols-outlined text-[24px]">{icon}</span>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<span className="text-[10px] font-mono text-outline font-semibold">
|
|
Status {status}
|
|
</span>
|
|
<h2 className="text-lg font-bold text-on-surface">{title}</h2>
|
|
<p className="text-xs text-on-surface-variant leading-relaxed">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="pt-4 border-t border-outline-variant/20 flex items-center justify-center gap-3">
|
|
{showSignIn && (
|
|
<Link
|
|
href="/login?returnTo=/admin"
|
|
className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg bg-primary-container text-on-primary-container text-xs font-bold shadow-sm hover:brightness-105 transition-all focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none"
|
|
>
|
|
<span className="material-symbols-outlined text-[16px]">login</span>
|
|
<span>Sign In</span>
|
|
</Link>
|
|
)}
|
|
|
|
{showRetry && onRetry && (
|
|
<button
|
|
type="button"
|
|
onClick={onRetry}
|
|
className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg bg-surface-container-high hover:bg-surface-bright text-on-surface border border-outline-variant/30 text-xs font-semibold transition-all focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none"
|
|
>
|
|
<span className="material-symbols-outlined text-[16px]">refresh</span>
|
|
<span>Retry</span>
|
|
</button>
|
|
)}
|
|
|
|
<Link
|
|
href="/"
|
|
className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-medium text-on-surface-variant hover:text-on-surface transition-colors"
|
|
>
|
|
Return Home
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|