This commit is contained in:
2026-03-18 20:54:43 +01:00
parent b3c8b77f12
commit 9fe656b34c
8058 changed files with 912898 additions and 23 deletions
+110
View File
@@ -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
+79
View File
@@ -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 }),
}))
+60
View File
@@ -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 }),
}))