Add session validation UI and expose isValidating in auth context

- LoginPage now shows a loading spinner while validating the session
- Redirect to dashboard automatically once validation completes and session is valid
- Expose isValidating state through AuthProvider for components to track validation status
- Update useAuth hook to return isValidating along with isAuthenticated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-30 21:02:49 +02:00
parent e24b1241fb
commit fc5f44ebe4
2 changed files with 33 additions and 4 deletions

View File

@@ -6,7 +6,7 @@
* parameter) or the dashboard. * parameter) or the dashboard.
*/ */
import { useState } from "react"; import { useState, useEffect } from "react";
import { import {
Button, Button,
Field, Field,
@@ -75,7 +75,7 @@ export function LoginPage(): React.JSX.Element {
const styles = useStyles(); const styles = useStyles();
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const { login } = useAuth(); const { login, isAuthenticated, isValidating } = useAuth();
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -84,6 +84,33 @@ export function LoginPage(): React.JSX.Element {
const next = searchParams.get("next") ?? ""; const next = searchParams.get("next") ?? "";
const safePath = /^\/(?!\/)/.test(next) ? next : "/"; const safePath = /^\/(?!\/)/.test(next) ? next : "/";
// Redirect to dashboard if session is valid and validation is complete
useEffect(() => {
if (isAuthenticated && !isValidating) {
navigate(safePath, { replace: true });
}
}, [isAuthenticated, isValidating, navigate, safePath]);
// Show loading state while validating session
if (isValidating) {
return (
<div className={styles.root}>
<div className={styles.card}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "200px",
}}
>
<Spinner size="large" label="Checking session…" />
</div>
</div>
</div>
);
}
function handlePasswordChange(ev: ChangeEvent<HTMLInputElement>): void { function handlePasswordChange(ev: ChangeEvent<HTMLInputElement>): void {
setPassword(ev.target.value); setPassword(ev.target.value);
setError(null); setError(null);

View File

@@ -62,6 +62,8 @@ import { useSessionValidation } from "../hooks/useSessionValidation";
export interface AuthContextValue { export interface AuthContextValue {
/** `true` when the backend considers the session valid. */ /** `true` when the backend considers the session valid. */
isAuthenticated: boolean; isAuthenticated: boolean;
/** `true` while the session is being validated on app mount. */
isValidating: boolean;
/** /**
* Authenticate with the master password. * Authenticate with the master password.
* Throws an `ApiError` on failure. * Throws an `ApiError` on failure.
@@ -216,8 +218,8 @@ export function AuthProvider({
}, []); }, []);
const value = useMemo<AuthContextValue>( const value = useMemo<AuthContextValue>(
() => ({ isAuthenticated, login, logout }), () => ({ isAuthenticated, isValidating, login, logout }),
[isAuthenticated, login, logout], [isAuthenticated, isValidating, login, logout],
); );
// Show loading spinner while validating session on mount. // Show loading spinner while validating session on mount.