From 6ea414bc887e4ec7583faaf857863a32d9ae376c Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Fri, 30 Jan 2026 22:48:09 +0800 Subject: [PATCH 01/12] fix(client): logout Signed-off-by: Noa Virellia --- client/cms/eslint.config.js | 2 +- .../src/client/@tanstack/react-query.gen.ts | 103 +++++++++++------- client/cms/src/client/index.ts | 4 +- client/cms/src/client/sdk.gen.ts | 8 +- client/cms/src/client/types.gen.ts | 68 +++++++----- client/cms/src/client/zod.gen.ts | 44 ++++---- .../profile/edit-profile-dialog.tsx | 2 +- .../src/components/profile/main-profile.tsx | 2 +- .../cms/src/components/sidebar/nav-user.tsx | 4 +- client/cms/src/hooks/data/useExchangeToken.ts | 2 +- client/cms/src/lib/client.ts | 33 +++--- client/cms/src/lib/token.ts | 44 +++++--- .../cms/src/routes/_sidebarLayout/index.tsx | 10 +- client/cms/src/routes/authorize.tsx | 4 +- client/cms/src/routes/token.tsx | 6 +- 15 files changed, 186 insertions(+), 150 deletions(-) diff --git a/client/cms/eslint.config.js b/client/cms/eslint.config.js index 377f8ce..d60f5e0 100644 --- a/client/cms/eslint.config.js +++ b/client/cms/eslint.config.js @@ -3,7 +3,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/**/*'], + ignores: ['**/node_modules/**', '**/dist/**', 'bun.lock', '**/routeTree.gen.ts', '**/ui/**', 'src/components/editor/**/*', 'src/client/**/*', 'openapi-ts.config.ts'], react: true, stylistic: { semi: true, diff --git a/client/cms/src/client/@tanstack/react-query.gen.ts b/client/cms/src/client/@tanstack/react-query.gen.ts index 6c77868..c575093 100644 --- a/client/cms/src/client/@tanstack/react-query.gen.ts +++ b/client/cms/src/client/@tanstack/react-query.gen.ts @@ -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, getUserFull, getUserInfo, 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, GetUserFullData, GetUserFullError, GetUserFullResponse, 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, 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, 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'; /** * Exchange Auth Code @@ -214,16 +214,16 @@ export const getEventInfoOptions = (options: Options) => query queryKey: getEventInfoQueryKey(options) }); -export const getUserFullQueryKey = (options?: Options) => createQueryKey('getUserFull', options); +export const getEventListQueryKey = (options: Options) => createQueryKey('getEventList', options); /** - * Get Full User Table + * List Events * - * Fetches all user records without pagination. This is typically used for administrative overview or data export. + * Fetches a list of events with support for pagination via limit and offset. Data is retrieved directly from the database for consistency. */ -export const getUserFullOptions = (options?: Options) => queryOptions>({ +export const getEventListOptions = (options: Options) => queryOptions>({ queryFn: async ({ queryKey, signal }) => { - const { data } = await getUserFull({ + const { data } = await getEventList({ ...options, ...queryKey[0], signal, @@ -231,7 +231,65 @@ export const getUserFullOptions = (options?: Options) => queryO }); return data; }, - queryKey: getUserFullQueryKey(options) + queryKey: getEventListQueryKey(options) +}); + +const createInfiniteParams = [0], 'body' | 'headers' | 'path' | 'query'>>(queryKey: QueryKey, page: K) => { + const params = { ...queryKey[0] }; + if (page.body) { + params.body = { + ...queryKey[0].body as any, + ...page.body as any + }; + } + if (page.headers) { + params.headers = { + ...queryKey[0].headers, + ...page.headers + }; + } + if (page.path) { + params.path = { + ...queryKey[0].path as any, + ...page.path as any + }; + } + if (page.query) { + params.query = { + ...queryKey[0].query as any, + ...page.query as any + }; + } + return params as unknown as typeof page; +}; + +export const getEventListInfiniteQueryKey = (options: Options): QueryKey> => createQueryKey('getEventList', options, true); + +/** + * List Events + * + * 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) => infiniteQueryOptions, QueryKey>, string | Pick>[0], 'body' | 'headers' | 'path' | 'query'>>( +// @ts-ignore +{ + queryFn: async ({ pageParam, queryKey, signal }) => { + // @ts-ignore + const page: Pick>[0], 'body' | 'headers' | 'path' | 'query'> = typeof pageParam === 'object' ? pageParam : { + query: { + offset: pageParam + } + }; + const params = createInfiniteParams(queryKey, page); + const { data } = await getEventList({ + ...options, + ...params, + signal, + throwOnError: true + }); + return data; + }, + queryKey: getEventListInfiniteQueryKey(options) }); export const getUserInfoQueryKey = (options?: Options) => createQueryKey('getUserInfo', options); @@ -274,35 +332,6 @@ export const getUserListOptions = (options: Options) => queryOp queryKey: getUserListQueryKey(options) }); -const createInfiniteParams = [0], 'body' | 'headers' | 'path' | 'query'>>(queryKey: QueryKey, page: K) => { - const params = { ...queryKey[0] }; - if (page.body) { - params.body = { - ...queryKey[0].body as any, - ...page.body as any - }; - } - if (page.headers) { - params.headers = { - ...queryKey[0].headers, - ...page.headers - }; - } - if (page.path) { - params.path = { - ...queryKey[0].path as any, - ...page.path as any - }; - } - if (page.query) { - params.query = { - ...queryKey[0].query as any, - ...page.query as any - }; - } - return params as unknown as typeof page; -}; - export const getUserListInfiniteQueryKey = (options: Options): QueryKey> => createQueryKey('getUserList', options, true); /** diff --git a/client/cms/src/client/index.ts b/client/cms/src/client/index.ts index e4e494e..a868299 100644 --- a/client/cms/src/client/index.ts +++ b/client/cms/src/client/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { getAuthRedirect, getEventCheckin, getEventCheckinQuery, getEventInfo, getUserFull, getUserInfo, getUserList, type Options, patchUserUpdate, postAuthExchange, postAuthMagic, postAuthRefresh, postAuthToken, postEventCheckinSubmit } from './sdk.gen'; -export type { ClientOptions, DataUser, DataUserSearchDoc, GetAuthRedirectData, GetAuthRedirectError, GetAuthRedirectErrors, GetEventCheckinData, GetEventCheckinError, GetEventCheckinErrors, GetEventCheckinQueryData, GetEventCheckinQueryError, GetEventCheckinQueryErrors, GetEventCheckinQueryResponse, GetEventCheckinQueryResponses, GetEventCheckinResponse, GetEventCheckinResponses, GetEventInfoData, GetEventInfoError, GetEventInfoErrors, GetEventInfoResponse, GetEventInfoResponses, GetUserFullData, GetUserFullError, GetUserFullErrors, GetUserFullResponse, GetUserFullResponses, 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, ServiceEventInfoResponse, ServiceUserUserInfoData, ServiceUserUserTableResponse, UtilsRespStatus } from './types.gen'; +export { getAuthRedirect, getEventCheckin, getEventCheckinQuery, getEventInfo, getEventList, getUserInfo, 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, 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, ServiceEventEventInfoResponse, ServiceUserUserInfoData, UtilsRespStatus } from './types.gen'; diff --git a/client/cms/src/client/sdk.gen.ts b/client/cms/src/client/sdk.gen.ts index d9d3e85..12608f4 100644 --- a/client/cms/src/client/sdk.gen.ts +++ b/client/cms/src/client/sdk.gen.ts @@ -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, GetUserFullData, GetUserFullErrors, GetUserFullResponses, 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, 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'; export type Options = Options2 & { /** @@ -117,11 +117,11 @@ export const postEventCheckinSubmit = (opt export const getEventInfo = (options: Options) => (options.client ?? client).get({ url: '/event/info', ...options }); /** - * Get Full User Table + * List Events * - * Fetches all user records without pagination. This is typically used for administrative overview or data export. + * Fetches a list of events with support for pagination via limit and offset. Data is retrieved directly from the database for consistency. */ -export const getUserFull = (options?: Options) => (options?.client ?? client).get({ url: '/user/full', ...options }); +export const getEventList = (options: Options) => (options.client ?? client).get({ url: '/event/list', ...options }); /** * Get My User Information diff --git a/client/cms/src/client/types.gen.ts b/client/cms/src/client/types.gen.ts index 4e66492..644c6f1 100644 --- a/client/cms/src/client/types.gen.ts +++ b/client/cms/src/client/types.gen.ts @@ -4,21 +4,16 @@ export type ClientOptions = { baseUrl: 'http://localhost:8000/api/v1' | 'https://localhost:8000/api/v1' | (string & {}); }; -export type DataUser = { - allow_public?: boolean; - avatar?: string; - bio?: string; - email?: string; - id?: number; - nickname?: string; - permission_level?: number; - subtitle?: string; - user_id?: string; - username?: string; - uuid?: string; +export type DataEventIndexDoc = { + description?: string; + end_time?: string; + event_id?: string; + name?: string; + start_time?: string; + type?: string; }; -export type DataUserSearchDoc = { +export type DataUserIndexDoc = { avatar?: string; email?: string; nickname?: string; @@ -76,7 +71,7 @@ export type ServiceEventCheckinSubmitData = { checkin_code?: string; }; -export type ServiceEventInfoResponse = { +export type ServiceEventEventInfoResponse = { end_time?: string; name?: string; start_time?: string; @@ -94,10 +89,6 @@ export type ServiceUserUserInfoData = { username?: string; }; -export type ServiceUserUserTableResponse = { - user_table?: Array; -}; - export type UtilsRespStatus = { code?: number; data?: unknown; @@ -528,22 +519,39 @@ export type GetEventInfoResponses = { * Successful retrieval */ 200: UtilsRespStatus & { - data?: ServiceEventInfoResponse; + data?: ServiceEventEventInfoResponse; }; }; export type GetEventInfoResponse = GetEventInfoResponses[keyof GetEventInfoResponses]; -export type GetUserFullData = { +export type GetEventListData = { body?: never; path?: never; - query?: never; - url: '/user/full'; + query: { + /** + * Maximum number of events to return (default 20) + */ + limit?: string; + /** + * Number of events to skip + */ + offset: string; + }; + url: '/event/list'; }; -export type GetUserFullErrors = { +export type GetEventListErrors = { /** - * Internal Server Error (Database Error) + * Invalid Input (Missing offset or malformed parameters) + */ + 400: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; + /** + * Internal Server Error (Database query failed) */ 500: UtilsRespStatus & { data?: { @@ -552,18 +560,18 @@ export type GetUserFullErrors = { }; }; -export type GetUserFullError = GetUserFullErrors[keyof GetUserFullErrors]; +export type GetEventListError = GetEventListErrors[keyof GetEventListErrors]; -export type GetUserFullResponses = { +export type GetEventListResponses = { /** - * Successful retrieval of full user table + * Successful paginated list retrieval */ 200: UtilsRespStatus & { - data?: ServiceUserUserTableResponse; + data?: Array; }; }; -export type GetUserFullResponse = GetUserFullResponses[keyof GetUserFullResponses]; +export type GetEventListResponse = GetEventListResponses[keyof GetEventListResponses]; export type GetUserInfoData = { body?: never; @@ -654,7 +662,7 @@ export type GetUserListResponses = { * Successful paginated list retrieval */ 200: UtilsRespStatus & { - data?: Array; + data?: Array; }; }; diff --git a/client/cms/src/client/zod.gen.ts b/client/cms/src/client/zod.gen.ts index efd557b..66b4449 100644 --- a/client/cms/src/client/zod.gen.ts +++ b/client/cms/src/client/zod.gen.ts @@ -2,21 +2,16 @@ import { z } from 'zod'; -export const zDataUser = z.object({ - allow_public: z.optional(z.boolean()), - avatar: z.optional(z.string()), - bio: z.optional(z.string()), - email: z.optional(z.string()), - id: z.optional(z.int()), - nickname: z.optional(z.string()), - permission_level: z.optional(z.int()), - subtitle: z.optional(z.string()), - user_id: z.optional(z.string()), - username: z.optional(z.string()), - uuid: z.optional(z.string()) +export const zDataEventIndexDoc = z.object({ + description: z.optional(z.string()), + end_time: z.optional(z.string()), + event_id: z.optional(z.string()), + name: z.optional(z.string()), + start_time: z.optional(z.string()), + type: z.optional(z.string()) }); -export const zDataUserSearchDoc = z.object({ +export const zDataUserIndexDoc = z.object({ avatar: z.optional(z.string()), email: z.optional(z.string()), nickname: z.optional(z.string()), @@ -74,7 +69,7 @@ export const zServiceEventCheckinSubmitData = z.object({ checkin_code: z.optional(z.string()) }); -export const zServiceEventInfoResponse = z.object({ +export const zServiceEventEventInfoResponse = z.object({ end_time: z.optional(z.string()), name: z.optional(z.string()), start_time: z.optional(z.string()) @@ -92,10 +87,6 @@ export const zServiceUserUserInfoData = z.object({ username: z.optional(z.string()) }); -export const zServiceUserUserTableResponse = z.object({ - user_table: z.optional(z.array(zDataUser)) -}); - export const zUtilsRespStatus = z.object({ code: z.optional(z.int()), data: z.optional(z.unknown()), @@ -221,20 +212,23 @@ export const zGetEventInfoData = z.object({ * Successful retrieval */ export const zGetEventInfoResponse = zUtilsRespStatus.and(z.object({ - data: z.optional(zServiceEventInfoResponse) + data: z.optional(zServiceEventEventInfoResponse) })); -export const zGetUserFullData = z.object({ +export const zGetEventListData = z.object({ body: z.optional(z.never()), path: z.optional(z.never()), - query: z.optional(z.never()) + query: z.object({ + limit: z.optional(z.string()), + offset: z.string() + }) }); /** - * Successful retrieval of full user table + * Successful paginated list retrieval */ -export const zGetUserFullResponse = zUtilsRespStatus.and(z.object({ - data: z.optional(zServiceUserUserTableResponse) +export const zGetEventListResponse = zUtilsRespStatus.and(z.object({ + data: z.optional(z.array(zDataEventIndexDoc)) })); export const zGetUserInfoData = z.object({ @@ -263,7 +257,7 @@ export const zGetUserListData = z.object({ * Successful paginated list retrieval */ export const zGetUserListResponse = zUtilsRespStatus.and(z.object({ - data: z.optional(z.array(zDataUserSearchDoc)) + data: z.optional(z.array(zDataUserIndexDoc)) })); export const zPatchUserUpdateData = z.object({ diff --git a/client/cms/src/components/profile/edit-profile-dialog.tsx b/client/cms/src/components/profile/edit-profile-dialog.tsx index 47be953..236d23f 100644 --- a/client/cms/src/components/profile/edit-profile-dialog.tsx +++ b/client/cms/src/components/profile/edit-profile-dialog.tsx @@ -79,7 +79,7 @@ export function EditProfileDialog() { onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); - form.handleSubmit().then(() => setOpen(false)); + void form.handleSubmit().then(() => setOpen(false)); }} className="grid gap-4" > diff --git a/client/cms/src/components/profile/main-profile.tsx b/client/cms/src/components/profile/main-profile.tsx index 28d4adf..edcb613 100644 --- a/client/cms/src/components/profile/main-profile.tsx +++ b/client/cms/src/components/profile/main-profile.tsx @@ -35,7 +35,7 @@ export function MainProfile() {
- {user.avatar ? : IdentIcon} + {user.avatar !== undefined ? : IdentIcon}
diff --git a/client/cms/src/components/sidebar/nav-user.tsx b/client/cms/src/components/sidebar/nav-user.tsx index 5ac0f2c..6a895e9 100644 --- a/client/cms/src/components/sidebar/nav-user.tsx +++ b/client/cms/src/components/sidebar/nav-user.tsx @@ -53,7 +53,7 @@ function NavUser_() { className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground" > - {user.avatar ? : IdentIcon} + {user.avatar !== undefined ? : IdentIcon}
{user.nickname} @@ -73,7 +73,7 @@ function NavUser_() {
- {user.avatar ? : IdentIcon} + {user.avatar !== undefined ? : IdentIcon}
{user.nickname} diff --git a/client/cms/src/hooks/data/useExchangeToken.ts b/client/cms/src/hooks/data/useExchangeToken.ts index 92f153d..d146c21 100644 --- a/client/cms/src/hooks/data/useExchangeToken.ts +++ b/client/cms/src/hooks/data/useExchangeToken.ts @@ -6,7 +6,7 @@ export function useExchangeToken() { return useMutation({ ...postAuthExchangeMutation(), onSuccess: (data) => { - window.location.href = data.data?.redirect_uri!; + window.location.href = data.data!.redirect_uri!; }, onError: (error) => { console.error(error); diff --git a/client/cms/src/lib/client.ts b/client/cms/src/lib/client.ts index 452fb0d..94437c0 100644 --- a/client/cms/src/lib/client.ts +++ b/client/cms/src/lib/client.ts @@ -1,13 +1,12 @@ import { isEmpty, isNil } from 'lodash-es'; import { client } from '@/client/client.gen'; -import { router } from './router'; import { - clearTokens, doRefreshToken, + getAccessToken, getRefreshToken, - getToken, + logout, + setAccessToken, setRefreshToken, - setToken, } from './token'; export function configInternalApiClient() { @@ -19,8 +18,8 @@ export function configInternalApiClient() { }); client.interceptors.request.use((request) => { - const token = getToken(); - if (token) { + const token = getAccessToken(); + if (!isNil(token) && !isEmpty(token)) { request.headers.set('Authorization', `Bearer ${token}`); } return request; @@ -28,14 +27,21 @@ export function configInternalApiClient() { client.interceptors.response.use(async (response, request, options) => { if (response.status === 401) { - const refreshToken = getRefreshToken(); // Avoid infinite loop if the refresh token request itself fails - if (!request.url.includes('/auth/refresh') && !isNil(refreshToken)) { - try { - const refreshResponse = await doRefreshToken(); + if (request.url.includes('/auth/refresh')) { + // Refresh token failed, clear tokens and redirect to login page + logout('Session expired'); + } + else { + const refreshToken = getRefreshToken(); + if (isNil(refreshToken) || isEmpty(refreshToken)) { + logout('You are not logged in'); + } + else { + const refreshResponse = await doRefreshToken(refreshToken); if (!isEmpty(refreshResponse)) { const { access_token, refresh_token } = refreshResponse; - setToken(access_token!); + setAccessToken(access_token!); setRefreshToken(refresh_token!); const fetchFn = options.fetch ?? globalThis.fetch; @@ -50,11 +56,6 @@ export function configInternalApiClient() { }); } } - catch (e) { - clearTokens(); - await router.navigate({ to: '/authorize' }); - return response; - } } } return response; diff --git a/client/cms/src/lib/token.ts b/client/cms/src/lib/token.ts index 469a54e..96c745c 100644 --- a/client/cms/src/lib/token.ts +++ b/client/cms/src/lib/token.ts @@ -1,40 +1,52 @@ import type { ServiceAuthTokenResponse } from '@/client'; +import { toast } from 'sonner'; import { postAuthRefresh } from '@/client'; +import { router } from './router'; -export function setToken(token: string) { - localStorage.setItem('token', token); +const ACCESS_TOKEN_LOCALSTORAGE_KEY = 'token'; +const REFRESH_TOKEN_LOCALSTORAGE_KEY = 'refreshToken'; + +export function setAccessToken(token: string) { + localStorage.setItem(ACCESS_TOKEN_LOCALSTORAGE_KEY, token); } -export function getToken() { - return localStorage.getItem('token'); +export function getAccessToken() { + return localStorage.getItem(ACCESS_TOKEN_LOCALSTORAGE_KEY); } -export function removeToken() { - localStorage.removeItem('token'); -} - -export function hasToken() { - return getToken() !== null; +export function removeAccessToken() { + localStorage.removeItem(ACCESS_TOKEN_LOCALSTORAGE_KEY); } export function setRefreshToken(refreshToken: string) { - localStorage.setItem('refreshToken', refreshToken); + localStorage.setItem(REFRESH_TOKEN_LOCALSTORAGE_KEY, refreshToken); } export function getRefreshToken() { - return localStorage.getItem('refreshToken'); + return localStorage.getItem(REFRESH_TOKEN_LOCALSTORAGE_KEY); +} + +export function removeRefreshToken() { + localStorage.removeItem(REFRESH_TOKEN_LOCALSTORAGE_KEY); } export function clearTokens() { - removeToken(); - setRefreshToken(''); + removeAccessToken(); + removeRefreshToken(); } -export async function doRefreshToken(): Promise { +export async function doRefreshToken(refreshToken: string): Promise { const { data } = await postAuthRefresh({ body: { - refresh_token: getRefreshToken()!, + refresh_token: refreshToken, }, }); return data?.data; } + +export function logout(message: string = 'Logged out') { + clearTokens(); + void router.navigate({ to: '/authorize' }).then(() => { + toast.error(message); + }); +} diff --git a/client/cms/src/routes/_sidebarLayout/index.tsx b/client/cms/src/routes/_sidebarLayout/index.tsx index 0b42a00..023f537 100644 --- a/client/cms/src/routes/_sidebarLayout/index.tsx +++ b/client/cms/src/routes/_sidebarLayout/index.tsx @@ -1,15 +1,7 @@ -import { createFileRoute, redirect } from '@tanstack/react-router'; -import { hasToken } from '@/lib/token'; +import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/_sidebarLayout/')({ component: Index, - loader: async () => { - if (!hasToken()) { - throw redirect({ - to: '/authorize', - }); - } - }, }); function Index() { diff --git a/client/cms/src/routes/authorize.tsx b/client/cms/src/routes/authorize.tsx index f8ed2ed..05fd479 100644 --- a/client/cms/src/routes/authorize.tsx +++ b/client/cms/src/routes/authorize.tsx @@ -6,7 +6,7 @@ import z from 'zod'; import { LoginForm } from '@/components/login-form'; import { useExchangeToken } from '@/hooks/data/useExchangeToken'; import { generateOAuthState } from '@/lib/random'; -import { getToken } from '@/lib/token'; +import { getAccessToken } from '@/lib/token'; const baseUrl = import.meta.env.VITE_APP_BASE_URL; @@ -25,7 +25,7 @@ export const Route = createFileRoute('/authorize')({ }); function RouteComponent() { - const token = getToken(); + const token = getAccessToken(); const oauthParams = Route.useSearch(); const mutation = useExchangeToken(); /** diff --git a/client/cms/src/routes/token.tsx b/client/cms/src/routes/token.tsx index b2c26dd..06963b6 100644 --- a/client/cms/src/routes/token.tsx +++ b/client/cms/src/routes/token.tsx @@ -6,7 +6,7 @@ import { } from 'react'; import z from 'zod'; import { postAuthTokenMutation } from '@/client/@tanstack/react-query.gen'; -import { setRefreshToken, setToken } from '@/lib/token'; +import { setAccessToken, setRefreshToken } from '@/lib/token'; const tokenCodeSchema = z.object({ code: z.string().nonempty(), @@ -25,8 +25,8 @@ function RouteComponent() { const mutation = useMutation({ ...postAuthTokenMutation(), onSuccess: (data) => { - setToken(data.data?.access_token!); - setRefreshToken(data.data?.refresh_token!); + setAccessToken(data.data!.access_token!); + setRefreshToken(data.data!.refresh_token!); void navigate({ to: '/' }); }, onError: () => { -- 2.49.1 From 635b0fbb731c662582776ddbd588b52fc25a8d0a Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Sat, 31 Jan 2026 12:29:35 +0800 Subject: [PATCH 02/12] feat(client): add storybook and workbench profile flow Signed-off-by: Noa Virellia --- client/cms/.gitignore | 3 + client/cms/.storybook/main.ts | 17 + client/cms/.storybook/preview.tsx | 24 + client/cms/.storybook/vitest.setup.ts | 7 + client/cms/eslint.config.js | 1 + client/cms/package.json | 20 +- client/cms/pnpm-lock.yaml | 1346 ++++++++++++++++- .../src/client/@tanstack/react-query.gen.ts | 24 +- client/cms/src/client/index.ts | 4 +- client/cms/src/client/sdk.gen.ts | 9 +- client/cms/src/client/types.gen.ts | 105 +- client/cms/src/client/zod.gen.ts | 24 +- .../profile/edit-profile-dialog.tsx | 6 +- .../src/components/profile/profile.error.tsx | 30 + .../components/profile/profile.skeleton.tsx | 29 + .../profile/{main-profile.tsx => profile.tsx} | 27 +- .../cms/src/components/sidebar/nav-user.tsx | 5 +- .../components/workbenchCards/event-card.tsx | 39 + client/cms/src/hooks/data/useUserInfo.ts | 13 +- client/cms/src/lib/navData.ts | 6 + client/cms/src/routeTree.gen.ts | 150 +- .../cms/src/routes/_sidebarLayout/profile.tsx | 14 - ...sidebarLayout.tsx => _workbenchLayout.tsx} | 2 +- .../src/routes/_workbenchLayout/events.tsx | 9 + .../index.tsx | 2 +- .../_workbenchLayout/profile.$userId.tsx | 34 + .../routes/_workbenchLayout/profile.index.tsx | 13 + client/cms/src/stories/event-card.stories.ts | 12 + client/cms/src/stories/profile.stories.tsx | 51 + client/cms/vite.config.ts | 53 +- client/cms/vitest.shims.d.ts | 1 + 31 files changed, 1946 insertions(+), 134 deletions(-) create mode 100644 client/cms/.storybook/main.ts create mode 100644 client/cms/.storybook/preview.tsx create mode 100644 client/cms/.storybook/vitest.setup.ts create mode 100644 client/cms/src/components/profile/profile.error.tsx create mode 100644 client/cms/src/components/profile/profile.skeleton.tsx rename client/cms/src/components/profile/{main-profile.tsx => profile.tsx} (87%) create mode 100644 client/cms/src/components/workbenchCards/event-card.tsx delete mode 100644 client/cms/src/routes/_sidebarLayout/profile.tsx rename client/cms/src/routes/{_sidebarLayout.tsx => _workbenchLayout.tsx} (93%) create mode 100644 client/cms/src/routes/_workbenchLayout/events.tsx rename client/cms/src/routes/{_sidebarLayout => _workbenchLayout}/index.tsx (90%) create mode 100644 client/cms/src/routes/_workbenchLayout/profile.$userId.tsx create mode 100644 client/cms/src/routes/_workbenchLayout/profile.index.tsx create mode 100644 client/cms/src/stories/event-card.stories.ts create mode 100644 client/cms/src/stories/profile.stories.tsx create mode 100644 client/cms/vitest.shims.d.ts diff --git a/client/cms/.gitignore b/client/cms/.gitignore index 668095b..26384d2 100644 --- a/client/cms/.gitignore +++ b/client/cms/.gitignore @@ -24,3 +24,6 @@ dist-ssr *.sw? .direnv + +*storybook.log +storybook-static diff --git a/client/cms/.storybook/main.ts b/client/cms/.storybook/main.ts new file mode 100644 index 0000000..78cf7b3 --- /dev/null +++ b/client/cms/.storybook/main.ts @@ -0,0 +1,17 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + "stories": [ + "../src/**/*.mdx", + "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)" + ], + "addons": [ + "@chromatic-com/storybook", + "@storybook/addon-vitest", + "@storybook/addon-a11y", + "@storybook/addon-docs", + "@storybook/addon-onboarding" + ], + "framework": "@storybook/react-vite" +}; +export default config; \ No newline at end of file diff --git a/client/cms/.storybook/preview.tsx b/client/cms/.storybook/preview.tsx new file mode 100644 index 0000000..ab9f5f5 --- /dev/null +++ b/client/cms/.storybook/preview.tsx @@ -0,0 +1,24 @@ +import type { Preview } from '@storybook/react-vite'; +import { ThemeProvider } from '../src/components/theme-provider'; +import '../src/index.css'; + +const preview: Preview = { + decorators: [(Story) => ], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + + a11y: { + // 'todo' - show a11y violations in the test UI only + // 'error' - fail CI on a11y violations + // 'off' - skip a11y checks entirely + test: 'todo' + } + }, +}; + +export default preview; diff --git a/client/cms/.storybook/vitest.setup.ts b/client/cms/.storybook/vitest.setup.ts new file mode 100644 index 0000000..44922d5 --- /dev/null +++ b/client/cms/.storybook/vitest.setup.ts @@ -0,0 +1,7 @@ +import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; +import { setProjectAnnotations } from '@storybook/react-vite'; +import * as projectAnnotations from './preview'; + +// This is an important step to apply the right configuration when testing your stories. +// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations +setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]); \ No newline at end of file diff --git a/client/cms/eslint.config.js b/client/cms/eslint.config.js index d60f5e0..fe10bc6 100644 --- a/client/cms/eslint.config.js +++ b/client/cms/eslint.config.js @@ -1,3 +1,4 @@ +// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format import antfu from '@antfu/eslint-config'; import pluginQuery from '@tanstack/eslint-plugin-query'; diff --git a/client/cms/package.json b/client/cms/package.json index 7350502..e6996a9 100644 --- a/client/cms/package.json +++ b/client/cms/package.json @@ -9,7 +9,9 @@ "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview", - "gen": "openapi-ts" + "gen": "openapi-ts", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" }, "dependencies": { "@base-ui/react": "^1.1.0", @@ -58,6 +60,7 @@ "qrcode": "^1.5.4", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-error-boundary": "^6.1.0", "react-hook-form": "^7.69.0", "react-markdown": "^10.1.0", "recharts": "2.15.4", @@ -71,9 +74,16 @@ }, "devDependencies": { "@antfu/eslint-config": "^6.7.1", + "@chromatic-com/storybook": "^5.0.0", "@eslint-react/eslint-plugin": "^2.3.13", "@eslint/js": "^9.39.1", "@hey-api/openapi-ts": "0.91.0", + "@storybook/addon-a11y": "^10.2.3", + "@storybook/addon-docs": "^10.2.3", + "@storybook/addon-onboarding": "^10.2.3", + "@storybook/addon-themes": "^10.2.3", + "@storybook/addon-vitest": "^10.2.3", + "@storybook/react-vite": "^10.2.3", "@tailwindcss/typography": "^0.5.19", "@tanstack/eslint-plugin-query": "^5.91.2", "@tanstack/router-plugin": "^1.141.7", @@ -86,18 +96,24 @@ "@types/react-dom": "^19.2.3", "@types/utf8": "^3.0.3", "@vitejs/plugin-react": "^5.1.1", + "@vitest/browser-playwright": "^4.0.18", + "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.26", + "eslint-plugin-storybook": "^10.2.3", "globals": "^16.5.0", "lint-staged": "^16.2.7", + "playwright": "^1.58.0", "simple-git-hooks": "^2.13.1", + "storybook": "^10.2.3", "tw-animate-css": "^1.4.0", "type-fest": "^5.4.1", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", "vite": "^7.2.4", - "vite-plugin-svgr": "^4.5.0" + "vite-plugin-svgr": "^4.5.0", + "vitest": "^4.0.18" }, "simple-git-hooks": { "pre-commit": "bun run lint-staged" diff --git a/client/cms/pnpm-lock.yaml b/client/cms/pnpm-lock.yaml index 412997d..0117e43 100644 --- a/client/cms/pnpm-lock.yaml +++ b/client/cms/pnpm-lock.yaml @@ -146,6 +146,9 @@ importers: react-dom: specifier: ^19.2.0 version: 19.2.3(react@19.2.3) + react-error-boundary: + specifier: ^6.1.0 + version: 6.1.0(react@19.2.3) react-hook-form: specifier: ^7.69.0 version: 7.71.1(react@19.2.3) @@ -179,7 +182,10 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^6.7.1 - version: 6.7.3(@eslint-react/eslint-plugin@2.7.2(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.27)(eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)))(eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 6.7.3(@eslint-react/eslint-plugin@2.7.2(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.27)(eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)))(eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18) + '@chromatic-com/storybook': + specifier: ^5.0.0 + version: 5.0.0(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) '@eslint-react/eslint-plugin': specifier: ^2.3.13 version: 2.7.2(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -188,7 +194,25 @@ importers: version: 9.39.2 '@hey-api/openapi-ts': specifier: 0.91.0 - version: 0.91.0(typescript@5.9.3) + version: 0.91.0(magicast@0.5.1)(typescript@5.9.3) + '@storybook/addon-a11y': + specifier: ^10.2.3 + version: 10.2.3(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@storybook/addon-docs': + specifier: ^10.2.3 + version: 10.2.3(@types/react@19.2.8)(esbuild@0.27.2)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@storybook/addon-onboarding': + specifier: ^10.2.3 + version: 10.2.3(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@storybook/addon-themes': + specifier: ^10.2.3 + version: 10.2.3(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@storybook/addon-vitest': + specifier: ^10.2.3 + version: 10.2.3(@vitest/browser-playwright@4.0.18)(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18))(@vitest/runner@4.0.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vitest@4.0.18) + '@storybook/react-vite': + specifier: ^10.2.3 + version: 10.2.3(esbuild@0.27.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.19(tailwindcss@4.1.18) @@ -225,6 +249,12 @@ importers: '@vitejs/plugin-react': specifier: ^5.1.1 version: 5.1.2(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/browser-playwright': + specifier: ^4.0.18 + version: 4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18) + '@vitest/coverage-v8': + specifier: ^4.0.18 + version: 4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18))(vitest@4.0.18) eslint: specifier: ^9.39.1 version: 9.39.2(jiti@2.6.1) @@ -234,15 +264,24 @@ importers: eslint-plugin-react-refresh: specifier: ^0.4.26 version: 0.4.26(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-storybook: + specifier: ^10.2.3 + version: 10.2.3(eslint@9.39.2(jiti@2.6.1))(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) globals: specifier: ^16.5.0 version: 16.5.0 lint-staged: specifier: ^16.2.7 version: 16.2.7 + playwright: + specifier: ^1.58.0 + version: 1.58.0 simple-git-hooks: specifier: ^2.13.1 version: 2.13.1 + storybook: + specifier: ^10.2.3 + version: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -261,9 +300,15 @@ importers: vite-plugin-svgr: specifier: ^4.5.0 version: 4.5.0(rollup@4.55.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + vitest: + specifier: ^4.0.18 + version: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@antfu/eslint-config@6.7.3': resolution: {integrity: sha512-0tYYzY59uLnxWgbP9xpuxpvodTcWDacj439kTAJZB3sn7O0BnPfVxTnRvleGYaKCEALBZkzdC/wCho9FD7ICLw==} hasBin: true @@ -442,6 +487,16 @@ packages: '@types/react': optional: true + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@chromatic-com/storybook@5.0.0': + resolution: {integrity: sha512-8wUsqL8kg6R5ue8XNE7Jv/iD1SuE4+6EXMIGIuE+T2loBITEACLfC3V8W44NJviCLusZRMWbzICddz0nU0bFaw==} + engines: {node: '>=20.0.0', yarn: '>=1.22.18'} + peerDependencies: + storybook: ^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 + '@clack/core@0.5.0': resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} @@ -996,6 +1051,27 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.3': + resolution: {integrity: sha512-9TGZuAX+liGkNKkwuo3FYJu7gHWT0vkBcf7GkOe7s7fmC19XwH/4u5u7sDIFrMooe558ORcmuBvBz7Ur5PlbHw==} + peerDependencies: + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1021,10 +1097,22 @@ packages: react: ^17.0.2 || ^18.0.0 || ^19.0 react-dom: ^17.0.2 || ^18.0.0 || ^19.0 + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + + '@neoconfetti/react@1.0.0': + resolution: {integrity: sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==} + '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -1640,9 +1728,109 @@ packages: resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==} engines: {node: '>=18'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@storybook/addon-a11y@10.2.3': + resolution: {integrity: sha512-EZLTmu/f5uENvbTKzCXlRN9TpaiKVzMfw4JF3qkw4Efae12xTJMIWieehHhl3Clc1x2d6ZtE7vhtMI9mKYQdKw==} + peerDependencies: + storybook: ^10.2.3 + + '@storybook/addon-docs@10.2.3': + resolution: {integrity: sha512-IPprt2qp4HN1uyE1Ki1sH0ZOE5B6z5sKzEMfrKMGokYKYk/AAJVfSiVIKju3q525GrBFlNhRW2+fB4pQfklv2w==} + peerDependencies: + storybook: ^10.2.3 + + '@storybook/addon-onboarding@10.2.3': + resolution: {integrity: sha512-slSExgyykz9lZMVpHu72L8tT3lzPTX6NXpcHYAkof4J+iu5Dl94vdBK320jri61Ox5xKh9h7CTkOK8jSUJP8mA==} + peerDependencies: + storybook: ^10.2.3 + + '@storybook/addon-themes@10.2.3': + resolution: {integrity: sha512-8OVXivm1pDvf2D38RCzzvVqergvFwwG+bNRtevzXW/fjdJ1dG2hMnVlqiONka1Q9w2a2XOxeDBjRxuos0oc3rA==} + peerDependencies: + storybook: ^10.2.3 + + '@storybook/addon-vitest@10.2.3': + resolution: {integrity: sha512-6ucVP+J3taV19/YxCZCUaG2VXku5DFKSK9zzNAd8ba/SNpoU3viy2km7kGvgyEZmSGHEZJpLdefbbPepxJreGg==} + peerDependencies: + '@vitest/browser': ^3.0.0 || ^4.0.0 + '@vitest/browser-playwright': ^4.0.0 + '@vitest/runner': ^3.0.0 || ^4.0.0 + storybook: ^10.2.3 + vitest: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/runner': + optional: true + vitest: + optional: true + + '@storybook/builder-vite@10.2.3': + resolution: {integrity: sha512-sKSccERL23gqC+nkTD+4io3ZF8vvNXkNiSi16X6BC29sGsbgWohw3Nv6tIGwVkIlQh0b1z24EQgXYWbdqivHGw==} + peerDependencies: + storybook: ^10.2.3 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@storybook/csf-plugin@10.2.3': + resolution: {integrity: sha512-/b/C8C40ukzXs3Xauud2+yOJqwBdOkADfRtJ9O4TzrhftzkEdqsNI03xXZySeh7eXW8eI3Vq4t75Ljuj27Xytw==} + peerDependencies: + esbuild: '*' + rollup: '*' + storybook: ^10.2.3 + vite: '*' + webpack: '*' + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + + '@storybook/icons@2.0.1': + resolution: {integrity: sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@storybook/react-dom-shim@10.2.3': + resolution: {integrity: sha512-xMZXvjfQCsmzOTqFCRQ1/gxs//jDGLlnmBCikH4NSGPPogRPaNUkxgdNjOResd6pB+G3ZYAOspJkmGEEbq8dVw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.2.3 + + '@storybook/react-vite@10.2.3': + resolution: {integrity: sha512-2muczz7r/Mnwscjzliwm8qrcAcJyqkgMA9kpgjDm5jh0EOo2ADHeW4515tRoiYOjeYCa3ixDNs28uhIvz/8BNA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.2.3 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@storybook/react@10.2.3': + resolution: {integrity: sha512-M67G7IY9TcLQQJ/9mHPItIiNvZFyuXf5r/wBY03YGquwCqo4GtLdp9uyGg3uCc2i0dS5VV5OQenisldmdjWFWQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.2.3 + typescript: '>= 4.9.x' + peerDependenciesMeta: + typescript: + optional: true + '@stylistic/eslint-plugin@5.7.0': resolution: {integrity: sha512-PsSugIf9ip1H/mWKj4bi/BlEoerxXAda9ByRFsYuwsmr6af9NxJL0AaiNXs8Le7R21QR5KMiD/KdxZZ71LjAxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1961,6 +2149,23 @@ packages: peerDependencies: zod: ^3.x + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1976,6 +2181,9 @@ packages: '@types/base-64@1.0.2': resolution: {integrity: sha512-uPgKMmM9fmn7I+Zi6YBqctOye4SlJsHKcisjHIMWpb2YKZRc36GpKyNuQ03JcT+oNXg1m7Uv4wU94EVltn8/cw==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/culori@4.0.1': resolution: {integrity: sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ==} @@ -2009,6 +2217,12 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -2033,6 +2247,9 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -2053,6 +2270,9 @@ packages: '@types/react@19.2.8': resolution: {integrity: sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -2145,6 +2365,26 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/browser-playwright@4.0.18': + resolution: {integrity: sha512-gfajTHVCiwpxRj1qh0Sh/5bbGLG4F/ZH/V9xvFVoFddpITfMta9YGow0W6ZpTTORv2vdJuz9TnrNSmjKvpOf4g==} + peerDependencies: + playwright: '*' + vitest: 4.0.18 + + '@vitest/browser@4.0.18': + resolution: {integrity: sha512-gVQqh7paBz3gC+ZdcCmNSWJMk70IUjDeVqi+5m5vYpEHsIwRgw3Y545jljtajhkekIpIp5Gg8oK7bctgY0E2Ng==} + peerDependencies: + vitest: 4.0.18 + + '@vitest/coverage-v8@4.0.18': + resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==} + peerDependencies: + '@vitest/browser': 4.0.18 + vitest: 4.0.18 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/eslint-plugin@1.6.6': resolution: {integrity: sha512-bwgQxQWRtnTVzsUHK824tBmHzjV0iTx3tZaiQIYDjX3SA7TsQS8CuDVqxXrRY3FaOUMgbGavesCxI9MOfFLm7Q==} engines: {node: '>=18'} @@ -2158,6 +2398,47 @@ packages: vitest: optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@vue/compiler-core@3.5.27': resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==} @@ -2206,6 +2487,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -2229,13 +2514,31 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} + ast-v8-to-istanbul@0.3.10: + resolution: {integrity: sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + axe-core@4.11.1: + resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} + engines: {node: '>=4'} + axios@1.13.2: resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} @@ -2331,6 +2634,14 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2350,6 +2661,10 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2358,6 +2673,18 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + chromatic@13.3.5: + resolution: {integrity: sha512-MzPhxpl838qJUo0A55osCF2ifwPbjcIPeElr1d4SHcjnHoIcg7l1syJDrAYK/a+PcCBrOGi06jPNpQAln5hWgw==} + hasBin: true + peerDependencies: + '@chromatic-com/cypress': ^0.*.* || ^1.0.0 + '@chromatic-com/playwright': ^0.*.* || ^1.0.0 + peerDependenciesMeta: + '@chromatic-com/cypress': + optional: true + '@chromatic-com/playwright': + optional: true + ci-info@4.3.1: resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} engines: {node: '>=8'} @@ -2464,6 +2791,9 @@ packages: css-selector-parser@3.3.0: resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2539,6 +2869,10 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2593,6 +2927,16 @@ packages: resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} hasBin: true + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -2607,6 +2951,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} @@ -2616,6 +2963,9 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} @@ -2651,6 +3001,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -2827,6 +3180,12 @@ packages: peerDependencies: eslint: '>=8.44.0' + eslint-plugin-storybook@10.2.3: + resolution: {integrity: sha512-5wy+OKe6VexZecAedroKv+GR+agciZqK/Su7cdo6b1mICWaWwejU/XjjTLL9zr6wiEjCN/0mhYg7yz70DoaMQQ==} + peerDependencies: + eslint: '>=8' + storybook: ^10.2.3 + eslint-plugin-toml@0.12.0: resolution: {integrity: sha512-+/wVObA9DVhwZB1nG83D2OAQRrcQZXy+drqUnFJKymqnmbnbfg/UPmEMCKrJNcEboUGxUjYrJlgy+/Y930mURQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2935,6 +3294,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2945,6 +3307,10 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -2980,6 +3346,10 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + filesize@10.1.6: + resolution: {integrity: sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==} + engines: {node: '>= 10.4.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -3012,6 +3382,10 @@ packages: debug: optional: true + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -3020,6 +3394,11 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3070,6 +3449,11 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@11.1.0: + resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} + engines: {node: 20 || >=22} + hasBin: true + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -3173,6 +3557,9 @@ packages: html-entities@2.6.0: resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -3201,6 +3588,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + indent-string@5.0.0: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} @@ -3229,6 +3620,10 @@ packages: resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} engines: {node: '>=18.20'} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} @@ -3290,6 +3685,22 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -3297,6 +3708,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -3335,6 +3749,9 @@ packages: resolution: {integrity: sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3456,9 +3873,16 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@11.2.5: + resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -3467,9 +3891,20 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -3628,6 +4063,14 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -3635,9 +4078,20 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3691,6 +4145,9 @@ packages: object-deep-merge@2.0.0: resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -3698,6 +4155,10 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + open@11.0.0: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} @@ -3726,6 +4187,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} @@ -3764,6 +4228,13 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3771,6 +4242,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} @@ -3790,12 +4265,26 @@ packages: engines: {node: '>=0.10'} hasBin: true + pixelmatch@7.1.0: + resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} + hasBin: true + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + playwright-core@1.58.0: + resolution: {integrity: sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.0: + resolution: {integrity: sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==} + engines: {node: '>=18'} + hasBin: true + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -3804,6 +4293,10 @@ packages: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + pnpm-workspace-yaml@1.5.0: resolution: {integrity: sha512-PxdyJuFvq5B0qm3s9PaH/xOtSxrcvpBRr+BblhucpWjs8c79d4b7/cXhyY4AyHOHCnqklCYZTjfl0bT/mFVTRw==} @@ -3832,6 +4325,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -3859,11 +4356,25 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} + peerDependencies: + typescript: '>= 4.3.x' + + react-docgen@8.0.2: + resolution: {integrity: sha512-+NRMYs2DyTP4/tqWz371Oo50JqmWltR1h2gcdgUMAWZJIAvrd0/SqlCfx7tpzpl/s36rzw6qH2MjoNrxtRNYhA==} + engines: {node: ^20.9.0 || >=22} + react-dom@19.2.3: resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: react: ^19.2.3 + react-error-boundary@6.1.0: + resolution: {integrity: sha512-02k9WQ/mUhdbXir0tC1NiMesGzRPaCsJEWU/4bcFrbY1YMZOtHShtZP6zw0SJrBWA/31H0KT9/FgdL8+sPKgHA==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-hook-form@7.71.1: resolution: {integrity: sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==} engines: {node: '>=18.0.0'} @@ -3873,6 +4384,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -3960,6 +4474,10 @@ packages: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + refa@0.12.1: resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -4052,6 +4570,11 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -4105,6 +4628,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -4113,6 +4639,10 @@ packages: resolution: {integrity: sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==} hasBin: true + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -4153,6 +4683,21 @@ packages: spdx-license-ids@3.0.22: resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + storybook@10.2.3: + resolution: {integrity: sha512-kjsJ0hctkTO0ipHiyv1MY39wP4tAyVM7rPQGyVMU1iQ7NYHxthiiCHhFB/szmVjXdJa58fu3ZH5cwENMn8Y5eA==} + hasBin: true + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -4164,6 +4709,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -4183,6 +4732,14 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-indent@4.1.1: resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} engines: {node: '>=12'} @@ -4201,6 +4758,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} @@ -4231,6 +4792,9 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@1.0.2: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} @@ -4239,6 +4803,18 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -4251,6 +4827,10 @@ packages: resolution: {integrity: sha512-9mjy3frhioGIVGcwamlVlUyJ9x+WHw/TXiz9R4YOlmsIuBN43r9Dp8HZ35SF9EKjHrn3BUZj04CF+YqZ2oJ+7w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -4268,9 +4848,17 @@ packages: peerDependencies: typescript: '>=4.0.0' + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + ts-pattern@5.9.0: resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -4329,6 +4917,10 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -4436,6 +5028,40 @@ packages: yaml: optional: true + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vue-eslint-parser@10.2.0: resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4456,6 +5082,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4464,10 +5095,34 @@ packages: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -4542,7 +5197,9 @@ packages: snapshots: - '@antfu/eslint-config@6.7.3(@eslint-react/eslint-plugin@2.7.2(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.27)(eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)))(eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@adobe/css-tools@4.4.4': {} + + '@antfu/eslint-config@6.7.3(@eslint-react/eslint-plugin@2.7.2(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.27)(eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)))(eslint-plugin-react-refresh@0.4.26(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 0.11.0 @@ -4551,7 +5208,7 @@ snapshots: '@stylistic/eslint-plugin': 5.7.0(eslint@9.39.2(jiti@2.6.1)) '@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@vitest/eslint-plugin': 1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@vitest/eslint-plugin': 1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18) ansis: 4.2.0 cac: 6.7.14 eslint: 9.39.2(jiti@2.6.1) @@ -4746,6 +5403,20 @@ snapshots: optionalDependencies: '@types/react': 19.2.8 + '@bcoe/v8-coverage@1.0.2': {} + + '@chromatic-com/storybook@5.0.0(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + dependencies: + '@neoconfetti/react': 1.0.0 + chromatic: 13.3.5 + filesize: 10.1.6 + jsonfile: 6.2.0 + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + strip-ansi: 7.1.2 + transitivePeerDependencies: + - '@chromatic-com/cypress' + - '@chromatic-com/playwright' + '@clack/core@0.5.0': dependencies: picocolors: 1.1.1 @@ -5202,11 +5873,11 @@ snapshots: '@floating-ui/utils@0.2.10': {} - '@hey-api/codegen-core@0.6.0(typescript@5.9.3)': + '@hey-api/codegen-core@0.6.0(magicast@0.5.1)(typescript@5.9.3)': dependencies: '@hey-api/types': 0.1.3(typescript@5.9.3) ansi-colors: 4.1.3 - c12: 3.3.3 + c12: 3.3.3(magicast@0.5.1) color-support: 1.1.3 typescript: 5.9.3 transitivePeerDependencies: @@ -5219,11 +5890,11 @@ snapshots: js-yaml: 4.1.1 lodash: 4.17.21 - '@hey-api/openapi-ts@0.91.0(typescript@5.9.3)': + '@hey-api/openapi-ts@0.91.0(magicast@0.5.1)(typescript@5.9.3)': dependencies: - '@hey-api/codegen-core': 0.6.0(typescript@5.9.3) + '@hey-api/codegen-core': 0.6.0(magicast@0.5.1)(typescript@5.9.3) '@hey-api/json-schema-ref-parser': 1.2.3 - '@hey-api/shared': 0.1.0(typescript@5.9.3) + '@hey-api/shared': 0.1.0(magicast@0.5.1)(typescript@5.9.3) '@hey-api/types': 0.1.3(typescript@5.9.3) ansi-colors: 4.1.3 color-support: 1.1.3 @@ -5232,9 +5903,9 @@ snapshots: transitivePeerDependencies: - magicast - '@hey-api/shared@0.1.0(typescript@5.9.3)': + '@hey-api/shared@0.1.0(magicast@0.5.1)(typescript@5.9.3)': dependencies: - '@hey-api/codegen-core': 0.6.0(typescript@5.9.3) + '@hey-api/codegen-core': 0.6.0(magicast@0.5.1)(typescript@5.9.3) '@hey-api/json-schema-ref-parser': 1.2.3 '@hey-api/types': 0.1.3(typescript@5.9.3) ansi-colors: 4.1.3 @@ -5265,6 +5936,29 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@joshwooding/vite-plugin-react-docgen-typescript@0.6.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + glob: 11.1.0 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + optionalDependencies: + typescript: 5.9.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5291,8 +5985,18 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + '@mdx-js/react@3.1.1(@types/react@19.2.8)(react@19.2.3)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 19.2.8 + react: 19.2.3 + + '@neoconfetti/react@1.0.0': {} + '@pkgr/core@0.2.9': {} + '@polka/url@1.0.0-next.29': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -5841,8 +6545,124 @@ snapshots: '@sindresorhus/base62@1.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} + '@storybook/addon-a11y@10.2.3(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + dependencies: + '@storybook/global': 5.0.0 + axe-core: 4.11.1 + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + + '@storybook/addon-docs@10.2.3(@types/react@19.2.8)(esbuild@0.27.2)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@mdx-js/react': 3.1.1(@types/react@19.2.8)(react@19.2.3) + '@storybook/csf-plugin': 10.2.3(esbuild@0.27.2)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@storybook/icons': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@storybook/react-dom-shim': 10.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + ts-dedent: 2.2.0 + transitivePeerDependencies: + - '@types/react' + - esbuild + - rollup + - vite + - webpack + + '@storybook/addon-onboarding@10.2.3(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + dependencies: + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + + '@storybook/addon-themes@10.2.3(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + dependencies: + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + ts-dedent: 2.2.0 + + '@storybook/addon-vitest@10.2.3(@vitest/browser-playwright@4.0.18)(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18))(@vitest/runner@4.0.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vitest@4.0.18)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + optionalDependencies: + '@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18) + '@vitest/browser-playwright': 4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18) + '@vitest/runner': 4.0.18 + vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + transitivePeerDependencies: + - react + - react-dom + + '@storybook/builder-vite@10.2.3(esbuild@0.27.2)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@storybook/csf-plugin': 10.2.3(esbuild@0.27.2)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + ts-dedent: 2.2.0 + vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + transitivePeerDependencies: + - esbuild + - rollup + - webpack + + '@storybook/csf-plugin@10.2.3(esbuild@0.27.2)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + unplugin: 2.3.11 + optionalDependencies: + esbuild: 0.27.2 + rollup: 4.55.2 + vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + + '@storybook/global@5.0.0': {} + + '@storybook/icons@2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + '@storybook/react-dom-shim@10.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + + '@storybook/react-vite@10.2.3(esbuild@0.27.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@rollup/pluginutils': 5.3.0(rollup@4.55.2) + '@storybook/builder-vite': 10.2.3(esbuild@0.27.2)(rollup@4.55.2)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@storybook/react': 10.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) + empathic: 2.0.0 + magic-string: 0.30.21 + react: 19.2.3 + react-docgen: 8.0.2 + react-dom: 19.2.3(react@19.2.3) + resolve: 1.22.11 + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + tsconfig-paths: 4.2.0 + vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + transitivePeerDependencies: + - esbuild + - rollup + - supports-color + - typescript + - webpack + + '@storybook/react@10.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + react: 19.2.3 + react-docgen: 8.0.2 + react-dom: 19.2.3(react@19.2.3) + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@stylistic/eslint-plugin@5.7.0(eslint@9.39.2(jiti@2.6.1))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) @@ -6161,6 +6981,32 @@ snapshots: '@tanstack/form-core': 0.42.1 zod: 4.3.5 + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.6 @@ -6184,6 +7030,11 @@ snapshots: '@types/base-64@1.0.2': {} + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/culori@4.0.1': {} '@types/d3-array@3.2.2': {} @@ -6214,6 +7065,10 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + + '@types/doctrine@0.0.9': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 @@ -6240,6 +7095,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/mdx@2.0.13': {} + '@types/ms@2.1.0': {} '@types/node@25.0.9': @@ -6260,6 +7117,8 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/resolve@1.20.6': {} + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -6406,16 +7265,124 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/eslint-plugin@1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@vitest/browser-playwright@4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)': + dependencies: + '@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18) + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + playwright: 1.58.0 + tinyrainbow: 3.0.3 + vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18)': + dependencies: + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/utils': 4.0.18 + magic-string: 0.30.21 + pixelmatch: 7.1.0 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.0.3 + vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/coverage-v8@4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18))(vitest@4.0.18)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.18 + ast-v8-to-istanbul: 0.3.10 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.1 + obug: 2.1.1 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + optionalDependencies: + '@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18) + + '@vitest/eslint-plugin@1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18)': dependencies: '@typescript-eslint/scope-manager': 8.53.1 '@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) optionalDependencies: typescript: 5.9.3 + vitest: 4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + '@vue/compiler-core@3.5.27': dependencies: '@babel/parser': 7.28.6 @@ -6475,6 +7442,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} ansis@4.2.0: {} @@ -6492,12 +7461,28 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + ast-types@0.16.1: dependencies: tslib: 2.8.1 + ast-v8-to-istanbul@0.3.10: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + asynckit@0.4.0: {} + axe-core@4.11.1: {} + axios@1.13.2: dependencies: follow-redirects: 1.15.11 @@ -6565,7 +7550,7 @@ snapshots: dependencies: run-applescript: 7.1.0 - c12@3.3.3: + c12@3.3.3(magicast@0.5.1): dependencies: chokidar: 5.0.0 confbox: 0.2.2 @@ -6579,6 +7564,8 @@ snapshots: perfect-debounce: 2.1.0 pkg-types: 2.3.0 rc9: 2.1.2 + optionalDependencies: + magicast: 0.5.1 cac@6.7.14: {} @@ -6597,6 +7584,16 @@ snapshots: ccount@2.0.1: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6612,6 +7609,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.3: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -6628,6 +7627,8 @@ snapshots: dependencies: readdirp: 5.0.0 + chromatic@13.3.5: {} + ci-info@4.3.1: {} citty@0.1.6: @@ -6718,6 +7719,8 @@ snapshots: css-selector-parser@3.3.0: {} + css.escape@1.5.1: {} + cssesc@3.0.0: {} csstype@3.2.3: {} @@ -6774,6 +7777,8 @@ snapshots: dependencies: character-entities: 2.0.2 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} default-browser-id@5.0.1: {} @@ -6809,6 +7814,14 @@ snapshots: direction@2.0.1: {} + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.28.6 @@ -6827,12 +7840,16 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.267: {} emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + empathic@2.0.0: {} enhanced-resolve@5.18.4: @@ -6856,6 +7873,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -7147,6 +8166,15 @@ snapshots: regexp-ast-analysis: 0.7.1 scslre: 0.3.0 + eslint-plugin-storybook@10.2.3(eslint@9.39.2(jiti@2.6.1))(storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3): + dependencies: + '@typescript-eslint/utils': 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + storybook: 10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + transitivePeerDependencies: + - supports-color + - typescript + eslint-plugin-toml@0.12.0(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 4.4.3 @@ -7302,12 +8330,18 @@ snapshots: estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} eventemitter3@4.0.7: {} eventemitter3@5.0.4: {} + expect-type@1.3.0: {} + exsolve@1.0.8: {} extend@3.0.2: {} @@ -7332,6 +8366,8 @@ snapshots: dependencies: flat-cache: 4.0.1 + filesize@10.1.6: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -7357,6 +8393,11 @@ snapshots: follow-redirects@1.15.11: {} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -7367,6 +8408,9 @@ snapshots: format@0.2.2: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -7421,6 +8465,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@11.1.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.1.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.1 + globals@14.0.0: {} globals@15.15.0: {} @@ -7601,6 +8654,8 @@ snapshots: html-entities@2.6.0: {} + html-escaper@2.0.2: {} + html-url-attributes@3.0.1: {} html-void-elements@3.0.0: {} @@ -7620,6 +8675,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + indent-string@5.0.0: {} inline-style-parser@0.2.7: {} @@ -7643,6 +8700,10 @@ snapshots: dependencies: builtin-modules: 5.0.0 + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + is-decimal@2.0.1: {} is-docker@3.0.0: {} @@ -7689,10 +8750,29 @@ snapshots: isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + jiti@2.6.1: {} js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -7720,6 +8800,12 @@ snapshots: espree: 9.6.1 semver: 7.7.3 + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -7833,10 +8919,14 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lower-case@2.0.2: dependencies: tslib: 2.8.1 + lru-cache@11.2.5: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -7845,10 +8935,22 @@ snapshots: dependencies: react: 19.2.3 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.5.1: + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + markdown-table@3.0.4: {} math-intrinsics@1.1.0: {} @@ -8228,6 +9330,12 @@ snapshots: mimic-function@5.0.1: {} + min-indent@1.0.1: {} + + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -8236,6 +9344,10 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minimist@1.2.8: {} + + minipass@7.1.2: {} + mlly@1.8.0: dependencies: acorn: 8.15.0 @@ -8243,6 +9355,8 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 + mrmime@2.0.1: {} + ms@2.1.3: {} nano-spawn@2.0.0: {} @@ -8283,12 +9397,21 @@ snapshots: object-deep-merge@2.0.0: {} + obug@2.1.1: {} + ohash@2.0.11: {} onetime@7.0.0: dependencies: mimic-function: 5.0.1 + open@10.2.0: + dependencies: + default-browser: 5.4.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + open@11.0.0: dependencies: default-browser: 5.4.0 @@ -8325,6 +9448,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + package-manager-detector@1.6.0: {} parent-module@1.0.1: @@ -8366,10 +9491,19 @@ snapshots: path-key@3.1.1: {} + path-parse@1.0.7: {} + + path-scurry@2.0.1: + dependencies: + lru-cache: 11.2.5 + minipass: 7.1.2 + path-type@4.0.0: {} pathe@2.0.3: {} + pathval@2.0.1: {} + perfect-debounce@2.1.0: {} picocolors@1.1.1: {} @@ -8380,6 +9514,10 @@ snapshots: pidtree@0.6.0: {} + pixelmatch@7.1.0: + dependencies: + pngjs: 7.0.0 + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -8392,10 +9530,20 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + playwright-core@1.58.0: {} + + playwright@1.58.0: + dependencies: + playwright-core: 1.58.0 + optionalDependencies: + fsevents: 2.3.2 + pluralize@8.0.0: {} pngjs@5.0.0: {} + pngjs@7.0.0: {} + pnpm-workspace-yaml@1.5.0: dependencies: yaml: 2.8.2 @@ -8422,6 +9570,12 @@ snapshots: prettier@3.8.0: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -8449,17 +9603,42 @@ snapshots: defu: 6.1.4 destr: 2.0.5 + react-docgen-typescript@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + react-docgen@8.0.2: + dependencies: + '@babel/core': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + '@types/doctrine': 0.0.9 + '@types/resolve': 1.20.6 + doctrine: 3.0.0 + resolve: 1.22.11 + strip-indent: 4.1.1 + transitivePeerDependencies: + - supports-color + react-dom@19.2.3(react@19.2.3): dependencies: react: 19.2.3 scheduler: 0.27.0 + react-error-boundary@6.1.0(react@19.2.3): + dependencies: + react: 19.2.3 + react-hook-form@7.71.1(react@19.2.3): dependencies: react: 19.2.3 react-is@16.13.1: {} + react-is@17.0.2: {} + react-is@18.3.1: {} react-markdown@10.1.0(@types/react@19.2.8)(react@19.2.3): @@ -8576,6 +9755,11 @@ snapshots: tiny-invariant: 1.3.3 victory-vendor: 36.9.2 + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + refa@0.12.1: dependencies: '@eslint-community/regexpp': 4.12.2 @@ -8725,6 +9909,12 @@ snapshots: resolve-pkg-maps@1.0.0: {} + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -8791,10 +9981,18 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} simple-git-hooks@2.13.1: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} slice-ansi@7.1.2: @@ -8829,6 +10027,33 @@ snapshots: spdx-license-ids@3.0.22: {} + stackback@0.0.2: {} + + std-env@3.10.0: {} + + storybook@10.2.3(@testing-library/dom@10.4.1)(prettier@3.8.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/spy': 3.2.4 + esbuild: 0.27.2 + open: 10.2.0 + recast: 0.23.11 + semver: 7.7.3 + use-sync-external-store: 1.6.0(react@19.2.3) + ws: 8.19.0 + optionalDependencies: + prettier: 3.8.0 + transitivePeerDependencies: + - '@testing-library/dom' + - bufferutil + - react + - react-dom + - utf-8-validate + string-argv@0.3.2: {} string-ts@2.3.1: {} @@ -8839,6 +10064,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -8863,6 +10094,12 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-indent@4.1.1: {} strip-json-comments@3.1.1: {} @@ -8879,6 +10116,8 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + svg-parser@2.0.4: {} synckit@0.11.12: @@ -8899,6 +10138,8 @@ snapshots: tiny-warning@1.0.3: {} + tinybench@2.9.0: {} + tinyexec@1.0.2: {} tinyglobby@0.2.15: @@ -8906,6 +10147,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyrainbow@2.0.0: {} + + tinyrainbow@3.0.3: {} + + tinyspy@4.0.4: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -8919,6 +10166,8 @@ snapshots: dependencies: eslint-visitor-keys: 3.4.3 + totalist@3.0.1: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -8932,8 +10181,16 @@ snapshots: picomatch: 4.0.3 typescript: 5.9.3 + ts-dedent@2.2.0: {} + ts-pattern@5.9.0: {} + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} tsx@4.21.0: @@ -9009,6 +10266,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universalify@2.0.1: {} + unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -9117,6 +10376,44 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 + vitest@4.0.18(@types/node@25.0.9)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.0.9 + '@vitest/browser-playwright': 4.0.18(playwright@1.58.0)(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.18) + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + vue-eslint-parser@10.2.0(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 4.4.3 @@ -9139,6 +10436,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@6.2.0: @@ -9147,12 +10449,30 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 strip-ansi: 7.1.2 + ws@8.19.0: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.0 + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 diff --git a/client/cms/src/client/@tanstack/react-query.gen.ts b/client/cms/src/client/@tanstack/react-query.gen.ts index c575093..731f806 100644 --- a/client/cms/src/client/@tanstack/react-query.gen.ts +++ b/client/cms/src/client/@tanstack/react-query.gen.ts @@ -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, 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, 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 } 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'; /** * Exchange Auth Code @@ -312,6 +312,26 @@ export const getUserInfoOptions = (options?: Options) => queryO queryKey: getUserInfoQueryKey(options) }); +export const getUserInfoByUserIdQueryKey = (options: Options) => createQueryKey('getUserInfoByUserId', options); + +/** + * Get Other User Information + * + * Fetches the complete profile data for the user associated with the provided session/token. + */ +export const getUserInfoByUserIdOptions = (options: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getUserInfoByUserId({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getUserInfoByUserIdQueryKey(options) +}); + export const getUserListQueryKey = (options: Options) => createQueryKey('getUserList', options); /** diff --git a/client/cms/src/client/index.ts b/client/cms/src/client/index.ts index a868299..a9d11b0 100644 --- a/client/cms/src/client/index.ts +++ b/client/cms/src/client/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { getAuthRedirect, getEventCheckin, getEventCheckinQuery, getEventInfo, getEventList, getUserInfo, 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, 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, ServiceEventEventInfoResponse, ServiceUserUserInfoData, UtilsRespStatus } from './types.gen'; +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'; diff --git a/client/cms/src/client/sdk.gen.ts b/client/cms/src/client/sdk.gen.ts index 12608f4..fd54c66 100644 --- a/client/cms/src/client/sdk.gen.ts +++ b/client/cms/src/client/sdk.gen.ts @@ -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, 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 } from './types.gen'; export type Options = Options2 & { /** @@ -130,6 +130,13 @@ export const getEventList = (options: Opti */ export const getUserInfo = (options?: Options) => (options?.client ?? client).get({ url: '/user/info', ...options }); +/** + * Get Other User Information + * + * Fetches the complete profile data for the user associated with the provided session/token. + */ +export const getUserInfoByUserId = (options: Options) => (options.client ?? client).get({ url: '/user/info/{user_id}', ...options }); + /** * List Users * diff --git a/client/cms/src/client/types.gen.ts b/client/cms/src/client/types.gen.ts index 644c6f1..c59d21e 100644 --- a/client/cms/src/client/types.gen.ts +++ b/client/cms/src/client/types.gen.ts @@ -10,6 +10,7 @@ export type DataEventIndexDoc = { event_id?: string; name?: string; start_time?: string; + thumbnail?: string; type?: string; }; @@ -71,12 +72,6 @@ export type ServiceEventCheckinSubmitData = { checkin_code?: string; }; -export type ServiceEventEventInfoResponse = { - end_time?: string; - name?: string; - start_time?: string; -}; - export type ServiceUserUserInfoData = { allow_public?: boolean; avatar?: string; @@ -370,6 +365,14 @@ export type GetEventCheckinErrors = { [key: string]: unknown; }; }; + /** + * Missing User ID / Unauthorized + */ + 401: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; /** * Internal Server Error */ @@ -494,6 +497,14 @@ export type GetEventInfoErrors = { [key: string]: unknown; }; }; + /** + * Missing User ID / Unauthorized + */ + 401: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; /** * Event Not Found */ @@ -519,7 +530,7 @@ export type GetEventInfoResponses = { * Successful retrieval */ 200: UtilsRespStatus & { - data?: ServiceEventEventInfoResponse; + data?: DataEventIndexDoc; }; }; @@ -550,6 +561,14 @@ export type GetEventListErrors = { [key: string]: unknown; }; }; + /** + * Missing User ID / Unauthorized + */ + 401: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; /** * Internal Server Error (Database query failed) */ @@ -584,7 +603,7 @@ export type GetUserInfoErrors = { /** * Missing User ID / Unauthorized */ - 403: UtilsRespStatus & { + 401: UtilsRespStatus & { data?: { [key: string]: unknown; }; @@ -620,6 +639,66 @@ export type GetUserInfoResponses = { export type GetUserInfoResponse = GetUserInfoResponses[keyof GetUserInfoResponses]; +export type GetUserInfoByUserIdData = { + body?: never; + path: { + /** + * Other user id + */ + user_id: string; + }; + query?: never; + url: '/user/info/{user_id}'; +}; + +export type GetUserInfoByUserIdErrors = { + /** + * Missing User ID / Unauthorized + */ + 401: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; + /** + * User Not Public + */ + 403: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; + /** + * User Not Found + */ + 404: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; + /** + * Internal Server Error (UUID Parse Failed) + */ + 500: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; +}; + +export type GetUserInfoByUserIdError = GetUserInfoByUserIdErrors[keyof GetUserInfoByUserIdErrors]; + +export type GetUserInfoByUserIdResponses = { + /** + * Successful profile retrieval + */ + 200: UtilsRespStatus & { + data?: ServiceUserUserInfoData; + }; +}; + +export type GetUserInfoByUserIdResponse = GetUserInfoByUserIdResponses[keyof GetUserInfoByUserIdResponses]; + export type GetUserListData = { body?: never; path?: never; @@ -645,6 +724,14 @@ export type GetUserListErrors = { [key: string]: unknown; }; }; + /** + * Missing User ID / Unauthorized + */ + 401: UtilsRespStatus & { + data?: { + [key: string]: unknown; + }; + }; /** * Internal Server Error (Search Engine or Missing Offset) */ @@ -690,7 +777,7 @@ export type PatchUserUpdateErrors = { /** * Missing User ID / Unauthorized */ - 403: UtilsRespStatus & { + 401: UtilsRespStatus & { data?: { [key: string]: unknown; }; diff --git a/client/cms/src/client/zod.gen.ts b/client/cms/src/client/zod.gen.ts index 66b4449..4b87cee 100644 --- a/client/cms/src/client/zod.gen.ts +++ b/client/cms/src/client/zod.gen.ts @@ -8,6 +8,7 @@ export const zDataEventIndexDoc = z.object({ event_id: z.optional(z.string()), name: z.optional(z.string()), start_time: z.optional(z.string()), + thumbnail: z.optional(z.string()), type: z.optional(z.string()) }); @@ -69,12 +70,6 @@ export const zServiceEventCheckinSubmitData = z.object({ checkin_code: z.optional(z.string()) }); -export const zServiceEventEventInfoResponse = z.object({ - end_time: z.optional(z.string()), - name: z.optional(z.string()), - start_time: z.optional(z.string()) -}); - export const zServiceUserUserInfoData = z.object({ allow_public: z.optional(z.boolean()), avatar: z.optional(z.string()), @@ -212,7 +207,7 @@ export const zGetEventInfoData = z.object({ * Successful retrieval */ export const zGetEventInfoResponse = zUtilsRespStatus.and(z.object({ - data: z.optional(zServiceEventEventInfoResponse) + data: z.optional(zDataEventIndexDoc) })); export const zGetEventListData = z.object({ @@ -244,6 +239,21 @@ export const zGetUserInfoResponse = zUtilsRespStatus.and(z.object({ data: z.optional(zServiceUserUserInfoData) })); +export const zGetUserInfoByUserIdData = z.object({ + body: z.optional(z.never()), + path: z.object({ + user_id: z.string() + }), + query: z.optional(z.never()) +}); + +/** + * Successful profile retrieval + */ +export const zGetUserInfoByUserIdResponse = zUtilsRespStatus.and(z.object({ + data: z.optional(zServiceUserUserInfoData) +})); + export const zGetUserListData = z.object({ body: z.optional(z.never()), path: z.optional(z.never()), diff --git a/client/cms/src/components/profile/edit-profile-dialog.tsx b/client/cms/src/components/profile/edit-profile-dialog.tsx index 236d23f..923f312 100644 --- a/client/cms/src/components/profile/edit-profile-dialog.tsx +++ b/client/cms/src/components/profile/edit-profile-dialog.tsx @@ -1,3 +1,4 @@ +import type { ServiceUserUserInfoData } from '@/client'; import { useForm } from '@tanstack/react-form'; import { useState } from 'react'; import { toast } from 'sonner'; @@ -21,7 +22,6 @@ import { Input, } from '@/components/ui/input'; import { useUpdateUser } from '@/hooks/data/useUpdateUser'; -import { useUserInfo } from '@/hooks/data/useUserInfo'; import { Switch } from '../ui/switch'; const formSchema = z.object({ @@ -31,9 +31,7 @@ const formSchema = z.object({ avatar: z.url().or(z.literal('')), allow_public: z.boolean(), }); -export function EditProfileDialog() { - const { data } = useUserInfo(); - const user = data.data!; +export function EditProfileDialog({ user }: { user: ServiceUserUserInfoData }) { const { mutateAsync } = useUpdateUser(); const form = useForm({ diff --git a/client/cms/src/components/profile/profile.error.tsx b/client/cms/src/components/profile/profile.error.tsx new file mode 100644 index 0000000..2f6f42a --- /dev/null +++ b/client/cms/src/components/profile/profile.error.tsx @@ -0,0 +1,30 @@ +import { Mail } from 'lucide-react'; +import { Skeleton } from '../ui/skeleton'; + +export function ProfileError({ reason }: { reason: string }) { + return ( +
+
+
+
+
+ +
+ + +
+
+
+ + +
+
+ +
+
+ + {reason} + +
+ ); +} diff --git a/client/cms/src/components/profile/profile.skeleton.tsx b/client/cms/src/components/profile/profile.skeleton.tsx new file mode 100644 index 0000000..42a95af --- /dev/null +++ b/client/cms/src/components/profile/profile.skeleton.tsx @@ -0,0 +1,29 @@ +import { Mail } from 'lucide-react'; +import { Skeleton } from '../ui/skeleton'; + +export function ProfileSkeleton() { + return ( +
+
+
+
+
+ +
+ + +
+
+
+ + +
+
+ +
+
+ + +
+ ); +} diff --git a/client/cms/src/components/profile/main-profile.tsx b/client/cms/src/components/profile/profile.tsx similarity index 87% rename from client/cms/src/components/profile/main-profile.tsx rename to client/cms/src/components/profile/profile.tsx index edcb613..4edc8ef 100644 --- a/client/cms/src/components/profile/main-profile.tsx +++ b/client/cms/src/components/profile/profile.tsx @@ -1,21 +1,22 @@ +import type { ServiceUserUserInfoData } from '@/client'; import { identicon } from '@dicebear/collection'; import { createAvatar } from '@dicebear/core'; import MDEditor from '@uiw/react-md-editor'; -import { isNil } from 'lodash-es'; +import { + isEmpty, + isNil, +} from 'lodash-es'; import { Mail, Pencil } from 'lucide-react'; import { useMemo, useState } from 'react'; import Markdown from 'react-markdown'; import { toast } from 'sonner'; import { Avatar, AvatarImage } from '@/components/ui/avatar'; import { useUpdateUser } from '@/hooks/data/useUpdateUser'; -import { useUserInfo } from '@/hooks/data/useUserInfo'; import { base64ToUtf8, utf8ToBase64 } from '@/lib/utils'; import { Button } from '../ui/button'; import { EditProfileDialog } from './edit-profile-dialog'; -export function MainProfile() { - const { data } = useUserInfo(); - const user = data.data!; +export function Profile({ user }: { user: ServiceUserUserInfoData }) { const [bio, setBio] = useState(() => base64ToUtf8(user.bio ?? '')); const [enableBioEdit, setEnableBioEdit] = useState(false); const { mutateAsync } = useUpdateUser(); @@ -35,7 +36,7 @@ export function MainProfile() {
- {user.avatar !== undefined ? : IdentIcon} + {!isEmpty(user.avatar) ? : IdentIcon}
@@ -47,19 +48,19 @@ export function MainProfile() { {user.email}
- +
{/* Bio */} {enableBioEdit ? ( - - ) + + ) :
{bio}
} + + + ) +} diff --git a/client/cms/src/hooks/data/useUserInfo.ts b/client/cms/src/hooks/data/useUserInfo.ts index db64bd6..369b0ee 100644 --- a/client/cms/src/hooks/data/useUserInfo.ts +++ b/client/cms/src/hooks/data/useUserInfo.ts @@ -1,5 +1,8 @@ import { useSuspenseQuery } from '@tanstack/react-query'; -import { getUserInfoOptions } from '@/client/@tanstack/react-query.gen'; +import { + getUserInfoByUserIdOptions, + getUserInfoOptions, +} from '@/client/@tanstack/react-query.gen'; export function useUserInfo() { return useSuspenseQuery({ @@ -7,3 +10,11 @@ export function useUserInfo() { staleTime: 10 * 60 * 1000, }); } + +export function useOtherUserInfo(userId: string) { + return useSuspenseQuery({ + ...getUserInfoByUserIdOptions({ path: { user_id: userId } }), + staleTime: 10 * 60 * 1000, + retry: (_failureCount, error) => error.code !== 403, + }); +} diff --git a/client/cms/src/lib/navData.ts b/client/cms/src/lib/navData.ts index 7266d47..83be1ad 100644 --- a/client/cms/src/lib/navData.ts +++ b/client/cms/src/lib/navData.ts @@ -1,4 +1,5 @@ import { + IconCalendarEvent, IconDashboard, IconUser, } from '@tabler/icons-react'; @@ -10,6 +11,11 @@ export const navData = { url: '/', icon: IconDashboard, }, + { + title: '活动列表', + url: '/events', + icon: IconCalendarEvent, + }, ], navSecondary: [ { diff --git a/client/cms/src/routeTree.gen.ts b/client/cms/src/routeTree.gen.ts index 7d1304c..26aa039 100644 --- a/client/cms/src/routeTree.gen.ts +++ b/client/cms/src/routeTree.gen.ts @@ -12,9 +12,11 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as TokenRouteImport } from './routes/token' import { Route as MagicLinkSentRouteImport } from './routes/magicLinkSent' import { Route as AuthorizeRouteImport } from './routes/authorize' -import { Route as SidebarLayoutRouteImport } from './routes/_sidebarLayout' -import { Route as SidebarLayoutIndexRouteImport } from './routes/_sidebarLayout/index' -import { Route as SidebarLayoutProfileRouteImport } from './routes/_sidebarLayout/profile' +import { Route as WorkbenchLayoutRouteImport } from './routes/_workbenchLayout' +import { Route as WorkbenchLayoutIndexRouteImport } from './routes/_workbenchLayout/index' +import { Route as WorkbenchLayoutEventsRouteImport } from './routes/_workbenchLayout/events' +import { Route as WorkbenchLayoutProfileIndexRouteImport } from './routes/_workbenchLayout/profile.index' +import { Route as WorkbenchLayoutProfileUserIdRouteImport } from './routes/_workbenchLayout/profile.$userId' const TokenRoute = TokenRouteImport.update({ id: '/token', @@ -31,61 +33,95 @@ const AuthorizeRoute = AuthorizeRouteImport.update({ path: '/authorize', getParentRoute: () => rootRouteImport, } as any) -const SidebarLayoutRoute = SidebarLayoutRouteImport.update({ - id: '/_sidebarLayout', +const WorkbenchLayoutRoute = WorkbenchLayoutRouteImport.update({ + id: '/_workbenchLayout', getParentRoute: () => rootRouteImport, } as any) -const SidebarLayoutIndexRoute = SidebarLayoutIndexRouteImport.update({ +const WorkbenchLayoutIndexRoute = WorkbenchLayoutIndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => SidebarLayoutRoute, + getParentRoute: () => WorkbenchLayoutRoute, } as any) -const SidebarLayoutProfileRoute = SidebarLayoutProfileRouteImport.update({ - id: '/profile', - path: '/profile', - getParentRoute: () => SidebarLayoutRoute, +const WorkbenchLayoutEventsRoute = WorkbenchLayoutEventsRouteImport.update({ + id: '/events', + path: '/events', + getParentRoute: () => WorkbenchLayoutRoute, } as any) +const WorkbenchLayoutProfileIndexRoute = + WorkbenchLayoutProfileIndexRouteImport.update({ + id: '/profile/', + path: '/profile/', + getParentRoute: () => WorkbenchLayoutRoute, + } as any) +const WorkbenchLayoutProfileUserIdRoute = + WorkbenchLayoutProfileUserIdRouteImport.update({ + id: '/profile/$userId', + path: '/profile/$userId', + getParentRoute: () => WorkbenchLayoutRoute, + } as any) export interface FileRoutesByFullPath { - '/': typeof SidebarLayoutIndexRoute + '/': typeof WorkbenchLayoutIndexRoute '/authorize': typeof AuthorizeRoute '/magicLinkSent': typeof MagicLinkSentRoute '/token': typeof TokenRoute - '/profile': typeof SidebarLayoutProfileRoute + '/events': typeof WorkbenchLayoutEventsRoute + '/profile/$userId': typeof WorkbenchLayoutProfileUserIdRoute + '/profile/': typeof WorkbenchLayoutProfileIndexRoute } export interface FileRoutesByTo { '/authorize': typeof AuthorizeRoute '/magicLinkSent': typeof MagicLinkSentRoute '/token': typeof TokenRoute - '/profile': typeof SidebarLayoutProfileRoute - '/': typeof SidebarLayoutIndexRoute + '/events': typeof WorkbenchLayoutEventsRoute + '/': typeof WorkbenchLayoutIndexRoute + '/profile/$userId': typeof WorkbenchLayoutProfileUserIdRoute + '/profile': typeof WorkbenchLayoutProfileIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport - '/_sidebarLayout': typeof SidebarLayoutRouteWithChildren + '/_workbenchLayout': typeof WorkbenchLayoutRouteWithChildren '/authorize': typeof AuthorizeRoute '/magicLinkSent': typeof MagicLinkSentRoute '/token': typeof TokenRoute - '/_sidebarLayout/profile': typeof SidebarLayoutProfileRoute - '/_sidebarLayout/': typeof SidebarLayoutIndexRoute + '/_workbenchLayout/events': typeof WorkbenchLayoutEventsRoute + '/_workbenchLayout/': typeof WorkbenchLayoutIndexRoute + '/_workbenchLayout/profile/$userId': typeof WorkbenchLayoutProfileUserIdRoute + '/_workbenchLayout/profile/': typeof WorkbenchLayoutProfileIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/authorize' | '/magicLinkSent' | '/token' | '/profile' - fileRoutesByTo: FileRoutesByTo - to: '/authorize' | '/magicLinkSent' | '/token' | '/profile' | '/' - id: - | '__root__' - | '/_sidebarLayout' + fullPaths: + | '/' | '/authorize' | '/magicLinkSent' | '/token' - | '/_sidebarLayout/profile' - | '/_sidebarLayout/' + | '/events' + | '/profile/$userId' + | '/profile/' + fileRoutesByTo: FileRoutesByTo + to: + | '/authorize' + | '/magicLinkSent' + | '/token' + | '/events' + | '/' + | '/profile/$userId' + | '/profile' + id: + | '__root__' + | '/_workbenchLayout' + | '/authorize' + | '/magicLinkSent' + | '/token' + | '/_workbenchLayout/events' + | '/_workbenchLayout/' + | '/_workbenchLayout/profile/$userId' + | '/_workbenchLayout/profile/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { - SidebarLayoutRoute: typeof SidebarLayoutRouteWithChildren + WorkbenchLayoutRoute: typeof WorkbenchLayoutRouteWithChildren AuthorizeRoute: typeof AuthorizeRoute MagicLinkSentRoute: typeof MagicLinkSentRoute TokenRoute: typeof TokenRoute @@ -114,46 +150,64 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthorizeRouteImport parentRoute: typeof rootRouteImport } - '/_sidebarLayout': { - id: '/_sidebarLayout' + '/_workbenchLayout': { + id: '/_workbenchLayout' path: '' fullPath: '/' - preLoaderRoute: typeof SidebarLayoutRouteImport + preLoaderRoute: typeof WorkbenchLayoutRouteImport parentRoute: typeof rootRouteImport } - '/_sidebarLayout/': { - id: '/_sidebarLayout/' + '/_workbenchLayout/': { + id: '/_workbenchLayout/' path: '/' fullPath: '/' - preLoaderRoute: typeof SidebarLayoutIndexRouteImport - parentRoute: typeof SidebarLayoutRoute + preLoaderRoute: typeof WorkbenchLayoutIndexRouteImport + parentRoute: typeof WorkbenchLayoutRoute } - '/_sidebarLayout/profile': { - id: '/_sidebarLayout/profile' + '/_workbenchLayout/events': { + id: '/_workbenchLayout/events' + path: '/events' + fullPath: '/events' + preLoaderRoute: typeof WorkbenchLayoutEventsRouteImport + parentRoute: typeof WorkbenchLayoutRoute + } + '/_workbenchLayout/profile/': { + id: '/_workbenchLayout/profile/' path: '/profile' - fullPath: '/profile' - preLoaderRoute: typeof SidebarLayoutProfileRouteImport - parentRoute: typeof SidebarLayoutRoute + fullPath: '/profile/' + preLoaderRoute: typeof WorkbenchLayoutProfileIndexRouteImport + parentRoute: typeof WorkbenchLayoutRoute + } + '/_workbenchLayout/profile/$userId': { + id: '/_workbenchLayout/profile/$userId' + path: '/profile/$userId' + fullPath: '/profile/$userId' + preLoaderRoute: typeof WorkbenchLayoutProfileUserIdRouteImport + parentRoute: typeof WorkbenchLayoutRoute } } } -interface SidebarLayoutRouteChildren { - SidebarLayoutProfileRoute: typeof SidebarLayoutProfileRoute - SidebarLayoutIndexRoute: typeof SidebarLayoutIndexRoute +interface WorkbenchLayoutRouteChildren { + WorkbenchLayoutEventsRoute: typeof WorkbenchLayoutEventsRoute + WorkbenchLayoutIndexRoute: typeof WorkbenchLayoutIndexRoute + WorkbenchLayoutProfileUserIdRoute: typeof WorkbenchLayoutProfileUserIdRoute + WorkbenchLayoutProfileIndexRoute: typeof WorkbenchLayoutProfileIndexRoute } -const SidebarLayoutRouteChildren: SidebarLayoutRouteChildren = { - SidebarLayoutProfileRoute: SidebarLayoutProfileRoute, - SidebarLayoutIndexRoute: SidebarLayoutIndexRoute, +const WorkbenchLayoutRouteChildren: WorkbenchLayoutRouteChildren = { + WorkbenchLayoutEventsRoute: WorkbenchLayoutEventsRoute, + WorkbenchLayoutIndexRoute: WorkbenchLayoutIndexRoute, + WorkbenchLayoutProfileUserIdRoute: WorkbenchLayoutProfileUserIdRoute, + WorkbenchLayoutProfileIndexRoute: WorkbenchLayoutProfileIndexRoute, } -const SidebarLayoutRouteWithChildren = SidebarLayoutRoute._addFileChildren( - SidebarLayoutRouteChildren, +const WorkbenchLayoutRouteWithChildren = WorkbenchLayoutRoute._addFileChildren( + WorkbenchLayoutRouteChildren, ) const rootRouteChildren: RootRouteChildren = { - SidebarLayoutRoute: SidebarLayoutRouteWithChildren, + WorkbenchLayoutRoute: WorkbenchLayoutRouteWithChildren, AuthorizeRoute: AuthorizeRoute, MagicLinkSentRoute: MagicLinkSentRoute, TokenRoute: TokenRoute, diff --git a/client/cms/src/routes/_sidebarLayout/profile.tsx b/client/cms/src/routes/_sidebarLayout/profile.tsx deleted file mode 100644 index 7dfcf31..0000000 --- a/client/cms/src/routes/_sidebarLayout/profile.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; -import { MainProfile } from '@/components/profile/main-profile'; - -export const Route = createFileRoute('/_sidebarLayout/profile')({ - component: RouteComponent, -}); - -function RouteComponent() { - return ( -
- -
- ); -} diff --git a/client/cms/src/routes/_sidebarLayout.tsx b/client/cms/src/routes/_workbenchLayout.tsx similarity index 93% rename from client/cms/src/routes/_sidebarLayout.tsx rename to client/cms/src/routes/_workbenchLayout.tsx index e282a5e..a52b375 100644 --- a/client/cms/src/routes/_sidebarLayout.tsx +++ b/client/cms/src/routes/_workbenchLayout.tsx @@ -3,7 +3,7 @@ import { AppSidebar } from '@/components/sidebar/app-sidebar'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; -export const Route = createFileRoute('/_sidebarLayout')({ +export const Route = createFileRoute('/_workbenchLayout')({ component: RouteComponent, }); diff --git a/client/cms/src/routes/_workbenchLayout/events.tsx b/client/cms/src/routes/_workbenchLayout/events.tsx new file mode 100644 index 0000000..22bd0b1 --- /dev/null +++ b/client/cms/src/routes/_workbenchLayout/events.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/_workbenchLayout/events')({ + component: RouteComponent, +}); + +function RouteComponent() { + return
Hello "/_sidebarLayout/events"!
; +} diff --git a/client/cms/src/routes/_sidebarLayout/index.tsx b/client/cms/src/routes/_workbenchLayout/index.tsx similarity index 90% rename from client/cms/src/routes/_sidebarLayout/index.tsx rename to client/cms/src/routes/_workbenchLayout/index.tsx index 023f537..c16b887 100644 --- a/client/cms/src/routes/_sidebarLayout/index.tsx +++ b/client/cms/src/routes/_workbenchLayout/index.tsx @@ -1,6 +1,6 @@ import { createFileRoute } from '@tanstack/react-router'; -export const Route = createFileRoute('/_sidebarLayout/')({ +export const Route = createFileRoute('/_workbenchLayout/')({ component: Index, }); diff --git a/client/cms/src/routes/_workbenchLayout/profile.$userId.tsx b/client/cms/src/routes/_workbenchLayout/profile.$userId.tsx new file mode 100644 index 0000000..5f18e4a --- /dev/null +++ b/client/cms/src/routes/_workbenchLayout/profile.$userId.tsx @@ -0,0 +1,34 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { Suspense } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { Profile } from '@/components/profile/profile'; +import { ProfileError } from '@/components/profile/profile.error'; +import { ProfileSkeleton } from '@/components/profile/profile.skeleton'; +import { useOtherUserInfo } from '@/hooks/data/useUserInfo'; + +export const Route = createFileRoute('/_workbenchLayout/profile/$userId')({ + component: RouteComponent, +}); + +function ProfileByUserId({ userId }: { userId: string }) { + const { data } = useOtherUserInfo(userId); + return ; +} + +function RouteComponent() { + const { userId } = Route.useParams(); + return ( +
+ { + if ((error.error as { code: number }).code === 403) + return ; + else return ; + }} + > + }> + + + +
+ ); +} diff --git a/client/cms/src/routes/_workbenchLayout/profile.index.tsx b/client/cms/src/routes/_workbenchLayout/profile.index.tsx new file mode 100644 index 0000000..090e248 --- /dev/null +++ b/client/cms/src/routes/_workbenchLayout/profile.index.tsx @@ -0,0 +1,13 @@ +import { createFileRoute, Navigate } from '@tanstack/react-router'; +import { useUserInfo } from '@/hooks/data/useUserInfo'; + +export const Route = createFileRoute('/_workbenchLayout/profile/')({ + component: RouteComponent, +}); + +function RouteComponent() { + const { data } = useUserInfo(); + return ( + + ); +} diff --git a/client/cms/src/stories/event-card.stories.ts b/client/cms/src/stories/event-card.stories.ts new file mode 100644 index 0000000..262abff --- /dev/null +++ b/client/cms/src/stories/event-card.stories.ts @@ -0,0 +1,12 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { EventCard } from '@/components/workbenchCards/event-card'; + +const meta = { + title: 'Cards/EventCard', + component: EventCard, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = {}; diff --git a/client/cms/src/stories/profile.stories.tsx b/client/cms/src/stories/profile.stories.tsx new file mode 100644 index 0000000..c4e6152 --- /dev/null +++ b/client/cms/src/stories/profile.stories.tsx @@ -0,0 +1,51 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { Profile } from '@/components/profile/profile'; +import { ProfileError } from '@/components/profile/profile.error'; +import { ProfileSkeleton } from '@/components/profile/profile.skeleton'; + +const queryClient = new QueryClient(); + +const meta = { + title: 'Profile', + component: Profile, + decorators: [ + Story => ( + + + + ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + user: { + username: 'nvirellia', + nickname: 'Noa Virellia', + subtitle: '天生骄傲', + email: 'noa@requiem.garden', + bio: '', + avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', + }, + }, +}; + +export const Skeleton: Story = { + render: () => , + args: { + user: {}, + }, +}; + +export const Error: Story = { + render: () => , + args: { + user: { + allow_public: false, + }, + }, +}; diff --git a/client/cms/vite.config.ts b/client/cms/vite.config.ts index 06cd972..f4dea43 100644 --- a/client/cms/vite.config.ts +++ b/client/cms/vite.config.ts @@ -1,3 +1,4 @@ +/// import path from 'node:path'; import tailwindcss from '@tailwindcss/vite'; import { tanstackRouter } from '@tanstack/router-plugin/vite'; @@ -6,27 +7,51 @@ 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 { playwright } from '@vitest/browser-playwright'; +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, - }), - react(), - tailwindcss(), - svgr(), - ], + plugins: [tanstackRouter({ + target: 'react', + 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: [{ + extends: true, + 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') + })], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: playwright({}), + instances: [{ + browser: 'chromium' + }] + }, + setupFiles: ['.storybook/vitest.setup.ts'] + } + }] + } +}); \ No newline at end of file diff --git a/client/cms/vitest.shims.d.ts b/client/cms/vitest.shims.d.ts new file mode 100644 index 0000000..7782f28 --- /dev/null +++ b/client/cms/vitest.shims.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file -- 2.49.1 From 65d493b91bff39df2efe41f9f977f65c390f2d58 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Sat, 31 Jan 2026 18:30:34 +0800 Subject: [PATCH 03/12] refactor(profile): split view/container and update nav state Signed-off-by: Noa Virellia --- client/cms/.zed/settings.json | 18 ++++++++ .../profile/edit-profile.dialog.container.tsx | 15 +++++++ ...ialog.tsx => edit-profile.dialog.view.tsx} | 25 ++++++----- .../components/profile/profile.container.tsx | 17 ++++++++ .../profile/{profile.tsx => profile.view.tsx} | 12 +++--- .../src/components/sidebar/app-sidebar.tsx | 4 +- .../cms/src/components/sidebar/nav-user.tsx | 7 ++- client/cms/src/components/site-header.tsx | 15 +------ client/cms/src/hooks/data/useUpdateUser.ts | 7 ++- client/cms/src/hooks/useLogout.ts | 14 ------ client/cms/src/lib/navData.ts | 1 + client/cms/src/lib/token.ts | 4 +- client/cms/src/routes/__root.tsx | 2 - client/cms/src/routes/_workbenchLayout.tsx | 16 +++++-- .../_workbenchLayout/profile.$userId.tsx | 10 +---- .../routes/_workbenchLayout/profile.index.tsx | 10 ++++- client/cms/src/routes/authorize.tsx | 2 +- client/cms/src/stories/nav-user.stories.tsx | 12 ++++++ .../profile/edit-profile.dialog.stories.tsx | 43 +++++++++++++++++++ .../stories/{ => profile}/profile.stories.tsx | 12 ++++-- 20 files changed, 173 insertions(+), 73 deletions(-) create mode 100644 client/cms/.zed/settings.json create mode 100644 client/cms/src/components/profile/edit-profile.dialog.container.tsx rename client/cms/src/components/profile/{edit-profile-dialog.tsx => edit-profile.dialog.view.tsx} (92%) create mode 100644 client/cms/src/components/profile/profile.container.tsx rename client/cms/src/components/profile/{profile.tsx => profile.view.tsx} (88%) delete mode 100644 client/cms/src/hooks/useLogout.ts create mode 100644 client/cms/src/stories/nav-user.stories.tsx create mode 100644 client/cms/src/stories/profile/edit-profile.dialog.stories.tsx rename client/cms/src/stories/{ => profile}/profile.stories.tsx (78%) diff --git a/client/cms/.zed/settings.json b/client/cms/.zed/settings.json new file mode 100644 index 0000000..aa63caf --- /dev/null +++ b/client/cms/.zed/settings.json @@ -0,0 +1,18 @@ +{ + "file_scan_exclusions": [ + "src/components/ui", + ".tanstack", + "node_modules", + "dist", + + // default values below + "**/.git", + "**/.svn", + "**/.hg", + "**/CVS", + "**/.DS_Store", + "**/Thumbs.db", + "**/.classpath", + "**/.settings", + ], +} diff --git a/client/cms/src/components/profile/edit-profile.dialog.container.tsx b/client/cms/src/components/profile/edit-profile.dialog.container.tsx new file mode 100644 index 0000000..53cd883 --- /dev/null +++ b/client/cms/src/components/profile/edit-profile.dialog.container.tsx @@ -0,0 +1,15 @@ +import type { ServiceUserUserInfoData } from '@/client'; +import { useUpdateUser } from '@/hooks/data/useUpdateUser'; +import { EditProfileDialogView } from './edit-profile.dialog.view'; + +export function EditProfileDialogContainer({ data }: { data: ServiceUserUserInfoData }) { + const { mutateAsync } = useUpdateUser(); + return ( + { + await mutateAsync({ body: data }); + }} + /> + ); +} diff --git a/client/cms/src/components/profile/edit-profile-dialog.tsx b/client/cms/src/components/profile/edit-profile.dialog.view.tsx similarity index 92% rename from client/cms/src/components/profile/edit-profile-dialog.tsx rename to client/cms/src/components/profile/edit-profile.dialog.view.tsx index 923f312..e4437c4 100644 --- a/client/cms/src/components/profile/edit-profile-dialog.tsx +++ b/client/cms/src/components/profile/edit-profile.dialog.view.tsx @@ -1,6 +1,9 @@ import type { ServiceUserUserInfoData } from '@/client'; import { useForm } from '@tanstack/react-form'; -import { useState } from 'react'; +import { + useEffect, + useState, +} from 'react'; import { toast } from 'sonner'; import z from 'zod'; import { Button } from '@/components/ui/button'; @@ -21,7 +24,6 @@ import { import { Input, } from '@/components/ui/input'; -import { useUpdateUser } from '@/hooks/data/useUpdateUser'; import { Switch } from '../ui/switch'; const formSchema = z.object({ @@ -31,9 +33,7 @@ const formSchema = z.object({ avatar: z.url().or(z.literal('')), allow_public: z.boolean(), }); -export function EditProfileDialog({ user }: { user: ServiceUserUserInfoData }) { - const { mutateAsync } = useUpdateUser(); - +export function EditProfileDialogView({ user, updateProfile }: { user: ServiceUserUserInfoData; updateProfile: (data: ServiceUserUserInfoData) => Promise }) { const form = useForm({ defaultValues: { avatar: user.avatar, @@ -49,7 +49,7 @@ export function EditProfileDialog({ user }: { user: ServiceUserUserInfoData }) { value, }) => { try { - await mutateAsync({ body: value }); + await updateProfile(value); toast.success('个人资料更新成功'); } catch (error) { @@ -61,11 +61,14 @@ export function EditProfileDialog({ user }: { user: ServiceUserUserInfoData }) { const [open, setOpen] = useState(false); - if (!open) { - setTimeout(() => { - form.reset(); - }, 200); - } + useEffect(() => { + if (!open) { + const id = setTimeout(() => { + form.reset(); + }, 200); + return () => clearTimeout(id); + } + }, [open, form]); return ( diff --git a/client/cms/src/components/profile/profile.container.tsx b/client/cms/src/components/profile/profile.container.tsx new file mode 100644 index 0000000..c82593e --- /dev/null +++ b/client/cms/src/components/profile/profile.container.tsx @@ -0,0 +1,17 @@ +import { useUpdateUser } from '@/hooks/data/useUpdateUser'; +import { useOtherUserInfo } from '@/hooks/data/useUserInfo'; +import { utf8ToBase64 } from '@/lib/utils'; +import { ProfileView } from './profile.view'; + +export function ProfileContainer({ userId }: { userId: string }) { + const { data } = useOtherUserInfo(userId); + const { mutateAsync } = useUpdateUser(); + return ( + { + await mutateAsync({ body: { bio: utf8ToBase64(bio) } }); + }} + /> + ); +} diff --git a/client/cms/src/components/profile/profile.tsx b/client/cms/src/components/profile/profile.view.tsx similarity index 88% rename from client/cms/src/components/profile/profile.tsx rename to client/cms/src/components/profile/profile.view.tsx index 4edc8ef..0669fdf 100644 --- a/client/cms/src/components/profile/profile.tsx +++ b/client/cms/src/components/profile/profile.view.tsx @@ -11,15 +11,13 @@ import { useMemo, useState } from 'react'; import Markdown from 'react-markdown'; import { toast } from 'sonner'; import { Avatar, AvatarImage } from '@/components/ui/avatar'; -import { useUpdateUser } from '@/hooks/data/useUpdateUser'; -import { base64ToUtf8, utf8ToBase64 } from '@/lib/utils'; +import { base64ToUtf8 } from '@/lib/utils'; import { Button } from '../ui/button'; -import { EditProfileDialog } from './edit-profile-dialog'; +import { EditProfileDialogContainer } from './edit-profile.dialog.container'; -export function Profile({ user }: { user: ServiceUserUserInfoData }) { +export function ProfileView({ user, onSaveBio }: { user: ServiceUserUserInfoData; onSaveBio: (bio: string) => Promise }) { const [bio, setBio] = useState(() => base64ToUtf8(user.bio ?? '')); const [enableBioEdit, setEnableBioEdit] = useState(false); - const { mutateAsync } = useUpdateUser(); const IdentIcon = useMemo(() => { const avatar = createAvatar(identicon, { @@ -48,7 +46,7 @@ export function Profile({ user }: { user: ServiceUserUserInfoData }) { {user.email}
- +
@@ -72,7 +70,7 @@ export function Profile({ user }: { user: ServiceUserUserInfoData }) { else { if (!isNil(bio)) { try { - await mutateAsync({ body: { bio: utf8ToBase64(bio) } }); + await onSaveBio(bio); setEnableBioEdit(false); } catch (error) { diff --git a/client/cms/src/components/sidebar/app-sidebar.tsx b/client/cms/src/components/sidebar/app-sidebar.tsx index 0d1a945..5b971d5 100644 --- a/client/cms/src/components/sidebar/app-sidebar.tsx +++ b/client/cms/src/components/sidebar/app-sidebar.tsx @@ -1,3 +1,4 @@ +import type { NavData } from '@/lib/navData'; import * as React from 'react'; import NixOSLogo from '@/assets/nixos.svg?react'; import { NavMain } from '@/components/sidebar/nav-main'; @@ -11,10 +12,9 @@ import { SidebarMenuButton, SidebarMenuItem, } from '@/components/ui/sidebar'; -import { navData } from '@/lib/navData'; import { NavUser } from './nav-user'; -export function AppSidebar({ ...props }: React.ComponentProps) { +export function AppSidebar({ navData, ...props }: React.ComponentProps & { navData: NavData }) { return ( diff --git a/client/cms/src/components/sidebar/nav-user.tsx b/client/cms/src/components/sidebar/nav-user.tsx index 2384aec..b60a467 100644 --- a/client/cms/src/components/sidebar/nav-user.tsx +++ b/client/cms/src/components/sidebar/nav-user.tsx @@ -26,15 +26,14 @@ import { useSidebar, } from '@/components/ui/sidebar'; import { useUserInfo } from '@/hooks/data/useUserInfo'; -import { useLogout } from '@/hooks/useLogout'; +import { logout } from '@/lib/token'; import { withFallback } from '../hoc/with-fallback'; import { Skeleton } from '../ui/skeleton'; -function NavUser_() { +export function NavUser_() { const { isMobile } = useSidebar(); const { data } = useUserInfo(); const user = data.data!; - const { logout } = useLogout(); const IdentIcon = useMemo(() => { const avatar = createAvatar(identicon, { @@ -85,7 +84,7 @@ function NavUser_() {
- + logout()}> 登出 diff --git a/client/cms/src/components/site-header.tsx b/client/cms/src/components/site-header.tsx index 9ecc44b..4eb3e5c 100644 --- a/client/cms/src/components/site-header.tsx +++ b/client/cms/src/components/site-header.tsx @@ -1,18 +1,7 @@ -import { useRouterState } from '@tanstack/react-router'; import { Separator } from '@/components/ui/separator'; import { SidebarTrigger } from '@/components/ui/sidebar'; -import { navData } from '@/lib/navData'; - -export function SiteHeader() { - const pathname = useRouterState({ select: state => state.location.pathname }); - const allNavItems = [...navData.navMain, ...navData.navSecondary]; - const currentTitle - = allNavItems.find(item => - item.url === '/' - ? pathname === '/' - : pathname.startsWith(item.url), - )?.title ?? '工作台'; +export function SiteHeader({ title }: { title: string }) { return (
@@ -21,7 +10,7 @@ export function SiteHeader() { orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" /> -

{currentTitle}

+

{title}

); diff --git a/client/cms/src/hooks/data/useUpdateUser.ts b/client/cms/src/hooks/data/useUpdateUser.ts index 9fe2efe..d99a302 100644 --- a/client/cms/src/hooks/data/useUpdateUser.ts +++ b/client/cms/src/hooks/data/useUpdateUser.ts @@ -1,12 +1,17 @@ +import type { ServiceUserUserInfoData } from '@/client'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { getUserInfoQueryKey, patchUserUpdateMutation } from '@/client/@tanstack/react-query.gen'; +import { getUserInfoByUserIdQueryKey, getUserInfoQueryKey, patchUserUpdateMutation } from '@/client/@tanstack/react-query.gen'; export function useUpdateUser() { const queryClient = useQueryClient(); + const data: { data: ServiceUserUserInfoData | undefined } | undefined = queryClient.getQueryData(getUserInfoQueryKey()); return useMutation({ ...patchUserUpdateMutation(), onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: getUserInfoQueryKey() }); + if ((data?.data?.user_id) != null) { + await queryClient.invalidateQueries({ queryKey: getUserInfoByUserIdQueryKey({ path: { user_id: data.data.user_id } }) }); + } }, }); } diff --git a/client/cms/src/hooks/useLogout.ts b/client/cms/src/hooks/useLogout.ts deleted file mode 100644 index e8f731c..0000000 --- a/client/cms/src/hooks/useLogout.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useNavigate } from '@tanstack/react-router'; -import { useCallback } from 'react'; -import { clearTokens } from '@/lib/token'; - -export function useLogout() { - const navigate = useNavigate(); - - const logout = useCallback(() => { - clearTokens(); - void navigate({ to: '/authorize' }); - }, [navigate]); - - return { logout }; -} diff --git a/client/cms/src/lib/navData.ts b/client/cms/src/lib/navData.ts index 83be1ad..f5f24ad 100644 --- a/client/cms/src/lib/navData.ts +++ b/client/cms/src/lib/navData.ts @@ -25,3 +25,4 @@ export const navData = { }, ], }; +export type NavData = typeof navData; diff --git a/client/cms/src/lib/token.ts b/client/cms/src/lib/token.ts index 96c745c..d491945 100644 --- a/client/cms/src/lib/token.ts +++ b/client/cms/src/lib/token.ts @@ -44,9 +44,9 @@ export async function doRefreshToken(refreshToken: string): Promise { - toast.error(message); + toast.info(message); }); } diff --git a/client/cms/src/routes/__root.tsx b/client/cms/src/routes/__root.tsx index faf535a..1f0df9e 100644 --- a/client/cms/src/routes/__root.tsx +++ b/client/cms/src/routes/__root.tsx @@ -12,11 +12,9 @@ const queryClient = new QueryClient({ const status // eslint-disable-next-line ts/no-unsafe-member-access = error?.response?.status ?? error?.status; - if (status >= 400 && status < 500) { return false; } - return failureCount < 3; }, }, diff --git a/client/cms/src/routes/_workbenchLayout.tsx b/client/cms/src/routes/_workbenchLayout.tsx index a52b375..4bf19ef 100644 --- a/client/cms/src/routes/_workbenchLayout.tsx +++ b/client/cms/src/routes/_workbenchLayout.tsx @@ -1,13 +1,23 @@ -import { createFileRoute, Outlet } from '@tanstack/react-router'; +import { createFileRoute, Outlet, useRouterState } from '@tanstack/react-router'; import { AppSidebar } from '@/components/sidebar/app-sidebar'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; +import { navData } from '@/lib/navData'; export const Route = createFileRoute('/_workbenchLayout')({ component: RouteComponent, }); function RouteComponent() { + const pathname = useRouterState({ select: state => state.location.pathname }); + const allNavItems = [...navData.navMain, ...navData.navSecondary]; + const title + = allNavItems.find(item => + item.url === '/' + ? pathname === '/' + : pathname.startsWith(item.url), + )?.title ?? '工作台'; + return ( - + - +
diff --git a/client/cms/src/routes/_workbenchLayout/profile.$userId.tsx b/client/cms/src/routes/_workbenchLayout/profile.$userId.tsx index 5f18e4a..f502805 100644 --- a/client/cms/src/routes/_workbenchLayout/profile.$userId.tsx +++ b/client/cms/src/routes/_workbenchLayout/profile.$userId.tsx @@ -1,20 +1,14 @@ import { createFileRoute } from '@tanstack/react-router'; import { Suspense } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; -import { Profile } from '@/components/profile/profile'; +import { ProfileContainer } from '@/components/profile/profile.container'; import { ProfileError } from '@/components/profile/profile.error'; import { ProfileSkeleton } from '@/components/profile/profile.skeleton'; -import { useOtherUserInfo } from '@/hooks/data/useUserInfo'; export const Route = createFileRoute('/_workbenchLayout/profile/$userId')({ component: RouteComponent, }); -function ProfileByUserId({ userId }: { userId: string }) { - const { data } = useOtherUserInfo(userId); - return ; -} - function RouteComponent() { const { userId } = Route.useParams(); return ( @@ -26,7 +20,7 @@ function RouteComponent() { }} > }> - +
diff --git a/client/cms/src/routes/_workbenchLayout/profile.index.tsx b/client/cms/src/routes/_workbenchLayout/profile.index.tsx index 090e248..c4add66 100644 --- a/client/cms/src/routes/_workbenchLayout/profile.index.tsx +++ b/client/cms/src/routes/_workbenchLayout/profile.index.tsx @@ -1,4 +1,8 @@ import { createFileRoute, Navigate } from '@tanstack/react-router'; +import { Suspense } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { ProfileError } from '@/components/profile/profile.error'; +import { ProfileSkeleton } from '@/components/profile/profile.skeleton'; import { useUserInfo } from '@/hooks/data/useUserInfo'; export const Route = createFileRoute('/_workbenchLayout/profile/')({ @@ -8,6 +12,10 @@ export const Route = createFileRoute('/_workbenchLayout/profile/')({ function RouteComponent() { const { data } = useUserInfo(); return ( - + }> + }> + + + ); } diff --git a/client/cms/src/routes/authorize.tsx b/client/cms/src/routes/authorize.tsx index 05fd479..36404e5 100644 --- a/client/cms/src/routes/authorize.tsx +++ b/client/cms/src/routes/authorize.tsx @@ -41,7 +41,7 @@ function RouteComponent() { }, }); } - }, [token, mutation.isIdle]); + }, [token, mutation.isIdle, mutation, oauthParams.client_id, oauthParams.redirect_uri, oauthParams.state]); return (
diff --git a/client/cms/src/stories/nav-user.stories.tsx b/client/cms/src/stories/nav-user.stories.tsx new file mode 100644 index 0000000..679366a --- /dev/null +++ b/client/cms/src/stories/nav-user.stories.tsx @@ -0,0 +1,12 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { NavUser_ } from '@/components/sidebar/nav-user'; + +const meta = { + title: 'NavUser', + component: NavUser_, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = {}; diff --git a/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx b/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx new file mode 100644 index 0000000..d3dcebf --- /dev/null +++ b/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { EditProfileDialogView } from '@/components/profile/edit-profile.dialog.view'; + +const meta = { + title: 'Profile/EditDialog', + component: EditProfileDialogView, + decorators: [ + Story => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + user: { + username: 'nvirellia', + nickname: 'Noa Virellia', + subtitle: '天生骄傲', + email: 'noa@requiem.garden', + bio: '', + avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', + }, + updateProfile: async () => { }, + }, + parameters: { + layout: 'fullscreen', + }, +}; diff --git a/client/cms/src/stories/profile.stories.tsx b/client/cms/src/stories/profile/profile.stories.tsx similarity index 78% rename from client/cms/src/stories/profile.stories.tsx rename to client/cms/src/stories/profile/profile.stories.tsx index c4e6152..8f84d18 100644 --- a/client/cms/src/stories/profile.stories.tsx +++ b/client/cms/src/stories/profile/profile.stories.tsx @@ -1,14 +1,14 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { Profile } from '@/components/profile/profile'; import { ProfileError } from '@/components/profile/profile.error'; import { ProfileSkeleton } from '@/components/profile/profile.skeleton'; +import { ProfileView } from '@/components/profile/profile.view'; const queryClient = new QueryClient(); const meta = { - title: 'Profile', - component: Profile, + title: 'Profile/View', + component: ProfileView, decorators: [ Story => ( @@ -16,7 +16,7 @@ const meta = { ), ], -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; @@ -31,13 +31,16 @@ export const Primary: Story = { bio: '', avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', }, + onSaveBio: async () => Promise.resolve(), }, + }; export const Skeleton: Story = { render: () => , args: { user: {}, + onSaveBio: async () => Promise.resolve(), }, }; @@ -47,5 +50,6 @@ export const Error: Story = { user: { allow_public: false, }, + onSaveBio: async () => Promise.resolve(), }, }; -- 2.49.1 From d57a724940a3c69bb03827fed3d63dbbcb3ef125 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Sun, 1 Feb 2026 09:03:12 +0800 Subject: [PATCH 04/12] refactor(sidebar): split nav views and add router decorator Signed-off-by: Noa Virellia --- client/cms/.storybook/preview.tsx | 16 ++++++- .../cms/src/components/hoc/with-fallback.tsx | 20 --------- .../{app-sidebar.tsx => app-sidebar.view.tsx} | 9 ++-- .../{nav-main.tsx => nav-main.view.tsx} | 0 ...v-secondary.tsx => nav-secondary.view.tsx} | 0 .../components/sidebar/nav-user.container.tsx | 11 +++++ .../components/sidebar/nav-user.skeletion.tsx | 18 ++++++++ .../{nav-user.tsx => nav-user.view.tsx} | 27 ++---------- client/cms/src/routes/_workbenchLayout.tsx | 15 ++++++- client/cms/src/stories/exampleUser.ts | 8 ++++ client/cms/src/stories/nav-user.stories.tsx | 12 ------ .../profile/edit-profile.dialog.stories.tsx | 10 +---- .../src/stories/profile/profile.stories.tsx | 13 ++---- client/cms/src/stories/sidebar.stories.tsx | 43 +++++++++++++++++++ 14 files changed, 119 insertions(+), 83 deletions(-) delete mode 100644 client/cms/src/components/hoc/with-fallback.tsx rename client/cms/src/components/sidebar/{app-sidebar.tsx => app-sidebar.view.tsx} (81%) rename client/cms/src/components/sidebar/{nav-main.tsx => nav-main.view.tsx} (100%) rename client/cms/src/components/sidebar/{nav-secondary.tsx => nav-secondary.view.tsx} (100%) create mode 100644 client/cms/src/components/sidebar/nav-user.container.tsx create mode 100644 client/cms/src/components/sidebar/nav-user.skeletion.tsx rename client/cms/src/components/sidebar/{nav-user.tsx => nav-user.view.tsx} (81%) create mode 100644 client/cms/src/stories/exampleUser.ts delete mode 100644 client/cms/src/stories/nav-user.stories.tsx create mode 100644 client/cms/src/stories/sidebar.stories.tsx diff --git a/client/cms/.storybook/preview.tsx b/client/cms/.storybook/preview.tsx index ab9f5f5..b9c2ec1 100644 --- a/client/cms/.storybook/preview.tsx +++ b/client/cms/.storybook/preview.tsx @@ -1,9 +1,21 @@ -import type { Preview } from '@storybook/react-vite'; +import type { Decorator, Preview } from '@storybook/react-vite'; import { ThemeProvider } from '../src/components/theme-provider'; import '../src/index.css'; +import { createRootRoute, createRouter, RouterProvider } from '@tanstack/react-router'; + +const RouterDecorator: Decorator = (Story) => { + const rootRoute = createRootRoute({ component: () => }); + const routeTree = rootRoute; + const router = createRouter({ routeTree }); + return ; +}; + +const ThemeDecorator: Decorator = (Story) => { + return ; +}; const preview: Preview = { - decorators: [(Story) => ], + decorators: [RouterDecorator, ThemeDecorator], parameters: { controls: { matchers: { diff --git a/client/cms/src/components/hoc/with-fallback.tsx b/client/cms/src/components/hoc/with-fallback.tsx deleted file mode 100644 index 5d4c0e6..0000000 --- a/client/cms/src/components/hoc/with-fallback.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { ReactNode } from 'react'; -import React, { Suspense } from 'react'; - -export function withFallback

( - Component: React.ComponentType

, - fallback: ReactNode, -) { - const Wrapped: React.FC

= (props) => { - return ( - - - - ); - }; - - Wrapped.displayName = `withFallback(${Component.displayName! || Component.name || 'Component' - })`; - - return Wrapped; -} diff --git a/client/cms/src/components/sidebar/app-sidebar.tsx b/client/cms/src/components/sidebar/app-sidebar.view.tsx similarity index 81% rename from client/cms/src/components/sidebar/app-sidebar.tsx rename to client/cms/src/components/sidebar/app-sidebar.view.tsx index 5b971d5..f6f1017 100644 --- a/client/cms/src/components/sidebar/app-sidebar.tsx +++ b/client/cms/src/components/sidebar/app-sidebar.view.tsx @@ -1,8 +1,8 @@ import type { NavData } from '@/lib/navData'; import * as React from 'react'; import NixOSLogo from '@/assets/nixos.svg?react'; -import { NavMain } from '@/components/sidebar/nav-main'; -import { NavSecondary } from '@/components/sidebar/nav-secondary'; +import { NavMain } from '@/components/sidebar/nav-main.view'; +import { NavSecondary } from '@/components/sidebar/nav-secondary.view'; import { Sidebar, SidebarContent, @@ -12,9 +12,8 @@ import { SidebarMenuButton, SidebarMenuItem, } from '@/components/ui/sidebar'; -import { NavUser } from './nav-user'; -export function AppSidebar({ navData, ...props }: React.ComponentProps & { navData: NavData }) { +export function AppSidebar({ navData, footerWidget, ...props }: React.ComponentProps & { navData: NavData; footerWidget: React.ReactNode }) { return ( @@ -37,7 +36,7 @@ export function AppSidebar({ navData, ...props }: React.ComponentProps - + {footerWidget} ); diff --git a/client/cms/src/components/sidebar/nav-main.tsx b/client/cms/src/components/sidebar/nav-main.view.tsx similarity index 100% rename from client/cms/src/components/sidebar/nav-main.tsx rename to client/cms/src/components/sidebar/nav-main.view.tsx diff --git a/client/cms/src/components/sidebar/nav-secondary.tsx b/client/cms/src/components/sidebar/nav-secondary.view.tsx similarity index 100% rename from client/cms/src/components/sidebar/nav-secondary.tsx rename to client/cms/src/components/sidebar/nav-secondary.view.tsx diff --git a/client/cms/src/components/sidebar/nav-user.container.tsx b/client/cms/src/components/sidebar/nav-user.container.tsx new file mode 100644 index 0000000..3e65113 --- /dev/null +++ b/client/cms/src/components/sidebar/nav-user.container.tsx @@ -0,0 +1,11 @@ +import { useUserInfo } from '@/hooks/data/useUserInfo'; +import { NavUserView } from './nav-user.view'; + +export function NavUserContainer() { + const { data } = useUserInfo(); + return ( + + ); +} diff --git a/client/cms/src/components/sidebar/nav-user.skeletion.tsx b/client/cms/src/components/sidebar/nav-user.skeletion.tsx new file mode 100644 index 0000000..de9d3c7 --- /dev/null +++ b/client/cms/src/components/sidebar/nav-user.skeletion.tsx @@ -0,0 +1,18 @@ +import { IconDotsVertical } from '@tabler/icons-react'; +import { SidebarMenuButton } from '../ui/sidebar'; +import { Skeleton } from '../ui/skeleton'; + +export function NavUserSkeleton() { + return ( + + +

+ + +
+ + + ); +} diff --git a/client/cms/src/components/sidebar/nav-user.tsx b/client/cms/src/components/sidebar/nav-user.view.tsx similarity index 81% rename from client/cms/src/components/sidebar/nav-user.tsx rename to client/cms/src/components/sidebar/nav-user.view.tsx index b60a467..6ba97ff 100644 --- a/client/cms/src/components/sidebar/nav-user.tsx +++ b/client/cms/src/components/sidebar/nav-user.view.tsx @@ -1,5 +1,6 @@ -import { identicon } from '@dicebear/collection'; +import type { ServiceUserUserInfoData } from '@/client'; +import { identicon } from '@dicebear/collection'; import { createAvatar } from '@dicebear/core'; import { IconDotsVertical, @@ -25,15 +26,10 @@ import { SidebarMenuItem, useSidebar, } from '@/components/ui/sidebar'; -import { useUserInfo } from '@/hooks/data/useUserInfo'; import { logout } from '@/lib/token'; -import { withFallback } from '../hoc/with-fallback'; -import { Skeleton } from '../ui/skeleton'; -export function NavUser_() { +export function NavUserView({ user }: { user: ServiceUserUserInfoData }) { const { isMobile } = useSidebar(); - const { data } = useUserInfo(); - const user = data.data!; const IdentIcon = useMemo(() => { const avatar = createAvatar(identicon, { @@ -94,20 +90,3 @@ export function NavUser_() { ); } - -function NavUserSkeleton() { - return ( - - -
- - -
- -
- ); -} - -export const NavUser = withFallback(NavUser_, ); diff --git a/client/cms/src/routes/_workbenchLayout.tsx b/client/cms/src/routes/_workbenchLayout.tsx index 4bf19ef..65a366f 100644 --- a/client/cms/src/routes/_workbenchLayout.tsx +++ b/client/cms/src/routes/_workbenchLayout.tsx @@ -1,5 +1,8 @@ import { createFileRoute, Outlet, useRouterState } from '@tanstack/react-router'; -import { AppSidebar } from '@/components/sidebar/app-sidebar'; +import { Suspense } from 'react'; +import { AppSidebar } from '@/components/sidebar/app-sidebar.view'; +import { NavUserContainer } from '@/components/sidebar/nav-user.container'; +import { NavUserSkeleton } from '@/components/sidebar/nav-user.skeletion'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; import { navData } from '@/lib/navData'; @@ -27,7 +30,15 @@ function RouteComponent() { } as React.CSSProperties } > - + }> + + + )} + variant="inset" + />
diff --git a/client/cms/src/stories/exampleUser.ts b/client/cms/src/stories/exampleUser.ts new file mode 100644 index 0000000..40800ff --- /dev/null +++ b/client/cms/src/stories/exampleUser.ts @@ -0,0 +1,8 @@ +export const user = { + username: 'nvirellia', + nickname: 'Noa Virellia', + subtitle: '天生骄傲', + email: 'noa@requiem.garden', + bio: '', + avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', +}; diff --git a/client/cms/src/stories/nav-user.stories.tsx b/client/cms/src/stories/nav-user.stories.tsx deleted file mode 100644 index 679366a..0000000 --- a/client/cms/src/stories/nav-user.stories.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { NavUser_ } from '@/components/sidebar/nav-user'; - -const meta = { - title: 'NavUser', - component: NavUser_, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Primary: Story = {}; diff --git a/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx b/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx index d3dcebf..e67e929 100644 --- a/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx +++ b/client/cms/src/stories/profile/edit-profile.dialog.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { EditProfileDialogView } from '@/components/profile/edit-profile.dialog.view'; +import { user } from '../exampleUser'; const meta = { title: 'Profile/EditDialog', @@ -27,14 +28,7 @@ type Story = StoryObj; export const Primary: Story = { args: { - user: { - username: 'nvirellia', - nickname: 'Noa Virellia', - subtitle: '天生骄傲', - email: 'noa@requiem.garden', - bio: '', - avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', - }, + user, updateProfile: async () => { }, }, parameters: { diff --git a/client/cms/src/stories/profile/profile.stories.tsx b/client/cms/src/stories/profile/profile.stories.tsx index 8f84d18..7590c49 100644 --- a/client/cms/src/stories/profile/profile.stories.tsx +++ b/client/cms/src/stories/profile/profile.stories.tsx @@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ProfileError } from '@/components/profile/profile.error'; import { ProfileSkeleton } from '@/components/profile/profile.skeleton'; import { ProfileView } from '@/components/profile/profile.view'; +import { user } from '../exampleUser'; const queryClient = new QueryClient(); @@ -23,20 +24,12 @@ type Story = StoryObj; export const Primary: Story = { args: { - user: { - username: 'nvirellia', - nickname: 'Noa Virellia', - subtitle: '天生骄傲', - email: 'noa@requiem.garden', - bio: '', - avatar: 'https://avatars.githubusercontent.com/u/54884471?v=4', - }, + user, onSaveBio: async () => Promise.resolve(), }, - }; -export const Skeleton: Story = { +export const Loading: Story = { render: () => , args: { user: {}, diff --git a/client/cms/src/stories/sidebar.stories.tsx b/client/cms/src/stories/sidebar.stories.tsx new file mode 100644 index 0000000..06dfae0 --- /dev/null +++ b/client/cms/src/stories/sidebar.stories.tsx @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { AppSidebar } from '@/components/sidebar/app-sidebar.view'; +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'; + +const meta = { + title: 'Navigation/Sidebar', + component: AppSidebar, + decorators: [ + Story => ( + + + + ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + args: { + navData, + footerWidget: , + }, +}; + +export const Loading: Story = { + args: { + navData, + footerWidget: , + }, +}; -- 2.49.1 From 56ee572d6e04611098603712fcac29b0bdc71a32 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Sun, 1 Feb 2026 09:06:22 +0800 Subject: [PATCH 05/12] fix(client): sidebar should be fullscreen Signed-off-by: Noa Virellia --- client/cms/src/stories/sidebar.stories.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/cms/src/stories/sidebar.stories.tsx b/client/cms/src/stories/sidebar.stories.tsx index 06dfae0..0cab568 100644 --- a/client/cms/src/stories/sidebar.stories.tsx +++ b/client/cms/src/stories/sidebar.stories.tsx @@ -23,6 +23,9 @@ const meta = { ), ], + parameters: { + layout: 'fullscreen', + }, } satisfies Meta; export default meta; -- 2.49.1 From 2df4d9aa49e1b56c390773ea2cdc313a78a7b0c2 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Sun, 1 Feb 2026 09:54:19 +0800 Subject: [PATCH 06/12] feat(client): event card Signed-off-by: Noa Virellia --- client/cms/package.json | 1 + client/cms/pnpm-lock.yaml | 8 +++ .../components/workbenchCards/event-card.tsx | 39 -------------- .../workbenchCards/event-card.view.tsx | 52 +++++++++++++++++++ client/cms/src/stories/event-card.stories.ts | 17 ++++-- 5 files changed, 74 insertions(+), 43 deletions(-) delete mode 100644 client/cms/src/components/workbenchCards/event-card.tsx create mode 100644 client/cms/src/components/workbenchCards/event-card.view.tsx diff --git a/client/cms/package.json b/client/cms/package.json index e6996a9..0f0db0f 100644 --- a/client/cms/package.json +++ b/client/cms/package.json @@ -53,6 +53,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "culori": "^4.0.2", + "dayjs": "^1.11.19", "immer": "^11.1.0", "lodash-es": "^4.17.22", "lucide-react": "^0.562.0", diff --git a/client/cms/pnpm-lock.yaml b/client/cms/pnpm-lock.yaml index 0117e43..f61ca18 100644 --- a/client/cms/pnpm-lock.yaml +++ b/client/cms/pnpm-lock.yaml @@ -125,6 +125,9 @@ importers: culori: specifier: ^4.0.2 version: 4.0.2 + dayjs: + specifier: ^1.11.19 + version: 1.11.19 immer: specifier: ^11.1.0 version: 11.1.3 @@ -2850,6 +2853,9 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -7765,6 +7771,8 @@ snapshots: d3-timer@3.0.1: {} + dayjs@1.11.19: {} + debug@4.4.3: dependencies: ms: 2.1.3 diff --git a/client/cms/src/components/workbenchCards/event-card.tsx b/client/cms/src/components/workbenchCards/event-card.tsx deleted file mode 100644 index 2820413..0000000 --- a/client/cms/src/components/workbenchCards/event-card.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { - Card, - CardAction, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card" -export function EventCard({ type, coverImage, eventName, description, startTime, endTime }: - { - type: 'official' | 'party', - coverImage: string, eventName: string, description: string, startTime: string, endTime: string - }) { - return ( - -
- Event cover - - - Featured - - Design systems meetup - - A practical talk on component APIs, accessibility, and shipping - faster. - - - - - - - ) -} diff --git a/client/cms/src/components/workbenchCards/event-card.view.tsx b/client/cms/src/components/workbenchCards/event-card.view.tsx new file mode 100644 index 0000000..7bb21cf --- /dev/null +++ b/client/cms/src/components/workbenchCards/event-card.view.tsx @@ -0,0 +1,52 @@ +import dayjs from 'dayjs'; +import { Calendar } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { + Card, + CardAction, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; + +export function EventCardView({ type, coverImage, eventName, description, startTime, endTime }: + { + type: 'official' | 'party'; + coverImage: string; + eventName: string; + description: string; + startTime: Date; + endTime: Date; + }) { + const startDayJs = dayjs(startTime); + const endDayJs = dayjs(endTime); + return ( + +
+ Event cover + + + {type === 'official' ? Official : Party} + + {eventName} + + + {`${startDayJs.format('YYYY/MM/DD')} - ${endDayJs.format('YYYY/MM/DD')}`} + + + {description} + + + + + + + + ); +} diff --git a/client/cms/src/stories/event-card.stories.ts b/client/cms/src/stories/event-card.stories.ts index 262abff..6a6017b 100644 --- a/client/cms/src/stories/event-card.stories.ts +++ b/client/cms/src/stories/event-card.stories.ts @@ -1,12 +1,21 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { EventCard } from '@/components/workbenchCards/event-card'; +import { EventCardView } from '@/components/workbenchCards/event-card.view'; const meta = { title: 'Cards/EventCard', - component: EventCard, -} satisfies Meta; + component: EventCardView, +} satisfies Meta; export default meta; type Story = StoryObj; -export const Primary: Story = {}; +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", + }, +}; -- 2.49.1 From 094d02d2034781136191b30d1a8377d8109eab56 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Sun, 1 Feb 2026 14:26:35 +0800 Subject: [PATCH 07/12] feat(client): event list Signed-off-by: Noa Virellia --- client/cms/src/assets/event-placeholder.png | Bin 0 -> 20063 bytes .../src/client/@tanstack/react-query.gen.ts | 61 +++++- client/cms/src/client/index.ts | 4 +- client/cms/src/client/sdk.gen.ts | 44 +++- client/cms/src/client/types.gen.ts | 196 ++++++++++++++++++ client/cms/src/client/zod.gen.ts | 63 ++++++ .../components/events/event-card.skeleton.tsx | 40 ++++ .../event-card.view.tsx | 18 +- .../events/event-grid.container.tsx | 23 ++ .../components/events/event-grid.skeleton.tsx | 12 ++ .../src/components/events/event-grid.view.tsx | 12 ++ client/cms/src/components/events/types.ts | 9 + .../workbenchCards/card-skeleton.tsx | 9 - client/cms/src/hooks/data/useGetEvents.ts | 21 ++ client/cms/src/hooks/data/useJoinEvent.ts | 8 + .../src/routes/_workbenchLayout/events.tsx | 7 +- client/cms/src/stories/event-card.stories.ts | 21 -- .../src/stories/events/event-card.stories.tsx | 36 ++++ .../src/stories/events/event-grid.stories.tsx | 61 ++++++ .../stories/{ => layout}/sidebar.stories.tsx | 4 +- 20 files changed, 600 insertions(+), 49 deletions(-) create mode 100644 client/cms/src/assets/event-placeholder.png create mode 100644 client/cms/src/components/events/event-card.skeleton.tsx rename client/cms/src/components/{workbenchCards => events}/event-card.view.tsx (80%) create mode 100644 client/cms/src/components/events/event-grid.container.tsx create mode 100644 client/cms/src/components/events/event-grid.skeleton.tsx create mode 100644 client/cms/src/components/events/event-grid.view.tsx create mode 100644 client/cms/src/components/events/types.ts delete mode 100644 client/cms/src/components/workbenchCards/card-skeleton.tsx create mode 100644 client/cms/src/hooks/data/useGetEvents.ts create mode 100644 client/cms/src/hooks/data/useJoinEvent.ts delete mode 100644 client/cms/src/stories/event-card.stories.ts create mode 100644 client/cms/src/stories/events/event-card.stories.tsx create mode 100644 client/cms/src/stories/events/event-grid.stories.tsx rename client/cms/src/stories/{ => layout}/sidebar.stories.tsx (93%) diff --git a/client/cms/src/assets/event-placeholder.png b/client/cms/src/assets/event-placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..dc2c293833bcd447a9193a52e221e9b927210e44 GIT binary patch literal 20063 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 => 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>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postEventJoin({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const getEventListQueryKey = (options: Options) => createQueryKey('getEventList', options); /** @@ -292,6 +311,44 @@ export const getEventListInfiniteOptions = (options: Options) 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>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + 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>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await postKycSession({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + export const getUserInfoQueryKey = (options?: Options) => createQueryKey('getUserInfo', options); /** diff --git a/client/cms/src/client/index.ts b/client/cms/src/client/index.ts index a9d11b0..a853517 100644 --- a/client/cms/src/client/index.ts +++ b/client/cms/src/client/index.ts @@ -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'; diff --git a/client/cms/src/client/sdk.gen.ts b/client/cms/src/client/sdk.gen.ts index fd54c66..475743d 100644 --- a/client/cms/src/client/sdk.gen.ts +++ b/client/cms/src/client/sdk.gen.ts @@ -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 = Options2 & { /** @@ -116,6 +116,20 @@ export const postEventCheckinSubmit = (opt */ export const getEventInfo = (options: Options) => (options.client ?? client).get({ 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 = (options: Options) => (options.client ?? client).post({ + url: '/event/join', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + /** * List Events * @@ -123,6 +137,34 @@ export const getEventInfo = (options: Opti */ export const getEventList = (options: Options) => (options.client ?? client).get({ url: '/event/list', ...options }); +/** + * Query KYC Status + * + * Checks the current state of a KYC session and updates local database if approved. + */ +export const postKycQuery = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).post({ + url: '/kyc/session', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } +}); + /** * Get My User Information * diff --git a/client/cms/src/client/types.gen.ts b/client/cms/src/client/types.gen.ts index c59d21e..85a22d1 100644 --- a/client/cms/src/client/types.gen.ts +++ b/client/cms/src/client/types.gen.ts @@ -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; diff --git a/client/cms/src/client/zod.gen.ts b/client/cms/src/client/zod.gen.ts index 4b87cee..19c081d 100644 --- a/client/cms/src/client/zod.gen.ts +++ b/client/cms/src/client/zod.gen.ts @@ -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()), diff --git a/client/cms/src/components/events/event-card.skeleton.tsx b/client/cms/src/components/events/event-card.skeleton.tsx new file mode 100644 index 0000000..0a874c0 --- /dev/null +++ b/client/cms/src/components/events/event-card.skeleton.tsx @@ -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 ( + +
+ + + + Official + + + + + + + + + + + + + + + + + ); +} diff --git a/client/cms/src/components/workbenchCards/event-card.view.tsx b/client/cms/src/components/events/event-card.view.tsx similarity index 80% rename from client/cms/src/components/workbenchCards/event-card.view.tsx rename to client/cms/src/components/events/event-card.view.tsx index 7bb21cf..3b5d5fd 100644 --- a/client/cms/src/components/workbenchCards/event-card.view.tsx +++ b/client/cms/src/components/events/event-card.view.tsx @@ -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" /> + {type === 'official' ? Official : Party} @@ -42,10 +39,9 @@ export function EventCardView({ type, coverImage, eventName, description, startT {description} - - + ); diff --git a/client/cms/src/components/events/event-grid.container.tsx b/client/cms/src/components/events/event-grid.container.tsx new file mode 100644 index 0000000..e0e981f --- /dev/null +++ b/client/cms/src/components/events/event-grid.container.tsx @@ -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 ; +} diff --git a/client/cms/src/components/events/event-grid.skeleton.tsx b/client/cms/src/components/events/event-grid.skeleton.tsx new file mode 100644 index 0000000..7c0046a --- /dev/null +++ b/client/cms/src/components/events/event-grid.skeleton.tsx @@ -0,0 +1,12 @@ +import { EventCardSkeleton } from './event-card.skeleton'; + +export function EventGridSkeleton() { + return ( +
+ {Array.from({ length: 8 }).map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} +
+ ); +} diff --git a/client/cms/src/components/events/event-grid.view.tsx b/client/cms/src/components/events/event-grid.view.tsx new file mode 100644 index 0000000..79c90c1 --- /dev/null +++ b/client/cms/src/components/events/event-grid.view.tsx @@ -0,0 +1,12 @@ +import type { EventInfo } from './types'; +import { EventCardView } from './event-card.view'; + +export function EventGridView({ events }: { events: EventInfo[] }) { + return ( +
+ {events.map(event => ( + + ))} +
+ ); +} diff --git a/client/cms/src/components/events/types.ts b/client/cms/src/components/events/types.ts new file mode 100644 index 0000000..22f52de --- /dev/null +++ b/client/cms/src/components/events/types.ts @@ -0,0 +1,9 @@ +export interface EventInfo { + type: 'official' | 'party'; + coverImage: string; + eventName: string; + description: string; + startTime: Date; + endTime: Date; + onJoinEvent: () => void; +} diff --git a/client/cms/src/components/workbenchCards/card-skeleton.tsx b/client/cms/src/components/workbenchCards/card-skeleton.tsx deleted file mode 100644 index 007b499..0000000 --- a/client/cms/src/components/workbenchCards/card-skeleton.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Skeleton } from '../ui/skeleton'; - -export function CardSkeleton() { - return ( - - ); -} diff --git a/client/cms/src/hooks/data/useGetEvents.ts b/client/cms/src/hooks/data/useGetEvents.ts new file mode 100644 index 0000000..4e02244 --- /dev/null +++ b/client/cms/src/hooks/data/useGetEvents.ts @@ -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; + }, + }); +} diff --git a/client/cms/src/hooks/data/useJoinEvent.ts b/client/cms/src/hooks/data/useJoinEvent.ts new file mode 100644 index 0000000..ae273ac --- /dev/null +++ b/client/cms/src/hooks/data/useJoinEvent.ts @@ -0,0 +1,8 @@ +import { useMutation } from '@tanstack/react-query'; +import { postEventJoinMutation } from '@/client/@tanstack/react-query.gen'; + +export function useJoinEvent() { + return useMutation({ + ...postEventJoinMutation(), + }); +} diff --git a/client/cms/src/routes/_workbenchLayout/events.tsx b/client/cms/src/routes/_workbenchLayout/events.tsx index 22bd0b1..79e31d1 100644 --- a/client/cms/src/routes/_workbenchLayout/events.tsx +++ b/client/cms/src/routes/_workbenchLayout/events.tsx @@ -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
Hello "/_sidebarLayout/events"!
; + return ( +
+ +
+ ); } diff --git a/client/cms/src/stories/event-card.stories.ts b/client/cms/src/stories/event-card.stories.ts deleted file mode 100644 index 6a6017b..0000000 --- a/client/cms/src/stories/event-card.stories.ts +++ /dev/null @@ -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; - -export default meta; -type Story = StoryObj; - -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", - }, -}; diff --git a/client/cms/src/stories/events/event-card.stories.tsx b/client/cms/src/stories/events/event-card.stories.tsx new file mode 100644 index 0000000..9180224 --- /dev/null +++ b/client/cms/src/stories/events/event-card.stories.tsx @@ -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; + +export default meta; +type Story = StoryObj; + +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: () => , + args: { + type: 'official', + coverImage: '', + eventName: '', + description: '', + startTime: new Date(0), + endTime: new Date(0), + onJoinEvent: () => { }, + }, +}; diff --git a/client/cms/src/stories/events/event-grid.stories.tsx b/client/cms/src/stories/events/event-grid.stories.tsx new file mode 100644 index 0000000..bb63feb --- /dev/null +++ b/client/cms/src/stories/events/event-grid.stories.tsx @@ -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; + +export default meta; +type Story = StoryObj; + +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: () => , + args: { + events: [], + }, +}; diff --git a/client/cms/src/stories/sidebar.stories.tsx b/client/cms/src/stories/layout/sidebar.stories.tsx similarity index 93% rename from client/cms/src/stories/sidebar.stories.tsx rename to client/cms/src/stories/layout/sidebar.stories.tsx index 0cab568..ac2aeac 100644 --- a/client/cms/src/stories/sidebar.stories.tsx +++ b/client/cms/src/stories/layout/sidebar.stories.tsx @@ -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 => ( -- 2.49.1 From 06f86cb8e3bb507923c923d271d400a1ecaa59d7 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Sun, 1 Feb 2026 15:31:29 +0800 Subject: [PATCH 08/12] chore(client): format Signed-off-by: Noa Virellia --- client/cms/.zed/settings.json | 4 +- client/cms/eslint.config.js | 2 +- .../events/event-grid.container.tsx | 16 +++---- .../src/components/profile/profile.view.tsx | 12 ++--- client/cms/src/hooks/use-mobile.ts | 1 + client/cms/src/routes/token.tsx | 1 + client/cms/vite.config.ts | 44 ++++++++++--------- 7 files changed, 42 insertions(+), 38 deletions(-) diff --git a/client/cms/.zed/settings.json b/client/cms/.zed/settings.json index aa63caf..6424aea 100644 --- a/client/cms/.zed/settings.json +++ b/client/cms/.zed/settings.json @@ -13,6 +13,6 @@ "**/.DS_Store", "**/Thumbs.db", "**/.classpath", - "**/.settings", - ], + "**/.settings" + ] } diff --git a/client/cms/eslint.config.js b/client/cms/eslint.config.js index fe10bc6..ec5180d 100644 --- a/client/cms/eslint.config.js +++ b/client/cms/eslint.config.js @@ -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, diff --git a/client/cms/src/components/events/event-grid.container.tsx b/client/cms/src/components/events/event-grid.container.tsx index e0e981f..41d47f1 100644 --- a/client/cms/src/components/events/event-grid.container.tsx +++ b/client/cms/src/components/events/event-grid.container.tsx @@ -10,14 +10,14 @@ export function EventGridContainer() { 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)); + 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 ; } diff --git a/client/cms/src/components/profile/profile.view.tsx b/client/cms/src/components/profile/profile.view.tsx index 0669fdf..5981f0b 100644 --- a/client/cms/src/components/profile/profile.view.tsx +++ b/client/cms/src/components/profile/profile.view.tsx @@ -53,12 +53,12 @@ export function ProfileView({ user, onSaveBio }: { user: ServiceUserUserInfoData {/* Bio */} {enableBioEdit ? ( - - ) + + ) :
{bio}
} + {actionFooter} ); diff --git a/client/cms/src/components/events/event-grid.container.tsx b/client/cms/src/components/events/event-grid.container.tsx index 41d47f1..868600b 100644 --- a/client/cms/src/components/events/event-grid.container.tsx +++ b/client/cms/src/components/events/event-grid.container.tsx @@ -1,23 +1,40 @@ 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 { Button } from '../ui/button'; +import { DialogTrigger } from '../ui/dialog'; import { EventGridView } from './event-grid.view'; +import { KycDialogContainer } from './kyc/kyc.dialog.container'; 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)); + type: it.type! as EventInfo['type'], + eventId: it.event_id!, + isJoined: it.is_joined!, + requireKyc: it.enable_kyc!, + coverImage: it.thumbnail! || PlaceholderImage, + eventName: it.name!, + description: it.description!, + startTime: new Date(it.start_time!), + endTime: new Date(it.end_time!), + } satisfies EventInfo)); - return ; + return ( + (eventInfo.isJoined + ? + : ( + + + + + + ) + )} + /> + ); } diff --git a/client/cms/src/components/events/event-grid.view.tsx b/client/cms/src/components/events/event-grid.view.tsx index 79c90c1..e5186bd 100644 --- a/client/cms/src/components/events/event-grid.view.tsx +++ b/client/cms/src/components/events/event-grid.view.tsx @@ -1,11 +1,11 @@ import type { EventInfo } from './types'; 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 (
{events.map(event => ( - + ))}
); diff --git a/client/cms/src/components/events/kyc/kyc-failed.dialog.view.tsx b/client/cms/src/components/events/kyc/kyc-failed.dialog.view.tsx new file mode 100644 index 0000000..768ee0e --- /dev/null +++ b/client/cms/src/components/events/kyc/kyc-failed.dialog.view.tsx @@ -0,0 +1,18 @@ +import { X } from 'lucide-react'; +import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../ui/dialog'; + +export function KycFailedDialogView() { + return ( + + + 失败 + +

提交身份认证失败,请重试。

+
+ +
+
+
+
+ ); +} diff --git a/client/cms/src/components/events/kyc/kyc-method-selection.dialog.view.tsx b/client/cms/src/components/events/kyc/kyc-method-selection.dialog.view.tsx new file mode 100644 index 0000000..238f666 --- /dev/null +++ b/client/cms/src/components/events/kyc/kyc-method-selection.dialog.view.tsx @@ -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 ( +
{ + e.preventDefault(); + e.stopPropagation(); + void form.handleSubmit(); + }} + className="flex flex-col gap-4" + > + + {field => ( + + 姓名 + field.handleChange(e.target.value)} + /> + + + )} + + + {field => ( + + 身份证号 + field.handleChange(e.target.value)} + /> + + + )} + + + [state.canSubmit, state.isPristine, state.isSubmitting]} + children={([canSubmit, isPristine, isSubmitting]) => ( + + )} + /> + +
+ ); +} + +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 ( +
{ + e.preventDefault(); + e.stopPropagation(); + void form.handleSubmit(); + }} + className="flex flex-col gap-4" + > + + {field => ( + + 护照号 + field.handleChange(e.target.value)} + /> + + + )} + + + [state.canSubmit, state.isPristine, state.isSubmitting]} + children={([canSubmit, isPristine, isSubmitting]) => ( + + )} + /> + +
+ ); +} + +type OnSubmit = (submission: KycSubmission) => Promise; + +export function KycMethodSelectionDialogView({ onSubmit }: { onSubmit: OnSubmit }) { + const [kycMethod, setKycMethod] = useState(null); + + return ( + + + 选择身份认证模式 + +

我们支持身份证和护照认证。

+
+
+ + + {kycMethod === 'cnrid' && } + {kycMethod === 'passport' && } +
+ ); +} diff --git a/client/cms/src/components/events/kyc/kyc-pending.dialog.view.tsx b/client/cms/src/components/events/kyc/kyc-pending.dialog.view.tsx new file mode 100644 index 0000000..5de1c35 --- /dev/null +++ b/client/cms/src/components/events/kyc/kyc-pending.dialog.view.tsx @@ -0,0 +1,18 @@ +import { HashLoader } from 'react-spinners/esm'; +import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../ui/dialog'; + +export function KycPendingDialogView() { + return ( + + + 等待身份认证结果 + +

认证页面已打开。正在等待认证服务器回传数据...

+
+ +
+
+
+
+ ); +} diff --git a/client/cms/src/components/events/kyc/kyc-prompt.dialog.view.tsx b/client/cms/src/components/events/kyc/kyc-prompt.dialog.view.tsx new file mode 100644 index 0000000..ff604c0 --- /dev/null +++ b/client/cms/src/components/events/kyc/kyc-prompt.dialog.view.tsx @@ -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 ( + + + 需要身份认证 + +

为了确保会议的安全性及合规性,我们需要对参会者进行实名认证。

+

您的个人隐私对我们至关重要:

+
    +
  • 数据加密:您的所有个人敏感信息均会通过 AES-256 标准进行强加密存储。
  • +
  • 用途限制:收集的信息仅用于本次活动的身份核实,不会用于任何商业用途。
  • +
  • 按时销毁:所有身份证明文件及关联的敏感原始数据将在活动完成后最多 30 天内从服务器中彻底删除。
  • +
+
+
+ + + +
+ ); +} diff --git a/client/cms/src/components/events/kyc/kyc-success.dialog.view.tsx b/client/cms/src/components/events/kyc/kyc-success.dialog.view.tsx new file mode 100644 index 0000000..867b20a --- /dev/null +++ b/client/cms/src/components/events/kyc/kyc-success.dialog.view.tsx @@ -0,0 +1,18 @@ +import { Check } from 'lucide-react'; +import { DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../ui/dialog'; + +export function KycSuccessDialogView() { + return ( + + + 成功 + +

已完成身份认证。

+
+ +
+
+
+
+ ); +} diff --git a/client/cms/src/components/events/kyc/kyc.dialog.container.tsx b/client/cms/src/components/events/kyc/kyc.dialog.container.tsx new file mode 100644 index 0000000..ad7953c --- /dev/null +++ b/client/cms/src/components/events/kyc/kyc.dialog.container.tsx @@ -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 ( + { + if (!open) { + void queryClient.invalidateQueries({ + queryKey: getEventListInfiniteQueryKey({ query: {}, headers: ver('20260205') }), + }); + } + setIsDialogOpen(open); + }} + > + {children} + {stage === 'prompt' && setStage('methodSelection')} />} + {stage === 'methodSelection' && } + {stage === 'pending' && } + {stage === 'success' && } + {stage === 'failed' && } + + ); +} diff --git a/client/cms/src/components/events/kyc/kyc.state.ts b/client/cms/src/components/events/kyc/kyc.state.ts new file mode 100644 index 0000000..34fc1bb --- /dev/null +++ b/client/cms/src/components/events/kyc/kyc.state.ts @@ -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()(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; diff --git a/client/cms/src/components/events/kyc/kyc.types.ts b/client/cms/src/components/events/kyc/kyc.types.ts new file mode 100644 index 0000000..4a9f0a1 --- /dev/null +++ b/client/cms/src/components/events/kyc/kyc.types.ts @@ -0,0 +1,8 @@ +export type KycSubmission = { + method: 'cnrid'; + cnrid: string; + name: string; +} | { + method: 'passport'; + passportId: string; +}; diff --git a/client/cms/src/components/events/types.ts b/client/cms/src/components/events/types.ts index 22f52de..6564ba3 100644 --- a/client/cms/src/components/events/types.ts +++ b/client/cms/src/components/events/types.ts @@ -1,9 +1,11 @@ export interface EventInfo { type: 'official' | 'party'; + eventId: string; + isJoined: boolean; + requireKyc: boolean; coverImage: string; eventName: string; description: string; startTime: Date; endTime: Date; - onJoinEvent: () => void; } diff --git a/client/cms/src/components/profile/edit-profile.dialog.view.tsx b/client/cms/src/components/profile/edit-profile.dialog.view.tsx index e4437c4..ed4f5dd 100644 --- a/client/cms/src/components/profile/edit-profile.dialog.view.tsx +++ b/client/cms/src/components/profile/edit-profile.dialog.view.tsx @@ -30,7 +30,7 @@ const formSchema = z.object({ username: z.string().min(5), nickname: z.string(), subtitle: z.string(), - avatar: z.url().or(z.literal('')), + avatar: z.string().url().or(z.literal('')), allow_public: z.boolean(), }); export function EditProfileDialogView({ user, updateProfile }: { user: ServiceUserUserInfoData; updateProfile: (data: ServiceUserUserInfoData) => Promise }) { diff --git a/client/cms/src/hooks/data/useCreateKycSession.ts b/client/cms/src/hooks/data/useCreateKycSession.ts new file mode 100644 index 0000000..4f0c342 --- /dev/null +++ b/client/cms/src/hooks/data/useCreateKycSession.ts @@ -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; + }, + }; +} diff --git a/client/cms/src/hooks/data/useGetEvents.ts b/client/cms/src/hooks/data/useGetEvents.ts index 4e02244..854969a 100644 --- a/client/cms/src/hooks/data/useGetEvents.ts +++ b/client/cms/src/hooks/data/useGetEvents.ts @@ -1,19 +1,19 @@ import { useInfiniteQuery } from '@tanstack/react-query'; import { isNil } from 'lodash-es'; import { getEventListInfiniteOptions } from '@/client/@tanstack/react-query.gen'; - -const LIMIT = 12; +import { ver } from '@/lib/apiVersion'; export function useGetEvents() { return useInfiniteQuery({ ...getEventListInfiniteOptions({ - query: { limit: String(LIMIT), offset: String(0) }, + query: {}, + headers: ver('20260205'), }), - initialPageParam: '0', + initialPageParam: 0, getNextPageParam: (lastPage, allPages) => { const currentData = lastPage?.data; - if (!isNil(currentData) && currentData.length === LIMIT) { - return String(allPages.length * LIMIT); + if (!isNil(currentData) && currentData.length === 20) { + return allPages.length * 20; } return undefined; }, diff --git a/client/cms/src/hooks/data/useJoinEvent.ts b/client/cms/src/hooks/data/useJoinEvent.ts deleted file mode 100644 index ae273ac..0000000 --- a/client/cms/src/hooks/data/useJoinEvent.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { postEventJoinMutation } from '@/client/@tanstack/react-query.gen'; - -export function useJoinEvent() { - return useMutation({ - ...postEventJoinMutation(), - }); -} diff --git a/client/cms/src/hooks/data/useUpdateUser.ts b/client/cms/src/hooks/data/useUpdateUser.ts index d99a302..c3940ea 100644 --- a/client/cms/src/hooks/data/useUpdateUser.ts +++ b/client/cms/src/hooks/data/useUpdateUser.ts @@ -1,16 +1,17 @@ import type { ServiceUserUserInfoData } from '@/client'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { getUserInfoByUserIdQueryKey, getUserInfoQueryKey, patchUserUpdateMutation } from '@/client/@tanstack/react-query.gen'; +import { ver } from '@/lib/apiVersion'; export function useUpdateUser() { 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({ ...patchUserUpdateMutation(), onSuccess: async () => { - await queryClient.invalidateQueries({ queryKey: getUserInfoQueryKey() }); + await queryClient.invalidateQueries({ queryKey: getUserInfoQueryKey({ headers: ver('20260205') }) }); 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') }) }); } }, }); diff --git a/client/cms/src/hooks/data/useUserInfo.ts b/client/cms/src/hooks/data/useUserInfo.ts index 369b0ee..b1b9d81 100644 --- a/client/cms/src/hooks/data/useUserInfo.ts +++ b/client/cms/src/hooks/data/useUserInfo.ts @@ -3,17 +3,18 @@ import { getUserInfoByUserIdOptions, getUserInfoOptions, } from '@/client/@tanstack/react-query.gen'; +import { ver } from '@/lib/apiVersion'; export function useUserInfo() { return useSuspenseQuery({ - ...getUserInfoOptions(), + ...getUserInfoOptions({ headers: ver('20260205') }), staleTime: 10 * 60 * 1000, }); } export function useOtherUserInfo(userId: string) { return useSuspenseQuery({ - ...getUserInfoByUserIdOptions({ path: { user_id: userId } }), + ...getUserInfoByUserIdOptions({ path: { user_id: userId }, headers: ver('20260205') }), staleTime: 10 * 60 * 1000, retry: (_failureCount, error) => error.code !== 403, }); diff --git a/client/cms/src/lib/apiVersion.ts b/client/cms/src/lib/apiVersion.ts new file mode 100644 index 0000000..0c757ea --- /dev/null +++ b/client/cms/src/lib/apiVersion.ts @@ -0,0 +1,5 @@ +export function ver(version: string) { + return { + 'X-Api-Version': version, + }; +} diff --git a/client/cms/src/lib/utils.ts b/client/cms/src/lib/utils.ts index 4306642..9c08cf1 100644 --- a/client/cms/src/lib/utils.ts +++ b/client/cms/src/lib/utils.ts @@ -1,3 +1,4 @@ +import type { Query } from '@tanstack/react-query'; import type { ClassValue } from 'clsx'; // eslint-disable-next-line unicorn/prefer-node-protocol import { Buffer } from 'buffer'; @@ -17,3 +18,12 @@ export function base64ToUtf8(base64: string): string { export function utf8ToBase64(utf8: string): string { return Buffer.from(utf8, 'utf-8').toString('base64'); } + +export function invalidateBlurry(id: string) { + return { + predicate: (query: Query) => { + const key = query.queryKey[0] as { _id: string }; + return key?._id === id; + }, + }; +} diff --git a/client/cms/src/stories/events/event-card.stories.tsx b/client/cms/src/stories/events/event-card.stories.tsx index 9180224..b07b120 100644 --- a/client/cms/src/stories/events/event-card.stories.tsx +++ b/client/cms/src/stories/events/event-card.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { EventCardSkeleton } from '@/components/events/event-card.skeleton'; import { EventCardView } from '@/components/events/event-card.view'; +import { Button } from '@/components/ui/button'; const meta = { title: 'Events/EventCard', @@ -12,25 +13,35 @@ type Story = StoryObj; 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: () => { }, + eventInfo: { + eventId: '1', + type: 'official', + requireKyc: true, + isJoined: false, + coverImage: 'https://github.com/NixOS/nixos-artwork/blob/master/wallpapers/nix-wallpaper-watersplash.png?raw=true', + eventName: 'Nix CN Conference 26.05', + description: 'Event Description', + startTime: new Date('2026-06-13T04:00:00.000Z'), + endTime: new Date('2026-06-14T04:00:00.000Z'), + }, + actionFooter: , }, }; export const Loading: Story = { render: () => , args: { - type: 'official', - coverImage: '', - eventName: '', - description: '', - startTime: new Date(0), - endTime: new Date(0), - onJoinEvent: () => { }, + eventInfo: { + eventId: '1', + type: 'official', + requireKyc: true, + coverImage: '', + isJoined: false, + eventName: '', + description: '', + startTime: new Date(0), + endTime: new Date(0), + }, + actionFooter: , }, }; diff --git a/client/cms/src/stories/events/event-grid.stories.tsx b/client/cms/src/stories/events/event-grid.stories.tsx index bb63feb..055ed05 100644 --- a/client/cms/src/stories/events/event-grid.stories.tsx +++ b/client/cms/src/stories/events/event-grid.stories.tsx @@ -1,6 +1,8 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { EventGridSkeleton } from '@/components/events/event-grid.skeleton'; import { EventGridView } from '@/components/events/event-grid.view'; +import { Button } from '@/components/ui/button'; +import { Skeleton as UiSkeleton } from '@/components/ui/skeleton'; const meta = { title: 'Events/EventGrid', @@ -14,42 +16,51 @@ export const Primary: Story = { args: { events: [ { + eventId: '1', + requireKyc: true, + isJoined: false, 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: () => { }, }, { + eventId: '2', + requireKyc: true, + isJoined: false, 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: () => { }, }, { + eventId: '3', + requireKyc: true, + isJoined: false, 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: () => { }, }, { + eventId: '4', + requireKyc: true, + isJoined: false, 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: () => { }, }, ], + assembleFooter: () => , }, }; @@ -57,5 +68,6 @@ export const Skeleton: Story = { render: () => , args: { events: [], + assembleFooter: () => , }, }; diff --git a/client/cms/src/stories/events/kyc-dialog.stories.tsx b/client/cms/src/stories/events/kyc-dialog.stories.tsx new file mode 100644 index 0000000..f2c04ad --- /dev/null +++ b/client/cms/src/stories/events/kyc-dialog.stories.tsx @@ -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 => ( + + + + ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Prompt: Story = { + args: { + }, +}; + +export const MethodSelection: Story = { + render: () => Promise.resolve()} />, + args: { + }, +}; + +export const Pending: Story = { + render: () => , + args: { + }, +}; + +export const Success: Story = { + render: () => , + args: { + }, +}; + +export const Failed: Story = { + render: () => , + args: { + }, +}; -- 2.49.1 From 50ad3888f5fbb8a7662eedb94f5403304f9f45c1 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Thu, 5 Feb 2026 19:13:49 +0800 Subject: [PATCH 10/12] feat(client): translate logout messages to Chinese Signed-off-by: Noa Virellia --- client/cms/src/lib/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/cms/src/lib/client.ts b/client/cms/src/lib/client.ts index 94437c0..c061f8f 100644 --- a/client/cms/src/lib/client.ts +++ b/client/cms/src/lib/client.ts @@ -30,12 +30,12 @@ export function configInternalApiClient() { // Avoid infinite loop if the refresh token request itself fails if (request.url.includes('/auth/refresh')) { // Refresh token failed, clear tokens and redirect to login page - logout('Session expired'); + logout('会话已过期'); } else { const refreshToken = getRefreshToken(); if (isNil(refreshToken) || isEmpty(refreshToken)) { - logout('You are not logged in'); + logout('未登录'); } else { const refreshResponse = await doRefreshToken(refreshToken); -- 2.49.1 From 2efb13238c10ea9eb98dadaf4bc59d3277851f4f Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Thu, 5 Feb 2026 19:14:12 +0800 Subject: [PATCH 11/12] format(client): eslint Signed-off-by: Noa Virellia --- .../events/event-grid.container.tsx | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/client/cms/src/components/events/event-grid.container.tsx b/client/cms/src/components/events/event-grid.container.tsx index 868600b..d442978 100644 --- a/client/cms/src/components/events/event-grid.container.tsx +++ b/client/cms/src/components/events/event-grid.container.tsx @@ -11,16 +11,16 @@ export function EventGridContainer() { const allEvents: EventInfo[] = isLoading ? [] : data.pages.flatMap(page => page.data!).map(it => ({ - type: it.type! as EventInfo['type'], - eventId: it.event_id!, - isJoined: it.is_joined!, - requireKyc: it.enable_kyc!, - coverImage: it.thumbnail! || PlaceholderImage, - eventName: it.name!, - description: it.description!, - startTime: new Date(it.start_time!), - endTime: new Date(it.end_time!), - } satisfies EventInfo)); + type: it.type! as EventInfo['type'], + eventId: it.event_id!, + isJoined: it.is_joined!, + requireKyc: it.enable_kyc!, + coverImage: it.thumbnail! || PlaceholderImage, + eventName: it.name!, + description: it.description!, + startTime: new Date(it.start_time!), + endTime: new Date(it.end_time!), + } satisfies EventInfo)); return ( (eventInfo.isJoined ? : ( - - - - - - ) + + + + + + ) )} /> ); -- 2.49.1 From af44745e953f7c4177bfd860d451da1a6b77cc06 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Thu, 5 Feb 2026 19:19:48 +0800 Subject: [PATCH 12/12] fix(client): shit apiVersion everywhere Signed-off-by: Noa Virellia --- client/cms/src/components/login-form.tsx | 3 ++- .../src/components/profile/edit-profile.dialog.container.tsx | 3 ++- client/cms/src/components/profile/profile.container.tsx | 3 ++- client/cms/src/lib/token.ts | 2 ++ client/cms/src/routes/authorize.tsx | 2 ++ client/cms/src/routes/token.tsx | 3 ++- client/cms/src/stories/events/kyc-dialog.stories.tsx | 5 +++++ 7 files changed, 17 insertions(+), 4 deletions(-) diff --git a/client/cms/src/components/login-form.tsx b/client/cms/src/components/login-form.tsx index 5447440..68afe8e 100644 --- a/client/cms/src/components/login-form.tsx +++ b/client/cms/src/components/login-form.tsx @@ -13,6 +13,7 @@ import { } from '@/components/ui/field'; import { Input } from '@/components/ui/input'; import { useGetMagicLink } from '@/hooks/data/useGetMagicLink'; +import { ver } from '@/lib/apiVersion'; import { cn } from '@/lib/utils'; export function LoginForm({ @@ -32,7 +33,7 @@ export function LoginForm({ event.preventDefault(); const formData = new FormData(formRef.current!); const email = formData.get('email')! as string; - mutateAsync({ body: { email, turnstile_token: token!, ...oauthParams } }).then(() => { + mutateAsync({ body: { email, turnstile_token: token!, ...oauthParams }, headers: ver('20260205') }).then(() => { void navigate({ to: '/magicLinkSent', search: { email } }); }).catch((error) => { console.error(error); diff --git a/client/cms/src/components/profile/edit-profile.dialog.container.tsx b/client/cms/src/components/profile/edit-profile.dialog.container.tsx index 53cd883..d8ade1b 100644 --- a/client/cms/src/components/profile/edit-profile.dialog.container.tsx +++ b/client/cms/src/components/profile/edit-profile.dialog.container.tsx @@ -1,5 +1,6 @@ import type { ServiceUserUserInfoData } from '@/client'; import { useUpdateUser } from '@/hooks/data/useUpdateUser'; +import { ver } from '@/lib/apiVersion'; import { EditProfileDialogView } from './edit-profile.dialog.view'; export function EditProfileDialogContainer({ data }: { data: ServiceUserUserInfoData }) { @@ -8,7 +9,7 @@ export function EditProfileDialogContainer({ data }: { data: ServiceUserUserInfo { - await mutateAsync({ body: data }); + await mutateAsync({ body: data, headers: ver('20260205') }); }} /> ); diff --git a/client/cms/src/components/profile/profile.container.tsx b/client/cms/src/components/profile/profile.container.tsx index c82593e..38afb2d 100644 --- a/client/cms/src/components/profile/profile.container.tsx +++ b/client/cms/src/components/profile/profile.container.tsx @@ -1,5 +1,6 @@ import { useUpdateUser } from '@/hooks/data/useUpdateUser'; import { useOtherUserInfo } from '@/hooks/data/useUserInfo'; +import { ver } from '@/lib/apiVersion'; import { utf8ToBase64 } from '@/lib/utils'; import { ProfileView } from './profile.view'; @@ -10,7 +11,7 @@ export function ProfileContainer({ userId }: { userId: string }) { { - await mutateAsync({ body: { bio: utf8ToBase64(bio) } }); + await mutateAsync({ body: { bio: utf8ToBase64(bio) }, headers: ver('20260205') }); }} /> ); diff --git a/client/cms/src/lib/token.ts b/client/cms/src/lib/token.ts index d491945..6354075 100644 --- a/client/cms/src/lib/token.ts +++ b/client/cms/src/lib/token.ts @@ -1,6 +1,7 @@ import type { ServiceAuthTokenResponse } from '@/client'; import { toast } from 'sonner'; import { postAuthRefresh } from '@/client'; +import { ver } from './apiVersion'; import { router } from './router'; const ACCESS_TOKEN_LOCALSTORAGE_KEY = 'token'; @@ -40,6 +41,7 @@ export async function doRefreshToken(refreshToken: string): Promise { if (mutation.isIdle) { - mutation.mutate({ body: { code } }); + mutation.mutate({ body: { code }, headers: ver('20260205') }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/client/cms/src/stories/events/kyc-dialog.stories.tsx b/client/cms/src/stories/events/kyc-dialog.stories.tsx index f2c04ad..6314dbe 100644 --- a/client/cms/src/stories/events/kyc-dialog.stories.tsx +++ b/client/cms/src/stories/events/kyc-dialog.stories.tsx @@ -23,29 +23,34 @@ type Story = StoryObj; export const Prompt: Story = { args: { + next: () => { }, }, }; export const MethodSelection: Story = { render: () => Promise.resolve()} />, args: { + next: () => { }, }, }; export const Pending: Story = { render: () => , args: { + next: () => { }, }, }; export const Success: Story = { render: () => , args: { + next: () => { }, }, }; export const Failed: Story = { render: () => , args: { + next: () => { }, }, }; -- 2.49.1