diff options
Diffstat (limited to 'frontend/src/store/authStore.ts')
| -rw-r--r-- | frontend/src/store/authStore.ts | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/frontend/src/store/authStore.ts b/frontend/src/store/authStore.ts new file mode 100644 index 0000000..652256c --- /dev/null +++ b/frontend/src/store/authStore.ts @@ -0,0 +1,157 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +const API_BASE = 'http://localhost:8000'; + +interface UserInfo { + id: number; + username: string; + email: string; + created_at: string; +} + +interface AuthState { + token: string | null; + user: UserInfo | null; + isAuthenticated: boolean; + isLoading: boolean; + error: string | null; + + // Actions + login: (username: string, password: string) => Promise<void>; + register: (username: string, email: string, password: string) => Promise<void>; + logout: () => void; + checkAuth: () => Promise<boolean>; + clearError: () => void; + getAuthHeader: () => { Authorization: string } | {}; +} + +export const useAuthStore = create<AuthState>()( + persist( + (set, get) => ({ + token: null, + user: null, + isAuthenticated: false, + isLoading: false, + error: null, + + login: async (username: string, password: string) => { + set({ isLoading: true, error: null }); + + try { + // Use JSON login endpoint + const res = await fetch(`${API_BASE}/api/auth/login/json`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + throw new Error(errorData.detail || 'Login failed'); + } + + const data = await res.json(); + set({ + token: data.access_token, + isAuthenticated: true, + isLoading: false + }); + + // Fetch user info + await get().checkAuth(); + } catch (err) { + set({ + isLoading: false, + error: (err as Error).message, + isAuthenticated: false, + token: null, + user: null + }); + throw err; + } + }, + + register: async (username: string, email: string, password: string) => { + set({ isLoading: true, error: null }); + + try { + const res = await fetch(`${API_BASE}/api/auth/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, email, password }), + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + throw new Error(errorData.detail || 'Registration failed'); + } + + set({ isLoading: false }); + } catch (err) { + set({ isLoading: false, error: (err as Error).message }); + throw err; + } + }, + + logout: () => { + set({ + token: null, + user: null, + isAuthenticated: false, + error: null + }); + }, + + checkAuth: async () => { + const token = get().token; + if (!token) { + set({ isAuthenticated: false, user: null }); + return false; + } + + try { + const res = await fetch(`${API_BASE}/api/auth/me`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (res.ok) { + const user = await res.json(); + set({ user, isAuthenticated: true }); + return true; + } else { + // Token is invalid + set({ token: null, user: null, isAuthenticated: false }); + return false; + } + } catch { + set({ token: null, user: null, isAuthenticated: false }); + return false; + } + }, + + clearError: () => { + set({ error: null }); + }, + + getAuthHeader: () => { + const token = get().token; + if (token) { + return { Authorization: `Bearer ${token}` }; + } + return {}; + }, + }), + { + name: 'contextflow-auth', + partialize: (state) => ({ + token: state.token, + user: state.user, + isAuthenticated: state.isAuthenticated + }), + } + ) +); + +export default useAuthStore; + |
