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.
251 lines
7.7 KiB
TypeScript
251 lines
7.7 KiB
TypeScript
'use client'
|
|
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import { apiClient, OfflineError } from '../client'
|
|
import { enqueue } from '../offline-queue'
|
|
import type { PaginatedResponse, ApiMessageSummary, ApiMessageFull } from '../types'
|
|
import {
|
|
mailFlagIsRead,
|
|
mailFlagsWithRead,
|
|
mailFlagsWithStarred,
|
|
mailFlagsWithImportant,
|
|
} from '@/lib/mail-flags'
|
|
import { useDemoMail } from '@/lib/demo/demo-mail-context'
|
|
import { useDemoMailStore } from '@/lib/demo/demo-mail-store'
|
|
import { mailMessagesQueryRoot } from '@/lib/api/hooks/use-mail-queries'
|
|
|
|
function messageDetailQueryRoot(isDemo: boolean) {
|
|
return isDemo ? (['demo', 'message'] as const) : (['message'] as const)
|
|
}
|
|
|
|
export function useUpdateFlags() {
|
|
const queryClient = useQueryClient()
|
|
const demoMail = useDemoMail()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id, flags }: { id: string; flags: string[] }) => {
|
|
if (demoMail) {
|
|
const updated = useDemoMailStore.getState().updateFlags(id, flags)
|
|
if (!updated) throw new Error('Message introuvable')
|
|
return updated
|
|
}
|
|
try {
|
|
return await apiClient.put<ApiMessageFull>(`/mail/messages/${id}/flags`, { flags })
|
|
} catch (err) {
|
|
if (err instanceof OfflineError) {
|
|
await enqueue({
|
|
id: `flags-${id}-${Date.now()}`,
|
|
timestamp: Date.now(),
|
|
type: 'update_flags',
|
|
payload: { message_id: id, flags },
|
|
retries: 0,
|
|
})
|
|
return undefined
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
onMutate: async ({ id, flags }) => {
|
|
const messagesRoot = mailMessagesQueryRoot(!!demoMail)
|
|
const messageRoot = messageDetailQueryRoot(!!demoMail)
|
|
|
|
await queryClient.cancelQueries({ queryKey: messagesRoot })
|
|
await queryClient.cancelQueries({ queryKey: [...messageRoot, id] })
|
|
|
|
const previousMessages = queryClient.getQueriesData<PaginatedResponse<ApiMessageSummary>>({
|
|
queryKey: messagesRoot,
|
|
})
|
|
|
|
queryClient.setQueriesData<PaginatedResponse<ApiMessageSummary>>(
|
|
{ queryKey: messagesRoot },
|
|
(old) => {
|
|
if (!old) return old
|
|
return { ...old, data: old.data.map((m) => (m.id === id ? { ...m, flags } : m)) }
|
|
}
|
|
)
|
|
|
|
queryClient.setQueriesData<ApiMessageFull>({ queryKey: [...messageRoot, id] }, (old) =>
|
|
old ? { ...old, flags } : old
|
|
)
|
|
|
|
return { previousMessages }
|
|
},
|
|
onError: (_err, _vars, context) => {
|
|
context?.previousMessages?.forEach(([key, data]) => queryClient.setQueryData(key, data))
|
|
},
|
|
onSettled: () => {
|
|
if (demoMail) {
|
|
useDemoMailStore.getState().bump()
|
|
}
|
|
queryClient.invalidateQueries({ queryKey: mailMessagesQueryRoot(!!demoMail) })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useUpdateLabels() {
|
|
const queryClient = useQueryClient()
|
|
const demoMail = useDemoMail()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id, labels }: { id: string; labels: string[] }) => {
|
|
if (demoMail) {
|
|
const updated = useDemoMailStore.getState().updateLabels(id, labels)
|
|
if (!updated) throw new Error('Message introuvable')
|
|
return updated
|
|
}
|
|
try {
|
|
return await apiClient.put<ApiMessageFull>(`/mail/messages/${id}/labels`, { labels })
|
|
} catch (err) {
|
|
if (err instanceof OfflineError) {
|
|
await enqueue({
|
|
id: `labels-${id}-${Date.now()}`,
|
|
timestamp: Date.now(),
|
|
type: 'update_labels',
|
|
payload: { message_id: id, labels },
|
|
retries: 0,
|
|
})
|
|
return undefined
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
onMutate: async ({ id, labels }) => {
|
|
const messagesRoot = mailMessagesQueryRoot(!!demoMail)
|
|
const messageRoot = messageDetailQueryRoot(!!demoMail)
|
|
|
|
await queryClient.cancelQueries({ queryKey: messagesRoot })
|
|
await queryClient.cancelQueries({ queryKey: [...messageRoot, id] })
|
|
|
|
const previousMessages = queryClient.getQueriesData<PaginatedResponse<ApiMessageSummary>>({
|
|
queryKey: messagesRoot,
|
|
})
|
|
|
|
queryClient.setQueriesData<PaginatedResponse<ApiMessageSummary>>(
|
|
{ queryKey: messagesRoot },
|
|
(old) => {
|
|
if (!old) return old
|
|
return { ...old, data: old.data.map((m) => (m.id === id ? { ...m, labels } : m)) }
|
|
}
|
|
)
|
|
|
|
queryClient.setQueriesData<ApiMessageFull>({ queryKey: [...messageRoot, id] }, (old) =>
|
|
old ? { ...old, labels } : old
|
|
)
|
|
|
|
return { previousMessages }
|
|
},
|
|
onError: (_err, _vars, context) => {
|
|
context?.previousMessages?.forEach(([key, data]) => queryClient.setQueryData(key, data))
|
|
},
|
|
onSettled: () => {
|
|
if (demoMail) {
|
|
useDemoMailStore.getState().bump()
|
|
}
|
|
queryClient.invalidateQueries({ queryKey: mailMessagesQueryRoot(!!demoMail) })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useDeleteMessage() {
|
|
const queryClient = useQueryClient()
|
|
const demoMail = useDemoMail()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id }: { id: string }) => {
|
|
if (demoMail) {
|
|
const updated = useDemoMailStore.getState().deleteMessage(id)
|
|
if (!updated) throw new Error('Message introuvable')
|
|
demoMail.notify('Message placé dans la corbeille')
|
|
return
|
|
}
|
|
try {
|
|
await apiClient.delete(`/mail/messages/${id}`)
|
|
} catch (err) {
|
|
if (err instanceof OfflineError) {
|
|
await enqueue({
|
|
id: `delete-${id}-${Date.now()}`,
|
|
timestamp: Date.now(),
|
|
type: 'delete_message',
|
|
payload: { message_id: id },
|
|
retries: 0,
|
|
})
|
|
return
|
|
}
|
|
throw err
|
|
}
|
|
},
|
|
onMutate: async ({ id }) => {
|
|
const messagesRoot = mailMessagesQueryRoot(!!demoMail)
|
|
const messageRoot = messageDetailQueryRoot(!!demoMail)
|
|
|
|
await queryClient.cancelQueries({ queryKey: messagesRoot })
|
|
|
|
const previousMessages = queryClient.getQueriesData<PaginatedResponse<ApiMessageSummary>>({
|
|
queryKey: messagesRoot,
|
|
})
|
|
|
|
queryClient.setQueriesData<PaginatedResponse<ApiMessageSummary>>(
|
|
{ queryKey: messagesRoot },
|
|
(old) => {
|
|
if (!old) return old
|
|
return { ...old, data: old.data.filter((m) => m.id !== id) }
|
|
}
|
|
)
|
|
|
|
queryClient.removeQueries({ queryKey: [...messageRoot, id] })
|
|
|
|
return { previousMessages }
|
|
},
|
|
onError: (_err, _vars, context) => {
|
|
context?.previousMessages?.forEach(([key, data]) => queryClient.setQueryData(key, data))
|
|
},
|
|
onSettled: () => {
|
|
if (demoMail) {
|
|
useDemoMailStore.getState().bump()
|
|
}
|
|
queryClient.invalidateQueries({ queryKey: mailMessagesQueryRoot(!!demoMail) })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useToggleStar() {
|
|
const updateFlags = useUpdateFlags()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id, flags, starred }: { id: string; flags: string[]; starred: boolean }) => {
|
|
const newFlags = mailFlagsWithStarred(flags, !starred)
|
|
return updateFlags.mutateAsync({ id, flags: newFlags })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useToggleImportant() {
|
|
const updateFlags = useUpdateFlags()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
id,
|
|
flags,
|
|
important,
|
|
}: {
|
|
id: string
|
|
flags: string[]
|
|
important: boolean
|
|
}) => {
|
|
const newFlags = mailFlagsWithImportant(flags, !important)
|
|
return updateFlags.mutateAsync({ id, flags: newFlags })
|
|
},
|
|
})
|
|
}
|
|
|
|
export function useMarkRead() {
|
|
const updateFlags = useUpdateFlags()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id, flags }: { id: string; flags: string[] }) => {
|
|
if (mailFlagIsRead(flags)) return
|
|
return updateFlags.mutateAsync({ id, flags: mailFlagsWithRead(flags, true) })
|
|
},
|
|
})
|
|
}
|