Files
GearBox/src/client/hooks/useGlobalItems.ts
Jean-Luc Makiola e4a65314bd feat(25-02): add attribution display on catalog detail page
- GlobalItem interface extended with sourceUrl, imageCredit, imageSourceUrl fields
- Attribution block below image: Photo credit and source link when present
- Product page link (sourceUrl) at bottom of detail page
- Image div mb-6 moved to attribution paragraph for consistent spacing
2026-04-10 11:05:52 +02:00

81 lines
2.1 KiB
TypeScript

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;
sourceUrl: string | null;
imageCredit: string | null;
imageSourceUrl: string | null;
createdAt: string;
}
interface GlobalItemWithOwnerCount extends GlobalItem {
ownerCount: number;
}
interface ItemGlobalLink {
id: number;
itemId: number;
globalItemId: number;
}
export function useGlobalItems(query?: string, tags?: string[]) {
const params = new URLSearchParams();
if (query) params.set("q", query);
if (tags && tags.length > 0) params.set("tags", tags.join(","));
const qs = params.toString();
return useQuery({
queryKey: ["global-items", query ?? "", tags ?? []],
queryFn: () =>
apiGet<GlobalItem[]>(`/api/global-items${qs ? `?${qs}` : ""}`),
});
}
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"] });
},
});
}