connect some api

This commit is contained in:
Jelaletdin12
2025-11-22 20:59:28 +05:00
parent 4fe0fb3d4e
commit 2857d34f4d
24 changed files with 1233 additions and 893 deletions

View File

@@ -1,214 +0,0 @@
"use client";
import { useState, MouseEvent } from "react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Heart, Minus, Plus } from "lucide-react";
import Image, { StaticImageData } from "next/image";
import { useAddToFavorites, useRemoveFromFavorites } from "@/lib/hooks";
import { useToast } from "@/hooks/use-toast";
type ProductCardProps = {
id: number;
name: string;
price: number | null;
struct_price_text: string;
discount?: number | null;
discount_text?: string | null;
images: (StaticImageData | string)[];
is_favorite: boolean;
labels?: { text: string; bg_color: string }[];
price_color?: string;
height?: number;
width?: number;
button?: boolean;
};
export default function ProductCard({
id,
name,
price,
struct_price_text,
discount,
discount_text,
images,
is_favorite,
labels = [],
price_color = "#005bff",
height = 360,
width = 280,
button = true,
}: ProductCardProps) {
const [favorite, setFavorite] = useState(is_favorite);
const [cart, setCart] = useState(false);
const [count, setCount] = useState(1);
const { toast } = useToast();
const { mutate: addToFavorites, isPending: isAdding } = useAddToFavorites();
const { mutate: removeFromFavorites, isPending: isRemoving } =
useRemoveFromFavorites();
const handleFavorite = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
const newFavoriteState = !favorite;
if (newFavoriteState) {
// Добавляем в избранное
addToFavorites(id, {
onSuccess: () => {
setFavorite(true);
toast({
title: "Товар добавлен в избранное",
});
},
onError: (error) => {
toast({
title: "Ошибка",
description: error.message,
variant: "destructive",
});
},
});
} else {
// Удаляем из избранного
removeFromFavorites(id, {
onSuccess: () => {
setFavorite(false);
toast({
title: "Товар удален из избранного",
});
},
onError: (error) => {
toast({
title: "Ошибка",
description: error.message,
variant: "destructive",
});
},
});
}
};
const handleAddToCart = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
setCart(true);
};
const handleIncrement = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
setCount((c) => c + 1);
};
const handleDecrement = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
setCount((c) => (c > 1 ? c - 1 : c));
};
const isPending = isAdding || isRemoving;
return (
<Link href={`/product/${id}`} className="no-underline">
<Card
className={`relative gap-2 border-none shadow-none! p-0 w-full max-w-[${width}px] overflow-hidden rounded-2xl hover:shadow-md transition-all cursor-pointer`}
style={{ height }}
>
{/* Image Section */}
<div className="relative w-full h-[260px]">
{images?.[0] && (
<Image
src={images[0]}
alt={name}
fill
sizes="(max-width: 600px) 100vw, 33vw"
className="object-contain"
priority
/>
)}
{/* Favorite Button */}
<button
onClick={handleFavorite}
disabled={isPending}
className="absolute top-3 right-3 z-10 rounded-full bg-white/80 p-2 hover:bg-white transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
{favorite ? (
<Heart className="w-5 h-5 text-red-500 fill-red-500" />
) : (
<Heart className="w-5 h-5 text-gray-700" />
)}
</button>
{/* Labels */}
{labels?.length > 0 && (
<div className="absolute bottom-2 left-2 flex flex-col gap-1">
{labels.map((label) => (
<Badge
key={label.text}
className="text-white text-[10px] font-bold uppercase rounded-r-md"
style={{ backgroundColor: label.bg_color }}
>
{label.text}
</Badge>
))}
</div>
)}
</div>
{/* Content */}
<CardContent className="p-0 space-y-1">
<p
className="text-sm font-semibold mx-2"
style={{ color: price_color }}
>
{struct_price_text}
</p>
<p className="text-gray-800 text-sm truncate mx-2">{name}</p>
</CardContent>
{/* Buttons - закомментированы в оригинале */}
{/* {button && (
<div className="p-3">
{!cart ? (
<Button
className="w-full font-bold text-base rounded-xl"
onClick={handleAddToCart}
>
Заказать
</Button>
) : (
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={handleDecrement}
disabled={count === 1}
className="rounded-xl"
>
<Minus className="w-4 h-4" />
</Button>
<div className="flex-1 text-center text-gray-700 border rounded-xl py-2">
{count}
</div>
<Button
variant="outline"
size="icon"
onClick={handleIncrement}
className="rounded-xl"
>
<Plus className="w-4 h-4" />
</Button>
</div>
)}
</div>
)} */}
</Card>
</Link>
);
}

View File

@@ -22,7 +22,7 @@ export default function CategoryMenu({ isOpen, onClose }: CategoryMenuProps) {
const activeCategory = hoveredCategory !== null ? categoryList[hoveredCategory] : null
return (
<div className="fixed left-0 right-0 top-22 z-40 bg-white border-b shadow-lg">
<div className="fixed left-0 right-0 top-22 z-40 bg-white border-b shadow-lg max-w-[1504px] mx-auto">
<div className="container mx-auto px-4">
<div className="flex">
<CategoryList

241
components/ui/carousel.tsx Normal file
View File

@@ -0,0 +1,241 @@
"use client"
import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
function Carousel({
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
}: React.ComponentProps<"div"> & CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
data-slot="carousel-content"
>
<div
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
}
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
const { orientation } = useCarousel()
return (
<div
role="group"
aria-roledescription="slide"
data-slot="carousel-item"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
}
function CarouselPrevious({
className,
variant = "outline",
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
data-slot="carousel-previous"
variant={variant}
size={size}
className={cn(
"absolute size-8 rounded-full",
orientation === "horizontal"
? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft />
<span className="sr-only">Previous slide</span>
</Button>
)
}
function CarouselNext({
className,
variant = "outline",
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
data-slot="carousel-next"
variant={variant}
size={size}
className={cn(
"absolute size-8 rounded-full",
orientation === "horizontal"
? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight />
<span className="sr-only">Next slide</span>
</Button>
)
}
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
}