بلاک‌ها · حساب کاربری

صورتحساب و اشتراک

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

پلنِ فعلی روی کارتِ برند با تمدید، ارتقا و لغو با تأیید؛ مصرفِ هر سهمیه با هشدارِ نزدیک به سقف؛ کارتِ بانکیِ ماسک‌شده و تاریخچهٔ فاکتورها با وضعیت، پرداختِ دوباره و دانلود.

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

این بلاک از ۱۰ آیتم دیگ استفاده می‌کند: alert-dialogbadgebuttoncardcontrol-sizeiconspersianprogresstabletoast

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

۲. کد صفحه

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

billing.tsx
"use client"

import * as React from "react"

import { CheckIcon, CreditCardIcon, DownloadIcon, FlashIcon } from "@/components/icons"
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Progress } from "@/components/ui/progress"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { toast } from "@/components/ui/toast"
import { formatCurrency, formatJalali, toFaDigits } from "@/lib/persian"

const USAGE = [
  { label: "اعضای تیم", used: 4, limit: 5, unit: "نفر" },
  { label: "پروژه‌ها", used: 7, limit: 10, unit: "پروژه" },
  { label: "فضای ذخیره‌سازی", used: 17.4, limit: 20, unit: "گیگابایت" },
]

const INVOICES = [
  { id: "1405-0718", date: "2026-10-01", amount: 490_000, status: "paid" as const },
  { id: "1405-0612", date: "2026-09-01", amount: 490_000, status: "paid" as const },
  { id: "1405-0507", date: "2026-08-01", amount: 490_000, status: "failed" as const },
  { id: "1405-0405", date: "2026-07-01", amount: 290_000, status: "paid" as const },
]

/**
 * صورتحساب و اشتراک: پلنِ فعلی روی کارتِ وارونهٔ برند با تمدید و ارتقا، مصرفِ
 * هر سهمیه با هشدارِ نزدیک به سقف، روشِ پرداخت و تاریخچهٔ فاکتورها با وضعیت و
 * دانلود. لغوِ اشتراک فقط با تأییدِ دوباره.
 */
