api ulandi

This commit is contained in:
Samandar Turgunboyev
2025-10-25 18:42:01 +05:00
parent 1a08775451
commit 05b752daf2
84 changed files with 11179 additions and 3724 deletions

View File

@@ -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,
};

View 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 };

View 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;
};
}

View File

@@ -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);
},

View File

@@ -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 };

View File

@@ -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 royxati": "Список вопросов",
"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 tolovlar": "Бронирования и платежи",
"Agentlik hisobotlari": "Отчеты агентств",
"Barcha bandlovlar": "Все бронирования",
"To'langan": "Оплачено",
"Kutilmoqda": "Ожидается",
"Bekor qilindi": "Отменено",
"Qaytarilgan": "Возвращено",
"Jami daromad": "Общий доход",
"Yakunlangan bandlovlardan": "От завершенных бронирований",
"Kutilayotgan tolovlar": "Ожидаемые платежи",
"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": "Название места",
"Yolovchilar 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": "Перетащите файлы сюда или нажмите, чтобы просмотреть",
"Qoshimcha rasmlar": "Дополнительные изображения",
"Rasmlarni tanlang": "Выберите изображения",
"Bir nechta rasm yuklashingiz mumkin": "Вы можете загрузить несколько изображений",
"Qulayliklar": "Удобства",
"Ikonka tanlang": "Выберите иконку",
"Yuklanmoqda...": "Загрузка...",
"Qulaylik nomi (masalan: Wi-Fi)": "Название удобства (например: Wi-Fi)",
"Qoshish": "Добавить",
"Mehmonxona haqida": "О отеле",
"Mehmonxona xizmatlari": "Услуги отеля",
"Yangi xizmat qoshish": "Добавить новую услугу",
"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 bolishi kerak": "Заголовок должен содержать не менее 2 символов",
"Narx kamida 1000 UZS bolishi kerak.": "Цена должна быть не менее 1000 UZS.",
"Kamida 1 yolovchi bolishi kerak.": "Должен быть как минимум 1 пассажир.",
"Ketish joyi eng kamida 2 ta belgidan iborat bolishi kerak.": "Место отправления должно содержать не менее 2 символов.",
"Borish joyi eng kamida 2 ta belgidan iborat bolishi kerak.": "Место прибытия должно содержать не менее 2 символов.",
"Eng kamida 2 ta belgidan iborat bolishi 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 qoshish": "Добавить новость",
"Yangilik sarlavhasi": "Заголовок новости",
"Yangilik ma'lumotlari": "Новостная информация",
"Yangilik nomi": "Название новости",
"Yangilik haqida": "О новостях",
"Kategoriya tanlang": "Выберите категорию",
"Kategoriyalar": "Категории",
"Keyingisi": "Следующий",
"Yangiliklar royxati": "Список новостей",
"Kamida 2 ta belgidan iborat bolishi kerak.": "Должно содержать не менее 2 символов.",
"News Categories": "Категории новостей",
"Yangi qoshish": "Добавить новое",
"Kategoriya nomi": "Название категории",
"Yangiliklar soni": "Количество новостей",
"Harakatlar": "Действия",
"Hech qanday kategoriya topilmadi": "Нет категорий",
"Kategoriya tahrirlash": "Редактировать категорию",
"Yangi kategoriya qoshish": "Добавить новую категорию",
"FAQ (Savol va javoblar)": "FAQ (Вопросы и ответы)",
"Savol": "Вопрос",
"Javob": "Ответ",
"Bu bolimda savollar yoq.": "В этом разделе нет вопросов.",
"FAQni tahrirlash": "Редактировать FAQ",
"Yangi FAQ qoshish": "Добавить новый FAQ",
"Haqiqatan ham ochirmoqchimisiz?": "Вы уверены, что хотите удалить?",
"FAQ Kategoriyalar": "FAQ Категории",
"Siz muvaffaqiyatli akkountga kirdingiz": "Вы успешно вошли в аккаунт",
"Xatolik yuz berdi": "Произошла ошибка",
"Ruxsat darajangiz ushbu amalni bajarishga yetarli emas.": "Уровень вашего доступа недостаточен для выполнения этого действия.",
"Kirishga huquq yoq!": "Нет права входить!",
"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": "Всё включено (питание, напитки и услуги полностью)"
}

