Skip to content

Commit 9449694

Browse files
Merge pull request #9 from websurferdoteth/feat/sort-locations
Feat/sort locations
2 parents 6b381f9 + 7c669a8 commit 9449694

File tree

3 files changed

+110
-1
lines changed

3 files changed

+110
-1
lines changed

packages/nextjs/app/api/locations/route.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextResponse } from "next/server";
22
import { Redis } from "@upstash/redis";
3+
import { fetchActiveListings } from "~~/services/marketplace/graphql";
34

45
const redis = Redis.fromEnv();
56
const GEO_KEY = "locations:geo";
@@ -13,6 +14,33 @@ function idFor(lat: number, lng: number, miles: number) {
1314
return `loc:${lat.toFixed(5)}:${lng.toFixed(5)}:${miles.toFixed(2)}`;
1415
}
1516

17+
async function getListingCountsByLocation(): Promise<Record<string, number>> {
18+
try {
19+
const items = await fetchActiveListings();
20+
21+
if (items.length === 0) {
22+
return {};
23+
}
24+
25+
const counts: Record<string, number> = {};
26+
27+
for (const item of items) {
28+
const locationId = item?.locationId;
29+
if (locationId && typeof locationId === "string") {
30+
const normalizedId = locationId.trim();
31+
if (normalizedId) {
32+
counts[normalizedId] = (counts[normalizedId] || 0) + 1;
33+
}
34+
}
35+
}
36+
37+
return counts;
38+
} catch (error: any) {
39+
console.error("[getListingCountsByLocation] Error:", error?.message || String(error));
40+
return {};
41+
}
42+
}
43+
1644
export async function GET(req: Request) {
1745
try {
1846
const { searchParams } = new URL(req.url);
@@ -83,6 +111,27 @@ export async function GET(req: Request) {
83111
return name.includes(q) || id.includes(q) || akaMatch;
84112
});
85113
}
114+
115+
const listingCounts = await getListingCountsByLocation();
116+
117+
locations = locations.map((loc: any) => {
118+
const locationId = String(loc?.id || "").trim();
119+
const count = locationId ? listingCounts[locationId] || 0 : 0;
120+
121+
return { ...loc, activeListingsCount: count };
122+
});
123+
124+
locations.sort((a: any, b: any) => {
125+
const countA = a.activeListingsCount || 0;
126+
const countB = b.activeListingsCount || 0;
127+
if (countB !== countA) {
128+
return countB - countA;
129+
}
130+
const nameA = (a.name || a.id || "").toLowerCase();
131+
const nameB = (b.name || b.id || "").toLowerCase();
132+
return nameA.localeCompare(nameB);
133+
});
134+
86135
return NextResponse.json({ locations: locations.slice(0, limit) });
87136
} catch (e: any) {
88137
if (String(e?.message || "").includes("Unexpected non-whitespace character after JSON")) {

packages/nextjs/app/page.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,24 @@ const HomeInner = () => {
137137
}}
138138
>
139139
<div className="card-body p-3">
140-
<div className="card-title text-base">{l.name || l.id}</div>
140+
<div className="flex items-start justify-between gap-2">
141+
<div className="flex-1 min-w-0">
142+
<div className="card-title text-base">{l.name || l.id}</div>
143+
<div className="text-sm opacity-70 mt-1">
144+
{l.activeListingsCount !== undefined ? (
145+
<>
146+
{l.activeListingsCount === 0 ? (
147+
<span className="opacity-50">No active listings</span>
148+
) : (
149+
<span>
150+
{l.activeListingsCount} {l.activeListingsCount === 1 ? "listing" : "listings"}
151+
</span>
152+
)}
153+
</>
154+
) : null}
155+
</div>
156+
</div>
157+
</div>
141158
</div>
142159
</button>
143160
))}

packages/nextjs/services/marketplace/graphql.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,46 @@ export async function fetchListingById(id: string): Promise<ListingData | null>
5050
const item = json?.data?.listings || null;
5151
return item;
5252
}
53+
54+
export interface ActiveListing {
55+
id: string;
56+
locationId: string | null;
57+
}
58+
59+
export async function fetchActiveListings(): Promise<ActiveListing[]> {
60+
const res = await fetch(PONDER_URL, {
61+
method: "POST",
62+
headers: { "content-type": "application/json" },
63+
body: JSON.stringify({
64+
query: `
65+
query ActiveListings {
66+
listingss(
67+
where: { active: true }
68+
) {
69+
items {
70+
id
71+
locationId
72+
}
73+
}
74+
}
75+
`,
76+
}),
77+
});
78+
79+
if (!res.ok) {
80+
throw new Error(`GraphQL request failed with status ${res.status}`);
81+
}
82+
83+
const json = await res.json();
84+
85+
if (json.errors) {
86+
throw new Error(`GraphQL errors: ${JSON.stringify(json.errors, null, 2)}`);
87+
}
88+
89+
const data = json?.data;
90+
if (!data) {
91+
throw new Error(`No data in response: ${JSON.stringify(json, null, 2)}`);
92+
}
93+
94+
return data?.listingss?.items || [];
95+
}

0 commit comments

Comments
 (0)