Skip to content

Commit 21a6c7b

Browse files
move listing fetch into hook
1 parent 9449694 commit 21a6c7b

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { useQuery, useQueryClient } from "@tanstack/react-query";
2+
3+
export interface Listing {
4+
id: string;
5+
title: string;
6+
image: string | null;
7+
tags: string[];
8+
priceWei: string | null;
9+
tokenSymbol: string | null;
10+
tokenDecimals: number | null;
11+
isOptimistic?: boolean;
12+
}
13+
14+
function parseTags(tags: unknown): string[] {
15+
if (Array.isArray(tags)) {
16+
return tags.map(t => String(t)).filter(Boolean);
17+
}
18+
if (typeof tags === "string" && tags) {
19+
try {
20+
const parsed = JSON.parse(tags);
21+
if (Array.isArray(parsed)) {
22+
return parsed.map(t => String(t)).filter(Boolean);
23+
}
24+
} catch {
25+
return tags.trim() ? [tags.trim()] : [];
26+
}
27+
}
28+
return [];
29+
}
30+
31+
function transformListingItem(item: any): Listing {
32+
return {
33+
id: item.id,
34+
title: item?.title ?? item.id,
35+
image: item?.image ?? null,
36+
tags: parseTags(item?.tags),
37+
priceWei: item?.priceWei ?? null,
38+
tokenSymbol: item?.tokenSymbol ?? null,
39+
tokenDecimals: item?.tokenDecimals ?? null,
40+
isOptimistic: false,
41+
};
42+
}
43+
44+
async function fetchListingsByLocation(locationId: string): Promise<Listing[]> {
45+
const ponderUrl = process.env.NEXT_PUBLIC_PONDER_URL || "http://localhost:42069/graphql";
46+
47+
const res = await fetch(ponderUrl, {
48+
method: "POST",
49+
headers: { "content-type": "application/json" },
50+
body: JSON.stringify({
51+
query: `
52+
query ListingsByLocation($loc: String!) {
53+
listingss(
54+
where: { locationId: $loc, active: true }
55+
orderBy: "createdBlockNumber"
56+
orderDirection: "desc"
57+
limit: 100
58+
) {
59+
items {
60+
id
61+
title
62+
image
63+
tags
64+
priceWei
65+
tokenSymbol
66+
tokenDecimals
67+
}
68+
}
69+
}
70+
`,
71+
variables: { loc: locationId },
72+
}),
73+
});
74+
75+
if (!res.ok) {
76+
throw new Error(`Failed to fetch listings: ${res.statusText}`);
77+
}
78+
79+
const json = await res.json();
80+
81+
if (json.errors) {
82+
throw new Error(`GraphQL errors: ${JSON.stringify(json.errors)}`);
83+
}
84+
85+
const items = (json?.data?.listingss?.items || []).map(transformListingItem);
86+
return items;
87+
}
88+
89+
function mergeListings(fetched: Listing[], optimistic: Listing[]): Listing[] {
90+
const fetchedIds = new Set(fetched.map(l => l.id));
91+
const pending = optimistic.filter(l => l.isOptimistic && !fetchedIds.has(l.id));
92+
return [...pending, ...fetched];
93+
}
94+
95+
export function useListingsByLocation(locationId: string | null | undefined) {
96+
const queryClient = useQueryClient();
97+
const queryKey = ["listings", "location", locationId];
98+
99+
return useQuery({
100+
queryKey,
101+
queryFn: async () => {
102+
if (!locationId) throw new Error("Location ID is required");
103+
const cached = queryClient.getQueryData<Listing[]>(queryKey);
104+
const optimistic = cached?.filter(l => l.isOptimistic) ?? [];
105+
const fetched = await fetchListingsByLocation(locationId);
106+
107+
return mergeListings(fetched, optimistic);
108+
},
109+
enabled: !!locationId,
110+
staleTime: 1000,
111+
refetchInterval: query => (query.state.data?.some(l => l.isOptimistic) ? 2000 : false),
112+
});
113+
}

0 commit comments

Comments
 (0)