- Dockerfile: replace nginx with serve for static files - Fix auth endpoint: /auth/login → /auth/token - Add ProjectsPage, ProjectDetailPage - Add MilestonesPage, MilestoneDetailPage with progress bar - Add NotificationsPage with unread count - Sidebar: add milestones/notifications nav, live unread badge - API: configurable VITE_API_BASE for host nginx proxy - Types: add Milestone, MilestoneProgress, Notification, ProjectMember
54 lines
1.4 KiB
TypeScript
54 lines
1.4 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react'
|
|
import api from '@/services/api'
|
|
import type { User } from '@/types'
|
|
|
|
interface AuthState {
|
|
user: User | null
|
|
token: string | null
|
|
loading: boolean
|
|
}
|
|
|
|
export function useAuth() {
|
|
const [state, setState] = useState<AuthState>({
|
|
user: null,
|
|
token: localStorage.getItem('token'),
|
|
loading: true,
|
|
})
|
|
|
|
const fetchUser = useCallback(async () => {
|
|
const token = localStorage.getItem('token')
|
|
if (!token) {
|
|
setState({ user: null, token: null, loading: false })
|
|
return
|
|
}
|
|
try {
|
|
const { data } = await api.get<User>('/auth/me')
|
|
setState({ user: data, token, loading: false })
|
|
} catch {
|
|
localStorage.removeItem('token')
|
|
setState({ user: null, token: null, loading: false })
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => { fetchUser() }, [fetchUser])
|
|
|
|
const login = async (username: string, password: string) => {
|
|
const form = new URLSearchParams()
|
|
form.append('username', username)
|
|
form.append('password', password)
|
|
const { data } = await api.post('/auth/token', form, {
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
})
|
|
localStorage.setItem('token', data.access_token)
|
|
setState((s) => ({ ...s, token: data.access_token }))
|
|
await fetchUser()
|
|
}
|
|
|
|
const logout = () => {
|
|
localStorage.removeItem('token')
|
|
setState({ user: null, token: null, loading: false })
|
|
}
|
|
|
|
return { ...state, login, logout }
|
|
}
|