main
ts 120 lines 2.96 KB
Raw
1 import { useEffect, useState } from "react";
2 import { BROWSER_API_PATHS } from "@/lib/apiPaths";
3 import { APIClientError, apiClient } from "@/lib/apiClient";
4 import { readAdminAuthToken, writeAdminAuthToken } from "@/lib/adminAuthToken";
5 import type {
6 AdminAuthLoginRequest,
7 AdminAuthLoginResponse,
8 AdminAuthStatusResponse,
9 } from "@/types/api";
10
11 interface AuthState {
12 isAuthenticated: boolean;
13 isLoading: boolean;
14 }
15
16 interface LoginResult {
17 success: boolean;
18 error?: string;
19 }
20
21 function emptyAuthState(): AuthState {
22 return {
23 isAuthenticated: false,
24 isLoading: false,
25 };
26 }
27
28 function authErrorMessage(err: unknown): string {
29 if (err instanceof APIClientError) {
30 return err.message || "Admin login failed.";
31 }
32 return err instanceof Error ? err.message : "Admin login failed.";
33 }
34
35 async function fetchAuthState(): Promise<AuthState> {
36 if (!readAdminAuthToken()) {
37 return emptyAuthState();
38 }
39 try {
40 const data = await apiClient.get<AdminAuthStatusResponse>(
41 BROWSER_API_PATHS.admin.authStatus
42 );
43 if (!data.authenticated) {
44 writeAdminAuthToken("");
45 return emptyAuthState();
46 }
47 return {
48 isAuthenticated: data.authenticated,
49 isLoading: false,
50 };
51 } catch {
52 writeAdminAuthToken("");
53 return emptyAuthState();
54 }
55 }
56
57 export function useAuth() {
58 const [authState, setAuthState] = useState<AuthState>({
59 isAuthenticated: false,
60 isLoading: true,
61 });
62
63 const checkAuth = async () => {
64 setAuthState(await fetchAuthState());
65 };
66
67 useEffect(() => {
68 void (async () => {
69 setAuthState(await fetchAuthState());
70 })();
71 }, []);
72
73 const login = async (token: string): Promise<LoginResult> => {
74 const trimmed = token.trim();
75 if (!trimmed) {
76 return { success: false, error: "Admin token is required." };
77 }
78 try {
79 const body: AdminAuthLoginRequest = { token: trimmed };
80 const data = await apiClient.post<AdminAuthLoginResponse>(
81 BROWSER_API_PATHS.admin.authLogin,
82 body
83 );
84 const accessToken = data.access_token?.trim() || "";
85 if (!accessToken) {
86 writeAdminAuthToken("");
87 return { success: false, error: "Admin login did not return an access token." };
88 }
89 writeAdminAuthToken(accessToken);
90 setAuthState({
91 isAuthenticated: true,
92 isLoading: false,
93 });
94 return { success: true };
95 } catch (err: unknown) {
96 writeAdminAuthToken("");
97 setAuthState(emptyAuthState());
98 return { success: false, error: authErrorMessage(err) };
99 }
100 };
101
102 const logout = async () => {
103 try {
104 await apiClient.post<unknown>(BROWSER_API_PATHS.admin.logout);
105 } catch {
106 // Logging out should clear local state even if the remote token is stale.
107 } finally {
108 writeAdminAuthToken("");
109 }
110 setAuthState(emptyAuthState());
111 };
112
113 return {
114 isAuthenticated: authState.isAuthenticated,
115 isLoading: authState.isLoading,
116 login,
117 logout,
118 checkAuth,
119 };
120 }