بلاک‌ها · فروشگاهی

علاقه‌مندی‌ها

پیش‌نمایش تمام‌صفحه

گریدِ کارتِ محصول با عکس، تبِ همه/تخفیف‌دار/موجود، برداشتن با امکانِ برگرداندن، افزودن به سبد و «موجود شد خبرم کن» برای ناموجودها، و حالتِ خالی.

۱. نصب کامپوننت‌ها

این بلاک از ۱۰ آیتم دیگ استفاده می‌کند: add-to-cartbadgebuttoncontrol-sizeempty-stateiconspersianproduct-cardtoasttoggle-group

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json
pnpm dlx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json
yarn dlx digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json
bunx --bun digdesign@latest add https://docs.digdesign.ir/r/add-to-cart.json https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/product-card.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

کد را در app/<route>/page.tsx بگذارید و متن‌ها، داده و هندلرها را با محصول خودتان عوض کنید.

wishlist.tsx
"use client"

import * as React from "react"

import { BellIcon, HeartIcon } from "@/components/icons"
import { Badge } from "@/components/ui/badge"
import { AddToCart } from "@/components/ui/add-to-cart"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateTitle } from "@/components/ui/empty-state"
import { ProductCard, ProductCardAction, ProductCardBadge, ProductCardBody, ProductCardCategory, ProductCardFooter, ProductCardGrid, ProductCardMedia, ProductCardPrice, ProductCardTitle } from "@/components/ui/product-card"
import { toast } from "@/components/ui/toast"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"

type Item = {
  id: string
  title: string
  category: string
  image: StockImageName
  price: number
  oldPrice?: number
  inStock: boolean
}

const INITIAL: Item[] = [
  { id: "w1", title: "گلدانِ سرامیکی دوقلو", category: "دکوراسیون", image: "productVase", price: 1_290_000, oldPrice: 1_520_000, inStock: true },
  { id: "w2", title: "ست لیوانِ کاغذی برند‌دار", category: "کافه", image: "projectCafeBrand", price: 420_000, inStock: true },
  { id: "w3", title: "میزِ کارِ چوبی مینیمال", category: "مبلمان", image: "coverMinimalRoom", price: 8_900_000, inStock: false },
  { id: "w4", title: "دفترچه و خودکارِ فلزی", category: "نوشت‌افزار", image: "coverNotebook", price: 360_000, oldPrice: 450_000, inStock: true },
]

/**
 * علاقه‌مندی‌ها: شمارش و تبِ «همه / تخفیف‌دار / موجود»، گریدِ کارتِ محصول با عکس،
 * برداشتن از علاقه‌مندی، افزودن به سبد، و برای ناموجودها «خبرم کن» به‌جای خرید.
 * فهرستِ خالی یک راهِ برگشت به فروشگاه دارد.
 */
export default function WishlistBlock() {
  const [items, setItems] = React.useState(INITIAL)
  const [filter, setFilter] = React.useState("all")
  const visible = items.filter((item) => filter === "all" || (filter === "sale" ? item.oldPrice : item.inStock))

  const remove = (item: Item) => {
    setItems((list) => list.filter((entry) => entry.id !== item.id))
    toast(`«${item.title}» از علاقه‌مندی‌ها برداشته شد.`, {
      action: { label: "برگردان", onClick: () => setItems((list) => [...list, item]) },
    })
  }

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-6xl flex-col gap-8 p-4 sm:p-6">
        <header className="flex flex-wrap items-end justify-between gap-x-4 gap-y-8">
          <div className="flex flex-col gap-1">
            <h1 className="text-2xl font-bold">علاقه‌مندی‌ها</h1>
            <p className="text-muted-foreground">{toFaDigits(items.length)} کالا · قیمت‌ها لحظه‌ای به‌روز می‌شوند</p>
          </div>
          <ToggleGroup type="single" variant="segmented" value={filter} onValueChange={(value) => value && setFilter(value as string)} aria-label="فیلترِ علاقه‌مندی‌ها">
            <ToggleGroupItem value="all">همه</ToggleGroupItem>
            <ToggleGroupItem value="sale">تخفیف‌دار</ToggleGroupItem>
            <ToggleGroupItem value="stock">موجود</ToggleGroupItem>
          </ToggleGroup>
        </header>

        {visible.length === 0 ? (
          <EmptyState variant="bordered">
            <EmptyStateTitle>{items.length === 0 ? "هنوز چیزی دوست نداشتی" : "کالایی با این فیلتر نیست"}</EmptyStateTitle>
            <EmptyStateDescription>روی قلبِ هر کالا بزن تا این‌جا بماند و از تخفیفش باخبر شوی.</EmptyStateDescription>
            <EmptyStateActions>
              <Button>رفتن به فروشگاه</Button>
            </EmptyStateActions>
          </EmptyState>
        ) : (
          <ProductCardGrid size="lg">
            {visible.map((item) => (
              <ProductCard key={item.id} size="lg">
                <ProductCardMedia>
                  {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
                  <img src={stockImages[item.image].src} alt={stockImages[item.image].alt} />
                  {/* تخفیف کنارِ قیمت آمده؛ روی عکس فقط وضعیتِ ناموجود. */}
                  {!item.inStock && (
                    <ProductCardBadge>
                      <Badge variant="soft">ناموجود</Badge>
                    </ProductCardBadge>
                  )}
                  <ProductCardAction>
                    <Button variant="outline" size="icon" aria-label={`برداشتنِ ${item.title} از علاقه‌مندی‌ها`} aria-pressed onClick={() => remove(item)} className="bg-background text-destructive hover:text-destructive">
                      <HeartIcon filled />
                    </Button>
                  </ProductCardAction>
                </ProductCardMedia>
                <ProductCardBody>
                  <ProductCardCategory>{item.category}</ProductCardCategory>
                  <ProductCardTitle>{item.title}</ProductCardTitle>
                  <ProductCardPrice
                    amount={formatCurrency(item.price)}
                    originalAmount={item.oldPrice ? formatCurrency(item.oldPrice) : undefined}
                    discount={item.oldPrice ? `٪${toFaDigits(Math.round((1 - item.price / item.oldPrice) * 100))}` : undefined}
                  />
                </ProductCardBody>
                <ProductCardFooter>
                  {item.inStock ? (
                    <AddToCart fullWidth variant="default" itemName={item.title} />
                  ) : (
                    <Button variant="outline" fullWidth onClick={() => toast.success("موجود شد، خبرت می‌کنیم.")}>
                      <BellIcon />
                      خبرم کن
                    </Button>
                  )}
                </ProductCardFooter>
              </ProductCard>
            ))}
          </ProductCardGrid>
        )}
      </main>
    </ControlSizeProvider>
  )
}