From c5d3b02737f78485bc9067f685afa998b3233dbb Mon Sep 17 00:00:00 2001 From: "nabijonovdavronbek619@gmail.com" Date: Wed, 24 Dec 2025 14:17:04 +0500 Subject: [PATCH] added backend --- components/productSection/ProductCard.tsx | 80 ++++++++++++---------- components/productSection/ProductModal.tsx | 64 +++++++++-------- components/productSection/ProductsGrid.tsx | 39 +++++------ components/productsPage/emptyData.tsx | 4 +- components/productsPage/products.tsx | 25 ++++--- lib/allProducts.ts | 14 ---- lib/products.ts | 73 ++++---------------- lib/translations.ts | 2 + locales/ru.json | 3 +- locales/uz.json | 3 +- next.config.ts | 21 ++++-- 11 files changed, 149 insertions(+), 179 deletions(-) delete mode 100644 lib/allProducts.ts diff --git a/components/productSection/ProductCard.tsx b/components/productSection/ProductCard.tsx index 75d344f..baf1ae2 100644 --- a/components/productSection/ProductCard.tsx +++ b/components/productSection/ProductCard.tsx @@ -3,62 +3,68 @@ import Image from "next/image"; import { motion } from "framer-motion"; import { ExternalLink } from "lucide-react"; -import type { Product } from "@/lib/products"; +import { type Product } from "@/lib/products"; import { useLanguage } from "@/context/language-context"; interface ProductCardProps { product: Product; - onViewDetails: (slug: string) => void; + onViewDetails: (slug: number) => void; } export function ProductCard({ product, onViewDetails }: ProductCardProps) { - const {t} = useLanguage(); + const { t, language } = useLanguage(); + const languageIndex = language === "uz" ? true : false; return ( - {/* Image */} -
- {product.nameKey} -
+ {/* Image Container - Fixed Height */} +
+ + {languageIndex?product.name_uz:product.name_ru} + + + {/* Gradient Overlay */} +
- {/* Content */} -
-

- {product.nameKey} + {/* Content Container - Flex Grow */} +
+ {/* Product Name */} +

+ {languageIndex?product.name_uz:product.name_ru}

-

- {product.shortDescriptionKey} + + {/* Short Description - Fixed Height with Line Clamp */} +

+ {languageIndex?product.name_uz:product.name_ru}

- {/* Specs Preview */} - {/*
- {product.specs.slice(0, 2).map((spec, idx) => ( -
- {spec.key}: - {spec.value} -
- ))} -
*/} - - {/* CTA Button */} + {/* CTA Button - Always at Bottom */} onViewDetails(product.slug)} - className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-primary/80 text-white rounded-lg font-medium hover:bg-primary transition-colors" + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + onClick={() => onViewDetails(product.id)} + className="w-full flex items-center justify-center gap-2 px-6 py-3 bg-primary text-white rounded-xl font-semibold shadow-lg shadow-blue-500/30 hover:shadow-xl hover:shadow-primary/40 transition-all duration-300 group/button" > - {t.details} - + {t.details} +
+ + {/* Decorative Corner */} +
); } diff --git a/components/productSection/ProductModal.tsx b/components/productSection/ProductModal.tsx index e768c08..59dd8cf 100644 --- a/components/productSection/ProductModal.tsx +++ b/components/productSection/ProductModal.tsx @@ -2,11 +2,12 @@ import { motion, AnimatePresence } from "framer-motion"; import { X } from "lucide-react"; -import { ProductViewer } from "./ProductViewer"; import type { Product } from "@/lib/products"; import { useLanguage } from "@/context/language-context"; import Link from "next/link"; import { useProductStore } from "@/lib/productZustand"; +import Image from "next/image"; +import { usePathname } from "next/navigation"; interface ProductModalProps { product: Product; @@ -14,8 +15,12 @@ interface ProductModalProps { } export function ProductModal({ product, onClose }: ProductModalProps) { - const { t } = useLanguage(); + const { t, language } = useLanguage(); const setProductName = useProductStore((state) => state.setProductName); + const languageIndex = language === "uz" ? true : false; + const pathName = usePathname(); + const isPathName = pathName === "/product"; + return ( @@ -34,9 +39,9 @@ export function ProductModal({ product, onClose }: ProductModalProps) { className="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto" > {/* Header */} -
-

- {product.nameKey} +
+

+ {languageIndex ? product.name_uz : product.name_ru}

{/* Content */} -
+
- {/* 3D Viewer / Gallery */} -
- + image
- {/* Details */} + {/* Specifications */}
-

- {product.longDescriptionKey - ? product.longDescriptionKey - : product.shortDescriptionKey} -

- - {/* Specifications */}

- Technical Specifications + {t.features}

- {product.specs.map((spec, idx) => ( + {product.features.map((spec, idx) => (
- {spec.key}: - - {spec.value} + + {languageIndex ? spec.key_uz : spec.key_ru}: + + + {languageIndex ? spec.value_uz : spec.value_ru}
))} @@ -90,15 +91,17 @@ export function ProductModal({ product, onClose }: ProductModalProps) { {/* CTA Buttons */}
- + { onClose(); - setProductName(product.nameKey); + setProductName( + languageIndex ? product.name_uz : product.name_ru + ); }} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} - className="w-full px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 transition-colors" + className="w-full px-6 py-3 bg-primary/80 text-white rounded-lg font-semibold hover:bg-primary transition-colors" > {t.contact.send} @@ -106,6 +109,9 @@ export function ProductModal({ product, onClose }: ProductModalProps) {
+
+ {languageIndex ? product.description_uz : product.description_ru} +
diff --git a/components/productSection/ProductsGrid.tsx b/components/productSection/ProductsGrid.tsx index 3262ad3..46d23ad 100644 --- a/components/productSection/ProductsGrid.tsx +++ b/components/productSection/ProductsGrid.tsx @@ -1,8 +1,7 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { motion } from "framer-motion"; -import { getAllProducts } from "@/lib/products"; import type { Product } from "@/lib/products"; import Image from "next/image"; import { useLanguage } from "@/context/language-context"; @@ -10,34 +9,33 @@ import Link from "next/link"; import { ChevronsRight } from "lucide-react"; import { ProductCard } from "./ProductCard"; import { ProductModal } from "./ProductModal"; +import axios from "axios"; // hello everyone export function ProductsGrid() { const { t } = useLanguage(); - const products = getAllProducts(); const [selectedProduct, setSelectedProduct] = useState(null); + const [allProducts, setAllProducts] = useState([]); - const handleViewDetails = (slug: string) => { - const product = products.find((p) => p.slug === slug); + useEffect(() => { + async function getData() { + await axios.get("https://api.serenmebel.uz/api/products/").then((res) => { + console.log("all data main page: ", res?.data); + const allData = res?.data || []; + setAllProducts(allData.slice(0,3)); + }); + } + getData(); + }, []); + + const handleViewDetails = (slug: number) => { + const product = allProducts.find((p: any) => p.id === slug); if (product) { setSelectedProduct(product); } }; - const containerVariants = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - transition: { staggerChildren: 0.1 }, - }, - }; - - const itemVariants = { - hidden: { opacity: 0, y: 20 }, - visible: { opacity: 1, y: 0 }, - }; - return ( <>
@@ -67,14 +65,13 @@ export function ProductsGrid() { {/* Product Grid */} - {products.map((product) => ( - + {allProducts.map((product: any) => ( +
- {t.empty_data.back || "Bosh sahifa"} - +
diff --git a/components/productsPage/products.tsx b/components/productsPage/products.tsx index 7a02310..e50db7c 100644 --- a/components/productsPage/products.tsx +++ b/components/productsPage/products.tsx @@ -1,10 +1,10 @@ "use client"; import { useEffect, useState } from "react"; import EmptyState from "./emptyData"; -import { GET } from "@/lib/allProducts"; import { ProductModal } from "../productSection/ProductModal"; import { motion } from "framer-motion"; import { ProductCard } from "../productSection/ProductCard"; +import axios from "axios"; const itemVariants = { hidden: { opacity: 0, y: 20 }, @@ -14,21 +14,26 @@ const itemVariants = { export default function Products() { const [allProducts, setAllProducts] = useState(null); const [selectedProduct, setSelectedProduct] = useState(null); - const all = GET(); + useEffect(() => { - all && Array.isArray(all) && all.length > 0 - ? setAllProducts(all) - : setAllProducts([]); - setAllProducts; - }, [all]); - const handleViewDetails = (slug: string) => { - const product = allProducts.find((p: any) => p.slug === slug); + async function getData() { + await axios.get("https://api.serenmebel.uz/api/products/").then((res) => { + console.log("all data: ", res?.data); + const allData = res?.data || []; + setAllProducts(allData); + }); + } + getData(); + }, []); + + const handleViewDetails = (id: number) => { + const product = allProducts.find((p: any) => p.id === id); if (product) { setSelectedProduct(product); } }; return ( -
+
{allProducts && allProducts.length > 0 ? (
{allProducts.map((product: any) => ( diff --git a/lib/allProducts.ts b/lib/allProducts.ts deleted file mode 100644 index 51864da..0000000 --- a/lib/allProducts.ts +++ /dev/null @@ -1,14 +0,0 @@ -import axios from "axios"; - -export async function GET() { - try { - const res = await axios.get("https://api.serenmebel.uz/api/products/"); - console.log("all products res: ", res?.data); - - return Response.json(res.data); - } catch (error: any) { - console.log("all products error: ", error); - - return Response.json({ error: error.message }); - } -} diff --git a/lib/products.ts b/lib/products.ts index 0ccccee..0480aee 100644 --- a/lib/products.ts +++ b/lib/products.ts @@ -1,61 +1,16 @@ +interface features { + key_uz: string; + key_ru: string; + value_uz: string; + value_ru: string; +} + export interface Product { - id: string; - nameKey: string; - slug: string; - shortDescriptionKey: string; - longDescriptionKey?: string; - images: string[]; - model3D?: string; - specs: { key: string; value: string }[]; -} - -// Sample products data -export const products: Product[] = [ - { - id: "1", - nameKey: "Schotchik Nasos", - slug: "Yuqori sifatli schotchik nasos, benzin, dizel va kerosinni tashishda ishlatiladi.", - shortDescriptionKey: "Xavfsiz neft mahsulotlarini tashish uchun", - images: ["/product/product.jpg", "/images/pump-1-alt.jpg"], - specs: [ - { key: "Flow Rate", value: "100 L/min" }, - { key: "Pressure", value: "10 bar" }, - { key: "Power", value: "5.5 kW" }, - { key: "Temperature Range", value: "-10°C to 60°C" }, - ], - }, - { - id: "2", - nameKey: "Agregat Nasos", - slug: "Katta volumli neft mahsulotlarini tashishda o'rnatilgan.", - shortDescriptionKey: "Kuchli va ishonchli aggregat nasos", - images: ["/product/product1.jpg", "/images/pump-2-alt.jpg"], - specs: [ - { key: "Flow Rate", value: "250 L/min" }, - { key: "Pressure", value: "15 bar" }, - { key: "Power", value: "11 kW" }, - { key: "Temperature Range", value: "-10°C to 70°C" }, - ], - }, - { - id: "3", - nameKey: "СЦЛ 20/24", - slug:"Chuqurligi 20-24 metrda ishlaydigan professional nasos.", - shortDescriptionKey: "Professional kalibrli nasos", - images: ["/product/product2.jpg", "/images/pump-3-alt.jpg"], - specs: [ - { key: "Depth Rating", value: "20-24 m" }, - { key: "Flow Rate", value: "150 L/min" }, - { key: "Suction Lift", value: "7 m" }, - { key: "Power", value: "7.5 kW" }, - ], - }, -]; - -export function getProductBySlug(slug: string): Product | undefined { - return products.find((p) => p.slug === slug); -} - -export function getAllProducts(): Product[] { - return products; + id: number; + name_uz: string; + name_ru: string; + description_uz: string; + description_ru: string; + features:features[]; + image:string; } diff --git a/lib/translations.ts b/lib/translations.ts index 80528c8..89c6342 100644 --- a/lib/translations.ts +++ b/lib/translations.ts @@ -92,6 +92,7 @@ export const translations = { }, more: "Ko'proq ko'rish", details: "Batafsil", + features: "Texnik tavsiflar", empty_data: { description: "Mahsulot topilmadi!!!", back: "Asosiy sahifaga qaytish", @@ -195,6 +196,7 @@ export const translations = { }, more: "Смотреть больше", details: "Подробнее", + features: "Технические характеристики", empty_data: { description: "Товар не найден!!!", back: "Вернуться на главную страницу", diff --git a/locales/ru.json b/locales/ru.json index 3ae8863..e5971d9 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -16,7 +16,8 @@ }, "products": { "title": "Продукты", - "viewDetails": "Подробнее" + "viewDetails": "Подробнее", + "features": "Технические характеристики" }, "faq": { "title": "Часто Задаваемые Вопросы", diff --git a/locales/uz.json b/locales/uz.json index 4638520..f4ff788 100644 --- a/locales/uz.json +++ b/locales/uz.json @@ -16,7 +16,8 @@ }, "products": { "title": "Mahsulotlar", - "viewDetails": "Batafsil" + "viewDetails": "Batafsil", + "features": "Texnik tavsiflar" }, "faq": { "title": "Tez-tez So'raladigan Savollar", diff --git a/next.config.ts b/next.config.ts index e9b9bb0..5b906bf 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,18 @@ +import type { NextConfig } from "next"; +import createNextIntlPlugin from "next-intl/plugin"; + +const nextConfig: NextConfig = { + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "api.serenmebel.uz", + pathname: "/resources/media/**", + }, + ], + }, +}; -import createNextIntlPlugin from 'next-intl/plugin'; - -const nextConfig = {}; - const withNextIntl = createNextIntlPlugin(); -export default withNextIntl(nextConfig); \ No newline at end of file + +export default withNextIntl(nextConfig);