feat: initial frontend - React + TypeScript + Vite

- Login page with JWT auth
- Dashboard with stats and charts
- Issues list with pagination, filtering
- Issue detail with comments, status transitions
- Create issue form
- Dark theme UI
- Docker (nginx) with API proxy to backend
- Sidebar navigation
This commit is contained in:
Zhi
2026-02-27 09:47:19 +00:00
parent 32557f1de2
commit 853594f447
20 changed files with 831 additions and 0 deletions

51
src/hooks/useAuth.ts Normal file
View File

@@ -0,0 +1,51 @@
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/login', form)
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 }
}