Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced new ContactAvatar and ContactAvatarPicker components for enhanced avatar management in contact views. - Updated ContactDetailView and ContactFormView to utilize the new avatar components, improving user experience when adding or editing contacts. - Enhanced ContactHoverCard and ContactRow components to display avatars, providing a more visually appealing interface. - Added loading and error states in ContactsListView for better user feedback during data fetching. - Implemented a new ContactsLoadState component to handle loading and error scenarios in the contacts list. - Updated package.json to include @formkit/auto-animate for improved UI animations.
81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
import type { QueryClient } from '@tanstack/react-query'
|
|
import type { ApiContact } from '@/lib/api/types'
|
|
import { contactApiPath } from '@/lib/contacts/contact-api-path'
|
|
import type { FullContact } from '@/lib/contacts/types'
|
|
|
|
function contactMatchesPath(api: ApiContact, apiPath: string): boolean {
|
|
const normalizedPath = apiPath.replace(/^\/+/, '')
|
|
const contactPath = api.path?.replace(/^\/+/, '')
|
|
return (
|
|
contactPath === normalizedPath ||
|
|
api.uid === normalizedPath ||
|
|
contactPath?.endsWith(`/${normalizedPath}`) === true ||
|
|
contactPath?.endsWith(`/${normalizedPath}.vcf`) === true
|
|
)
|
|
}
|
|
|
|
/** Merge selection snapshot with latest React Query cache (etag, path). */
|
|
export function resolveContactForUpdate(
|
|
queryClient: QueryClient,
|
|
contact: FullContact,
|
|
): FullContact {
|
|
const api = findApiContactInCaches(queryClient, contact)
|
|
if (!api) return contact
|
|
const etag = contact.etag ?? api.etag
|
|
const path = contact.path ?? api.path
|
|
if (etag === contact.etag && path === contact.path) return contact
|
|
return { ...contact, etag, path }
|
|
}
|
|
|
|
export function findApiContactInCaches(
|
|
queryClient: QueryClient,
|
|
contact: Pick<FullContact, 'id' | 'path'>,
|
|
): ApiContact | undefined {
|
|
const apiPath = contactApiPath(contact)
|
|
for (const [, list] of queryClient.getQueriesData<ApiContact[]>({
|
|
queryKey: ['contacts'],
|
|
})) {
|
|
if (!list) continue
|
|
const hit = list.find(
|
|
(c) => c.uid === contact.id || contactMatchesPath(c, apiPath),
|
|
)
|
|
if (hit) return hit
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
export function appendContactToBookCache(
|
|
queryClient: QueryClient,
|
|
bookId: string,
|
|
contact: ApiContact,
|
|
) {
|
|
if (!contact.uid && !contact.path) return
|
|
queryClient.setQueryData<ApiContact[]>(['contacts', bookId], (old) => {
|
|
const list = old ?? []
|
|
const uid = contact.uid
|
|
if (uid && list.some((item) => item.uid === uid)) return list
|
|
return [...list, contact]
|
|
})
|
|
}
|
|
|
|
export function replaceContactInBookCaches(
|
|
queryClient: QueryClient,
|
|
apiPath: string,
|
|
patch: Partial<ApiContact>,
|
|
) {
|
|
for (const [key, list] of queryClient.getQueriesData<ApiContact[]>({
|
|
queryKey: ['contacts'],
|
|
})) {
|
|
if (!list) continue
|
|
let changed = false
|
|
const next = list.map((item) => {
|
|
if (!contactMatchesPath(item, apiPath)) return item
|
|
changed = true
|
|
return { ...item, ...patch }
|
|
})
|
|
if (changed) {
|
|
queryClient.setQueryData(key, next)
|
|
}
|
|
}
|
|
}
|