ultisuite-client/lib/api/hooks/use-contact-mutations.ts
2026-05-25 13:52:40 +02:00

136 lines
3.8 KiB
TypeScript

'use client'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { apiClient, OfflineError } from '../client'
import { enqueue } from '../offline-queue'
import type { ApiContact } from '../types'
function appendContactToBookCache(
queryClient: ReturnType<typeof useQueryClient>,
bookId: string,
contact: ApiContact,
) {
queryClient.setQueryData<ApiContact[]>(['contacts', bookId], (old) => {
const list = old ?? []
if (list.some((item) => item.uid === contact.uid)) return list
return [...list, contact]
})
}
export function useCreateContact() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (vars: { bookId: string; contact: Partial<ApiContact> }) => {
const created = await apiClient.post<ApiContact | undefined>(
`/contacts/books/${vars.bookId}`,
vars.contact,
)
if (created?.uid) return created
return vars.contact as ApiContact
},
onSuccess: (data, vars) => {
if (data?.uid) {
appendContactToBookCache(queryClient, vars.bookId, data)
}
queryClient.invalidateQueries({ queryKey: ['contacts', vars.bookId] })
},
onError: (err, vars) => {
if (err instanceof OfflineError) {
enqueue({
id: crypto.randomUUID(),
timestamp: Date.now(),
type: 'create_contact',
payload: { bookId: vars.bookId, ...vars.contact },
retries: 0,
})
}
},
})
}
export function useUpdateContact() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (vars: {
path: string
contact: Partial<ApiContact>
etag?: string
}) => apiClient.put<ApiContact>(`/contacts/${vars.path}`, {
...vars.contact,
etag: vars.etag,
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['contacts'] })
},
onError: (err, vars) => {
if (err instanceof OfflineError) {
enqueue({
id: crypto.randomUUID(),
timestamp: Date.now(),
type: 'update_contact',
payload: { path: vars.path, ...vars.contact },
retries: 0,
})
}
},
})
}
export function useDeleteContact() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (vars: { path: string; bookId?: string }) =>
apiClient.delete(`/contacts/${vars.path}`),
onMutate: async (vars) => {
await queryClient.cancelQueries({ queryKey: ['contacts'] })
const queries = queryClient.getQueriesData<ApiContact[]>({ queryKey: ['contacts'] })
const snapshots: [readonly unknown[], ApiContact[] | undefined][] = []
for (const [key, data] of queries) {
if (data) {
snapshots.push([key, data])
queryClient.setQueryData(
key,
data.filter((c) => c.path !== vars.path && c.uid !== vars.path)
)
}
}
return { snapshots }
},
onError: (err, vars, context) => {
if (context?.snapshots) {
for (const [key, data] of context.snapshots) {
queryClient.setQueryData(key, data)
}
}
if (err instanceof OfflineError) {
enqueue({
id: crypto.randomUUID(),
timestamp: Date.now(),
type: 'delete_contact',
payload: { path: vars.path, uid: vars.path },
retries: 0,
})
}
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['contacts'] })
},
})
}
export function useMergeDuplicates() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (vars: { bookId: string }) =>
apiClient.post(`/contacts/books/${vars.bookId}/merge-duplicates`),
onSuccess: (_data, vars) => {
queryClient.invalidateQueries({ queryKey: ['contacts', vars.bookId] })
},
})
}