Update admin theme/layout and refresh README details.
Align the project baseline with the latest admin interface styling and layout structure while documenting setup and usage updates in README.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import axios, { type AxiosError, type InternalAxiosRequestConfig } from "axios";
|
||||
import { useAuthStore } from "@modules/auth/store/authStore";
|
||||
import { getApiBaseUrl } from "./config";
|
||||
|
||||
const apiBaseUrl = getApiBaseUrl();
|
||||
|
||||
export const apiClient = axios.create({
|
||||
baseURL: apiBaseUrl,
|
||||
timeout: 10_000
|
||||
});
|
||||
|
||||
export const authClient = axios.create({
|
||||
baseURL: apiBaseUrl,
|
||||
timeout: 10_000,
|
||||
withCredentials: true
|
||||
});
|
||||
let getAccessToken: () => string | null = () => null;
|
||||
let refreshPromise: Promise<string | null> | null = null;
|
||||
|
||||
export function setAccessTokenGetter(getter: () => string | null): void {
|
||||
getAccessToken = getter;
|
||||
}
|
||||
|
||||
export function applyAuthHeader(headers: Record<string, string>): Record<string, string> {
|
||||
const token = getAccessToken();
|
||||
if (token) {
|
||||
return { ...headers, Authorization: `Bearer ${token}` };
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
export async function bootstrapSessionRefresh(): Promise<string | null> {
|
||||
return refreshAccessToken();
|
||||
}
|
||||
|
||||
async function refreshAccessToken(): Promise<string | null> {
|
||||
if (!refreshPromise) {
|
||||
refreshPromise = authClient
|
||||
.post("/api/v1/auth/refresh")
|
||||
.then((response) => {
|
||||
const { access_token: accessToken, user } = response.data;
|
||||
useAuthStore.getState().setSession(accessToken, user);
|
||||
return accessToken as string;
|
||||
})
|
||||
.catch(() => {
|
||||
useAuthStore.getState().clearSession();
|
||||
return null;
|
||||
})
|
||||
.finally(() => {
|
||||
refreshPromise = null;
|
||||
});
|
||||
}
|
||||
return refreshPromise;
|
||||
}
|
||||
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
config.headers = applyAuthHeader(config.headers as Record<string, string>) as typeof config.headers;
|
||||
return config;
|
||||
});
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
const detail =
|
||||
error.response && typeof error.response.data === "object" && error.response.data !== null
|
||||
? (error.response.data as { detail?: string }).detail
|
||||
: undefined;
|
||||
if (
|
||||
error.response?.status === 403 &&
|
||||
(detail === "ACCOUNT_BLOCKED" || detail === "EMAIL_NOT_VERIFIED")
|
||||
) {
|
||||
useAuthStore.getState().clearSession();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
|
||||
if (error.response?.status !== 401 || !originalRequest || originalRequest._retry) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
if (originalRequest.url?.includes("/api/v1/auth/")) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
originalRequest._retry = true;
|
||||
const accessToken = await refreshAccessToken();
|
||||
if (!accessToken) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
originalRequest.headers = applyAuthHeader(
|
||||
originalRequest.headers as Record<string, string>
|
||||
) as typeof originalRequest.headers;
|
||||
return apiClient.request(originalRequest);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user