123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
'use client'
|
|
|
|
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { api, registerUnauthorizedHandler } from '@/lib/api'
|
|
|
|
export interface User {
|
|
userId: string
|
|
username: string
|
|
roleId: string
|
|
roleCode: string
|
|
roleLevel: number
|
|
}
|
|
|
|
interface AuthContextValue {
|
|
user: User | null
|
|
permissions: Set<string>
|
|
loading: boolean
|
|
login: (identifier: string, password: string) => Promise<void>
|
|
logout: () => Promise<void>
|
|
hasPermission: (code: string) => boolean
|
|
}
|
|
|
|
const PERMISSIONS_KEY = 'app:permissions'
|
|
const USER_STORAGE_KEYS = [PERMISSIONS_KEY, 'distribution-dashboard-widgets-v3']
|
|
|
|
function savePermissions(perms: string[]): void {
|
|
try {
|
|
localStorage.setItem(PERMISSIONS_KEY, JSON.stringify(perms))
|
|
} catch {}
|
|
}
|
|
|
|
function readPermissions(): Set<string> {
|
|
try {
|
|
const raw = localStorage.getItem(PERMISSIONS_KEY)
|
|
if (raw) return new Set(JSON.parse(raw) as string[])
|
|
} catch {}
|
|
return new Set()
|
|
}
|
|
|
|
function clearUserStorage(): void {
|
|
try {
|
|
USER_STORAGE_KEYS.forEach(k => localStorage.removeItem(k))
|
|
} catch {}
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextValue | null>(null)
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const router = useRouter()
|
|
const [user, setUser] = useState<User | null>(null)
|
|
const [permissions, setPermissions] = useState<Set<string>>(new Set())
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
const loadSession = useCallback(async () => {
|
|
setLoading(true)
|
|
try {
|
|
const { data } = await api.get<User>('/auth/me')
|
|
setUser(data)
|
|
setPermissions(readPermissions())
|
|
} catch {
|
|
setUser(null)
|
|
setPermissions(new Set())
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
loadSession()
|
|
}, [loadSession])
|
|
|
|
useEffect(() => {
|
|
return registerUnauthorizedHandler(() => {
|
|
clearUserStorage()
|
|
setUser(null)
|
|
setPermissions(new Set())
|
|
router.push('/login')
|
|
})
|
|
}, [router])
|
|
|
|
async function login(identifier: string, password: string) {
|
|
const { data: res } = await api.post<{ statusCode: number; success: boolean; message: string; data: unknown | null; profile: User | null }>('/auth/login', { identifier, password, deviceName: 'Web' })
|
|
if (!res.profile) throw new Error(res.message ?? 'Invalid credentials')
|
|
setUser(res.profile)
|
|
try {
|
|
const { data: permData } = await api.get<{ permissions: string[] }>('/authorization/my-permissions')
|
|
savePermissions(permData.permissions)
|
|
setPermissions(new Set(permData.permissions))
|
|
} catch {
|
|
// permissions unavailable — user is still authenticated
|
|
}
|
|
router.push('/')
|
|
}
|
|
|
|
async function logout() {
|
|
try {
|
|
await api.post('/auth/logout')
|
|
} finally {
|
|
clearUserStorage()
|
|
setUser(null)
|
|
setPermissions(new Set())
|
|
router.push('/login')
|
|
}
|
|
}
|
|
|
|
function hasPermission(code: string): boolean {
|
|
return permissions.has(code)
|
|
}
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, permissions, loading, login, logout, hasPermission }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useAuth(): AuthContextValue {
|
|
const ctx = useContext(AuthContext)
|
|
if (!ctx) throw new Error('useAuth must be used inside AuthProvider')
|
|
return ctx
|
|
}
|