refactor: extract empty state and update basepath to /app/
All checks were successful
Client CMS Check Build (NixCN CMS) TeamCity build finished
Backend Check Build (NixCN CMS) TeamCity build finished

Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
2026-02-06 20:41:50 +08:00
parent 0fc57ac637
commit 6411268090
7 changed files with 60 additions and 45 deletions

View File

@@ -1,10 +1,8 @@
import type { EventInfo } from './types'; import type { EventInfo } from './types';
import { FileQuestionMark } from 'lucide-react';
import PlaceholderImage from '@/assets/event-placeholder.png'; import PlaceholderImage from '@/assets/event-placeholder.png';
import { useGetEvents } from '@/hooks/data/useGetEvents'; import { useGetEvents } from '@/hooks/data/useGetEvents';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { DialogTrigger } from '../ui/dialog'; import { DialogTrigger } from '../ui/dialog';
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '../ui/empty';
import { EventGridView } from './event-grid.view'; import { EventGridView } from './event-grid.view';
import { KycDialogContainer } from './kyc/kyc.dialog.container'; import { KycDialogContainer } from './kyc/kyc.dialog.container';
@@ -25,35 +23,18 @@ export function EventGridContainer() {
} satisfies EventInfo)); } satisfies EventInfo));
return ( return (
<> <EventGridView
{allEvents.length > 0 && ( events={allEvents}
<EventGridView footer={eventInfo => (eventInfo.isJoined
events={allEvents} ? <Button className="w-full" disabled></Button>
assembleFooter={eventInfo => (eventInfo.isJoined : (
? <Button className="w-full" disabled></Button> <KycDialogContainer eventIdToJoin={eventInfo.eventId}>
: ( <DialogTrigger asChild>
<KycDialogContainer eventIdToJoin={eventInfo.eventId}> <Button className="w-full"></Button>
<DialogTrigger asChild> </DialogTrigger>
<Button className="w-full"></Button> </KycDialogContainer>
</DialogTrigger>
</KycDialogContainer>
)
)}
/>
)}
{
allEvents.length === 0 && (
<Empty className="h-full">
<EmptyHeader>
<EmptyMedia variant="icon">
<FileQuestionMark />
</EmptyMedia>
<EmptyTitle></EmptyTitle>
<EmptyDescription> </EmptyDescription>
</EmptyHeader>
</Empty>
) )
} )}
</> />
); );
} }

View File

@@ -0,0 +1,16 @@
import { FileQuestionMark } from 'lucide-react';
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '../ui/empty';
export function EventGridEmpty() {
return (
<Empty className="h-full">
<EmptyHeader>
<EmptyMedia variant="icon">
<FileQuestionMark />
</EmptyMedia>
<EmptyTitle></EmptyTitle>
<EmptyDescription> </EmptyDescription>
</EmptyHeader>
</Empty>
);
}

View File

@@ -1,12 +1,18 @@
import type { EventInfo } from './types'; import type { EventInfo } from './types';
import { EventCardView } from './event-card.view'; import { EventCardView } from './event-card.view';
import { EventGridEmpty } from './event-grid.empty';
export function EventGridView({ events, assembleFooter }: { events: EventInfo[]; assembleFooter: (event: EventInfo) => React.ReactNode }) { export function EventGridView({ events, footer }: { events: EventInfo[]; footer: (event: EventInfo) => React.ReactNode }) {
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4"> <>
{events.map(event => ( {events.length > 0 && (
<EventCardView key={event.eventId} eventInfo={event} actionFooter={assembleFooter(event)} /> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 h-full">
))} {events.map(event => (
</div> <EventCardView key={event.eventId} eventInfo={event} actionFooter={footer(event)} />
))}
</div>
)}
{events.length === 0 && <EventGridEmpty />}
</>
); );
} }

View File

@@ -11,7 +11,7 @@ import {
export function configInternalApiClient() { export function configInternalApiClient() {
client.setConfig({ client.setConfig({
baseUrl: '/api/v1/', baseUrl: '/app/api/v1/',
headers: { headers: {
'X-Api-Version': 'latest', 'X-Api-Version': 'latest',
}, },

View File

@@ -3,7 +3,7 @@ import { createRouter } from '@tanstack/react-router';
import { routeTree } from '../routeTree.gen'; import { routeTree } from '../routeTree.gen';
// Create a new router instance // Create a new router instance
export const router = createRouter({ routeTree }); export const router = createRouter({ routeTree, basepath: '/app/' });
// Register the router instance for type safety // Register the router instance for type safety
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {

View File

@@ -60,14 +60,25 @@ export const Primary: Story = {
endTime: new Date('2026-06-14T04:00:00.000Z'), endTime: new Date('2026-06-14T04:00:00.000Z'),
}, },
], ],
assembleFooter: () => <Button className="w-full"></Button>, footer: () => <Button className="w-full"></Button>,
}, },
}; };
export const Skeleton: Story = { export const Empty: Story = {
decorators: [Story => <div className="h-screen">{Story()}</div>],
args: {
events: [],
footer: () => <Button className="w-full"></Button>,
},
parameters: {
layout: 'fullscreen',
},
};
export const Loading: Story = {
render: () => <EventGridSkeleton />, render: () => <EventGridSkeleton />,
args: { args: {
events: [], events: [],
assembleFooter: () => <UiSkeleton className="w-full" />, footer: () => <UiSkeleton className="w-full" />,
}, },
}; };

View File

@@ -15,6 +15,7 @@ const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(file
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon // More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineConfig({ export default defineConfig({
base: '/app/',
plugins: [tanstackRouter({ plugins: [tanstackRouter({
target: 'react', target: 'react',
autoCodeSplitting: true, autoCodeSplitting: true,
@@ -26,7 +27,7 @@ export default defineConfig({
}, },
server: { server: {
proxy: { proxy: {
'/api': 'http://10.0.0.10:8000', '/app/api': 'http://10.0.0.10:8000',
}, },
host: '0.0.0.0', host: '0.0.0.0',
port: 5173, port: 5173,
@@ -36,8 +37,8 @@ export default defineConfig({
projects: [{ projects: [{
extends: true, extends: true,
plugins: [ plugins: [
// The plugin will run tests for the stories defined in your Storybook config // The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
storybookTest({ storybookTest({
configDir: path.join(dirname, '.storybook'), configDir: path.join(dirname, '.storybook'),
}), }),