Compare commits

..

6 Commits

Author SHA1 Message Date
nabijonovdavronbek619@gmail.com
fd0a98ce1a for gitea 2026-05-04 09:38:40 +05:00
nabijonovdavronbek619@gmail.com
73912860ec update product component sorted base static data 2026-04-29 18:33:55 +05:00
nabijonovdavronbek619@gmail.com
77c6baaa55 slider component updated 2026-04-28 11:29:06 +05:00
nabijonovdavronbek619@gmail.com
cfa84d04ab new things 2026-04-27 12:29:17 +05:00
nabijonovdavronbek619@gmail.com
5b7dbd5019 github 2026-04-18 17:58:50 +05:00
nabijonovdavronbek619@gmail.com
80c2dd8930 slider image change 2026-04-18 17:57:32 +05:00
28 changed files with 1444 additions and 1817 deletions

View File

@@ -8,10 +8,10 @@ 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 { useTranslations } from "next-intl";
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";
// for gitea
const baseUrl = "https://api.spes-texnika.uz/api/v1/products/"; 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() { export default function CarDetailPage() {
const t = useTranslations();
const [modalOpen, setModalOpen] = useState<boolean>(false); const [modalOpen, setModalOpen] = useState<boolean>(false);
const initialSubCategory = useSubCategory( 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) { if (!firstData) {
return ( return (
@@ -124,30 +126,28 @@ export default function CarDetailPage() {
dir="ltr" dir="ltr"
className="my-10 max-w-[1200px] w-full mx-auto space-y-8 flex flex-col items-start justify-center px-2" 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"> <div className="text-2xl font-bold w-full text-center text-secondary mb-10">
<Text txt={""} /> <Text txt={""} />
</div> </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"> <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"> <div className="max-w-[600px] w-full h-auto">
<Image <Image
src={firstData?.image ? firstData?.image : logoImg} src={firstData?.image ? firstData?.image : logoImg}
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>
{/* Asosiy ma'lumotlar */}
<div className="lg:space-y-6 space-y-3 w-full"> <div className="lg:space-y-6 space-y-3 w-full">
<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>
@@ -161,13 +161,11 @@ export default function CarDetailPage() {
</div> </div>
)} )}
{/* Izoh */}
<div className="space-y-2 text-gray-500 text-lg"> <div className="space-y-2 text-gray-500 text-lg">
<Text txt="note1" /> <Text txt="note1" />
<Text txt="note2" /> <Text txt="note2" />
</div> </div>
{/* Buyurtma tugmasi */}
<div className="flex gap-6"> <div className="flex gap-6">
<button <button
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
@@ -180,13 +178,13 @@ export default function CarDetailPage() {
</div> </div>
</div> </div>
{/* Texnik xususiyatlar */} {firstData?.features.length > 0 && (
<div className="w-full border-t border-gray-300 pt-6"> <div className="w-full border-t border-gray-300 pt-6">
<h2 className="text-xl font-semibold mb-4 text-secondary"> <h2 className="text-xl font-semibold mb-4 text-secondary">
Texnik xususiyatlari {t("tech-features")}
</h2> </h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-gray-700"> <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 <div
key={item.id} key={item.id}
className="p-3 rounded-md bg-gray-50 border border-gray-200 hover:bg-gray-100 transition" 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>
</div> </div>
)}
{/* Ijara modal */}
<CarRentalModal <CarRentalModal
car={firstData} car={firstData}
isOpen={modalOpen} isOpen={modalOpen}

View File

@@ -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";
@@ -6,19 +5,25 @@ import Contact from "@/components/pageParts/contact";
import UpScrollIcon from "@/components/lib_components/upScroll"; import UpScrollIcon from "@/components/lib_components/upScroll";
import { Suspense } from "react"; import { Suspense } from "react";
import Time from "@/components/lib_components/time"; 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({ export default async function LangLayout({
children, children,
params, params,
}: { }: {
children: React.ReactNode; 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 ( return (
<html lang={lang} dir="rtl"> <html lang={lang} dir="rtl" suppressHydrationWarning>
<body> <body>
<NextIntlClientProvider messages={messages}>
<Suspense fallback={null}> <Suspense fallback={null}>
<Header /> <Header />
</Suspense> </Suspense>
@@ -30,6 +35,7 @@ export default async function LangLayout({
<Footer /> <Footer />
<UpScrollIcon /> <UpScrollIcon />
<Time /> <Time />
</NextIntlClientProvider>
</body> </body>
</html> </html>
); );

View File

@@ -91,7 +91,7 @@ export default function InnerProductcard({ data }: { data: any }) {
{/* Nomi va tavsif */} {/* Nomi va tavsif */}
<div className="mb-4"> <div className="mb-4">
<h3 <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" group-hover:text-[#f2a01c] transition-colors duration-300"
> >
<Text txt={data.name} /> <Text txt={data.name} />

View File

@@ -6,12 +6,22 @@ import Link from "next/link";
import Text from "../lib_components/text"; import Text from "../lib_components/text";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useSubCategory } from "@/store/subCategory";
export default function SliderCard({ data }: { data: ProductTypes }) { export default function SliderCard({ data }: { data: ProductTypes }) {
const router = useParams(); const router = useParams();
const setInitialSubcategory = useSubCategory(
(state) => state.setInitialSubCategory,
);
return ( return (
<Link <Link
href={`/${router.lang}/${data.path}`} href={`/${router.lang}/${data.id}`}
onClick={() => {
setInitialSubcategory({
name:data.truck_name,
id: data.id,
});
}}
id="news_slider_card" id="news_slider_card"
className="group hover:cursor-pointer block" className="group hover:cursor-pointer block"
> >
@@ -22,13 +32,13 @@ export default function SliderCard({ data }: { data: ProductTypes }) {
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.98 }}
className="rounded-xl overflow-hidden shadow-md hover:shadow-lg transition-all" className="rounded-xl overflow-hidden shadow-md hover:shadow-lg transition-all"
> >
{/* Rasm qismi */} {/* Rasm qismi for github*/}
<Image <Image
src={data.image} src={data.image}
alt="slider image" alt="slider image"
width={600} width={600}
height={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 */} {/* Pastki kontent */}

View File

@@ -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 { useTranslations } from "next-intl";
interface CarRentalModalProps { interface CarRentalModalProps {
car: innerCardTypes; car: innerCardTypes;
@@ -15,8 +16,10 @@ export default function CarRentalModal({
isOpen, isOpen,
onClose, onClose,
}: CarRentalModalProps) { }: CarRentalModalProps) {
const t = useTranslations();
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,23 +27,48 @@ export default function CarRentalModal({
if (car.price) setTotal(hours * car.price); if (car.price) setTotal(hours * car.price);
}, [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>) => { const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
if (!userName || !phone || !hours) { if (!userName || !phone || !hours) {
alert("Iltimos, barcha maydonlarni toldiring!"); alert(t("modal-fill-required"));
return;
}
if (!isPhoneValid(phone)) {
setPhoneError(t("modal-phone-error"));
return; return;
} }
try { try {
// ⚙️ Telegram bot ma'lumotlari const token = "8277200651:AAHYi2iE7bW0PEfEahTFyqFQhUcR9bGZnnY";
const token = // "7940057045:AAHRFPvgUCo_7pqpXD6uq4li7-_DYx2J96g"
process.env.NEXT_PUBLIC_TELEGRAM_TOKEN || const chatId = "6735606461"
"7940057045:AAHRFPvgUCo_7pqpXD6uq4li7-_DYx2J96g"; // "6134458285";
const chatId = process.env.NEXT_PUBLIC_TELEGRAM_CHAT_ID || "6134458285";
// 🧾 Yuboriladigan xabar
const message = ` const message = `
🚗 *Yangi buyurtma!* 🚗 *Yangi buyurtma!*
@@ -51,28 +79,27 @@ export default function CarRentalModal({
💰 Umumiy summa: ${total?.toLocaleString("uz-UZ")} UZS 💰 Umumiy summa: ${total?.toLocaleString("uz-UZ")} UZS
📦 Mashina: ${car.name} 📦 Mashina: ${car.name}
⛽️ Yoqilgi turi: ${car.fuelType || "Nomalum"} ⛽️ Yoqilg'i turi: ${car.fuelType || "Noma'lum"}
⚙️ Dvigatel: ${car.enginePower_hp || "-"} ⚙️ Dvigatel: ${car.enginePower_hp || "-"}
🚀 Maks tezlik: ${car.maxSpeed_kmh ? car.maxSpeed_kmh + " km/soat" : "-"} 🚀 Maks tezlik: ${car.maxSpeed_kmh ? car.maxSpeed_kmh + " km/soat" : "-"}
📝 Qoshimcha malumot: ${car.path || "-"} 📝 Qo'shimcha ma'lumot: ${car.path || "-"}
`; `;
// 📤 Telegram API orqali yuborish
await axios.post(`https://api.telegram.org/bot${token}/sendMessage`, { await axios.post(`https://api.telegram.org/bot${token}/sendMessage`, {
chat_id: chatId, chat_id: chatId,
text: message, text: message,
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 koring!"); alert(t("modal-error"));
} }
}; };
@@ -81,7 +108,6 @@ export default function CarRentalModal({
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"> <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"> <div className="bg-white rounded-xl shadow-xl max-w-lg w-full p-6 relative animate-fade-in">
{/* Close button */}
<button <button
className="absolute top-4 right-4 text-gray-500 hover:text-gray-900 text-2xl font-bold transition" className="absolute top-4 right-4 text-gray-500 hover:text-gray-900 text-2xl font-bold transition"
onClick={onClose} onClick={onClose}
@@ -89,25 +115,26 @@ export default function CarRentalModal({
× ×
</button> </button>
{/* Header */}
<div className="flex flex-col md:flex-row gap-4"> <div className="flex flex-col md:flex-row gap-4">
<div className="flex-1"> <div className="flex-1">
<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>
</div> </div>
{/* Form */}
<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 +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" 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 +178,10 @@ 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 +189,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>

View File

@@ -1,8 +1,8 @@
'use client' 'use client'
import { useTranslation } from "react-i18next"; import { useTranslations } from "next-intl";
export default function Text({ txt }: { txt: string }) { export default function Text({ txt }: { txt: string }) {
const { t } = useTranslation(); const t = useTranslations();
return <div>{t(txt)}</div>; return <div>{t(txt)}</div>;
} }

View File

@@ -1,10 +1,10 @@
'use client' 'use client'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslations } from 'next-intl'
export default function Time() { export default function Time() {
const {t} = useTranslation(); const t = useTranslations();
return ( 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'> <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> <p>{t('work_day_title')}</p>

View File

@@ -1,11 +1,8 @@
"use client"; "use client";
import { FaLocationDot } from "react-icons/fa6"; import { FaLocationDot } from "react-icons/fa6";
import Text from "../lib_components/text";
import { useTranslation } from "react-i18next";
export default function Header() { export default function Header() {
const { t } = useTranslation();
return ( return (
<div <div
dir="ltr" dir="ltr"

View File

@@ -2,26 +2,25 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import i18n from "@/i18n";
import { animateScroll as scroll } from "react-scroll"; import { animateScroll as scroll } from "react-scroll";
import Text from "../lib_components/text"; import Text from "../lib_components/text";
import "./navbar.css"; import "./navbar.css";
import { logoImg } from "@/assets"; import { logoImg } from "@/assets";
import Image from "next/image"; import Image from "next/image";
import { usePathname, useRouter } from "next/navigation"; import { X } from "lucide-react";
import { X } from "lucide-react"; // ❗ exit icon import { useLocale } from "next-intl";
import { useRouter, usePathname } from "@/i18n/navigation";
export default function Navbar() { export default function Navbar() {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const locale = useLocale();
const [toggle, setToggle] = useState(false); const [toggle, setToggle] = useState(false);
const [togglerIcon, setTogglerIcon] = useState("toggler"); const [togglerIcon, setTogglerIcon] = useState("toggler");
const [lang, setLang] = useState<"uz" | "ru">("uz");
const handleChangeLang = (lng: "uz" | "ru") => { const handleChangeLang = (lng: "uz" | "ru") => {
setLang(lng); router.replace(pathname, { locale: lng });
i18n.changeLanguage(lng);
}; };
const changeToggler = () => { const changeToggler = () => {
@@ -32,26 +31,23 @@ export default function Navbar() {
}; };
const scrollOrRoute = (id: string) => { const scrollOrRoute = (id: string) => {
if (pathname !== `/${lang}`) { if (pathname !== "/") {
// Agar user boshqa sahifada bo'lsa asosiy sahifaga yo'naltirish router.push(`/#${id}`);
router.push(`/${lang}#${id}`);
} else { } else {
// Agar main page-da bo'lsa scroll qilish
scroll.scrollTo(document.getElementById(id)!.offsetTop - 100); scroll.scrollTo(document.getElementById(id)!.offsetTop - 100);
} }
changeToggler(); changeToggler();
}; };
const goHome = () => { const goHome = () => {
router.push(`/${lang}`); router.push("/");
}; };
// ❗ Scrollni bloklash uchun effect
useEffect(() => { useEffect(() => {
if (toggle) { if (toggle) {
document.body.style.overflow = "hidden"; // orqa scrollni ochir document.body.style.overflow = "hidden";
} else { } else {
document.body.style.overflow = "auto"; // qayta yoq document.body.style.overflow = "auto";
} }
}, [toggle]); }, [toggle]);
@@ -69,9 +65,9 @@ export default function Navbar() {
<button <button
onClick={() => handleChangeLang("uz")} onClick={() => handleChangeLang("uz")}
className={`hover:cursor-pointer ${ 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] ${ } 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 UZ
@@ -79,9 +75,9 @@ export default function Navbar() {
<button <button
onClick={() => handleChangeLang("ru")} onClick={() => handleChangeLang("ru")}
className={`hover:cursor-pointer ${ 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] ${ } 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 RU

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslations } from "next-intl";
import Text from "../lib_components/text"; import Text from "../lib_components/text";
import axios from "axios"; import axios from "axios";
import { FormEvent } from "react"; import { FormEvent } from "react";
@@ -42,7 +42,7 @@ const isValidPhone = (value: string) => {
}; };
export default function Contact() { export default function Contact() {
const { t } = useTranslation(); const t = useTranslations();
const [phone, setPhone] = useState(""); const [phone, setPhone] = useState("");
const handlePhoneChange = (value: string) => { const handlePhoneChange = (value: string) => {
@@ -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;
} }
@@ -63,7 +63,7 @@ export default function Contact() {
: `+998${normalized}`; : `+998${normalized}`;
try { try {
const token = "7940057045:AAHRFPvgUCo_7pqpXD6uq4li7-_DYx2J96g"; // Use environment variable const token = "7940057045:AAHRFPvgUCo_7pqpXD6uq4li7-_DYx2J96g";
const chatId = 6134458285; const chatId = 6134458285;
if (!token || !chatId) { if (!token || !chatId) {
@@ -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"));
} }
}; };
@@ -99,7 +99,6 @@ export default function Contact() {
<Text txt="contact-h2" /> <Text txt="contact-h2" />
</h2> </h2>
{/* Form */}
<form <form
className="flex max-sm:flex-col gap-5 w-full max-w-2xl px-4" className="flex max-sm:flex-col gap-5 w-full max-w-2xl px-4"
onSubmit={sendMessage} onSubmit={sendMessage}

View File

@@ -6,13 +6,89 @@ import "swiper/css";
import "swiper/css/navigation"; import "swiper/css/navigation";
import Title from "../lib_components/title"; import Title from "../lib_components/title";
import SliderCard from "../cards/sliderCard"; 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 // The custom CSS selectors for navigation
const navigationPrevEl = ".custom-swiper-prev"; const navigationPrevEl = ".custom-swiper-prev";
const navigationNextEl = ".custom-swiper-next"; 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() { 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 ( return (
<div <div
dir="ltr" dir="ltr"
@@ -29,7 +105,7 @@ export default function CustomSlider() {
<button <button
className={`${navigationPrevEl.replace( 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`} )} 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 <button
className={`${navigationNextEl.replace( 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 `} )} 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 }, 1024: { slidesPerView: 3 },
}} }}
> >
{sliderData.map((item, index) => ( {cars.map((item, index) => (
<SwiperSlide key={index}> <SwiperSlide key={index}>
<SliderCard data={item} /> <SliderCard data={item} />
</SwiperSlide> </SwiperSlide>

View File

@@ -4,13 +4,28 @@ import React, { useEffect, useState } from "react";
import Title from "../../lib_components/title"; import Title from "../../lib_components/title";
import Text from "../../lib_components/text"; import Text from "../../lib_components/text";
import type { ProductTypes } from "@/types"; import type { ProductTypes } from "@/types";
import { allProducts } from "@/data";
import ProductCard from "../../cards/productCard"; import ProductCard from "../../cards/productCard";
import { LoadingSkeleton } from "@/components/loadingProduct"; import { LoadingSkeleton } from "@/components/loadingProduct";
import { EmptyState } from "@/components/emptyState"; import { EmptyState } from "@/components/emptyState";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { baseUrl } from "@/data/url"; 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() { export default function Products() {
const [cars, setCars] = useState<ProductTypes[]>([]); const [cars, setCars] = useState<ProductTypes[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -35,29 +50,26 @@ export default function Products() {
} }
const data = await response.json(); const data = await response.json();
console.log("backend Data: ", data?.data);
if (data?.data && data.data.length > 0) { if (data?.data && data.data.length > 0) {
setCars(data.data); // ✅ Qayta tartiblash
const sorted = sortByCustomOrder(data.data);
setCars(sorted);
} else { } else {
setCars([]); setCars([]);
} }
} catch (error) { } catch (error) {
console.log("Xatolik: ", error);
setError(error instanceof Error ? error.message : "Noma'lum xatolik"); setError(error instanceof Error ? error.message : "Noma'lum xatolik");
// Xatolik bo'lsa ham local data'ni ko'rsatish
setCars(allProducts);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
fetchProducts(); fetchProducts();
}, [lang]); // Bo'sh array - faqat bitta marta ishlaydi }, [lang]);
return ( return (
<div dir="ltr" className="max-w-[1200px] w-full mx-auto"> <div dir="ltr" className="max-w-[1200px] w-full mx-auto">
{/* title part */}
<div className="flex flex-col mb-10"> <div className="flex flex-col mb-10">
<div className="flex items-center justify-center w-full"> <div className="flex items-center justify-center w-full">
<div className="text-[#f2a01c] px-2 text-[18px] font-semibold"> <div className="text-[#f2a01c] px-2 text-[18px] font-semibold">
@@ -67,7 +79,6 @@ export default function Products() {
<Title text="pricing-h2" /> <Title text="pricing-h2" />
</div> </div>
{/* Error message */}
{error && ( {error && (
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg"> <div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-600 text-center"> <p className="text-red-600 text-center">
@@ -76,7 +87,6 @@ export default function Products() {
</div> </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"> <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 ? ( {loading ? (
<LoadingSkeleton /> <LoadingSkeleton />

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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
View 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
View 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
View 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
View 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": "Ogirligi",
"maxLength_m": "Maksimum uzunligi",
"maxHeight_m": "Maksimum balandligi",
"capacity_tons": "Yuk kotarish qobiliyati (tonna)",
"capacity_kg": "Yuk kotarish qobiliyati (kg)",
"fuelType": "Yoqilgi 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 sigimi",
"kompressor_sig": "Kompressor sigimi",
"katalog": "Katalog bilan tanishing",
"hero_title": "Ishonchli SpesTexnika — sizning loyihangiz uchun eng yaxshi tanlov!",
"hero_desc": "Biz eng songgi 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 kochiruvchi 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
View 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|.*\\..*).*)"],
};

View File

@@ -1,8 +0,0 @@
module.exports = {
i18n:{
defaultLocale:'uz',
locales:['uz','ru']
},
reloadPrerender:false,
}

View File

@@ -1,22 +1,25 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin("./i18n/request.ts");
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
images: { images: {
remotePatterns: [ remotePatterns: [
{ {
protocol: 'http', protocol: "http",
hostname: 'api.spes-texnika.uz', hostname: "api.spes-texnika.uz",
port: '', port: "",
pathname: '/resources/media/**', pathname: "/resources/media/**",
}, },
{ {
protocol: 'https', protocol: "https",
hostname: 'api.spes-texnika.uz', hostname: "api.spes-texnika.uz",
port: '', port: "",
pathname: '/resources/media/**', pathname: "/resources/media/**",
}, },
], ],
}, },
}; };
export default nextConfig; export default withNextIntl(nextConfig);

902
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,17 +11,12 @@
"antd": "^5.27.4", "antd": "^5.27.4",
"axios": "^1.13.2", "axios": "^1.13.2",
"framer-motion": "^12.23.24", "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", "leaflet": "^1.9.4",
"lucide-react": "^0.553.0", "lucide-react": "^0.553.0",
"next": "^15.5.15", "next": "^15.5.15",
"next-i18next": "^15.4.2", "next-intl": "^4.9.2",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-i18next": "^16.0.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
"react-scroll": "^1.9.3", "react-scroll": "^1.9.3",

View File

@@ -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": "Произошла ошибка при отправке!"
} }

View File

@@ -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!"
} }

1
types/css.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module "*.css" {}

View File

@@ -38,7 +38,6 @@ export interface ProductTypes {
id: number; id: number;
truck_name: string; truck_name: string;
desc: string; desc: string;
path: string;
image: string | StaticImageData; image: string | StaticImageData;
} }