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:
parent
9189fc6584
commit
64ba187ad4
4 changed files with 19 additions and 5 deletions
|
|
@ -106,7 +106,7 @@ export function useMarketplaceItem(pubkey?: string, identifier?: string) {
|
||||||
const signal = AbortSignal.timeout(TIMEOUT);
|
const signal = AbortSignal.timeout(TIMEOUT);
|
||||||
const events = await querySkateContent(
|
const events = await querySkateContent(
|
||||||
[{ kinds: [MARKETPLACE_ITEM_KIND], authors: [pubkey], '#d': [identifier], limit: 5 }],
|
[{ 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];
|
const newest = [...events].sort((a, b) => b.created_at - a.created_at)[0];
|
||||||
return newest ? parseMarketplaceItem(newest) : null;
|
return newest ? parseMarketplaceItem(newest) : null;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export function useSkateEvents(limit = 50, options: { includeHidden?: boolean }
|
||||||
limit,
|
limit,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ signal }
|
{ signal, requireAny: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const skateEvents: SkateEvent[] = [];
|
const skateEvents: SkateEvent[] = [];
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import {
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { EmptyState as SharedEmptyState, type EmptyStateAction } from '@/components/EmptyState';
|
import { EmptyState as SharedEmptyState, type EmptyStateAction } from '@/components/EmptyState';
|
||||||
|
import { ErrorState } from '@/components/ErrorState';
|
||||||
|
|
||||||
// ─── Accent colours ───────────────────────────────────────────────────────────
|
// ─── Accent colours ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -822,8 +823,8 @@ export default function EventsPage() {
|
||||||
const followCount = followingList?.length ?? 0;
|
const followCount = followingList?.length ?? 0;
|
||||||
const smartDefault: TrustFilterLevel = (user && followCount >= 5) ? 'trusted' : 'all';
|
const smartDefault: TrustFilterLevel = (user && followCount >= 5) ? 'trusted' : 'all';
|
||||||
|
|
||||||
const { data: events, isLoading: eventsLoading } = useSkateEvents(100);
|
const { data: events, isLoading: eventsLoading, isError: eventsError, refetch: refetchEvents } = useSkateEvents(100);
|
||||||
const { data: tours, isLoading: toursLoading } = useTours(100);
|
const { data: tours, isLoading: toursLoading, isError: toursError, refetch: refetchTours } = useTours(100);
|
||||||
|
|
||||||
// ── View state ──────────────────────────────────────────────────────────────
|
// ── View state ──────────────────────────────────────────────────────────────
|
||||||
const [mainView, setMainView] = useState<MainView>('events');
|
const [mainView, setMainView] = useState<MainView>('events');
|
||||||
|
|
@ -1228,6 +1229,8 @@ export default function EventsPage() {
|
||||||
{mainView === 'events' && layout === 'list' && (
|
{mainView === 'events' && layout === 'list' && (
|
||||||
eventsLoading ? (
|
eventsLoading ? (
|
||||||
<SkeletonGrid />
|
<SkeletonGrid />
|
||||||
|
) : eventsError ? (
|
||||||
|
<ErrorState onRetry={() => { void refetchEvents(); }} />
|
||||||
) : activeTab === 'upcoming' ? (
|
) : activeTab === 'upcoming' ? (
|
||||||
filteredEvents.upcoming.length > 0 ? (
|
filteredEvents.upcoming.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 stagger-children">
|
<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' && (
|
{mainView === 'tours' && layout === 'list' && (
|
||||||
toursLoading ? (
|
toursLoading ? (
|
||||||
<TourSkeletonGrid />
|
<TourSkeletonGrid />
|
||||||
|
) : toursError ? (
|
||||||
|
<ErrorState onRetry={() => { void refetchTours(); }} />
|
||||||
) : filteredTours.length > 0 ? (
|
) : filteredTours.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 stagger-children">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 stagger-children">
|
||||||
{filteredTours.map((tour) => (
|
{filteredTours.map((tour) => (
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
import { useAuthor } from '@/hooks/useAuthor';
|
import { useAuthor } from '@/hooks/useAuthor';
|
||||||
import { useSellerInfo } from '@/hooks/useSellerInfo';
|
import { useSellerInfo } from '@/hooks/useSellerInfo';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
|
import { ErrorState } from '@/components/ErrorState';
|
||||||
import { formatPrice, GEAR_CONDITIONS, GEAR_CATEGORIES } from '@/lib/types';
|
import { formatPrice, GEAR_CONDITIONS, GEAR_CATEGORIES } from '@/lib/types';
|
||||||
import { GEAR_CATEGORY_KEYS, GEAR_CONDITION_KEYS } from '@/lib/labelKeys';
|
import { GEAR_CATEGORY_KEYS, GEAR_CONDITION_KEYS } from '@/lib/labelKeys';
|
||||||
import { genUserName } from '@/lib/genUserName';
|
import { genUserName } from '@/lib/genUserName';
|
||||||
|
|
@ -54,7 +55,7 @@ export default function ListingDetailPage() {
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const { data: item, isLoading } = useMarketplaceItem(pubkey, identifier);
|
const { data: item, isLoading, isError, refetch } = useMarketplaceItem(pubkey, identifier);
|
||||||
const author = useAuthor(pubkey);
|
const author = useAuthor(pubkey);
|
||||||
const metadata = author.data?.metadata;
|
const metadata = author.data?.metadata;
|
||||||
const { data: sellerInfo } = useSellerInfo(pubkey);
|
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) {
|
if (!item) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue