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

دیدگاه‌های در انتظار

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

پنلِ حسابِ مشتری: زبانه‌های در انتظارِ دیدگاه، دیدگاه‌ها و پرسش‌ها؛ کالاهای خریده‌شده با امتیازِ ستاره‌ای و ثبتِ دیدگاهی که بعد از امتیاز برجسته می‌شود.

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

این بلاک از ۷ آیتم دیگ استفاده می‌کند: buttoncontrol-sizeiconspersianratingtoasttoggle-group

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/rating.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/rating.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/rating.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/rating.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

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

account-reviews.tsx
"use client"

import * as React from "react"

import { ChatIcon, CheckIcon, ChevronLeftIcon, DashboardIcon, EditIcon, HeartIcon, LocationIcon, LogoutIcon, ShopIcon, UserIcon, WalletIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Rating } from "@/components/ui/rating"
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"

const NAV = [
  { id: "overview", label: "خلاصهٔ فعالیت‌ها", icon: <DashboardIcon /> },
  { id: "orders", label: "سفارش‌ها", icon: <ShopIcon /> },
  { id: "lists", label: "لیست‌های من", icon: <HeartIcon /> },
  { id: "reviews", label: "دیدگاه‌ها و پرسش‌ها", icon: <ChatIcon /> },
  { id: "addresses", label: "نشانی‌ها", icon: <LocationIcon /> },
  { id: "account", label: "اطلاعاتِ حساب", icon: <UserIcon /> },
]

/** ستونِ کنارِ پنلِ حساب: نام و موبایل، کیف پول با افزایشِ موجودی، و منوی بخش‌ها. */
function AccountNav({ active }: { active: string }) {
  return (
    <aside className="flex flex-col gap-2 rounded-xl border border-border bg-card p-4 lg:sticky lg:top-6">
      <div className="flex items-start justify-between gap-2 px-2 pt-1 pb-4">
        <span className="flex flex-col gap-0.5">
          <span className="font-bold">آرمیتا رستگار</span>
          <bdi dir="ltr" className="text-sm text-muted-foreground tabular-nums">
            {toFaDigits("0912 345 6789")}
          </bdi>
        </span>
        <Button variant="ghost" size="icon" aria-label="ویرایشِ اطلاعات" className="-me-2 -mt-2">
          <EditIcon />
        </Button>
      </div>
      <div className="flex items-center justify-between gap-2 rounded-lg bg-subtle p-3">
        <span className="flex items-center gap-2 text-sm [&>svg]:size-4 [&>svg]:text-muted-foreground">
          <WalletIcon aria-hidden />
          کیف پول
        </span>
        <span className="font-bold tabular-nums">{formatCurrency(150_129)}</span>
      </div>
      <a href="#" className="flex items-center gap-1 self-end px-2 pb-2 text-sm font-medium text-brand-secondary hover:underline">
        افزایشِ موجودی
        <ChevronLeftIcon aria-hidden className="size-4" />
      </a>
      <nav aria-label="حسابِ کاربری" className="flex flex-col border-t border-border pt-2">
        {NAV.map((item) => (
          <a
            key={item.id}
            href="#"
            aria-current={item.id === active ? "page" : undefined}
            className={`flex min-h-12 items-center gap-3 rounded-lg px-3 text-sm [&>svg]:size-5 ${item.id === active ? "bg-primary-subtle font-bold" : "text-muted-foreground hover:bg-subtle hover:text-foreground"}`}
          >
            {item.icon}
            {item.label}
          </a>
        ))}
        <a href="#" className="flex min-h-12 items-center gap-3 rounded-lg px-3 text-sm text-destructive hover:bg-destructive-subtle [&>svg]:size-5">
          <LogoutIcon aria-hidden />
          خروج از حساب
        </a>
      </nav>
    </aside>
  )
}

const PENDING: { id: string; title: string; seller: string; color?: string; date: string; image: StockImageName }[] = [
  { id: "p1", title: "دفترِ یادداشتِ جلدسخت A5", seller: "هنرِ نقاش", color: "سفید", date: "۳ مرداد ۱۴۰۵", image: "coverNotebook" },
  { id: "p2", title: "ست لوازم‌التحریرِ رومیزی", seller: "تجارتِ لوازمِ تحریر", date: "۳ مرداد ۱۴۰۵", image: "coverOfficeSupplies" },
  { id: "p3", title: "گلدانِ سرامیکی دوقلو", seller: "خانهٔ سفال", color: "کرم", date: "۵ فروردین ۱۴۰۵", image: "productVase" },
  { id: "p4", title: "ماشین‌حسابِ رومیزی", seller: "دفترِ نو", color: "مشکی", date: "۵ فروردین ۱۴۰۵", image: "coverCalculator" },
]