View File

@@ -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 royxati": "Savollar royxati",
"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 tolovlar": "Bandlovlar va tolovlar",
"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 tolovlar": "Kutilayotgan tolovlar",
"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",
"Yolovchilar soni": "Yolovchilar 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",
"Qoshimcha rasmlar": "Qoshimcha 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)",
"Qoshish": "Qoshish",
"Mehmonxona haqida": "Mehmonxona haqida",
"Mehmonxona xizmatlari": "Mehmonxona xizmatlari",
"Yangi xizmat qoshish": "Yangi xizmat qoshish",
"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 bolishi kerak.": "Narx kamida 1000 UZS bolishi kerak.",
"Kamida 1 yolovchi bolishi kerak.": "Kamida 1 yolovchi bolishi kerak.",
"Sarlavha kamida 2 ta belgidan iborat bolishi kerak": "Sarlavha kamida 2 ta belgidan iborat bolishi kerak",
"Ketish joyi eng kamida 2 ta belgidan iborat bolishi kerak.": "Ketish joyi eng kamida 2 ta belgidan iborat bolishi kerak.",
"Borish joyi eng kamida 2 ta belgidan iborat bolishi kerak.": "Borish joyi eng kamida 2 ta belgidan iborat bolishi kerak.",
"Eng kamida 2 ta belgidan iborat bolishi kerak.": "Eng kamida 2 ta belgidan iborat bolishi 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 qoshish": "Yangi yangilik qoshish",
"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 royxati": "Yangiliklar royxati",
"Kamida 2 ta belgidan iborat bolishi kerak.": "Kamida 2 ta belgidan iborat bolishi kerak.",
"News Categories": "Yangiliklar turkumlari",
"Yangi qoshish": "Yangi qoshish",
"Kategoriya nomi": "Kategoriya nomi",
"Yangiliklar soni": "Yangiliklar soni",
"Harakatlar": "Harakatlar",
"Hech qanday kategoriya topilmadi": "Hech qanday kategoriya topilmadi",
"Kategoriya tahrirlash": "Kategoriya tahrirlash",
"Yangi kategoriya qoshish": "Yangi kategoriya qoshish",
"FAQ (Savol va javoblar)": "FAQ (Savol va javoblar)",
"Savol": "Savol",
"Javob": "Javob",
"Bu bolimda savollar yoq.": "Bu bolimda savollar yoq.",
"FAQni tahrirlash": "FAQni tahrirlash",
"Yangi FAQ qoshish": "Yangi FAQ qoshish",
"Haqiqatan ham ochirmoqchimisiz?": "Haqiqatan ham ochirmoqchimisiz?",
"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 yoq!": "Kirishga huquq yoq!",
"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": "Toliq pansion (nonushta, tushlik va kechki ovqat)",
"All Inclusive": "Toliq pansion (nonushta, tushlik va kechki ovqat)"
}

42
src/shared/hooks/user.ts Normal file
View 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;

View 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 ochiradi
*/
export const removeAuthToken = () => {
Cookies.remove(TOKEN_KEY);
};
export const removeRefAuthToken = () => {
Cookies.remove(REF_TOKEN_KEY);
};
/**
* Foydalanuvchi tizimga kirgan yoki yoqligini tekshiradi
* @returns boolean
*/
export const isAuthenticated = (): boolean => {
return !!Cookies.get(TOKEN_KEY);
};

View File

@@ -13,7 +13,7 @@ const formatPrice = (amount: number | string, withLabel = false): string => {
? locale === LanguageRoutes.RU
? " сум"
: locale === LanguageRoutes.UZ
? " сўм"
? " som"
: " som"
: "";

View 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
View 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 }

View File

@@ -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";

View File

@@ -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,
};

View File

@@ -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>
)}

View 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
View 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 }