ultisuite-client/lib/stores/mail-store.ts
2026-05-15 23:22:24 +02:00

203 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { create } from "zustand"
import { persist } from "zustand/middleware"
/**
* Persistent mail store — survives across navigations and page reloads.
* Tracks user-driven mutations on top of the static `emails` array from email-data.ts.
* Designed for future server sync: every action is a discrete delta.
*/
export type LabelEditState = {
additions: Record<string, string[]>
removals: Record<string, string[]>
}
type MailStoreState = {
readOverrides: Record<string, boolean>
starredIds: string[]
importantIds: string[]
labelEdits: LabelEditState
hiddenEmailIds: string[]
seenEmailIds: string[]
/** Ids marqués comme non-spam (réintégration boîte de réception dans lUI). */
notSpamEmailIds: string[]
recentMoveTargets: string[]
/** Dernières boîtes visitées (clés `mailNavVisitKey`), la plus récente en tête. */
recentFolderVisits: string[]
}
type MailStoreActions = {
setReadOverride: (id: string, read: boolean) => void
setReadOverrides: (overrides: Record<string, boolean>) => void
toggleStar: (id: string) => void
setStar: (id: string, starred: boolean) => void
toggleImportant: (id: string) => void
setImportant: (id: string, important: boolean) => void
addLabel: (emailId: string, label: string) => void
removeLabel: (emailId: string, label: string) => void
setLabelEdits: (updater: (prev: LabelEditState) => LabelEditState) => void
hideEmail: (id: string) => void
hideEmails: (ids: string[]) => void
unhideEmail: (id: string) => void
markSeen: (id: string) => void
/** Réintègre le message comme non-spam (liste / boîte de réception). */
markNotSpam: (id: string) => void
resetHidden: () => void
pushRecentMoveTarget: (targetId: string) => void
pushRecentFolderVisit: (visitKey: string) => void
}
export const useMailStore = create<MailStoreState & MailStoreActions>()(
persist(
(set) => ({
readOverrides: {},
starredIds: [],
importantIds: [],
labelEdits: { additions: {}, removals: {} },
hiddenEmailIds: [],
seenEmailIds: [],
notSpamEmailIds: [],
recentMoveTargets: [],
recentFolderVisits: [],
setReadOverride: (id, read) =>
set((s) => ({ readOverrides: { ...s.readOverrides, [id]: read } })),
setReadOverrides: (overrides) =>
set((s) => ({ readOverrides: { ...s.readOverrides, ...overrides } })),
toggleStar: (id) =>
set((s) => ({
starredIds: s.starredIds.includes(id)
? s.starredIds.filter((x) => x !== id)
: [...s.starredIds, id],
})),
setStar: (id, starred) =>
set((s) => ({
starredIds: starred
? s.starredIds.includes(id) ? s.starredIds : [...s.starredIds, id]
: s.starredIds.filter((x) => x !== id),
})),
toggleImportant: (id) =>
set((s) => ({
importantIds: s.importantIds.includes(id)
? s.importantIds.filter((x) => x !== id)
: [...s.importantIds, id],
})),
setImportant: (id, important) =>
set((s) => ({
importantIds: important
? s.importantIds.includes(id) ? s.importantIds : [...s.importantIds, id]
: s.importantIds.filter((x) => x !== id),
})),
addLabel: (emailId, label) =>
set((s) => {
const curr = s.labelEdits.additions[emailId] ?? []
if (curr.some((l) => l.toLowerCase() === label.toLowerCase())) return s
return {
labelEdits: {
additions: { ...s.labelEdits.additions, [emailId]: [...curr, label] },
removals: {
...s.labelEdits.removals,
[emailId]: (s.labelEdits.removals[emailId] ?? []).filter(
(r) => r.toLowerCase() !== label.toLowerCase()
),
},
},
}
}),
removeLabel: (emailId, label) =>
set((s) => {
const curr = s.labelEdits.removals[emailId] ?? []
if (curr.some((l) => l.toLowerCase() === label.toLowerCase())) return s
return {
labelEdits: {
removals: { ...s.labelEdits.removals, [emailId]: [...curr, label] },
additions: {
...s.labelEdits.additions,
[emailId]: (s.labelEdits.additions[emailId] ?? []).filter(
(a) => a.toLowerCase() !== label.toLowerCase()
),
},
},
}
}),
setLabelEdits: (updater) =>
set((s) => ({ labelEdits: updater(s.labelEdits) })),
hideEmail: (id) =>
set((s) => ({
hiddenEmailIds: s.hiddenEmailIds.includes(id)
? s.hiddenEmailIds
: [...s.hiddenEmailIds, id],
})),
hideEmails: (ids) =>
set((s) => {
const existing = new Set(s.hiddenEmailIds)
const toAdd = ids.filter((id) => !existing.has(id))
return toAdd.length > 0
? { hiddenEmailIds: [...s.hiddenEmailIds, ...toAdd] }
: s
}),
unhideEmail: (id) =>
set((s) => ({
hiddenEmailIds: s.hiddenEmailIds.filter((x) => x !== id),
})),
markNotSpam: (id) =>
set((s) =>
s.notSpamEmailIds.includes(id)
? s
: { notSpamEmailIds: [...s.notSpamEmailIds, id] }
),
markSeen: (id) =>
set((s) => ({
seenEmailIds: s.seenEmailIds.includes(id)
? s.seenEmailIds
: [...s.seenEmailIds, id],
})),
resetHidden: () => set({ hiddenEmailIds: [] }),
pushRecentMoveTarget: (targetId) =>
set((s) => {
const MAX = 5
const filtered = s.recentMoveTargets.filter((t) => t !== targetId)
return { recentMoveTargets: [targetId, ...filtered].slice(0, MAX) }
}),
pushRecentFolderVisit: (visitKey) =>
set((s) => {
const MAX = 4
const filtered = s.recentFolderVisits.filter((k) => k !== visitKey)
return { recentFolderVisits: [visitKey, ...filtered].slice(0, MAX) }
}),
}),
{
name: "ultimail-mail-state",
version: 3,
migrate: (persisted, version) => {
const state = persisted as MailStoreState & { notSpamEmailIds?: string[] }
if (version < 2) {
return { ...state, recentFolderVisits: [], notSpamEmailIds: [] }
}
if (version < 3) {
return { ...state, notSpamEmailIds: state.notSpamEmailIds ?? [] }
}
return state
},
}
)
)