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

پلن‌ها با پرداخت ماهانه و سالانه، پلن پیشنهادی و سؤالات متداول.

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

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

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

۲. کد صفحه

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

pricing.tsx
"use client"

import * as React from "react"

import { FlashIcon, ShieldCheckIcon, SparklesIcon } from "@/components/icons"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Button } from "@/components/ui/button"
import {
  PricingCard,
  PricingCardDescription,
  PricingCardFeature,
  PricingCardFeatures,
  PricingCardFeaturesHeader,
  PricingCardHeader,
  PricingCardPrice,
  PricingCardIcon,
  PricingCardPanel,
  PricingCardTitle,
} from "@/components/ui/pricing-card"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatNumber } from "@/lib/persian"

const PLANS = [
  {
    id: "basic",
    icon: SparklesIcon,
    title: "پایه",
    description: "برای شروع و آزمودنِ امکانات",
    monthly: 0,
    includes: "امکاناتِ پلنِ رایگان:",
    features: ["۱ پروژه", "۱ عضو", "۵۰۰ مگابایت فضا"],
    missing: ["پشتیبانی ویژه", "گزارش پیشرفته"],
    highlighted: false,
  },
  {
    id: "pro",
    icon: FlashIcon,
    title: "حرفه‌ای",
    description: "برای تیم‌های کوچک که سریع رشد می‌کنند",
    monthly: 490_000,
    includes: "همهٔ امکاناتِ پایه، به‌علاوه:",
    features: ["۱۰ پروژه", "۵ عضو", "۲۰ گیگابایت فضا", "پشتیبانی ویژه"],
    missing: ["گزارش پیشرفته"],
    highlighted: true,
  },
  {
    id: "team",
    icon: ShieldCheckIcon,
    title: "سازمانی",
    description: "برای سازمان‌ها با نیازِ امنیت و کنترل",
    monthly: 1_900_000,
    includes: "همهٔ امکاناتِ حرفه‌ای، به‌علاوه:",
    features: ["پروژه و عضوِ نامحدود", "فضای نامحدود", "پشتیبانی ویژه", "گزارش پیشرفته", "ورودِ یکپارچه (SSO)"],
    missing: [],
    highlighted: false,
  },
]
const FAQ = [
  { q: "آیا می‌توانم پلن را عوض کنم؟", a: "بله، هر زمان؛ مبلغ به نسبت روزهای باقی‌مانده حساب می‌شود." },
  { q: "پرداخت سالانه چقدر تخفیف دارد؟", a: "دو ماه رایگان: مبلغ سالانه برابر ده ماه است." },
  { q: "آیا فاکتور رسمی صادر می‌شود؟", a: "بله، برای همهٔ پلن‌های پولی." },
]

export default function PricingBlock() {
  const [period, setPeriod] = React.useState("monthly")
  const yearly = period === "yearly"

  return (
    <ControlSizeProvider size="xl" asChild>
    <main className="mx-auto flex w-full max-w-5xl flex-col items-center gap-8 p-4 sm:p-8">
      {/* سلسله‌مراتب: عنوان و زیرعنوان یک گروهِ نزدیک‌اند، کنترل با فاصلهٔ بیشتر جدا. */}
      <header className="flex flex-col items-center gap-6 text-center">
        <div className="flex flex-col items-center gap-2">
          <h1 className="text-3xl font-bold leading-snug">پلنی که به کارتان می‌آید</h1>
          <p className="text-muted-foreground">۱۴ روز رایگان امتحان کنید، بدون نیاز به کارت بانکی.</p>
        </div>
        {/* انتخابِ دوره با تب؛ مزیتِ سالانه کنارِ همان گزینه. */}
        <ToggleGroup type="single" variant="segmented" value={period} onValueChange={(value) => value && setPeriod(value as string)} aria-label="دورهٔ پرداخت">
          <ToggleGroupItem value="monthly">ماهانه</ToggleGroupItem>
          <ToggleGroupItem value="yearly">سالانه · ۲ ماه رایگان</ToggleGroupItem>
        </ToggleGroup>
      </header>

      <section className="grid grid-cols-1 w-full gap-4 md:grid-cols-3" aria-label="پلن‌ها">
        {PLANS.map((plan) => (
          <PricingCard key={plan.id} highlighted={plan.highlighted} badge="محبوب">
            {/* بخشِ تصمیم: نام، قیمت و دکمه کنارِ هم؛ امکانات بعد از جداکننده. */}
            <div className="flex flex-col gap-5">
              <PricingCardIcon>
                <plan.icon />
              </PricingCardIcon>
              <PricingCardHeader>
                <PricingCardTitle>{plan.title}</PricingCardTitle>
                <PricingCardDescription>{plan.description}</PricingCardDescription>
              </PricingCardHeader>
              <PricingCardPrice
                amount={plan.monthly === 0 ? "رایگان" : formatNumber(yearly ? plan.monthly * 10 : plan.monthly)}
                period={plan.monthly === 0 ? "برای همیشه" : yearly ? "تومان / سال" : "تومان / ماه"}
              />
              <div className="flex flex-col gap-2">
                {/* دکمهٔ توپر در هر سه کارت؛ روی کارتِ بنفش سفید با متنِ بنفش (بیشترین تضاد). */}
                <Button
                  fullWidth
                  className={plan.highlighted ? "[--primary-foreground:var(--brand-secondary-foreground)] [--primary:var(--brand-secondary)]" : undefined}
                >
                  {plan.monthly === 0 ? "شروع رایگان" : `انتخاب ${plan.title}`}
                </Button>
                <p className="text-center text-xs text-muted-foreground">
                  {plan.monthly === 0 ? "بدون نیاز به کارت بانکی" : "۱۴ روز رایگان، لغو در هر زمان"}
                </p>
              </div>
            </div>
            <PricingCardPanel>
              <PricingCardFeaturesHeader title="امکانات" description={plan.includes} />
              <PricingCardFeatures>
                {plan.features.map((feature) => (
                  <PricingCardFeature key={feature}>{feature}</PricingCardFeature>
                ))}
                {plan.missing.map((feature) => (
                  <PricingCardFeature key={feature} included={false}>
                    {feature}
                  </PricingCardFeature>
                ))}
              </PricingCardFeatures>
            </PricingCardPanel>
          </PricingCard>
        ))}
      </section>

      <section className="mt-8 w-full max-w-2xl" aria-labelledby="faq-title">
        <h2 id="faq-title" className="mb-2 text-xl font-semibold">
          سؤالات متداول
        </h2>
        <Accordion type="single" collapsible>
          {FAQ.map((item) => (
            <AccordionItem key={item.q} value={item.q}>
              <AccordionTrigger>{item.q}</AccordionTrigger>
              <AccordionContent className="text-muted-foreground">{item.a}</AccordionContent>
            </AccordionItem>
          ))}
        </Accordion>
      </section>
    </main>
    </ControlSizeProvider>
  )
}