149 lines
4.6 KiB
TypeScript
149 lines
4.6 KiB
TypeScript
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
|
|
import { useRouter } from 'next/router'
|
|
import { authApi, api } from '../services/chatApiService'
|
|
import { socketService } from '../services/socketService'
|
|
|
|
interface User {
|
|
id: string
|
|
email: string
|
|
name?: string
|
|
role: 'user' | 'admin'
|
|
daysRemaining?: number | null
|
|
planId?: string | null
|
|
}
|
|
|
|
interface AuthContextType {
|
|
user: User | null
|
|
token: string | null
|
|
loading: boolean
|
|
login: (token: string, userData: User, refreshToken?: string) => void
|
|
logout: () => void
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType>({
|
|
user: null,
|
|
token: null,
|
|
loading: true,
|
|
login: () => {},
|
|
logout: () => {},
|
|
})
|
|
|
|
const PUBLIC_ROUTES = ['/login']
|
|
|
|
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
|
const [user, setUser] = useState<User | null>(null)
|
|
const [token, setToken] = useState<string | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const router = useRouter()
|
|
|
|
// ── Valida sessão ao montar ──────────────────────────────────────────────
|
|
|
|
useEffect(() => {
|
|
const init = async () => {
|
|
try {
|
|
const storedToken = localStorage.getItem('token')
|
|
if (!storedToken) return
|
|
|
|
if (typeof window !== 'undefined') {
|
|
document.cookie = `accessToken=${storedToken}; path=/; SameSite=Lax; Secure`
|
|
}
|
|
|
|
setToken(storedToken)
|
|
socketService.connect(storedToken)
|
|
const me = await authApi.me()
|
|
|
|
if (me?.id) {
|
|
setUser({
|
|
id: me.id,
|
|
email: me.email,
|
|
name: me.name,
|
|
role: (me.role ?? 'USER').toLowerCase() as 'user' | 'admin',
|
|
daysRemaining: me.daysRemaining ?? null,
|
|
planId: me.planId ?? null,
|
|
})
|
|
} else {
|
|
await tryRefresh()
|
|
}
|
|
} catch {
|
|
await tryRefresh()
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
init()
|
|
}, [])
|
|
|
|
// ── Redirect guard ───────────────────────────────────────────────────────
|
|
|
|
useEffect(() => {
|
|
if (loading) return
|
|
const isPublic = PUBLIC_ROUTES.some((r) => router.pathname.startsWith(r))
|
|
if (!user && !isPublic) router.replace('/login')
|
|
}, [loading, user, router.pathname])
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
|
|
const tryRefresh = async () => {
|
|
try {
|
|
const refreshToken = localStorage.getItem('refreshToken')
|
|
if (!refreshToken) { clearSession(); return }
|
|
|
|
const { data } = await api.post('/api/auth/refresh', { refreshToken })
|
|
localStorage.setItem('token', data.accessToken)
|
|
if (typeof window !== 'undefined') {
|
|
document.cookie = `accessToken=${data.accessToken}; path=/; SameSite=Lax; Secure`
|
|
}
|
|
localStorage.setItem('refreshToken', data.refreshToken)
|
|
setToken(data.accessToken)
|
|
setUser({
|
|
id: data.user.id,
|
|
email: data.user.email,
|
|
name: data.user.name,
|
|
role: (data.user.role ?? 'USER').toLowerCase() as 'user' | 'admin',
|
|
})
|
|
} catch {
|
|
clearSession()
|
|
}
|
|
}
|
|
|
|
const clearSession = () => {
|
|
localStorage.removeItem('token')
|
|
localStorage.removeItem('refreshToken')
|
|
if (typeof window !== 'undefined') {
|
|
document.cookie = 'accessToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'
|
|
}
|
|
setToken(null)
|
|
setUser(null)
|
|
}
|
|
|
|
const login = useCallback((newToken: string, userData: User, refreshToken?: string) => {
|
|
localStorage.setItem('token', newToken)
|
|
if (typeof window !== 'undefined') {
|
|
document.cookie = `accessToken=${newToken}; path=/; SameSite=Lax; Secure`
|
|
}
|
|
if (refreshToken) localStorage.setItem('refreshToken', refreshToken)
|
|
setToken(newToken)
|
|
setUser(userData)
|
|
socketService.connect(newToken)
|
|
router.push(userData.role === 'admin' ? '/admin/dashboard' : '/inbox')
|
|
}, [router])
|
|
|
|
const logout = useCallback(async () => {
|
|
try {
|
|
await authApi.logout().catch(() => {})
|
|
} finally {
|
|
socketService.disconnect()
|
|
clearSession()
|
|
router.replace('/login')
|
|
}
|
|
}, [router])
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, token, loading, login, logout }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
)
|
|
}
|
|
|
|
export const useAuth = () => useContext(AuthContext)
|