بلاک‌ها · معرفی محصول

قیمت‌گذاریِ مصرفی

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

قیمت‌گذاریِ پلکانی به اندازهٔ مصرف: اسلایدرِ حجمِ ماهانه، جدولِ پله‌ها با پلهٔ درگیرِ برجسته و سهمِ هر پله، و کارتِ تیرهٔ هزینهٔ برآوردی با میانگینِ هر واحد.

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

این بلاک از ۵ آیتم دیگ استفاده می‌کند: buttoncontrol-sizeiconspersianslider

پکیج‌منیجر پیش‌فرض 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/slider.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/slider.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/slider.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/slider.json

۲. کد صفحه

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

pricing-usage.tsx
"use client"

import * as React from "react"

import { InfoIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Slider } from "@/components/ui/slider"
import { formatNumber } from "@/lib/persian"
import { cn } from "@/lib/utils"

/** پله‌های مصرف: هر هزار پیامک در هر پله قیمتِ خودش را دارد. */
const TIERS = [
  { upTo: 10_000, price: 0, label: "۱۰ هزارِ اول" },
  { upTo: 100_000, price: 180, label: "۱۰ تا ۱۰۰ هزار" },
  { upTo: 1_000_000, price: 140, label: "۱۰۰ هزار تا ۱ میلیون" },
  { upTo: Infinity, price: 110, label: "بیش از ۱ میلیون" },
]
const STOPS = [5_000, 25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, 2_000_000]

function costOf(volume: number) {
  let rest = volume
  let from = 0
  return TIERS.map((tier) => {
    const used = Math.max(0, Math.min(rest, tier.upTo - from))
    rest -= used
    from = tier.upTo
    return { ...tier, used, cost: used * tier.price }
  })
}

/**
 * قیمت‌گذاریِ مصرفی و پلکانی: اسلایدرِ حجمِ ماهانه (پیامک یا درخواست) با پله‌های معنادار،
 * جدولِ پله‌ها که پلهٔ درگیر را برجسته و سهمِ هر پله را جدا نشان می‌دهد، و جمعِ برآوردشده با
 * توضیحِ اینکه فقط مصرفِ واقعی حساب می‌شود.
 */
export default function PricingUsageBlock() {
  const [stop, setStop] = React.useState(3)
  const volume = STOPS[stop]
  const rows = costOf(volume)
  const total = rows.reduce((sum, row) => sum + row.cost, 0)

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-5xl flex-col gap-12 px-4 py-24 sm:px-6">
        <header className="flex flex-col items-center gap-4 text-center">
          <span className="text-sm font-semibold text-brand-secondary">پرداخت به اندازهٔ مصرف</span>
          <h1 className="text-4xl leading-tight font-black text-balance sm:text-5xl">هر چه فرستادی، همان را بپرداز</h1>
          <p className="max-w-lg leading-7 text-muted-foreground">بدونِ اشتراکِ ماهانه؛ ۱۰ هزار پیامکِ اول هر ماه رایگان است و هرچه بیشتر بفرستید، هر پیامک ارزان‌تر می‌شود.</p>
        </header>

        <div className="grid grid-cols-1 gap-6 lg:grid-cols-[1.4fr_1fr]">
          <section aria-labelledby="pu-calc" className="flex flex-col gap-8 rounded-xl border border-border bg-card p-6 sm:p-8">
            <div className="flex flex-col gap-1">
              <h2 id="pu-calc" className="text-lg font-bold">
                حجمِ ماهانه
              </h2>
              <span className="text-3xl font-black tabular-nums">{formatNumber(volume)} پیامک</span>
            </div>
            <Slider
              value={[stop]}
              min={0}
              max={STOPS.length - 1}
              step={1}
              onValueChange={(value) => setStop(value[0])}
              aria-label="حجمِ ماهانهٔ پیامک"
              aria-valuetext={`${formatNumber(volume)} پیامک`}
            />
            <div className="flex justify-between text-xs text-muted-foreground">
              <span>{formatNumber(STOPS[0])}</span>
              <span>{formatNumber(STOPS[STOPS.length - 1])}</span>
            </div>
            <ul className="flex flex-col divide-y divide-border rounded-lg border border-border">
              {rows.map((row) => (
                <li key={row.label} className={cn("flex items-center justify-between gap-4 px-4 py-3 text-sm", row.used > 0 ? "bg-subtle" : "text-muted-foreground")}>
                  <span className="flex flex-col">
                    <span className="font-semibold">{row.label}</span>
                    <span className="text-xs text-muted-foreground">{row.price === 0 ? "رایگان" : `${formatNumber(row.price)} تومان هر پیامک`}</span>
                  </span>
                  <span className="tabular-nums">{row.used > 0 ? `${formatNumber(row.used)} × ` : "—"}</span>
                </li>
              ))}
            </ul>
          </section>

          <aside aria-live="polite" className="flex flex-col justify-between gap-8 rounded-xl bg-inverse p-6 text-[var(--dig-white)] [--primary-foreground:var(--dig-graphite-950)] [--primary:var(--dig-white)] sm:p-8">
            <div className="flex flex-col gap-2">
              <span className="text-sm text-[var(--dig-neutral-400)]">هزینهٔ برآوردیِ این ماه</span>
              <span className="text-4xl font-black tabular-nums">{total === 0 ? "رایگان" : `${formatNumber(Math.round(total / 1000))} هزار تومان`}</span>
              <span className="text-sm text-[var(--dig-neutral-400)]">میانگینِ هر پیامک: {volume > 0 ? formatNumber(Math.round(total / volume)) : 0} تومان</span>
            </div>
            <p className="flex items-start gap-2 text-sm leading-6 text-[var(--dig-neutral-300)]">
              <InfoIcon aria-hidden className="mt-1 size-4 shrink-0" />
              آخرِ ماه فقط پیامک‌های تحویل‌شده حساب می‌شود؛ ناموفق‌ها رایگان‌اند.
            </p>
            <div className="flex flex-col gap-2">
              <Button fullWidth>شروعِ رایگان</Button>
              <Button fullWidth variant="link" className="text-[var(--dig-white)]">
                بیش از ۲ میلیون؟ تماس با فروش
              </Button>
            </div>
          </aside>
        </div>
      </main>
    </ControlSizeProvider>
  )
}