192 lines
6.1 KiB
TypeScript
192 lines
6.1 KiB
TypeScript
"use client"
|
|
|
|
import { create } from "zustand"
|
|
import { persist } from "zustand/middleware"
|
|
import type { Email } from "@/lib/email-data"
|
|
|
|
export type ScheduleSendPayload = {
|
|
sendAtIso: string
|
|
to: { name: string; email: string }[]
|
|
subject: string
|
|
previewText: string
|
|
bodyHtml: string
|
|
}
|
|
|
|
type ScheduledStoreState = {
|
|
scheduledEmails: Email[]
|
|
snoozedEmails: Email[]
|
|
sentPlaceholderEmails: Email[]
|
|
}
|
|
|
|
function rowToSchedulePayload(row: Email): ScheduleSendPayload {
|
|
const email = row.senderEmail?.trim() ?? ""
|
|
const name = row.scheduledToName ?? row.sender
|
|
return {
|
|
sendAtIso: row.scheduledSendAt ?? new Date().toISOString(),
|
|
to: email ? [{ name, email }] : [],
|
|
subject: row.subject,
|
|
previewText: row.preview,
|
|
bodyHtml: row.body ?? `<p></p>`,
|
|
}
|
|
}
|
|
|
|
type ScheduledStoreActions = {
|
|
createScheduledSend: (payload: ScheduleSendPayload) => { id: string }
|
|
deleteScheduledSend: (id: string) => void
|
|
archiveScheduledSend: (id: string) => void
|
|
snoozeScheduledSend: (id: string) => void
|
|
rescheduleScheduledSend: (id: string, sendAtIso: string) => void
|
|
markScheduledReadState: (id: string, read: boolean) => void
|
|
getScheduledEditPayload: (id: string) => ScheduleSendPayload | null
|
|
updateScheduledSend: (id: string, payload: ScheduleSendPayload) => void
|
|
sendScheduledNow: (id: string) => void
|
|
removeScheduledLocal: (id: string) => void
|
|
}
|
|
|
|
export const useScheduledStore = create<ScheduledStoreState & ScheduledStoreActions>()(
|
|
persist(
|
|
(set, get) => ({
|
|
scheduledEmails: [],
|
|
snoozedEmails: [],
|
|
sentPlaceholderEmails: [],
|
|
|
|
createScheduledSend: (payload) => {
|
|
const id = `sched-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
|
|
const first = payload.to[0]
|
|
const toName = first?.name?.trim() || first?.email || "Destinataire"
|
|
const row: Email = {
|
|
id,
|
|
sender: toName,
|
|
senderEmail: first?.email,
|
|
subject: payload.subject.trim() || "(Sans objet)",
|
|
preview: payload.previewText.slice(0, 200),
|
|
body: payload.bodyHtml,
|
|
date: "",
|
|
read: true,
|
|
starred: false,
|
|
important: false,
|
|
category: "primary",
|
|
labels: ["scheduled"],
|
|
scheduledSendAt: payload.sendAtIso,
|
|
scheduledToName: toName,
|
|
}
|
|
set((s) => ({
|
|
scheduledEmails: [row, ...s.scheduledEmails.filter((e) => e.id !== id)],
|
|
}))
|
|
return { id }
|
|
},
|
|
|
|
deleteScheduledSend: (id) =>
|
|
set((s) => ({
|
|
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
|
|
})),
|
|
|
|
archiveScheduledSend: (id) =>
|
|
set((s) => ({
|
|
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
|
|
})),
|
|
|
|
snoozeScheduledSend: (id) =>
|
|
set((s) => {
|
|
const row = s.scheduledEmails.find((e) => e.id === id)
|
|
if (!row) return s
|
|
const wake = new Date(Date.now() + 24 * 60 * 60 * 1000)
|
|
return {
|
|
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
|
|
snoozedEmails: [
|
|
{
|
|
...row,
|
|
labels: ["snoozed"],
|
|
scheduledSendAt: undefined,
|
|
scheduledToName: undefined,
|
|
snoozeWakeAt: wake.toISOString(),
|
|
sender: row.scheduledToName ?? row.sender,
|
|
date: wake.toLocaleString("fr-FR", { dateStyle: "medium", timeStyle: "short" }),
|
|
read: true,
|
|
},
|
|
...s.snoozedEmails,
|
|
],
|
|
}
|
|
}),
|
|
|
|
rescheduleScheduledSend: (id, sendAtIso) =>
|
|
set((s) => ({
|
|
scheduledEmails: s.scheduledEmails.map((e) =>
|
|
e.id === id ? { ...e, scheduledSendAt: sendAtIso } : e
|
|
),
|
|
})),
|
|
|
|
markScheduledReadState: (id, read) =>
|
|
set((s) => ({
|
|
scheduledEmails: s.scheduledEmails.map((e) =>
|
|
e.id === id ? { ...e, read } : e
|
|
),
|
|
})),
|
|
|
|
getScheduledEditPayload: (id) => {
|
|
const row = get().scheduledEmails.find((e) => e.id === id)
|
|
if (!row) return null
|
|
return rowToSchedulePayload(row)
|
|
},
|
|
|
|
updateScheduledSend: (id, payload) =>
|
|
set((s) => {
|
|
const first = payload.to[0]
|
|
const toName = first?.name?.trim() || first?.email || "Destinataire"
|
|
return {
|
|
scheduledEmails: s.scheduledEmails.map((e) =>
|
|
e.id === id
|
|
? {
|
|
...e,
|
|
sender: toName,
|
|
senderEmail: first?.email,
|
|
subject: payload.subject.trim() || "(Sans objet)",
|
|
preview: payload.previewText.slice(0, 200),
|
|
body: payload.bodyHtml,
|
|
scheduledSendAt: payload.sendAtIso,
|
|
scheduledToName: toName,
|
|
}
|
|
: e
|
|
),
|
|
}
|
|
}),
|
|
|
|
sendScheduledNow: (id) =>
|
|
set((s) => {
|
|
const row = s.scheduledEmails.find((e) => e.id === id)
|
|
if (!row) return s
|
|
const now = new Date()
|
|
return {
|
|
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
|
|
sentPlaceholderEmails: [
|
|
{
|
|
id: `sent-now-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`,
|
|
sender: row.scheduledToName ?? row.sender,
|
|
senderEmail: row.senderEmail,
|
|
subject: row.subject,
|
|
preview: row.preview,
|
|
body: row.body,
|
|
date: now.toLocaleString("fr-FR", { dateStyle: "short", timeStyle: "short" }),
|
|
read: true,
|
|
starred: false,
|
|
important: false,
|
|
category: "primary",
|
|
labels: ["sent"],
|
|
},
|
|
...s.sentPlaceholderEmails,
|
|
],
|
|
}
|
|
}),
|
|
|
|
removeScheduledLocal: (id) =>
|
|
set((s) => ({
|
|
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
|
|
})),
|
|
}),
|
|
{
|
|
name: "ultimail-scheduled-state",
|
|
version: 1,
|
|
}
|
|
)
|
|
)
|