vulneribilty fixed
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
"about": "Go to the about page"
|
||||
},
|
||||
"Navbar": {
|
||||
"logo": "Plagat",
|
||||
"logo": "Plagiat",
|
||||
"aboutSite": "About Site",
|
||||
"contact": "Contact",
|
||||
"login": "Login",
|
||||
@@ -260,7 +260,61 @@
|
||||
"close": "Close",
|
||||
"personalCabinet": "Personal Cabinet",
|
||||
"plagiatChecks": "Plagiarism Checks",
|
||||
"dashboard": "Dashboard"
|
||||
"dashboard": "Dashboard",
|
||||
"welcome": "Welcome, {userName} 👋",
|
||||
"welcomeDesc": "Welcome to your personal cabinet",
|
||||
"quickActions": "Quick Actions",
|
||||
"totalChecks": "Total Checks",
|
||||
"thisMonth": "This Month",
|
||||
"paidAmount": "Amount Paid",
|
||||
"noData": "No data found",
|
||||
"checkModules": "Check Modules",
|
||||
"checkModulesDesc": "All sources used for plagiarism detection",
|
||||
"modulesCount": "{count} modules",
|
||||
"totalModules": "Total Modules",
|
||||
"freeInternetSources": "Free Internet Sources",
|
||||
"aiAnalysisModules": "AI Analysis Modules",
|
||||
"categories": "Categories",
|
||||
"paymentsCount": "{count} payments",
|
||||
"loading": "Loading...",
|
||||
"noPayments": "No payment history",
|
||||
"tableNum": "#",
|
||||
"service": "Service",
|
||||
"amount": "Amount",
|
||||
"discount": "Discount",
|
||||
"date": "Date",
|
||||
"status": "Status",
|
||||
"unknown": "Unknown",
|
||||
"noSiChecks": "No AI checks yet",
|
||||
"loadError": "Failed to load data",
|
||||
"paid": "Paid",
|
||||
"unpaid": "Unpaid",
|
||||
"checksCount": "{count} checks",
|
||||
"tableTitle": "Title",
|
||||
"tableFile": "File",
|
||||
"words": "Words",
|
||||
"action": "Action",
|
||||
"pay": "Pay",
|
||||
"view": "View",
|
||||
"profileDesc": "Manage your information",
|
||||
"personalInfo": "Personal Information",
|
||||
"changePassword": "Change Password",
|
||||
"firstName": "First Name",
|
||||
"lastName": "Last Name",
|
||||
"phone": "Phone",
|
||||
"email": "Email",
|
||||
"newPassword": "New Password",
|
||||
"saved": "Saved",
|
||||
"saving": "Saving…",
|
||||
"save": "Save",
|
||||
"firstNameRequired": "First name is required",
|
||||
"lastNameRequired": "Last name is required",
|
||||
"phoneInvalid": "Phone number must be 9 digits",
|
||||
"passwordTooShort": "Password must be at least 8 characters",
|
||||
"discountThisMonth": "Discount this month",
|
||||
"discountRemaining": "Discount expires after {remaining} documents",
|
||||
"discountAllUsed": "All discounts for this month have been used",
|
||||
"discountUsed": "{count} used"
|
||||
},
|
||||
"SiDetail": {
|
||||
"siCheck": "AI Check",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"about": "Перейти на страницу о нас"
|
||||
},
|
||||
"Navbar": {
|
||||
"logo": "Plagat",
|
||||
"logo": "Plagiat",
|
||||
"aboutSite": "О сайте",
|
||||
"contact": "Контакты",
|
||||
"login": "Войти",
|
||||
@@ -259,7 +259,61 @@
|
||||
"close": "Закрыть",
|
||||
"personalCabinet": "Личный кабинет",
|
||||
"plagiatChecks": "Проверки на плагиат",
|
||||
"dashboard": "Dashboard"
|
||||
"dashboard": "Dashboard",
|
||||
"welcome": "Добро пожаловать, {userName} 👋",
|
||||
"welcomeDesc": "Добро пожаловать в ваш личный кабинет",
|
||||
"quickActions": "Быстрые действия",
|
||||
"totalChecks": "Всего проверок",
|
||||
"thisMonth": "Этот месяц",
|
||||
"paidAmount": "Оплаченная сумма",
|
||||
"noData": "Данные не найдены",
|
||||
"checkModules": "Модули проверки",
|
||||
"checkModulesDesc": "Все источники, используемые для обнаружения плагиата",
|
||||
"modulesCount": "{count} модулей",
|
||||
"totalModules": "Всего модулей",
|
||||
"freeInternetSources": "Бесплатные интернет-источники",
|
||||
"aiAnalysisModules": "Модули AI анализа",
|
||||
"categories": "Категория",
|
||||
"paymentsCount": "{count} платежей",
|
||||
"loading": "Загрузка...",
|
||||
"noPayments": "История платежей отсутствует",
|
||||
"tableNum": "#",
|
||||
"service": "Услуга",
|
||||
"amount": "Сумма",
|
||||
"discount": "Скидка",
|
||||
"date": "Дата",
|
||||
"status": "Статус",
|
||||
"unknown": "Неизвестно",
|
||||
"noSiChecks": "Проверок ИИ пока нет",
|
||||
"loadError": "Ошибка загрузки данных",
|
||||
"paid": "Оплачено",
|
||||
"unpaid": "Не оплачено",
|
||||
"checksCount": "{count} проверок",
|
||||
"tableTitle": "Заголовок",
|
||||
"tableFile": "Файл",
|
||||
"words": "Слов",
|
||||
"action": "Действие",
|
||||
"pay": "Оплатить",
|
||||
"view": "Просмотр",
|
||||
"profileDesc": "Управляйте своими данными",
|
||||
"personalInfo": "Личные данные",
|
||||
"changePassword": "Изменить пароль",
|
||||
"firstName": "Имя",
|
||||
"lastName": "Фамилия",
|
||||
"phone": "Телефон",
|
||||
"email": "Email",
|
||||
"newPassword": "Новый пароль",
|
||||
"saved": "Сохранено",
|
||||
"saving": "Сохранение…",
|
||||
"save": "Сохранить",
|
||||
"firstNameRequired": "Имя обязательно",
|
||||
"lastNameRequired": "Фамилия обязательна",
|
||||
"phoneInvalid": "Номер телефона должен содержать 9 цифр",
|
||||
"passwordTooShort": "Пароль должен содержать не менее 8 символов",
|
||||
"discountThisMonth": "Скидка в этом месяце",
|
||||
"discountRemaining": "Скидка истекает через {remaining} документов",
|
||||
"discountAllUsed": "Все скидки этого месяца использованы",
|
||||
"discountUsed": "{count} использовано"
|
||||
},
|
||||
"SiDetail": {
|
||||
"siCheck": "Проверка ИИ",
|
||||
|
||||
@@ -7,7 +7,7 @@ declare const messages: {
|
||||
about: "Biz haqimizda sahifasiga o'ting";
|
||||
};
|
||||
Navbar: {
|
||||
logo: 'Plagat';
|
||||
logo: 'Plagiat';
|
||||
aboutSite: 'Sayt haqida';
|
||||
contact: 'Aloqa';
|
||||
login: 'Kirish';
|
||||
@@ -264,6 +264,60 @@ declare const messages: {
|
||||
personalCabinet: 'Shaxsiy kabinet';
|
||||
plagiatChecks: 'Plagiat tekshiruvlar';
|
||||
dashboard: 'Dashboard';
|
||||
welcome: 'Xush kelibsiz, {userName} 👋';
|
||||
welcomeDesc: 'Shaxsiy kabinetingizga xush kelibsiz';
|
||||
quickActions: 'Tezkor harakatlar';
|
||||
totalChecks: 'Jami tekshiruvlar';
|
||||
thisMonth: 'Bu oy';
|
||||
paidAmount: "To'langan summa";
|
||||
noData: "Ma'lumot topilmadi";
|
||||
checkModules: 'Tekshiruv modullari';
|
||||
checkModulesDesc: 'Plagiat aniqlashda foydalaniladigan barcha manbalar';
|
||||
modulesCount: '{count} ta modul';
|
||||
totalModules: 'Jami modullar';
|
||||
freeInternetSources: 'Bepul internet manbalari';
|
||||
aiAnalysisModules: 'AI tahlil modullari';
|
||||
categories: 'Kategoriya';
|
||||
paymentsCount: "{count} ta to'lov";
|
||||
loading: 'Yuklanmoqda...';
|
||||
noPayments: "To'lovlar tarixi mavjud emas";
|
||||
tableNum: '#';
|
||||
service: 'Xizmat';
|
||||
amount: 'Summa';
|
||||
discount: 'Chegirma';
|
||||
date: 'Sana';
|
||||
status: 'Holat';
|
||||
unknown: "Noma'lum";
|
||||
noSiChecks: "Hozircha SI tekshiruvlar yo'q";
|
||||
loadError: "Ma'lumotlarni yuklashda xatolik yuz berdi";
|
||||
paid: "To'langan";
|
||||
unpaid: "To'lanmagan";
|
||||
checksCount: '{count} ta tekshiruv';
|
||||
tableTitle: 'Sarlavha';
|
||||
tableFile: 'Fayl';
|
||||
words: "So'z";
|
||||
action: 'Amal';
|
||||
pay: "To'lash";
|
||||
view: "Ko'rish";
|
||||
profileDesc: "Ma'lumotlaringizni boshqaring";
|
||||
personalInfo: "Shaxsiy ma'lumotlar";
|
||||
changePassword: "Parol o'zgartirish";
|
||||
firstName: 'Ism';
|
||||
lastName: 'Familiya';
|
||||
phone: 'Telefon';
|
||||
email: 'Email';
|
||||
newPassword: 'Yangi parol';
|
||||
saved: 'Saqlandi';
|
||||
saving: 'Saqlanmoqda…';
|
||||
save: 'Saqlash';
|
||||
firstNameRequired: 'Ism kiritilishi shart';
|
||||
lastNameRequired: 'Familiya kiritilishi shart';
|
||||
phoneInvalid: "Telefon raqami 9 ta raqamdan iborat bo'lishi kerak";
|
||||
passwordTooShort: "Parol kamida 8 ta belgidan iborat bo'lishi kerak";
|
||||
discountThisMonth: 'Bu oyda chegirma';
|
||||
discountRemaining: '{remaining} ta hujjatdan keyin chegirma tugaydi';
|
||||
discountAllUsed: 'Bu oyda barcha chegirmalar ishlatildi';
|
||||
discountUsed: '{count} ta ishlatildi';
|
||||
};
|
||||
SiDetail: {
|
||||
siCheck: 'SI tekshiruv';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"about": "Biz haqimizda sahifasiga o'ting"
|
||||
},
|
||||
"Navbar": {
|
||||
"logo": "Plagat",
|
||||
"logo": "Plagiat",
|
||||
"aboutSite": "Sayt haqida",
|
||||
"contact": "Aloqa",
|
||||
"login": "Kirish",
|
||||
@@ -260,7 +260,61 @@
|
||||
"close": "Yopish",
|
||||
"personalCabinet": "Shaxsiy kabinet",
|
||||
"plagiatChecks": "Plagiat tekshiruvlar",
|
||||
"dashboard": "Dashboard"
|
||||
"dashboard": "Dashboard",
|
||||
"welcome": "Xush kelibsiz, {userName} 👋",
|
||||
"welcomeDesc": "Shaxsiy kabinetingizga xush kelibsiz",
|
||||
"quickActions": "Tezkor harakatlar",
|
||||
"totalChecks": "Jami tekshiruvlar",
|
||||
"thisMonth": "Bu oy",
|
||||
"paidAmount": "To'langan summa",
|
||||
"noData": "Ma'lumot topilmadi",
|
||||
"checkModules": "Tekshiruv modullari",
|
||||
"checkModulesDesc": "Plagiat aniqlashda foydalaniladigan barcha manbalar",
|
||||
"modulesCount": "{count} ta modul",
|
||||
"totalModules": "Jami modullar",
|
||||
"freeInternetSources": "Bepul internet manbalari",
|
||||
"aiAnalysisModules": "AI tahlil modullari",
|
||||
"categories": "Kategoriya",
|
||||
"paymentsCount": "{count} ta to'lov",
|
||||
"loading": "Yuklanmoqda...",
|
||||
"noPayments": "To'lovlar tarixi mavjud emas",
|
||||
"tableNum": "#",
|
||||
"service": "Xizmat",
|
||||
"amount": "Summa",
|
||||
"discount": "Chegirma",
|
||||
"date": "Sana",
|
||||
"status": "Holat",
|
||||
"unknown": "Noma'lum",
|
||||
"noSiChecks": "Hozircha SI tekshiruvlar yo'q",
|
||||
"loadError": "Ma'lumotlarni yuklashda xatolik yuz berdi",
|
||||
"paid": "To'langan",
|
||||
"unpaid": "To'lanmagan",
|
||||
"checksCount": "{count} ta tekshiruv",
|
||||
"tableTitle": "Sarlavha",
|
||||
"tableFile": "Fayl",
|
||||
"words": "So'z",
|
||||
"action": "Amal",
|
||||
"pay": "To'lash",
|
||||
"view": "Ko'rish",
|
||||
"profileDesc": "Ma'lumotlaringizni boshqaring",
|
||||
"personalInfo": "Shaxsiy ma'lumotlar",
|
||||
"changePassword": "Parol o'zgartirish",
|
||||
"firstName": "Ism",
|
||||
"lastName": "Familiya",
|
||||
"phone": "Telefon",
|
||||
"email": "Email",
|
||||
"newPassword": "Yangi parol",
|
||||
"saved": "Saqlandi",
|
||||
"saving": "Saqlanmoqda…",
|
||||
"save": "Saqlash",
|
||||
"firstNameRequired": "Ism kiritilishi shart",
|
||||
"lastNameRequired": "Familiya kiritilishi shart",
|
||||
"phoneInvalid": "Telefon raqami 9 ta raqamdan iborat bo'lishi kerak",
|
||||
"passwordTooShort": "Parol kamida 8 ta belgidan iborat bo'lishi kerak",
|
||||
"discountThisMonth": "Bu oyda chegirma",
|
||||
"discountRemaining": "{remaining} ta hujjatdan keyin chegirma tugaydi",
|
||||
"discountAllUsed": "Bu oyda barcha chegirmalar ishlatildi",
|
||||
"discountUsed": "{count} ta ishlatildi"
|
||||
},
|
||||
"SiDetail": {
|
||||
"siCheck": "SI tekshiruv",
|
||||
|
||||
@@ -4,8 +4,44 @@ import axios, {
|
||||
AxiosError,
|
||||
InternalAxiosRequestConfig,
|
||||
} from 'axios';
|
||||
import { toast } from 'react-toastify';
|
||||
import { getRouteLang } from './getLanguage';
|
||||
|
||||
// ─── Error message extractor ───────────────────────────────────────────────────
|
||||
|
||||
function extractErrorMessage(error: AxiosError): string {
|
||||
const data = error.response?.data as Record<string, unknown> | undefined;
|
||||
|
||||
if (!data) {
|
||||
if (error.code === 'ECONNABORTED')
|
||||
return 'Request timed out. Please try again.';
|
||||
if (!navigator.onLine) return 'No internet connection.';
|
||||
return error.message || 'An unexpected error occurred.';
|
||||
}
|
||||
|
||||
// Simple string fields: { message, detail, error }
|
||||
if (typeof data.message === 'string' && data.message) return data.message;
|
||||
if (typeof data.detail === 'string' && data.detail) return data.detail;
|
||||
if (typeof data.error === 'string' && data.error) return data.error;
|
||||
|
||||
// Wrapped: { errors: { field: ["msg"] } }
|
||||
if (data.errors && typeof data.errors === 'object') {
|
||||
const first = Object.values(data.errors as Record<string, unknown>)[0];
|
||||
if (Array.isArray(first) && first.length > 0) return String(first[0]);
|
||||
if (typeof first === 'string') return first;
|
||||
}
|
||||
|
||||
// DRF field-level errors at top level: { phone: ["msg"], name: ["msg"] }
|
||||
for (const val of Object.values(data)) {
|
||||
if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'string') {
|
||||
return val[0];
|
||||
}
|
||||
if (typeof val === 'string' && val) return val;
|
||||
}
|
||||
|
||||
return 'An unexpected error occurred.';
|
||||
}
|
||||
|
||||
// ─── Constants ─────────────────────────────────────────────────────────────────
|
||||
|
||||
// const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
@@ -107,9 +143,14 @@ api.interceptors.response.use(
|
||||
};
|
||||
|
||||
const status = error.response?.status;
|
||||
const requestUrl = originalRequest.url ?? '';
|
||||
const isAuthEndpoint =
|
||||
requestUrl.includes('/users/login/') ||
|
||||
requestUrl.includes('/users/register/');
|
||||
|
||||
// Only attempt refresh on 401 and only once per request
|
||||
if (status !== 401 || originalRequest._retry) {
|
||||
// For auth endpoints, 401 means wrong credentials — show error, don't refresh
|
||||
if (isAuthEndpoint || status !== 401 || originalRequest._retry) {
|
||||
toast.error(extractErrorMessage(error));
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const links = {
|
||||
login: '/users/login/',
|
||||
register: '/users/register/',
|
||||
plagiarismCheck: '/shared/documents/',
|
||||
plagiarismCheck: '/shared/document/',
|
||||
history: '/shared/documents/list/',
|
||||
detail: (id: number) => `/shared/documents/${id}/`,
|
||||
payment: (order_id: number) => `/users/payme/link/${order_id}/`,
|
||||
|
||||
Reference in New Issue
Block a user