From 64ba187ad418084441e2efe677694af396a614fb Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Fri, 2 Oct 2026 21:36:44 +0700 Subject: [PATCH] =?UTF-8?q?fix(trust):=20H15=20=E2=80=94=20relay=20outage?= =?UTF-8?q?=20must=20not=20read=20as=20'not=20found'/'no=20events'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useMarketplaceItem / useSkateEvents now pass requireAny, so a total outage throws (isError) instead of silently returning []. - ListingDetailPage and EventsPage render the shared ErrorState with a retry instead of a false 'Listing not found' / 'No upcoming events'. --- src/hooks/useMarketplace.ts | 2 +- src/hooks/useSkateEvents.ts | 2 +- src/pages/EventsPage.tsx | 9 +++++++-- src/pages/ListingDetailPage.tsx | 11 ++++++++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/hooks/useMarketplace.ts b/src/hooks/useMarketplace.ts index 3b16416..3a5d7df 100644 --- a/src/hooks/useMarketplace.ts +++ b/src/hooks/useMarketplace.ts @@ -106,7 +106,7 @@ export function useMarketplaceItem(pubkey?: string, identifier?: string) { const signal = AbortSignal.timeout(TIMEOUT); const events = await querySkateContent( [{ kinds: [MARKETPLACE_ITEM_KIND], authors: [pubkey], '#d': [identifier], limit: 5 }], - { signal }, + { signal, requireAny: true }, ); const newest = [...events].sort((a, b) => b.created_at - a.created_at)[0]; return newest ? parseMarketplaceItem(newest) : null; diff --git a/src/hooks/useSkateEvents.ts b/src/hooks/useSkateEvents.ts index d5ac7e2..785033e 100644 --- a/src/hooks/useSkateEvents.ts +++ b/src/hooks/useSkateEvents.ts @@ -24,7 +24,7 @@ export function useSkateEvents(limit = 50, options: { includeHidden?: boolean } limit, }, ], - { signal } + { signal, requireAny: true } ); const skateEvents: SkateEvent[] = []; diff --git a/src/pages/EventsPage.tsx b/src/pages/EventsPage.tsx index 8cf0168..0e49066 100644 --- a/src/pages/EventsPage.tsx +++ b/src/pages/EventsPage.tsx @@ -46,6 +46,7 @@ import { } from 'lucide-react'; import { cn } from '@/lib/utils'; import { EmptyState as SharedEmptyState, type EmptyStateAction } from '@/components/EmptyState'; +import { ErrorState } from '@/components/ErrorState'; // ─── Accent colours ─────────────────────────────────────────────────────────── @@ -822,8 +823,8 @@ export default function EventsPage() { const followCount = followingList?.length ?? 0; const smartDefault: TrustFilterLevel = (user && followCount >= 5) ? 'trusted' : 'all'; - const { data: events, isLoading: eventsLoading } = useSkateEvents(100); - const { data: tours, isLoading: toursLoading } = useTours(100); + const { data: events, isLoading: eventsLoading, isError: eventsError, refetch: refetchEvents } = useSkateEvents(100); + const { data: tours, isLoading: toursLoading, isError: toursError, refetch: refetchTours } = useTours(100); // ── View state ────────────────────────────────────────────────────────────── const [mainView, setMainView] = useState('events'); @@ -1228,6 +1229,8 @@ export default function EventsPage() { {mainView === 'events' && layout === 'list' && ( eventsLoading ? ( + ) : eventsError ? ( + { void refetchEvents(); }} /> ) : activeTab === 'upcoming' ? ( filteredEvents.upcoming.length > 0 ? (
@@ -1280,6 +1283,8 @@ export default function EventsPage() { {mainView === 'tours' && layout === 'list' && ( toursLoading ? ( + ) : toursError ? ( + { void refetchTours(); }} /> ) : filteredTours.length > 0 ? (
{filteredTours.map((tour) => ( diff --git a/src/pages/ListingDetailPage.tsx b/src/pages/ListingDetailPage.tsx index ab594f4..e7ebf17 100644 --- a/src/pages/ListingDetailPage.tsx +++ b/src/pages/ListingDetailPage.tsx @@ -22,6 +22,7 @@ import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useSellerInfo } from '@/hooks/useSellerInfo'; import { useToast } from '@/hooks/useToast'; +import { ErrorState } from '@/components/ErrorState'; import { formatPrice, GEAR_CONDITIONS, GEAR_CATEGORIES } from '@/lib/types'; import { GEAR_CATEGORY_KEYS, GEAR_CONDITION_KEYS } from '@/lib/labelKeys'; import { genUserName } from '@/lib/genUserName'; @@ -54,7 +55,7 @@ export default function ListingDetailPage() { const { user } = useCurrentUser(); const { toast } = useToast(); - const { data: item, isLoading } = useMarketplaceItem(pubkey, identifier); + const { data: item, isLoading, isError, refetch } = useMarketplaceItem(pubkey, identifier); const author = useAuthor(pubkey); const metadata = author.data?.metadata; const { data: sellerInfo } = useSellerInfo(pubkey); @@ -83,6 +84,14 @@ export default function ListingDetailPage() { ); } + if (isError) { + return ( +
+ { void refetch(); }} /> +
+ ); + } + if (!item) { return ( <>