Compare commits

...

2 Commits

Author SHA1 Message Date
06f86cb8e3 chore(client): format
All checks were successful
Client CMS Check Build (NixCN CMS) TeamCity build finished
Backend Check Build (NixCN CMS) TeamCity build finished
Signed-off-by: Noa Virellia <noa@requiem.garden>
2026-02-01 15:31:29 +08:00
094d02d203 feat(client): event list
Signed-off-by: Noa Virellia <noa@requiem.garden>
2026-02-01 14:26:35 +08:00
26 changed files with 634 additions and 79 deletions

View File

@@ -13,6 +13,6 @@
"**/.DS_Store",
"**/Thumbs.db",
"**/.classpath",
"**/.settings",
],
"**/.settings"
]
}

View File

@@ -4,7 +4,7 @@ import pluginQuery from '@tanstack/eslint-plugin-query';
export default antfu({
gitignore: true,
ignores: ['**/node_modules/**', '**/dist/**', 'bun.lock', '**/routeTree.gen.ts', '**/ui/**', 'src/components/editor/**/*', 'src/client/**/*', 'openapi-ts.config.ts'],
ignores: ['**/node_modules/**', '**/dist/**', 'bun.lock', '**/routeTree.gen.ts', '**/ui/**', 'src/components/editor/**/*', 'src/client/**/*', 'openapi-ts.config.ts', 'vitest.shims.d.ts', '.storybook/**/*'],
react: true,
stylistic: {
semi: true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -3,8 +3,8 @@
import { type InfiniteData, infiniteQueryOptions, queryOptions, type UseMutationOptions } from '@tanstack/react-query';
import { client } from '../client.gen';
import { getAuthRedirect, getEventCheckin, getEventCheckinQuery, getEventInfo, getEventList, getUserInfo, getUserInfoByUserId, getUserList, type Options, patchUserUpdate, postAuthExchange, postAuthMagic, postAuthRefresh, postAuthToken, postEventCheckinSubmit } 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 } from '../types.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 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';
/**
* Exchange Auth Code
@@ -214,6 +214,25 @@ export const getEventInfoOptions = (options: Options<GetEventInfoData>) => query
queryKey: getEventInfoQueryKey(options)
});
/**
* Join an Event
*
* Allows an authenticated user to join an event by providing the event ID. The user's role and state are initialized by the service.
*/
export const postEventJoinMutation = (options?: Partial<Options<PostEventJoinData>>): UseMutationOptions<PostEventJoinResponse, PostEventJoinError, Options<PostEventJoinData>> => {
const mutationOptions: UseMutationOptions<PostEventJoinResponse, PostEventJoinError, Options<PostEventJoinData>> = {
mutationFn: async (fnOptions) => {
const { data } = await postEventJoin({
...options,
...fnOptions,
throwOnError: true
});
return data;
}
};
return mutationOptions;
};
export const getEventListQueryKey = (options: Options<GetEventListData>) => createQueryKey('getEventList', options);
/**
@@ -292,6 +311,44 @@ export const getEventListInfiniteOptions = (options: Options<GetEventListData>)
queryKey: getEventListInfiniteQueryKey(options)
});
/**
* Query KYC Status
*
* Checks the current state of a KYC session and updates local database if approved.
*/
export const postKycQueryMutation = (options?: Partial<Options<PostKycQueryData>>): UseMutationOptions<PostKycQueryResponse, PostKycQueryError, Options<PostKycQueryData>> => {
const mutationOptions: UseMutationOptions<PostKycQueryResponse, PostKycQueryError, Options<PostKycQueryData>> = {
mutationFn: async (fnOptions) => {
const { data } = await postKycQuery({
...options,
...fnOptions,
throwOnError: true
});
return data;
}
};
return mutationOptions;
};
/**
* Create KYC Session
*
* Initializes a KYC process (CNRid or Passport) and returns the status or redirect URI.
*/
export const postKycSessionMutation = (options?: Partial<Options<PostKycSessionData>>): UseMutationOptions<PostKycSessionResponse, PostKycSessionError, Options<PostKycSessionData>> => {
const mutationOptions: UseMutationOptions<PostKycSessionResponse, PostKycSessionError, Options<PostKycSessionData>> = {
mutationFn: async (fnOptions) => {
const { data } = await postKycSession({
...options,
...fnOptions,
throwOnError: true
});
return data;
}
};
return mutationOptions;
};
export const getUserInfoQueryKey = (options?: Options<GetUserInfoData>) => createQueryKey('getUserInfo', options);
/**

View File

@@ -1,4 +1,4 @@
// 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 } 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, ServiceAuthExchangeData, ServiceAuthExchangeResponse, ServiceAuthMagicData, ServiceAuthMagicResponse, ServiceAuthRefreshData, ServiceAuthTokenData, ServiceAuthTokenResponse, ServiceEventCheckinQueryResponse, ServiceEventCheckinResponse, ServiceEventCheckinSubmitData, ServiceUserUserInfoData, UtilsRespStatus } from './types.gen';
export { getAuthRedirect, 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';

View File

@@ -2,7 +2,7 @@
import type { Client, Options as Options2, TDataShape } from './client';
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 } from './types.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';
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
/**
@@ -116,6 +116,20 @@ export const postEventCheckinSubmit = <ThrowOnError extends boolean = false>(opt
*/
export const getEventInfo = <ThrowOnError extends boolean = false>(options: Options<GetEventInfoData, ThrowOnError>) => (options.client ?? client).get<GetEventInfoResponses, GetEventInfoErrors, ThrowOnError>({ url: '/event/info', ...options });
/**
* Join an Event
*
* Allows an authenticated user to join an event by providing the event ID. The user's role and state are initialized by the service.
*/
export const postEventJoin = <ThrowOnError extends boolean = false>(options: Options<PostEventJoinData, ThrowOnError>) => (options.client ?? client).post<PostEventJoinResponses, PostEventJoinErrors, ThrowOnError>({
url: '/event/join',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
/**
* List Events
*
@@ -123,6 +137,34 @@ export const getEventInfo = <ThrowOnError extends boolean = false>(options: Opti
*/
export const getEventList = <ThrowOnError extends boolean = false>(options: Options<GetEventListData, ThrowOnError>) => (options.client ?? client).get<GetEventListResponses, GetEventListErrors, ThrowOnError>({ url: '/event/list', ...options });
/**
* Query KYC Status
*
* Checks the current state of a KYC session and updates local database if approved.
*/
export const postKycQuery = <ThrowOnError extends boolean = false>(options: Options<PostKycQueryData, ThrowOnError>) => (options.client ?? client).post<PostKycQueryResponses, PostKycQueryErrors, ThrowOnError>({
url: '/kyc/query',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
/**
* Create KYC Session
*
* Initializes a KYC process (CNRid or Passport) and returns the status or redirect URI.
*/
export const postKycSession = <ThrowOnError extends boolean = false>(options: Options<PostKycSessionData, ThrowOnError>) => (options.client ?? client).post<PostKycSessionResponses, PostKycSessionErrors, ThrowOnError>({
url: '/kyc/session',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
/**
* Get My User Information
*

View File

@@ -72,6 +72,42 @@ export type ServiceEventCheckinSubmitData = {
checkin_code?: string;
};
export type ServiceEventEventJoinData = {
event_id?: string;
kyc_id?: string;
};
export type ServiceKycKycQueryData = {
kyc_id?: string;
};
export type ServiceKycKycQueryResponse = {
/**
* success | pending | failed
*/
status?: string;
};
export type ServiceKycKycSessionData = {
/**
* base64 json
*/
identity?: string;
/**
* cnrid | passport
*/
type?: string;
};
export type ServiceKycKycSessionResponse = {
kyc_id?: string;
redirect_uri?: string;
/**
* success | processing
*/
status?: string;
};
export type ServiceUserUserInfoData = {
allow_public?: boolean;
avatar?: string;
@@ -536,6 +572,66 @@ export type GetEventInfoResponses = {
export type GetEventInfoResponse = GetEventInfoResponses[keyof GetEventInfoResponses];
export type PostEventJoinData = {
/**
* Event Join Details (UserId and EventId are required)
*/
body: ServiceEventEventJoinData;
path?: never;
query?: never;
url: '/event/join';
};
export type PostEventJoinErrors = {
/**
* Invalid Input or UUID Parse Failed
*/
400: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Missing User ID / Unauthorized
*/
401: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Unauthorized / Missing User ID
*/
403: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Internal Server Error / Database Error
*/
500: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
};
export type PostEventJoinError = PostEventJoinErrors[keyof PostEventJoinErrors];
export type PostEventJoinResponses = {
/**
* Successfully joined the event
*/
200: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
};
export type PostEventJoinResponse = PostEventJoinResponses[keyof PostEventJoinResponses];
export type GetEventListData = {
body?: never;
path?: never;
@@ -592,6 +688,106 @@ export type GetEventListResponses = {
export type GetEventListResponse = GetEventListResponses[keyof GetEventListResponses];
export type PostKycQueryData = {
/**
* KYC query data (KycId)
*/
body: ServiceKycKycQueryData;
path?: never;
query?: never;
url: '/kyc/query';
};
export type PostKycQueryErrors = {
/**
* Invalid UUID or input
*/
400: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Unauthorized
*/
403: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Internal Server Error
*/
500: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
};
export type PostKycQueryError = PostKycQueryErrors[keyof PostKycQueryErrors];
export type PostKycQueryResponses = {
/**
* Query processed (success/pending/failed)
*/
200: UtilsRespStatus & {
data?: ServiceKycKycQueryResponse;
};
};
export type PostKycQueryResponse = PostKycQueryResponses[keyof PostKycQueryResponses];
export type PostKycSessionData = {
/**
* KYC session data (Type and Base64 Identity)
*/
body: ServiceKycKycSessionData;
path?: never;
query?: never;
url: '/kyc/session';
};
export type PostKycSessionErrors = {
/**
* Invalid input or decode failed
*/
400: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Missing User ID
*/
403: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
/**
* Internal Server Error / KYC Service Error
*/
500: UtilsRespStatus & {
data?: {
[key: string]: unknown;
};
};
};
export type PostKycSessionError = PostKycSessionErrors[keyof PostKycSessionErrors];
export type PostKycSessionResponses = {
/**
* Session created successfully
*/
200: UtilsRespStatus & {
data?: ServiceKycKycSessionResponse;
};
};
export type PostKycSessionResponse = PostKycSessionResponses[keyof PostKycSessionResponses];
export type GetUserInfoData = {
body?: never;
path?: never;

View File

@@ -70,6 +70,30 @@ export const zServiceEventCheckinSubmitData = z.object({
checkin_code: z.optional(z.string())
});
export const zServiceEventEventJoinData = z.object({
event_id: z.optional(z.string()),
kyc_id: z.optional(z.string())
});
export const zServiceKycKycQueryData = z.object({
kyc_id: z.optional(z.string())
});
export const zServiceKycKycQueryResponse = z.object({
status: z.optional(z.string())
});
export const zServiceKycKycSessionData = z.object({
identity: z.optional(z.string()),
type: z.optional(z.string())
});
export const zServiceKycKycSessionResponse = z.object({
kyc_id: z.optional(z.string()),
redirect_uri: z.optional(z.string()),
status: z.optional(z.string())
});
export const zServiceUserUserInfoData = z.object({
allow_public: z.optional(z.boolean()),
avatar: z.optional(z.string()),
@@ -210,6 +234,19 @@ export const zGetEventInfoResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zDataEventIndexDoc)
}));
export const zPostEventJoinData = z.object({
body: zServiceEventEventJoinData,
path: z.optional(z.never()),
query: z.optional(z.never())
});
/**
* Successfully joined the event
*/
export const zPostEventJoinResponse = zUtilsRespStatus.and(z.object({
data: z.optional(z.record(z.string(), z.unknown()))
}));
export const zGetEventListData = z.object({
body: z.optional(z.never()),
path: z.optional(z.never()),
@@ -226,6 +263,32 @@ export const zGetEventListResponse = zUtilsRespStatus.and(z.object({
data: z.optional(z.array(zDataEventIndexDoc))
}));
export const zPostKycQueryData = z.object({
body: zServiceKycKycQueryData,
path: z.optional(z.never()),
query: z.optional(z.never())
});
/**
* Query processed (success/pending/failed)
*/
export const zPostKycQueryResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceKycKycQueryResponse)
}));
export const zPostKycSessionData = z.object({
body: zServiceKycKycSessionData,
path: z.optional(z.never()),
query: z.optional(z.never())
});
/**
* Session created successfully
*/
export const zPostKycSessionResponse = zUtilsRespStatus.and(z.object({
data: z.optional(zServiceKycKycSessionResponse)
}));
export const zGetUserInfoData = z.object({
body: z.optional(z.never()),
path: z.optional(z.never()),

View File

@@ -0,0 +1,40 @@
import { Calendar } from 'lucide-react';
import {
Card,
CardAction,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Badge } from '../ui/badge';
import { Skeleton } from '../ui/skeleton';
export function EventCardSkeleton() {
return (
<Card className="relative mx-auto w-full max-w-sm pt-0">
<div className="absolute inset-0 z-30 aspect-video bg-black/10" />
<Skeleton
className="relative z-20 aspect-video w-full object-cover rounded-t-xl"
/>
<CardHeader>
<CardAction>
<Badge variant="secondary" className="bg-accent animate-pulse text-accent select-none">Official</Badge>
</CardAction>
<CardTitle>
<Skeleton className="h-4 max-w-48" />
</CardTitle>
<CardDescription className="flex flex-row items-center text-xs">
<Calendar className="size-4 mr-2" />
<Skeleton className="h-4 w-24" />
</CardDescription>
<CardDescription className="mt-1">
<Skeleton className="h-5 max-w-64" />
</CardDescription>
</CardHeader>
<CardFooter>
<Skeleton className="h-9 px-4 py-2 w-full"></Skeleton>
</CardFooter>
</Card>
);
}

View File

@@ -1,3 +1,4 @@
import type { EventInfo } from './types';
import dayjs from 'dayjs';
import { Calendar } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
@@ -10,16 +11,9 @@ import {
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Skeleton } from '../ui/skeleton';
export function EventCardView({ type, coverImage, eventName, description, startTime, endTime }:
{
type: 'official' | 'party';
coverImage: string;
eventName: string;
description: string;
startTime: Date;
endTime: Date;
}) {
export function EventCardView({ type, coverImage, eventName, description, startTime, endTime, onJoinEvent }: EventInfo) {
const startDayJs = dayjs(startTime);
const endDayJs = dayjs(endTime);
return (
@@ -30,6 +24,9 @@ export function EventCardView({ type, coverImage, eventName, description, startT
alt="Event cover"
className="relative z-20 aspect-video w-full object-cover rounded-t-xl"
/>
<Skeleton
className="absolute z-15 aspect-video w-full object-cover rounded-t-xl"
/>
<CardHeader>
<CardAction>
{type === 'official' ? <Badge variant="secondary">Official</Badge> : <Badge variant="destructive">Party</Badge>}
@@ -42,10 +39,9 @@ export function EventCardView({ type, coverImage, eventName, description, startT
<CardDescription className="mt-1">
{description}
</CardDescription>
</CardHeader>
<CardFooter>
<Button className="w-full"></Button>
<Button className="w-full" onClick={onJoinEvent}></Button>
</CardFooter>
</Card>
);

View File

@@ -0,0 +1,23 @@
import type { EventInfo } from './types';
import PlaceholderImage from '@/assets/event-placeholder.png';
import { useGetEvents } from '@/hooks/data/useGetEvents';
import { useJoinEvent } from '@/hooks/data/useJoinEvent';
import { EventGridView } from './event-grid.view';
export function EventGridContainer() {
const { data, isLoading } = useGetEvents();
const { mutate } = useJoinEvent();
const allEvents: EventInfo[] = isLoading
? []
: data.pages.flatMap(page => page.data!).map(it => ({
type: it.type! as EventInfo['type'],
coverImage: it.thumbnail! || PlaceholderImage,
eventName: it.name!,
description: it.description!,
startTime: new Date(it.start_time!),
endTime: new Date(it.end_time!),
onJoinEvent: () => mutate({ body: { event_id: it.event_id } }),
} satisfies EventInfo));
return <EventGridView events={allEvents} />;
}

View File

@@ -0,0 +1,12 @@
import { EventCardSkeleton } from './event-card.skeleton';
export function EventGridSkeleton() {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{Array.from({ length: 8 }).map((_, i) => (
// eslint-disable-next-line react/no-array-index-key
<EventCardSkeleton key={i} />
))}
</div>
);
}

View File

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

View File

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

View File

@@ -53,12 +53,12 @@ export function ProfileView({ user, onSaveBio }: { user: ServiceUserUserInfoData
{/* Bio */}
{enableBioEdit
? (
<MDEditor
value={bio}
onChange={setBio}
height="100%"
/>
)
<MDEditor
value={bio}
onChange={setBio}
height="100%"
/>
)
: <div className="p-6 prose dark:prose-invert"><Markdown>{bio}</Markdown></div>}
<Button
className="absolute bottom-4 right-4"

View File

@@ -1,9 +0,0 @@
import { Skeleton } from '../ui/skeleton';
export function CardSkeleton() {
return (
<Skeleton
className="gap-6 rounded-xl py-6 h-full"
/>
);
}

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ export function useIsMobile() {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener('change', onChange);
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener('change', onChange);
}, []);

View File

@@ -1,9 +1,14 @@
import { createFileRoute } from '@tanstack/react-router';
import { EventGridContainer } from '@/components/events/event-grid.container';
export const Route = createFileRoute('/_workbenchLayout/events')({
component: RouteComponent,
});
function RouteComponent() {
return <div>Hello "/_sidebarLayout/events"!</div>;
return (
<div className="py-4 px-6 md:gap-6 md:py-6">
<EventGridContainer />
</div>
);
}

View File

@@ -38,6 +38,7 @@ function RouteComponent() {
if (mutation.isIdle) {
mutation.mutate({ body: { code } });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div>{status}</div>;

View File

@@ -1,21 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { EventCardView } from '@/components/workbenchCards/event-card.view';
const meta = {
title: 'Cards/EventCard',
component: EventCardView,
} satisfies Meta<typeof EventCardView>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
type: 'official',
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: "2026-06-13T04:00:00.000Z",
endTime: "2026-06-14T04:00:00.000Z",
},
};

View File

@@ -0,0 +1,36 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { EventCardSkeleton } from '@/components/events/event-card.skeleton';
import { EventCardView } from '@/components/events/event-card.view';
const meta = {
title: 'Events/EventCard',
component: EventCardView,
} satisfies Meta<typeof EventCardView>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
type: 'official',
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'),
onJoinEvent: () => { },
},
};
export const Loading: Story = {
render: () => <EventCardSkeleton />,
args: {
type: 'official',
coverImage: '',
eventName: '',
description: '',
startTime: new Date(0),
endTime: new Date(0),
onJoinEvent: () => { },
},
};

View File

@@ -0,0 +1,61 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { EventGridSkeleton } from '@/components/events/event-grid.skeleton';
import { EventGridView } from '@/components/events/event-grid.view';
const meta = {
title: 'Events/EventGrid',
component: EventGridView,
} satisfies Meta<typeof EventGridView>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
events: [
{
type: 'official',
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'),
onJoinEvent: () => { },
},
{
type: 'official',
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-moonscape.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'),
onJoinEvent: () => { },
},
{
type: 'official',
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-nineish-catppuccin-latte.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'),
onJoinEvent: () => { },
},
{
type: 'official',
coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nixos-wallpaper-catppuccin-macchiato.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'),
onJoinEvent: () => { },
},
],
},
};
export const Skeleton: Story = {
render: () => <EventGridSkeleton />,
args: {
events: [],
},
};

View File

@@ -4,10 +4,10 @@ import { NavUserSkeleton } from '@/components/sidebar/nav-user.skeletion';
import { NavUserView } from '@/components/sidebar/nav-user.view';
import { SidebarProvider } from '@/components/ui/sidebar';
import { navData } from '@/lib/navData';
import { user } from './exampleUser';
import { user } from '../exampleUser';
const meta = {
title: 'Navigation/Sidebar',
title: 'Layout/Sidebar',
component: AppSidebar,
decorators: [
Story => (

View File

@@ -1,35 +1,36 @@
/// <reference types="vitest/config" />
import path from 'node:path';
import tailwindcss from '@tailwindcss/vite';
import { tanstackRouter } from '@tanstack/router-plugin/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
// https://vite.dev/config/
import { fileURLToPath } from 'node:url';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
import tailwindcss from '@tailwindcss/vite';
import { tanstackRouter } from '@tanstack/router-plugin/vite';
import react from '@vitejs/plugin-react';
import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vite';
import svgr from 'vite-plugin-svgr';
const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineConfig({
plugins: [tanstackRouter({
target: 'react',
autoCodeSplitting: true
autoCodeSplitting: true,
}), react(), tailwindcss(), svgr()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
'@': path.resolve(__dirname, './src'),
},
},
server: {
proxy: {
'/api': 'http://10.0.0.10:8000'
'/api': 'http://10.0.0.10:8000',
},
host: '0.0.0.0',
port: 5173,
allowedHosts: ['nix.org.cn', 'nixos.party']
allowedHosts: ['nix.org.cn', 'nixos.party'],
},
test: {
projects: [{
@@ -37,9 +38,10 @@ export default defineConfig({
plugins: [
// The plugin will run tests for the stories defined in your Storybook config
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
storybookTest({
configDir: path.join(dirname, '.storybook')
})],
storybookTest({
configDir: path.join(dirname, '.storybook'),
}),
],
test: {
name: 'storybook',
browser: {
@@ -47,11 +49,11 @@ export default defineConfig({
headless: true,
provider: playwright({}),
instances: [{
browser: 'chromium'
}]
browser: 'chromium',
}],
},
setupFiles: ['.storybook/vitest.setup.ts']
}
}]
}
});
setupFiles: ['.storybook/vitest.setup.ts'],
},
}],
},
});