First merge from develop to main (WIP) #7
@@ -44,6 +44,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"culori": "^4.0.2",
|
||||
"immer": "^11.1.0",
|
||||
"lodash-es": "^4.17.22",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"qrcode": "^1.5.4",
|
||||
@@ -69,6 +70,7 @@
|
||||
"@tanstack/router-plugin": "^1.141.7",
|
||||
"@types/base-64": "^1.0.2",
|
||||
"@types/culori": "^4.0.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@types/react": "^19.2.5",
|
||||
@@ -82,6 +84,7 @@
|
||||
"lint-staged": "^16.2.7",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"type-fest": "^5.4.1",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.46.4",
|
||||
"vite": "^7.2.4",
|
||||
|
||||
40
client/cms/pnpm-lock.yaml
generated
40
client/cms/pnpm-lock.yaml
generated
@@ -110,6 +110,9 @@ importers:
|
||||
immer:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.3
|
||||
lodash-es:
|
||||
specifier: ^4.17.22
|
||||
version: 4.17.22
|
||||
lucide-react:
|
||||
specifier: ^0.562.0
|
||||
version: 0.562.0(react@19.2.3)
|
||||
@@ -180,6 +183,9 @@ importers:
|
||||
'@types/culori':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
'@types/lodash-es':
|
||||
specifier: ^4.17.12
|
||||
version: 4.17.12
|
||||
'@types/node':
|
||||
specifier: ^25.0.3
|
||||
version: 25.0.9
|
||||
@@ -219,6 +225,9 @@ importers:
|
||||
tw-animate-css:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
type-fest:
|
||||
specifier: ^5.4.1
|
||||
version: 5.4.1
|
||||
typescript:
|
||||
specifier: ~5.9.3
|
||||
version: 5.9.3
|
||||
@@ -1730,6 +1739,12 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
|
||||
|
||||
'@types/lodash@4.17.23':
|
||||
resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==}
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||
|
||||
@@ -2981,6 +2996,9 @@ packages:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lodash-es@4.17.22:
|
||||
resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==}
|
||||
|
||||
lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
@@ -3650,6 +3668,10 @@ packages:
|
||||
resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
tagged-tag@1.0.0:
|
||||
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
tailwind-merge@3.4.0:
|
||||
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
|
||||
|
||||
@@ -3721,6 +3743,10 @@ packages:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
type-fest@5.4.1:
|
||||
resolution: {integrity: sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
typescript-eslint@8.53.1:
|
||||
resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -5392,6 +5418,12 @@ snapshots:
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
dependencies:
|
||||
'@types/lodash': 4.17.23
|
||||
|
||||
'@types/lodash@4.17.23': {}
|
||||
|
||||
'@types/mdast@4.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
@@ -6733,6 +6765,8 @@ snapshots:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
||||
lodash-es@4.17.22: {}
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
@@ -7639,6 +7673,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@pkgr/core': 0.2.9
|
||||
|
||||
tagged-tag@1.0.0: {}
|
||||
|
||||
tailwind-merge@3.4.0: {}
|
||||
|
||||
tailwindcss@4.1.18: {}
|
||||
@@ -7699,6 +7735,10 @@ snapshots:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
type-fest@5.4.1:
|
||||
dependencies:
|
||||
tagged-tag: 1.0.0
|
||||
|
||||
typescript-eslint@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@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)
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import type { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
import type { JsonValue } from 'type-fest';
|
||||
import axios from 'axios';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { router } from '@/lib/router';
|
||||
import { doRefreshToken, getRefreshToken, getToken, setRefreshToken, setToken } from './token';
|
||||
import { clearTokens, doRefreshToken, getRefreshToken, getToken, setRefreshToken, setToken } from './token';
|
||||
|
||||
export const HEADER_API_VERSION = {
|
||||
'X-Api-Version': 'latest',
|
||||
};
|
||||
|
||||
export const axiosClient = axios.create({
|
||||
baseURL: '/api/v1/',
|
||||
headers: HEADER_API_VERSION,
|
||||
});
|
||||
|
||||
axiosClient.interceptors.request.use((config) => {
|
||||
@@ -18,27 +25,43 @@ axiosClient.interceptors.request.use((config) => {
|
||||
|
||||
type RetryConfig = AxiosRequestConfig & { _retry?: boolean };
|
||||
|
||||
axiosClient.interceptors.response.use(undefined, async (error: AxiosError) => {
|
||||
interface ResponseData {
|
||||
code: number;
|
||||
error_id: string;
|
||||
status: string;
|
||||
data: JsonValue;
|
||||
}
|
||||
|
||||
axiosClient.interceptors.response.use(async (response) => {
|
||||
const data = response.data as ResponseData;
|
||||
if (data.code !== 200) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
response.data = data.data;
|
||||
return response;
|
||||
}, 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) {
|
||||
if (error.response.status === 401 && originalRequest.url !== '/auth/refresh' && !isNil(getRefreshToken())) {
|
||||
try {
|
||||
const maybeRefreshTokenValue = await doRefreshToken();
|
||||
const { access_token, refresh_token } = maybeRefreshTokenValue.data;
|
||||
const maybeRefreshTokenResponse = await doRefreshToken();
|
||||
if (maybeRefreshTokenResponse.status !== 200) {
|
||||
throw new Error('Failed to refresh token');
|
||||
}
|
||||
const { access_token, refresh_token } = maybeRefreshTokenResponse.data;
|
||||
originalRequest.headers = originalRequest.headers ?? {};
|
||||
originalRequest.headers.Authorization = `Bearer ${access_token}`;
|
||||
setToken(access_token);
|
||||
setRefreshToken(refresh_token);
|
||||
return await axiosClient(originalRequest);
|
||||
}
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
catch (e) {
|
||||
if (e instanceof AxiosError && e.status === 401) {
|
||||
await router.navigate({ to: '/authorize' });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
// Should remove token (tokens are out of date)
|
||||
clearTokens();
|
||||
await router.navigate({ to: '/authorize' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios from 'axios';
|
||||
import { axiosClient, HEADER_API_VERSION } from './axios';
|
||||
|
||||
export function setToken(token: string) {
|
||||
localStorage.setItem('token', token);
|
||||
@@ -30,11 +30,11 @@ export function clearTokens() {
|
||||
}
|
||||
|
||||
export async function doSetTokenByCode(code: string) {
|
||||
const { data } = await axios.post<{ access_token: string; refresh_token: string }>('/api/v1/auth/token', { code });
|
||||
const { data } = await axiosClient.post<{ access_token: string; refresh_token: string }>('/auth/token', { code }, { headers: HEADER_API_VERSION });
|
||||
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() });
|
||||
return axiosClient.post<{ access_token: string; refresh_token: string }>('/auth/refresh', { refresh_token: getRefreshToken() }, { headers: HEADER_API_VERSION });
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { zodValidator } from '@tanstack/zod-adapter';
|
||||
import { isNil } from 'lodash-es';
|
||||
import z from 'zod';
|
||||
import { LoginForm } from '@/components/login-form';
|
||||
import { axiosClient } from '@/lib/axios';
|
||||
import { generateOAuthState } from '@/lib/random';
|
||||
import { getToken } from '@/lib/token';
|
||||
|
||||
@@ -22,15 +24,21 @@ export const Route = createFileRoute('/authorize')({
|
||||
function RouteComponent() {
|
||||
const token = getToken();
|
||||
const oauthParams = Route.useSearch();
|
||||
if (token !== null) {
|
||||
const base = new URL(window.location.origin);
|
||||
const url = new URL('/api/v1/auth/redirect', base);
|
||||
url.searchParams.set('client_id', oauthParams.client_id);
|
||||
url.searchParams.set('response_type', oauthParams.response_type);
|
||||
url.searchParams.set('redirect_uri', oauthParams.redirect_uri);
|
||||
url.searchParams.set('state', oauthParams.state);
|
||||
window.location.href = url.toString();
|
||||
return null;
|
||||
/**
|
||||
* Auth by Token Flow
|
||||
*/
|
||||
if (!isNil(token)) {
|
||||
axiosClient.post<{ redirect_uri: string }>('/auth/exchange', {
|
||||
client_id: oauthParams.client_id,
|
||||
redirect_uri: oauthParams.redirect_uri,
|
||||
state: oauthParams.state,
|
||||
}).then((res) => {
|
||||
window.location.href = res.data.redirect_uri;
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
return 'Token exchange failed';
|
||||
});
|
||||
return 'Redirecting';
|
||||
}
|
||||
return (
|
||||
<div className="bg-background flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import path from "node:path";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import path from 'node:path';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { tanstackRouter } from '@tanstack/router-plugin/vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tanstackRouter({
|
||||
target: "react",
|
||||
target: 'react',
|
||||
autoCodeSplitting: true,
|
||||
}),
|
||||
react(),
|
||||
@@ -18,15 +18,15 @@ export default defineConfig({
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": "http://10.0.0.250:8000",
|
||||
'/api': 'http://10.0.0.10:8000',
|
||||
},
|
||||
host: "0.0.0.0",
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
allowedHosts: ["nix.org.cn", "nixos.party"],
|
||||
allowedHosts: ['nix.org.cn', 'nixos.party'],
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user