fix(trust): H15 — relay outage must not read as 'not found'/'no events'

- 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'.
This commit is contained in:
SkateSpots 2026-10-02 21:36:44 +07:00
parent 9189fc6584
commit 64ba187ad4
4 changed files with 19 additions and 5 deletions

View file

@ -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;

View file

@ -24,7 +24,7 @@ export function useSkateEvents(limit = 50, options: { includeHidden?: boolean }
limit,
},
],
{ signal }
{ signal, requireAny: true }
);
const skateEvents: SkateEvent[] = [];

View file

@ -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<MainView>('events');
@ -1228,6 +1229,8 @@ export default function EventsPage() {
{mainView === 'events' && layout === 'list' && (
eventsLoading ? (
<SkeletonGrid />
) : eventsError ? (
<ErrorState onRetry={() => { void refetchEvents(); }} />
) : activeTab === 'upcoming' ? (
filteredEvents.upcoming.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 stagger-children">
@ -1280,6 +1283,8 @@ export default function EventsPage() {
{mainView === 'tours' && layout === 'list' && (
toursLoading ? (
<TourSkeletonGrid />
) : toursError ? (
<ErrorState onRetry={() => { void refetchTours(); }} />
) : filteredTours.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 stagger-children">
{filteredTours.map((tour) => (

View file

@ -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 (
<div className="container mx-auto max-w-md px-4 py-16">
<ErrorState onRetry={() => { void refetch(); }} />
</div>
);
}
if (!item) {
return (
<>