ultisuite-client/lib/api/hooks/use-compose-mutations.ts
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- 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.
2026-06-12 19:10:24 +02:00

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'] })
},
})
}