mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-17 05:05:44 +02:00
fase 5
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Auth Store (Zustand) – stato autenticazione globale.
|
||||
*
|
||||
* Persiste access_token e refresh_token in localStorage.
|
||||
* L'utente viene memorizzato in memoria per sicurezza.
|
||||
*/
|
||||
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { authApi } from '@/api/auth.api'
|
||||
import type { LoginRequest, UserResponse } from '@/types/api.types'
|
||||
|
||||
interface AuthState {
|
||||
user: UserResponse | null
|
||||
accessToken: string | null
|
||||
refreshToken: string | null
|
||||
isAuthenticated: boolean
|
||||
isLoading: boolean
|
||||
|
||||
// Actions
|
||||
login: (credentials: LoginRequest) => Promise<void>
|
||||
logout: () => Promise<void>
|
||||
loadUser: () => Promise<void>
|
||||
setTokens: (access: string, refresh: string) => void
|
||||
clearAuth: () => void
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
user: null,
|
||||
accessToken: localStorage.getItem('access_token'),
|
||||
refreshToken: localStorage.getItem('refresh_token'),
|
||||
isAuthenticated: !!localStorage.getItem('access_token'),
|
||||
isLoading: false,
|
||||
|
||||
login: async (credentials) => {
|
||||
set({ isLoading: true })
|
||||
try {
|
||||
const tokens = await authApi.login(credentials)
|
||||
localStorage.setItem('access_token', tokens.access_token)
|
||||
localStorage.setItem('refresh_token', tokens.refresh_token)
|
||||
|
||||
// Carica i dati utente
|
||||
const user = await authApi.me()
|
||||
set({
|
||||
user,
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
})
|
||||
} catch (error) {
|
||||
set({ isLoading: false })
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
const { refreshToken } = get()
|
||||
if (refreshToken) {
|
||||
try {
|
||||
await authApi.logout(refreshToken)
|
||||
} catch {
|
||||
// Ignora errori di logout (token già scaduto)
|
||||
}
|
||||
}
|
||||
get().clearAuth()
|
||||
},
|
||||
|
||||
loadUser: async () => {
|
||||
const token = localStorage.getItem('access_token')
|
||||
if (!token) return
|
||||
|
||||
set({ isLoading: true })
|
||||
try {
|
||||
const user = await authApi.me()
|
||||
set({ user, isAuthenticated: true, isLoading: false })
|
||||
} catch {
|
||||
get().clearAuth()
|
||||
set({ isLoading: false })
|
||||
}
|
||||
},
|
||||
|
||||
setTokens: (access, refresh) => {
|
||||
localStorage.setItem('access_token', access)
|
||||
localStorage.setItem('refresh_token', refresh)
|
||||
set({ accessToken: access, refreshToken: refresh, isAuthenticated: true })
|
||||
},
|
||||
|
||||
clearAuth: () => {
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
set({
|
||||
user: null,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
})
|
||||
},
|
||||
}),
|
||||
{ name: 'PecFlow/Auth' },
|
||||
),
|
||||
)
|
||||
|
||||
// Selettori tipizzati
|
||||
export const selectUser = (s: AuthState) => s.user
|
||||
export const selectIsAdmin = (s: AuthState) =>
|
||||
s.user?.role === 'admin' || s.user?.role === 'super_admin'
|
||||
export const selectIsAuthenticated = (s: AuthState) => s.isAuthenticated
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Inbox Store (Zustand) – stato messaggi e filtri.
|
||||
*/
|
||||
|
||||
import { create } from 'zustand'
|
||||
import type { MessageResponse } from '@/types/api.types'
|
||||
|
||||
interface InboxFilters {
|
||||
mailbox_id?: string
|
||||
direction?: 'inbound' | 'outbound'
|
||||
state?: string
|
||||
is_read?: boolean
|
||||
is_starred?: boolean
|
||||
search?: string
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
interface InboxState {
|
||||
messages: MessageResponse[]
|
||||
total: number
|
||||
filters: InboxFilters
|
||||
selectedMessageId: string | null
|
||||
unreadCount: number
|
||||
isLoading: boolean
|
||||
|
||||
setFilters: (filters: Partial<InboxFilters>) => void
|
||||
resetFilters: () => void
|
||||
setMessages: (messages: MessageResponse[], total: number) => void
|
||||
prependMessage: (message: MessageResponse) => void
|
||||
updateMessage: (id: string, updates: Partial<MessageResponse>) => void
|
||||
selectMessage: (id: string | null) => void
|
||||
incrementUnread: () => void
|
||||
resetUnread: () => void
|
||||
setLoading: (loading: boolean) => void
|
||||
}
|
||||
|
||||
const DEFAULT_FILTERS: InboxFilters = {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}
|
||||
|
||||
export const useInboxStore = create<InboxState>()((set) => ({
|
||||
messages: [],
|
||||
total: 0,
|
||||
filters: DEFAULT_FILTERS,
|
||||
selectedMessageId: null,
|
||||
unreadCount: 0,
|
||||
isLoading: false,
|
||||
|
||||
setFilters: (filters) =>
|
||||
set((state) => ({
|
||||
filters: { ...state.filters, ...filters, page: 1 },
|
||||
})),
|
||||
|
||||
resetFilters: () => set({ filters: DEFAULT_FILTERS }),
|
||||
|
||||
setMessages: (messages, total) => set({ messages, total }),
|
||||
|
||||
prependMessage: (message) =>
|
||||
set((state) => ({
|
||||
messages: [message, ...state.messages],
|
||||
total: state.total + 1,
|
||||
unreadCount: state.unreadCount + (message.is_read ? 0 : 1),
|
||||
})),
|
||||
|
||||
updateMessage: (id, updates) =>
|
||||
set((state) => ({
|
||||
messages: state.messages.map((m) => (m.id === id ? { ...m, ...updates } : m)),
|
||||
})),
|
||||
|
||||
selectMessage: (id) => set({ selectedMessageId: id }),
|
||||
|
||||
incrementUnread: () => set((state) => ({ unreadCount: state.unreadCount + 1 })),
|
||||
|
||||
resetUnread: () => set({ unreadCount: 0 }),
|
||||
|
||||
setLoading: (loading) => set({ isLoading: loading }),
|
||||
}))
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Mailbox Store (Zustand) – lista caselle PEC.
|
||||
*/
|
||||
|
||||
import { create } from 'zustand'
|
||||
import type { MailboxResponse } from '@/types/api.types'
|
||||
|
||||
interface MailboxState {
|
||||
mailboxes: MailboxResponse[]
|
||||
selectedMailboxId: string | null
|
||||
isLoading: boolean
|
||||
|
||||
setMailboxes: (mailboxes: MailboxResponse[]) => void
|
||||
upsertMailbox: (mailbox: MailboxResponse) => void
|
||||
removeMailbox: (id: string) => void
|
||||
selectMailbox: (id: string | null) => void
|
||||
updateMailboxStatus: (id: string, status: string, errorMsg?: string) => void
|
||||
setLoading: (loading: boolean) => void
|
||||
}
|
||||
|
||||
export const useMailboxStore = create<MailboxState>()((set) => ({
|
||||
mailboxes: [],
|
||||
selectedMailboxId: null,
|
||||
isLoading: false,
|
||||
|
||||
setMailboxes: (mailboxes) => set({ mailboxes }),
|
||||
|
||||
upsertMailbox: (mailbox) =>
|
||||
set((state) => {
|
||||
const idx = state.mailboxes.findIndex((m) => m.id === mailbox.id)
|
||||
if (idx >= 0) {
|
||||
const updated = [...state.mailboxes]
|
||||
updated[idx] = mailbox
|
||||
return { mailboxes: updated }
|
||||
}
|
||||
return { mailboxes: [...state.mailboxes, mailbox] }
|
||||
}),
|
||||
|
||||
removeMailbox: (id) =>
|
||||
set((state) => ({
|
||||
mailboxes: state.mailboxes.filter((m) => m.id !== id),
|
||||
})),
|
||||
|
||||
selectMailbox: (id) => set({ selectedMailboxId: id }),
|
||||
|
||||
updateMailboxStatus: (id, status, errorMsg) =>
|
||||
set((state) => ({
|
||||
mailboxes: state.mailboxes.map((m) =>
|
||||
m.id === id
|
||||
? {
|
||||
...m,
|
||||
status: status as MailboxResponse['status'],
|
||||
sync_error_msg: errorMsg ?? m.sync_error_msg,
|
||||
}
|
||||
: m,
|
||||
),
|
||||
})),
|
||||
|
||||
setLoading: (loading) => set({ isLoading: loading }),
|
||||
}))
|
||||
Reference in New Issue
Block a user