ultisuite-client/lib/stores/mail-store.ts
2026-05-15 17:40:17 +02:00

188 lines
5.9 KiB
TypeScript

"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[]
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
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: [],
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),
})),
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: 2,
migrate: (persisted, version) => {
const state = persisted as MailStoreState
if (version < 2) {
return { ...state, recentFolderVisits: [] }
}
return state
},
}
)
)