web: separate search route

This commit is contained in:
2026-02-14 00:22:19 -08:00
parent 6f5787f923
commit acca7be7f0
9 changed files with 529 additions and 282 deletions

View File

@@ -0,0 +1,103 @@
import { useEffect, useState } from "preact/hooks";
import { getConfiguredToken, setAuthToken, verifySession } from "@/lib/api";
const TOKEN_STORAGE_KEY = "sybil_admin_token";
export type AuthMode = "open" | "token";
function readStoredToken() {
return localStorage.getItem(TOKEN_STORAGE_KEY)?.trim() || null;
}
function persistToken(token: string | null) {
if (token) {
localStorage.setItem(TOKEN_STORAGE_KEY, token);
return;
}
localStorage.removeItem(TOKEN_STORAGE_KEY);
}
function normalizeAuthError(message: string) {
if (message.includes("missing bearer token") || message.includes("invalid bearer token")) {
return "Authentication failed. Enter the ADMIN_TOKEN configured in server/.env.";
}
return message;
}
export function useSessionAuth() {
const initialToken = readStoredToken() ?? getConfiguredToken() ?? "";
const [authTokenInput, setAuthTokenInput] = useState(initialToken);
const [isCheckingSession, setIsCheckingSession] = useState(true);
const [isSigningIn, setIsSigningIn] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [authMode, setAuthMode] = useState<AuthMode | null>(null);
const [authError, setAuthError] = useState<string | null>(null);
const completeSessionCheck = async (tokenCandidate: string | null) => {
setAuthToken(tokenCandidate);
const session = await verifySession();
setIsAuthenticated(true);
setAuthMode(session.mode);
setAuthError(null);
persistToken(tokenCandidate);
};
const handleAuthFailure = (message: string) => {
setIsAuthenticated(false);
setAuthMode(null);
setAuthError(normalizeAuthError(message));
setAuthToken(null);
persistToken(null);
};
const handleSignIn = async (tokenCandidate: string | null) => {
setIsSigningIn(true);
setAuthError(null);
try {
await completeSessionCheck(tokenCandidate);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
setAuthError(normalizeAuthError(message));
setIsAuthenticated(false);
setAuthMode(null);
} finally {
setIsSigningIn(false);
}
};
const logout = () => {
setAuthToken(null);
persistToken(null);
setIsAuthenticated(false);
setAuthMode(null);
setAuthError(null);
};
useEffect(() => {
const token = readStoredToken() ?? getConfiguredToken();
void (async () => {
try {
await completeSessionCheck(token);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
handleAuthFailure(message);
} finally {
setIsCheckingSession(false);
}
})();
}, []);
return {
authTokenInput,
setAuthTokenInput,
isCheckingSession,
isSigningIn,
isAuthenticated,
authMode,
authError,
handleAuthFailure,
handleSignIn,
logout,
};
}