- Add useGlobalItemPrices and useGlobalItemCommunityStats hooks - Add MarketPricesSection component with user's market MSRP prominent - Show community price stats per market with median and report count - Collapsible "Other Markets" section (collapsed by default) - Import useCurrency, useExchangeRates, formatPrice for market display Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
118 lines
2.9 KiB
TypeScript
118 lines
2.9 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"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
interface MarketPriceData {
|
|
id: number;
|
|
globalItemId: number;
|
|
market: string;
|
|
currency: string;
|
|
priceCents: number;
|
|
source: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
interface CommunityPriceStat {
|
|
market: string;
|
|
currency: string;
|
|
medianPrice: number;
|
|
reportCount: number;
|
|
}
|
|
|
|
export function useGlobalItemPrices(globalItemId: number) {
|
|
return useQuery({
|
|
queryKey: ["global-item-prices", globalItemId],
|
|
queryFn: () =>
|
|
apiGet<{ marketPrices: MarketPriceData[] }>(
|
|
`/api/market-prices/global-items/${globalItemId}/prices`,
|
|
),
|
|
enabled: globalItemId > 0,
|
|
});
|
|
}
|
|
|
|
export function useGlobalItemCommunityStats(globalItemId: number) {
|
|
return useQuery({
|
|
queryKey: ["global-item-community-stats", globalItemId],
|
|
queryFn: () =>
|
|
apiGet<CommunityPriceStat[]>(`/api/community-prices/${globalItemId}`),
|
|
enabled: globalItemId > 0,
|
|
});
|
|
}
|
|
|
|
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"] });
|
|
},
|
|
});
|
|
}
|