CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
useAuth.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| c53cf00 | 1 | import { useState, useEffect, useCallback } from 'react'; |
| 2 | import { api, type User } from '../api/client'; | |
| 3 | import { | |
| 4 | saveToken, | |
| 5 | loadToken, | |
| 6 | clearAll, | |
| 7 | saveUser, | |
| 8 | loadUser, | |
| 9 | saveHost, | |
| 10 | loadHost, | |
| 11 | } from '../store/auth'; | |
| 12 | import { setBaseUrl } from '../api/client'; | |
| 13 | ||
| 14 | export interface AuthState { | |
| 15 | user: User | null; | |
| 16 | token: string | null; | |
| 17 | loading: boolean; | |
| 18 | error: string | null; | |
| 19 | } | |
| 20 | ||
| 21 | export function useAuth() { | |
| 22 | const [state, setState] = useState<AuthState>({ | |
| 23 | user: null, | |
| 24 | token: null, | |
| 25 | loading: true, | |
| 26 | error: null, | |
| 27 | }); | |
| 28 | ||
| 29 | // Hydrate from secure storage on mount | |
| 30 | useEffect(() => { | |
| 31 | (async () => { | |
| 32 | try { | |
| 33 | const [token, user, host] = await Promise.all([ | |
| 34 | loadToken(), | |
| 35 | loadUser(), | |
| 36 | loadHost(), | |
| 37 | ]); | |
| 38 | if (host) { | |
| 39 | setBaseUrl(host); | |
| 40 | } | |
| 41 | if (token && user) { | |
| 42 | setState({ user, token, loading: false, error: null }); | |
| 43 | } else { | |
| 44 | setState({ user: null, token: null, loading: false, error: null }); | |
| 45 | } | |
| 46 | } catch { | |
| 47 | setState({ user: null, token: null, loading: false, error: null }); | |
| 48 | } | |
| 49 | })(); | |
| 50 | }, []); | |
| 51 | ||
| 52 | const login = useCallback(async (usernameOrToken: string, passwordOrEmpty?: string, host?: string) => { | |
| 53 | setState((s) => ({ ...s, loading: true, error: null })); | |
| 54 | try { | |
| 55 | if (host) { | |
| 56 | setBaseUrl(host); | |
| 57 | await saveHost(host); | |
| 58 | } | |
| 59 | ||
| 60 | let token: string; | |
| 61 | let user: User; | |
| 62 | ||
| 63 | // PAT token login: if passwordOrEmpty is empty, treat usernameOrToken as raw token | |
| 64 | if (!passwordOrEmpty) { | |
| 65 | token = usernameOrToken; | |
| 66 | api.setToken(token); | |
| 67 | user = await api.getMe(); | |
| 68 | } else { | |
| 69 | // Username + password login | |
| 70 | const res = await api.login(usernameOrToken, passwordOrEmpty); | |
| 71 | token = res.token; | |
| 72 | api.setToken(token); | |
| 73 | user = await api.getMe(); | |
| 74 | } | |
| 75 | ||
| 76 | await Promise.all([saveToken(token), saveUser(user)]); | |
| 77 | setState({ user, token, loading: false, error: null }); | |
| 78 | } catch (err: unknown) { | |
| 79 | const msg = err instanceof Error ? err.message : 'Login failed'; | |
| 80 | setState((s) => ({ ...s, loading: false, error: msg })); | |
| 81 | throw err; | |
| 82 | } | |
| 83 | }, []); | |
| 84 | ||
| 85 | const logout = useCallback(async () => { | |
| 86 | await clearAll(); | |
| 87 | setState({ user: null, token: null, loading: false, error: null }); | |
| 88 | }, []); | |
| 89 | ||
| 90 | const clearError = useCallback(() => { | |
| 91 | setState((s) => ({ ...s, error: null })); | |
| 92 | }, []); | |
| 93 | ||
| 94 | return { | |
| 95 | ...state, | |
| 96 | isAuthenticated: !!state.token && !!state.user, | |
| 97 | login, | |
| 98 | logout, | |
| 99 | clearError, | |
| 100 | }; | |
| 101 | } |