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

تاریخچهٔ سفارش‌ها

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

پنلِ حسابِ مشتری: ستونِ کنار با کیف پول و منو، زبانه‌های وضعیت با شمار، جست‌وجوی کدِ سفارش، کارتِ هر سفارش با عکسِ کالاها و فاکتور، و حالتِ خالیِ راهنما با قدمِ بعد.

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

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

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

۲. کد صفحه

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

account-orders.tsx
"use client"

import * as React from "react"

import { ChatIcon, CheckIcon, ChevronLeftIcon, ClockIcon, DashboardIcon, EditIcon, HeartIcon, LocationIcon, LogoutIcon, ReceiptIcon, SearchIcon, ShopIcon, UserIcon, WalletIcon } from "@/components/icons"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { SearchField } from "@/components/ui/search-field"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"


type Status = "current" | "delivered" | "returned" | "canceled"
const TABS: { id: Status; label: string }[] = [
  { id: "current", label: "جاری" },
  { id: "delivered", label: "تحویل شده" },
  { id: "returned", label: "مرجوع شده" },
  { id: "canceled", label: "لغو شده" },
]
const ORDERS: { id: string; status: Status; date: string; amount: number; items: StockImageName[] }[] = [
  { id: "1159260945", status: "delivered", date: "۳ مرداد ۱۴۰۵", amount: 2_684_000, items: ["coverNotebook", "coverOfficeSupplies", "productVase"] },
  { id: "5264036488", status: "delivered", date: "۵ فروردین ۱۴۰۵", amount: 7_890_380, items: ["coverMinimalRoom", "coverWorkspace", "coverCalculator", "productVase"] },
  { id: "4026928258", status: "delivered", date: "۹ مهر ۱۴۰۴", amount: 319_110, items: ["studentStudy"] },
  { id: "4011317730", status: "returned", date: "۱۵ تیر ۱۴۰۴", amount: 789_460, items: ["coverCalculator", "projectCafeBrand"] },
  { id: "3684817827", status: "canceled", date: "۲ تیر ۱۴۰۴", amount: 2_083_880, items: ["coverOfficeSupplies"] },
]
const STATUS: Record<Status, { label: string; color: "info" | "success" | "warning" | "default" }> = {
  current: { label: "در جریان", color: "info" },
  delivered: { label: "تحویل شده", color: "success" },
  returned: { label: "مرجوع شده", color: "warning" },
  canceled: { label: "لغو شده", color: "default" },
}

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>
  )
}

/**
 * تاریخچهٔ سفارش‌ها در پنلِ حسابِ مشتری: ستونِ کنار با کیف پول و منو، زبانه‌های وضعیت با شمار، جست‌وجو،
 * کارتِ هر سفارش (وضعیت، تاریخ، کدِ سفارش، مبلغ، عکسِ کالاها، فاکتور) و حالتِ خالیِ راهنما که می‌گوید
 * این‌جا چه می‌آید و قدمِ بعد چیست، نه فقط «سفارشی ندارید».
 */
