This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-01 19:20:00 +05:00
parent 7b76901f5f
commit dfa3e14d58
8 changed files with 232 additions and 31 deletions

View File

@@ -1,31 +1,198 @@
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import axios, {
AxiosRequestConfig,
AxiosResponse,
AxiosError,
InternalAxiosRequestConfig,
} from 'axios';
import { getRouteLang } from './getLanguage';
// ─── Constants ─────────────────────────────────────────────────────────────────
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;
const DEFAULT_LOCALE = 'uz'; // fallback locale for redirect
// ─── Token helpers ─────────────────────────────────────────────────────────────
// Adjust key names to match whatever your auth stores them under.
export const TokenStorage = {
getAccess: (): string | null => localStorage.getItem('access_token'),
getRefresh: (): string | null => localStorage.getItem('refresh_token'),
setAccess: (token: string): void =>
localStorage.setItem('access_token', token),
setRefresh: (token: string): void =>
localStorage.setItem('refresh_token', token),
setTokens: (access: string, refresh: string): void => {
localStorage.setItem('access_token', access);
localStorage.setItem('refresh_token', refresh);
},
clear: (): void => {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
},
};
// ─── Redirect helper ───────────────────────────────────────────────────────────
function redirectToMain(): void {
// Detect current locale from the URL path, fall back to DEFAULT_LOCALE
const pathLocale = window.location.pathname.split('/')[1];
const validLocales = ['uz', 'ru', 'en'];
const locale = validLocales.includes(pathLocale)
? pathLocale
: DEFAULT_LOCALE;
window.location.href = `/${locale}`;
}
// ─── Axios instance ────────────────────────────────────────────────────────────
const api = axios.create({
baseURL: baseUrl,
timeout: 15_000,
});
// ─── Flag to prevent multiple simultaneous refresh calls ──────────────────────
let isRefreshing = false;
// Queue of failed requests waiting for the new token
type FailedRequestResolver = (token: string) => void;
let failedQueue: {
resolve: FailedRequestResolver;
reject: (err: unknown) => void;
}[] = [];
function processQueue(error: unknown, token: string | null = null): void {
failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error);
} else if (token) {
resolve(token);
}
});
failedQueue = [];
}
// ─── Request interceptor — attach access token ────────────────────────────────
api.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = TokenStorage.getAccess();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
config.headers['Accept-Language'] = getRouteLang();
return config;
},
(error: AxiosError) => Promise.reject(error),
);
// ─── Response interceptor — handle 401, refresh, retry ───────────────────────
api.interceptors.response.use(
// ── 2xx: pass through unchanged ────────────────────────────────────────────
(response: AxiosResponse) => response,
// ── Error: attempt token refresh on 401 ────────────────────────────────────
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig & {
_retry?: boolean;
};
const status = error.response?.status;
// Only attempt refresh on 401 and only once per request
if (status !== 401 || originalRequest._retry) {
return Promise.reject(error);
}
const refreshToken = TokenStorage.getRefresh();
// No refresh token available — clear everything and redirect
if (!refreshToken) {
TokenStorage.clear();
redirectToMain();
return Promise.reject(error);
}
// If a refresh is already in progress, queue this request
if (isRefreshing) {
return new Promise((resolve, reject) => {
failedQueue.push({
resolve: (token: string) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(api(originalRequest));
},
reject,
});
});
}
// Mark that we're refreshing and this request has been retried
originalRequest._retry = true;
isRefreshing = true;
try {
// ── Call your refresh endpoint ──────────────────────────────────────────
// Adjust the URL and payload shape to match your backend.
const { data } = await axios.post<{
access_token: string;
refresh_token: string;
}>(
`${baseUrl}/auth/refresh`, // ← your refresh endpoint
{ refresh_token: refreshToken },
{ headers: { 'Accept-Language': getRouteLang() } },
);
const { access_token, refresh_token } = data;
TokenStorage.setTokens(access_token, refresh_token);
// Update the Authorization header for the retried request
originalRequest.headers.Authorization = `Bearer ${access_token}`;
// Unblock all queued requests with the new token
processQueue(null, access_token);
// Retry the original failed request
return api(originalRequest);
} catch (refreshError) {
// Refresh itself failed — clear tokens and redirect to home
processQueue(refreshError, null);
TokenStorage.clear();
redirectToMain();
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
},
);
// ─── Public request function ───────────────────────────────────────────────────
export const apiRequest = async <T>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
url: string,
data?: unknown,
config?: Omit<AxiosRequestConfig, 'method' | 'url' | 'data'>,
): Promise<AxiosResponse<T>> => {
// ← return full response
const response: AxiosResponse<T> = await api.request<T>({
const response = await api.request<T>({
method,
url,
data,
...config,
headers: {
'Accept-Language': getRouteLang(),
...config?.headers,
// Accept-Language is already set in the request interceptor,
// but config?.headers can still override it if needed.
},
});
console.log(response);
return response; // ← return response, not response.data
return response;
};

View File

@@ -2,4 +2,5 @@ export const links = {
login: '/users/login/',
register: '/users/register/',
plagiarismCheck: '/plagiarism/check/',
history: '/shared/documents/list/',
};

View File

@@ -6,14 +6,14 @@ interface User {
surname: string;
}
interface UserStore {
interface UserPlagiat {
user: User | null;
setUser: (user: User | null) => void;
clearUser: () => void;
getUser: () => User | null;
}
export const useUserStore = create<UserStore>((set, get) => ({
export const useUserPlagiatStore = create<UserPlagiat>((set, get) => ({
user: null,
setUser: (user: User | null) => set({ user }),
clearUser: () => set({ user: null }),