203 lines
6.5 KiB
TypeScript
203 lines
6.5 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[]
|
||
/** Ids marqués comme non-spam (réintégration boîte de réception dans l’UI). */
|
||
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
|
||
},
|
||
}
|
||
)
|
||
)
|