catalog part connected to backend , added empty data and loading component
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-geist-mono);
|
||||||
--font-roboto: "Roboto", sans-serif;
|
--font-roboto: "Roboto", sans-serif;
|
||||||
--font-almarai: "Almarai", sans-serif;
|
--font-almarai: "Almarai", sans-serif;
|
||||||
--font-unbounded:"Unbounded",sans-serif;
|
--font-unbounded: "Unbounded", sans-serif;
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
@@ -135,6 +135,25 @@ body {
|
|||||||
background: #1e1d1c;
|
background: #1e1d1c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loio{
|
.loio {
|
||||||
color:#8b1515, #c91d1d
|
color: #8b1515, #c91d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* globals.css ga qo'shing */
|
||||||
|
@keyframes shimmer {
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-shimmer {
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-150 {
|
||||||
|
animation-delay: 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-300 {
|
||||||
|
animation-delay: 300ms;
|
||||||
}
|
}
|
||||||
|
|||||||
48
components/EmptyData.tsx
Normal file
48
components/EmptyData.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// components/EmptyData.tsx
|
||||||
|
import { PackageOpen, ShoppingBag } from "lucide-react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
interface EmptyDataProps {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
icon?: "package" | "shopping";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EmptyData({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon = "package",
|
||||||
|
}: EmptyDataProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
|
const Icon = icon === "package" ? PackageOpen : ShoppingBag;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-100 py-12 px-4">
|
||||||
|
{/* Animated background circles */}
|
||||||
|
<div className="relative flex items-center justify-center ">
|
||||||
|
{/* Icon */}
|
||||||
|
<div className="relative z-10 w-20 h-20 mx-auto mb-6 rounded-full bg-linear-to-br from-[#444242] to-gray-900 border border-white/10 flex items-center justify-center">
|
||||||
|
<Icon className="w-10 h-10 text-white/40" strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Text content */}
|
||||||
|
<div className="text-center space-y-3 max-w-md">
|
||||||
|
<h3 className="text-2xl font-unbounded font-bold text-white">
|
||||||
|
{title || t("no_data_title")}
|
||||||
|
</h3>
|
||||||
|
<p className="text-white/50 font-almarai">
|
||||||
|
{description || t("no_data_description")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Decorative elements */}
|
||||||
|
<div className="mt-8 flex gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-red-500/30 animate-pulse" />
|
||||||
|
<div className="w-2 h-2 rounded-full bg-red-500/30 animate-pulse delay-150" />
|
||||||
|
<div className="w-2 h-2 rounded-full bg-red-500/30 animate-pulse delay-300" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
components/loadingSkleton.tsx
Normal file
41
components/loadingSkleton.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// components/CatalogCardSkeleton.tsx
|
||||||
|
export default function CatalogCardSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="relative h-112.5 w-full overflow-hidden rounded-2xl bg-[#17161679] border border-white/10 animate-pulse">
|
||||||
|
{/* Content container */}
|
||||||
|
<div className="relative h-full flex flex-col p-6">
|
||||||
|
{/* Title section */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="flex items-start justify-between mb-2">
|
||||||
|
{/* Title skeleton */}
|
||||||
|
<div className="flex-1 space-y-2">
|
||||||
|
<div className="h-7 bg-white/10 rounded-md w-3/4" />
|
||||||
|
<div className="h-7 bg-white/10 rounded-md w-1/2" />
|
||||||
|
</div>
|
||||||
|
{/* Icon skeleton */}
|
||||||
|
<div className="shrink-0 w-8 h-8 rounded-full bg-white/10" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description skeleton */}
|
||||||
|
<div className="space-y-2 mt-3">
|
||||||
|
<div className="h-4 bg-white/10 rounded w-full" />
|
||||||
|
<div className="h-4 bg-white/10 rounded w-4/5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Image container skeleton */}
|
||||||
|
<div className="relative flex-1 rounded-xl overflow-hidden bg-linear-to-br from-[#444242] to-gray-900/50 border border-white/5">
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-32 h-32 bg-white/5 rounded-lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom accent bar skeleton */}
|
||||||
|
<div className="mt-4 h-1 w-1/3 bg-white/10 rounded-full" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Shimmer effect */}
|
||||||
|
<div className="absolute inset-0 -translate-x-full animate-shimmer bg-linear-to-r from-transparent via-white/5 to-transparent" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
|
import DotAnimatsiya from "@/components/dot/DotAnimatsiya";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { ProductCatalog } from "@/lib/demoData";
|
import Catalog from "./catalog";
|
||||||
import CatalogCard from "../products/catalog";
|
|
||||||
|
|
||||||
export function Blog() {
|
export function Blog() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
@@ -26,17 +25,7 @@ export function Blog() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Blog Cards Grid */}
|
{/* Blog Cards Grid */}
|
||||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3 place-items-center">
|
<Catalog />
|
||||||
{ProductCatalog.map((item, index) => (
|
|
||||||
<CatalogCard
|
|
||||||
key={index}
|
|
||||||
id={item.id}
|
|
||||||
title={item.title}
|
|
||||||
description={item.description}
|
|
||||||
image={item.image}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
57
components/pages/home/blog/catalog.tsx
Normal file
57
components/pages/home/blog/catalog.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import httpClient from "@/request/api";
|
||||||
|
import { endPoints } from "@/request/links";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import CatalogCard from "../../products/catalog";
|
||||||
|
import CatalogCardSkeleton from "@/components/loadingSkleton";
|
||||||
|
import EmptyData from "@/components/EmptyData";
|
||||||
|
import { getRouteLang } from "@/request/getLang";
|
||||||
|
import { CategoryType } from "@/lib/types";
|
||||||
|
|
||||||
|
export default function Catalog() {
|
||||||
|
const language = getRouteLang();
|
||||||
|
const { data, isLoading } = useQuery({
|
||||||
|
queryKey: ["categorycasd", language],
|
||||||
|
queryFn: () => httpClient(endPoints.category.all),
|
||||||
|
select: (data): CategoryType[] => data?.data?.results,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("product catalog data: ", data);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{[...Array(3)].map((_, index) => (
|
||||||
|
<CatalogCardSkeleton key={index} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ma'lumot yo'q holati
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return (
|
||||||
|
<EmptyData
|
||||||
|
title="Katalog topilmadi"
|
||||||
|
description="Hozircha kategoriyalar mavjud emas. Keyinroq qaytib keling."
|
||||||
|
icon="shopping"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3 place-items-center">
|
||||||
|
{data.map((item, index) => (
|
||||||
|
<CatalogCard
|
||||||
|
key={index}
|
||||||
|
id={item.id}
|
||||||
|
title={item.name}
|
||||||
|
description={item.description}
|
||||||
|
image={item.image}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,4 +5,4 @@ export { Video } from "./video";
|
|||||||
export { OurService } from "./ourService";
|
export { OurService } from "./ourService";
|
||||||
export { Testimonial } from "./testimonal";
|
export { Testimonial } from "./testimonal";
|
||||||
export { Line } from "./line";
|
export { Line } from "./line";
|
||||||
export { Blog } from "./blog";
|
export { Blog } from "./blog/blog";
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import Link from "next/link";
|
|||||||
import { ArrowUpRight } from "lucide-react";
|
import { ArrowUpRight } from "lucide-react";
|
||||||
|
|
||||||
interface CatalogProps {
|
interface CatalogProps {
|
||||||
id: string;
|
id: number;
|
||||||
image: string;
|
image: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|||||||
39
lib/types.ts
Normal file
39
lib/types.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
export interface CategoryType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
image: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubCategoryType {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
category: number;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductsPageTypes {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductImage {
|
||||||
|
id: number;
|
||||||
|
product: number;
|
||||||
|
image: string;
|
||||||
|
is_main: boolean;
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductDetail {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
articular: string;
|
||||||
|
status: string;
|
||||||
|
description: string;
|
||||||
|
size: number;
|
||||||
|
price: string;
|
||||||
|
features: string[];
|
||||||
|
images: ProductImage[];
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ export const endPoints = {
|
|||||||
detail: (id: number) => `product/${id}/`,
|
detail: (id: number) => `product/${id}/`,
|
||||||
},
|
},
|
||||||
faq: "faq/",
|
faq: "faq/",
|
||||||
gallery: "gallery/",
|
gallery: "gallery/?page_size=500",
|
||||||
contact: "contact/",
|
contact: "contact/",
|
||||||
statistics: "statistics/",
|
statistics: "statistics/",
|
||||||
filter: {
|
filter: {
|
||||||
|
|||||||
19
store/useCategory.ts
Normal file
19
store/useCategory.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { CategoryType } from "@/lib/types";
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
interface CategoryZustandType {
|
||||||
|
category: CategoryType;
|
||||||
|
setCategory: (category: CategoryType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoCategory: CategoryType = {
|
||||||
|
id: 0,
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
image: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCategory = create<CategoryZustandType>((set) => ({
|
||||||
|
category: demoCategory,
|
||||||
|
setCategory: (data) => set({ category: data }),
|
||||||
|
}));
|
||||||
0
store/useProduct.ts
Normal file
0
store/useProduct.ts
Normal file
18
store/useSubCategory.ts
Normal file
18
store/useSubCategory.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { SubCategoryType } from "@/lib/types";
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
interface SubCategoryZustandType {
|
||||||
|
subCategory: SubCategoryType;
|
||||||
|
setSubCategory: (subCategory: SubCategoryType) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoSubCategory = {
|
||||||
|
id: 0,
|
||||||
|
name: "",
|
||||||
|
category: 0,
|
||||||
|
image: "",
|
||||||
|
};
|
||||||
|
export const useSubCategory = create<SubCategoryZustandType>((set) => ({
|
||||||
|
subCategory: demoSubCategory,
|
||||||
|
setSubCategory: (data) => set({}),
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user