'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, bookId: string, contact: ApiContact, ) { queryClient.setQueryData(['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 }) => { const created = await apiClient.post( `/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 etag?: string }) => apiClient.put(`/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({ 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] }) }, }) }