دیدگاههای در انتظار
پیشنمایش تمامصفحهپنلِ حسابِ مشتری: زبانههای در انتظارِ دیدگاه، دیدگاهها و پرسشها؛ کالاهای خریدهشده با امتیازِ ستارهای و ثبتِ دیدگاهی که بعد از امتیاز برجسته میشود.
۱. نصب کامپوننتها
این بلاک از ۷ آیتم دیگ استفاده میکند: 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.jsonpnpm 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.jsonyarn 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.jsonbunx --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>
)
}