146 lines
4.4 KiB
TypeScript
146 lines
4.4 KiB
TypeScript
import { create } from 'zustand'
|
|
import { persist } from 'zustand/middleware'
|
|
import type { UserDto } from '@/types/models'
|
|
|
|
interface AuthState {
|
|
user: UserDto | null
|
|
token: string | null
|
|
refreshToken: string | null
|
|
expiresIn: number | null
|
|
tokenExpiresAt: number | null // timestamp when token expires
|
|
isAuthenticated: boolean
|
|
isLoading: boolean
|
|
error: string | null
|
|
}
|
|
|
|
interface AuthActions {
|
|
login: (token: string, refreshToken: string | null, expiresIn: number | null, user: UserDto) => void
|
|
updateToken: (token: string, refreshToken: string, expiresIn: number) => void
|
|
logout: () => void
|
|
setLoading: (loading: boolean) => void
|
|
setError: (error: string | null) => void
|
|
shouldRefreshToken: () => boolean
|
|
}
|
|
|
|
type AuthStore = AuthState & AuthActions
|
|
|
|
export const useAuthStore = create<AuthStore>()(
|
|
persist(
|
|
(set, get) => ({
|
|
// State
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
expiresIn: null,
|
|
tokenExpiresAt: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: null,
|
|
|
|
// Actions
|
|
login: (token: string, refreshToken: string | null, expiresIn: number | null, user: UserDto) => {
|
|
console.log('[AuthStore] Saving token to localStorage, length:', token.length)
|
|
localStorage.setItem('admin_token', token)
|
|
if (refreshToken) {
|
|
localStorage.setItem('admin_refresh_token', refreshToken)
|
|
}
|
|
|
|
// Calculate expiration time (refresh 5 minutes before expiration)
|
|
const tokenExpiresAt = expiresIn
|
|
? Date.now() + (expiresIn - 300) * 1000 // 5 minutes buffer
|
|
: null
|
|
|
|
// Verify it was saved
|
|
const savedToken = localStorage.getItem('admin_token')
|
|
console.log('[AuthStore] Token saved, verification:', savedToken ? `Token exists, length: ${savedToken.length}` : 'Token NOT found!')
|
|
set({
|
|
user,
|
|
token,
|
|
refreshToken: refreshToken ?? null,
|
|
expiresIn: expiresIn ?? null,
|
|
tokenExpiresAt,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
error: null,
|
|
})
|
|
},
|
|
|
|
updateToken: (token: string, refreshToken: string, expiresIn: number) => {
|
|
console.log('[AuthStore] Updating token')
|
|
localStorage.setItem('admin_token', token)
|
|
localStorage.setItem('admin_refresh_token', refreshToken)
|
|
|
|
const tokenExpiresAt = Date.now() + (expiresIn - 300) * 1000 // 5 minutes buffer
|
|
|
|
set({
|
|
token,
|
|
refreshToken,
|
|
expiresIn,
|
|
tokenExpiresAt,
|
|
})
|
|
},
|
|
|
|
logout: () => {
|
|
localStorage.removeItem('admin_token')
|
|
localStorage.removeItem('admin_refresh_token')
|
|
set({
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
expiresIn: null,
|
|
tokenExpiresAt: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: null,
|
|
})
|
|
},
|
|
|
|
setLoading: (isLoading: boolean) => {
|
|
set({ isLoading })
|
|
},
|
|
|
|
setError: (error: string | null) => {
|
|
set({ error, isLoading: false })
|
|
},
|
|
|
|
shouldRefreshToken: () => {
|
|
const state = get()
|
|
if (!state.refreshToken || !state.token) {
|
|
return false
|
|
}
|
|
|
|
// If we have expiresIn but no tokenExpiresAt, calculate it
|
|
if (state.expiresIn && !state.tokenExpiresAt) {
|
|
const calculatedExpiresAt = Date.now() + (state.expiresIn - 300) * 1000
|
|
set({ tokenExpiresAt: calculatedExpiresAt })
|
|
return Date.now() >= calculatedExpiresAt
|
|
}
|
|
|
|
if (!state.tokenExpiresAt) {
|
|
return false
|
|
}
|
|
|
|
// Refresh if token expires in less than 5 minutes
|
|
return Date.now() >= state.tokenExpiresAt
|
|
},
|
|
}),
|
|
{
|
|
name: 'admin-auth',
|
|
partialize: (state) => ({
|
|
user: state.user,
|
|
token: state.token,
|
|
refreshToken: state.refreshToken,
|
|
expiresIn: state.expiresIn,
|
|
tokenExpiresAt: state.tokenExpiresAt,
|
|
isAuthenticated: state.isAuthenticated,
|
|
}),
|
|
onRehydrateStorage: () => (state) => {
|
|
// After rehydration, recalculate tokenExpiresAt if needed
|
|
if (state && state.expiresIn && !state.tokenExpiresAt) {
|
|
const tokenExpiresAt = Date.now() + (state.expiresIn - 300) * 1000
|
|
state.tokenExpiresAt = tokenExpiresAt
|
|
}
|
|
},
|
|
}
|
|
)
|
|
)
|