connected to backend
This commit is contained in:
@@ -5,9 +5,11 @@ import Image from "next/image";
|
||||
import { useParams } from "next/navigation";
|
||||
import Text from "../lib_components/text";
|
||||
import Link from "next/link";
|
||||
import { useCarType } from "@/store/carType";
|
||||
|
||||
export default function CarType_Header() {
|
||||
const params = useParams();
|
||||
const initialCar = useCarType((state) => state.initialCar);
|
||||
console.log(params);
|
||||
return (
|
||||
<div
|
||||
@@ -47,12 +49,20 @@ export default function CarType_Header() {
|
||||
<Text txt="home" />
|
||||
</Link>
|
||||
/
|
||||
<Link href={`/${params.lang}/${params.carType}`} className={params.carDeatil ? "hover:text-secondary hover:cursor-pointer":"text-secondary"}>
|
||||
<Text txt={`${params.carType}`} />
|
||||
<Link
|
||||
href={`/${params.lang}/${params.carType}`}
|
||||
className={
|
||||
params.carDeatil
|
||||
? "hover:text-secondary hover:cursor-pointer"
|
||||
: "text-secondary"
|
||||
}
|
||||
>
|
||||
{initialCar.name}
|
||||
</Link>
|
||||
{params.carDeatil && '/'}
|
||||
{params.carDeatil && <div className="text-secondary">
|
||||
<Text txt={`${params.carType}`} /></div>}
|
||||
{params.carDeatil && "/"}
|
||||
{params.carDeatil && (
|
||||
<div className="text-secondary">{initialCar.name}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"use client";
|
||||
|
||||
import { innerCardTypes } from "@/types";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
@@ -8,74 +7,132 @@ import Image from "next/image";
|
||||
import Text from "../lib_components/text";
|
||||
import { useCarDetail } from "../lib_components/carDetailProvider";
|
||||
import { motion } from "framer-motion";
|
||||
import { useCarType } from "@/store/carType";
|
||||
|
||||
export default function InnerProductcard({ data }: { data: innerCardTypes }) {
|
||||
export default function InnerProductcard({ data }: { data: any }) {
|
||||
const route = useParams();
|
||||
const { setDetail } = useCarDetail();
|
||||
const setInitialCar = useCarType((state) => state.setInitialCar);
|
||||
|
||||
const carInfo = {
|
||||
id: data?.id,
|
||||
name: data?.name,
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
transition={{ duration: 0.5, ease: "easeOut" }}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
boxShadow: "0px 0px 15px rgba(0,0,0,0.1)",
|
||||
<Link
|
||||
href={`/${route.lang}/${route.carType}/${data.name}`}
|
||||
onClick={() => {
|
||||
setDetail(data);
|
||||
setInitialCar(carInfo);
|
||||
}}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="h-[420px] rounded-lg overflow-hidden bg-white transition-all"
|
||||
className="block h-full"
|
||||
>
|
||||
<Link
|
||||
href={`/${route.lang}/${route.carType}/${data.name}`}
|
||||
onClick={() => setDetail(data)}
|
||||
className="h-full flex flex-col items-center justify-between rounded-lg hover:cursor-pointer"
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
transition={{ duration: 0.5, ease: "easeOut" }}
|
||||
className="h-full group"
|
||||
>
|
||||
{/* Rasm qismi */}
|
||||
<div className="rounded-t-lg bg-white">
|
||||
<Image
|
||||
src={data.image}
|
||||
alt={data.name}
|
||||
width={0}
|
||||
height={200}
|
||||
className="object-fill w-full max-h-[200px] h-full rounded-t-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Pastki qism */}
|
||||
<div className="bg-[#fafafa] w-full p-2 px-4 rounded-b-lg flex flex-col items-start justify-start gap-2">
|
||||
<div className="text-xl font-semibold">
|
||||
<Text txt={data.name} />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Text txt="hour-price" />
|
||||
{data.price?.toLocaleString("uz-UZ")}
|
||||
<Text txt="wallet" />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Text txt="min-time" />
|
||||
{data.min_order_time}
|
||||
<Text txt="time" />
|
||||
</div>
|
||||
|
||||
{/* Tugma animatsiyasi */}
|
||||
<motion.button
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
backgroundColor: "#ffffff",
|
||||
color: "#dc2626", // hoverda text-secondary (agar sizda secondary = red)
|
||||
borderColor: "#dc2626",
|
||||
}}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="w-full p-3 bg-secondary rounded-lg text-white border-2 border-secondary"
|
||||
<div
|
||||
className="h-full bg-white rounded-2xl overflow-hidden border-2 border-gray-100
|
||||
hover:border-[#f2a01c] transition-all duration-300
|
||||
shadow-sm hover:shadow-xl flex flex-col"
|
||||
>
|
||||
{/* Rasm container - katta va responsive */}
|
||||
<div
|
||||
className="relative w-full aspect-[4/3] bg-gradient-to-br from-gray-50 via-white to-gray-50
|
||||
overflow-hidden group-hover:bg-gradient-to-br group-hover:from-orange-50
|
||||
group-hover:via-white group-hover:to-orange-50 transition-all duration-500"
|
||||
>
|
||||
<Text txt="more" />
|
||||
</motion.button>
|
||||
{/* Rasm */}
|
||||
<div className="absolute inset-0 p-6 flex items-center justify-center">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className="relative w-full h-full"
|
||||
>
|
||||
<Image
|
||||
src={data.image}
|
||||
alt={data.name}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
className="object-contain"
|
||||
priority
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Dekorativ element */}
|
||||
<div
|
||||
className="absolute top-4 right-4 w-12 h-12 bg-[#f2a01c]/10 rounded-full
|
||||
flex items-center justify-center opacity-0 group-hover:opacity-100
|
||||
transition-opacity duration-300"
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6 text-[#f2a01c]"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content qismi */}
|
||||
<div className="flex-1 p-6 flex flex-col justify-between bg-white">
|
||||
{/* Nomi va tavsif */}
|
||||
<div className="mb-4">
|
||||
<h3
|
||||
className="text-xl font-bold text-[#0c1239] mb-2 line-clamp-2
|
||||
group-hover:text-[#f2a01c] transition-colors duration-300"
|
||||
>
|
||||
<Text txt={data.name} />
|
||||
</h3>
|
||||
|
||||
{/* Agar qo'shimcha ma'lumot bo'lsa */}
|
||||
{data.description && (
|
||||
<p className="text-sm text-gray-600 line-clamp-2">
|
||||
<Text txt={data.description} />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tugma - past qismda */}
|
||||
<div className="pt-4 border-t border-gray-100">
|
||||
<motion.div
|
||||
whileHover={{ x: 4 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="flex items-center justify-between text-[#f2a01c] font-semibold"
|
||||
>
|
||||
<span>
|
||||
<Text txt="more" />
|
||||
</span>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { ProductTypes } from "@/types";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Text from "../lib_components/text";
|
||||
import { useParams } from "next/navigation";
|
||||
import { motion } from "framer-motion";
|
||||
import { useCarType } from "@/store/carType";
|
||||
|
||||
export default function ProductCard({ data }: { data: ProductTypes }) {
|
||||
export default function ProductCard({ data }: { data: any }) {
|
||||
const { lang } = useParams();
|
||||
|
||||
const setInitialCar = useCarType((state) => state.setInitialCar);
|
||||
const carData = {
|
||||
name: data?.name,
|
||||
id: data?.id,
|
||||
};
|
||||
|
||||
console.log("data: ", data);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
@@ -23,14 +31,15 @@ export default function ProductCard({ data }: { data: ProductTypes }) {
|
||||
className="rounded-xl border-2 border-primary h-[430px]"
|
||||
>
|
||||
<Link
|
||||
href={`/${lang}/${data.path}`}
|
||||
href={`/${lang}/${data.name}`}
|
||||
onClick={() => setInitialCar(carData)}
|
||||
className="h-full flex flex-col items-center justify-between rounded-lg bg-white transition-transform"
|
||||
>
|
||||
{/* Yuqori qism - rasm */}
|
||||
<div className="rounded-t-lg bg-white py-10 px-2 flex justify-center items-center">
|
||||
<Image
|
||||
src={data.image}
|
||||
alt={data.truck_name}
|
||||
alt={data.name}
|
||||
width={260}
|
||||
height={200}
|
||||
className="object-contain max-h-[200px] h-full rounded-xl"
|
||||
@@ -40,10 +49,10 @@ export default function ProductCard({ data }: { data: ProductTypes }) {
|
||||
{/* Pastki qism - matn */}
|
||||
<div className="bg-[#fafafa] w-full py-5 rounded-b-lg flex flex-col items-center justify-center space-y-1">
|
||||
<div className="font-medium text-primary text-xl text-center">
|
||||
<Text txt={data.truck_name} />
|
||||
<Text txt={data.name} />
|
||||
</div>
|
||||
<div className="text-secondary text-md font-extrabold text-center">
|
||||
<Text txt={data.desc} />
|
||||
<Text txt="agreement" />
|
||||
</div>
|
||||
<div className="text-center text-secondary bg-primary max-w-[200px] w-full rounded-xl text-lg py-2 hover:cursor-pointer">
|
||||
<Text txt="more" />
|
||||
|
||||
30
components/emptyState.tsx
Normal file
30
components/emptyState.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import Text from "./lib_components/text";
|
||||
|
||||
// Empty State Component
|
||||
export const EmptyState = () => {
|
||||
return (
|
||||
<div className="col-span-full flex flex-col items-center justify-center py-20 px-4">
|
||||
<div className="w-24 h-24 mb-6 rounded-full bg-[#f2a01c]/10 flex items-center justify-center">
|
||||
<svg
|
||||
className="w-12 h-12 text-[#f2a01c]"
|
||||
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>
|
||||
<h3 className="text-2xl font-bold text-[#0c1239] mb-2">
|
||||
<Text txt="noData" />
|
||||
</h3>
|
||||
<p className="text-gray-600 text-center max-w-md">
|
||||
<Text txt="noDataDesc" />
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
|
||||
export default function Time() {
|
||||
const {t} = useTranslation();
|
||||
return (
|
||||
<div dir='ltr' className='icon_animation2 fixed bottom-10 right-20 z-10 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>24/7</p>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function UpScrollIcon() {
|
||||
{showButton && (
|
||||
<span
|
||||
onClick={scrollToTop}
|
||||
className="fixed bottom-6 right-6 bg-secondary hover:bg-primary text-white p-3 rounded-full cursor-pointer shadow-lg transition"
|
||||
className="fixed z-40 bottom-6 right-6 bg-secondary hover:bg-primary text-white p-3 rounded-full cursor-pointer shadow-lg transition"
|
||||
>
|
||||
<FaArrowUp size={20} />
|
||||
</span>
|
||||
|
||||
19
components/loadingProduct.tsx
Normal file
19
components/loadingProduct.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
// Loading Skeleton Component
|
||||
export const LoadingSkeleton = () => {
|
||||
return (
|
||||
<>
|
||||
{[1, 2, 3, 4].map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className="bg-white rounded-lg shadow-md overflow-hidden animate-pulse"
|
||||
>
|
||||
<div className="w-full h-48 bg-gray-200"></div>
|
||||
<div className="p-4">
|
||||
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
|
||||
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -48,7 +48,7 @@ export default function HeroSection() {
|
||||
className={`${navigationPrevEl.replace(
|
||||
".",
|
||||
""
|
||||
)} w-10 h-10 absolute z-10 left-[10%] top-50 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 absolute z-10 left-[10%] top-50 p-0 bg-primary text-[30px] text-center text-white lg:flex hidden items-center justify-center hover:bg-secondary hover:cursor-pointer transition`}
|
||||
>
|
||||
<ArrowLeft />
|
||||
</button>
|
||||
@@ -56,7 +56,7 @@ export default function HeroSection() {
|
||||
className={`${navigationNextEl.replace(
|
||||
".",
|
||||
""
|
||||
)} w-10 h-10 absolute z-10 right-[10%] top-50 bg-primary text-[30px] text-center text-white flex items-center justify-center hover:bg-secondary hover:cursor-pointer transition `}
|
||||
)} w-10 h-10 absolute z-10 right-[10%] top-50 bg-primary text-[30px] text-center text-white lg:flex hidden items-center justify-center hover:bg-secondary hover:cursor-pointer transition `}
|
||||
>
|
||||
<ArrowRight />
|
||||
</button>
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function Map() {
|
||||
</div>
|
||||
|
||||
{/* contact information */}
|
||||
<div className="absolute flex flex-col gap-3 sm:top-20 top-5 sm:right-20 z-50 bg-white rounded-[15px] p-5 px-10 max-w-[400px] w-full text-left ">
|
||||
<div className="absolute flex flex-col gap-3 sm:top-20 top-5 sm:right-20 z-30 bg-white rounded-[15px] p-5 px-10 max-w-[400px] w-full text-left ">
|
||||
<div className="text-left flex w-full justify-start">
|
||||
<Title text="contacts" />
|
||||
</div>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Title from "../lib_components/title";
|
||||
import Text from "../lib_components/text";
|
||||
import { Asphalt, Ekskavator, Forklift, Kran, Truck } from "@/assets";
|
||||
import type { productFilterTypes, ProductTypes } from "@/types";
|
||||
import {
|
||||
allProducts,
|
||||
} from "@/data";
|
||||
import ProductCard from "../cards/productCard";
|
||||
|
||||
export default function Products() {
|
||||
|
||||
const [cars, setCars] = useState(allProducts);
|
||||
|
||||
return (
|
||||
<div dir="ltr" className="max-w-[1200px] w-full mx-auto">
|
||||
{/* title part */}
|
||||
<div className="flex flex-col mb-10">
|
||||
<div className="flex items-center justify-center w-full ">
|
||||
<div className="text-secondary px-2 text-[18px] font-semibold ">
|
||||
<Text txt="katalog" />
|
||||
</div>
|
||||
</div>
|
||||
<Title text="pricing-h2" />
|
||||
</div>
|
||||
|
||||
{/* product filters */}
|
||||
{/* <div className="flex flex-wrap gap-1 gap-y-4 items-center justify-center mb-10 ">
|
||||
{productFilterTypesMainPage.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setProductFilter(item.name)}
|
||||
className={`${
|
||||
productFilter === item.name ? "bg-secondary" : ""
|
||||
} flex items-center gap-2 h-[58px] hover:bg-secondary border-gray-300 hover:border-secondary border-[1px] px-7 text-2xl rounded-tr-full rounded-bl-full `}
|
||||
>
|
||||
<Text txt={item.name} />
|
||||
{item.image && (
|
||||
<Image
|
||||
src={item.image}
|
||||
alt="Truck images"
|
||||
width={50}
|
||||
height={50}
|
||||
className="object-cover"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</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">
|
||||
{cars.map((item: ProductTypes, index: number) => (
|
||||
<div key={index}>
|
||||
<ProductCard data={item} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
95
components/pageParts/products/products.tsx
Normal file
95
components/pageParts/products/products.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Title from "../../lib_components/title";
|
||||
import Text from "../../lib_components/text";
|
||||
import type { ProductTypes } from "@/types";
|
||||
import { allProducts } from "@/data";
|
||||
import ProductCard from "../../cards/productCard";
|
||||
import { LoadingSkeleton } from "@/components/loadingProduct";
|
||||
import { EmptyState } from "@/components/emptyState";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { baseUrl } from "@/data/url";
|
||||
|
||||
export default function Products() {
|
||||
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: ", data?.data);
|
||||
|
||||
if (data?.data && data.data.length > 0) {
|
||||
setCars(data.data);
|
||||
} else {
|
||||
setCars([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Xatolik: ", error);
|
||||
setError(error instanceof Error ? error.message : "Noma'lum xatolik");
|
||||
// Xatolik bo'lsa ham local data'ni ko'rsatish
|
||||
setCars(allProducts);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProducts();
|
||||
}, [lang]); // Bo'sh array - faqat bitta marta ishlaydi
|
||||
|
||||
return (
|
||||
<div dir="ltr" className="max-w-[1200px] w-full mx-auto">
|
||||
{/* title part */}
|
||||
<div className="flex flex-col mb-10">
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<div className="text-[#f2a01c] px-2 text-[18px] font-semibold">
|
||||
<Text txt="katalog" />
|
||||
</div>
|
||||
</div>
|
||||
<Title text="pricing-h2" />
|
||||
</div>
|
||||
|
||||
{/* Error message */}
|
||||
{error && (
|
||||
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-600 text-center">
|
||||
<Text txt="downloadError" /> : {error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* products */}
|
||||
<div className="px-4 grid gap-5 grid-cols-1 place-content-center min-[500px]:grid-cols-2 min-lg:grid-cols-4 min-[1210px]:grid-cols-4">
|
||||
{loading ? (
|
||||
<LoadingSkeleton />
|
||||
) : cars.length > 0 ? (
|
||||
cars.map((item: ProductTypes, index: number) => (
|
||||
<div key={item.id || index}>
|
||||
<ProductCard data={item} />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<EmptyState />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user