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 { 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>
|
||||||
|
)
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 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
|
||||||
|
|||||||
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 { 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>,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
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