api ulandi
This commit is contained in:
@@ -1,6 +1,41 @@
|
||||
const BASE_URL =
|
||||
import.meta.env.VITE_API_URL || 'https://jsonplaceholder.typicode.com';
|
||||
import.meta.env.VITE_API_URL || "https://simple-travel.felixits.uz/api/v1/";
|
||||
|
||||
const ENDP_POSTS = '/posts/';
|
||||
const AUTH_LOGIN = "auth/token/phone/";
|
||||
const GET_ME = "auth/me/";
|
||||
const GET_ALL_USERS = "dashboard/users/";
|
||||
const DOWNLOAD_PDF = "get-order-pdf/";
|
||||
const UPDATE_USER = "/dashboard/users/";
|
||||
const GET_ALL_AGENCY = "dashboard/tour-agency/";
|
||||
const GET_ALL_EMPLOYEES = "dashboard/employees/";
|
||||
const GET_TICKET = "dashboard/dashboard-tickets/";
|
||||
const HOTEL_BADGE = "dashboard/dashboard-tickets-settings-badge/";
|
||||
const HOTEL_FEATURES = "dashboard/dashboard-ticket-hotel-feature-type/";
|
||||
const HOTEL_FEATURES_TYPE = "dashboard/dashboard-ticket-hotel-feature/";
|
||||
const HOTEL_TARIF = "dashboard/dashboard-tickets-settings-tariff/";
|
||||
const TOUR_TRANSPORT = "dashboard/dashboard-tickets-settings-transport/";
|
||||
const HPTEL_TYPES = "dashboard/dashboard-tickets-settings-hotel-type/";
|
||||
const NEWS = "dashboard/dashboard-post/";
|
||||
const NEWS_CATEGORY = "dashboard/dashboard-category/";
|
||||
const HOTEL = "dashboard/dashboard-hotel/";
|
||||
|
||||
export { BASE_URL, ENDP_POSTS };
|
||||
export {
|
||||
AUTH_LOGIN,
|
||||
BASE_URL,
|
||||
DOWNLOAD_PDF,
|
||||
GET_ALL_AGENCY,
|
||||
GET_ALL_EMPLOYEES,
|
||||
GET_ALL_USERS,
|
||||
GET_ME,
|
||||
GET_TICKET,
|
||||
HOTEL,
|
||||
HOTEL_BADGE,
|
||||
HOTEL_FEATURES,
|
||||
HOTEL_FEATURES_TYPE,
|
||||
HOTEL_TARIF,
|
||||
HPTEL_TYPES,
|
||||
NEWS,
|
||||
NEWS_CATEGORY,
|
||||
TOUR_TRANSPORT,
|
||||
UPDATE_USER,
|
||||
};
|
||||
|
||||
22
src/shared/config/api/auth/api.ts
Normal file
22
src/shared/config/api/auth/api.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { LoginData, MeData } from "@/shared/config/api/auth/auth.model";
|
||||
import httpClient from "@/shared/config/api/httpClient";
|
||||
import { AUTH_LOGIN, GET_ME } from "@/shared/config/api/URLs";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
const authLogin = async ({
|
||||
phone,
|
||||
password,
|
||||
}: {
|
||||
phone: string;
|
||||
password: string;
|
||||
}): Promise<AxiosResponse<LoginData>> => {
|
||||
const response = await httpClient.post(AUTH_LOGIN, { phone, password });
|
||||
return response;
|
||||
};
|
||||
|
||||
const getMe = async (): Promise<AxiosResponse<MeData>> => {
|
||||
const response = await httpClient.get(GET_ME);
|
||||
return response;
|
||||
};
|
||||
|
||||
export { authLogin, getMe };
|
||||
32
src/shared/config/api/auth/auth.model.ts
Normal file
32
src/shared/config/api/auth/auth.model.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export interface LoginData {
|
||||
access: string;
|
||||
refresh: string;
|
||||
}
|
||||
|
||||
export interface MeData {
|
||||
status: boolean;
|
||||
data: {
|
||||
id: number;
|
||||
last_login: string;
|
||||
is_superuser: boolean;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
is_staff: boolean;
|
||||
is_active: boolean;
|
||||
date_joined: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
username: string;
|
||||
avatar: string;
|
||||
validated_at: string;
|
||||
role:
|
||||
| "superuser"
|
||||
| "admin"
|
||||
| "moderator"
|
||||
| "tour_admin"
|
||||
| "buxgalter"
|
||||
| "operator"
|
||||
| "user";
|
||||
travel_agency: number;
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,28 @@
|
||||
import i18n from "@/shared/config/i18n";
|
||||
import axios from "axios";
|
||||
import {
|
||||
getAuthToken,
|
||||
getRefAuthToken,
|
||||
removeAuthToken,
|
||||
removeRefAuthToken,
|
||||
setAuthToken,
|
||||
} from "@/shared/lib/authCookies";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import { BASE_URL } from "./URLs";
|
||||
|
||||
let isRefreshing = false;
|
||||
let failedQueue: {
|
||||
resolve: (token: string) => void;
|
||||
reject: (error: any) => void;
|
||||
}[] = [];
|
||||
|
||||
const processQueue = (error: any, token: string | null = null) => {
|
||||
failedQueue.forEach((prom) => {
|
||||
if (error) prom.reject(error);
|
||||
else if (token) prom.resolve(token);
|
||||
});
|
||||
failedQueue = [];
|
||||
};
|
||||
|
||||
const httpClient = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
timeout: 10000,
|
||||
@@ -9,13 +30,16 @@ const httpClient = axios.create({
|
||||
|
||||
httpClient.interceptors.request.use(
|
||||
async (config) => {
|
||||
// Language configs
|
||||
const language = i18n.language;
|
||||
config.headers["Accept-Language"] = language;
|
||||
// const accessToken = localStorage.getItem('accessToken');
|
||||
// if (accessToken) {
|
||||
// config.headers['Authorization'] = `Bearer ${accessToken}`;
|
||||
// }
|
||||
// Faqat GET so'rovlarida Accept-Language headerini qo'shish
|
||||
if (config.method?.toLowerCase() === "get") {
|
||||
const language = i18n.language;
|
||||
config.headers["Accept-Language"] = language;
|
||||
}
|
||||
|
||||
const accessToken = getAuthToken();
|
||||
if (accessToken) {
|
||||
config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
@@ -24,7 +48,62 @@ httpClient.interceptors.request.use(
|
||||
|
||||
httpClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
async (error: AxiosError) => {
|
||||
const originalRequest = error.config as any;
|
||||
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
if (isRefreshing) {
|
||||
return new Promise((resolve, reject) => {
|
||||
failedQueue.push({ resolve, reject });
|
||||
})
|
||||
.then((token) => {
|
||||
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
||||
return httpClient(originalRequest);
|
||||
})
|
||||
.catch((err) => Promise.reject(err));
|
||||
}
|
||||
|
||||
originalRequest._retry = true;
|
||||
isRefreshing = true;
|
||||
|
||||
const refreshToken = getRefAuthToken();
|
||||
if (!refreshToken) {
|
||||
removeAuthToken();
|
||||
removeRefAuthToken();
|
||||
window.location.href = "/login";
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post(`${BASE_URL}auth/token/refresh/`, {
|
||||
refresh: refreshToken,
|
||||
});
|
||||
|
||||
const newAccessToken = response.data.access;
|
||||
setAuthToken(newAccessToken);
|
||||
|
||||
httpClient.defaults.headers["Authorization"] =
|
||||
`Bearer ${newAccessToken}`;
|
||||
processQueue(null, newAccessToken);
|
||||
|
||||
originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`;
|
||||
return httpClient(originalRequest);
|
||||
} catch (refreshError: any) {
|
||||
processQueue(refreshError, null);
|
||||
removeAuthToken();
|
||||
removeRefAuthToken();
|
||||
const status = refreshError.response?.status;
|
||||
|
||||
if ([401].includes(status)) {
|
||||
window.location.href = "/login";
|
||||
}
|
||||
|
||||
return Promise.reject(refreshError);
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
console.error("API error:", error);
|
||||
return Promise.reject(error);
|
||||
},
|
||||
|
||||
@@ -1,14 +1 @@
|
||||
import httpClient from '@/shared/config/api/httpClient';
|
||||
import type { TestApiType } from '@/shared/config/api/test/test.model';
|
||||
import type { ReqWithPagination } from '@/shared/config/api/types';
|
||||
import { ENDP_POSTS } from '@/shared/config/api/URLs';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
|
||||
const getPosts = async (
|
||||
pagination?: ReqWithPagination,
|
||||
): Promise<AxiosResponse<TestApiType>> => {
|
||||
const response = await httpClient.get(ENDP_POSTS, { params: pagination });
|
||||
return response;
|
||||
};
|
||||
|
||||
export { getPosts };
|
||||
|
||||
@@ -1,13 +1,332 @@
|
||||
{
|
||||
"welcome": "Добро пожаловать на наш сайт",
|
||||
"language": "Язык",
|
||||
"Admin Panelga Kirish": "Вход в Админ Панель",
|
||||
"Telefon raqam": "Номер телефона",
|
||||
"Parol": "Пароль",
|
||||
"Parolingizni kiriting": "Введите пароль",
|
||||
"Kirish": "Войти",
|
||||
"Admin Panel": "Админ Панель",
|
||||
"Foydalanuvchilar": "Пользователи",
|
||||
"Tur firmalar": "Турфирмы",
|
||||
"Xodimlar": "Сотрудники",
|
||||
"Byudjet": "Бюджет",
|
||||
"Turlar": "Туры",
|
||||
"Tur sozlamalari": "Настройки туров",
|
||||
"Bronlar": "Бронирования",
|
||||
"Yangiliklar": "Новости",
|
||||
"Yordam Arizalar": "Заявки на помощь",
|
||||
"Tur sozlamalari": "Настройки тура"
|
||||
"Kategoriya": "Категория",
|
||||
"FAQ": "FAQ",
|
||||
"Savollar ro‘yxati": "Список вопросов",
|
||||
"Savollar kategoriyasi": "Категории вопросов",
|
||||
"Arizalar": "Заявки",
|
||||
"Agentlik arizalari": "Заявки агентств",
|
||||
"Yordam arizalari": "Заявки на помощь",
|
||||
"Sayt sozlamalari": "Настройки сайта",
|
||||
"Sayt SEOsi": "SEO сайта",
|
||||
"Offerta": "Оферта",
|
||||
"Yordam pagelari": "Страницы помощи",
|
||||
"Jami": "Всего",
|
||||
"ta foydalanuvchini boshqaring": "управляйте пользователями",
|
||||
"Foydalanuvchi Qo'shish": "Добавить пользователя",
|
||||
"Jami foydalanuvchilar": "Всего пользователей",
|
||||
"Email bilan ro'yxatlangan": "Зарегистрированы по email",
|
||||
"Telefon bilan ro'yxatlangan": "Зарегистрированы по телефону",
|
||||
"Username, email yoki telefon raqami bo'yicha qidirish": "Поиск по имени пользователя, email или номеру телефона...",
|
||||
"Faol": "Активен",
|
||||
"Ko'rish": "Просмотр",
|
||||
"Tahrirlash": "Редактировать",
|
||||
"O'chirish": "Удалить",
|
||||
"Foydalanuvchini o'chirish": "Удалить пользователя",
|
||||
"Siz": "Вы",
|
||||
"foydalanuvchini o'chirmoqchimisiz?": "хотите удалить пользователя?",
|
||||
"Ushbu amalni qaytarib bo'lmaydi": "Это действие невозможно отменить.",
|
||||
"Bekor qilish": "Отмена",
|
||||
"Orqaga": "Назад",
|
||||
"Yangi foydalanuvchi": "Новый пользователь",
|
||||
"Ma'lumotlarni to'ldiring va saqlang": "Заполните данные и сохраните",
|
||||
"Ismi": "Имя",
|
||||
"Email": "Email",
|
||||
"Telefon raqami": "Номер телефона",
|
||||
"Parolni tasdiqlang": "Подтвердите пароль",
|
||||
"Saqlash": "Сохранить",
|
||||
"Username majburiy": "Имя пользователя обязательно",
|
||||
"Username kamida 3 ta belgidan iborat bo'lishi kerak": "Имя пользователя должно содержать не менее 3 символов",
|
||||
"Email yoki telefon raqamlardan kamida bittasi kiritilishi kerak": "Должен быть введен как минимум один из email или номера телефона",
|
||||
"Email formati noto'g'ri": "Неверный формат email",
|
||||
"Telefon raqami formati: +998901234567": "Формат номера телефона: +998901234567",
|
||||
"Parol majburiy": "Пароль обязателен",
|
||||
"Parol kamida 8 ta belgidan iborat bo'lishi kerak": "Пароль должен содержать не менее 8 символов",
|
||||
"Parollar mos kelmaydi": "Пароли не совпадают",
|
||||
"Ma'lumotlarni yangilang": "Обновите данные",
|
||||
"Yangilash": "Обновить",
|
||||
"Nofaol": "Неактивен",
|
||||
"Aloqa ma'lumotlari": "Контактная информация",
|
||||
"Hisob ma'lumotlari": "Данные учетной записи",
|
||||
"Yaratilgan sana": "Дата создания",
|
||||
"Sotib olingan chiptalar": "Купленные билеты",
|
||||
"Chipta turi": "Тип билета",
|
||||
"Xizmat": "Сервис",
|
||||
"Manzil": "Адрес",
|
||||
"Transport": "Транспорт",
|
||||
"Jo'nash": "Отправление",
|
||||
"Yetish": "Прибытие",
|
||||
"Yo'lovchilar": "Пассажиры",
|
||||
"Qo'shimcha xizmatlar": "Дополнительные услуги",
|
||||
"Pullik xizmatlar": "Платные услуги",
|
||||
"Jami narx": "Общая цена",
|
||||
"PDF yuklab olish": "Скачать PDF",
|
||||
"Hozircha chiptalar mavjud emas": "Пока что билетов нет",
|
||||
"Hamrohlar": "Партнёры",
|
||||
"Erkak": "Мужчина",
|
||||
"Ayol": "Женщина",
|
||||
"Tug'ilgan sana": "Дата рождения",
|
||||
"Passport rasmlari": "Изображения паспорта",
|
||||
"Hozircha hamrohlar qo'shilmagan": "Пока что партнёры не добавлены",
|
||||
"Statistika": "Статистика",
|
||||
"Chiptalar": "Билеты",
|
||||
"Status": "Статус",
|
||||
"ID": "ID",
|
||||
"Qo'shimcha ma'lumot": "Дополнительная информация",
|
||||
"Bu foydalanuvchi hozirda tizimda faol holatda. Barcha ma'lumotlar to'liq va tasdiqlangan.": "Этот пользователь в настоящее время активен в системе. Все данные полные и подтвержденные.",
|
||||
"Tur firmalari": "Турфирмы",
|
||||
"Tur firmalaringizni boshqaring va ularning faoliyatini kuzatib boring.": "Управляйте своими турфирмами и отслеживайте их деятельность.",
|
||||
"Jami firmalar": "Всего фирм",
|
||||
"Faol firmalar": "Активные фирмы",
|
||||
"Jami turlar": "Всего туров",
|
||||
"Umumiy daromad": "Общий доход",
|
||||
"Komissiya": "Комиссия",
|
||||
"Jami tur": "Всего туров",
|
||||
"Sotilgan tur": "Проданные туры",
|
||||
"Daromad": "Доход",
|
||||
"Egasi": "Владелец",
|
||||
"Qo'shilgan turlar soni": "Количество добавленных туров",
|
||||
"Sotilgan turlar soni": "Количество проданных туров",
|
||||
"Jami sotilgan turlar": "Всего проданных туров",
|
||||
"Ulush foizi": "Процент доли",
|
||||
"Har bir sotuvdan": "С каждого проданного тура",
|
||||
"so'm daromad": "сум дохода",
|
||||
"Qo'shilgan turlar": "Добавленные туры",
|
||||
"Firma tomonidan qo'shilgan barcha turlar ro'yxati": "Список всех туров, добавленных фирмой",
|
||||
"Sotilgan": "Продано",
|
||||
"ta xodim": "сотрудников",
|
||||
"Xodim qo'shish": "Добавить сотрудника",
|
||||
"Operator": "Оператор",
|
||||
"Bugalter": "Бухгалтер",
|
||||
"Manager": "Менеджер",
|
||||
"Xodimni tahrirlash": "Редактировать сотрудника",
|
||||
"Familiyasi": "Фамилия",
|
||||
"Role": "Роль",
|
||||
"Qo'shish": "Добавить",
|
||||
"Role tanlang": "Выберите роль",
|
||||
"Sayohat moliyasi boshqaruv paneli": "Панель управления финансовыми аспектами путешествий",
|
||||
"Bronlar, to'lovlar va agentlik moliyalari boshqaruvi": "Управление бронированиями, платежами и финансами агентств",
|
||||
"Bandlovlar va to‘lovlar": "Бронирования и платежи",
|
||||
"Agentlik hisobotlari": "Отчеты агентств",
|
||||
"Barcha bandlovlar": "Все бронирования",
|
||||
"To'langan": "Оплачено",
|
||||
"Kutilmoqda": "Ожидается",
|
||||
"Bekor qilindi": "Отменено",
|
||||
"Qaytarilgan": "Возвращено",
|
||||
"Jami daromad": "Общий доход",
|
||||
"Yakunlangan bandlovlardan": "От завершенных бронирований",
|
||||
"Kutilayotgan to‘lovlar": "Ожидаемые платежи",
|
||||
"Tasdiqlash kutilmoqda": "Ожидает подтверждения",
|
||||
"Tasdiqlangan bandlovlar": "Подтвержденные бронирования",
|
||||
"Kutilayotgan bandlovlar": "Ожидаемые бронирования",
|
||||
"Oxirgi bandlovlar": "Последние бронирования",
|
||||
"Sayohat sanasi": "Дата путешествия",
|
||||
"Miqdor": "Сумма",
|
||||
"Paid": "Оплачено",
|
||||
"Pending": "В ожидании",
|
||||
"Cancelled": "Отменено",
|
||||
"Foydalanuvchi moliyaviy tafsilotlari": "Финансовые детали пользователя",
|
||||
"uchun batafsil moliyaviy sharh": "для подробного финансового обзора",
|
||||
"Total Spent": "Всего потрачено",
|
||||
"Total Bookings": "Всего бронирований",
|
||||
"All completed bookings": "Все завершенные бронирования",
|
||||
"Pending Payments": "Ожидаемые платежи",
|
||||
"Awaiting confirmation": "Ожидает подтверждения",
|
||||
"All time bookings": "Бронирования за все время",
|
||||
"Member Level": "Уровень участника",
|
||||
"Loyalty status": "Статус лояльности",
|
||||
"Booking History": "История бронирований",
|
||||
"User Details": "Детали пользователя",
|
||||
"Booking Ref": "Номер бронирования",
|
||||
"Destination": "Направление",
|
||||
"Travel Dates": "Даты путешествия",
|
||||
"Travelers": "Путешественники",
|
||||
"Amount": "Сумма",
|
||||
"Booked on": "Забронировано",
|
||||
"Personal Information": "Личная информация",
|
||||
"Full Name": "Полное имя",
|
||||
"Phone Number": "Номер телефона",
|
||||
"Email Address": "Адрес электронной почты",
|
||||
"Member Since": "Участник с",
|
||||
"Travel Statistics": "Статистика путешествий",
|
||||
"Favorite Destination": "Любимое направление",
|
||||
"bookings": "бронирований",
|
||||
"Preferred Agency": "Предпочитаемое агентство",
|
||||
"out of": "из",
|
||||
"Average Booking Value": "Средняя стоимость бронирования",
|
||||
"Turlar ro'yxati": "Список туров",
|
||||
"Yangi tur qo'shish": "Добавить новый тур",
|
||||
"Davomiyligi": "Продолжительность",
|
||||
"Narx Oralig'i": "Ценовой диапазон",
|
||||
"Mehmonxona": "Отель",
|
||||
"Imkoniyatlar": "Удобства",
|
||||
"Amallar": "Операции",
|
||||
"kun": "дней",
|
||||
"yulduzli mehmonxona": "звездочный отель",
|
||||
"Bilet turi": "Тип билета",
|
||||
"Batafsil": "Подробнее",
|
||||
"Turni o'chirishni tasdiqlang": "Подтвердите удаление тура",
|
||||
"Haqiqatan ham bu turni o'chirib tashlamoqchimisiz? Bu amalni ortga qaytarib bo'lmaydi.": "Вы действительно хотите удалить этот тур? Это действие невозможно отменить.",
|
||||
"Tur ma'lumotlari": "Информация о туре",
|
||||
"Sarlavha": "Заголовок",
|
||||
"Narx": "Цена",
|
||||
"Ketish joyi": "Место отправления",
|
||||
"Borish joyi": "Место прибытия",
|
||||
"Manzil nomi": "Название места",
|
||||
"Yo‘lovchilar soni": "Количество пассажиров",
|
||||
"Ketish sanasi": "Дата отправления",
|
||||
"Sana tanlang": "Выберите дату",
|
||||
"Ketish vaqti": "Время отправления",
|
||||
"Qaytish sanasi": "Дата возвращения",
|
||||
"Qaytish vaqti": "Время возвращения",
|
||||
"Tillar": "Языки",
|
||||
"Har bir tilni vergul (,) bilan ajrating": "Разделяйте каждый язык запятой (,)",
|
||||
"Tur davomiyligi": "Продолжительность тура",
|
||||
"Belgilar (Badge)": "Значки (Badge)",
|
||||
"Belgilarni tanlang": "Выберите значки",
|
||||
"Tariflar": "Тарифы",
|
||||
"Tarfilarni tanlang": "Выберите тарифы",
|
||||
"Transportlar": "Транспорт",
|
||||
"Transportlarni tanlang": "Выберите транспорт",
|
||||
"Banner rasmi": "Изображение баннера",
|
||||
"Drag or select files": "Выберите файлы",
|
||||
"Drop files here or click to browse": "Перетащите файлы сюда или нажмите, чтобы просмотреть",
|
||||
"Qo‘shimcha rasmlar": "Дополнительные изображения",
|
||||
"Rasmlarni tanlang": "Выберите изображения",
|
||||
"Bir nechta rasm yuklashingiz mumkin": "Вы можете загрузить несколько изображений",
|
||||
"Qulayliklar": "Удобства",
|
||||
"Ikonka tanlang": "Выберите иконку",
|
||||
"Yuklanmoqda...": "Загрузка...",
|
||||
"Qulaylik nomi (masalan: Wi-Fi)": "Название удобства (например: Wi-Fi)",
|
||||
"Qo‘shish": "Добавить",
|
||||
"Mehmonxona haqida": "О отеле",
|
||||
"Mehmonxona xizmatlari": "Услуги отеля",
|
||||
"Yangi xizmat qo‘shish": "Добавить новую услугу",
|
||||
"Xizmat nomi": "Название услуги",
|
||||
"Xizmat tavsifi": "Описание услуги",
|
||||
"Mehmonxona taomlari haqida": "О еде в отеле",
|
||||
"Mehmonxona taomlari": "Еда в отеле",
|
||||
"Mehmonxona taomlari ro'yxati": "Список еды в отеле",
|
||||
"Mehmonxona nomi": "Название отеля",
|
||||
"Mehmonxona raytingi": "Рейтинг отеля",
|
||||
"Meal Plan": "План питания",
|
||||
"Taom rejasini tanlang": "Выберите план питания",
|
||||
"Mehmonxona turi": "Тип отеля",
|
||||
"Mehmonxona turini tanlang": "Выберите тип отеля",
|
||||
"Sarlavha kamida 2 ta belgidan iborat bo‘lishi kerak": "Заголовок должен содержать не менее 2 символов",
|
||||
"Narx kamida 1000 UZS bo‘lishi kerak.": "Цена должна быть не менее 1000 UZS.",
|
||||
"Kamida 1 yo‘lovchi bo‘lishi kerak.": "Должен быть как минимум 1 пассажир.",
|
||||
"Ketish joyi eng kamida 2 ta belgidan iborat bo‘lishi kerak.": "Место отправления должно содержать не менее 2 символов.",
|
||||
"Borish joyi eng kamida 2 ta belgidan iborat bo‘lishi kerak.": "Место прибытия должно содержать не менее 2 символов.",
|
||||
"Eng kamida 2 ta belgidan iborat bo‘lishi kerak.": "Должно содержать не менее 2 символов.",
|
||||
"Majburiy maydon": "Обязательное поле",
|
||||
"Kamida 1kun bo'lishi kerak": "Должно быть не менее 1 дня",
|
||||
"Kamida bitta belgi tanlang.": "Выберите как минимум один значок.",
|
||||
"Banner rasmi majburiy": "Изображение баннера обязательно.",
|
||||
"Kamida bitta rasm yuklang.": "Загрузите как минимум одно изображение.",
|
||||
"Qulaylik nomi majburiy": "Название удобства обязательно",
|
||||
"Icon nomi majburiy": "Название иконки обязательно",
|
||||
"Kamida bitta qulaylik kiriting.": "Введите как минимум одно удобство.",
|
||||
"Kamida bitta xizmat kiriting.": "Введите как минимум одну услугу.",
|
||||
"Taom rejasi tanlanishi majburiy": "Выбор плана питания обязателен",
|
||||
"Mehmonxona turi tanlanishi majburiy": "Выбор типа отеля обязателен",
|
||||
"Tur Sozlamalari": "Настройки туров",
|
||||
"Ovqatlanish": "Питание",
|
||||
"Otel turlari": "Типы отелей",
|
||||
"Qidirish...": "Поиск...",
|
||||
"Yangi qo'shish": "Добавить новый",
|
||||
"Nomi": "Название",
|
||||
"Rang": "Цвет",
|
||||
"Ma'lumot topilmadi": "Данные не найдены",
|
||||
"Tarif nomi": "Название тарифа",
|
||||
"Transport nomi": "Название транспорта",
|
||||
"Tur nomi": "Название тура",
|
||||
"Narxi": "Цена",
|
||||
"kishi": "человека",
|
||||
"Jo'nash sanasi": "Дата отправления",
|
||||
"Umumiy": "Общий",
|
||||
"Marshshrut": "Маршрут",
|
||||
"Xizmatlar": "Услуги",
|
||||
"Sharhlar": "Отзывы",
|
||||
"Tur haqida ma'lumot": "Информация о виде",
|
||||
"Jo'nash joyi": "Место отправления",
|
||||
"Yo'nalish": "Направление",
|
||||
"Tarif": "Тариф",
|
||||
"Sayohat marshshruti": "Маршрут путешествия",
|
||||
"Narxga kiritilgan xizmatlar": "Услуги включены в цену",
|
||||
"Mehmonxona va ovqatlanish": "Гостиницы и питание",
|
||||
"Ovqatlanish tafsilotlari": "Детали питания",
|
||||
"Mijozlar sharhlari": "Отзывы клиентов",
|
||||
"sharh": "комментарий",
|
||||
"Tur firmasi": "Туристическая фирма",
|
||||
"Firma ID": "Фирменный ID",
|
||||
"Firma sahifasiga o'tish": "Перейти на страницу компании",
|
||||
"Bronlar Paneli": "Панель бронирования",
|
||||
"Foydalanuvchi": "Пользователь",
|
||||
"Tour (Agent)": "Тип (Агент)",
|
||||
"Total / Paid": "Итого / Оплачено",
|
||||
"Details": "Подробности",
|
||||
"ta yangilik mavjud": "есть новость об этом",
|
||||
"Yangilik qo'shish": "Добавить обновление",
|
||||
"Hozircha yangilik yo'q": "Пока нет новостей",
|
||||
"Birinchi yangilikni qo'shishni boshlang": "Начните добавлять первую новость",
|
||||
"Yangilikni o'chirishni tasdiqlang": "Подтвердите удаление новости",
|
||||
"Yangilikni tahrirlash": "Редактировать новости",
|
||||
"Yangi yangilik qo‘shish": "Добавить новость",
|
||||
"Yangilik sarlavhasi": "Заголовок новости",
|
||||
"Yangilik ma'lumotlari": "Новостная информация",
|
||||
"Yangilik nomi": "Название новости",
|
||||
"Yangilik haqida": "О новостях",
|
||||
"Kategoriya tanlang": "Выберите категорию",
|
||||
"Kategoriyalar": "Категории",
|
||||
"Keyingisi": "Следующий",
|
||||
"Yangiliklar ro‘yxati": "Список новостей",
|
||||
"Kamida 2 ta belgidan iborat bo‘lishi kerak.": "Должно содержать не менее 2 символов.",
|
||||
"News Categories": "Категории новостей",
|
||||
"Yangi qo‘shish": "Добавить новое",
|
||||
"Kategoriya nomi": "Название категории",
|
||||
"Yangiliklar soni": "Количество новостей",
|
||||
"Harakatlar": "Действия",
|
||||
"Hech qanday kategoriya topilmadi": "Нет категорий",
|
||||
"Kategoriya tahrirlash": "Редактировать категорию",
|
||||
"Yangi kategoriya qo‘shish": "Добавить новую категорию",
|
||||
"FAQ (Savol va javoblar)": "FAQ (Вопросы и ответы)",
|
||||
"Savol": "Вопрос",
|
||||
"Javob": "Ответ",
|
||||
"Bu bo‘limda savollar yo‘q.": "В этом разделе нет вопросов.",
|
||||
"FAQni tahrirlash": "Редактировать FAQ",
|
||||
"Yangi FAQ qo‘shish": "Добавить новый FAQ",
|
||||
"Haqiqatan ham o‘chirmoqchimisiz?": "Вы уверены, что хотите удалить?",
|
||||
"FAQ Kategoriyalar": "FAQ Категории",
|
||||
"Siz muvaffaqiyatli akkountga kirdingiz": "Вы успешно вошли в аккаунт",
|
||||
"Xatolik yuz berdi": "Произошла ошибка",
|
||||
"Ruxsat darajangiz ushbu amalni bajarishga yetarli emas.": "Уровень вашего доступа недостаточен для выполнения этого действия.",
|
||||
"Kirishga huquq yo‘q!": "Нет права входить!",
|
||||
"Ma'lumotlar yuklanmoqda...": "Загрузка данных...",
|
||||
"Ma'lumotlarni yuklashda xatolik yuz berdi.": "При загрузке данных произошла ошибка.",
|
||||
"Qayta urinish": "Повторить попытку",
|
||||
"Umumiy ma'lumot": "Общие сведения",
|
||||
"Agentlik haqida batafsil ma'lumot": "Подробнее об агентстве",
|
||||
"Ma'lumot yo'q": "Нет данных",
|
||||
"Veb-sayt": "Веб-сайт",
|
||||
"ID raqami": "ID номер",
|
||||
"Nomi (ru)": "Название (ru)",
|
||||
"Otel sharoitlari": "гостиничные условия",
|
||||
"Breakfast Only": "Только завтрак",
|
||||
"Half Board": "Полупансион (завтрак и обед или ужин)",
|
||||
"Full Board": "Полный пансион (завтрак, обед и ужин)",
|
||||
"All Inclusive": "Всё включено (питание, напитки и услуги полностью)"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,332 @@
|
||||
{
|
||||
"welcome": "Uzbek. Bizning saytga xush kelibsiz",
|
||||
"language": "Til",
|
||||
"Admin Panelga Kirish": "Admin Panelga Kirish",
|
||||
"Telefon raqam": "Telefon raqam",
|
||||
"Parol": "Parol",
|
||||
"Parolingizni kiriting": "Parolingizni kiriting",
|
||||
"Kirish": "Kirish",
|
||||
"Admin Panel": "Admin Panel",
|
||||
"Foydalanuvchilar": "Foydalanuvchilar",
|
||||
"Tur firmalar": "Tur firmalar",
|
||||
"Xodimlar": "Xodimlar",
|
||||
"Byudjet": "Byudjet",
|
||||
"Turlar": "Turlar",
|
||||
"Tur sozlamalari": "Tur sozlamalari",
|
||||
"Bronlar": "Bronlar",
|
||||
"Yangiliklar": "Yangiliklar",
|
||||
"Yordam Arizalar": "Yordam Arizalar",
|
||||
"Tur sozlamalari": "Tur sozlamalari"
|
||||
"Kategoriya": "Kategoriya",
|
||||
"FAQ": "FAQ",
|
||||
"Savollar ro‘yxati": "Savollar ro‘yxati",
|
||||
"Savollar kategoriyasi": "Savollar kategoriyasi",
|
||||
"Arizalar": "Arizalar",
|
||||
"Agentlik arizalari": "Agentlik arizalari",
|
||||
"Yordam arizalari": "Yordam arizalari",
|
||||
"Sayt sozlamalari": "Sayt sozlamalari",
|
||||
"Sayt SEOsi": "Sayt SEOsi",
|
||||
"Offerta": "Offerta",
|
||||
"Yordam pagelari": "Yordam pagelari",
|
||||
"Jami": "Jami",
|
||||
"ta foydalanuvchini boshqaring": "ta foydalanuvchini boshqaring",
|
||||
"Foydalanuvchi Qo'shish": "Foydalanuvchi Qo'shish",
|
||||
"Jami foydalanuvchilar": "Jami foydalanuvchilar",
|
||||
"Email bilan ro'yxatlangan": "Email bilan ro'yxatlangan",
|
||||
"Telefon bilan ro'yxatlangan": "Telefon bilan ro'yxatlangan",
|
||||
"Username, email yoki telefon raqami bo'yicha qidirish": "Username, email yoki telefon raqami bo'yicha qidirish...",
|
||||
"Faol": "Faol",
|
||||
"Ko'rish": "Ko'rish",
|
||||
"Tahrirlash": "Tahrirlash",
|
||||
"O'chirish": "O'chirish",
|
||||
"FAQ Kategoriyalar": "FAQ Kategoriyalar",
|
||||
"Foydalanuvchini o'chirish": "Foydalanuvchini o'chirish",
|
||||
"Siz": "Siz",
|
||||
"foydalanuvchini o'chirmoqchimisiz?": "foydalanuvchini o'chirmoqchimisiz?",
|
||||
"Ushbu amalni qaytarib bo'lmaydi": "Ushbu amalni qaytarib bo'lmaydi.",
|
||||
"Bekor qilish": "Bekor qilish",
|
||||
"Orqaga": "Orqaga",
|
||||
"Yangi foydalanuvchi": "Yangi foydalanuvchi",
|
||||
"Ma'lumotlarni to'ldiring va saqlang": "Ma'lumotlarni to'ldiring va saqlang",
|
||||
"Ismi": "Ismi",
|
||||
"Email": "Email",
|
||||
"Telefon raqami": "Telefon raqami",
|
||||
"Parolni tasdiqlang": "Parolni tasdiqlang",
|
||||
"Saqlash": "Saqlash",
|
||||
"Username majburiy": "Username majburiy",
|
||||
"Username kamida 3 ta belgidan iborat bo'lishi kerak": "Username kamida 3 ta belgidan iborat bo'lishi kerak",
|
||||
"Email yoki telefon raqamlardan kamida bittasi kiritilishi kerak": "Email yoki telefon raqamlardan kamida bittasi kiritilishi kerak",
|
||||
"Email formati noto'g'ri": "Email formati noto'g'ri",
|
||||
"Telefon raqami formati: +998901234567": "Telefon raqami formati: +998901234567",
|
||||
"Parol majburiy": "Parol majburiy",
|
||||
"Parol kamida 8 ta belgidan iborat bo'lishi kerak": "Parol kamida 8 ta belgidan iborat bo'lishi kerak",
|
||||
"Parollar mos kelmaydi": "Parollar mos kelmaydi",
|
||||
"Ma'lumotlarni yangilang": "Ma'lumotlarni yangilang",
|
||||
"Yangilash": "Yangilash",
|
||||
"Nofaol": "Nofaol",
|
||||
"Aloqa ma'lumotlari": "Aloqa ma'lumotlari",
|
||||
"Hisob ma'lumotlari": "Hisob ma'lumotlari",
|
||||
"Yaratilgan sana": "Yaratilgan sana",
|
||||
"Sotib olingan chiptalar": "Sotib olingan chiptalar",
|
||||
"Chipta turi": "Chipta turi",
|
||||
"Xizmat": "Xizmat",
|
||||
"Manzil": "Manzil",
|
||||
"Transport": "Transport",
|
||||
"Jo'nash": "Jo'nash",
|
||||
"Yetish": "Yetish",
|
||||
"Yo'lovchilar": "Yo'lovchilar",
|
||||
"Qo'shimcha xizmatlar": "Qo'shimcha xizmatlar",
|
||||
"Pullik xizmatlar": "Pullik xizmatlar",
|
||||
"Jami narx": "Jami narx",
|
||||
"PDF yuklab olish": "PDF yuklab olish",
|
||||
"Hozircha chiptalar mavjud emas": "Hozircha chiptalar mavjud emas",
|
||||
"Hamrohlar": "Hamrohlar",
|
||||
"Erkak": "Erkak",
|
||||
"Ayol": "Ayol",
|
||||
"Tug'ilgan sana": "Tug'ilgan sana",
|
||||
"Passport rasmlari": "Passport rasmlari",
|
||||
"Hozircha hamrohlar qo'shilmagan": "Hozircha hamrohlar qo'shilmagan",
|
||||
"Statistika": "Statistika",
|
||||
"Chiptalar": "Chiptalar",
|
||||
"Status": "Status",
|
||||
"ID": "ID",
|
||||
"Qo'shimcha ma'lumot": "Qo'shimcha ma'lumot",
|
||||
"Bu foydalanuvchi hozirda tizimda faol holatda. Barcha ma'lumotlar to'liq va tasdiqlangan.": "Bu foydalanuvchi hozirda tizimda faol holatda. Barcha ma'lumotlar to'liq va tasdiqlangan.",
|
||||
"Tur firmalari": "Tur firmalari",
|
||||
"Tur firmalaringizni boshqaring va ularning faoliyatini kuzatib boring.": "Tur firmalaringizni boshqaring va ularning faoliyatini kuzatib boring.",
|
||||
"Jami firmalar": "Jami firmalar",
|
||||
"Faol firmalar": "Faol firmalar",
|
||||
"Jami turlar": "Jami turlar",
|
||||
"Umumiy daromad": "Umumiy daromad",
|
||||
"Komissiya": "Komissiya",
|
||||
"Jami tur": "Jami tur",
|
||||
"Sotilgan tur": "Sotilgan tur",
|
||||
"Daromad": "Daromad",
|
||||
"Egasi": "Egasi",
|
||||
"Qo'shilgan turlar soni": "Qo'shilgan turlar soni",
|
||||
"Sotilgan turlar soni": "Sotilgan turlar soni",
|
||||
"Jami sotilgan turlar": "Jami sotilgan turlar",
|
||||
"Ulush foizi": "Ulush foizi",
|
||||
"Har bir sotuvdan": "Har bir sotuvdan",
|
||||
"so'm daromad": "so'm daromad",
|
||||
"Qo'shilgan turlar": "Qo'shilgan turlar",
|
||||
"Firma tomonidan qo'shilgan barcha turlar ro'yxati": "Firma tomonidan qo'shilgan barcha turlar ro'yxati",
|
||||
"Sotilgan": "Sotilgan",
|
||||
"ta xodim": "ta xodim",
|
||||
"Xodim qo'shish": "Xodim qo'shish",
|
||||
"Operator": "Operator",
|
||||
"Bugalter": "Bugalter",
|
||||
"Manager": "Manager",
|
||||
"Xodimni tahrirlash": "Xodimni tahrirlash",
|
||||
"Familiyasi": "Familiyasi",
|
||||
"Role": "Role",
|
||||
"Qo'shish": "Qo'shish",
|
||||
"Role tanlang": "Role tanlang",
|
||||
"Sayohat moliyasi boshqaruv paneli": "Sayohat moliyasi boshqaruv paneli",
|
||||
"Bronlar, to'lovlar va agentlik moliyalari boshqaruvi": "Bronlar, to'lovlar va agentlik moliyalari boshqaruvi",
|
||||
"Bandlovlar va to‘lovlar": "Bandlovlar va to‘lovlar",
|
||||
"Agentlik hisobotlari": "Agentlik hisobotlari",
|
||||
"Barcha bandlovlar": "Barcha bandlovlar",
|
||||
"To'langan": "To'langan",
|
||||
"Bekor qilindi": "Bekor qilindi",
|
||||
"Kutilmoqda": "Kutilmoqda",
|
||||
"Qaytarilgan": "Qaytarilgan",
|
||||
"Jami daromad": "Jami daromad",
|
||||
"Yakunlangan bandlovlardan": "Yakunlangan bandlovlardan",
|
||||
"Kutilayotgan to‘lovlar": "Kutilayotgan to‘lovlar",
|
||||
"Tasdiqlash kutilmoqda": "Tasdiqlash kutilmoqda",
|
||||
"Tasdiqlangan bandlovlar": "Tasdiqlangan bandlovlar",
|
||||
"Kutilayotgan bandlovlar": "Kutilayotgan bandlovlar",
|
||||
"Oxirgi bandlovlar": "Oxirgi bandlovlar",
|
||||
"Sayohat sanasi": "Sayohat sanasi",
|
||||
"Miqdor": "Miqdor",
|
||||
"Paid": "To'langan",
|
||||
"Pending": "Kutilmoqda",
|
||||
"Cancelled": "Bekor qilindi",
|
||||
"Foydalanuvchi moliyaviy tafsilotlari": "Foydalanuvchi moliyaviy tafsilotlari",
|
||||
"uchun batafsil moliyaviy sharh": "uchun batafsil moliyaviy sharh",
|
||||
"Total Spent": "Jami sarflangan summa",
|
||||
"Total Bookings": "Jami bandlovlar",
|
||||
"All completed bookings": "Barcha yakunlangan bandlovlar",
|
||||
"Pending Payments": "Kutilayotgan to'lovlar",
|
||||
"Awaiting confirmation": "Tasdiqlash kutilmoqda",
|
||||
"All time bookings": "Barcha vaqtlar bandlovlari",
|
||||
"Member Level": "A'zo darajasi",
|
||||
"Loyalty status": "Sodiqlik holati",
|
||||
"Booking History": "Bandlovlar tarixi",
|
||||
"User Details": "Foydalanuvchi tafsilotlari",
|
||||
"Booking Ref": "Bandlov",
|
||||
"Destination": "Manzil",
|
||||
"Travel Dates": "Sayohat sanalari",
|
||||
"Travelers": "Sayohatchilar",
|
||||
"Amount": "Miqdor",
|
||||
"Booked on": "Band qilingan sana",
|
||||
"Personal Information": "Shaxsiy ma'lumotlar",
|
||||
"Full Name": "To'liq ism",
|
||||
"Phone Number": "Telefon raqami",
|
||||
"Email Address": "Email manzili",
|
||||
"Member Since": "A'zo bo'lgan sana",
|
||||
"Travel Statistics": "Sayohat statistikasi",
|
||||
"Favorite Destination": "Sevimli manzil",
|
||||
"bookings": "bandlovlar",
|
||||
"Preferred Agency": "Afzal ko'rilgan agentlik",
|
||||
"out of": "ichidan",
|
||||
"Average Booking Value": "O'rtacha bandlov qiymati",
|
||||
"Turlar ro'yxati": "Turlar ro'yxati",
|
||||
"Yangi tur qo'shish": "Yangi tur qo'shish",
|
||||
"Davomiyligi": "Davomiyligi",
|
||||
"Narx Oralig'i": "Narx Oralig'i",
|
||||
"Mehmonxona": "Mehmonxona",
|
||||
"Imkoniyatlar": "Imkoniyatlar",
|
||||
"Amallar": "Amallar",
|
||||
"kun": "kun",
|
||||
"Otel sharoitlari": "Otel sharoitlari",
|
||||
"yulduzli mehmonxona": "yulduzli mehmonxona",
|
||||
"Bilet turi": "Bilet turi",
|
||||
"Batafsil": "Batafsil",
|
||||
"Turni o'chirishni tasdiqlang": "Turni o'chirishni tasdiqlang",
|
||||
"Haqiqatan ham bu turni o'chirib tashlamoqchimisiz? Bu amalni ortga qaytarib bo'lmaydi.": "Haqiqatan ham bu turni o'chirib tashlamoqchimisiz? Bu amalni ortga qaytarib bo'lmaydi.",
|
||||
"Tur ma'lumotlari": "Tur ma'lumotlari",
|
||||
"Sarlavha": "Sarlavha",
|
||||
"Narx": "Narx",
|
||||
"Ketish joyi": "Ketish joyi",
|
||||
"Borish joyi": "Borish joyi",
|
||||
"Manzil nomi": "Manzil nomi",
|
||||
"Yo‘lovchilar soni": "Yo‘lovchilar soni",
|
||||
"Ketish sanasi": "Ketish sanasi",
|
||||
"Sana tanlang": "Sana tanlang",
|
||||
"Ketish vaqti": "Ketish vaqti",
|
||||
"Qaytish sanasi": "Qaytish sanasi",
|
||||
"Qaytish vaqti": "Qaytish vaqti",
|
||||
"Tillar": "Tillar",
|
||||
"Har bir tilni vergul (,) bilan ajrating": "Har bir tilni vergul (,) bilan ajrating",
|
||||
"Tur davomiyligi": "Tur davomiyligi",
|
||||
"Belgilar (Badge)": "Belgilar (Badge)",
|
||||
"Belgilarni tanlang": "Belgilarni tanlang",
|
||||
"Tariflar": "Tariflar",
|
||||
"Tarfilarni tanlang": "Tarfilarni tanlang",
|
||||
"Transportlar": "Transportlar",
|
||||
"Transportlarni tanlang": "Transportlarni tanlang",
|
||||
"Banner rasmi": "Banner rasmi",
|
||||
"Drag or select files": "Faylni tanlang",
|
||||
"Drop files here or click to browse": "Fayllarni shu yerga tashlang yoki ko'rib chiqish uchun bosing",
|
||||
"Qo‘shimcha rasmlar": "Qo‘shimcha rasmlar",
|
||||
"Rasmlarni tanlang": "Rasmlarni tanlang",
|
||||
"Bir nechta rasm yuklashingiz mumkin": "Bir nechta rasm yuklashingiz mumkin",
|
||||
"Qulayliklar": "Qulayliklar",
|
||||
"Ikonka tanlang": "Ikonka tanlang",
|
||||
"Yuklanmoqda...": "Yuklanmoqda...",
|
||||
"Qulaylik nomi (masalan: Wi-Fi)": "Qulaylik nomi (masalan: Wi-Fi)",
|
||||
"Qo‘shish": "Qo‘shish",
|
||||
"Mehmonxona haqida": "Mehmonxona haqida",
|
||||
"Mehmonxona xizmatlari": "Mehmonxona xizmatlari",
|
||||
"Yangi xizmat qo‘shish": "Yangi xizmat qo‘shish",
|
||||
"Xizmat nomi": "Xizmat nomi",
|
||||
"Xizmat tavsifi": "Xizmat tavsifi",
|
||||
"Mehmonxona taomlari haqida": "Mehmonxona taomlari haqida",
|
||||
"Mehmonxona taomlari": "Mehmonxona taomlari",
|
||||
"Mehmonxona taomlari ro'yxati": "Mehmonxona taomlari ro'yxati",
|
||||
"Mehmonxona nomi": "Mehmonxona nomi",
|
||||
"Mehmonxona raytingi": "Mehmonxona raytingi",
|
||||
"Meal Plan": "Taom rejasi",
|
||||
"Taom rejasini tanlang": "Taom rejasini tanlang",
|
||||
"Mehmonxona turi": "Mehmonxona turi",
|
||||
"Mehmonxona turini tanlang": "Mehmonxona turini tanlang",
|
||||
"Narx kamida 1000 UZS bo‘lishi kerak.": "Narx kamida 1000 UZS bo‘lishi kerak.",
|
||||
"Kamida 1 yo‘lovchi bo‘lishi kerak.": "Kamida 1 yo‘lovchi bo‘lishi kerak.",
|
||||
"Sarlavha kamida 2 ta belgidan iborat bo‘lishi kerak": "Sarlavha kamida 2 ta belgidan iborat bo‘lishi kerak",
|
||||
"Ketish joyi eng kamida 2 ta belgidan iborat bo‘lishi kerak.": "Ketish joyi eng kamida 2 ta belgidan iborat bo‘lishi kerak.",
|
||||
"Borish joyi eng kamida 2 ta belgidan iborat bo‘lishi kerak.": "Borish joyi eng kamida 2 ta belgidan iborat bo‘lishi kerak.",
|
||||
"Eng kamida 2 ta belgidan iborat bo‘lishi kerak.": "Eng kamida 2 ta belgidan iborat bo‘lishi kerak.",
|
||||
"Majburiy maydon": "Majburiy maydon",
|
||||
"Kamida 1kun bo'lishi kerak": "Kamida 1kun bo'lishi kerak",
|
||||
"Kamida bitta belgi tanlang.": "Kamida bitta belgi tanlang.",
|
||||
"Banner rasmi majburiy": "Banner rasmi majburiy.",
|
||||
"Kamida bitta rasm yuklang.": "Kamida bitta rasm yuklang.",
|
||||
"Qulaylik nomi majburiy": "Qulaylik nomi majburiy.",
|
||||
"Icon nomi majburiy": "Icon nomi majburiy.",
|
||||
"Kamida bitta qulaylik kiriting.": "Kamida bitta qulaylik kiriting.",
|
||||
"Kamida bitta xizmat kiriting.": "Kamida bitta xizmat kiriting.",
|
||||
"Taom rejasi tanlanishi majburiy": "Taom rejasi tanlanishi majburiy.",
|
||||
"Mehmonxona turi tanlanishi majburiy": "Mehmonxona turi tanlanishi majburiy.",
|
||||
"Tur Sozlamalari": "Tur Sozlamalari",
|
||||
"Ovqatlanish": "Ovqatlanish",
|
||||
"Otel turlari": "Otel turlari",
|
||||
"Qidirish...": "Qidirish...",
|
||||
"Yangi qo'shish": "Yangi qo'shish",
|
||||
"Nomi": "Nomi",
|
||||
"Rang": "Rang",
|
||||
"Ma'lumot topilmadi": "Ma'lumot topilmadi",
|
||||
"Tarif nomi": "Tarif nomi",
|
||||
"Transport nomi": "Transport nomi",
|
||||
"Tur nomi": "Tur nomi",
|
||||
"Narxi": "Narxi",
|
||||
"kishi": "kishi",
|
||||
"Jo'nash sanasi": "Jo'nash sanasi",
|
||||
"Umumiy": "Umumiy",
|
||||
"Marshshrut": "Marshshrut",
|
||||
"Xizmatlar": "Xizmatlar",
|
||||
"Sharhlar": "Sharhlar",
|
||||
"Tur haqida ma'lumot": "Tur haqida ma'lumot",
|
||||
"Jo'nash joyi": "Jo'nash joyi",
|
||||
"Yo'nalish": "Yo'nalish",
|
||||
"Tarif": "Tarif",
|
||||
"Sayohat marshshruti": "Sayohat marshshruti",
|
||||
"Narxga kiritilgan xizmatlar": "Narxga kiritilgan xizmatlar",
|
||||
"Mehmonxona va ovqatlanish": "Mehmonxona va ovqatlanish",
|
||||
"Ovqatlanish tafsilotlari": "Ovqatlanish tafsilotlari",
|
||||
"Mijozlar sharhlari": "Mijozlar sharhlari",
|
||||
"sharh": "sharh",
|
||||
"Tur firmasi": "Tur firmasi",
|
||||
"Firma ID": "Firma ID",
|
||||
"Firma sahifasiga o'tish": "Firma sahifasiga o'tish",
|
||||
"Bronlar Paneli": "Bronlar Paneli",
|
||||
"Foydalanuvchi": "Foydalanuvchi",
|
||||
"Tour (Agent)": "Tur (Agent)",
|
||||
"Total / Paid": "Total / Paid",
|
||||
"Details": "Batafsil",
|
||||
"ta yangilik mavjud": "ta yangilik mavjud",
|
||||
"Yangilik qo'shish": "Yangilik qo'shish",
|
||||
"Hozircha yangilik yo'q": "'Hozircha yangilik yo'q",
|
||||
"Birinchi yangilikni qo'shishni boshlang": "Birinchi yangilikni qo'shishni boshlang",
|
||||
"Yangilikni o'chirishni tasdiqlang": "Yangilikni o'chirishni tasdiqlang",
|
||||
"Yangilikni tahrirlash": "Yangilikni tahrirlash",
|
||||
"Yangi yangilik qo‘shish": "Yangi yangilik qo‘shish",
|
||||
"Yangilik sarlavhasi": "Yangilik sarlavhasi",
|
||||
"Yangilik ma'lumotlari": "Yangilik ma'lumotlari",
|
||||
"Yangilik nomi": "Yangilik nomi",
|
||||
"Yangilik haqida": "Yangilik haqida",
|
||||
"Kategoriya tanlang": "Kategoriya tanlang",
|
||||
"Kategoriyalar": "Kategoriyalar",
|
||||
"Keyingisi": "Keyingisi",
|
||||
"Yangiliklar ro‘yxati": "Yangiliklar ro‘yxati",
|
||||
"Kamida 2 ta belgidan iborat bo‘lishi kerak.": "Kamida 2 ta belgidan iborat bo‘lishi kerak.",
|
||||
"News Categories": "Yangiliklar turkumlari",
|
||||
"Yangi qo‘shish": "Yangi qo‘shish",
|
||||
"Kategoriya nomi": "Kategoriya nomi",
|
||||
"Yangiliklar soni": "Yangiliklar soni",
|
||||
"Harakatlar": "Harakatlar",
|
||||
"Hech qanday kategoriya topilmadi": "Hech qanday kategoriya topilmadi",
|
||||
"Kategoriya tahrirlash": "Kategoriya tahrirlash",
|
||||
"Yangi kategoriya qo‘shish": "Yangi kategoriya qo‘shish",
|
||||
"FAQ (Savol va javoblar)": "FAQ (Savol va javoblar)",
|
||||
"Savol": "Savol",
|
||||
"Javob": "Javob",
|
||||
"Bu bo‘limda savollar yo‘q.": "Bu bo‘limda savollar yo‘q.",
|
||||
"FAQni tahrirlash": "FAQni tahrirlash",
|
||||
"Yangi FAQ qo‘shish": "Yangi FAQ qo‘shish",
|
||||
"Haqiqatan ham o‘chirmoqchimisiz?": "Haqiqatan ham o‘chirmoqchimisiz?",
|
||||
"Siz muvaffaqiyatli akkountga kirdingiz": "Siz muvaffaqiyatli akkountga kirdingiz",
|
||||
"Ma'lumotlar yuklanmoqda...": "Ma'lumotlar yuklanmoqda...",
|
||||
"Xatolik yuz berdi": "Xatolik yuz berdi",
|
||||
"Ruxsat darajangiz ushbu amalni bajarishga yetarli emas.": "Ruxsat darajangiz ushbu amalni bajarishga yetarli emas.",
|
||||
"Kirishga huquq yo‘q!": "Kirishga huquq yo‘q!",
|
||||
"Ma'lumotlarni yuklashda xatolik yuz berdi.": "Ma'lumotlarni yuklashda xatolik yuz berdi.",
|
||||
"Qayta urinish": "Qayta urinish",
|
||||
"Umumiy ma'lumot": "Umumiy ma'lumot",
|
||||
"Agentlik haqida batafsil ma'lumot": "Agentlik haqida batafsil ma'lumot",
|
||||
"Ma'lumot yo'q": "Ma'lumot yo'q",
|
||||
"Veb-sayt": "Veb-sayt",
|
||||
"ID raqami": "ID raqami",
|
||||
"Nomi (ru)": "Nomi (ru)",
|
||||
"Breakfast Only": "Faqat nonushta",
|
||||
"Half Board": "Yarim pansion (nonushta va tushlik yoki kechki ovqat)",
|
||||
"Full Board": "To‘liq pansion (nonushta, tushlik va kechki ovqat)",
|
||||
"All Inclusive": "To‘liq pansion (nonushta, tushlik va kechki ovqat)"
|
||||
}
|
||||
|
||||
42
src/shared/hooks/user.ts
Normal file
42
src/shared/hooks/user.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
type User = {
|
||||
id: number;
|
||||
last_login: string;
|
||||
is_superuser: boolean;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
is_staff: boolean;
|
||||
is_active: boolean;
|
||||
date_joined: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
username: string;
|
||||
avatar: string;
|
||||
validated_at: string;
|
||||
role:
|
||||
| "superuser"
|
||||
| "admin"
|
||||
| "moderator"
|
||||
| "tour_admin"
|
||||
| "buxgalter"
|
||||
| "operator"
|
||||
| "user";
|
||||
travel_agency: number;
|
||||
} | null;
|
||||
|
||||
interface UserStore {
|
||||
user: User;
|
||||
setUser: (user: User) => void;
|
||||
clearUser: () => void;
|
||||
}
|
||||
|
||||
const useUserStore = create<UserStore>((set) => ({
|
||||
user: null,
|
||||
|
||||
setUser: (user) => set({ user }),
|
||||
|
||||
clearUser: () => set({ user: null }),
|
||||
}));
|
||||
|
||||
export default useUserStore;
|
||||
57
src/shared/lib/authCookies.ts
Normal file
57
src/shared/lib/authCookies.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const TOKEN_KEY = "auth_token"; // cookie nomi
|
||||
const REF_TOKEN_KEY = "ref_auth_token"; // cookie nomi
|
||||
const EXPIRE_DAYS = 1; // token necha kun saqlansin
|
||||
const REF_EXPIRE_DAYS = 30; // token necha kun saqlansin
|
||||
|
||||
/**
|
||||
* Tokenni cookie'ga saqlaydi
|
||||
* @param token - foydalanuvchining JWT tokeni
|
||||
*/
|
||||
export const setAuthToken = (token: string) => {
|
||||
Cookies.set(TOKEN_KEY, token, {
|
||||
expires: EXPIRE_DAYS,
|
||||
secure: true, // faqat https da ishlaydi
|
||||
sameSite: "strict", // CSRF xavfsizligi
|
||||
});
|
||||
};
|
||||
|
||||
export const setAuthRefToken = (token: string) => {
|
||||
Cookies.set(REF_TOKEN_KEY, token, {
|
||||
expires: REF_EXPIRE_DAYS,
|
||||
secure: true, // faqat https da ishlaydi
|
||||
sameSite: "strict", // CSRF xavfsizligi
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Cookie'dan tokenni oladi
|
||||
* @returns string | undefined
|
||||
*/
|
||||
export const getAuthToken = (): string | undefined => {
|
||||
return Cookies.get(TOKEN_KEY);
|
||||
};
|
||||
|
||||
export const getRefAuthToken = (): string | undefined => {
|
||||
return Cookies.get(REF_TOKEN_KEY);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tokenni cookie'dan o‘chiradi
|
||||
*/
|
||||
export const removeAuthToken = () => {
|
||||
Cookies.remove(TOKEN_KEY);
|
||||
};
|
||||
|
||||
export const removeRefAuthToken = () => {
|
||||
Cookies.remove(REF_TOKEN_KEY);
|
||||
};
|
||||
|
||||
/**
|
||||
* Foydalanuvchi tizimga kirgan yoki yo‘qligini tekshiradi
|
||||
* @returns boolean
|
||||
*/
|
||||
export const isAuthenticated = (): boolean => {
|
||||
return !!Cookies.get(TOKEN_KEY);
|
||||
};
|
||||
@@ -13,7 +13,7 @@ const formatPrice = (amount: number | string, withLabel = false): string => {
|
||||
? locale === LanguageRoutes.RU
|
||||
? " сум"
|
||||
: locale === LanguageRoutes.UZ
|
||||
? " сўм"
|
||||
? " so‘m"
|
||||
: " so‘m"
|
||||
: "";
|
||||
|
||||
|
||||
6
src/shared/lib/onlyNumber.ts
Normal file
6
src/shared/lib/onlyNumber.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
const onlyNumber = (digits: string | number) => {
|
||||
const phone = digits.toString();
|
||||
return phone.replace(/\D/g, "");
|
||||
};
|
||||
|
||||
export default onlyNumber;
|
||||
51
src/shared/ui/avatar.tsx
Normal file
51
src/shared/ui/avatar.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
|
||||
function Avatar({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
data-slot="avatar"
|
||||
className={cn(
|
||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||
return (
|
||||
<AvatarPrimitive.Image
|
||||
data-slot="avatar-image"
|
||||
className={cn("aspect-square size-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarFallback({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||
return (
|
||||
<AvatarPrimitive.Fallback
|
||||
data-slot="avatar-fallback"
|
||||
className={cn(
|
||||
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { XIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import * as React from "react";
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
import { Label } from "@/shared/ui/label";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
@@ -138,6 +139,7 @@ function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||
|
||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const { t } = useTranslation();
|
||||
const body = error ? String(error?.message ?? "") : props.children;
|
||||
|
||||
if (!body) {
|
||||
@@ -151,18 +153,18 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||
className={cn("text-destructive text-sm", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
{t(error?.message ? error.message : "")}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
useFormField,
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import React, {
|
||||
type ComponentType,
|
||||
type LazyExoticComponent,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// 🔹 Lazy icon faqat tanlangan icon uchun
|
||||
const LazyIcon: React.FC<{ name: string }> = ({ name }) => {
|
||||
@@ -49,6 +50,7 @@ const IconSelect: React.FC<IconSelectProps> = ({
|
||||
setSelectedIcon,
|
||||
}) => {
|
||||
const [icons, setIcons] = useState<string[]>([]);
|
||||
const { t } = useTranslation();
|
||||
const [visibleIcons, setVisibleIcons] = useState<string[]>([]);
|
||||
const [chunkSize] = useState(100);
|
||||
const [index, setIndex] = useState(1);
|
||||
@@ -116,14 +118,14 @@ const IconSelect: React.FC<IconSelectProps> = ({
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
<SelectTrigger className="!h-12 w-[220px] text-md">
|
||||
<SelectValue placeholder="Ikonka tanlang">
|
||||
<SelectValue placeholder={t("Ikonka tanlang")}>
|
||||
{selectedIcon ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<LazyIcon name={selectedIcon} />
|
||||
{selectedIcon}
|
||||
</div>
|
||||
) : (
|
||||
"Ikonka tanlang"
|
||||
t("Ikonka tanlang")
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
@@ -151,7 +153,9 @@ const IconSelect: React.FC<IconSelectProps> = ({
|
||||
{!searchTerm && isOpen && (
|
||||
<div ref={loaderRef} className="h-6 flex justify-center items-center">
|
||||
{visibleIcons.length < icons.length && (
|
||||
<span className="text-xs text-gray-400">Yuklanmoqda...</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{t("Yuklanmoqda...")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
43
src/shared/ui/radio-group.tsx
Normal file
43
src/shared/ui/radio-group.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as React from "react"
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||
import { CircleIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
|
||||
function RadioGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Root
|
||||
data-slot="radio-group"
|
||||
className={cn("grid gap-3", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function RadioGroupItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
data-slot="radio-group-item"
|
||||
className={cn(
|
||||
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator
|
||||
data-slot="radio-group-indicator"
|
||||
className="relative flex items-center justify-center"
|
||||
>
|
||||
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
export { RadioGroup, RadioGroupItem }
|
||||
38
src/shared/ui/sonner.tsx
Normal file
38
src/shared/ui/sonner.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
CircleCheckIcon,
|
||||
InfoIcon,
|
||||
Loader2Icon,
|
||||
OctagonXIcon,
|
||||
TriangleAlertIcon,
|
||||
} from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
icons={{
|
||||
success: <CircleCheckIcon className="size-4" />,
|
||||
info: <InfoIcon className="size-4" />,
|
||||
warning: <TriangleAlertIcon className="size-4" />,
|
||||
error: <OctagonXIcon className="size-4" />,
|
||||
loading: <Loader2Icon className="size-4 animate-spin" />,
|
||||
}}
|
||||
style={
|
||||
{
|
||||
"--normal-bg": "var(--popover)",
|
||||
"--normal-text": "var(--popover-foreground)",
|
||||
"--normal-border": "var(--border)",
|
||||
"--border-radius": "var(--radius)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Toaster }
|
||||
Reference in New Issue
Block a user