Compare commits
6 Commits
b60c0af553
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd0a98ce1a | ||
|
|
73912860ec | ||
|
|
77c6baaa55 | ||
|
|
cfa84d04ab | ||
|
|
5b7dbd5019 | ||
|
|
80c2dd8930 |
@@ -8,10 +8,10 @@ import { usePathname, useParams } from "next/navigation";
|
||||
import { logoImg } from "@/assets";
|
||||
import { useSubCategory } from "@/store/subCategory";
|
||||
import { minimumValues } from "@/data/minimimValues";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { LoadingSkeleton } from "@/components/loadingProduct";
|
||||
import { EmptyState } from "@/components/emptyState";
|
||||
import { ErrorState } from "@/components/errorState";
|
||||
// for gitea
|
||||
|
||||
const baseUrl = "https://api.spes-texnika.uz/api/v1/products/";
|
||||
|
||||
@@ -32,6 +32,7 @@ function checkCategory(categoryName: string | undefined, lang: string) {
|
||||
}
|
||||
|
||||
export default function CarDetailPage() {
|
||||
const t = useTranslations();
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
|
||||
const initialSubCategory = useSubCategory(
|
||||
@@ -107,7 +108,8 @@ export default function CarDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const firstData = cars[0];
|
||||
const firstData = cars[0]?.data[0];
|
||||
console.log("First Data: ", firstData);
|
||||
|
||||
if (!firstData) {
|
||||
return (
|
||||
@@ -124,30 +126,28 @@ export default function CarDetailPage() {
|
||||
dir="ltr"
|
||||
className="my-10 max-w-[1200px] w-full mx-auto space-y-8 flex flex-col items-start justify-center px-2"
|
||||
>
|
||||
{/* Mashina nomi */}
|
||||
<div className="text-2xl font-bold w-full text-center text-secondary mb-10">
|
||||
<Text txt={""} />
|
||||
</div>
|
||||
|
||||
{/* Rasmi + asosiy narx ma'lumotlari */}
|
||||
<div className="flex flex-col md:flex-row md:items-start items-center gap-6 justify-center w-full">
|
||||
{/* Mashina rasmi */}
|
||||
<div className="max-w-[600px] w-full h-auto">
|
||||
<Image
|
||||
src={firstData?.image ? firstData?.image : logoImg}
|
||||
alt={firstData?.name ? firstData?.name : "image"}
|
||||
width={600}
|
||||
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>
|
||||
|
||||
{/* Asosiy ma'lumotlar */}
|
||||
<div className="lg:space-y-6 space-y-3 w-full">
|
||||
<div className="text-lg font-semibold flex gap-2">
|
||||
{isMinimum ? <p>{text}</p> : <Text txt="hour-price" />} :
|
||||
<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" />
|
||||
</span>
|
||||
</div>
|
||||
@@ -161,13 +161,11 @@ export default function CarDetailPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Izoh */}
|
||||
<div className="space-y-2 text-gray-500 text-lg">
|
||||
<Text txt="note1" />
|
||||
<Text txt="note2" />
|
||||
</div>
|
||||
|
||||
{/* Buyurtma tugmasi */}
|
||||
<div className="flex gap-6">
|
||||
<button
|
||||
onClick={() => setModalOpen(true)}
|
||||
@@ -180,13 +178,13 @@ export default function CarDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Texnik xususiyatlar */}
|
||||
{firstData?.features.length > 0 && (
|
||||
<div className="w-full border-t border-gray-300 pt-6">
|
||||
<h2 className="text-xl font-semibold mb-4 text-secondary">
|
||||
Texnik xususiyatlari
|
||||
{t("tech-features")}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-gray-700">
|
||||
{firstData?.features.map((item: any) => (
|
||||
{firstData?.features?.map((item: any) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="p-3 rounded-md bg-gray-50 border border-gray-200 hover:bg-gray-100 transition"
|
||||
@@ -197,8 +195,8 @@ export default function CarDetailPage() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Ijara modal */}
|
||||
<CarRentalModal
|
||||
car={firstData}
|
||||
isOpen={modalOpen}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { dir } from "i18next";
|
||||
import Header from "@/components/nav_foot/header";
|
||||
import Navbar from "@/components/nav_foot/navbar";
|
||||
import Footer from "@/components/nav_foot/footer";
|
||||
@@ -6,19 +5,25 @@ import Contact from "@/components/pageParts/contact";
|
||||
import UpScrollIcon from "@/components/lib_components/upScroll";
|
||||
import { Suspense } from "react";
|
||||
import Time from "@/components/lib_components/time";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages } from "next-intl/server";
|
||||
|
||||
// for gitia
|
||||
|
||||
export default async function LangLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ lang: string }>; // ✅ Promise shaklda
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params; // ✅ await bilan destructuring
|
||||
const { lang } = await params;
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html lang={lang} dir="rtl">
|
||||
<html lang={lang} dir="rtl" suppressHydrationWarning>
|
||||
<body>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<Suspense fallback={null}>
|
||||
<Header />
|
||||
</Suspense>
|
||||
@@ -29,7 +34,8 @@ export default async function LangLayout({
|
||||
</section>
|
||||
<Footer />
|
||||
<UpScrollIcon />
|
||||
<Time/>
|
||||
<Time />
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function InnerProductcard({ data }: { data: any }) {
|
||||
{/* Nomi va tavsif */}
|
||||
<div className="mb-4">
|
||||
<h3
|
||||
className="text-xl font-bold text-[#0c1239] mb-2 line-clamp-2
|
||||
className="text-lg font-bold text-[#0c1239] mb-2 line-clamp-2
|
||||
group-hover:text-[#f2a01c] transition-colors duration-300"
|
||||
>
|
||||
<Text txt={data.name} />
|
||||
|
||||
@@ -6,12 +6,22 @@ import Link from "next/link";
|
||||
import Text from "../lib_components/text";
|
||||
import { motion } from "framer-motion";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useSubCategory } from "@/store/subCategory";
|
||||
|
||||
export default function SliderCard({ data }: { data: ProductTypes }) {
|
||||
const router = useParams();
|
||||
const setInitialSubcategory = useSubCategory(
|
||||
(state) => state.setInitialSubCategory,
|
||||
);
|
||||
return (
|
||||
<Link
|
||||
href={`/${router.lang}/${data.path}`}
|
||||
href={`/${router.lang}/${data.id}`}
|
||||
onClick={() => {
|
||||
setInitialSubcategory({
|
||||
name:data.truck_name,
|
||||
id: data.id,
|
||||
});
|
||||
}}
|
||||
id="news_slider_card"
|
||||
className="group hover:cursor-pointer block"
|
||||
>
|
||||
@@ -22,13 +32,13 @@ export default function SliderCard({ data }: { data: ProductTypes }) {
|
||||
whileTap={{ scale: 0.98 }}
|
||||
className="rounded-xl overflow-hidden shadow-md hover:shadow-lg transition-all"
|
||||
>
|
||||
{/* Rasm qismi */}
|
||||
{/* Rasm qismi for github*/}
|
||||
<Image
|
||||
src={data.image}
|
||||
alt="slider image"
|
||||
width={600}
|
||||
height={600}
|
||||
className="object-cover max-w-[600px] w-full h-[300px]"
|
||||
className="object-contain max-w-[600px] w-full h-[300px]"
|
||||
/>
|
||||
|
||||
{/* Pastki kontent */}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { innerCardTypes } from "@/types";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface CarRentalModalProps {
|
||||
car: innerCardTypes;
|
||||
@@ -15,8 +16,10 @@ export default function CarRentalModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: CarRentalModalProps) {
|
||||
const t = useTranslations();
|
||||
const [userName, setUserName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [phone, setPhone] = useState("+998 ");
|
||||
const [phoneError, setPhoneError] = useState("");
|
||||
const [hours, setHours] = useState<number>(1);
|
||||
const [total, setTotal] = useState<number | undefined>(car?.price);
|
||||
|
||||
@@ -24,23 +27,48 @@ export default function CarRentalModal({
|
||||
if (car.price) setTotal(hours * car.price);
|
||||
}, [hours, car.price]);
|
||||
|
||||
// 🧩 Telegramga yuboruvchi funksiya
|
||||
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("");
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
// ⚙️ Telegram bot ma'lumotlari
|
||||
const token =
|
||||
process.env.NEXT_PUBLIC_TELEGRAM_TOKEN ||
|
||||
"7940057045:AAHRFPvgUCo_7pqpXD6uq4li7-_DYx2J96g";
|
||||
const chatId = process.env.NEXT_PUBLIC_TELEGRAM_CHAT_ID || "6134458285";
|
||||
const token = "8277200651:AAHYi2iE7bW0PEfEahTFyqFQhUcR9bGZnnY";
|
||||
// "7940057045:AAHRFPvgUCo_7pqpXD6uq4li7-_DYx2J96g"
|
||||
const chatId = "6735606461"
|
||||
// "6134458285";
|
||||
|
||||
// 🧾 Yuboriladigan xabar
|
||||
const message = `
|
||||
🚗 *Yangi buyurtma!*
|
||||
|
||||
@@ -51,28 +79,27 @@ export default function CarRentalModal({
|
||||
💰 Umumiy summa: ${total?.toLocaleString("uz-UZ")} UZS
|
||||
|
||||
📦 Mashina: ${car.name}
|
||||
⛽️ Yoqilg‘i turi: ${car.fuelType || "Noma’lum"}
|
||||
⛽️ Yoqilg'i turi: ${car.fuelType || "Noma'lum"}
|
||||
⚙️ Dvigatel: ${car.enginePower_hp || "-"}
|
||||
🚀 Maks tezlik: ${car.maxSpeed_kmh ? car.maxSpeed_kmh + " km/soat" : "-"}
|
||||
|
||||
📝 Qo‘shimcha ma’lumot: ${car.path || "-"}
|
||||
📝 Qo'shimcha ma'lumot: ${car.path || "-"}
|
||||
`;
|
||||
|
||||
// 📤 Telegram API orqali yuborish
|
||||
await axios.post(`https://api.telegram.org/bot${token}/sendMessage`, {
|
||||
chat_id: chatId,
|
||||
text: message,
|
||||
parse_mode: "Markdown",
|
||||
});
|
||||
|
||||
alert("✅ Buyurtmangiz muvaffaqiyatli yuborildi!");
|
||||
alert(t("modal-success"));
|
||||
onClose();
|
||||
setUserName("");
|
||||
setPhone("");
|
||||
setPhone("+998 ");
|
||||
setHours(1);
|
||||
} catch (error) {
|
||||
console.error("Yuborishda xatolik:", error);
|
||||
alert("❌ Xatolik yuz berdi. Qayta urinib ko‘ring!");
|
||||
alert(t("modal-error"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,7 +108,6 @@ export default function CarRentalModal({
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl max-w-lg w-full p-6 relative animate-fade-in">
|
||||
{/* Close button */}
|
||||
<button
|
||||
className="absolute top-4 right-4 text-gray-500 hover:text-gray-900 text-2xl font-bold transition"
|
||||
onClick={onClose}
|
||||
@@ -89,25 +115,26 @@ export default function CarRentalModal({
|
||||
×
|
||||
</button>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<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-700 mt-2 font-semibold">
|
||||
Soatlik narx:{" "}
|
||||
{t("modal-hourly-price")}{" "}
|
||||
<span className="text-red-600">
|
||||
{car.price?.toLocaleString()} UZS
|
||||
{car.price
|
||||
? Math.round(Number(car.price)).toLocaleString("ru-RU")
|
||||
: ""}{" "}
|
||||
UZS
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Ismingiz
|
||||
{t("modal-name-label")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -115,27 +142,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"
|
||||
value={userName}
|
||||
onChange={(e) => setUserName(e.target.value)}
|
||||
placeholder="Ismingizni kiriting"
|
||||
placeholder={t("modal-name-placeholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Telefon
|
||||
{t("modal-phone-label")}
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
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}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="+998 90 123 45 67"
|
||||
onChange={handlePhoneChange}
|
||||
placeholder={t("modal-phone-placeholder")}
|
||||
/>
|
||||
{phoneError && (
|
||||
<p className="text-red-500 text-xs mt-1">{phoneError}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Qancha vaqt (soat)
|
||||
{t("modal-hours-label")}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
@@ -148,9 +178,10 @@ export default function CarRentalModal({
|
||||
</div>
|
||||
|
||||
<div className="text-lg font-semibold text-gray-800 mt-2">
|
||||
Jami summa:{" "}
|
||||
{t("modal-total")}{" "}
|
||||
<span className="text-green-600">
|
||||
{total?.toLocaleString()} UZS
|
||||
{total ? Math.round(Number(total)).toLocaleString("ru-RU") : ""}{" "}
|
||||
UZS
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +189,7 @@ export default function CarRentalModal({
|
||||
type="submit"
|
||||
className="w-full bg-secondary text-white py-2 rounded-lg hover:bg-primary transition font-medium"
|
||||
>
|
||||
Buyurtma berish
|
||||
{t("book")}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function Text({txt}:{txt:string}) {
|
||||
const { t } = useTranslation();
|
||||
export default function Text({ txt }: { txt: string }) {
|
||||
const t = useTranslations();
|
||||
return <div>{t(txt)}</div>;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
export default function Time() {
|
||||
const {t} = useTranslation();
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<div dir='ltr' className='icon_animation2 fixed bottom-10 right-20 z-40 rounded-[50%] bg-primary p-5 text-lg text-secondary flex flex-col items-center justify-center'>
|
||||
<p>{t('work_day_title')}</p>
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { FaLocationDot } from "react-icons/fa6";
|
||||
import Text from "../lib_components/text";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Header() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
dir="ltr"
|
||||
|
||||
@@ -2,26 +2,25 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import i18n from "@/i18n";
|
||||
import { animateScroll as scroll } from "react-scroll";
|
||||
import Text from "../lib_components/text";
|
||||
import "./navbar.css";
|
||||
import { logoImg } from "@/assets";
|
||||
import Image from "next/image";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { X } from "lucide-react"; // ❗ exit icon
|
||||
import { X } from "lucide-react";
|
||||
import { useLocale } from "next-intl";
|
||||
import { useRouter, usePathname } from "@/i18n/navigation";
|
||||
|
||||
export default function Navbar() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const locale = useLocale();
|
||||
|
||||
const [toggle, setToggle] = useState(false);
|
||||
const [togglerIcon, setTogglerIcon] = useState("toggler");
|
||||
const [lang, setLang] = useState<"uz" | "ru">("uz");
|
||||
|
||||
const handleChangeLang = (lng: "uz" | "ru") => {
|
||||
setLang(lng);
|
||||
i18n.changeLanguage(lng);
|
||||
router.replace(pathname, { locale: lng });
|
||||
};
|
||||
|
||||
const changeToggler = () => {
|
||||
@@ -32,26 +31,23 @@ export default function Navbar() {
|
||||
};
|
||||
|
||||
const scrollOrRoute = (id: string) => {
|
||||
if (pathname !== `/${lang}`) {
|
||||
// Agar user boshqa sahifada bo'lsa asosiy sahifaga yo'naltirish
|
||||
router.push(`/${lang}#${id}`);
|
||||
if (pathname !== "/") {
|
||||
router.push(`/#${id}`);
|
||||
} else {
|
||||
// Agar main page-da bo'lsa scroll qilish
|
||||
scroll.scrollTo(document.getElementById(id)!.offsetTop - 100);
|
||||
}
|
||||
changeToggler();
|
||||
};
|
||||
|
||||
const goHome = () => {
|
||||
router.push(`/${lang}`);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
// ❗ Scrollni bloklash uchun effect
|
||||
useEffect(() => {
|
||||
if (toggle) {
|
||||
document.body.style.overflow = "hidden"; // orqa scrollni o‘chir
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "auto"; // qayta yoq
|
||||
document.body.style.overflow = "auto";
|
||||
}
|
||||
}, [toggle]);
|
||||
|
||||
@@ -69,9 +65,9 @@ export default function Navbar() {
|
||||
<button
|
||||
onClick={() => handleChangeLang("uz")}
|
||||
className={`hover:cursor-pointer ${
|
||||
lang === "uz" && "bg-secondary text-primary rounded-[8px]"
|
||||
locale === "uz" && "bg-secondary text-primary rounded-[8px]"
|
||||
} px-2 py-1 text-[20px] ${
|
||||
lang !== "uz" && "border-l-2 border-b-2 border-primary"
|
||||
locale !== "uz" && "border-l-2 border-b-2 border-primary"
|
||||
} `}
|
||||
>
|
||||
UZ
|
||||
@@ -79,9 +75,9 @@ export default function Navbar() {
|
||||
<button
|
||||
onClick={() => handleChangeLang("ru")}
|
||||
className={`hover:cursor-pointer ${
|
||||
lang === "ru" && "bg-secondary text-primary rounded-[8px]"
|
||||
locale === "ru" && "bg-secondary text-primary rounded-[8px]"
|
||||
} px-2 py-1 text-[20px] ${
|
||||
lang !== "ru" && "border-r-2 border-b-2 border-primary"
|
||||
locale !== "ru" && "border-r-2 border-b-2 border-primary"
|
||||
}`}
|
||||
>
|
||||
RU
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Text from "../lib_components/text";
|
||||
import axios from "axios";
|
||||
import { FormEvent } from "react";
|
||||
@@ -42,7 +42,7 @@ const isValidPhone = (value: string) => {
|
||||
};
|
||||
|
||||
export default function Contact() {
|
||||
const { t } = useTranslation();
|
||||
const t = useTranslations();
|
||||
const [phone, setPhone] = useState("");
|
||||
|
||||
const handlePhoneChange = (value: string) => {
|
||||
@@ -53,7 +53,7 @@ export default function Contact() {
|
||||
event.preventDefault();
|
||||
|
||||
if (!phone || !isValidPhone(phone)) {
|
||||
alert("Iltimos, telefon raqamingizni to'g'ri kiriting!");
|
||||
alert(t("contact-phone-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function Contact() {
|
||||
: `+998${normalized}`;
|
||||
|
||||
try {
|
||||
const token = "7940057045:AAHRFPvgUCo_7pqpXD6uq4li7-_DYx2J96g"; // Use environment variable
|
||||
const token = "7940057045:AAHRFPvgUCo_7pqpXD6uq4li7-_DYx2J96g";
|
||||
const chatId = 6134458285;
|
||||
|
||||
if (!token || !chatId) {
|
||||
@@ -77,11 +77,11 @@ export default function Contact() {
|
||||
text: message,
|
||||
});
|
||||
|
||||
alert("✅ Muvaffaqiyatli yuborildi!");
|
||||
alert(t("contact-success"));
|
||||
setPhone("");
|
||||
} catch (error) {
|
||||
console.error("Yuborishda xatolik:", error);
|
||||
alert("❌ Yuborishda xatolik yuz berdi!");
|
||||
alert(t("contact-error"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,7 +99,6 @@ export default function Contact() {
|
||||
<Text txt="contact-h2" />
|
||||
</h2>
|
||||
|
||||
{/* Form */}
|
||||
<form
|
||||
className="flex max-sm:flex-col gap-5 w-full max-w-2xl px-4"
|
||||
onSubmit={sendMessage}
|
||||
|
||||
@@ -6,13 +6,89 @@ import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import Title from "../lib_components/title";
|
||||
import SliderCard from "../cards/sliderCard";
|
||||
import { sliderData } from "@/data";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ProductTypes } from "@/types";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Text from "../lib_components/text";
|
||||
import { LoadingSkeleton } from "../loadingProduct";
|
||||
import { baseUrl } from "@/data/url";
|
||||
|
||||
// The custom CSS selectors for navigation
|
||||
const navigationPrevEl = ".custom-swiper-prev";
|
||||
const navigationNextEl = ".custom-swiper-next";
|
||||
|
||||
function getRandomItems(arr: any[], count: number) {
|
||||
const shuffled = [...arr];
|
||||
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
|
||||
return shuffled.slice(0, count);
|
||||
}
|
||||
|
||||
export default function CustomSlider() {
|
||||
const [cars, setCars] = useState<ProductTypes[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const pathname = usePathname();
|
||||
const lang = pathname.split("/")[1];
|
||||
useEffect(() => {
|
||||
const fetchProducts = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const response = await fetch(baseUrl, {
|
||||
headers: {
|
||||
"Accept-Language": lang,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Server xatosi");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("backend Data slider: ", data?.data);
|
||||
|
||||
if (data?.data && data.data.length > 0) {
|
||||
const dataSlider = getRandomItems(data.data, 6);
|
||||
const formattedData = dataSlider.map((item) => ({
|
||||
id: item.id,
|
||||
truck_name: item.name,
|
||||
desc: "news-title1",
|
||||
image: item.image || "",
|
||||
}));
|
||||
setCars(formattedData);
|
||||
} else {
|
||||
setCars([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Xatolik: ", error);
|
||||
setError(error instanceof Error ? error.message : "Noma'lum xatolik");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProducts();
|
||||
}, [lang]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-600 text-center">
|
||||
<Text txt="downloadError" /> : {error}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <LoadingSkeleton />;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
dir="ltr"
|
||||
@@ -29,7 +105,7 @@ export default function CustomSlider() {
|
||||
<button
|
||||
className={`${navigationPrevEl.replace(
|
||||
".",
|
||||
""
|
||||
"",
|
||||
)} w-10 h-10 p-0 bg-primary text-[30px] text-center text-white flex items-center justify-center hover:bg-secondary hover:cursor-pointer transition`}
|
||||
>
|
||||
‹
|
||||
@@ -37,7 +113,7 @@ export default function CustomSlider() {
|
||||
<button
|
||||
className={`${navigationNextEl.replace(
|
||||
".",
|
||||
""
|
||||
"",
|
||||
)} w-10 h-10 bg-primary text-[30px] text-center text-white flex items-center justify-center hover:bg-secondary hover:cursor-pointer transition `}
|
||||
>
|
||||
›
|
||||
@@ -62,7 +138,7 @@ export default function CustomSlider() {
|
||||
1024: { slidesPerView: 3 },
|
||||
}}
|
||||
>
|
||||
{sliderData.map((item, index) => (
|
||||
{cars.map((item, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<SliderCard data={item} />
|
||||
</SwiperSlide>
|
||||
|
||||
@@ -4,13 +4,28 @@ import React, { useEffect, useState } from "react";
|
||||
import Title from "../../lib_components/title";
|
||||
import Text from "../../lib_components/text";
|
||||
import type { ProductTypes } from "@/types";
|
||||
import { allProducts } from "@/data";
|
||||
import ProductCard from "../../cards/productCard";
|
||||
import { LoadingSkeleton } from "@/components/loadingProduct";
|
||||
import { EmptyState } from "@/components/emptyState";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { baseUrl } from "@/data/url";
|
||||
|
||||
// Kerakli tartib (id lar bo'yicha)
|
||||
const CATEGORY_ORDER: number[] = [1, 2, 3, 4, 6, 5, 8, 9, 23, 11, 12, 10, 14, 26, 17, 13, 20, 19, 21, 28];
|
||||
|
||||
function sortByCustomOrder(data: ProductTypes[]): ProductTypes[] {
|
||||
return [...data].sort((a, b) => {
|
||||
const indexA = CATEGORY_ORDER.indexOf(a.id);
|
||||
const indexB = CATEGORY_ORDER.indexOf(b.id);
|
||||
|
||||
// Agar id topilmasa - oxiriga surish
|
||||
if (indexA === -1) return 1;
|
||||
if (indexB === -1) return -1;
|
||||
|
||||
return indexA - indexB;
|
||||
});
|
||||
}
|
||||
|
||||
export default function Products() {
|
||||
const [cars, setCars] = useState<ProductTypes[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -35,29 +50,26 @@ export default function Products() {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("backend Data: ", data?.data);
|
||||
|
||||
if (data?.data && data.data.length > 0) {
|
||||
setCars(data.data);
|
||||
// ✅ Qayta tartiblash
|
||||
const sorted = sortByCustomOrder(data.data);
|
||||
setCars(sorted);
|
||||
} else {
|
||||
setCars([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Xatolik: ", error);
|
||||
setError(error instanceof Error ? error.message : "Noma'lum xatolik");
|
||||
// Xatolik bo'lsa ham local data'ni ko'rsatish
|
||||
setCars(allProducts);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProducts();
|
||||
}, [lang]); // Bo'sh array - faqat bitta marta ishlaydi
|
||||
}, [lang]);
|
||||
|
||||
return (
|
||||
<div dir="ltr" className="max-w-[1200px] w-full mx-auto">
|
||||
{/* title part */}
|
||||
<div className="flex flex-col mb-10">
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<div className="text-[#f2a01c] px-2 text-[18px] font-semibold">
|
||||
@@ -67,7 +79,6 @@ export default function Products() {
|
||||
<Title text="pricing-h2" />
|
||||
</div>
|
||||
|
||||
{/* Error message */}
|
||||
{error && (
|
||||
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-600 text-center">
|
||||
@@ -76,7 +87,6 @@ export default function Products() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* products */}
|
||||
<div className="px-4 grid gap-5 grid-cols-1 place-content-center min-[500px]:grid-cols-2 min-lg:grid-cols-4 min-[1210px]:grid-cols-4">
|
||||
{loading ? (
|
||||
<LoadingSkeleton />
|
||||
|
||||
1434
data/index.ts
1434
data/index.ts
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import Backend from "i18next-http-backend";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
// import lanaguage files
|
||||
import uzWords from "../public/locales/uz/common.json";
|
||||
import ruWords from "../public/locales/ru/common.json";
|
||||
|
||||
i18n
|
||||
.use(Backend)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: {
|
||||
uz: { translation: uzWords },
|
||||
ru: { translation: ruWords },
|
||||
},
|
||||
lng: "uz",
|
||||
fallbackLng: "uz",
|
||||
debug: true,
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
5
i18n/navigation.ts
Normal file
5
i18n/navigation.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createNavigation } from "next-intl/navigation";
|
||||
import { routing } from "./routing";
|
||||
|
||||
export const { Link, redirect, usePathname, useRouter, getPathname } =
|
||||
createNavigation(routing);
|
||||
15
i18n/request.ts
Normal file
15
i18n/request.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { routing } from "./routing";
|
||||
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
let locale = await requestLocale;
|
||||
|
||||
if (!locale || !routing.locales.includes(locale as "uz" | "ru")) {
|
||||
locale = routing.defaultLocale;
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`../messages/${locale}.json`)).default,
|
||||
};
|
||||
});
|
||||
6
i18n/routing.ts
Normal file
6
i18n/routing.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { defineRouting } from "next-intl/routing";
|
||||
|
||||
export const routing = defineRouting({
|
||||
locales: ["uz", "ru"],
|
||||
defaultLocale: "uz",
|
||||
});
|
||||
207
messages/ru.json
Normal file
207
messages/ru.json
Normal file
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"location": "Узбекистан, Ташкент",
|
||||
"work_day_title": "Рабочее время",
|
||||
"all": "Все машины",
|
||||
"home": "Главное",
|
||||
"products": "Продукты",
|
||||
"news": "Новости",
|
||||
"contact": "Контакты",
|
||||
"pricing-h2": "Прокат спецтехники в Ташкенте",
|
||||
"amazing": "Потрясающее",
|
||||
"equipment": "Оборудование",
|
||||
"trucks": "Грузовые автомобили",
|
||||
"cranes": "Краны",
|
||||
"forklift-trucks": "Вилочные погрузчики",
|
||||
"forklift-truck": "Вилочный погрузчик",
|
||||
"excavators": "Экскаваторы",
|
||||
"excavator": "Экскаватор",
|
||||
"road-repairs": "Дорожные ремонтные",
|
||||
"agreement": "По договоренности",
|
||||
"cement-trucks": "Автоцементовозы",
|
||||
"dumb-trucks": "Самосвалы",
|
||||
"dumb-truck": "Самосвал",
|
||||
"trailers": "Тягачи",
|
||||
"trailer": "Тягач",
|
||||
"gazels": "Газели",
|
||||
"avtotowers": "Автовышки",
|
||||
"avtocranes": "Автокраны",
|
||||
"crawler-cranes": "Гусеничные краны",
|
||||
"crawler-crane": "Гусеничный кран",
|
||||
"tower-cranes": "Башенные краны",
|
||||
"tower-crane": "Башенный кран",
|
||||
"lifters": "Дизельные подъемники",
|
||||
"lifter": "Дизельный подъемник",
|
||||
"front-loaders": "Фронталный погрузчик",
|
||||
"front-loader": "Фронтальный погрузчик",
|
||||
"tele-loaders": "Телескопические погрузчики",
|
||||
"tele-loader": "Телескопические погрузчики",
|
||||
"crawler-excavators": "Гусеничные экскаваторы",
|
||||
"crawler-excavator": "Гусеничный экскаватор",
|
||||
"wheel-excavators": "Eкскаватор",
|
||||
"wheel-excavator": "Колесный экскаватор",
|
||||
"mini-excavators": "Мини-экскаваторы",
|
||||
"excavator-loaders": "Экскаватор-погрузчики",
|
||||
"excavator-loader": "Экскаватор-погрузчик",
|
||||
"avtograders": "Автогрейдеры",
|
||||
"avtograder": "Автогрейдер",
|
||||
"buldozers": "Бульдозеры",
|
||||
"buldozer": "Бульдозер",
|
||||
"katkas": "Bыброкаток",
|
||||
"mini-loaders": "Мини погрузчик",
|
||||
"katka": "Катка",
|
||||
"compressors": "Компрессоры",
|
||||
"testimonials": "Отзывы",
|
||||
"clients'": "Клиентов",
|
||||
"brand-h2": "Транспортный парк состоит из спецтехники мировых брендов",
|
||||
"about-h2": "Мы предлагаем вам лучшее",
|
||||
"about-p": "Мы предлагаем в аренду только надежное и современное оборудование от ведущих мировых производителей. Наша техника регулярно проходит техническое обслуживание, что гарантирует ее бесперебойную работу на протяжении всего срока аренды. Кроме того, наши специалисты всегда готовы оказать вам квалифицированную помощь и консультацию по всем вопросам, связанным с эксплуатацией арендованного оборудования.",
|
||||
"about-block-quote": "Пожалуйста, обратите внимание на наши специальные предложения и скидки! Свяжитесь с нами сегодня и получите консультацию от наших экспертов",
|
||||
"contact-us": "Свяжитесь с нами",
|
||||
"news-h2": "Последние новости и обновления",
|
||||
"Excavator": "Экскаватор",
|
||||
"faq-h2": "Часто задаваемые вопросы",
|
||||
"faq1": "Можно ли заказать спецтехнику на выходных ?",
|
||||
"faq-answer-1": "Да, можно. Мы принимаем и выполняем заказы 24/7.",
|
||||
"faq2": "Какой минимальный срок оказания услуг по спецтехнике ?",
|
||||
"faq-answer-2": "Минимальный срок аренды составляет от 30 часов.",
|
||||
"faq3": "Какие виды спецтехники есть у вас ?",
|
||||
"faq-answer-3": "В нашем автопарке имеется:",
|
||||
"faq-answer-3-1": "Манипуляторы грузоподъемностью до 7 тонн",
|
||||
"faq-answer-3-2": "Колесные и гусеничные экскаваторы",
|
||||
"faq-answer-3-3": "Фронтальные и вилочные(телескопические) погрузчики и другие",
|
||||
"faq4": " Форма оплата?",
|
||||
"faq-answer-4": "Форма оплата любая, в том числе через мобильное приложения.",
|
||||
"partner-h2": "Наши партнёры",
|
||||
"contact-h2": "Заказать звонок",
|
||||
"footer-p": "Надежность, производительность и долговечность для успешного выполнения любых строительных проектов.",
|
||||
"call": "Перезвони мне",
|
||||
"subscribe": "Подписывайтесь на нас",
|
||||
"links": "Полезная ссылка",
|
||||
"contact-info": "Контактная информация",
|
||||
"length": "Длина",
|
||||
"width": "Ширина",
|
||||
"height": "Высота",
|
||||
"load": "Грузоподъемность",
|
||||
"compressor": "Компрессор",
|
||||
"conditioning": "Кондиционер",
|
||||
"rent-tower-crane": "Аренда башенные краны",
|
||||
"more": "Узнать больше",
|
||||
"presssing-width": "Ширина уплотнения",
|
||||
"max-height": "Максимальная высота",
|
||||
"max-length": "Максимальная длина",
|
||||
"gasoline": "Тип топлива",
|
||||
"gasoline-volume": "Емкость топлива",
|
||||
"air-volume": "Емкость воздуха",
|
||||
"compressor-volume": "Емкость компрессора",
|
||||
"compressing-volume": "Давление сжатия",
|
||||
"power": "Мощность двигателя",
|
||||
"transmission": "Коробка передач",
|
||||
"max-speed": "Максимальная скорость",
|
||||
"book": "Заказать",
|
||||
"ask": "Спросить в Telegram",
|
||||
"description": "Описание",
|
||||
"avtotower": "Автовышка",
|
||||
"avtocrane": "Автокран",
|
||||
"samosval": "Самосвалы",
|
||||
"details": "Детали",
|
||||
"feature1": "Лучшая строительная техника",
|
||||
"feature2": "По доступной цене",
|
||||
"feature3": "Вопреки общему мнению",
|
||||
"feature4": "Проверка качества",
|
||||
"feature5": "Техническая поддержка",
|
||||
"feature6": "Сервис в нужное время",
|
||||
"weight": "Вес",
|
||||
"contacts": "Контакты",
|
||||
"rent-samosval": "Аренда Самосвалов",
|
||||
"rent-cement-trucks": "Аренда Автоцементовозов",
|
||||
"rent-avtocranes": "Аренда Автокранов",
|
||||
"rent-trailers": "Аренда Тягачов",
|
||||
"rent-gazels": "Аренда Газелов",
|
||||
"rent-avtotower": "Аренда Автовышков",
|
||||
"rent-crawler-cranes": "Аренда Гусеничной кранов",
|
||||
"rent-lifters": "Аренда Дизельное подъемников",
|
||||
"rent-excavator-loader": "Аренда Экскаватор-погрузчиков",
|
||||
"rent-crawler-excavators": "Аренда Гусеничное экскаваторов",
|
||||
"rent-mini-excavators": "Аренда Мини-экскаваторов",
|
||||
"rent-wheel-excavators": "Аренда Колесное экскаваторов",
|
||||
"rent-forklift-trucks": "Аренда вилочное Погрузчиков",
|
||||
"rent-front-loaders": "Аренда Фронтальное погрузчиков",
|
||||
"rent-tele-loaders": "Аренда телескопическое погрузчиков",
|
||||
"rent-avtograder": "Аренда Автогрейдеров",
|
||||
"rent-buldozer": "Аренда Бульдозеров",
|
||||
"rent-compressor": "Аренда Компрессоров",
|
||||
"rent-katkas": "Аренда Катков",
|
||||
"cement-truck": "Автоцементовоз",
|
||||
"cement-truck-howo": "Автоцементовоз HOWO",
|
||||
"tank-volume": "Объем бака",
|
||||
"intercooler": "Интеркулер",
|
||||
"yes": "Есть",
|
||||
"turbo-dizel": "Турбодизель",
|
||||
"digging-depth": "Глубина копания",
|
||||
"gazel": "Газель",
|
||||
"knife": "Ширина лезвия",
|
||||
"bom-length": "Длина стрелы",
|
||||
"header-location": "Узбекистан, Ташкент",
|
||||
"phone": "Номер телефона",
|
||||
"news-title1": "Лучший прокат оборудования для твой следующий проект",
|
||||
"news-title2": "Новый погрузчик - это именно то, что вам нужно!",
|
||||
"news-title3": "Новый комплект компрессоров специально для вас",
|
||||
"news-title4": "С нашими мощными кранами ваша работа станет проще",
|
||||
"tex-rent": "Аренда оборудования",
|
||||
"wallet": "сумов",
|
||||
"time": "часов",
|
||||
"hour-price": "Цена за 1 час: ",
|
||||
"min-time": "Минимальный заказ: ",
|
||||
"day-price": "Цена аренды за день: ",
|
||||
"note1": "Мы предоставляем автолифт только с нашим водителем.",
|
||||
"note2": "Доставка автолифта включена в стоимость аренды.",
|
||||
"weight_kg": "Вес",
|
||||
"maxLength_m": "Максимальная длина",
|
||||
"maxHeight_m": "Максимальная высота",
|
||||
"capacity_tons": "Грузоподъёмность (тонн)",
|
||||
"capacity_kg": "Грузоподъёмность (кг)",
|
||||
"fuelType": "Тип топлива",
|
||||
"tankVolume_m3": "Объём бака",
|
||||
"maxSpeed_kmh": "Максимальная скорость",
|
||||
"enginePower_hp": "Мощность двигателя",
|
||||
"bom": "Длина стрелы",
|
||||
"qazish": "Глубина копания",
|
||||
"pichoq": "Длина лезвия",
|
||||
"zichlash": "Ширина уплотнения асфальта",
|
||||
"siqish": "Давление сжатия",
|
||||
"havo": "Ёмкость воздуха",
|
||||
"kompressor_sig": "Объём компрессора",
|
||||
"katalog": "Познакомьтесь с каталогом",
|
||||
"hero_title": "Надёжная спецтехника — лучший выбор для вашего проекта!",
|
||||
"hero_desc": "Мы предлагаем новейшую технику, специализированный транспорт и строительное оборудование по выгодной цене.",
|
||||
"shalanda_traller": "Шаланда и Траллеры",
|
||||
"evakuator": "Эвакуаторы",
|
||||
"asfalt_yotqizuvchi": "Асфальтоукладчик",
|
||||
"asfalt_kochiruvchi": "Асфальтоперегружатель",
|
||||
"beton_nasos": "Бетононасосы",
|
||||
"vodovoz": "Водовоз",
|
||||
"assenizator": "Ассенизатор",
|
||||
"manipulyator": "Манипулятор",
|
||||
"Avtovishka": "Автовышка",
|
||||
"Avtolift": "Автолифт",
|
||||
"errorTitle": "Произошла ошибка",
|
||||
"retry": "Повторить",
|
||||
"downloadError": "Ошибка при загрузке данных",
|
||||
"noData": "Специальная техника не найдена",
|
||||
"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": "Произошла ошибка при отправке!"
|
||||
}
|
||||
207
messages/uz.json
Normal file
207
messages/uz.json
Normal file
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"location":"Uzbekistan , Tashkent",
|
||||
"work_day_title": "Ish vaqti",
|
||||
"all": "Barcha mashinalar",
|
||||
"home": "Asosiy",
|
||||
"products": "Maxsulotlar",
|
||||
"news": "Yangiliklar",
|
||||
"contact": "Kontakt",
|
||||
"pricing-h2": "Toshkentda maxsus texnika ijarasi",
|
||||
"amazing": "Ajoyib",
|
||||
"equipment": "Qurilmalar",
|
||||
"trucks": "Yuk mashinalari",
|
||||
"cranes": "Kranlar",
|
||||
"forklift-trucks": "Forkliftlar",
|
||||
"forklift-truck": "Forklift",
|
||||
"excavators": "Ekskavatorlar",
|
||||
"excavator": "Ekskavator",
|
||||
"road-repairs": "Yo'l ta'miri",
|
||||
"agreement": "Kelishuv asosida",
|
||||
"cement-trucks": "Avtosementovozlar",
|
||||
"dumb-trucks": "Samosvallar",
|
||||
"dumb-truck": "Samosval",
|
||||
"trailers": "Treylerlar",
|
||||
"trailer": "Treyler",
|
||||
"gazels": "Gazellar",
|
||||
"avtotowers": "Avtominoralar",
|
||||
"avtocranes": "Avtokranlar",
|
||||
"crawler-cranes": "Paletli kranlar",
|
||||
"crawler-crane": "Paletli kran",
|
||||
"tower-cranes": "Minora kranlari",
|
||||
"tower-crane": "Minora krani",
|
||||
"lifters": "Dizel ko'targichlar",
|
||||
"lifter": "Dizel ko'targich",
|
||||
"front-loaders": "Old yuklagichlar",
|
||||
"front-loader": "Old yuklagich",
|
||||
"tele-loaders": "Teleskopik yuklagichlar",
|
||||
"tele-loader": "Teleskopik yuklagich",
|
||||
"crawler-excavators": "Paletli ekskavatorlar",
|
||||
"crawler-excavator": "Paletli ekskavator",
|
||||
"wheel-excavators": "Ekskavatorlar",
|
||||
"wheel-excavator": "G'ildirakli ekskavator",
|
||||
"mini-excavators": "Mini-ekskavatorlar",
|
||||
"excavator-loaders": "Ekskavator yuklagichlari",
|
||||
"excavator-loader": "Ekskavator-yuklagich",
|
||||
"avtograders": "Avtogreyderlar",
|
||||
"avtograder": "Avtogreyder",
|
||||
"buldozers": "Buldozerlar",
|
||||
"buldozer": "Buldozer",
|
||||
"katkas": "Katkalar",
|
||||
"katka": "Katka",
|
||||
"mini-loaders": "Kichik yuklagichlar",
|
||||
"compressors": "Kompressorlar",
|
||||
"testimonials": "Sharhlar",
|
||||
"clients'": "Mijozlar",
|
||||
"brand-h2": "Avtoparkimiz jahon brendlarining maxsus texnikalaridan iborat",
|
||||
"about-h2": "Biz sizga eng yaxshisini taklif qilamiz",
|
||||
"about-p": "Toshkentda ixtisoslashtirilgan uskunalarni ijaraga olish murakkab loyihalarni amalga oshirishni soddalashtiradigan va qurilish jarayonlarini tezlashtiradigan, shu bilan birga eng yuqori natijani ta'minlaydigan eng muhim xizmatdir. Bunday uskunani sotib olish muammosi bir nechta jihozlarni sotib olish bilan bog'liq yuqori xarajatlar va uzoq muddatli qoplash muddatlarini hisobga olgan holda qiyin bo'lishi mumkin. Yaxshiyamki, bizning kompaniyamiz tejamkor alternativani taklif qiladi: ixtisoslashtirilgan uskunalarni ijaraga berish xizmatlari",
|
||||
"about-block-quote": "Iltimos, bizning maxsus takliflarimiz va chegirmalarimizga e'tibor bering! Bugun biz bilan bog'laning va mutaxassislarimizdan maslahat oling",
|
||||
"contact-us": "Biz bilan bog'laning",
|
||||
"news-h2": "So'nggi yangiliklar va yangilanishlar",
|
||||
"Excavator": "Ekskavator",
|
||||
"faq-h2": "Tez-tez so'raladigan savollar",
|
||||
"faq1": "? Dam olish kunlari maxsus jihozlarga buyurtma berish mumkinmi",
|
||||
"faq-answer-1": "Ha, mumkin. Biz 24/7 buyurtmalarni qabul qilamiz va bajaramiz",
|
||||
"faq2": "? Maxsus texnika xizmatlarini ko'rsatishning minimal muddati qancha",
|
||||
"faq-answer-2": "Minimal ijara muddati 30 soatdan boshlanadi",
|
||||
"faq3": "? Sizda qanday maxsus uskunalar mavjud",
|
||||
"faq-answer-3": ": Bizning parkimizda quyidagilar mavjud",
|
||||
"faq-answer-3-1": "Yuk ko'tarish quvvati 7 tonnagacha bo'lgan manipulyatorlar",
|
||||
"faq-answer-3-2": "G'ildirakli va izli ekskavatorlar",
|
||||
"faq-answer-3-3": "Old va vilkalar (teleskopik) yuklagichlar va boshqalar",
|
||||
"faq4": "? To'lov shakli qanday",
|
||||
"faq-answer-4": "To`lov shakli har qanday, shu jumladan mobil ilovalar orqali",
|
||||
"partner-h2": "Bizning sheriklarimiz",
|
||||
"contact-h2": "Qo'ng'iroqni buyurtma qiling",
|
||||
"footer-p": "Har qanday qurilish loyihalarini muvaffaqiyatli bajarish uchun ishonchlilik, ishlash va chidamlilik.",
|
||||
"call": "Menga qo'ng'iroq qiling",
|
||||
"subscribe": "Bizni kuzatib boring",
|
||||
"links": "Foydali linklar",
|
||||
"contact-info": "Aloqa ma'lumotlari",
|
||||
"length": "Uzunligi",
|
||||
"width": "Kengligi",
|
||||
"height": "Balandligi",
|
||||
"load": "Yuk ko'tarish qobiliyati",
|
||||
"compressor": "Kompressor",
|
||||
"rent-tower-crane": "Minora kranlari ijarasi",
|
||||
"more": "Batafsil",
|
||||
"gasoline": "Yoqilg'i turi",
|
||||
"gasoline-volume": "Yoqilg'i sig'imi",
|
||||
"air-volume": "Havo sig'imi",
|
||||
"compressor-volume": "Kompressor sig'imi",
|
||||
"compressing-volume": "Siqish bosimi",
|
||||
"conditioning": "Konditsioner",
|
||||
"presssing-width": "Zichlash kengligi",
|
||||
"max-height": "Maksimum balandligi",
|
||||
"max-length": "Maksimum uzunligi",
|
||||
"power": "Dvigatel quvvati",
|
||||
"transmission": "O'tkazmalar qutisi",
|
||||
"max-speed": "Maksimum tezligi",
|
||||
"book": "Buyurtma berish",
|
||||
"ask": "Telegramdan so'rang",
|
||||
"description": "Tavsif",
|
||||
"avtotower": "Avtominora",
|
||||
"avtocrane": "Avtokran",
|
||||
"rent-cement-trucks": "Sementovozlar ijarasi",
|
||||
"details": "Tafsilotlar",
|
||||
"feature1": "Eng yaxshi qurilish texnikasi",
|
||||
"feature2": "Arzon narxda",
|
||||
"feature3": "Boshqalardan farqli xizmat",
|
||||
"feature4": "Sifat nazorati",
|
||||
"feature5": "Texnik qo'llovlar",
|
||||
"feature6": "Kerakli paytdagi servis xizmati",
|
||||
"rent-avtocranes": "Avtokranlar ijarasi",
|
||||
"weight": "Og'irligi",
|
||||
"contacts": "Aloqa",
|
||||
"digging-depth": "Qazish chuqurligi",
|
||||
"rent-samosval": "Samosvallar ijarasi",
|
||||
"samosval": "Samosvallar",
|
||||
"rent-trailers": "Treylerlar ijarasi",
|
||||
"rent-gazels": "Gazellar ijarasi",
|
||||
"rent-avtotower": "Avtominoralar ijarasi",
|
||||
"rent-crawler-cranes": "Paletli kranlar ijarasi",
|
||||
"rent-lifters": "Dizel ko'targichlar ijarasi",
|
||||
"rent-excavator-loader": "Ekskavator yuklagichlar ijarasi",
|
||||
"rent-crawler-excavators": "Paletli ekskavatorlar ijarasi",
|
||||
"rent-mini-excavators": "Mini ekskavatorlar ijarasi",
|
||||
"rent-wheel-excavators": "G'ildirakli ekskavatorlar ijarasi",
|
||||
"rent-forklift-trucks": "Forkliftlar ijarasi",
|
||||
"rent-front-loaders": "Old yuklagichlar ijarasi",
|
||||
"rent-tele-loaders": "Teleskopik yuklagichlar ijarasi",
|
||||
"rent-avtograder": "Avtogreyder ijarasi",
|
||||
"rent-buldozer": "Buldozerlar ijarasi",
|
||||
"rent-compressor": "Kompressorlar ijarasi",
|
||||
"rent-katka": "Katkalar ijarasi",
|
||||
"intercooler": "Interkuler",
|
||||
"cement-truck": "Avtosementovoz",
|
||||
"cement_truck_howo": "Avtosementovoz HOWO",
|
||||
"tank-volume": "Tank hajmi",
|
||||
"yes": "Bor",
|
||||
"turbo-dizel": "Turbo dizel",
|
||||
"gazel": "Gazel",
|
||||
"knife": "Pichoq kengligi",
|
||||
"bom-length": "Bomning uzunligi",
|
||||
"header-location": "O'zbekiston, Toshkent",
|
||||
"phone": "Telefon nomer",
|
||||
"news-title1": "Keyingi loyihangiz uchun eng yaxshi texnikalar ijarasi",
|
||||
"news-title2": "Yangi yuklagich siz uchun eng yaxshi texnika!",
|
||||
"news-title3": "Yangi kompressorlar to'plami aynan siz uchun",
|
||||
"news-title4": "Bizning kuchli kranlarimiz bilan ishingiz yanada osonlashadi.",
|
||||
"tex-rent": "Texnikalar ijarasi",
|
||||
"wallet": "so'm",
|
||||
"time": "soat",
|
||||
"hour-price": "1 soat uchun narx: ",
|
||||
"min-time": "Minimal buyrtma: ",
|
||||
"day-price": "Bir kunlik ijara narxi: ",
|
||||
"note1": "Biz avtoliftni faqat haydovchimiz bilan taqdim etamiz.",
|
||||
"note2": "Avtoliftni yetkazib berish ijara narxiga kiritilgan.",
|
||||
"weight_kg": "Og‘irligi",
|
||||
"maxLength_m": "Maksimum uzunligi",
|
||||
"maxHeight_m": "Maksimum balandligi",
|
||||
"capacity_tons": "Yuk ko‘tarish qobiliyati (tonna)",
|
||||
"capacity_kg": "Yuk ko‘tarish qobiliyati (kg)",
|
||||
"fuelType": "Yoqilg‘i turi",
|
||||
"tankVolume_m3": "Tank hajmi",
|
||||
"maxSpeed_kmh": "Maksimum tezligi",
|
||||
"enginePower_hp": "Dvigatel quvvati",
|
||||
"bom": "Bom uzunligi",
|
||||
"qazish": "Qazish chuqurligi",
|
||||
"pichoq": "Pichoq uzunligi",
|
||||
"zichlash": "Asfalt zichlash kengligi",
|
||||
"siqish": "Siqish bosimi",
|
||||
"havo": "Havo sig‘imi",
|
||||
"kompressor_sig": "Kompressor sig‘imi",
|
||||
"katalog": "Katalog bilan tanishing",
|
||||
"hero_title": "Ishonchli SpesTexnika — sizning loyihangiz uchun eng yaxshi tanlov!",
|
||||
"hero_desc": "Biz eng so‘nggi texnikalar, maxsus transportlar va qurilish uskunalarini qulay narxda taqdim etamiz.",
|
||||
"shalanda_traller": "Shalanda va Trallerlar",
|
||||
"evakuator": "Evakuatorlar",
|
||||
"asfalt_yotqizuvchi": "Asfalt yotqizuvchi mashina",
|
||||
"asfalt_kochiruvchi": "Asfalt ko‘chiruvchi mashina",
|
||||
"beton_nasos": "Beton nasos mashinalari",
|
||||
"vodovoz": "Vodovoz",
|
||||
"assenizator": "Assenizator",
|
||||
"manipulyator": "Manipulyator",
|
||||
"Avtovishka": "Avtovishka",
|
||||
"Avtolift": "Avtolift",
|
||||
"errorTitle": "Xatolik yuz berdi",
|
||||
"retry": "Qayta urinish",
|
||||
"downloadError": "Ma'lumotlarni yuklashda xatolik",
|
||||
"noData": "Mahsus texnikalar topilmadi",
|
||||
"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!"
|
||||
}
|
||||
8
middleware.ts
Normal file
8
middleware.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import createMiddleware from "next-intl/middleware";
|
||||
import { routing } from "./i18n/routing";
|
||||
|
||||
export default createMiddleware(routing);
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
i18n:{
|
||||
defaultLocale:'uz',
|
||||
locales:['uz','ru']
|
||||
},
|
||||
reloadPrerender:false,
|
||||
}
|
||||
@@ -1,22 +1,25 @@
|
||||
import type { NextConfig } from "next";
|
||||
import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
const withNextIntl = createNextIntlPlugin("./i18n/request.ts");
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'http',
|
||||
hostname: 'api.spes-texnika.uz',
|
||||
port: '',
|
||||
pathname: '/resources/media/**',
|
||||
protocol: "http",
|
||||
hostname: "api.spes-texnika.uz",
|
||||
port: "",
|
||||
pathname: "/resources/media/**",
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'api.spes-texnika.uz',
|
||||
port: '',
|
||||
pathname: '/resources/media/**',
|
||||
protocol: "https",
|
||||
hostname: "api.spes-texnika.uz",
|
||||
port: "",
|
||||
pathname: "/resources/media/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
export default withNextIntl(nextConfig);
|
||||
|
||||
902
package-lock.json
generated
902
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,17 +11,12 @@
|
||||
"antd": "^5.27.4",
|
||||
"axios": "^1.13.2",
|
||||
"framer-motion": "^12.23.24",
|
||||
"i18next": "^25.5.3",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"lucide-react": "^0.553.0",
|
||||
"next": "^15.5.15",
|
||||
"next-i18next": "^15.4.2",
|
||||
"next-intl": "^4.9.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-i18next": "^16.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-scroll": "^1.9.3",
|
||||
|
||||
@@ -188,5 +188,20 @@
|
||||
"retry": "Повторить",
|
||||
"downloadError": "Ошибка при загрузке данных",
|
||||
"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",
|
||||
"downloadError": "Ma'lumotlarni yuklashda xatolik",
|
||||
"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!"
|
||||
}
|
||||
|
||||
1
types/css.d.ts
vendored
Normal file
1
types/css.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module "*.css" {}
|
||||
@@ -38,7 +38,6 @@ export interface ProductTypes {
|
||||
id: number;
|
||||
truck_name: string;
|
||||
desc: string;
|
||||
path: string;
|
||||
image: string | StaticImageData;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user