export default function AccountOrdersBlock() {
  const [tab, setTab] = React.useState<Status>("delivered")
  const [query, setQuery] = React.useState("")
  const [searching, setSearching] = React.useState(false)
  const rows = ORDERS.filter((order) => order.status === tab && (!query || order.id.includes(query.replace(/[۰-۹]/g, (d) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(d))))))
  const count = (status: Status) => ORDERS.filter((order) => order.status === status).length

  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="orders" />
        </div>

        <section aria-labelledby="orders-title" className="flex min-w-0 flex-col rounded-xl border border-border bg-card">
          <header className="flex items-center justify-between gap-3 px-5 pt-5">
            <h1 id="orders-title" className="text-xl font-black">
              تاریخچهٔ سفارش‌ها
            </h1>
            <Button variant="ghost" size="icon" aria-label="جست‌وجو در سفارش‌ها" aria-pressed={searching} onClick={() => setSearching((value) => !value)}>
              <SearchIcon />
            </Button>
          </header>
          {searching && (
            <div className="px-5 pt-4">
              <SearchField aria-label="کدِ سفارش" placeholder="کدِ سفارش" inputMode="numeric" value={query} onValueChange={setQuery} autoFocus />
            </div>
          )}
          <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 Status)} aria-label="وضعیتِ سفارش" className="w-max gap-1">
              {TABS.map((item) => (
                <ToggleGroupItem
                  key={item.id}
                  value={item.id}
                  className="gap-2 rounded-none rounded-t-lg border-b-2 border-transparent px-3 bg-card hover:bg-subtle data-[state=on]:border-primary data-[state=on]:bg-card data-[state=on]:font-bold"
                >
                  {item.label}
                  <span className="flex min-w-6 items-center justify-center rounded-md bg-muted px-1.5 text-xs tabular-nums">{toFaDigits(count(item.id))}</span>
                </ToggleGroupItem>
              ))}
            </ToggleGroup>
          </div>

          {rows.length === 0 ? (
            // حالتِ خالیِ راهنما: چه می‌آید این‌جا و قدمِ بعدی، نه فقط «چیزی نیست».
            <div className="flex flex-col items-center gap-5 px-6 py-14 text-center">
              <span aria-hidden className="relative flex size-28 items-center justify-center rounded-full bg-[color-mix(in_oklab,var(--dig-violet-500)_10%,var(--card))]">
                <span className="flex size-16 rotate-[-6deg] items-center justify-center rounded-xl bg-card text-[var(--dig-violet-600)] shadow-card [&>svg]:size-8">
                  <ReceiptIcon />
                </span>
                <span className="absolute end-3 top-4 size-3 rounded-full bg-[var(--dig-amber-400)]" />
                <span className="absolute start-4 bottom-5 size-2 rounded-full bg-[var(--dig-violet-400)]" />
              </span>
              <div className="flex flex-col gap-2">
                <h2 className="text-lg font-bold">{query ? "سفارشی با این کد پیدا نشد" : tab === "current" ? "سفارش‌های در جریانت این‌جا می‌آیند" : "هنوز چیزی این‌جا نیست"}</h2>
                <p className="max-w-sm text-sm leading-6 text-muted-foreground">
                  {query ? "کدِ سفارش را از پیامکِ ثبتِ سفارش دوباره نگاه کن." : "بعد از خرید، وضعیتِ ارسال و تحویلِ هر سفارش را همین‌جا دنبال می‌کنی."}
                </p>
              </div>
              {query ? (
                <Button variant="outline" onClick={() => setQuery("")}>
                  پاک کردنِ جست‌وجو
                </Button>
              ) : (
                <Button>شروعِ خرید</Button>
              )}
            </div>
          ) : (
            <ul className="flex flex-col gap-4 p-5">
              {rows.map((order) => (
                <li key={order.id} className="overflow-hidden rounded-xl border border-border">
                  <a href="#" className="flex items-start justify-between gap-3 p-4 hover:bg-subtle">
                    <span className="flex min-w-0 flex-col gap-2">
                      <Badge variant="soft" color={STATUS[order.status].color} className="w-fit gap-1">
                        {order.status === "delivered" ? <CheckIcon aria-hidden /> : <ClockIcon aria-hidden />}
                        {STATUS[order.status].label}
                      </Badge>
                      <span className="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm">
                        <span>{order.date}</span>
                        <span aria-hidden className="text-muted-foreground">·</span>
                        <span className="text-muted-foreground">
                          کدِ سفارش <bdi className="font-medium text-foreground tabular-nums">{toFaDigits(order.id)}</bdi>
                        </span>
                        <span aria-hidden className="text-muted-foreground">·</span>
                        <span className="text-muted-foreground">
                          مبلغ <span className="font-bold text-foreground tabular-nums">{formatCurrency(order.amount)}</span>
                        </span>
                      </span>
                    </span>
                    <ChevronLeftIcon aria-hidden className="mt-1 size-5 shrink-0 text-muted-foreground" />
                    <span className="sr-only">جزئیاتِ سفارش</span>
                  </a>
                  <div className="flex flex-wrap items-center justify-between gap-3 border-t border-border p-4">
                    <ul aria-label="کالاها" className="flex gap-2">
                      {order.items.map((image) => (
                        <li key={image}>
                          {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
                          <img src={stockImages[image].src} alt={stockImages[image].alt} loading="lazy" className="size-14 rounded-lg object-cover" />
                        </li>
                      ))}
                    </ul>
                    <ControlSizeProvider size="lg" asChild>
                      <Button variant="ghost" className="gap-1.5 text-brand-secondary">
                        <ReceiptIcon />
                        مشاهدهٔ فاکتور
                      </Button>
                    </ControlSizeProvider>
                  </div>
                </li>
              ))}
            </ul>
          )}
        </section>
      </main>
    </ControlSizeProvider>
  )
}