import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useClient } from 'hooks/useClient'; import type { FavoriteKind, GroupedFavorites } from './shared'; const KEY = ['music', 'favorites'] as const; const EMPTY: GroupedFavorites = { tracks: [], albums: [], artists: [] }; const groupOf = (kind: FavoriteKind): keyof GroupedFavorites => kind === 'track' ? 'tracks' : kind === 'album' ? 'albums' : 'artists'; /** * The user's music favorites (tracks / albums / artists) for the /music workspace, backed by the * platform's `/api/music/favorites`. One shared react-query cache, so every heart reflects the same * state; toggling is optimistic (flips instantly, rolls back on failure). */ export function useMusicFavorites() { const { get, post, delete: del } = useClient(); const qc = useQueryClient(); const { data } = useQuery({ queryKey: KEY, queryFn: () => get('/music/favorites'), staleTime: 60_000, }); const mutation = useMutation({ mutationFn: ({ on, kind, key }: { on: boolean; kind: FavoriteKind; key: string }) => on ? post('/music/favorites', { kind, key }) : del(`/music/favorites?kind=${encodeURIComponent(kind)}&key=${encodeURIComponent(key)}`), onMutate: async ({ on, kind, key }) => { await qc.cancelQueries({ queryKey: KEY }); const prev = qc.getQueryData(KEY) ?? EMPTY; const g = groupOf(kind); qc.setQueryData(KEY, { ...prev, [g]: on ? [key, ...prev[g].filter((k) => k !== key)] : prev[g].filter((k) => k !== key), }); return { prev }; }, onError: (_e, _v, ctx) => { if (ctx?.prev) qc.setQueryData(KEY, ctx.prev); }, }); const isFavorite = (kind: FavoriteKind, key: string) => (data ?? EMPTY)[groupOf(kind)].includes(key); const toggle = (kind: FavoriteKind, key: string) => { if (!key) return; mutation.mutate({ on: !isFavorite(kind, key), kind, key }); }; return { favorites: data ?? EMPTY, isFavorite, toggle }; }