feat(client): add loading skeleton and global error handling components
Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
@@ -3,14 +3,19 @@ import PlaceholderImage from '@/assets/event-placeholder.png';
|
||||
import { useGetEvents } from '@/hooks/data/useGetEvents';
|
||||
import { Button } from '../ui/button';
|
||||
import { DialogTrigger } from '../ui/dialog';
|
||||
import { EventGridSkeleton } from './event-grid.skeleton';
|
||||
import { EventGridView } from './event-grid.view';
|
||||
import { KycDialogContainer } from './kyc/kyc.dialog.container';
|
||||
|
||||
export function EventGridContainer() {
|
||||
const { data, isLoading } = useGetEvents();
|
||||
const allEvents: EventInfo[] = isLoading
|
||||
? []
|
||||
: data.pages.flatMap(page => page.data ?? []).map(it => ({
|
||||
|
||||
return (
|
||||
isLoading
|
||||
? <EventGridSkeleton />
|
||||
: (
|
||||
<EventGridView
|
||||
events={data.pages.flatMap(page => page.data ?? []).map(it => ({
|
||||
type: it.type! as EventInfo['type'],
|
||||
eventId: it.event_id!,
|
||||
isJoined: it.is_joined!,
|
||||
@@ -20,11 +25,7 @@ export function EventGridContainer() {
|
||||
description: it.description!,
|
||||
startTime: new Date(it.start_time!),
|
||||
endTime: new Date(it.end_time!),
|
||||
} satisfies EventInfo));
|
||||
|
||||
return (
|
||||
<EventGridView
|
||||
events={allEvents}
|
||||
} satisfies EventInfo))}
|
||||
footer={eventInfo => (eventInfo.isJoined
|
||||
? <Button className="w-full" disabled>已加入</Button>
|
||||
: (
|
||||
@@ -36,5 +37,6 @@ export function EventGridContainer() {
|
||||
)
|
||||
)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
16
client/cms/src/components/events/event-grid.error.tsx
Normal file
16
client/cms/src/components/events/event-grid.error.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { FileExclamationPoint } from 'lucide-react';
|
||||
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '../ui/empty';
|
||||
|
||||
export function EventGridError() {
|
||||
return (
|
||||
<Empty className="h-full">
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<FileExclamationPoint />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>活动列表加载失败</EmptyTitle>
|
||||
<EmptyDescription>前面的区域 以后再来探索吧</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { EventCardSkeleton } from './event-card.skeleton';
|
||||
export function EventGridSkeleton() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<EventCardSkeleton key={i} />
|
||||
))}
|
||||
|
||||
@@ -6,7 +6,7 @@ export function EventGridView({ events, footer }: { events: EventInfo[]; footer:
|
||||
return (
|
||||
<>
|
||||
{events.length > 0 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 h-full">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||
{events.map(event => (
|
||||
<EventCardView key={event.eventId} eventInfo={event} actionFooter={footer(event)} />
|
||||
))}
|
||||
|
||||
18
client/cms/src/components/global.error.tsx
Normal file
18
client/cms/src/components/global.error.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { RawError } from '@/lib/types';
|
||||
import { TriangleAlert } from 'lucide-react';
|
||||
import { isRawError } from '@/lib/types';
|
||||
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from './ui/empty';
|
||||
|
||||
export function GlobalError({ error }: { error: Error | RawError }) {
|
||||
return (
|
||||
<Empty className="h-screen w-full">
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<TriangleAlert />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>出错了</EmptyTitle>
|
||||
<EmptyDescription>{isRawError(error) ? error.error_id : error.message}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>
|
||||
);
|
||||
}
|
||||
@@ -4,3 +4,13 @@ export interface RawError {
|
||||
error_id: string;
|
||||
data: null;
|
||||
}
|
||||
|
||||
export function isRawError(obj: any): obj is RawError {
|
||||
return (
|
||||
typeof obj === 'object'
|
||||
&& obj !== null
|
||||
&& 'code' in obj
|
||||
&& 'status' in obj
|
||||
&& 'error_id' in obj
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { createRootRoute, Outlet } from '@tanstack/react-router';
|
||||
import { GlobalError } from '@/components/global.error';
|
||||
import { ThemeProvider } from '@/components/theme-provider';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
import '@/index.css';
|
||||
@@ -34,4 +35,4 @@ function RootLayout() {
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRootRoute({ component: RootLayout });
|
||||
export const Route = createRootRoute({ component: RootLayout, errorComponent: ({ error }) => <GlobalError error={error} /> });
|
||||
|
||||
Reference in New Issue
Block a user