refactor(client): split client to cms/mobile/party

Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
2026-01-20 16:11:38 +08:00
parent ecbb890cac
commit b2c5f8de38
144 changed files with 21 additions and 17 deletions

View File

@@ -0,0 +1,44 @@
import type { AxiosRequestConfig } from 'axios';
import axios, { AxiosError } from 'axios';
import { router } from '@/lib/router';
import { doRefreshToken, getRefreshToken, getToken, setRefreshToken, setToken } from './token';
export const axiosClient = axios.create({
baseURL: '/api/v1/',
});
axiosClient.interceptors.request.use((config) => {
const token = getToken();
if (token !== null) {
config.headers = config.headers ?? {};
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
type RetryConfig = AxiosRequestConfig & { _retry?: boolean };
axiosClient.interceptors.response.use(undefined, async (error: AxiosError) => {
const originalRequest = error.config as RetryConfig | undefined;
if (!error.response || error.response.status !== 401 || !originalRequest) {
return Promise.reject(error);
}
if (error.response.status === 401 && getRefreshToken() !== null) {
try {
const maybeRefreshTokenValue = await doRefreshToken();
const { access_token, refresh_token } = maybeRefreshTokenValue.data;
originalRequest.headers = originalRequest.headers ?? {};
originalRequest.headers.Authorization = `Bearer ${access_token}`;
setToken(access_token);
setRefreshToken(refresh_token);
return await axiosClient(originalRequest);
}
catch (e) {
if (e instanceof AxiosError && e.status === 401) {
await router.navigate({ to: '/authorize' });
return Promise.reject(error);
}
}
}
});

View File

@@ -0,0 +1,21 @@
import {
IconDashboard,
IconUser,
} from '@tabler/icons-react';
export const navData = {
navMain: [
{
title: '工作台',
url: '/',
icon: IconDashboard,
},
],
navSecondary: [
{
title: '个人资料',
url: '/profile',
icon: IconUser,
},
],
};

View File

@@ -0,0 +1,14 @@
/**
* Generate a cryptographically secure OAuth2 state string
* base64url encoded, URL-safe
*/
export function generateOAuthState(bytes: number = 32): string {
const random = new Uint8Array(bytes);
crypto.getRandomValues(random);
// base64url encode
return btoa(String.fromCharCode(...random))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}

View File

@@ -0,0 +1,13 @@
import { createRouter } from '@tanstack/react-router';
// Import the generated route tree
import { routeTree } from '../routeTree.gen';
// Create a new router instance
export const router = createRouter({ routeTree });
// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}

View File

@@ -0,0 +1,40 @@
import axios from 'axios';
export function setToken(token: string) {
localStorage.setItem('token', token);
}
export function getToken() {
return localStorage.getItem('token');
}
export function removeToken() {
localStorage.removeItem('token');
}
export function hasToken() {
return getToken() !== null;
}
export function setRefreshToken(refreshToken: string) {
localStorage.setItem('refreshToken', refreshToken);
}
export function getRefreshToken() {
return localStorage.getItem('refreshToken');
}
export function clearTokens() {
removeToken();
setRefreshToken('');
}
export async function doSetTokenByCode(code: string) {
const { data } = await axios.post<{ access_token: string; refresh_token: string }>('/api/v1/auth/token', { code });
setToken(data.access_token);
setRefreshToken(data.refresh_token);
}
export async function doRefreshToken() {
return axios.post<{ access_token: string; refresh_token: string }>('/api/v1/auth/refresh', { refresh_token: getRefreshToken() });
}

View File

@@ -0,0 +1,19 @@
import type { ClassValue } from 'clsx';
// eslint-disable-next-line unicorn/prefer-node-protocol
import { Buffer } from 'buffer';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function base64ToUtf8(base64: string): string {
return new TextDecoder('utf-8').decode(
Uint8Array.from(Buffer.from(base64, 'base64')),
);
}
export function utf8ToBase64(utf8: string): string {
return Buffer.from(utf8, 'utf-8').toString('base64');
}