Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
238 lines
6.5 KiB
TypeScript
238 lines
6.5 KiB
TypeScript
'use client'
|
|
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import { apiClient, OfflineError } from '../client'
|
|
import { enqueue } from '../offline-queue'
|
|
import type { Recipient, ApiOutboxMessage, PaginatedResponse } from '../types'
|
|
import { useDemoMail } from '@/lib/demo/demo-mail-context'
|
|
import { useDemoMailStore } from '@/lib/demo/demo-mail-store'
|
|
|
|
export interface SendMessagePayload {
|
|
account_id: string
|
|
to: Recipient[]
|
|
cc?: Recipient[]
|
|
bcc?: Recipient[]
|
|
subject: string
|
|
body_html: string
|
|
in_reply_to?: string
|
|
idempotency_key: string
|
|
scheduled_at?: string
|
|
}
|
|
|
|
export type DraftPayload = Omit<SendMessagePayload, 'idempotency_key' | 'scheduled_at'>
|
|
|
|
export function useSendMessage() {
|
|
const queryClient = useQueryClient()
|
|
const demoMail = useDemoMail()
|
|
|
|
return useMutation({
|
|
mutationFn: async (payload: SendMessagePayload) => {
|
|
if (demoMail) {
|
|
const created = useDemoMailStore.getState().sendMessage(payload)
|
|
demoMail.notify('Message « envoyé »')
|
|
return {
|
|
id: created.id,
|
|
account_id: created.account_id,
|
|
status: 'sent',
|
|
to: created.to,
|
|
subject: created.subject,
|
|
body_html: created.body_html ?? '',
|
|
created_at: created.date,
|
|
} satisfies ApiOutboxMessage
|
|
}
|
|
try {
|
|
return await apiClient.post<ApiOutboxMessage>('/mail/send', payload)
|
|
} catch (err) {
|
|
if (err instanceof OfflineError) {
|
|
await enqueue({
|
|
id: payload.idempotency_key,
|
|
timestamp: Date.now(),
|
|
type: 'send_message',
|
|
payload,
|
|
retries: 0,
|
|
})
|
|
return null
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
onSuccess: () => {
|
|
if (demoMail) {
|
|
useDemoMailStore.getState().bump()
|
|
}
|
|
queryClient.invalidateQueries({ queryKey: ['messages', 'sent'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useCreateDraft() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async (payload: DraftPayload) => {
|
|
try {
|
|
return await apiClient.post<ApiOutboxMessage>('/mail/drafts', payload)
|
|
} catch (err) {
|
|
if (err instanceof OfflineError) {
|
|
await enqueue({
|
|
id: `draft-create-${Date.now()}`,
|
|
timestamp: Date.now(),
|
|
type: 'create_draft',
|
|
payload,
|
|
retries: 0,
|
|
})
|
|
return null
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['messages', 'drafts'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useUpdateDraft() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id, ...payload }: DraftPayload & { id: string }) => {
|
|
try {
|
|
return await apiClient.put<ApiOutboxMessage>(`/mail/drafts/${id}`, payload)
|
|
} catch (err) {
|
|
if (err instanceof OfflineError) {
|
|
await enqueue({
|
|
id: `draft-update-${id}-${Date.now()}`,
|
|
timestamp: Date.now(),
|
|
type: 'update_draft',
|
|
payload: { draft_id: id, ...payload },
|
|
retries: 0,
|
|
})
|
|
return null
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
onSuccess: (_data, variables) => {
|
|
queryClient.invalidateQueries({ queryKey: ['messages', 'drafts'] })
|
|
queryClient.invalidateQueries({ queryKey: ['message', variables.id] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useDeleteDraft() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id }: { id: string }) => {
|
|
try {
|
|
await apiClient.delete(`/mail/drafts/${id}`)
|
|
} catch (err) {
|
|
if (err instanceof OfflineError) {
|
|
await enqueue({
|
|
id: `draft-delete-${id}-${Date.now()}`,
|
|
timestamp: Date.now(),
|
|
type: 'delete_draft',
|
|
payload: { draft_id: id },
|
|
retries: 0,
|
|
})
|
|
return
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
onMutate: async ({ id }) => {
|
|
await queryClient.cancelQueries({ queryKey: ['messages', 'drafts'] })
|
|
|
|
const previous = queryClient.getQueriesData<PaginatedResponse<ApiOutboxMessage>>({
|
|
queryKey: ['messages', 'drafts'],
|
|
})
|
|
|
|
queryClient.setQueriesData<PaginatedResponse<ApiOutboxMessage>>(
|
|
{ queryKey: ['messages', 'drafts'] },
|
|
(old) => {
|
|
if (!old) return old
|
|
return { ...old, data: old.data.filter((m) => m.id !== id) }
|
|
}
|
|
)
|
|
|
|
return { previous }
|
|
},
|
|
onError: (_err, _vars, context) => {
|
|
context?.previous?.forEach(([key, data]) => queryClient.setQueryData(key, data))
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['messages', 'drafts'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useScheduleSend() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async (payload: SendMessagePayload & { scheduled_at: string }) => {
|
|
try {
|
|
return await apiClient.post<ApiOutboxMessage>('/mail/send', payload)
|
|
} catch (err) {
|
|
if (err instanceof OfflineError) {
|
|
await enqueue({
|
|
id: payload.idempotency_key,
|
|
timestamp: Date.now(),
|
|
type: 'schedule_send',
|
|
payload,
|
|
retries: 0,
|
|
})
|
|
return null
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['outbox'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useRescheduleSend() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id, scheduled_at }: { id: string; scheduled_at: string }) => {
|
|
return await apiClient.post<ApiOutboxMessage>(`/mail/outbox/${id}/reschedule`, {
|
|
scheduled_at,
|
|
})
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['outbox'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useCancelScheduled() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id }: { id: string }) => {
|
|
return await apiClient.post<ApiOutboxMessage>(`/mail/outbox/${id}/cancel`)
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['outbox'] })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useSendNow() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id }: { id: string }) => {
|
|
return await apiClient.post<ApiOutboxMessage>(`/mail/outbox/${id}/send-now`)
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['outbox'] })
|
|
queryClient.invalidateQueries({ queryKey: ['messages', 'sent'] })
|
|
},
|
|
})
|
|
}
|