| import { useEffect } from 'react' |
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' |
| import { api } from '../utils/api' |
| import { useAppStore } from '../store/useAppStore' |
|
|
| export function useAuth() { |
| const queryClient = useQueryClient() |
| const token = useAppStore((state) => state.token) |
| const setToken = useAppStore((state) => state.setToken) |
| const openAuth = useAppStore((state) => state.openAuth) |
| const closeAuth = useAppStore((state) => state.closeAuth) |
| const addToast = useAppStore((state) => state.addToast) |
| const authMode = useAppStore((state) => state.authMode) |
| const activeModal = useAppStore((state) => state.activeModal) |
| const startNewChat = useAppStore((state) => state.startNewChat) |
|
|
| useEffect(() => { |
| if (typeof window === 'undefined') return |
| const params = new URLSearchParams(window.location.search) |
| const accessToken = params.get('access_token') |
| if (accessToken) { |
| setToken(accessToken) |
| window.history.replaceState({}, '', window.location.pathname) |
| } |
| }, [setToken]) |
|
|
| const meQuery = useQuery({ |
| queryKey: ['auth', 'me', token], |
| queryFn: () => api.get('/auth/me', { token }), |
| enabled: Boolean(token), |
| retry: false, |
| staleTime: 60_000, |
| }) |
|
|
| useEffect(() => { |
| if (!token || !meQuery.isError) return |
| setToken(null) |
| }, [meQuery.isError, setToken, token]) |
|
|
| const loginMutation = useMutation({ |
| mutationFn: ({ username, password }) => api.post('/auth/login', { username, password }), |
| onSuccess: (data) => { |
| setToken(data.access_token) |
| closeAuth() |
| addToast({ |
| title: 'Welcome back', |
| description: 'You are signed in and ready to chat.', |
| variant: 'success', |
| }) |
| queryClient.invalidateQueries({ queryKey: ['auth', 'me'] }) |
| }, |
| onError: (error) => { |
| addToast({ |
| title: 'Sign in failed', |
| description: error.message, |
| variant: 'danger', |
| }) |
| }, |
| }) |
|
|
| const registerMutation = useMutation({ |
| mutationFn: async ({ username, name, password }) => { |
| await api.post('/auth/register', { username, name, password }) |
| return api.post('/auth/login', { username, password }) |
| }, |
| onSuccess: (data) => { |
| setToken(data.access_token) |
| closeAuth() |
| addToast({ |
| title: 'Account created', |
| description: 'Your workspace is ready.', |
| variant: 'success', |
| }) |
| queryClient.invalidateQueries({ queryKey: ['auth', 'me'] }) |
| }, |
| onError: (error) => { |
| addToast({ |
| title: 'Registration failed', |
| description: error.message, |
| variant: 'danger', |
| }) |
| }, |
| }) |
|
|
| const logout = () => { |
| setToken(null) |
| startNewChat() |
| queryClient.removeQueries({ queryKey: ['auth'] }) |
| queryClient.removeQueries({ queryKey: ['history'] }) |
| queryClient.removeQueries({ queryKey: ['sessions'] }) |
| addToast({ |
| title: 'Signed out', |
| description: 'Your local session has been cleared.', |
| variant: 'info', |
| }) |
| } |
|
|
| const loginWithGoogle = () => { |
| window.location.href = '/auth/google/login' |
| } |
|
|
| const requireAuth = (callback) => { |
| if (!meQuery.data) { |
| openAuth('login') |
| return false |
| } |
| callback?.() |
| return true |
| } |
|
|
| return { |
| token, |
| user: meQuery.data || null, |
| isAuthLoading: Boolean(token) && meQuery.isLoading, |
| isAuthenticated: Boolean(meQuery.data), |
| authMode, |
| isAuthDialogOpen: activeModal === 'auth', |
| openAuth, |
| closeAuth, |
| login: loginMutation.mutateAsync, |
| register: registerMutation.mutateAsync, |
| loginPending: loginMutation.isPending, |
| registerPending: registerMutation.isPending, |
| logout, |
| loginWithGoogle, |
| requireAuth, |
| } |
| } |
|
|