new things
This commit is contained in:
@@ -8,6 +8,7 @@ import { usePathname, useParams } from "next/navigation";
|
|||||||
import { logoImg } from "@/assets";
|
import { logoImg } from "@/assets";
|
||||||
import { useSubCategory } from "@/store/subCategory";
|
import { useSubCategory } from "@/store/subCategory";
|
||||||
import { minimumValues } from "@/data/minimimValues";
|
import { minimumValues } from "@/data/minimimValues";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { LoadingSkeleton } from "@/components/loadingProduct";
|
import { LoadingSkeleton } from "@/components/loadingProduct";
|
||||||
import { EmptyState } from "@/components/emptyState";
|
import { EmptyState } from "@/components/emptyState";
|
||||||
import { ErrorState } from "@/components/errorState";
|
import { ErrorState } from "@/components/errorState";
|
||||||
@@ -32,6 +33,7 @@ function checkCategory(categoryName: string | undefined, lang: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CarDetailPage() {
|
export default function CarDetailPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const initialSubCategory = useSubCategory(
|
const initialSubCategory = useSubCategory(
|
||||||
@@ -107,7 +109,8 @@ export default function CarDetailPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstData = cars[0];
|
const firstData = cars[0]?.data[0];
|
||||||
|
console.log("First Data: ", firstData);
|
||||||
|
|
||||||
if (!firstData) {
|
if (!firstData) {
|
||||||
return (
|
return (
|
||||||
@@ -138,7 +141,7 @@ export default function CarDetailPage() {
|
|||||||
alt={firstData?.name ? firstData?.name : "image"}
|
alt={firstData?.name ? firstData?.name : "image"}
|
||||||
width={600}
|
width={600}
|
||||||
height={200}
|
height={200}
|
||||||
className="rounded-lg object-cover border border-gray-200 w-full"
|
className="rounded-lg object-contain border border-gray-200 w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -147,7 +150,9 @@ export default function CarDetailPage() {
|
|||||||
<div className="text-lg font-semibold flex gap-2">
|
<div className="text-lg font-semibold flex gap-2">
|
||||||
{isMinimum ? <p>{text}</p> : <Text txt="hour-price" />} :
|
{isMinimum ? <p>{text}</p> : <Text txt="hour-price" />} :
|
||||||
<span className="font-medium flex gap-2 text-gray-500">
|
<span className="font-medium flex gap-2 text-gray-500">
|
||||||
{firstData.price?.toLocaleString("uz-UZ")}
|
{firstData.price
|
||||||
|
? Math.round(Number(firstData.price)).toLocaleString("ru-RU")
|
||||||
|
: ""}
|
||||||
<Text txt="wallet" />
|
<Text txt="wallet" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,22 +186,24 @@ export default function CarDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Texnik xususiyatlar */}
|
{/* Texnik xususiyatlar */}
|
||||||
<div className="w-full border-t border-gray-300 pt-6">
|
{firstData?.features.length > 0 && (
|
||||||
<h2 className="text-xl font-semibold mb-4 text-secondary">
|
<div className="w-full border-t border-gray-300 pt-6">
|
||||||
Texnik xususiyatlari
|
<h2 className="text-xl font-semibold mb-4 text-secondary">
|
||||||
</h2>
|
{t("tech-features")}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-gray-700">
|
</h2>
|
||||||
{firstData?.features.map((item: any) => (
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-gray-700">
|
||||||
<div
|
{firstData?.features?.map((item: any) => (
|
||||||
key={item.id}
|
<div
|
||||||
className="p-3 rounded-md bg-gray-50 border border-gray-200 hover:bg-gray-100 transition"
|
key={item.id}
|
||||||
>
|
className="p-3 rounded-md bg-gray-50 border border-gray-200 hover:bg-gray-100 transition"
|
||||||
<p className="font-medium">{item?.name}:</p>
|
>
|
||||||
<p className="text-gray-600">{item?.value}</p>
|
<p className="font-medium">{item?.name}:</p>
|
||||||
</div>
|
<p className="text-gray-600">{item?.value}</p>
|
||||||
))}
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* Ijara modal */}
|
{/* Ijara modal */}
|
||||||
<CarRentalModal
|
<CarRentalModal
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { dir } from "i18next";
|
|
||||||
import Header from "@/components/nav_foot/header";
|
import Header from "@/components/nav_foot/header";
|
||||||
import Navbar from "@/components/nav_foot/navbar";
|
import Navbar from "@/components/nav_foot/navbar";
|
||||||
import Footer from "@/components/nav_foot/footer";
|
import Footer from "@/components/nav_foot/footer";
|
||||||
@@ -17,7 +16,7 @@ export default async function LangLayout({
|
|||||||
const { lang } = await params; // ✅ await bilan destructuring
|
const { lang } = await params; // ✅ await bilan destructuring
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={lang} dir="rtl">
|
<html lang={lang} dir="rtl" suppressHydrationWarning>
|
||||||
<body>
|
<body>
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Header />
|
<Header />
|
||||||
@@ -29,7 +28,7 @@ export default async function LangLayout({
|
|||||||
</section>
|
</section>
|
||||||
<Footer />
|
<Footer />
|
||||||
<UpScrollIcon />
|
<UpScrollIcon />
|
||||||
<Time/>
|
<Time />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { innerCardTypes } from "@/types";
|
import { innerCardTypes } from "@/types";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface CarRentalModalProps {
|
interface CarRentalModalProps {
|
||||||
car: innerCardTypes;
|
car: innerCardTypes;
|
||||||
@@ -15,8 +16,10 @@ export default function CarRentalModal({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
}: CarRentalModalProps) {
|
}: CarRentalModalProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [userName, setUserName] = useState("");
|
const [userName, setUserName] = useState("");
|
||||||
const [phone, setPhone] = useState("");
|
const [phone, setPhone] = useState("+998 ");
|
||||||
|
const [phoneError, setPhoneError] = useState("");
|
||||||
const [hours, setHours] = useState<number>(1);
|
const [hours, setHours] = useState<number>(1);
|
||||||
const [total, setTotal] = useState<number | undefined>(car?.price);
|
const [total, setTotal] = useState<number | undefined>(car?.price);
|
||||||
|
|
||||||
@@ -24,12 +27,40 @@ export default function CarRentalModal({
|
|||||||
if (car.price) setTotal(hours * car.price);
|
if (car.price) setTotal(hours * car.price);
|
||||||
}, [hours, car.price]);
|
}, [hours, car.price]);
|
||||||
|
|
||||||
|
const formatPhone = (value: string): string => {
|
||||||
|
let digits = value.replace(/\D/g, "");
|
||||||
|
if (digits.startsWith("998")) digits = digits.slice(3);
|
||||||
|
digits = digits.slice(0, 9);
|
||||||
|
let formatted = "+998";
|
||||||
|
if (digits.length > 0) formatted += " " + digits.slice(0, 2);
|
||||||
|
if (digits.length > 2) formatted += " " + digits.slice(2, 5);
|
||||||
|
if (digits.length > 5) formatted += " " + digits.slice(5, 7);
|
||||||
|
if (digits.length > 7) formatted += " " + digits.slice(7, 9);
|
||||||
|
return formatted;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPhoneValid = (value: string): boolean => {
|
||||||
|
const digits = value.replace(/\D/g, "");
|
||||||
|
return digits.length === 12;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const formatted = formatPhone(e.target.value);
|
||||||
|
setPhone(formatted);
|
||||||
|
if (phoneError) setPhoneError("");
|
||||||
|
};
|
||||||
|
|
||||||
// 🧩 Telegramga yuboruvchi funksiya
|
// 🧩 Telegramga yuboruvchi funksiya
|
||||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!userName || !phone || !hours) {
|
if (!userName || !phone || !hours) {
|
||||||
alert("Iltimos, barcha maydonlarni to‘ldiring!");
|
alert(t("modal-fill-required"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPhoneValid(phone)) {
|
||||||
|
setPhoneError(t("modal-phone-error"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,14 +96,14 @@ export default function CarRentalModal({
|
|||||||
parse_mode: "Markdown",
|
parse_mode: "Markdown",
|
||||||
});
|
});
|
||||||
|
|
||||||
alert("✅ Buyurtmangiz muvaffaqiyatli yuborildi!");
|
alert(t("modal-success"));
|
||||||
onClose();
|
onClose();
|
||||||
setUserName("");
|
setUserName("");
|
||||||
setPhone("");
|
setPhone("+998 ");
|
||||||
setHours(1);
|
setHours(1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Yuborishda xatolik:", error);
|
console.error("Yuborishda xatolik:", error);
|
||||||
alert("❌ Xatolik yuz berdi. Qayta urinib ko‘ring!");
|
alert(t("modal-error"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,9 +126,11 @@ export default function CarRentalModal({
|
|||||||
<h2 className="text-2xl font-bold text-gray-800">{car.name}</h2>
|
<h2 className="text-2xl font-bold text-gray-800">{car.name}</h2>
|
||||||
<p className="text-gray-600 mt-2">{car.path}</p>
|
<p className="text-gray-600 mt-2">{car.path}</p>
|
||||||
<p className="text-gray-700 mt-2 font-semibold">
|
<p className="text-gray-700 mt-2 font-semibold">
|
||||||
Soatlik narx:{" "}
|
{t("modal-hourly-price")}{" "}
|
||||||
<span className="text-red-600">
|
<span className="text-red-600">
|
||||||
{car.price?.toLocaleString()} UZS
|
{car.price
|
||||||
|
? Math.round(Number(car.price)).toLocaleString("ru-RU")
|
||||||
|
: ""} UZS
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,7 +140,7 @@ export default function CarRentalModal({
|
|||||||
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
|
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Ismingiz
|
{t("modal-name-label")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -115,27 +148,30 @@ export default function CarRentalModal({
|
|||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-secondary focus:border-transparent"
|
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-secondary focus:border-transparent"
|
||||||
value={userName}
|
value={userName}
|
||||||
onChange={(e) => setUserName(e.target.value)}
|
onChange={(e) => setUserName(e.target.value)}
|
||||||
placeholder="Ismingizni kiriting"
|
placeholder={t("modal-name-placeholder")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Telefon
|
{t("modal-phone-label")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
name="phone"
|
name="phone"
|
||||||
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-secondary focus:border-transparent"
|
className={`w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-secondary focus:border-transparent ${phoneError ? "border-red-500" : "border-gray-300"}`}
|
||||||
value={phone}
|
value={phone}
|
||||||
onChange={(e) => setPhone(e.target.value)}
|
onChange={handlePhoneChange}
|
||||||
placeholder="+998 90 123 45 67"
|
placeholder={t("modal-phone-placeholder")}
|
||||||
/>
|
/>
|
||||||
|
{phoneError && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">{phoneError}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Qancha vaqt (soat)
|
{t("modal-hours-label")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -148,9 +184,11 @@ export default function CarRentalModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-lg font-semibold text-gray-800 mt-2">
|
<div className="text-lg font-semibold text-gray-800 mt-2">
|
||||||
Jami summa:{" "}
|
{t("modal-total")}{" "}
|
||||||
<span className="text-green-600">
|
<span className="text-green-600">
|
||||||
{total?.toLocaleString()} UZS
|
{total
|
||||||
|
? Math.round(Number(total)).toLocaleString("ru-RU")
|
||||||
|
: ""} UZS
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -158,7 +196,7 @@ export default function CarRentalModal({
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="w-full bg-secondary text-white py-2 rounded-lg hover:bg-primary transition font-medium"
|
className="w-full bg-secondary text-white py-2 rounded-lg hover:bg-primary transition font-medium"
|
||||||
>
|
>
|
||||||
Buyurtma berish
|
{t("book")}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function Contact() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!phone || !isValidPhone(phone)) {
|
if (!phone || !isValidPhone(phone)) {
|
||||||
alert("Iltimos, telefon raqamingizni to'g'ri kiriting!");
|
alert(t("contact-phone-error"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +77,11 @@ export default function Contact() {
|
|||||||
text: message,
|
text: message,
|
||||||
});
|
});
|
||||||
|
|
||||||
alert("✅ Muvaffaqiyatli yuborildi!");
|
alert(t("contact-success"));
|
||||||
setPhone("");
|
setPhone("");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Yuborishda xatolik:", error);
|
console.error("Yuborishda xatolik:", error);
|
||||||
alert("❌ Yuborishda xatolik yuz berdi!");
|
alert(t("contact-error"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -188,5 +188,20 @@
|
|||||||
"retry": "Повторить",
|
"retry": "Повторить",
|
||||||
"downloadError": "Ошибка при загрузке данных",
|
"downloadError": "Ошибка при загрузке данных",
|
||||||
"noData": "Специальная техника не найдена",
|
"noData": "Специальная техника не найдена",
|
||||||
"noDataDesc": "На данный момент товары отсутствуют. Пожалуйста, попробуйте позже."
|
"noDataDesc": "На данный момент товары отсутствуют. Пожалуйста, попробуйте позже.",
|
||||||
|
"tech-features": "Технические характеристики",
|
||||||
|
"modal-fill-required": "Пожалуйста, заполните все поля!",
|
||||||
|
"modal-phone-error": "Введите полный номер телефона: +998 XX XXX XX XX",
|
||||||
|
"modal-success": "Ваш заказ успешно отправлен!",
|
||||||
|
"modal-error": "Произошла ошибка. Попробуйте ещё раз!",
|
||||||
|
"modal-hourly-price": "Почасовая цена:",
|
||||||
|
"modal-name-label": "Ваше имя",
|
||||||
|
"modal-name-placeholder": "Введите ваше имя",
|
||||||
|
"modal-phone-label": "Телефон",
|
||||||
|
"modal-phone-placeholder": "+998 90 123 45 67",
|
||||||
|
"modal-hours-label": "Количество часов",
|
||||||
|
"modal-total": "Итоговая сумма:",
|
||||||
|
"contact-phone-error": "Пожалуйста, введите правильный номер телефона!",
|
||||||
|
"contact-success": "Успешно отправлено!",
|
||||||
|
"contact-error": "Произошла ошибка при отправке!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,5 +188,20 @@
|
|||||||
"retry": "Qayta urinish",
|
"retry": "Qayta urinish",
|
||||||
"downloadError": "Ma'lumotlarni yuklashda xatolik",
|
"downloadError": "Ma'lumotlarni yuklashda xatolik",
|
||||||
"noData": "Mahsus texnikalar topilmadi",
|
"noData": "Mahsus texnikalar topilmadi",
|
||||||
"noDataDesc": "Hozircha mahsulotlar mavjud emas. Iltimos, keyinroq qayta urinib ko'ring."
|
"noDataDesc": "Hozircha mahsulotlar mavjud emas. Iltimos, keyinroq qayta urinib ko'ring.",
|
||||||
|
"tech-features": "Texnik xususiyatlari",
|
||||||
|
"modal-fill-required": "Iltimos, barcha maydonlarni to'ldiring!",
|
||||||
|
"modal-phone-error": "Telefon raqamini to'liq kiriting: +998 XX XXX XX XX",
|
||||||
|
"modal-success": "Buyurtmangiz muvaffaqiyatli yuborildi!",
|
||||||
|
"modal-error": "Xatolik yuz berdi. Qayta urinib ko'ring!",
|
||||||
|
"modal-hourly-price": "Soatlik narx:",
|
||||||
|
"modal-name-label": "Ismingiz",
|
||||||
|
"modal-name-placeholder": "Ismingizni kiriting",
|
||||||
|
"modal-phone-label": "Telefon",
|
||||||
|
"modal-phone-placeholder": "+998 90 123 45 67",
|
||||||
|
"modal-hours-label": "Qancha vaqt (soat)",
|
||||||
|
"modal-total": "Jami summa:",
|
||||||
|
"contact-phone-error": "Iltimos, telefon raqamingizni to'g'ri kiriting!",
|
||||||
|
"contact-success": "Muvaffaqiyatli yuborildi!",
|
||||||
|
"contact-error": "Yuborishda xatolik yuz berdi!"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user