feat(18-04): add global item hooks, catalog browse page, and detail page
- useGlobalItems/useGlobalItem/useLinkItem/useUnlinkItem hooks - Global catalog browse page with search, debounce, and skeleton loading - Global item detail page with owner count badge - GlobalItemCard component with brand, model, specs badges
This commit is contained in:
74
src/client/hooks/useGlobalItems.ts
Normal file
74
src/client/hooks/useGlobalItems.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ApiError, apiDelete, apiGet, apiPost } from "../lib/api";
|
||||
|
||||
interface GlobalItem {
|
||||
id: number;
|
||||
brand: string;
|
||||
model: string;
|
||||
category: string | null;
|
||||
weightGrams: number | null;
|
||||
priceCents: number | null;
|
||||
imageUrl: string | null;
|
||||
description: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface GlobalItemWithOwnerCount extends GlobalItem {
|
||||
ownerCount: number;
|
||||
}
|
||||
|
||||
interface ItemGlobalLink {
|
||||
id: number;
|
||||
itemId: number;
|
||||
globalItemId: number;
|
||||
}
|
||||
|
||||
export function useGlobalItems(query?: string) {
|
||||
return useQuery({
|
||||
queryKey: ["global-items", query ?? ""],
|
||||
queryFn: () =>
|
||||
apiGet<GlobalItem[]>(
|
||||
`/api/global-items${query ? `?q=${encodeURIComponent(query)}` : ""}`,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
export function useGlobalItem(id: number | null) {
|
||||
return useQuery({
|
||||
queryKey: ["global-items", id],
|
||||
queryFn: () => apiGet<GlobalItemWithOwnerCount>(`/api/global-items/${id}`),
|
||||
enabled: id != null,
|
||||
retry: (count, error) =>
|
||||
error instanceof ApiError && error.status === 404 ? false : count < 3,
|
||||
});
|
||||
}
|
||||
|
||||
export function useLinkItem() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
itemId,
|
||||
globalItemId,
|
||||
}: {
|
||||
itemId: number;
|
||||
globalItemId: number;
|
||||
}) =>
|
||||
apiPost<ItemGlobalLink>(`/api/items/${itemId}/link`, { globalItemId }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["items"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["global-items"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnlinkItem() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (itemId: number) =>
|
||||
apiDelete<{ success: boolean }>(`/api/items/${itemId}/link`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["items"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["global-items"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user