export default function BillingBlock() {
  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-col gap-1">
          <h1 className="text-2xl font-bold">صورتحساب و اشتراک</h1>
          <p className="text-muted-foreground">پلن، مصرف و فاکتورهای حساب.</p>
        </header>

        <div className="grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1.3fr)_minmax(0,1fr)]">
          {/* پلنِ فعلی: کارتِ برند با رنگ‌های بازتعریف‌شده. */}
          <section
            aria-labelledby="billing-plan"
            className="relative flex flex-col gap-6 overflow-hidden rounded-xl bg-[var(--dig-violet-800)] p-6 text-[var(--dig-white)] sm:p-8 [--muted-foreground:var(--dig-violet-200)] [--primary-foreground:var(--dig-violet-800)] [--primary:var(--dig-white)]"
          >
            <span aria-hidden className="absolute -end-20 -top-20 size-64 rounded-full bg-[var(--dig-violet-700)]" />
            <div className="relative flex items-start justify-between gap-4">
              <div className="flex flex-col gap-2">
                <span className="text-sm text-muted-foreground">پلنِ فعلی</span>
                <h2 id="billing-plan" className="flex items-center gap-2 text-3xl font-black">
                  حرفه‌ای
                  <Badge className="bg-[var(--dig-amber-400)] text-[var(--dig-graphite-950)]">فعال</Badge>
                </h2>
              </div>
              <span aria-hidden className="flex size-12 items-center justify-center rounded-xl bg-[var(--dig-violet-600)] [&>svg]:size-6">
                <FlashIcon />
              </span>
            </div>
            <div className="relative flex items-baseline gap-2">
              <span className="text-4xl font-black tabular-nums">{formatCurrency(490_000).replace(" تومان", "")}</span>
              <span className="text-muted-foreground">تومان / ماه</span>
            </div>
            <p className="relative text-sm text-muted-foreground">
              تمدیدِ خودکار در {formatJalali(new Date("2026-11-01"), "long")} · ۵ عضو، ۱۰ پروژه، ۲۰ گیگابایت
            </p>
            <div className="relative mt-2 flex flex-wrap gap-3">
              <Button>ارتقا به سازمانی</Button>
              <AlertDialog>
                <AlertDialogTrigger asChild>
                  <Button variant="outline" className="border-[var(--dig-violet-500)] bg-[var(--dig-violet-800)] text-[var(--dig-white)] hover:bg-[var(--dig-violet-700)] hover:text-[var(--dig-white)]">
                    لغوِ اشتراک
                  </Button>
                </AlertDialogTrigger>
                <ControlSizeProvider size="xl" asChild>
                  <AlertDialogContent>
                    <AlertDialogHeader>
                      <AlertDialogTitle>اشتراک لغو شود؟</AlertDialogTitle>
                      <AlertDialogDescription>
                        تا {formatJalali(new Date("2026-11-01"), "long")} همهٔ امکانات فعال می‌ماند؛ بعد از آن حساب به پلنِ پایه برمی‌گردد و پروژه‌های اضافه فقط‌خواندنی می‌شوند.
                      </AlertDialogDescription>
                    </AlertDialogHeader>
                    <AlertDialogFooter>
                      <AlertDialogCancel>منصرف شدم</AlertDialogCancel>
                      <AlertDialogAction variant="destructive" onClick={() => toast.success("اشتراک تا پایانِ دوره فعال می‌ماند و تمدید نمی‌شود.")}>
                        لغوِ اشتراک
                      </AlertDialogAction>
                    </AlertDialogFooter>
                  </AlertDialogContent>
                </ControlSizeProvider>
              </AlertDialog>
            </div>
          </section>

          {/* مصرف: هر سهمیه با نوار؛ نزدیک به سقف رنگِ هشدار و پیشنهادِ ارتقا. */}
          <Card>
            <CardHeader>
              <CardTitle className="text-base">مصرفِ این دوره</CardTitle>
              <CardDescription>تا {formatJalali(new Date("2026-11-01"), "long")}</CardDescription>
            </CardHeader>
            <CardContent className="flex flex-col gap-5">
              {USAGE.map((item) => {
                const percent = (item.used / item.limit) * 100
                const near = percent >= 85
                return (
                  <div key={item.label} className="flex flex-col gap-2">
                    <div className="flex items-baseline justify-between gap-2 text-sm">
                      <span className="font-medium">{item.label}</span>
                      <span className={near ? "font-semibold text-warning tabular-nums" : "text-muted-foreground tabular-nums"}>
                        {toFaDigits(item.used)} از {toFaDigits(item.limit)} {item.unit}
                      </span>
                    </div>
                    <Progress value={percent} color={near ? "warning" : "primary"} aria-label={`مصرفِ ${item.label}`} />
                  </div>
                )
              })}
              <p className="rounded-lg bg-[color-mix(in_oklab,var(--dig-amber-500)_10%,var(--background))] p-3 text-sm leading-6">
                فضای ذخیره‌سازی نزدیکِ سقف است؛ با پلنِ سازمانی نامحدود می‌شود.
              </p>
            </CardContent>
          </Card>
        </div>

        <div className="grid grid-cols-1 items-start gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)]">
          <Card>
            <CardHeader>
              <CardTitle className="text-base">روشِ پرداخت</CardTitle>
            </CardHeader>
            <CardContent className="flex flex-col gap-4">
              {/* کارتِ بانکی: شمارهٔ ماسک‌شده و تاریخِ انقضا. */}
              <div className="flex aspect-[1.6/1] flex-col justify-between rounded-xl bg-linear-to-br from-[var(--dig-graphite-800)] to-[var(--dig-graphite-950)] p-5 text-[var(--dig-white)]">
                <div className="flex items-center justify-between">
                  <span className="text-sm font-semibold">بانکِ سکه</span>
                  <CreditCardIcon aria-hidden className="size-6 text-[var(--dig-neutral-400)]" />
                </div>
                <bdi dir="ltr" className="text-start text-lg tracking-widest tabular-nums">
                  {toFaDigits("6037 •••• •••• 4521")}
                </bdi>
                <div className="flex items-center justify-between text-xs text-[var(--dig-neutral-300)]">
                  <span>سارا محمدی</span>
                  <span className="tabular-nums">{toFaDigits("08/07")}</span>
                </div>
              </div>
              <p className="flex items-center gap-2 text-sm text-muted-foreground">
                <CheckIcon aria-hidden className="size-4 text-success" />
                برداشتِ خودکار برای تمدید فعال است.
              </p>
              <Button variant="outline" fullWidth>
                تغییرِ کارت
              </Button>
            </CardContent>
          </Card>

          <section aria-labelledby="billing-invoices" className="flex flex-col gap-4">
            <h2 id="billing-invoices" className="text-lg font-bold">
              فاکتورها
            </h2>
            <div>
              <Table stackOnMobile variant="card">
                <TableHeader>
                  <TableRow>
                    <TableHead>شمارهٔ فاکتور</TableHead>
                    <TableHead>تاریخ</TableHead>
                    <TableHead>مبلغ</TableHead>
                    <TableHead>وضعیت</TableHead>
                    <TableHead className="w-12 px-0">
                      <span className="sr-only">دانلود</span>
                    </TableHead>
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {INVOICES.map((invoice) => (
                    <TableRow key={invoice.id}>
                      <TableCell className="font-medium tabular-nums">{toFaDigits(invoice.id)}</TableCell>
                      <TableCell>{formatJalali(new Date(invoice.date), "long")}</TableCell>
                      <TableCell className="tabular-nums">{formatCurrency(invoice.amount)}</TableCell>
                      <TableCell>
                        {invoice.status === "paid" ? (
                          <Badge variant="soft" color="success">
                            پرداخت‌شده
                          </Badge>
                        ) : (
                          <span className="flex items-center gap-2">
                            <Badge variant="soft" color="destructive">
                              ناموفق
                            </Badge>
                            <Button variant="link" className="h-auto p-0 text-sm">
                              پرداختِ دوباره
                            </Button>
                          </span>
                        )}
                      </TableCell>
                      <TableCell className="w-12 px-0 text-center">
                        <Button variant="ghost" size="icon" aria-label={`دانلودِ فاکتورِ ${toFaDigits(invoice.id)}`}>
                          <DownloadIcon />
                        </Button>
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            </div>
          </section>
        </div>
      </main>
    </ControlSizeProvider>
  )
}