124 lines
3.7 KiB
TypeScript
124 lines
3.7 KiB
TypeScript
/**
|
|
* authStore.ts — Estado de autenticação global via Zustand.
|
|
* O AuthContext (Next.js) permanece como provider de redirect guard.
|
|
* Este store é usado pelos demais stores (chatStore, etc.) sem hook drilling.
|
|
*/
|
|
import { create } from 'zustand'
|
|
import { persist } from 'zustand/middleware'
|
|
import { authApi } from '../services/chatApiService'
|
|
import { socketService } from '../services/socketService'
|
|
|
|
export interface AuthUser {
|
|
id: string
|
|
email: string
|
|
name: string | null
|
|
role: 'USER' | 'ADMIN'
|
|
planId: string | null
|
|
daysRemaining: number | null
|
|
}
|
|
|
|
interface AuthState {
|
|
user: AuthUser | null
|
|
token: string | null
|
|
refreshToken: string | null
|
|
isHydrated: boolean
|
|
|
|
// Actions
|
|
setAuth: (token: string, refreshToken: string, user: AuthUser) => void
|
|
clearAuth: () => void
|
|
refreshSession: () => Promise<boolean>
|
|
fetchMe: () => Promise<void>
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>()(
|
|
persist(
|
|
(set, get) => ({
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
isHydrated: false,
|
|
|
|
setAuth: (token, refreshToken, user) => {
|
|
localStorage.setItem('token', token)
|
|
localStorage.setItem('refreshToken', refreshToken)
|
|
if (typeof window !== 'undefined') {
|
|
document.cookie = `accessToken=${token}; path=/; SameSite=Lax; Secure`
|
|
}
|
|
socketService.connect(token)
|
|
set({ token, refreshToken, user })
|
|
},
|
|
|
|
clearAuth: () => {
|
|
localStorage.removeItem('token')
|
|
localStorage.removeItem('refreshToken')
|
|
if (typeof window !== 'undefined') {
|
|
document.cookie = 'accessToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'
|
|
}
|
|
socketService.disconnect()
|
|
set({ token: null, refreshToken: null, user: null })
|
|
},
|
|
|
|
refreshSession: async () => {
|
|
const { refreshToken } = get()
|
|
if (!refreshToken) return false
|
|
try {
|
|
const { data } = await import('axios').then(({ default: axios }) =>
|
|
axios.post(
|
|
`${process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8008'}/api/auth/refresh`,
|
|
{ refreshToken }
|
|
)
|
|
)
|
|
get().setAuth(data.accessToken, data.refreshToken, {
|
|
id: data.user.id,
|
|
email: data.user.email,
|
|
name: data.user.name ?? null,
|
|
role: data.user.role as 'USER' | 'ADMIN',
|
|
planId: data.user.planId ?? null,
|
|
daysRemaining: data.user.daysRemaining ?? null,
|
|
})
|
|
return true
|
|
} catch {
|
|
get().clearAuth()
|
|
return false
|
|
}
|
|
},
|
|
|
|
fetchMe: async () => {
|
|
try {
|
|
const me = await authApi.me()
|
|
if (me?.id) {
|
|
set({
|
|
user: {
|
|
id: me.id,
|
|
email: me.email,
|
|
name: me.name ?? null,
|
|
role: (me.role ?? 'USER') as 'USER' | 'ADMIN',
|
|
planId: me.planId ?? null,
|
|
daysRemaining: me.daysRemaining ?? null,
|
|
},
|
|
})
|
|
}
|
|
} catch {
|
|
// token inválido — clearAuth será chamado pelo interceptor 401
|
|
}
|
|
},
|
|
}),
|
|
{
|
|
name: 'newwhats:auth',
|
|
partialize: (s) => ({ token: s.token, refreshToken: s.refreshToken, user: s.user }),
|
|
onRehydrateStorage: () => (state) => {
|
|
if (state) {
|
|
state.isHydrated = true
|
|
// Reconecta socket ao restaurar sessão do localStorage
|
|
if (state.token) {
|
|
socketService.connect(state.token)
|
|
if (typeof window !== 'undefined') {
|
|
document.cookie = `accessToken=${state.token}; path=/; SameSite=Lax; Secure`
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
)
|
|
)
|