Compare commits

..

2 Commits

Author SHA1 Message Date
nabijonovdavronbek619@gmail.com
259af77384 translation done , services page added backend service detail page added 2026-02-17 11:44:26 +05:00
nabijonovdavronbek619@gmail.com
1d34ea1d47 connected backend to service page and detail 2026-02-16 15:57:19 +05:00
24 changed files with 878 additions and 175 deletions

View File

@@ -61,7 +61,7 @@ export default function SlugPage() {
const features = product.features.map((item: any) => item.name); const features = product.features.map((item: any) => item.name);
return ( return (
<div className="min-h-screen bg-[#1e1d1c] px-4 md:px-8"> <div className="min-h-screen bg-[#1e1d1c] px-4 md:px-8 pb-35">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<div className="pt-30 pb-10"> <div className="pt-30 pb-10">
<Breadcrumb /> <Breadcrumb />

View File

@@ -0,0 +1,280 @@
"use client";
import Image from "next/image";
import { motion } from "framer-motion";
import { useEffect, useState } from "react";
import { useTranslations } from "next-intl";
import {
getOperationalSystems,
SystemFeature,
} from "@/lib/api/demoapi/operationalSystems";
import { Breadcrumb } from "@/components/breadCrumb";
export default function OperationalSystemsPage() {
const t = useTranslations("operationalSystems");
const [data, setData] = useState<SystemFeature[] | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
// Demo data - fallback ma'lumotlar
const demoData: SystemFeature[] = [
{
id: "1",
title: t("systems.sprinkler.title"),
shortDesc:t("systems.sprinkler.short-desc"),
description: t("systems.sprinkler.description"),
features: [
t("systems.sprinkler.features.0"),
t("systems.sprinkler.features.1"),
t("systems.sprinkler.features.2"),
t("systems.sprinkler.features.3"),
],
image: "/images/services/sprinkler.jpg",
},
];
const fetchData = async () => {
try {
setLoading(true);
setError(false);
const result = await getOperationalSystems();
// Agar backend ma'lumot qaytarsa
if (result && result.length > 0) {
setData(result);
} else {
// Backend bo'sh ma'lumot qaytarsa, demo ma'lumotlarni ishlatamiz
setData(demoData);
}
} catch (err) {
setError(true);
// Xatolik bo'lsa, demo ma'lumotlarni ishlatamiz
setData(demoData);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
// Animation variants
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
ease: [0.22, 1, 0.36, 1] as const,
},
},
};
const cardVariants = {
hidden: { opacity: 0, scale: 0.95 },
visible: {
opacity: 1,
scale: 1,
transition: {
duration: 0.5,
ease: [0.22, 1, 0.36, 1] as const,
},
},
};
// Loading state
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-black">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center space-y-6"
>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
className="w-16 h-16 border-4 border-red-500 border-t-transparent rounded-full mx-auto"
/>
<p className="text-white text-xl font-medium font-unbounded">
{t("loading")}
</p>
</motion.div>
</div>
);
}
// Error state with retry
if (error && !data) {
return (
<div className="min-h-screen flex items-center justify-center bg-black px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center space-y-6 max-w-md"
>
<div className="text-7xl"></div>
<p className="text-white text-xl font-medium">{t("error")}</p>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={fetchData}
className="px-6 py-3 bg-red-500 text-white rounded-lg font-medium hover:bg-red-600 transition-colors"
>
{t("retry")}
</motion.button>
</motion.div>
</div>
);
}
return (
<div className="pt-20 md:pt-30 pb-35 max-w-6xl mx-auto w-full px-4">
{/* Header */}
{/* <motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="flex md:flex-row flex-col items-start justify-between gap-6 md:gap-8 w-full mb-16 md:mb-24"
>
<motion.div variants={itemVariants} className="space-y-4 md:space-y-8 flex-1">
<h1 className="bg-linear-to-br from-white via-white to-gray-400 text-transparent bg-clip-text text-3xl md:text-4xl lg:text-5xl font-semibold font-almarai">
{t('title')}
</h1>
<p className="text-white font-medium font-unbounded text-base sm:text-lg md:text-xl max-w-2xl leading-relaxed">
{t('subtitle')}
</p>
</motion.div>
<motion.div
variants={itemVariants}
whileHover={{ scale: 1.05, rotate: 3 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
className="shrink-0 relative"
>
<Image
src="/images/home/redShlang.png"
alt="Fire hose"
fill
priority
className="w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 lg:w-52 lg:h-52 object-contain"
/>
</motion.div>
</motion.div> */}
<div className="mb-5">
<Breadcrumb />
</div>
{/* Main Content */}
{!data || data.length === 0 ? (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center py-20"
>
<p className="text-gray-400 text-xl font-unbounded">{t("noData")}</p>
</motion.div>
) : (
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="space-y-12 md:space-y-20"
>
{data.map((system, index) => (
<motion.div
key={system.id}
variants={cardVariants}
whileHover={{ y: -8 }}
className={`flex flex-col gap-8 md:gap-12 items-center`}
>
{/* Image Section */}
<motion.div
whileHover={{ scale: 1.03 }}
transition={{ duration: 0.3 }}
className="w-full relative h-64 md:h-80 lg:h-96 rounded-xl overflow-hidden"
>
<Image
src={system.image}
alt={system.title}
fill
className="object-cover w-full h-auto"
/>
<div className="absolute inset-0 bg-linear-to-t from-black/10 to-transparent" />
</motion.div>
{/* Content Section */}
<div className="w-full space-y-6">
<motion.h2
initial={{ opacity: 0, x: index % 2 === 0 ? -20 : 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="text-2xl md:text-3xl lg:text-4xl font-bold text-white font-almarai"
>
{system.title}
</motion.h2>
<motion.p
initial={{ opacity: 0, x: index % 2 === 0 ? -20 : 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-gray-300 text-sm md:text-base leading-relaxed font-unbounded"
>
{system.shortDesc}
</motion.p>
<motion.p
initial={{ opacity: 0, x: index % 2 === 0 ? -20 : 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-gray-300 text-sm md:text-base leading-relaxed font-unbounded"
>
{system.description}
</motion.p>
<div className="space-y-4">
<ul className="space-y-3">
{system.features.map((feature, featureIndex) => (
<motion.li
key={featureIndex}
initial={{ opacity: 0, x: index % 2 === 0 ? -20 : 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.3 + featureIndex * 0.1,
}}
className="flex items-start gap-3 text-gray-300"
>
<span className="text-sm md:text-base font-unbounded">
{feature}
</span>
</motion.li>
))}
</ul>
</div>
</div>
</motion.div>
))}
</motion.div>
)}
</div>
);
}

View File

@@ -140,6 +140,7 @@ export function Breadcrumb({
if (segment === "special_product") { if (segment === "special_product") {
return product.name; return product.name;
} }
if(segment === 'detail') return '';
return t(`breadcrumb.${segment}`); return t(`breadcrumb.${segment}`);
} catch { } catch {
// Aks holda, segment nomini formatlash // Aks holda, segment nomini formatlash

View File

@@ -61,7 +61,10 @@ export function Footer() {
const handleSubscribe = (e: React.FormEvent) => { const handleSubscribe = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (email) { if (email) {
formRequest.mutate({ number: email }); // Telefon raqamni tozalash (faqat raqamlar)
const cleanPhone = email.replace(/\D/g, "");
console.log("without 998: ", cleanPhone.slice(3));
formRequest.mutate({ number: Number(cleanPhone.slice(3)) });
} }
}; };
@@ -73,7 +76,7 @@ export function Footer() {
"linear-gradient(to top right, #452811 0%, #000000 20%, #000000 40%, #000000 60%, #000000 80%, #000000 100%)", "linear-gradient(to top right, #452811 0%, #000000 20%, #000000 40%, #000000 60%, #000000 80%, #000000 100%)",
}} }}
> >
{/* Newsletter Section for gitea */} {/* Newsletter Section */}
<div className=" absolute w-full -top-40 px-4 py-12 md:py-16"> <div className=" absolute w-full -top-40 px-4 py-12 md:py-16">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<div className="rounded-2xl bg-red-600 px-6 py-8 md:flex lg:flex-row flex-col max-lg:gap-5 md:items-center lg:justify-between justify-center md:px-10 md:py-12"> <div className="rounded-2xl bg-red-600 px-6 py-8 md:flex lg:flex-row flex-col max-lg:gap-5 md:items-center lg:justify-between justify-center md:px-10 md:py-12">
@@ -238,16 +241,28 @@ export function Footer() {
{/* Copyright Section */} {/* Copyright Section */}
<div className="border-t border-gray-800 px-4 py-8"> <div className="border-t border-gray-800 px-4 py-8">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<div className="font-almarai flex flex-col justify-center gap-4 text-lg text-gray-400 md:flex-row md:items-center"> <div className="font-almarai flex flex-col justify-between gap-4 text-lg text-gray-400 md:flex-row md:items-center">
<a href="http://felix-its.uz/" className="hover:text-red-600">{t("footer.create", { name: "Felix-its.uz" })}</a> {locale === "uz" ? (
{/* <div className="flex gap-6"> <div className="flex gap-2 ">
<a href="#terms" className="hover:text-white"> <a
Terms & Conditions href="http://felix-its.uz/"
className="hover:text-red-600 hover:cursor-pointer text-blue-300 underline"
>
Felix-its.uz
</a> </a>
<a href="#privacy" className="hover:text-white"> <p>- Jamoasi tomonidan ishlab chiqilgan</p>
Privacy Policy </div>
) : (
<div className="flex gap-2">
{locale === "ru" ? <p>Разработано -</p> : <p>Created by - </p>}
<a
href="http://felix-its.uz/"
className="hover:text-red-600 hover:cursor-pointer text-blue-300 underline"
>
Felix-its.uz
</a> </a>
</div> */} </div>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,12 +5,10 @@ import { ChevronDown, Phone, Menu, X } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import LanguageSelectRadix from "../languageSwitcher"; import LanguageSelectRadix from "../languageSwitcher";
import { useLocale, useTranslations } from "next-intl"; import { useLocale, useTranslations } from "next-intl";
import NavbarLogo from "./navbarLogo/navbarLogo";
export function Navbar() { export function Navbar() {
const locale = useLocale(); const locale = useLocale();
const t = useTranslations(); const t = useTranslations();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
@@ -42,7 +40,12 @@ export function Navbar() {
<Link href={`/${locale}/home`} className="hover:cursor-pointer"> <Link href={`/${locale}/home`} className="hover:cursor-pointer">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className=" flex items-center justify-center"> <div className=" flex items-center justify-center">
<Image src={'/images/IGNUM/PNG/1.@6x.png'} alt="logo image" width={80} height={80} /> <Image
src={"/images/IGNUM/PNG/1.@6x.png"}
alt="logo image"
width={80}
height={80}
/>
</div> </div>
</div> </div>
</Link> </Link>
@@ -103,8 +106,8 @@ export function Navbar() {
</span> </span>
<div> <div>
<div className="text-white text-sm font-bold"> <div className="text-white text-sm font-bold">
<div>+998-55-055-21-21</div>
<div>+998-77-372-21-21</div> <div>+998-77-372-21-21</div>
<div>+998-98-099-21-21</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -42,7 +42,7 @@ export function Contact() {
className="absolute inset-0" className="absolute inset-0"
style={{ style={{
background: background:
"radial-gradient(at center bottom, rgb(144 74 20) 0%, rgba(30, 29, 28, 0.914) 70%, rgba(30, 29, 28, 0.914) 70%)", "radial-gradient(at center bottom, rgb(144 74 20) 0%, rgba(30, 29, 28, 0.914) 50%, rgba(30, 29, 28, 0.914) 70%)",
}} }}
/> />
</div> </div>

View File

@@ -7,6 +7,7 @@ import CatalogCardSkeleton from "@/components/loadingSkleton";
import EmptyData from "@/components/EmptyData"; import EmptyData from "@/components/EmptyData";
import { getRouteLang } from "@/request/getLang"; import { getRouteLang } from "@/request/getLang";
import { CategoryType } from "@/lib/types"; import { CategoryType } from "@/lib/types";
import { useTranslations } from "next-intl";
export default function Catalog() { export default function Catalog() {
const language = getRouteLang(); const language = getRouteLang();
@@ -15,6 +16,7 @@ export default function Catalog() {
queryFn: () => httpClient(endPoints.category.all), queryFn: () => httpClient(endPoints.category.all),
select: (data): CategoryType[] => data?.data?.results, select: (data): CategoryType[] => data?.data?.results,
}); });
const t = useTranslations();
if (isLoading) { if (isLoading) {
return ( return (
@@ -30,8 +32,8 @@ export default function Catalog() {
if (!data || data.length === 0) { if (!data || data.length === 0) {
return ( return (
<EmptyData <EmptyData
title="Katalog topilmadi" title={t("products.noData.title")}
description="Hozircha kategoriyalar mavjud emas. Keyinroq qaytib keling." description={t("products.noData.description")}
icon="shopping" icon="shopping"
/> />
); );

View File

@@ -2,7 +2,7 @@ export { Banner } from "./banner";
export { Statistics } from "./statistics"; export { Statistics } from "./statistics";
export { AboutUs } from "./about"; export { AboutUs } from "./about";
export { Video } from "./video"; export { Video } from "./video";
export { OurService } from "./ourService"; export { OurService } from "../services/ourService";
export { Testimonial } from "./testimonal"; export { Testimonial } from "./testimonal";
export { Line } from "./line"; export { Line } from "./line";
export { Blog } from "./blog/blog"; export { Blog } from "./blog/blog";

View File

@@ -1,122 +0,0 @@
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import { ChevronRight } from "lucide-react";
import { useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
export function OurService() {
const t = useTranslations();
return (
<div className="bg-[#1e1d1c] py-10 md:py-16 lg:py-20 mb-30">
<div className="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */}
<div className="space-y-4 md:space-y-6">
<div className="font-almarai flex items-center justify-center gap-2 text-base sm:text-lg md:text-xl text-white font-bold">
<DotAnimatsiya />
{t("home.services.title")}
</div>
<h1 className="uppercase font-unbounded text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl tracking-wider lg:tracking-[5px] font-bold bg-linear-to-br from-white via-white to-gray-400 text-transparent bg-clip-text text-center w-full">
{t("home.services.subtitle")}
</h1>
<p className="font-almarai text-center text-sm sm:text-base md:text-lg text-gray-400 max-w-4xl mx-auto px-4">
{t("home.services.description")}
</p>
</div>
{/* cards */}
<div className="max-w-250 w-full mx-auto flex sm:flex-row flex-col items-center gap-5 my-10">
<div className="relative space-y-4 py-6 px-8 rounded-xl sm:w-[55%] w-full bg-[linear-gradient(to_bottom_right,#000000,#190b00,#542604,#8f4308)]">
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.operation.title")}
</p>
<p className="font-almarai text-gray-400 max-w-80 w-full">
{t("home.services.services.operation.description")}
</p>
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/gruop.png"
alt="images"
width={200}
height={100}
className="object-contain sm:absolute bottom-0 right-2 z-10"
/>
</div>
<div className="relative overflow-hidden space-y-4 py-6 px-8 rounded-xl sm:w-[45%] w-full bg-[linear-gradient(to_bottom_right,#000000,#190b00,#542604,#8f4308)]">
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.suppression.title")}
</p>
<p className="font-almarai text-gray-400 max-w-70 w-full">
{t("home.services.services.suppression.description")}
</p>
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/redShlang.png"
alt="images"
width={200}
height={100}
className="object-contain sm:absolute -bottom-4 -right-4 z-10"
/>
</div>
</div>
<div className="max-w-250 flex sm:flex-row flex-col items-start justify-between gap-5 mt-5 w-full mx-auto">
<div className="relative rounded-xl sm:w-[40%] w-full bg-[linear-gradient(to_bottom_right,#000000,#190b00,#542604,#8f4308)]">
<Image
src="/images/home/ambulance.png"
alt="images"
width={300}
height={200}
className="object-contain mt-5"
/>
<div className="space-y-4 py-6 px-8">
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.safety.title")}
</p>
<p className="font-almarai text-gray-400 max-w-80 w-full">
{t("home.services.services.safety.description")}
</p>
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
</div>
</div>
<div className="sm:w-[60%] w-full">
<div className="relative overflow-hidden space-y-4 py-6 px-8 rounded-xl w-full bg-[linear-gradient(to_bottom_right,#000000,#190b00,#542604,#8f4308)]">
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.monitoring.title")}
</p>
<p className="font-almarai text-gray-400 max-w-70 w-full">
{t("home.services.services.monitoring.description")}
</p>
<button className="font-almarai sm:mt-38 mt-0 text-[#dc2626] font-semibold flex items-center gap-2 text-sm">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/balon.png"
alt="images"
width={200}
height={100}
className="object-contain sm:absolute -bottom-20 -right-4 max-sm:-mb-20 z-10"
/>
</div>
<div className="py-8 px-8 rounded-xl mt-5 w-full p-5 bg-[linear-gradient(to_top_right,#000000,#190b00,#542604,#8f4308)] flex sm:flex-row flex-col gap-5 items-center justify-between">
<h2 className="font-unbounded sm:text-3xl text-xl font-semibold font-armanai text-white">
{t("home.services.viewMoreServices")}
</h2>
<Link
href="/services"
className="font-almarai shadow-[0px_0px_2px_6px_#a60404ad] bg-red-600 hover:bg-red-700 text-white font-bold sm:py-3 sm:px-8 px-8 py-2 rounded-full transition duration-300 transform hover:scale-105 w-fit"
>
{t("home.services.viewMore")}
</Link>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -91,7 +91,7 @@ export default function CatalogCard({
<Link <Link
href={navigateLink} href={navigateLink}
onClick={updateZustands} onClick={updateZustands}
className="group relative h-112.5 w-full overflow-hidden rounded-2xl bg-[#171616] from-[#2a2a2a] to-black border hover:border-red-700 border-white/10 transition-all duration-500 hover:-translate-y-1" className="group relative h-112.5 w-full overflow-hidden rounded-2xl bg-[#171616] bg-linear-to-br from-[#2a2a2a] to-black border hover:border-red-700 border-white/10 transition-all duration-500 hover:-translate-y-1"
> >
{/* Background glow effect */} {/* Background glow effect */}
<div className="absolute inset-0 bg-linear-to-t from-red-600/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" /> <div className="absolute inset-0 bg-linear-to-t from-red-600/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />

View File

@@ -0,0 +1,72 @@
import { useTranslations } from "next-intl";
import { motion } from "framer-motion";
// Empty State Component
export function EmptyServices() {
const t = useTranslations();
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="max-w-250 w-full mx-auto py-20"
>
<div className="bg-[#171616] bg-linear-to-br from-[#2a2a2a] to-black rounded-2xl p-10 md:p-16 text-center border border-gray-500">
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.2, type: "spring", stiffness: 200 }}
className="mb-8"
>
<div className="w-24 h-24 mx-auto bg-red-500/10 rounded-full flex items-center justify-center">
<svg
className="w-12 h-12 text-red-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
</div>
</motion.div>
<motion.h3
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="text-2xl md:text-3xl font-bold text-white font-unbounded mb-4"
>
{t("operationalSystems.noData.title") || "Xizmatlar topilmadi"}
</motion.h3>
<motion.p
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
className="text-gray-400 font-almarai text-base md:text-lg mb-8 max-w-md mx-auto"
>
{t("operationalSystems.noData.description") || "Hozircha hech qanday xizmat mavjud emas. Tez orada yangi xizmatlar qo'shiladi."}
</motion.p>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
>
<button
onClick={() => window.location.reload()}
className="font-almarai bg-red-600 hover:bg-red-700 text-white font-semibold py-3 px-8 rounded-full transition-all duration-300 transform hover:scale-105 shadow-lg hover:shadow-red-500/50"
>
{t("operationalSystems.noData.empty") || "Qayta yuklash"}
</button>
</motion.div>
</div>
</motion.div>
);
}

View File

@@ -0,0 +1,45 @@
import { motion } from "framer-motion";
// Loading Skeleton Component
function ServiceCardSkeleton() {
return (
<div className="animate-pulse space-y-4 py-6 px-8 rounded-xl bg-[#171616] bg-linear-to-br from-[#2a2a2a] to-black">
<div className="h-6 bg-[#171616] rounded w-3/4"></div>
<div className="h-4 bg-[#171616] rounded w-full"></div>
<div className="h-4 bg-[#171616] rounded w-5/6"></div>
<div className="h-8 bg-[#171616] rounded w-32"></div>
</div>
);
}
// Loading Component
export function ServicesLoading() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="max-w-250 w-full mx-auto space-y-5"
>
{/* Top Cards Skeleton */}
<div className="flex sm:flex-row flex-col items-center gap-5">
<div className="sm:w-[55%] w-full">
<ServiceCardSkeleton />
</div>
<div className="sm:w-[45%] w-full">
<ServiceCardSkeleton />
</div>
</div>
{/* Bottom Cards Skeleton */}
<div className="flex sm:flex-row flex-col items-start justify-between gap-5">
<div className="sm:w-[40%] w-full">
<ServiceCardSkeleton />
</div>
<div className="sm:w-[60%] w-full space-y-5">
<ServiceCardSkeleton />
<div className="h-24 bg-[#171616] rounded-xl animate-pulse"></div>
</div>
</div>
</motion.div>
);
}

View File

@@ -0,0 +1,206 @@
"use client"
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
import httpClient from "@/request/api";
import { endPoints } from "@/request/links";
import { useQuery } from "@tanstack/react-query";
import { ChevronRight } from "lucide-react";
import { useLocale, useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
import { motion } from "framer-motion";
import { ServicesLoading } from "./loading";
import { EmptyServices } from "./empty";
export function OurService() {
const t = useTranslations();
const locale = useLocale();
// get request
const { data, isLoading, isError } = useQuery({
queryKey: ['firesafety'],
queryFn: () => httpClient(endPoints.services.all)
});
console.log("service data: ", data);
// Animation variants
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const cardVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
ease: [0.22, 1, 0.36, 1] as const,
},
},
};
return (
<div className="bg-[#1e1d1c] py-10 md:py-16 lg:py-20 mb-30">
<div className="max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="space-y-4 md:space-y-6"
>
<div className="font-almarai flex items-center justify-center gap-2 text-base sm:text-lg md:text-xl text-white font-bold">
<DotAnimatsiya />
{t("home.services.title")}
</div>
<h1 className="uppercase font-unbounded text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl tracking-wider lg:tracking-[5px] font-bold bg-linear-to-br from-white via-white to-gray-400 text-transparent bg-clip-text text-center w-full">
{t("home.services.subtitle")}
</h1>
<p className="font-almarai text-center text-sm sm:text-base md:text-lg text-gray-400 max-w-4xl mx-auto px-4">
{t("home.services.description")}
</p>
</motion.div>
{/* Conditional Rendering */}
{isLoading ? (
<div className="my-10">
<ServicesLoading />
</div>
) : !data || (Array.isArray(data) && data.length === 0) ? (
<div className="my-10">
<EmptyServices />
</div>
) : (
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
>
{/* cards */}
<div className="max-w-250 w-full mx-auto flex sm:flex-row flex-col items-center gap-5 my-10">
<motion.div variants={cardVariants} className="sm:w-[55%] w-full">
<Link
href={`/${locale}/services/detail`}
className="block hover:cursor-pointer relative space-y-4 py-6 px-8 rounded-xl w-full bg-[linear-gradient(to_bottom_right,#000000,#190b00,#542604,#8f4308)] hover:shadow-2xl hover:shadow-red-500/20 transition-all duration-300"
>
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.operation.title")}
</p>
<p className="font-almarai text-gray-400 max-w-80 w-full">
{t("home.services.services.operation.description")}
</p>
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm hover:gap-3 transition-all">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/gruop.png"
alt="images"
width={200}
height={100}
className="object-contain sm:absolute bottom-0 right-2 z-10"
/>
</Link>
</motion.div>
<motion.div variants={cardVariants} className="sm:w-[45%] w-full">
<div className="hover:cursor-pointer relative overflow-hidden space-y-4 py-6 px-8 rounded-xl w-full bg-[linear-gradient(to_bottom_right,#000000,#190b00,#542604,#8f4308)] hover:shadow-2xl hover:shadow-red-500/20 transition-all duration-300">
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.suppression.title")}
</p>
<p className="font-almarai text-gray-400 max-w-70 w-full">
{t("home.services.services.suppression.description")}
</p>
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm hover:gap-3 transition-all">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/redShlang.png"
alt="images"
width={200}
height={100}
className="object-contain sm:absolute -bottom-4 -right-4 z-10"
/>
</div>
</motion.div>
</div>
<div className="max-w-250 flex sm:flex-row flex-col items-start justify-between gap-5 mt- w-full mx-auto">
<motion.div variants={cardVariants} className="sm:w-[40%] w-full -mt-5">
<Link
href={`/${locale}/services/detail`}
className="block hover:cursor-pointer relative rounded-xl w-full bg-[linear-gradient(to_bottom_right,#000000,#190b00,#542604,#8f4308)] hover:shadow-2xl hover:shadow-red-500/20 transition-all duration-300"
>
<Image
src="/images/home/ambulance.png"
alt="images"
width={300}
height={200}
className="object-contain mt-5"
/>
<div className="space-y-4 py-6 px-8">
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.safety.title")}
</p>
<p className="font-almarai text-gray-400 max-w-80 w-full">
{t("home.services.services.safety.description")}
</p>
<button className="font-almarai text-[#dc2626] font-semibold flex items-center gap-2 text-sm hover:gap-3 transition-all">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
</div>
</Link>
</motion.div>
<div className="sm:w-[60%] w-full">
<motion.div variants={cardVariants}>
<Link href={`/${locale}/services/detail`}>
<div className="hover:cursor-pointer relative overflow-hidden space-y-4 py-6 px-8 rounded-xl w-full bg-[linear-gradient(to_bottom_right,#000000,#190b00,#542604,#8f4308)] hover:shadow-2xl hover:shadow-red-500/20 transition-all duration-300">
<p className="uppercase font-unbounded font-bold bg-linear-to-br from-white via-white to-black text-transparent bg-clip-text">
{t("home.services.services.monitoring.title")}
</p>
<p className="font-almarai text-gray-400 max-w-70 w-full">
{t("home.services.services.monitoring.description")}
</p>
<button className="font-almarai sm:mt-38 mt-0 text-[#dc2626] font-semibold flex items-center gap-2 text-sm hover:gap-3 transition-all">
{t("home.services.learnmore")} <ChevronRight size={20} />
</button>
<Image
src="/images/home/balon.png"
alt="images"
width={200}
height={100}
className="object-contain sm:absolute -bottom-20 -right-4 max-sm:-mb-20 z-10"
/>
</div>
</Link>
</motion.div>
<motion.div
variants={cardVariants}
className="py-6 px-8 rounded-xl mt-5 w-full p-5 bg-[linear-gradient(to_top_right,#000000,#190b00,#542604,#8f4308)] flex sm:flex-row flex-col gap-5 items-center justify-between hover:shadow-2xl hover:shadow-red-500/20 transition-all duration-300"
>
<h2 className="font-unbounded sm:text-3xl text-xl font-semibold font-armanai text-white">
{t("home.services.viewMoreServices")}
</h2>
<Link
href="/services"
className="font-almarai shadow-[0px_0px_2px_6px_#a60404ad] bg-red-600 hover:bg-red-700 text-white font-bold sm:py-3 sm:px-8 px-8 py-2 rounded-full transition duration-300 transform hover:scale-105 w-fit"
>
{t("home.services.viewMore")}
</Link>
</motion.div>
</div>
</div>
</motion.div>
)}
</div>
</div>
);
}

View File

@@ -12,7 +12,7 @@ import { toast } from "react-toastify";
interface FormType { interface FormType {
name: string; name: string;
product: number; product: number;
phone: number; // ✅ String bo'lishi kerak number: number; // ✅ String bo'lishi kerak
} }
export function PriceModal() { export function PriceModal() {
@@ -21,12 +21,12 @@ export function PriceModal() {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: "", name: "",
phone: "+998 ", number: "+998 ",
}); });
const [errors, setErrors] = useState({ const [errors, setErrors] = useState({
name: "", name: "",
phone: "", number: "",
}); });
const formRequest = useMutation({ const formRequest = useMutation({
@@ -35,7 +35,7 @@ export function PriceModal() {
onSuccess: () => { onSuccess: () => {
setFormData({ setFormData({
name: "", name: "",
phone: "+998 ", number: "+998 ",
}); });
toast.success(t("success") || "Muvaffaqiyatli yuborildi!"); toast.success(t("success") || "Muvaffaqiyatli yuborildi!");
closeModal(); closeModal();
@@ -51,11 +51,11 @@ export function PriceModal() {
if (!isOpen) { if (!isOpen) {
setFormData({ setFormData({
name: "", name: "",
phone: "+998 ", number: "+998 ",
}); });
setErrors({ setErrors({
name: "", name: "",
phone: "", number: '',
}); });
} }
}, [isOpen]); }, [isOpen]);
@@ -90,9 +90,9 @@ export function PriceModal() {
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const formatted = formatPhoneNumber(e.target.value); const formatted = formatPhoneNumber(e.target.value);
setFormData({ ...formData, phone: formatted }); setFormData({ ...formData, number: formatted });
if (errors.phone) { if (errors.number) {
setErrors({ ...errors, phone: "" }); setErrors({ ...errors, number: "" });
} }
}; };
@@ -107,7 +107,7 @@ export function PriceModal() {
const validateForm = () => { const validateForm = () => {
const newErrors = { const newErrors = {
name: "", name: "",
phone: "", number: "",
}; };
// Name validation // Name validation
@@ -116,17 +116,17 @@ export function PriceModal() {
} }
// Phone validation // Phone validation
const phoneNumbers = formData.phone.replace(/\D/g, ""); const phoneNumbers = formData.number.replace(/\D/g, "");
if (phoneNumbers.length !== 12) { if (phoneNumbers.length !== 12) {
newErrors.phone = newErrors.number =
t("validation.phoneRequired") || "To'liq telefon raqam kiriting"; t("validation.phoneRequired") || "To'liq telefon raqam kiriting";
} else if (!phoneNumbers.startsWith("998")) { } else if (!phoneNumbers.startsWith("998")) {
newErrors.phone = newErrors.number =
t("validation.phoneInvalid") || "Noto'g'ri telefon raqam"; t("validation.phoneInvalid") || "Noto'g'ri telefon raqam";
} }
setErrors(newErrors); setErrors(newErrors);
return !newErrors.name && !newErrors.phone; return !newErrors.name && !newErrors.number;
}; };
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
@@ -137,11 +137,11 @@ export function PriceModal() {
} }
// Telefon raqamni tozalash (faqat raqamlar) // Telefon raqamni tozalash (faqat raqamlar)
const cleanPhone = formData.phone.replace(/\D/g, ""); const cleanPhone = formData.number.replace(/\D/g, "");
const sendedData: FormType = { const sendedData: FormType = {
name: formData.name, name: formData.name,
phone: Number(cleanPhone), // ✅ String sifatida yuborish number: Number(cleanPhone.slice(3)), // ✅ String sifatida yuborish
product: product?.id || 0, product: product?.id || 0,
}; };
@@ -236,16 +236,16 @@ export function PriceModal() {
type="tel" type="tel"
id="phone" id="phone"
name="phone" name="phone"
value={formData.phone} value={formData.number}
onChange={handlePhoneChange} onChange={handlePhoneChange}
className={`w-full px-4 py-3 bg-[#1e1e1e] border ${ className={`w-full px-4 py-3 bg-[#1e1e1e] border ${
errors.phone ? "border-red-500" : "border-gray-700" errors.number ? "border-red-500" : "border-gray-700"
} rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-red-700 transition`} } rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-red-700 transition`}
placeholder="+998 90 123 45 67" placeholder="+998 90 123 45 67"
maxLength={17} maxLength={17}
/> />
{errors.phone && ( {errors.number && (
<p className="mt-1 text-sm text-red-500">{errors.phone}</p> <p className="mt-1 text-sm text-red-500">{errors.number}</p>
)} )}
</div> </div>

View File

@@ -0,0 +1,27 @@
import axios from 'axios';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com';
export interface SystemFeature {
id: string;
title: string;
shortDesc?:string;
description: string;
features: string[];
image: string;
}
export const getOperationalSystems = async (): Promise<SystemFeature[]> => {
try {
const response = await axios.get(`${API_BASE_URL}/operational-systems`, {
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
return response.data;
} catch (error) {
console.error('Error fetching operational systems:', error);
throw error;
}
};

View File

@@ -146,6 +146,10 @@
} }
}, },
"products": { "products": {
"noData": {
"title": "Catalog Not Found",
"description": "There are currently no categories available. Please check back later."
},
"catalog": { "catalog": {
"blockdescription": "Polypropylene pipes and fittings for automatic fire suppression systems and internal fire water supply", "blockdescription": "Polypropylene pipes and fittings for automatic fire suppression systems and internal fire water supply",
"cadescription": "Equipment for automatic fire suppression", "cadescription": "Equipment for automatic fire suppression",
@@ -223,7 +227,55 @@
"help": "Help" "help": "Help"
}, },
"address": "Tashkent city, Yunusabad district, 3rd dead-end of Niyozbek Yoli street, house 39", "address": "Tashkent city, Yunusabad district, 3rd dead-end of Niyozbek Yoli street, house 39",
"create": "Created by {name}" "create": "Created by {name}",
"terms": "Terms & Conditions",
"privacy": "Privacy Policy"
},
"operationalSystems": {
"title": "Operating Systems",
"subtitle": "Automatic fire detection and extinguishing systems. Latest technological achievements.",
"loading": "Loading...",
"error": "An error occurred. Please try again.",
"noData": {
"empty": "No data found",
"title": "No Services Found",
"description": "There are currently no services available. New services will be added soon."
},
"retry": "Retry",
"features": "Features",
"systems": {
"sprinkler": {
"title": "Sprinkler Fire Suppression System",
"short-desc": "Automatic fire detection and extinguishing systems. The latest achievements in technology.",
"description": "The sprinkler fire suppression system controls and extinguishes fires at an early stage through automatic water spraying. The system activates automatically when temperature rises and operates only in the fire detection area.",
"features": [
"Automatic activation mechanism",
"Covers only the affected area with water",
"Suitable for industrial and commercial buildings",
"Reliable and widely used system"
]
},
"gas": {
"title": "Gas Fire Suppression System",
"description": "The gas fire suppression system extinguishes fires using special inert or chemical gases. This system is used in places where water cannot be used — server rooms, data centers, and areas with electrical equipment.",
"features": [
"Does not damage electrical equipment",
"Fast and effective suppression",
"Leaves no residue",
"Ideal for server and IT rooms"
]
},
"foam": {
"title": "Foam Fire Suppression System",
"description": "The foam fire suppression system effectively extinguishes fires involving flammable liquids and petroleum products. The foam covers the combustible material, blocks oxygen access, and quickly suppresses the flames.",
"features": [
"Effective for flammable liquids",
"Used in oil depots and warehouses",
"Quickly isolates fire",
"High safety level"
]
}
}
}, },
"rasmlar": "Images", "rasmlar": "Images",
"fotogalereya": "Photo Gallery", "fotogalereya": "Photo Gallery",

View File

@@ -146,6 +146,10 @@
} }
}, },
"products": { "products": {
"noData": {
"title": "Каталог не найден",
"description": "В настоящее время категории отсутствуют. Пожалуйста, зайдите позже."
},
"catalog": { "catalog": {
"blockdescription": "Полипропиленовые трубы и фитинги для систем автоматического пожаротушения и внутреннего противопожарного водопровода", "blockdescription": "Полипропиленовые трубы и фитинги для систем автоматического пожаротушения и внутреннего противопожарного водопровода",
"cadescription": "Оборудование для автоматического пожаротушения", "cadescription": "Оборудование для автоматического пожаротушения",
@@ -223,7 +227,55 @@
"help": "Помощь" "help": "Помощь"
}, },
"address": "г. Ташкент, Юнусабадский район, 3-й тупик улицы Ниязбек йўли, дом 39", "address": "г. Ташкент, Юнусабадский район, 3-й тупик улицы Ниязбек йўли, дом 39",
"create": "Разработано {name}" "create": "Разработано",
"terms": "Условия использования",
"privacy": "Политика конфиденциальности"
},
"operationalSystems": {
"title": "Операционные системы",
"subtitle": "Системы автоматического обнаружения и тушения пожаров. Последние достижения технологий.",
"loading": "Загрузка...",
"error": "Произошла ошибка. Пожалуйста, попробуйте снова.",
"noData": {
"empty": "Данные не найдены",
"title": "Услуги не найдены",
"description": "В настоящее время нет доступных услуг. Новые услуги будут добавлены в ближайшее время."
},
"retry": "Повторить",
"features": "Характеристики",
"systems": {
"sprinkler": {
"title": "Спринклерная система пожаротушения",
"short-desc": "Системы автоматического обнаружения и тушения пожаров. Новейшие достижения технологий.",
"description": "Спринклерная система пожаротушения контролирует и тушит пожар на начальной стадии путем автоматического распыления воды. Система автоматически активируется при повышении температуры и работает только в зоне обнаружения пожара.",
"features": [
"Автоматический механизм активации",
"Покрывает водой только поврежденную зону",
"Подходит для промышленных и торговых зданий",
"Надежная и широко применяемая система"
]
},
"gas": {
"title": "Газовая система пожаротушения",
"description": "Газовая система пожаротушения тушит пожар с помощью специальных инертных или химических газов. Эта система применяется в местах, где нельзя использовать воду — серверные комнаты, дата-центры и помещения с электрооборудованием.",
"features": [
"Не наносит вреда электрооборудованию",
"Быстрое и эффективное тушение",
"Не оставляет остатков",
"Идеально для серверных и IT помещений"
]
},
"foam": {
"title": "Пенная система пожаротушения",
"description": "Пенная система пожаротушения эффективно тушит пожары, связанные с горючими жидкостями и нефтепродуктами. Пена покрывает горючее вещество, перекрывает доступ кислорода и быстро подавляет огонь.",
"features": [
"Эффективна для горючих жидкостей",
"Применяется на нефтебазах и складах",
"Быстро изолирует пожар",
"Высокий уровень безопасности"
]
}
}
}, },
"rasmlar": "Изображения", "rasmlar": "Изображения",
"fotogalereya": "Фотогалерея", "fotogalereya": "Фотогалерея",

View File

@@ -146,6 +146,10 @@
} }
}, },
"products": { "products": {
"noData":{
"title":"Katalog topilmadi",
"description":"Hozircha kategoriyalar mavjud emas. Keyinroq urinib ko'ring"
},
"catalog": { "catalog": {
"blockdescription": "Avtomatik yongin ochirish tizimlari va ichki yonginga qarshi suv taminoti uchun polipropilen quvurlar va fitinglar", "blockdescription": "Avtomatik yongin ochirish tizimlari va ichki yonginga qarshi suv taminoti uchun polipropilen quvurlar va fitinglar",
"cadescription": "Avtomatik yongin ochirish uchun uskunalar", "cadescription": "Avtomatik yongin ochirish uchun uskunalar",
@@ -223,7 +227,55 @@
"help": "Yordam" "help": "Yordam"
}, },
"address": "Toshkent shahri , Yunusabod tumani , Niyozbek yo'li 3 tor ko'chasi , 39 uy", "address": "Toshkent shahri , Yunusabod tumani , Niyozbek yo'li 3 tor ko'chasi , 39 uy",
"create": "{name} - Jamoasi tomonidan ishlab chiqilgan" "create": "{name} - Jamoasi tomonidan ishlab chiqilgan",
"terms":"Foydalanish shartlari",
"privacy":"Maxfiylik siyosati"
},
"operationalSystems": {
"title": "Operatsion tizimlar",
"subtitle": "Yong'inni avtomatik aniqlash va o'chirish tizimlari. Texnologiyaning eng so'nggi yutuqlari.",
"loading": "Yuklanmoqda...",
"error": "Xatolik yuz berdi. Iltimos, qaytadan urinib ko'ring.",
"noData": {
"empty": "Ma'lumotlar topilmadi",
"title": "Xizmatlar topilmadi",
"description": "Hozircha hech qanday xizmat mavjud emas. Tez orada yangi xizmatlar qo'shiladi."
},
"retry": "Qayta urinish",
"features": "Hususiyatlari",
"systems": {
"sprinkler": {
"title": "Sprinklerli yong'in o'chirish tizimi",
"short-desc": "Yong'inni avtomatik aniqlash va o'chirish tizimlari. Texnologiyaning eng so'nggi yutuqlari.",
"description": "Sprinklerli yong'in o'chirish tizimi avtomatik suv purkash orqali yong'inni dastlabki bosqichida nazorat qiladi va o'chiradi. Tizim harorat oshganda avtomatik ishga tushadi va faqat yong'in aniqlangan hududda faoliyat ko'rsatadi.",
"features": [
"Avtomatik ishga tushish mexanizmi",
"Faqat zararlangan hududni suv bilan qoplaydi",
"Sanoat va savdo binolari uchun mos",
"Ishonchli va keng qo'llaniladigan tizim"
]
},
"gas": {
"title": "Gazli yong'in o'chirish tizimi",
"description": "Gazli yong'in o'chirish tizimi maxsus inert yoki kimyoviy gazlar yordamida yong'inni o'chiradi. Bu tizim suv ishlatish mumkin bo'lmagan joylarda — server xonalari, data markazlar va elektr jihozlari mavjud hududlarda qo'llaniladi.",
"features": [
"Elektr jihozlariga zarar yetkazmaydi",
"Tez va samarali o'chirish",
"Qoldiq modda qoldirmaydi",
"Server va IT xonalari uchun ideal"
]
},
"foam": {
"title": "Ko'pikli yong'in o'chirish tizimi",
"description": "Ko'pikli yong'in o'chirish tizimi yonuvchi suyuqliklar va neft mahsulotlari bilan bog'liq yong'inlarni samarali o'chiradi. Ko'pik yonuvchi modda ustini qoplab, kislorod kirishini to'sadi va olovni tezda bostiradi.",
"features": [
"Yonuvchi suyuqliklar uchun samarali",
"Neft bazalari va omborlarda qo'llaniladi",
"Yong'inni tez izolyatsiya qiladi",
"Yuqori xavfsizlik darajasi"
]
}
}
}, },
"rasmlar": "Rasmlar", "rasmlar": "Rasmlar",
"fotogalereya": "Fotogalereya", "fotogalereya": "Fotogalereya",
@@ -262,7 +314,7 @@
"about": "Biz haqimizda", "about": "Biz haqimizda",
"services": "Xizmatlar", "services": "Xizmatlar",
"catalog_page": "Mahsulotlar", "catalog_page": "Mahsulotlar",
"subCategory":"{subCategory}", "subCategory": "{subCategory}",
"contact": "Bog'lanish", "contact": "Bog'lanish",
"blog": "Blog", "blog": "Blog",
"fire-safety": "Yong'in xavfsizligi", "fire-safety": "Yong'in xavfsizligi",
@@ -271,9 +323,9 @@
"installation": "O'rnatish", "installation": "O'rnatish",
"maintenance": "Texnik xizmat" "maintenance": "Texnik xizmat"
}, },
"filter":{ "filter": {
"category":"Kategoriyalar", "category": "Kategoriyalar",
"catalog":"Bo'lim", "catalog": "Bo'lim",
"size":"O'lchamlar" "size": "O'lchamlar"
} }
} }

View File

@@ -6,7 +6,7 @@
"build": "next build", "build": "next build",
"dev": "next dev", "dev": "next dev",
"lint": "eslint .", "lint": "eslint .",
"start": "next start" "start": "next start -p 3909"
}, },
"dependencies": { "dependencies": {
"@formatjs/intl-localematcher": "^0.8.0", "@formatjs/intl-localematcher": "^0.8.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -5,6 +5,10 @@ export const endPoints = {
subCategory: { subCategory: {
byId: (id: number) => `subCategory/?category=${id}`, byId: (id: number) => `subCategory/?category=${id}`,
}, },
services: {
all: "firesafety/",
detail: (id: number) => `/api/firesafety/${id}`,
},
product: { product: {
byCategory: (categoryId: number) => `product/?category=${categoryId}`, byCategory: (categoryId: number) => `product/?category=${categoryId}`,
bySubCategory: (subCategoryId: number) => bySubCategory: (subCategoryId: number) =>
@@ -18,10 +22,10 @@ export const endPoints = {
filter: { filter: {
size: "size/", size: "size/",
sizePageItems: "size/?page_size=500", sizePageItems: "size/?page_size=500",
sizeCategoryId:(id:number)=>`size/?category=${id}&page_size=500`, sizeCategoryId: (id: number) => `size/?category=${id}&page_size=500`,
catalog: "catalog/", catalog: "catalog/",
catalogPageItems: "catalog/?page_size=500", catalogPageItems: "catalog/?page_size=500",
catalogCategoryId:(id:number)=>`catalog/?category=${id}&page_size=500`, catalogCategoryId: (id: number) => `catalog/?category=${id}&page_size=500`,
}, },
post: { post: {
sendNumber: "callBack/", sendNumber: "callBack/",

14
store/useService.ts Normal file
View File

@@ -0,0 +1,14 @@
import { create } from "zustand";
interface ServiceIdZustandType {
serviceId: number;
setServiceId: (serviceId: number) => void;
}
export const useServiceDetail = create<ServiceIdZustandType>((set) => ({
serviceId: 0,
setServiceId: (data: number) =>
set({
serviceId: data,
}),
}));