feat(events): add event join functionality with no kyc

Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
2026-02-07 17:05:54 +08:00
parent c43c37a127
commit eddc23a2e8
8 changed files with 128 additions and 22 deletions

View File

@@ -5,8 +5,15 @@ import { Button } from '../ui/button';
import { DialogTrigger } from '../ui/dialog'; import { DialogTrigger } from '../ui/dialog';
import { EventGridSkeleton } from './event-grid.skeleton'; import { EventGridSkeleton } from './event-grid.skeleton';
import { EventGridView } from './event-grid.view'; import { EventGridView } from './event-grid.view';
import { EventJoinContainer } from './event-join.dialog.container';
import { KycDialogContainer } from './kyc/kyc.dialog.container'; import { KycDialogContainer } from './kyc/kyc.dialog.container';
function JoinButton() {
return (
<Button className="w-full"></Button>
);
}
export function EventGridContainer() { export function EventGridContainer() {
const { data, isLoading } = useGetEvents(); const { data, isLoading } = useGetEvents();
@@ -29,12 +36,22 @@ export function EventGridContainer() {
footer={eventInfo => (eventInfo.isJoined footer={eventInfo => (eventInfo.isJoined
? <Button className="w-full" disabled></Button> ? <Button className="w-full" disabled></Button>
: ( : (
<KycDialogContainer eventIdToJoin={eventInfo.eventId}> eventInfo.requireKyc
? (
<KycDialogContainer event={eventInfo}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="w-full"></Button> <JoinButton />
</DialogTrigger> </DialogTrigger>
</KycDialogContainer> </KycDialogContainer>
) )
: (
<EventJoinContainer event={eventInfo}>
<DialogTrigger asChild>
<JoinButton />
</DialogTrigger>
</EventJoinContainer>
)
)
)} )}
/> />
) )

View File

@@ -0,0 +1,25 @@
import type { EventInfo } from './types';
import { useCallback } from 'react';
import { toast } from 'sonner';
import { useJoinEvent } from '@/hooks/data/useJoinEvent';
import { Dialog } from '../ui/dialog';
import { EventJoinDialogView } from './event-join.dialog.view';
export function EventJoinContainer({ event, children }: { event: EventInfo; children: React.ReactNode }) {
const { mutateAsync } = useJoinEvent();
const join = useCallback(() => {
mutateAsync({ body: { event_id: event.eventId } }).then(() => {
toast('加入活动成功');
}).catch((error) => {
console.error(error);
toast.error('加入活动失败');
});
}, [event.eventId, mutateAsync]);
return (
<Dialog>
{children}
<EventJoinDialogView event={event} onJoinEvent={join} />
</Dialog>
);
}

View File

@@ -0,0 +1,25 @@
import type { EventInfo } from './types';
import { Button } from '../ui/button';
import { DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog';
export function EventJoinDialogView({ event, onJoinEvent }: { event: EventInfo; onJoinEvent: () => void }) {
return (
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
{' '}
{event.eventName}
?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline"></Button>
</DialogClose>
<Button onClick={onJoinEvent}></Button>
</DialogFooter>
</DialogContent>
);
}

View File

@@ -1,3 +1,4 @@
import type { EventInfo } from '../types';
import type { KycSubmission } from './kyc.types'; import type { KycSubmission } from './kyc.types';
import { Dialog } from '@radix-ui/react-dialog'; import { Dialog } from '@radix-ui/react-dialog';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
@@ -13,8 +14,8 @@ import { KycPromptDialogView } from './kyc-prompt.dialog.view';
import { KycSuccessDialogView } from './kyc-success.dialog.view'; import { KycSuccessDialogView } from './kyc-success.dialog.view';
import { createKycStore } from './kyc.state'; import { createKycStore } from './kyc.state';
export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin: string; children: React.ReactNode }) { export function KycDialogContainer({ event, children }: { event: EventInfo; children: React.ReactNode }) {
const [store] = useState(() => createKycStore(eventIdToJoin)); const [store] = useState(() => createKycStore(event.eventId));
const isDialogOpen = useStore(store, s => s.isDialogOpen); const isDialogOpen = useStore(store, s => s.isDialogOpen);
const setIsDialogOpen = useStore(store, s => s.setIsDialogOpen); const setIsDialogOpen = useStore(store, s => s.setIsDialogOpen);
const stage = useStore(store, s => s.stage); const stage = useStore(store, s => s.stage);
@@ -43,7 +44,7 @@ export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin:
const { data } = await mutateAsync(submission); const { data } = await mutateAsync(submission);
setKycId(data!.kyc_id!); setKycId(data!.kyc_id!);
if (data!.status === 'success') { if (data!.status === 'success') {
await joinEvent(eventIdToJoin, data!.kyc_id!, undefined); await joinEvent(event.eventId, data!.kyc_id!, undefined);
} }
else if (data!.status === 'processing') { else if (data!.status === 'processing') {
window.open(data!.redirect_uri, '_blank'); window.open(data!.redirect_uri, '_blank');
@@ -54,7 +55,7 @@ export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin:
console.error(e); console.error(e);
setStage('failed'); setStage('failed');
} }
}, [eventIdToJoin, joinEvent, mutateAsync, setKycId, setStage]); }, [event.eventId, joinEvent, mutateAsync, setKycId, setStage]);
useEffect(() => { useEffect(() => {
if (stage !== 'pending' || !isDialogOpen) { if (stage !== 'pending' || !isDialogOpen) {
@@ -74,7 +75,7 @@ export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin:
const status = data?.data?.status; const status = data?.data?.status;
if (status === 'success') { if (status === 'success') {
void joinEvent(eventIdToJoin, store.getState().kycId!, controller.signal); void joinEvent(event.eventId, store.getState().kycId!, controller.signal);
} }
else if (status === 'failed') { else if (status === 'failed') {
setStage('failed'); setStage('failed');
@@ -101,7 +102,7 @@ export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin:
controller.abort(); controller.abort();
clearTimeout(timer); clearTimeout(timer);
}; };
}, [stage, store, setStage, isDialogOpen, joinEvent, eventIdToJoin]); }, [stage, store, setStage, isDialogOpen, joinEvent, event.eventId]);
return ( return (
<Dialog <Dialog

View File

@@ -0,0 +1,8 @@
import { useMutation } from '@tanstack/react-query';
import { postEventJoinMutation } from '@/client/@tanstack/react-query.gen';
export function useJoinEvent() {
return useMutation({
...postEventJoinMutation(),
});
}

View File

@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
import { EventCardSkeleton } from '@/components/events/event-card.skeleton'; import { EventCardSkeleton } from '@/components/events/event-card.skeleton';
import { EventCardView } from '@/components/events/event-card.view'; import { EventCardView } from '@/components/events/event-card.view';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { exampleEvent } from './event.example';
const meta = { const meta = {
title: 'Events/EventCard', title: 'Events/EventCard',
@@ -13,17 +14,7 @@ type Story = StoryObj<typeof meta>;
export const Primary: Story = { export const Primary: Story = {
args: { args: {
eventInfo: { eventInfo: exampleEvent,
eventId: '1',
type: 'official',
requireKyc: true,
isJoined: false,
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-watersplash.png?raw=true',
eventName: 'Nix CN Conference 26.05',
description: 'Event Description',
startTime: new Date('2026-06-13T04:00:00.000Z'),
endTime: new Date('2026-06-14T04:00:00.000Z'),
},
actionFooter: <Button className="w-full"></Button>, actionFooter: <Button className="w-full"></Button>,
}, },
}; };

View File

@@ -0,0 +1,13 @@
import type { EventInfo } from '@/components/events/types';
export const exampleEvent: EventInfo = {
eventId: '1',
type: 'official',
requireKyc: true,
isJoined: false,
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-watersplash.png?raw=true',
eventName: 'Nix CN Conference 26.05',
description: 'Event Description',
startTime: new Date('2026-06-13T04:00:00.000Z'),
endTime: new Date('2026-06-14T04:00:00.000Z'),
};

View File

@@ -0,0 +1,26 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { EventJoinDialogView } from '@/components/events/event-join.dialog.view';
import { Dialog } from '@/components/ui/dialog';
import { exampleEvent } from './event.example';
const meta = {
title: 'Events/JoinDialog',
component: EventJoinDialogView,
decorators: [
Story => (
<Dialog open={true}>
<Story />
</Dialog>
),
],
} satisfies Meta<typeof EventJoinDialogView>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Confirm: Story = {
args: {
event: exampleEvent,
onJoinEvent: () => { },
},
};