fix(#411): QA-007 — explicit error state on login config fetch failure

Login page now shows error state with retry button when /auth/config
fetch fails, instead of silently falling back to email-only config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-16 13:44:01 -06:00
parent 08e32d42a3
commit e600cfd2d0
2 changed files with 75 additions and 16 deletions

View File

@@ -14,16 +14,12 @@ import { LoginForm } from "@/components/auth/LoginForm";
import { AuthDivider } from "@/components/auth/AuthDivider";
import { AuthErrorBanner } from "@/components/auth/AuthErrorBanner";
/** Fallback config when the backend is unreachable */
const EMAIL_ONLY_CONFIG: AuthConfigResponse = {
providers: [{ id: "email", name: "Email", type: "credentials" }],
};
export default function LoginPage(): ReactElement {
const router = useRouter();
const searchParams = useSearchParams();
const [config, setConfig] = useState<AuthConfigResponse | null>(null);
const [config, setConfig] = useState<AuthConfigResponse | null | undefined>(undefined);
const [loadingConfig, setLoadingConfig] = useState(true);
const [retryCount, setRetryCount] = useState(0);
const [oauthLoading, setOauthLoading] = useState<string | null>(null);
const [credentialsLoading, setCredentialsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -59,8 +55,10 @@ export default function LoginPage(): ReactElement {
} catch (err: unknown) {
if (!cancelled) {
console.error("[Auth] Failed to load auth config:", err);
setConfig(EMAIL_ONLY_CONFIG);
setUrlError("Some sign-in options may be temporarily unavailable.");
setConfig(null);
setUrlError(
"Unable to load sign-in options. Please refresh the page or try again in a moment."
);
}
} finally {
if (!cancelled) {
@@ -74,7 +72,7 @@ export default function LoginPage(): ReactElement {
return (): void => {
cancelled = true;
};
}, []);
}, [retryCount]);
const oauthProviders: AuthProviderConfig[] =
config?.providers.filter((p) => p.type === "oauth") ?? [];
@@ -123,6 +121,14 @@ export default function LoginPage(): ReactElement {
[router]
);
const handleRetry = useCallback((): void => {
setConfig(undefined);
setLoadingConfig(true);
setUrlError(null);
setError(null);
setRetryCount((c) => c + 1);
}, []);
return (
<main className="flex min-h-screen flex-col items-center justify-center p-4 sm:p-8 bg-gray-50">
<div className="w-full max-w-md space-y-8">
@@ -145,6 +151,19 @@ export default function LoginPage(): ReactElement {
<Loader2 className="h-8 w-8 animate-spin text-blue-500" aria-hidden="true" />
<span className="sr-only">Loading authentication options</span>
</div>
) : config === null ? (
<div className="space-y-4" data-testid="config-error-state">
<AuthErrorBanner message={urlError ?? "Unable to load sign-in options."} />
<div className="flex justify-center">
<button
type="button"
onClick={handleRetry}
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
Try again
</button>
</div>
</div>
) : (
<>
{urlError && (