From 22fdcd20206bc217b092caacaf3277fa3ea14aef Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Fri, 30 Jan 2026 22:48:09 +0800 Subject: [PATCH] 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: () => {