/**
 * دیدگاه‌های در انتظار در پنلِ حسابِ مشتری: زبانه‌های «در انتظارِ دیدگاه / دیدگاه‌های من / پرسش‌های من»،
 * گریدِ کالاهای خریده‌شده با فروشنده، رنگ و تاریخ، امتیازِ یک‌لمسی با ستاره و «ثبتِ دیدگاه» که بعد از
 * امتیاز دادن فعال و برجسته می‌شود؛ کالای ثبت‌شده از فهرست بیرون می‌رود.
 */
export default function AccountReviewsBlock() {
  const [tab, setTab] = React.useState("pending")
  const [items, setItems] = React.useState(PENDING)
  const [scores, setScores] = React.useState<Record<string, number>>({ p1: 4 })

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto grid w-full max-w-6xl grid-cols-1 items-start gap-6 p-4 sm:p-6 lg:grid-cols-[17rem_minmax(0,1fr)]">
        <div className="hidden lg:block">
          <AccountNav active="reviews" />
        </div>

        <section aria-labelledby="reviews-title" className="flex min-w-0 flex-col rounded-xl border border-border bg-card">
          <header className="px-5 pt-5">
            <h1 id="reviews-title" className="text-xl font-black">
              دیدگاه‌ها و پرسش‌ها
            </h1>
          </header>
          <div className="overflow-x-auto border-b border-border px-5 pt-4 [scrollbar-width:none]">
            <ToggleGroup type="single" value={tab} onValueChange={(value) => value && setTab(value as string)} aria-label="بخش" className="w-max gap-1">
              {[
                ["pending", `در انتظارِ دیدگاه`],
                ["mine", "دیدگاه‌های من"],
                ["questions", "پرسش‌های من"],
              ].map(([id, label]) => (
                <ToggleGroupItem
                  key={id}
                  value={id}
                  className="gap-2 rounded-none rounded-t-lg border-b-2 border-transparent bg-card px-3 hover:bg-subtle data-[state=on]:border-primary data-[state=on]:bg-card data-[state=on]:font-bold"
                >
                  {label}
                  {id === "pending" && <span className="flex min-w-6 items-center justify-center rounded-md bg-muted px-1.5 text-xs tabular-nums">{toFaDigits(items.length)}</span>}
                </ToggleGroupItem>
              ))}
            </ToggleGroup>
          </div>

          {tab !== "pending" || items.length === 0 ? (
            <div className="flex flex-col items-center gap-4 px-6 py-14 text-center">
              <span aria-hidden className="flex size-20 items-center justify-center rounded-full bg-[color-mix(in_oklab,var(--dig-violet-500)_10%,var(--card))] text-[var(--dig-violet-600)] [&>svg]:size-8">
                {items.length === 0 ? <CheckIcon /> : <ChatIcon />}
              </span>
              <h2 className="text-lg font-bold">{items.length === 0 && tab === "pending" ? "به همه دیدگاه دادی" : "دیدگاه‌ها و پرسش‌هایت این‌جا می‌آیند"}</h2>
              <p className="max-w-sm text-sm leading-6 text-muted-foreground">دیدگاهِ تو به خریدارهای بعدی کمک می‌کند؛ بعد از هر خرید این‌جا امتیاز بده.</p>
            </div>
          ) : (
            <ul className="grid grid-cols-1 gap-4 p-5 md:grid-cols-2">
              {items.map((item) => {
                const score = scores[item.id] ?? 0
                return (
                  <li key={item.id} className="flex flex-col gap-4 rounded-xl border border-border p-4">
                    <div className="flex gap-3">
                      {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
                      <img src={stockImages[item.image].src} alt="" loading="lazy" className="size-20 shrink-0 rounded-lg object-cover" />
                      <span className="flex min-w-0 flex-col gap-1.5">
                        <span className="line-clamp-2 leading-6 font-bold">{item.title}</span>
                        <span className="text-xs text-muted-foreground">
                          {item.seller}
                          {item.color && ` · ${item.color}`} · {item.date}
                        </span>
                      </span>
                    </div>
                    <div className="flex flex-wrap items-center justify-between gap-2 border-t border-border pt-4">
                      <span className="text-sm font-semibold">امتیاز بده</span>
                      <Rating value={score} onValueChange={(value) => setScores((current) => ({ ...current, [item.id]: value }))} aria-label={`امتیازِ ${item.title}`} />
                    </div>
                    <Button
                      variant={score ? "default" : "outline"}
                      fullWidth
                      onClick={() => {
                        setItems((list) => list.filter((entry) => entry.id !== item.id))
                        toast.success(`دیدگاهِ «${item.title}» ثبت شد.`)
                      }}
                    >
                      <ChatIcon />
                      {score ? `ثبتِ دیدگاه با ${toFaDigits(score)} ستاره` : "ثبتِ دیدگاه"}
                    </Button>
                  </li>
                )
              })}
            </ul>
          )}
        </section>
      </main>
    </ControlSizeProvider>
  )
}