feat(events): add event join functionality with no kyc
Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
@@ -5,8 +5,15 @@ import { Button } from '../ui/button';
|
||||
import { DialogTrigger } from '../ui/dialog';
|
||||
import { EventGridSkeleton } from './event-grid.skeleton';
|
||||
import { EventGridView } from './event-grid.view';
|
||||
import { EventJoinContainer } from './event-join.dialog.container';
|
||||
import { KycDialogContainer } from './kyc/kyc.dialog.container';
|
||||
|
||||
function JoinButton() {
|
||||
return (
|
||||
<Button className="w-full">加入活动</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function EventGridContainer() {
|
||||
const { data, isLoading } = useGetEvents();
|
||||
|
||||
@@ -29,11 +36,21 @@ export function EventGridContainer() {
|
||||
footer={eventInfo => (eventInfo.isJoined
|
||||
? <Button className="w-full" disabled>已加入</Button>
|
||||
: (
|
||||
<KycDialogContainer eventIdToJoin={eventInfo.eventId}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-full">加入活动</Button>
|
||||
</DialogTrigger>
|
||||
</KycDialogContainer>
|
||||
eventInfo.requireKyc
|
||||
? (
|
||||
<KycDialogContainer event={eventInfo}>
|
||||
<DialogTrigger asChild>
|
||||
<JoinButton />
|
||||
</DialogTrigger>
|
||||
</KycDialogContainer>
|
||||
)
|
||||
: (
|
||||
<EventJoinContainer event={eventInfo}>
|
||||
<DialogTrigger asChild>
|
||||
<JoinButton />
|
||||
</DialogTrigger>
|
||||
</EventJoinContainer>
|
||||
)
|
||||
)
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
25
client/cms/src/components/events/event-join.dialog.view.tsx
Normal file
25
client/cms/src/components/events/event-join.dialog.view.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { EventInfo } from '../types';
|
||||
import type { KycSubmission } from './kyc.types';
|
||||
import { Dialog } from '@radix-ui/react-dialog';
|
||||
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 { createKycStore } from './kyc.state';
|
||||
|
||||
export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin: string; children: React.ReactNode }) {
|
||||
const [store] = useState(() => createKycStore(eventIdToJoin));
|
||||
export function KycDialogContainer({ event, children }: { event: EventInfo; children: React.ReactNode }) {
|
||||
const [store] = useState(() => createKycStore(event.eventId));
|
||||
const isDialogOpen = useStore(store, s => s.isDialogOpen);
|
||||
const setIsDialogOpen = useStore(store, s => s.setIsDialogOpen);
|
||||
const stage = useStore(store, s => s.stage);
|
||||
@@ -43,7 +44,7 @@ export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin:
|
||||
const { data } = await mutateAsync(submission);
|
||||
setKycId(data!.kyc_id!);
|
||||
if (data!.status === 'success') {
|
||||
await joinEvent(eventIdToJoin, data!.kyc_id!, undefined);
|
||||
await joinEvent(event.eventId, data!.kyc_id!, undefined);
|
||||
}
|
||||
else if (data!.status === 'processing') {
|
||||
window.open(data!.redirect_uri, '_blank');
|
||||
@@ -54,7 +55,7 @@ export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin:
|
||||
console.error(e);
|
||||
setStage('failed');
|
||||
}
|
||||
}, [eventIdToJoin, joinEvent, mutateAsync, setKycId, setStage]);
|
||||
}, [event.eventId, joinEvent, mutateAsync, setKycId, setStage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (stage !== 'pending' || !isDialogOpen) {
|
||||
@@ -74,7 +75,7 @@ export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin:
|
||||
const status = data?.data?.status;
|
||||
|
||||
if (status === 'success') {
|
||||
void joinEvent(eventIdToJoin, store.getState().kycId!, controller.signal);
|
||||
void joinEvent(event.eventId, store.getState().kycId!, controller.signal);
|
||||
}
|
||||
else if (status === 'failed') {
|
||||
setStage('failed');
|
||||
@@ -101,7 +102,7 @@ export function KycDialogContainer({ eventIdToJoin, children }: { eventIdToJoin:
|
||||
controller.abort();
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [stage, store, setStage, isDialogOpen, joinEvent, eventIdToJoin]);
|
||||
}, [stage, store, setStage, isDialogOpen, joinEvent, event.eventId]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
||||
8
client/cms/src/hooks/data/useJoinEvent.ts
Normal file
8
client/cms/src/hooks/data/useJoinEvent.ts
Normal 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(),
|
||||
});
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { EventCardSkeleton } from '@/components/events/event-card.skeleton';
|
||||
import { EventCardView } from '@/components/events/event-card.view';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { exampleEvent } from './event.example';
|
||||
|
||||
const meta = {
|
||||
title: 'Events/EventCard',
|
||||
@@ -13,17 +14,7 @@ type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
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'),
|
||||
},
|
||||
eventInfo: exampleEvent,
|
||||
actionFooter: <Button className="w-full">加入活动(示例)</Button>,
|
||||
},
|
||||
};
|
||||
|
||||
13
client/cms/src/stories/events/event.example.ts
Normal file
13
client/cms/src/stories/events/event.example.ts
Normal 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'),
|
||||
};
|
||||
26
client/cms/src/stories/events/join-dialog.stories.tsx
Normal file
26
client/cms/src/stories/events/join-dialog.stories.tsx
Normal 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: () => { },
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user