Fix
This commit is contained in:
@@ -4,12 +4,12 @@ import Link from "next/link";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Autoplay, Navigation } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
import type { ApiProduct } from "@/lib/types";
|
||||
import type { SpecialCarouselItem } from "@/lib/types";
|
||||
import ProductCard from "./ui/ProductCard";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "./icons";
|
||||
|
||||
export default function SpecialSaleCarousel({ products }: { products: ApiProduct[] }) {
|
||||
if (products.length === 0) return null;
|
||||
export default function SpecialSaleCarousel({ items }: { items: SpecialCarouselItem[] }) {
|
||||
if (items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section id="special-sale" className="boxed py-10 md:py-14">
|
||||
@@ -40,13 +40,13 @@ export default function SpecialSaleCarousel({ products }: { products: ApiProduct
|
||||
prevEl: ".sale-prev",
|
||||
nextEl: ".sale-next",
|
||||
}}
|
||||
loop={products.length > 5}
|
||||
loop={items.length > 5}
|
||||
spaceBetween={14}
|
||||
slidesPerView={2.15}
|
||||
slidesPerView={1.8}
|
||||
breakpoints={{
|
||||
480: { slidesPerView: 2.4 },
|
||||
640: { slidesPerView: 3.2 },
|
||||
1024: { slidesPerView: 5, spaceBetween: 20 },
|
||||
480: { slidesPerView: 2.2 },
|
||||
640: { slidesPerView: 3 },
|
||||
1024: { slidesPerView: 4.5, spaceBetween: 20 },
|
||||
}}
|
||||
>
|
||||
<SwiperSlide>
|
||||
@@ -56,9 +56,9 @@ export default function SpecialSaleCarousel({ products }: { products: ApiProduct
|
||||
className="w-full h-full aspect-[4/5] object-cover rounded-lg"
|
||||
/>
|
||||
</SwiperSlide>
|
||||
{products.map((product) => (
|
||||
{items.map(({ product, discount }) => (
|
||||
<SwiperSlide key={product.id}>
|
||||
<ProductCard product={product} />
|
||||
<ProductCard product={product} discount={discount} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
@@ -4,13 +4,13 @@ import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Navigation } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import type { ApiProduct } from "@/lib/types";
|
||||
import type { SpecialCarouselItem } from "@/lib/types";
|
||||
import ProductCard from "./ui/ProductCard";
|
||||
import SectionHeading from "./ui/SectionHeading";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "./icons";
|
||||
|
||||
export default function TopSellingCarousel({ products }: { products: ApiProduct[] }) {
|
||||
if (products.length === 0) return null;
|
||||
export default function TopSellingCarousel({ items }: { items: SpecialCarouselItem[] }) {
|
||||
if (items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section className="boxed py-10 md:py-14">
|
||||
@@ -42,16 +42,16 @@ export default function TopSellingCarousel({ products }: { products: ApiProduct[
|
||||
nextEl: ".top-selling-next",
|
||||
}}
|
||||
spaceBetween={16}
|
||||
slidesPerView={2.2}
|
||||
slidesPerView={1.8}
|
||||
breakpoints={{
|
||||
640: { slidesPerView: 3.2 },
|
||||
1024: { slidesPerView: 4 },
|
||||
1280: { slidesPerView: 5 },
|
||||
640: { slidesPerView: 2.8 },
|
||||
1024: { slidesPerView: 3.5 },
|
||||
1280: { slidesPerView: 4.5 },
|
||||
}}
|
||||
>
|
||||
{products.map((product) => (
|
||||
{items.map(({ product, discount }) => (
|
||||
<SwiperSlide key={product.id}>
|
||||
<ProductCard product={product} />
|
||||
<ProductCard product={product} discount={discount} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
@@ -155,6 +155,14 @@ export function CheckIcon({ className }: IconProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function PlusIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" className={className} {...base}>
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommentIcon({ className }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" className={className} {...base}>
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
import Link from "next/link";
|
||||
import type { ApiProduct } from "@/lib/types";
|
||||
import { formatToman } from "@/lib/format";
|
||||
"use client";
|
||||
|
||||
export default function ProductCard({ product }: { product: ApiProduct }) {
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import type { ApiProduct, ProductDiscount } from "@/lib/types";
|
||||
import { formatToman } from "@/lib/format";
|
||||
import { CheckIcon, PlusIcon } from "../icons";
|
||||
|
||||
export default function ProductCard({
|
||||
product,
|
||||
discount,
|
||||
}: {
|
||||
product: ApiProduct;
|
||||
discount?: ProductDiscount;
|
||||
}) {
|
||||
const [added, setAdded] = useState(false);
|
||||
const title = product.nameFa || product.title;
|
||||
const image =
|
||||
product.thumbnailUrl || product.image || product.thumbnail || "/images/products/product-1.svg";
|
||||
const store = product.store;
|
||||
const price = discount ? discount.price : store?.minPrice;
|
||||
|
||||
function handleQuickAdd(e: React.MouseEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setAdded(true);
|
||||
setTimeout(() => setAdded(false), 1500);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
@@ -26,15 +45,44 @@ export default function ProductCard({ product }: { product: ApiProduct }) {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col p-3.5">
|
||||
<h3 className="text-[13px] md:text-sm text-ink line-clamp-2 min-h-[2.5em]">{title}</h3>
|
||||
<div className="mt-2 flex items-center gap-2 flex-wrap">
|
||||
<span className="font-bold text-sm md:text-[15px] text-brand">
|
||||
{store?.minPrice != null ? formatToman(store.minPrice) : "تماس بگیرید"}
|
||||
</span>
|
||||
{store?.maxPrice != null && store.maxPrice !== store.minPrice && (
|
||||
<span className="text-xs text-body/60">تا {formatToman(store.maxPrice)}</span>
|
||||
{/* Fixed height regardless of 1 or 2 lines, so card heights stay
|
||||
consistent across a row of mixed-length titles. */}
|
||||
<h3 className="h-10 md:h-11 leading-tight text-[13px] md:text-sm text-ink text-right line-clamp-2">
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
{/* Always reserves its height, even with no discount, so a
|
||||
discounted card and a plain card end up the same height. Right
|
||||
aligned to line up with the price row below; discount badge
|
||||
sits to the left of the old price. */}
|
||||
<div className="mt-2 h-6 flex items-center justify-start gap-2">
|
||||
{discount && (
|
||||
<>
|
||||
<span className="text-xs text-body/60 line-through truncate">
|
||||
{discount.oldPrice.toLocaleString("en-US")}
|
||||
</span>
|
||||
<span className="shrink-0 rounded-md bg-gold/20 text-gold-dark text-xs font-bold px-1.5 py-0.5">
|
||||
%{discount.percent}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<span className="font-bold text-sm md:text-[15px] text-brand truncate">
|
||||
{price != null ? formatToman(price) : "تماس بگیرید"}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleQuickAdd}
|
||||
aria-label="افزودن سریع به سبد خرید"
|
||||
className={`shrink-0 flex items-center justify-center size-8 rounded-full transition-colors ${
|
||||
added ? "bg-brand-dark text-white" : "bg-brand text-white hover:bg-brand-dark"
|
||||
}`}
|
||||
>
|
||||
{added ? <CheckIcon className="size-4" /> : <PlusIcon className="size-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user