feat(client): add KYC for event joining
Some checks failed
Client CMS Check Build (NixCN CMS) TeamCity build failed
Backend Check Build (NixCN CMS) TeamCity build finished

Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
2026-02-05 19:12:57 +08:00
parent 06f86cb8e3
commit f12e7ac3c1
31 changed files with 1760 additions and 187 deletions

View File

@@ -5,7 +5,11 @@ export default defineConfig({
output: 'src/client', output: 'src/client',
plugins: [ plugins: [
'@hey-api/typescript', '@hey-api/typescript',
'@tanstack/react-query', {
name: '@tanstack/react-query',
infiniteQueryOptions: true,
infiniteQueryKeys: true,
},
'zod', 'zod',
{ {
name: '@hey-api/transformers', name: '@hey-api/transformers',

View File

@@ -59,18 +59,20 @@
"lucide-react": "^0.562.0", "lucide-react": "^0.562.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"radix-ui": "^1.4.3",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-error-boundary": "^6.1.0", "react-error-boundary": "^6.1.0",
"react-hook-form": "^7.69.0", "react-hook-form": "^7.69.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-spinners": "^0.17.0",
"recharts": "2.15.4", "recharts": "2.15.4",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^4.2.1", "zod": "^3.25.76",
"zustand": "^5.0.9" "zustand": "^5.0.9"
}, },
"devDependencies": { "devDependencies": {
@@ -79,6 +81,7 @@
"@eslint-react/eslint-plugin": "^2.3.13", "@eslint-react/eslint-plugin": "^2.3.13",
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@hey-api/openapi-ts": "0.91.0", "@hey-api/openapi-ts": "0.91.0",
"@redux-devtools/extension": "^3.3.0",
"@storybook/addon-a11y": "^10.2.3", "@storybook/addon-a11y": "^10.2.3",
"@storybook/addon-docs": "^10.2.3", "@storybook/addon-docs": "^10.2.3",
"@storybook/addon-onboarding": "^10.2.3", "@storybook/addon-onboarding": "^10.2.3",

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@
import { type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/react-query'; import { type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/react-query';
import { client } from '../client.gen'; import { client } from '../client.gen';
import { getAuthRedirect, getEventCheckin, getEventCheckinQuery, getEventInfo, getEventList, getUserInfo, getUserInfoByUserId, getUserList, type Options, patchUserUpdate, postAuthExchange, postAuthMagic, postAuthRefresh, postAuthToken, postEventCheckinSubmit, postEventJoin, postKycQuery, postKycSession } from '../sdk.gen'; import { getAuthRedirect, getEventAttendance, getEventCheckin, getEventCheckinQuery, getEventInfo, getEventList, getUserInfo, getUserInfoByUserId, getUserList, type Options, patchUserUpdate, postAuthExchange, postAuthMagic, postAuthRefresh, postAuthToken, postEventCheckinSubmit, postEventJoin, postKycQuery, postKycSession } from '../sdk.gen';
import type { GetAuthRedirectData, GetAuthRedirectError, GetEventCheckinData, GetEventCheckinError, GetEventCheckinQueryData, GetEventCheckinQueryError, GetEventCheckinQueryResponse, GetEventCheckinResponse, GetEventInfoData, GetEventInfoError, GetEventInfoResponse, GetEventListData, GetEventListError, GetEventListResponse, GetUserInfoByUserIdData, GetUserInfoByUserIdError, GetUserInfoByUserIdResponse, GetUserInfoData, GetUserInfoError, GetUserInfoResponse, GetUserListData, GetUserListError, GetUserListResponse, PatchUserUpdateData, PatchUserUpdateError, PatchUserUpdateResponse, PostAuthExchangeData, PostAuthExchangeError, PostAuthExchangeResponse, PostAuthMagicData, PostAuthMagicError, PostAuthMagicResponse, PostAuthRefreshData, PostAuthRefreshError, PostAuthRefreshResponse, PostAuthTokenData, PostAuthTokenError, PostAuthTokenResponse, PostEventCheckinSubmitData, PostEventCheckinSubmitError, PostEventCheckinSubmitResponse, PostEventJoinData, PostEventJoinError, PostEventJoinResponse, PostKycQueryData, PostKycQueryError, PostKycQueryResponse, PostKycSessionData, PostKycSessionError, PostKycSessionResponse } from '../types.gen'; import type { GetAuthRedirectData, GetAuthRedirectError, GetEventAttendanceData, GetEventAttendanceError, GetEventAttendanceResponse, GetEventCheckinData, GetEventCheckinError, GetEventCheckinQueryData, GetEventCheckinQueryError, GetEventCheckinQueryResponse, GetEventCheckinResponse, GetEventInfoData, GetEventInfoError, GetEventInfoResponse, GetEventListData, GetEventListError, GetEventListResponse, GetUserInfoByUserIdData, GetUserInfoByUserIdError, GetUserInfoByUserIdResponse, GetUserInfoData, GetUserInfoError, GetUserInfoResponse, GetUserListData, GetUserListError, GetUserListResponse, PatchUserUpdateData, PatchUserUpdateError, PatchUserUpdateResponse, PostAuthExchangeData, PostAuthExchangeError, PostAuthExchangeResponse, PostAuthMagicData, PostAuthMagicError, PostAuthMagicResponse, PostAuthRefreshData, PostAuthRefreshError, PostAuthRefreshResponse, PostAuthTokenData, PostAuthTokenError, PostAuthTokenResponse, PostEventCheckinSubmitData, PostEventCheckinSubmitError, PostEventCheckinSubmitResponse, PostEventJoinData, PostEventJoinError, PostEventJoinResponse, PostKycQueryData, PostKycQueryError, PostKycQueryResponse, PostKycSessionData, PostKycSessionError, PostKycSessionResponse } from '../types.gen';
/** /**
* Exchange Auth Code * Exchange Auth Code
@@ -135,6 +135,26 @@ export const postAuthTokenMutation = (options?: Partial<Options<PostAuthTokenDat
return mutationOptions; return mutationOptions;
}; };
export const getEventAttendanceQueryKey = (options: Options<GetEventAttendanceData>) => createQueryKey('getEventAttendance', options);
/**
* Get Attendance List
*
* Retrieves the list of attendees, including user info and decrypted KYC data for a specified event.
*/
export const getEventAttendanceOptions = (options: Options<GetEventAttendanceData>) => queryOptions<GetEventAttendanceResponse, GetEventAttendanceError, GetEventAttendanceResponse, ReturnType<typeof getEventAttendanceQueryKey>>({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getEventAttendance({
...options,
...queryKey[0],
signal,
throwOnError: true
});
return data;
},
queryKey: getEventAttendanceQueryKey(options)
});
export const getEventCheckinQueryKey = (options: Options<GetEventCheckinData>) => createQueryKey('getEventCheckin', options); export const getEventCheckinQueryKey = (options: Options<GetEventCheckinData>) => createQueryKey('getEventCheckin', options);
/** /**
@@ -289,7 +309,7 @@ export const getEventListInfiniteQueryKey = (options: Options<GetEventListData>)
* *
* Fetches a list of events with support for pagination via limit and offset. Data is retrieved directly from the database for consistency. * Fetches a list of events with support for pagination via limit and offset. Data is retrieved directly from the database for consistency.
*/ */
export const getEventListInfiniteOptions = (options: Options<GetEventListData>) => infiniteQueryOptions<GetEventListResponse, GetEventListError, InfiniteData<GetEventListResponse>, QueryKey<Options<GetEventListData>>, string | Pick<QueryKey<Options<GetEventListData>>[0], 'body' | 'headers' | 'path' | 'query'>>( export const getEventListInfiniteOptions = (options: Options<GetEventListData>) => infiniteQueryOptions<GetEventListResponse, GetEventListError, InfiniteData<GetEventListResponse>, QueryKey<Options<GetEventListData>>, number | Pick<QueryKey<Options<GetEventListData>>[0], 'body' | 'headers' | 'path' | 'query'>>(
// @ts-ignore // @ts-ignore
{ {
queryFn: async ({ pageParam, queryKey, signal }) => { queryFn: async ({ pageParam, queryKey, signal }) => {
@@ -349,14 +369,14 @@ export const postKycSessionMutation = (options?: Partial<Options<PostKycSessionD
return mutationOptions; return mutationOptions;
}; };
export const getUserInfoQueryKey = (options?: Options<GetUserInfoData>) => createQueryKey('getUserInfo', options); export const getUserInfoQueryKey = (options: Options<GetUserInfoData>) => createQueryKey('getUserInfo', options);
/** /**
* Get My User Information * Get My User Information
* *
* Fetches the complete profile data for the user associated with the provided session/token. * Fetches the complete profile data for the user associated with the provided session/token.
*/ */
export const getUserInfoOptions = (options?: Options<GetUserInfoData>) => queryOptions<GetUserInfoResponse, GetUserInfoError, GetUserInfoResponse, ReturnType<typeof getUserInfoQueryKey>>({ export const getUserInfoOptions = (options: Options<GetUserInfoData>) => queryOptions<GetUserInfoResponse, GetUserInfoError, GetUserInfoResponse, ReturnType<typeof getUserInfoQueryKey>>({
queryFn: async ({ queryKey, signal }) => { queryFn: async ({ queryKey, signal }) => {
const { data } = await getUserInfo({ const { data } = await getUserInfo({
...options, ...options,

View File

@@ -1,4 +1,4 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
export { getAuthRedirect, getEventCheckin, getEventCheckinQuery, getEventInfo, getEventList, getUserInfo, getUserInfoByUserId, getUserList, type Options, patchUserUpdate, postAuthExchange, postAuthMagic, postAuthRefresh, postAuthToken, postEventCheckinSubmit, postEventJoin, postKycQuery, postKycSession } from './sdk.gen'; export { getAuthRedirect, getEventAttendance, getEventCheckin, getEventCheckinQuery, getEventInfo, getEventList, getUserInfo, getUserInfoByUserId, getUserList, type Options, patchUserUpdate, postAuthExchange, postAuthMagic, postAuthRefresh, postAuthToken, postEventCheckinSubmit, postEventJoin, postKycQuery, postKycSession } from './sdk.gen';
export type { ClientOptions, DataEventIndexDoc, DataUserIndexDoc, GetAuthRedirectData, GetAuthRedirectError, GetAuthRedirectErrors, GetEventCheckinData, GetEventCheckinError, GetEventCheckinErrors, GetEventCheckinQueryData, GetEventCheckinQueryError, GetEventCheckinQueryErrors, GetEventCheckinQueryResponse, GetEventCheckinQueryResponses, GetEventCheckinResponse, GetEventCheckinResponses, GetEventInfoData, GetEventInfoError, GetEventInfoErrors, GetEventInfoResponse, GetEventInfoResponses, GetEventListData, GetEventListError, GetEventListErrors, GetEventListResponse, GetEventListResponses, GetUserInfoByUserIdData, GetUserInfoByUserIdError, GetUserInfoByUserIdErrors, GetUserInfoByUserIdResponse, GetUserInfoByUserIdResponses, GetUserInfoData, GetUserInfoError, GetUserInfoErrors, GetUserInfoResponse, GetUserInfoResponses, GetUserListData, GetUserListError, GetUserListErrors, GetUserListResponse, GetUserListResponses, PatchUserUpdateData, PatchUserUpdateError, PatchUserUpdateErrors, PatchUserUpdateResponse, PatchUserUpdateResponses, PostAuthExchangeData, PostAuthExchangeError, PostAuthExchangeErrors, PostAuthExchangeResponse, PostAuthExchangeResponses, PostAuthMagicData, PostAuthMagicError, PostAuthMagicErrors, PostAuthMagicResponse, PostAuthMagicResponses, PostAuthRefreshData, PostAuthRefreshError, PostAuthRefreshErrors, PostAuthRefreshResponse, PostAuthRefreshResponses, PostAuthTokenData, PostAuthTokenError, PostAuthTokenErrors, PostAuthTokenResponse, PostAuthTokenResponses, PostEventCheckinSubmitData, PostEventCheckinSubmitError, PostEventCheckinSubmitErrors, PostEventCheckinSubmitResponse, PostEventCheckinSubmitResponses, PostEventJoinData, PostEventJoinError, PostEventJoinErrors, PostEventJoinResponse, PostEventJoinResponses, PostKycQueryData, PostKycQueryError, PostKycQueryErrors, PostKycQueryResponse, PostKycQueryResponses, PostKycSessionData, PostKycSessionError, PostKycSessionErrors, PostKycSessionResponse, PostKycSessionResponses, ServiceAuthExchangeData, ServiceAuthExchangeResponse, ServiceAuthMagicData, ServiceAuthMagicResponse, ServiceAuthRefreshData, ServiceAuthTokenData, ServiceAuthTokenResponse, ServiceEventCheckinQueryResponse, ServiceEventCheckinResponse, ServiceEventCheckinSubmitData, ServiceEventEventJoinData, ServiceKycKycQueryData, ServiceKycKycQueryResponse, ServiceKycKycSessionData, ServiceKycKycSessionResponse, ServiceUserUserInfoData, UtilsRespStatus } from './types.gen'; export type { ClientOptions, DataEventIndexDoc, DataUserIndexDoc, GetAuthRedirectData, GetAuthRedirectError, GetAuthRedirectErrors, GetEventAttendanceData, GetEventAttendanceError, GetEventAttendanceErrors, GetEventAttendanceResponse, GetEventAttendanceResponses, GetEventCheckinData, GetEventCheckinError, GetEventCheckinErrors, GetEventCheckinQueryData, GetEventCheckinQueryError, GetEventCheckinQueryErrors, GetEventCheckinQueryResponse, GetEventCheckinQueryResponses, GetEventCheckinResponse, GetEventCheckinResponses, GetEventInfoData, GetEventInfoError, GetEventInfoErrors, GetEventInfoResponse, GetEventInfoResponses, GetEventListData, GetEventListError, GetEventListErrors, GetEventListResponse, GetEventListResponses, GetUserInfoByUserIdData, GetUserInfoByUserIdError, GetUserInfoByUserIdErrors, GetUserInfoByUserIdResponse, GetUserInfoByUserIdResponses, GetUserInfoData, GetUserInfoError, GetUserInfoErrors, GetUserInfoResponse, GetUserInfoResponses, GetUserListData, GetUserListError, GetUserListErrors, GetUserListResponse, GetUserListResponses, PatchUserUpdateData, PatchUserUpdateError, PatchUserUpdateErrors, PatchUserUpdateResponse, PatchUserUpdateResponses, PostAuthExchangeData, PostAuthExchangeError, PostAuthExchangeErrors, PostAuthExchangeResponse, PostAuthExchangeResponses, PostAuthMagicData, PostAuthMagicError, PostAuthMagicErrors, PostAuthMagicResponse, PostAuthMagicResponses, PostAuthRefreshData, PostAuthRefreshError, PostAuthRefreshErrors, PostAuthRefreshResponse, PostAuthRefreshResponses, PostAuthTokenData, PostAuthTokenError, PostAuthTokenErrors, PostAuthTokenResponse, PostAuthTokenResponses, PostEventCheckinSubmitData, PostEventCheckinSubmitError, PostEventCheckinSubmitErrors, PostEventCheckinSubmitResponse, PostEventCheckinSubmitResponses, PostEventJoinData, PostEventJoinError, PostEventJoinErrors, PostEventJoinResponse, PostEventJoinResponses, PostKycQueryData, PostKycQueryError, PostKycQueryErrors, PostKycQueryResponse, PostKycQueryResponses, PostKycSessionData, PostKycSessionError, PostKycSessionErrors, PostKycSessionResponse, PostKycSessionResponses, ServiceAuthExchangeData, ServiceAuthExchangeResponse, ServiceAuthMagicData, ServiceAuthMagicResponse, ServiceAuthRefreshData, ServiceAuthTokenData, ServiceAuthTokenResponse, ServiceEventAttendanceListResponse, ServiceEventCheckinQueryResponse, ServiceEventCheckinResponse, ServiceEventCheckinSubmitData, ServiceEventEventJoinData, ServiceKycKycQueryData, ServiceKycKycQueryResponse, ServiceKycKycSessionData, ServiceKycKycSessionResponse, ServiceUserUserInfoData, UtilsRespStatus } from './types.gen';

View File

@@ -2,7 +2,7 @@
import type { Client, Options as Options2, TDataShape } from './client'; import type { Client, Options as Options2, TDataShape } from './client';
import { client } from './client.gen'; import { client } from './client.gen';
import type { GetAuthRedirectData, GetAuthRedirectErrors, GetEventCheckinData, GetEventCheckinErrors, GetEventCheckinQueryData, GetEventCheckinQueryErrors, GetEventCheckinQueryResponses, GetEventCheckinResponses, GetEventInfoData, GetEventInfoErrors, GetEventInfoResponses, GetEventListData, GetEventListErrors, GetEventListResponses, GetUserInfoByUserIdData, GetUserInfoByUserIdErrors, GetUserInfoByUserIdResponses, GetUserInfoData, GetUserInfoErrors, GetUserInfoResponses, GetUserListData, GetUserListErrors, GetUserListResponses, PatchUserUpdateData, PatchUserUpdateErrors, PatchUserUpdateResponses, PostAuthExchangeData, PostAuthExchangeErrors, PostAuthExchangeResponses, PostAuthMagicData, PostAuthMagicErrors, PostAuthMagicResponses, PostAuthRefreshData, PostAuthRefreshErrors, PostAuthRefreshResponses, PostAuthTokenData, PostAuthTokenErrors, PostAuthTokenResponses, PostEventCheckinSubmitData, PostEventCheckinSubmitErrors, PostEventCheckinSubmitResponses, PostEventJoinData, PostEventJoinErrors, PostEventJoinResponses, PostKycQueryData, PostKycQueryErrors, PostKycQueryResponses, PostKycSessionData, PostKycSessionErrors, PostKycSessionResponses } from './types.gen'; import type { GetAuthRedirectData, GetAuthRedirectErrors, GetEventAttendanceData, GetEventAttendanceErrors, GetEventAttendanceResponses, GetEventCheckinData, GetEventCheckinErrors, GetEventCheckinQueryData, GetEventCheckinQueryErrors, GetEventCheckinQueryResponses, GetEventCheckinResponses, GetEventInfoData, GetEventInfoErrors, GetEventInfoResponses, GetEventListData, GetEventListErrors, GetEventListResponses, GetUserInfoByUserIdData, GetUserInfoByUserIdErrors, GetUserInfoByUserIdResponses, GetUserInfoData, GetUserInfoErrors, GetUserInfoResponses, GetUserListData, GetUserListErrors, GetUserListResponses, PatchUserUpdateData, PatchUserUpdateErrors, PatchUserUpdateResponses, PostAuthExchangeData, PostAuthExchangeErrors, PostAuthExchangeResponses, PostAuthMagicData, PostAuthMagicErrors, PostAuthMagicResponses, PostAuthRefreshData, PostAuthRefreshErrors, PostAuthRefreshResponses, PostAuthTokenData, PostAuthTokenErrors, PostAuthTokenResponses, PostEventCheckinSubmitData, PostEventCheckinSubmitErrors, PostEventCheckinSubmitResponses, PostEventJoinData, PostEventJoinErrors, PostEventJoinResponses, PostKycQueryData, PostKycQueryErrors, PostKycQueryResponses, PostKycSessionData, PostKycSessionErrors, PostKycSessionResponses } from './types.gen';
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & { export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
/** /**
@@ -81,6 +81,13 @@ export const postAuthToken = <ThrowOnError extends boolean = false>(options: Opt
} }
}); });
/**
* Get Attendance List
*
* Retrieves the list of attendees, including user info and decrypted KYC data for a specified event.
*/
export const getEventAttendance = <ThrowOnError extends boolean = false>(options: Options<GetEventAttendanceData, ThrowOnError>) => (options.client ?? client).get<GetEventAttendanceResponses, GetEventAttendanceErrors, ThrowOnError>({ url: '/event/attendance', ...options });
/** /**
* Generate Check-in Code * Generate Check-in Code
* *
@@ -170,7 +177,7 @@ export const postKycSession = <ThrowOnError extends boolean = false>(options: Op
* *
* Fetches the complete profile data for the user associated with the provided session/token. * Fetches the complete profile data for the user associated with the provided session/token.
*/ */
export const getUserInfo = <ThrowOnError extends boolean = false>(options?: Options<GetUserInfoData, ThrowOnError>) => (options?.client ?? client).get<GetUserInfoResponses, GetUserInfoErrors, ThrowOnError>({ url: '/user/info', ...options }); export const getUserInfo = <ThrowOnError extends boolean = false>(options: Options<GetUserInfoData, ThrowOnError>) => (options.client ?? client).get<GetUserInfoResponses, GetUserInfoErrors, ThrowOnError>({ url: '/user/info', ...options });
/** /**
* Get Other User Information * Get Other User Information

View File

@@ -5,9 +5,13 @@ export type ClientOptions = {
}; };
export type DataEventIndexDoc = { export type DataEventIndexDoc = {
checkin_count?: number;
description?: string; description?: string;
enable_kyc?: boolean;
end_time?: string; end_time?: string;
event_id?: string; event_id?: string;
is_joined?: boolean;
join_count?: number;
name?: string; name?: string;
start_time?: string; start_time?: string;
thumbnail?: string; thumbnail?: string;
@@ -60,6 +64,13 @@ export type ServiceAuthTokenResponse = {
refresh_token?: string; refresh_token?: string;
}; };
export type ServiceEventAttendanceListResponse = {
attendance_id?: string;
kyc_info?: unknown;
kyc_type?: string;
user_info?: ServiceUserUserInfoData;
};
export type ServiceEventCheckinQueryResponse = { export type ServiceEventCheckinQueryResponse = {
checkin_at?: string; checkin_at?: string;
}; };
@@ -132,6 +143,12 @@ export type PostAuthExchangeData = {
* Exchange Request Credentials * Exchange Request Credentials
*/ */
body: ServiceAuthExchangeData; body: ServiceAuthExchangeData;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/auth/exchange'; url: '/auth/exchange';
@@ -182,6 +199,12 @@ export type PostAuthMagicData = {
* Magic Link Request Data * Magic Link Request Data
*/ */
body: ServiceAuthMagicData; body: ServiceAuthMagicData;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/auth/magic'; url: '/auth/magic';
@@ -285,6 +308,12 @@ export type PostAuthRefreshData = {
* Refresh Token Body * Refresh Token Body
*/ */
body: ServiceAuthRefreshData; body: ServiceAuthRefreshData;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/auth/refresh'; url: '/auth/refresh';
@@ -335,6 +364,12 @@ export type PostAuthTokenData = {
* Token Request Body * Token Request Body
*/ */
body: ServiceAuthTokenData; body: ServiceAuthTokenData;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/auth/token'; url: '/auth/token';
@@ -380,8 +415,72 @@ export type PostAuthTokenResponses = {
export type PostAuthTokenResponse = PostAuthTokenResponses[keyof PostAuthTokenResponses]; export type PostAuthTokenResponse = PostAuthTokenResponses[keyof PostAuthTokenResponses];
export type GetEventAttendanceData = {
body?: never;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never;
query: {
/**
* Event UUID
*/
event_id: string;
};
url: '/event/attendance';
};
export type GetEventAttendanceErrors = {
/**
* Invalid Input
*/
400: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Unauthorized
*/
401: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Internal Server Error
*/
500: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
};
export type GetEventAttendanceError = GetEventAttendanceErrors[keyof GetEventAttendanceErrors];
export type GetEventAttendanceResponses = {
/**
* Successful retrieval
*/
200: UtilsRespStatus & {
data?: Array<ServiceEventAttendanceListResponse>;
};
};
export type GetEventAttendanceResponse = GetEventAttendanceResponses[keyof GetEventAttendanceResponses];
export type GetEventCheckinData = { export type GetEventCheckinData = {
body?: never; body?: never;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query: { query: {
/** /**
@@ -514,6 +613,12 @@ export type PostEventCheckinSubmitResponse = PostEventCheckinSubmitResponses[key
export type GetEventInfoData = { export type GetEventInfoData = {
body?: never; body?: never;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query: { query: {
/** /**
@@ -577,6 +682,12 @@ export type PostEventJoinData = {
* Event Join Details (UserId and EventId are required) * Event Join Details (UserId and EventId are required)
*/ */
body: ServiceEventEventJoinData; body: ServiceEventEventJoinData;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/event/join'; url: '/event/join';
@@ -600,7 +711,7 @@ export type PostEventJoinErrors = {
}; };
}; };
/** /**
* Unauthorized / Missing User ID * Unauthorized / Missing User ID / Event Limit Exceeded
*/ */
403: UtilsRespStatus & { 403: UtilsRespStatus & {
data?: { data?: {
@@ -634,16 +745,22 @@ export type PostEventJoinResponse = PostEventJoinResponses[keyof PostEventJoinRe
export type GetEventListData = { export type GetEventListData = {
body?: never; body?: never;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query: { query?: {
/** /**
* Maximum number of events to return (default 20) * Maximum number of events to return (default 20)
*/ */
limit?: string; limit?: number;
/** /**
* Number of events to skip * Number of events to skip
*/ */
offset: string; offset?: number;
}; };
url: '/event/list'; url: '/event/list';
}; };
@@ -693,6 +810,12 @@ export type PostKycQueryData = {
* KYC query data (KycId) * KYC query data (KycId)
*/ */
body: ServiceKycKycQueryData; body: ServiceKycKycQueryData;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/kyc/query'; url: '/kyc/query';
@@ -743,6 +866,12 @@ export type PostKycSessionData = {
* KYC session data (Type and Base64 Identity) * KYC session data (Type and Base64 Identity)
*/ */
body: ServiceKycKycSessionData; body: ServiceKycKycSessionData;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/kyc/session'; url: '/kyc/session';
@@ -790,6 +919,12 @@ export type PostKycSessionResponse = PostKycSessionResponses[keyof PostKycSessio
export type GetUserInfoData = { export type GetUserInfoData = {
body?: never; body?: never;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/user/info'; url: '/user/info';
@@ -837,6 +972,12 @@ export type GetUserInfoResponse = GetUserInfoResponses[keyof GetUserInfoResponse
export type GetUserInfoByUserIdData = { export type GetUserInfoByUserIdData = {
body?: never; body?: never;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path: { path: {
/** /**
* Other user id * Other user id
@@ -897,6 +1038,12 @@ export type GetUserInfoByUserIdResponse = GetUserInfoByUserIdResponses[keyof Get
export type GetUserListData = { export type GetUserListData = {
body?: never; body?: never;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query: { query: {
/** /**
@@ -956,6 +1103,12 @@ export type PatchUserUpdateData = {
* Updated User Profile Data * Updated User Profile Data
*/ */
body: ServiceUserUserInfoData; body: ServiceUserUserInfoData;
headers: {
/**
* latest
*/
'X-Api-Version': string;
};
path?: never; path?: never;
query?: never; query?: never;
url: '/user/update'; url: '/user/update';

View File

@@ -3,184 +3,228 @@
import { z } from 'zod'; import { z } from 'zod';
export const zDataEventIndexDoc = z.object({ export const zDataEventIndexDoc = z.object({
description: z.optional(z.string()), checkin_count: z.number().int().optional(),
end_time: z.optional(z.string()), description: z.string().optional(),
event_id: z.optional(z.string()), enable_kyc: z.boolean().optional(),
name: z.optional(z.string()), end_time: z.string().optional(),
start_time: z.optional(z.string()), event_id: z.string().optional(),
thumbnail: z.optional(z.string()), is_joined: z.boolean().optional(),
type: z.optional(z.string()) join_count: z.number().int().optional(),
name: z.string().optional(),
start_time: z.string().optional(),
thumbnail: z.string().optional(),
type: z.string().optional()
}); });
export const zDataUserIndexDoc = z.object({ export const zDataUserIndexDoc = z.object({
avatar: z.optional(z.string()), avatar: z.string().optional(),
email: z.optional(z.string()), email: z.string().optional(),
nickname: z.optional(z.string()), nickname: z.string().optional(),
subtitle: z.optional(z.string()), subtitle: z.string().optional(),
type: z.optional(z.string()), type: z.string().optional(),
user_id: z.optional(z.string()), user_id: z.string().optional(),
username: z.optional(z.string()) username: z.string().optional()
}); });
export const zServiceAuthExchangeData = z.object({ export const zServiceAuthExchangeData = z.object({
client_id: z.optional(z.string()), client_id: z.string().optional(),
redirect_uri: z.optional(z.string()), redirect_uri: z.string().optional(),
state: z.optional(z.string()) state: z.string().optional()
}); });
export const zServiceAuthExchangeResponse = z.object({ export const zServiceAuthExchangeResponse = z.object({
redirect_uri: z.optional(z.string()) redirect_uri: z.string().optional()
}); });
export const zServiceAuthMagicData = z.object({ export const zServiceAuthMagicData = z.object({
client_id: z.optional(z.string()), client_id: z.string().optional(),
client_ip: z.optional(z.string()), client_ip: z.string().optional(),
email: z.optional(z.string()), email: z.string().optional(),
redirect_uri: z.optional(z.string()), redirect_uri: z.string().optional(),
state: z.optional(z.string()), state: z.string().optional(),
turnstile_token: z.optional(z.string()) turnstile_token: z.string().optional()
}); });
export const zServiceAuthMagicResponse = z.object({ export const zServiceAuthMagicResponse = z.object({
uri: z.optional(z.string()) uri: z.string().optional()
}); });
export const zServiceAuthRefreshData = z.object({ export const zServiceAuthRefreshData = z.object({
refresh_token: z.optional(z.string()) refresh_token: z.string().optional()
}); });
export const zServiceAuthTokenData = z.object({ export const zServiceAuthTokenData = z.object({
code: z.optional(z.string()) code: z.string().optional()
}); });
export const zServiceAuthTokenResponse = z.object({ export const zServiceAuthTokenResponse = z.object({
access_token: z.optional(z.string()), access_token: z.string().optional(),
refresh_token: z.optional(z.string()) refresh_token: z.string().optional()
}); });
export const zServiceEventCheckinQueryResponse = z.object({ export const zServiceEventCheckinQueryResponse = z.object({
checkin_at: z.optional(z.string()) checkin_at: z.string().optional()
}); });
export const zServiceEventCheckinResponse = z.object({ export const zServiceEventCheckinResponse = z.object({
checkin_code: z.optional(z.string()) checkin_code: z.string().optional()
}); });
export const zServiceEventCheckinSubmitData = z.object({ export const zServiceEventCheckinSubmitData = z.object({
checkin_code: z.optional(z.string()) checkin_code: z.string().optional()
}); });
export const zServiceEventEventJoinData = z.object({ export const zServiceEventEventJoinData = z.object({
event_id: z.optional(z.string()), event_id: z.string().optional(),
kyc_id: z.optional(z.string()) kyc_id: z.string().optional()
}); });
export const zServiceKycKycQueryData = z.object({ export const zServiceKycKycQueryData = z.object({
kyc_id: z.optional(z.string()) kyc_id: z.string().optional()
}); });
export const zServiceKycKycQueryResponse = z.object({ export const zServiceKycKycQueryResponse = z.object({
status: z.optional(z.string()) status: z.string().optional()
}); });
export const zServiceKycKycSessionData = z.object({ export const zServiceKycKycSessionData = z.object({
identity: z.optional(z.string()), identity: z.string().optional(),
type: z.optional(z.string()) type: z.string().optional()
}); });
export const zServiceKycKycSessionResponse = z.object({ export const zServiceKycKycSessionResponse = z.object({
kyc_id: z.optional(z.string()), kyc_id: z.string().optional(),
redirect_uri: z.optional(z.string()), redirect_uri: z.string().optional(),
status: z.optional(z.string()) status: z.string().optional()
}); });
export const zServiceUserUserInfoData = z.object({ export const zServiceUserUserInfoData = z.object({
allow_public: z.optional(z.boolean()), allow_public: z.boolean().optional(),
avatar: z.optional(z.string()), avatar: z.string().optional(),
bio: z.optional(z.string()), bio: z.string().optional(),
email: z.optional(z.string()), email: z.string().optional(),
nickname: z.optional(z.string()), nickname: z.string().optional(),
permission_level: z.optional(z.int()), permission_level: z.number().int().optional(),
subtitle: z.optional(z.string()), subtitle: z.string().optional(),
user_id: z.optional(z.string()), user_id: z.string().optional(),
username: z.optional(z.string()) username: z.string().optional()
});
export const zServiceEventAttendanceListResponse = z.object({
attendance_id: z.string().optional(),
kyc_info: z.unknown().optional(),
kyc_type: z.string().optional(),
user_info: zServiceUserUserInfoData.optional()
}); });
export const zUtilsRespStatus = z.object({ export const zUtilsRespStatus = z.object({
code: z.optional(z.int()), code: z.number().int().optional(),
data: z.optional(z.unknown()), data: z.unknown().optional(),
error_id: z.optional(z.string()), error_id: z.string().optional(),
status: z.optional(z.string()) status: z.string().optional()
}); });
export const zPostAuthExchangeData = z.object({ export const zPostAuthExchangeData = z.object({
body: zServiceAuthExchangeData, body: zServiceAuthExchangeData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Successful exchange * Successful exchange
*/ */
export const zPostAuthExchangeResponse = zUtilsRespStatus.and(z.object({ export const zPostAuthExchangeResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceAuthExchangeResponse) data: zServiceAuthExchangeResponse.optional()
})); }));
export const zPostAuthMagicData = z.object({ export const zPostAuthMagicData = z.object({
body: zServiceAuthMagicData, body: zServiceAuthMagicData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Successful request * Successful request
*/ */
export const zPostAuthMagicResponse = zUtilsRespStatus.and(z.object({ export const zPostAuthMagicResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceAuthMagicResponse) data: zServiceAuthMagicResponse.optional()
})); }));
export const zGetAuthRedirectData = z.object({ export const zGetAuthRedirectData = z.object({
body: z.optional(z.never()), body: z.never().optional(),
path: z.optional(z.never()), path: z.never().optional(),
query: z.object({ query: z.object({
client_id: z.string(), client_id: z.string(),
redirect_uri: z.string(), redirect_uri: z.string(),
code: z.string(), code: z.string(),
state: z.optional(z.string()) state: z.string().optional()
}) })
}); });
export const zPostAuthRefreshData = z.object({ export const zPostAuthRefreshData = z.object({
body: zServiceAuthRefreshData, body: zServiceAuthRefreshData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Successful rotation * Successful rotation
*/ */
export const zPostAuthRefreshResponse = zUtilsRespStatus.and(z.object({ export const zPostAuthRefreshResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceAuthTokenResponse) data: zServiceAuthTokenResponse.optional()
})); }));
export const zPostAuthTokenData = z.object({ export const zPostAuthTokenData = z.object({
body: zServiceAuthTokenData, body: zServiceAuthTokenData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Successful token issuance * Successful token issuance
*/ */
export const zPostAuthTokenResponse = zUtilsRespStatus.and(z.object({ export const zPostAuthTokenResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceAuthTokenResponse) data: zServiceAuthTokenResponse.optional()
}));
export const zGetEventAttendanceData = z.object({
body: z.never().optional(),
path: z.never().optional(),
query: z.object({
event_id: z.string()
}),
headers: z.object({
'X-Api-Version': z.string()
})
});
/**
* Successful retrieval
*/
export const zGetEventAttendanceResponse = zUtilsRespStatus.and(z.object({
data: z.array(zServiceEventAttendanceListResponse).optional()
})); }));
export const zGetEventCheckinData = z.object({ export const zGetEventCheckinData = z.object({
body: z.optional(z.never()), body: z.never().optional(),
path: z.optional(z.never()), path: z.never().optional(),
query: z.object({ query: z.object({
event_id: z.string() event_id: z.string()
}),
headers: z.object({
'X-Api-Version': z.string()
}) })
}); });
@@ -188,12 +232,12 @@ export const zGetEventCheckinData = z.object({
* Successfully generated code * Successfully generated code
*/ */
export const zGetEventCheckinResponse = zUtilsRespStatus.and(z.object({ export const zGetEventCheckinResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceEventCheckinResponse) data: zServiceEventCheckinResponse.optional()
})); }));
export const zGetEventCheckinQueryData = z.object({ export const zGetEventCheckinQueryData = z.object({
body: z.optional(z.never()), body: z.never().optional(),
path: z.optional(z.never()), path: z.never().optional(),
query: z.object({ query: z.object({
event_id: z.string() event_id: z.string()
}) })
@@ -203,27 +247,30 @@ export const zGetEventCheckinQueryData = z.object({
* Current attendance status * Current attendance status
*/ */
export const zGetEventCheckinQueryResponse = zUtilsRespStatus.and(z.object({ export const zGetEventCheckinQueryResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceEventCheckinQueryResponse) data: zServiceEventCheckinQueryResponse.optional()
})); }));
export const zPostEventCheckinSubmitData = z.object({ export const zPostEventCheckinSubmitData = z.object({
body: zServiceEventCheckinSubmitData, body: zServiceEventCheckinSubmitData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional()
}); });
/** /**
* Attendance marked successfully * Attendance marked successfully
*/ */
export const zPostEventCheckinSubmitResponse = zUtilsRespStatus.and(z.object({ export const zPostEventCheckinSubmitResponse = zUtilsRespStatus.and(z.object({
data: z.optional(z.record(z.string(), z.unknown())) data: z.record(z.unknown()).optional()
})); }));
export const zGetEventInfoData = z.object({ export const zGetEventInfoData = z.object({
body: z.optional(z.never()), body: z.never().optional(),
path: z.optional(z.never()), path: z.never().optional(),
query: z.object({ query: z.object({
event_id: z.string() event_id: z.string()
}),
headers: z.object({
'X-Api-Version': z.string()
}) })
}); });
@@ -231,28 +278,34 @@ export const zGetEventInfoData = z.object({
* Successful retrieval * Successful retrieval
*/ */
export const zGetEventInfoResponse = zUtilsRespStatus.and(z.object({ export const zGetEventInfoResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zDataEventIndexDoc) data: zDataEventIndexDoc.optional()
})); }));
export const zPostEventJoinData = z.object({ export const zPostEventJoinData = z.object({
body: zServiceEventEventJoinData, body: zServiceEventEventJoinData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Successfully joined the event * Successfully joined the event
*/ */
export const zPostEventJoinResponse = zUtilsRespStatus.and(z.object({ export const zPostEventJoinResponse = zUtilsRespStatus.and(z.object({
data: z.optional(z.record(z.string(), z.unknown())) data: z.record(z.unknown()).optional()
})); }));
export const zGetEventListData = z.object({ export const zGetEventListData = z.object({
body: z.optional(z.never()), body: z.never().optional(),
path: z.optional(z.never()), path: z.never().optional(),
query: z.object({ query: z.object({
limit: z.optional(z.string()), limit: z.number().int().optional(),
offset: z.string() offset: z.number().int().optional()
}).optional(),
headers: z.object({
'X-Api-Version': z.string()
}) })
}); });
@@ -260,69 +313,84 @@ export const zGetEventListData = z.object({
* Successful paginated list retrieval * Successful paginated list retrieval
*/ */
export const zGetEventListResponse = zUtilsRespStatus.and(z.object({ export const zGetEventListResponse = zUtilsRespStatus.and(z.object({
data: z.optional(z.array(zDataEventIndexDoc)) data: z.array(zDataEventIndexDoc).optional()
})); }));
export const zPostKycQueryData = z.object({ export const zPostKycQueryData = z.object({
body: zServiceKycKycQueryData, body: zServiceKycKycQueryData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Query processed (success/pending/failed) * Query processed (success/pending/failed)
*/ */
export const zPostKycQueryResponse = zUtilsRespStatus.and(z.object({ export const zPostKycQueryResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceKycKycQueryResponse) data: zServiceKycKycQueryResponse.optional()
})); }));
export const zPostKycSessionData = z.object({ export const zPostKycSessionData = z.object({
body: zServiceKycKycSessionData, body: zServiceKycKycSessionData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Session created successfully * Session created successfully
*/ */
export const zPostKycSessionResponse = zUtilsRespStatus.and(z.object({ export const zPostKycSessionResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceKycKycSessionResponse) data: zServiceKycKycSessionResponse.optional()
})); }));
export const zGetUserInfoData = z.object({ export const zGetUserInfoData = z.object({
body: z.optional(z.never()), body: z.never().optional(),
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Successful profile retrieval * Successful profile retrieval
*/ */
export const zGetUserInfoResponse = zUtilsRespStatus.and(z.object({ export const zGetUserInfoResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceUserUserInfoData) data: zServiceUserUserInfoData.optional()
})); }));
export const zGetUserInfoByUserIdData = z.object({ export const zGetUserInfoByUserIdData = z.object({
body: z.optional(z.never()), body: z.never().optional(),
path: z.object({ path: z.object({
user_id: z.string() user_id: z.string()
}), }),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Successful profile retrieval * Successful profile retrieval
*/ */
export const zGetUserInfoByUserIdResponse = zUtilsRespStatus.and(z.object({ export const zGetUserInfoByUserIdResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceUserUserInfoData) data: zServiceUserUserInfoData.optional()
})); }));
export const zGetUserListData = z.object({ export const zGetUserListData = z.object({
body: z.optional(z.never()), body: z.never().optional(),
path: z.optional(z.never()), path: z.never().optional(),
query: z.object({ query: z.object({
limit: z.optional(z.string()), limit: z.string().optional(),
offset: z.string() offset: z.string()
}),
headers: z.object({
'X-Api-Version': z.string()
}) })
}); });
@@ -330,18 +398,21 @@ export const zGetUserListData = z.object({
* Successful paginated list retrieval * Successful paginated list retrieval
*/ */
export const zGetUserListResponse = zUtilsRespStatus.and(z.object({ export const zGetUserListResponse = zUtilsRespStatus.and(z.object({
data: z.optional(z.array(zDataUserIndexDoc)) data: z.array(zDataUserIndexDoc).optional()
})); }));
export const zPatchUserUpdateData = z.object({ export const zPatchUserUpdateData = z.object({
body: zServiceUserUserInfoData, body: zServiceUserUserInfoData,
path: z.optional(z.never()), path: z.never().optional(),
query: z.optional(z.never()) query: z.never().optional(),
headers: z.object({
'X-Api-Version': z.string()
})
}); });
/** /**
* Successful profile update * Successful profile update
*/ */
export const zPatchUserUpdateResponse = zUtilsRespStatus.and(z.object({ export const zPatchUserUpdateResponse = zUtilsRespStatus.and(z.object({
data: z.optional(z.record(z.string(), z.unknown())) data: z.record(z.unknown()).optional()
})); }));

View File

@@ -2,7 +2,6 @@ import type { EventInfo } from './types';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { Calendar } from 'lucide-react'; import { Calendar } from 'lucide-react';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { import {
Card, Card,
CardAction, CardAction,
@@ -13,7 +12,8 @@ import {
} from '@/components/ui/card'; } from '@/components/ui/card';
import { Skeleton } from '../ui/skeleton'; import { Skeleton } from '../ui/skeleton';
export function EventCardView({ type, coverImage, eventName, description, startTime, endTime, onJoinEvent }: EventInfo) { export function EventCardView({ eventInfo, actionFooter }: { eventInfo: EventInfo; actionFooter: React.ReactNode }) {
const { type, coverImage, eventName, description, startTime, endTime } = eventInfo;
const startDayJs = dayjs(startTime); const startDayJs = dayjs(startTime);
const endDayJs = dayjs(endTime); const endDayJs = dayjs(endTime);
return ( return (
@@ -41,7 +41,7 @@ export function EventCardView({ type, coverImage, eventName, description, startT
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter> <CardFooter>
<Button className="w-full" onClick={onJoinEvent}></Button> {actionFooter}
</CardFooter> </CardFooter>
</Card> </Card>
); );

View File

@@ -1,23 +1,40 @@
import type { EventInfo } from './types'; import type { EventInfo } from './types';
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 { useJoinEvent } from '@/hooks/data/useJoinEvent'; import { Button } from '../ui/button';
import { DialogTrigger } from '../ui/dialog';
import { EventGridView } from './event-grid.view'; import { EventGridView } from './event-grid.view';
import { KycDialogContainer } from './kyc/kyc.dialog.container';
export function EventGridContainer() { export function EventGridContainer() {
const { data, isLoading } = useGetEvents(); const { data, isLoading } = useGetEvents();
const { mutate } = useJoinEvent();
const allEvents: EventInfo[] = isLoading const allEvents: EventInfo[] = isLoading
? [] ? []
: data.pages.flatMap(page => page.data!).map(it => ({ : data.pages.flatMap(page => page.data!).map(it => ({
type: it.type! as EventInfo['type'], type: it.type! as EventInfo['type'],
eventId: it.event_id!,
isJoined: it.is_joined!,
requireKyc: it.enable_kyc!,
coverImage: it.thumbnail! || PlaceholderImage, coverImage: it.thumbnail! || PlaceholderImage,
eventName: it.name!, eventName: it.name!,
description: it.description!, description: it.description!,
startTime: new Date(it.start_time!), startTime: new Date(it.start_time!),
endTime: new Date(it.end_time!), endTime: new Date(it.end_time!),
onJoinEvent: () => mutate({ body: { event_id: it.event_id } }),
} satisfies EventInfo)); } satisfies EventInfo));
return <EventGridView events={allEvents} />; return (
<EventGridView
events={allEvents}
assembleFooter={eventInfo => (eventInfo.isJoined
? <Button className="w-full" disabled></Button>
: (
<KycDialogContainer eventIdToJoin={eventInfo.eventId}>
<DialogTrigger asChild>
<Button className="w-full"></Button>
</DialogTrigger>
</KycDialogContainer>
)
)}
/>
);
} }

View File

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

View File

@@ -0,0 +1,18 @@
import { X } from 'lucide-react';
import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../ui/dialog';
export function KycFailedDialogView() {
return (
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
<p></p>
<div className="flex justify-center my-12">
<X size={100} />
</div>
</DialogDescription>
</DialogHeader>
</DialogContent>
);
}

View File

@@ -0,0 +1,177 @@
import type { KycSubmission } from './kyc.types';
import { useForm } from '@tanstack/react-form';
import { useState } from 'react';
import { toast } from 'sonner';
import z from 'zod';
import {
Field,
FieldError,
FieldLabel,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Button } from '../../ui/button';
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../../ui/dialog';
const CnridSchema = z.object({
cnrid: z.string().min(18, '身份证号应为18位').max(18, '身份证号应为18位'),
name: z.string().min(2, '姓名应至少2个字符').max(10, '姓名应不超过10个字符'),
});
function CnridForm({ onSubmit }: { onSubmit: OnSubmit }) {
const form = useForm({
defaultValues: {
cnrid: '',
name: '',
},
validators: {
onSubmit: CnridSchema,
},
onSubmit: async (values) => {
await onSubmit({
method: 'cnrid',
...values.value,
}).catch(() => {
toast('认证失败,请稍后再试');
});
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
void form.handleSubmit();
}}
className="flex flex-col gap-4"
>
<form.Field name="name">
{field => (
<Field>
<FieldLabel htmlFor="name"></FieldLabel>
<Input
id="name"
name="name"
value={field.state.value}
onBlur={field.handleBlur}
onChange={e => field.handleChange(e.target.value)}
/>
<FieldError errors={field.state.meta.errors} />
</Field>
)}
</form.Field>
<form.Field name="cnrid">
{field => (
<Field>
<FieldLabel htmlFor="cnrid"></FieldLabel>
<Input
id="cnrid"
name="cnrid"
value={field.state.value}
onBlur={field.handleBlur}
onChange={e => field.handleChange(e.target.value)}
/>
<FieldError errors={field.state.meta.errors} />
</Field>
)}
</form.Field>
<DialogFooter>
<form.Subscribe
selector={state => [state.canSubmit, state.isPristine, state.isSubmitting]}
children={([canSubmit, isPristine, isSubmitting]) => (
<Button type="submit" disabled={!canSubmit || isPristine || isSubmitting}>{isSubmitting ? '...' : '开始认证'}</Button>
)}
/>
</DialogFooter>
</form>
);
}
const PassportSchema = z.object({
passportId: z.string().min(9, '护照号应为9个字符').max(9, '护照号应为9个字符'),
});
function PassportForm({ onSubmit }: { onSubmit: OnSubmit }) {
const form = useForm({
defaultValues: {
passportId: '',
},
validators: {
onSubmit: PassportSchema,
},
onSubmit: async (values) => {
await onSubmit({
method: 'passport',
...values.value,
}).catch(() => {
toast('认证失败,请稍后再试');
});
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
void form.handleSubmit();
}}
className="flex flex-col gap-4"
>
<form.Field name="passportId">
{field => (
<Field>
<FieldLabel htmlFor="passportId"></FieldLabel>
<Input
id="passportId"
name="passportId"
value={field.state.value}
onBlur={field.handleBlur}
onChange={e => field.handleChange(e.target.value)}
/>
<FieldError errors={field.state.meta.errors} />
</Field>
)}
</form.Field>
<DialogFooter>
<form.Subscribe
selector={state => [state.canSubmit, state.isPristine, state.isSubmitting]}
children={([canSubmit, isPristine, isSubmitting]) => (
<Button type="submit" disabled={!canSubmit || isPristine || isSubmitting}>{isSubmitting ? '...' : '开始认证'}</Button>
)}
/>
</DialogFooter>
</form>
);
}
type OnSubmit = (submission: KycSubmission) => Promise<void>;
export function KycMethodSelectionDialogView({ onSubmit }: { onSubmit: OnSubmit }) {
const [kycMethod, setKycMethod] = useState<string | null>(null);
return (
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription className="prose">
<p></p>
</DialogDescription>
</DialogHeader>
<Label htmlFor="selection"></Label>
<Select onValueChange={setKycMethod}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="请选择..." />
</SelectTrigger>
<SelectContent>
<SelectGroup id="selection">
<SelectItem value="cnrid"></SelectItem>
<SelectItem value="passport"></SelectItem>
</SelectGroup>
</SelectContent>
</Select>
{kycMethod === 'cnrid' && <CnridForm onSubmit={onSubmit} />}
{kycMethod === 'passport' && <PassportForm onSubmit={onSubmit} />}
</DialogContent>
);
}

View File

@@ -0,0 +1,18 @@
import { HashLoader } from 'react-spinners/esm';
import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../ui/dialog';
export function KycPendingDialogView() {
return (
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
<p>...</p>
<div className="flex justify-center my-12">
<HashLoader color="#e0e0e0" size={100} />
</div>
</DialogDescription>
</DialogHeader>
</DialogContent>
);
}

View File

@@ -0,0 +1,24 @@
import { Button } from '../../ui/button';
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../../ui/dialog';
export function KycPromptDialogView({ next }: { next: () => void }) {
return (
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription className="prose">
<p></p>
<p></p>
<ul>
<li> AES-256 </li>
<li></li>
<li> 30 </li>
</ul>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={next}></Button>
</DialogFooter>
</DialogContent>
);
}

View File

@@ -0,0 +1,18 @@
import { Check } from 'lucide-react';
import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../ui/dialog';
export function KycSuccessDialogView() {
return (
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
<p></p>
<div className="flex justify-center my-12">
<Check size={100} />
</div>
</DialogDescription>
</DialogHeader>
</DialogContent>
);
}

View File

@@ -0,0 +1,129 @@
import type { KycSubmission } from './kyc.types';
import { Dialog } from '@radix-ui/react-dialog';
import { useQueryClient } from '@tanstack/react-query';
import { useCallback, useEffect, useState } from 'react';
import { useStore } from 'zustand';
import { postEventJoin, postKycQuery } from '@/client';
import { getEventListInfiniteQueryKey } from '@/client/@tanstack/react-query.gen';
import { useCreateKycSession } from '@/hooks/data/useCreateKycSession';
import { ver } from '@/lib/apiVersion';
import { KycFailedDialogView } from './kyc-failed.dialog.view';
import { KycMethodSelectionDialogView } from './kyc-method-selection.dialog.view';
import { KycPendingDialogView } from './kyc-pending.dialog.view';
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));
const isDialogOpen = useStore(store, s => s.isDialogOpen);
const setIsDialogOpen = useStore(store, s => s.setIsDialogOpen);
const stage = useStore(store, s => s.stage);
const setStage = useStore(store, s => s.setStage);
const setKycId = useStore(store, s => s.setKycId);
const { mutateAsync } = useCreateKycSession();
const queryClient = useQueryClient();
const joinEvent = useCallback(async (eventId: string, kycId: string, abortSignal?: AbortSignal) => {
try {
await postEventJoin({
signal: abortSignal,
body: { event_id: eventId, kyc_id: kycId },
headers: ver('20260205'),
});
setStage('success');
}
catch (e) {
console.error('Error joining event:', e);
setStage('failed');
}
}, [setStage]);
const onKycSessionCreate = useCallback(async (submission: KycSubmission) => {
try {
const { data } = await mutateAsync(submission);
setKycId(data!.kyc_id!);
if (data!.status === 'success') {
await joinEvent(eventIdToJoin, data!.kyc_id!, undefined);
}
else if (data!.status === 'processing') {
window.open(data!.redirect_uri, '_blank');
setStage('pending');
}
}
catch (e) {
console.error(e);
setStage('failed');
}
}, [eventIdToJoin, joinEvent, mutateAsync, setKycId, setStage]);
useEffect(() => {
if (stage !== 'pending' || !isDialogOpen) {
return;
}
const controller = new AbortController();
let timer: NodeJS.Timeout;
const poll = async () => {
try {
const { data } = await postKycQuery({
signal: controller.signal,
body: { kyc_id: store.getState().kycId! },
headers: ver('20260205'),
});
const status = data?.data?.status;
if (status === 'success') {
void joinEvent(eventIdToJoin, store.getState().kycId!, controller.signal);
}
else if (status === 'failed') {
setStage('failed');
}
else if (status === 'pending') {
timer = setTimeout(() => void poll(), 1000);
}
else {
// What the fuck?
setStage('failed');
}
}
catch (e) {
if ((e as Error).name === 'AbortError')
return;
console.error('Error fetching KYC status:', e);
setStage('failed');
}
};
void poll();
return () => {
controller.abort();
clearTimeout(timer);
};
}, [stage, store, setStage, isDialogOpen, joinEvent, eventIdToJoin]);
return (
<Dialog
open={isDialogOpen}
onOpenChange={(open) => {
if (!open) {
void queryClient.invalidateQueries({
queryKey: getEventListInfiniteQueryKey({ query: {}, headers: ver('20260205') }),
});
}
setIsDialogOpen(open);
}}
>
{children}
{stage === 'prompt' && <KycPromptDialogView next={() => setStage('methodSelection')} />}
{stage === 'methodSelection' && <KycMethodSelectionDialogView onSubmit={onKycSessionCreate} />}
{stage === 'pending' && <KycPendingDialogView />}
{stage === 'success' && <KycSuccessDialogView />}
{stage === 'failed' && <KycFailedDialogView />}
</Dialog>
);
}

View File

@@ -0,0 +1,34 @@
import { createStore } from 'zustand';
import { devtools } from 'zustand/middleware';
interface KycState {
isDialogOpen: boolean;
eventIdToJoin: string;
kycId: string | null;
stage: 'prompt' | 'methodSelection' | 'pending' | 'success' | 'failed';
setIsDialogOpen: (open: boolean) => void;
setStage: (stage: KycState['stage']) => void;
setKycId: (kycId: string) => void;
}
export function createKycStore(eventIdToJoin: string) {
const initialState = {
isDialogOpen: false,
eventIdToJoin,
kycId: null,
stage: 'prompt' as const,
};
return createStore<KycState>()(devtools(set => ({
...initialState,
setIsDialogOpen: (open: boolean) => set(() =>
open
? { ...initialState, isDialogOpen: true }
: { ...initialState, isDialogOpen: false },
),
setStage: (stage: KycState['stage']) => set(() => ({ stage })),
setKycId: (kycId: string) => set(() => ({ kycId })),
})));
}
export type KycStore = ReturnType<typeof createKycStore>;

View File

@@ -0,0 +1,8 @@
export type KycSubmission = {
method: 'cnrid';
cnrid: string;
name: string;
} | {
method: 'passport';
passportId: string;
};

View File

@@ -1,9 +1,11 @@
export interface EventInfo { export interface EventInfo {
type: 'official' | 'party'; type: 'official' | 'party';
eventId: string;
isJoined: boolean;
requireKyc: boolean;
coverImage: string; coverImage: string;
eventName: string; eventName: string;
description: string; description: string;
startTime: Date; startTime: Date;
endTime: Date; endTime: Date;
onJoinEvent: () => void;
} }

View File

@@ -30,7 +30,7 @@ const formSchema = z.object({
username: z.string().min(5), username: z.string().min(5),
nickname: z.string(), nickname: z.string(),
subtitle: z.string(), subtitle: z.string(),
avatar: z.url().or(z.literal('')), avatar: z.string().url().or(z.literal('')),
allow_public: z.boolean(), allow_public: z.boolean(),
}); });
export function EditProfileDialogView({ user, updateProfile }: { user: ServiceUserUserInfoData; updateProfile: (data: ServiceUserUserInfoData) => Promise<void> }) { export function EditProfileDialogView({ user, updateProfile }: { user: ServiceUserUserInfoData; updateProfile: (data: ServiceUserUserInfoData) => Promise<void> }) {

View File

@@ -0,0 +1,45 @@
import type { KycSubmission } from '@/components/events/kyc/kyc.types';
import { useMutation } from '@tanstack/react-query';
import { postKycSessionMutation } from '@/client/@tanstack/react-query.gen';
import { ver } from '@/lib/apiVersion';
import { utf8ToBase64 } from '@/lib/utils';
type CreateKycSessionBase64Payload = {
// Cnrid
legal_name: string;
resident_id: string;
} | {
// Passport
id: string;
};
export function useCreateKycSession() {
const mutation = useMutation({
...postKycSessionMutation(),
});
return {
...mutation,
mutate: null, // Don't ever use this
mutateAsync: async (data: KycSubmission) => {
const payload = utf8ToBase64(JSON.stringify(
data.method === 'cnrid'
? {
legal_name: data.name,
resident_id: data.cnrid,
} satisfies CreateKycSessionBase64Payload
: {
id: data.passportId,
} satisfies CreateKycSessionBase64Payload,
));
const response = await mutation.mutateAsync({
body: {
identity: payload,
type: data.method,
},
headers: ver('20260205'),
});
return response;
},
};
}

View File

@@ -1,19 +1,19 @@
import { useInfiniteQuery } from '@tanstack/react-query'; import { useInfiniteQuery } from '@tanstack/react-query';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { getEventListInfiniteOptions } from '@/client/@tanstack/react-query.gen'; import { getEventListInfiniteOptions } from '@/client/@tanstack/react-query.gen';
import { ver } from '@/lib/apiVersion';
const LIMIT = 12;
export function useGetEvents() { export function useGetEvents() {
return useInfiniteQuery({ return useInfiniteQuery({
...getEventListInfiniteOptions({ ...getEventListInfiniteOptions({
query: { limit: String(LIMIT), offset: String(0) }, query: {},
headers: ver('20260205'),
}), }),
initialPageParam: '0', initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => { getNextPageParam: (lastPage, allPages) => {
const currentData = lastPage?.data; const currentData = lastPage?.data;
if (!isNil(currentData) && currentData.length === LIMIT) { if (!isNil(currentData) && currentData.length === 20) {
return String(allPages.length * LIMIT); return allPages.length * 20;
} }
return undefined; return undefined;
}, },

View File

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

View File

@@ -1,16 +1,17 @@
import type { ServiceUserUserInfoData } from '@/client'; import type { ServiceUserUserInfoData } from '@/client';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { getUserInfoByUserIdQueryKey, getUserInfoQueryKey, patchUserUpdateMutation } from '@/client/@tanstack/react-query.gen'; import { getUserInfoByUserIdQueryKey, getUserInfoQueryKey, patchUserUpdateMutation } from '@/client/@tanstack/react-query.gen';
import { ver } from '@/lib/apiVersion';
export function useUpdateUser() { export function useUpdateUser() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const data: { data: ServiceUserUserInfoData | undefined } | undefined = queryClient.getQueryData(getUserInfoQueryKey()); const data: { data: ServiceUserUserInfoData | undefined } | undefined = queryClient.getQueryData(getUserInfoQueryKey({ headers: ver('20260205') }));
return useMutation({ return useMutation({
...patchUserUpdateMutation(), ...patchUserUpdateMutation(),
onSuccess: async () => { onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: getUserInfoQueryKey() }); await queryClient.invalidateQueries({ queryKey: getUserInfoQueryKey({ headers: ver('20260205') }) });
if ((data?.data?.user_id) != null) { if ((data?.data?.user_id) != null) {
await queryClient.invalidateQueries({ queryKey: getUserInfoByUserIdQueryKey({ path: { user_id: data.data.user_id } }) }); await queryClient.invalidateQueries({ queryKey: getUserInfoByUserIdQueryKey({ path: { user_id: data.data.user_id }, headers: ver('20260205') }) });
} }
}, },
}); });

View File

@@ -3,17 +3,18 @@ import {
getUserInfoByUserIdOptions, getUserInfoByUserIdOptions,
getUserInfoOptions, getUserInfoOptions,
} from '@/client/@tanstack/react-query.gen'; } from '@/client/@tanstack/react-query.gen';
import { ver } from '@/lib/apiVersion';
export function useUserInfo() { export function useUserInfo() {
return useSuspenseQuery({ return useSuspenseQuery({
...getUserInfoOptions(), ...getUserInfoOptions({ headers: ver('20260205') }),
staleTime: 10 * 60 * 1000, staleTime: 10 * 60 * 1000,
}); });
} }
export function useOtherUserInfo(userId: string) { export function useOtherUserInfo(userId: string) {
return useSuspenseQuery({ return useSuspenseQuery({
...getUserInfoByUserIdOptions({ path: { user_id: userId } }), ...getUserInfoByUserIdOptions({ path: { user_id: userId }, headers: ver('20260205') }),
staleTime: 10 * 60 * 1000, staleTime: 10 * 60 * 1000,
retry: (_failureCount, error) => error.code !== 403, retry: (_failureCount, error) => error.code !== 403,
}); });

View File

@@ -0,0 +1,5 @@
export function ver(version: string) {
return {
'X-Api-Version': version,
};
}

View File

@@ -1,3 +1,4 @@
import type { Query } from '@tanstack/react-query';
import type { ClassValue } from 'clsx'; import type { ClassValue } from 'clsx';
// eslint-disable-next-line unicorn/prefer-node-protocol // eslint-disable-next-line unicorn/prefer-node-protocol
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
@@ -17,3 +18,12 @@ export function base64ToUtf8(base64: string): string {
export function utf8ToBase64(utf8: string): string { export function utf8ToBase64(utf8: string): string {
return Buffer.from(utf8, 'utf-8').toString('base64'); return Buffer.from(utf8, 'utf-8').toString('base64');
} }
export function invalidateBlurry(id: string) {
return {
predicate: (query: Query<unknown, Error, unknown, readonly unknown[]>) => {
const key = query.queryKey[0] as { _id: string };
return key?._id === id;
},
};
}

View File

@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react-vite'; 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';
const meta = { const meta = {
title: 'Events/EventCard', title: 'Events/EventCard',
@@ -12,25 +13,35 @@ type Story = StoryObj<typeof meta>;
export const Primary: Story = { export const Primary: Story = {
args: { args: {
eventInfo: {
eventId: '1',
type: 'official', type: 'official',
requireKyc: true,
isJoined: false,
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-watersplash.png?raw=true', coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-watersplash.png?raw=true',
eventName: 'Nix CN Conference 26.05', eventName: 'Nix CN Conference 26.05',
description: 'Event Description', description: 'Event Description',
startTime: new Date('2026-06-13T04:00:00.000Z'), startTime: new Date('2026-06-13T04:00:00.000Z'),
endTime: new Date('2026-06-14T04:00:00.000Z'), endTime: new Date('2026-06-14T04:00:00.000Z'),
onJoinEvent: () => { }, },
actionFooter: <Button className="w-full"></Button>,
}, },
}; };
export const Loading: Story = { export const Loading: Story = {
render: () => <EventCardSkeleton />, render: () => <EventCardSkeleton />,
args: { args: {
eventInfo: {
eventId: '1',
type: 'official', type: 'official',
requireKyc: true,
coverImage: '', coverImage: '',
isJoined: false,
eventName: '', eventName: '',
description: '', description: '',
startTime: new Date(0), startTime: new Date(0),
endTime: new Date(0), endTime: new Date(0),
onJoinEvent: () => { }, },
actionFooter: <Button className="w-full"></Button>,
}, },
}; };

View File

@@ -1,6 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react-vite'; import type { Meta, StoryObj } from '@storybook/react-vite';
import { EventGridSkeleton } from '@/components/events/event-grid.skeleton'; import { EventGridSkeleton } from '@/components/events/event-grid.skeleton';
import { EventGridView } from '@/components/events/event-grid.view'; import { EventGridView } from '@/components/events/event-grid.view';
import { Button } from '@/components/ui/button';
import { Skeleton as UiSkeleton } from '@/components/ui/skeleton';
const meta = { const meta = {
title: 'Events/EventGrid', title: 'Events/EventGrid',
@@ -14,42 +16,51 @@ export const Primary: Story = {
args: { args: {
events: [ events: [
{ {
eventId: '1',
requireKyc: true,
isJoined: false,
type: 'official', type: 'official',
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-watersplash.png?raw=true', coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-watersplash.png?raw=true',
eventName: 'Nix CN Conference 26.05', eventName: 'Nix CN Conference 26.05',
description: 'Event Description', description: 'Event Description',
startTime: new Date('2026-06-13T04:00:00.000Z'), startTime: new Date('2026-06-13T04:00:00.000Z'),
endTime: new Date('2026-06-14T04:00:00.000Z'), endTime: new Date('2026-06-14T04:00:00.000Z'),
onJoinEvent: () => { },
}, },
{ {
eventId: '2',
requireKyc: true,
isJoined: false,
type: 'official', type: 'official',
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-moonscape.png?raw=true', coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-moonscape.png?raw=true',
eventName: 'Nix CN Conference 26.05', eventName: 'Nix CN Conference 26.05',
description: 'Event Description', description: 'Event Description',
startTime: new Date('2026-06-13T04:00:00.000Z'), startTime: new Date('2026-06-13T04:00:00.000Z'),
endTime: new Date('2026-06-14T04:00:00.000Z'), endTime: new Date('2026-06-14T04:00:00.000Z'),
onJoinEvent: () => { },
}, },
{ {
eventId: '3',
requireKyc: true,
isJoined: false,
type: 'official', type: 'official',
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-nineish-catppuccin-latte.png?raw=true', coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-nineish-catppuccin-latte.png?raw=true',
eventName: 'Nix CN Conference 26.05', eventName: 'Nix CN Conference 26.05',
description: 'Event Description', description: 'Event Description',
startTime: new Date('2026-06-13T04:00:00.000Z'), startTime: new Date('2026-06-13T04:00:00.000Z'),
endTime: new Date('2026-06-14T04:00:00.000Z'), endTime: new Date('2026-06-14T04:00:00.000Z'),
onJoinEvent: () => { },
}, },
{ {
eventId: '4',
requireKyc: true,
isJoined: false,
type: 'official', type: 'official',
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nixos-wallpaper-catppuccin-macchiato.png?raw=true', coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nixos-wallpaper-catppuccin-macchiato.png?raw=true',
eventName: 'Nix CN Conference 26.05', eventName: 'Nix CN Conference 26.05',
description: 'Event Description', description: 'Event Description',
startTime: new Date('2026-06-13T04:00:00.000Z'), startTime: new Date('2026-06-13T04:00:00.000Z'),
endTime: new Date('2026-06-14T04:00:00.000Z'), endTime: new Date('2026-06-14T04:00:00.000Z'),
onJoinEvent: () => { },
}, },
], ],
assembleFooter: () => <Button className="w-full"></Button>,
}, },
}; };
@@ -57,5 +68,6 @@ export const Skeleton: Story = {
render: () => <EventGridSkeleton />, render: () => <EventGridSkeleton />,
args: { args: {
events: [], events: [],
assembleFooter: () => <UiSkeleton className="w-full" />,
}, },
}; };

View File

@@ -0,0 +1,51 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { KycFailedDialogView } from '@/components/events/kyc/kyc-failed.dialog.view';
import { KycMethodSelectionDialogView } from '@/components/events/kyc/kyc-method-selection.dialog.view';
import { KycPendingDialogView } from '@/components/events/kyc/kyc-pending.dialog.view';
import { KycPromptDialogView } from '@/components/events/kyc/kyc-prompt.dialog.view';
import { KycSuccessDialogView } from '@/components/events/kyc/kyc-success.dialog.view';
import { Dialog } from '@/components/ui/dialog';
const meta = {
title: 'Events/KycDialog',
component: KycPromptDialogView,
decorators: [
Story => (
<Dialog open={true}>
<Story />
</Dialog>
),
],
} satisfies Meta<typeof KycPromptDialogView>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Prompt: Story = {
args: {
},
};
export const MethodSelection: Story = {
render: () => <KycMethodSelectionDialogView onSubmit={async () => Promise.resolve()} />,
args: {
},
};
export const Pending: Story = {
render: () => <KycPendingDialogView />,
args: {
},
};
export const Success: Story = {
render: () => <KycSuccessDialogView />,
args: {
},
};
export const Failed: Story = {
render: () => <KycFailedDialogView />,
args: {
},
};