بلاک‌ها · بانکی و پرداخت

شارژ و بستهٔ اینترنت: موبایل، اپراتور، مبلغ‌های آماده و پرداخت.

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

این بلاک از ۸ آیتم دیگ استفاده می‌کند: buttoncardcontrol-sizelabelpersiantext-fieldtoasttoggle-group

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

۲. کد صفحه

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

top-up.tsx
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Label } from "@/components/ui/label"
import { TextField } from "@/components/ui/text-field"
import { toast } from "@/components/ui/toast"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, formatNumber, isValidMobile } from "@/lib/persian"

const OPERATORS = [
  { value: "mci", label: "همراه اول", prefixes: ["0910", "0911", "0912", "0913", "0914", "0915", "0916", "0917", "0918", "0919", "0990", "0991", "0992", "0993", "0994"] },
  { value: "mtn", label: "ایرانسل", prefixes: ["0900", "0901", "0902", "0903", "0904", "0905", "0930", "0933", "0935", "0936", "0937", "0938", "0939", "0941"] },
  { value: "rightel", label: "رایتل", prefixes: ["0920", "0921", "0922"] },
]

/** اپراتور از ۴ رقمِ اولِ موبایل؛ کاربر هنوز می‌تواند دستی عوضش کند (مثلاً بعد از ترابرد). */
const operatorOf = (mobile: string) => OPERATORS.find((item) => item.prefixes.includes(mobile.slice(0, 4)))?.value
const PRESETS = [20_000, 50_000, 100_000, 200_000]

export default function TopUpBlock() {
  const [mobile, setMobile] = React.useState("")
  const [operator, setOperator] = React.useState("mci")
  const [preset, setPreset] = React.useState("50000")
  const [custom, setCustom] = React.useState("")
  const amount = Number(custom || preset || 0)

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="flex min-h-svh items-start justify-center bg-muted p-4 sm:items-center">
        <Card className="w-full max-w-md">
          <CardHeader>
            <CardTitle asChild>
              <h1>خرید شارژ</h1>
            </CardTitle>
            <CardDescription>شارژ مستقیم برای هر شماره.</CardDescription>
          </CardHeader>
          <ControlSizeProvider size="xl">
            <form
              onSubmit={(event) => {
                event.preventDefault()
                toast.success(`شارژ ${formatCurrency(amount)} خریداری شد.`)
              }}
            >
              <CardContent className="flex flex-col gap-5">
                <TextField
                  label="شماره موبایل"
                  name="mobile"
                  type="tel"
                  dir="ltr"
                  maxLength={11}
                  placeholder="۰۹۱۲۳۴۵۶۷۸۹"
                  value={mobile}
                  onValueChange={(next) => {
                    setMobile(next)
                    const detected = operatorOf(next)
                    if (detected && next.length === 4) setOperator(detected)
                  }}
                  required
                  validate={(value) => (isValidMobile(value) ? undefined : "شماره موبایل باید با ۰۹ شروع شود و ۱۱ رقم باشد.")}
                />
                <div className="flex flex-col gap-2">
                  <Label id="operator-label">اپراتور</Label>
                  <ToggleGroup
                    type="single"
                    variant="segmented"
                    fullWidth
                    value={operator}
                    onValueChange={(value) => value && setOperator(value as string)}
                    aria-labelledby="operator-label"
                  >
                    {OPERATORS.map((item) => (
                      <ToggleGroupItem key={item.value} value={item.value} className="gap-2">
                        {/* لوگوهای اپراتور در public/logos/operators */}
                        {/* eslint-disable-next-line @next/next/no-img-element -- لوگوی کوچکِ ثابت */}
                        <img src={`/logos/operators/${item.value}.svg`} alt="" className="size-5" />
                        {item.label}
                      </ToggleGroupItem>
                    ))}
                  </ToggleGroup>
                </div>
                <div className="flex flex-col gap-2">
                  <Label id="amount-label">مبلغ شارژ (تومان)</Label>
                  <ToggleGroup
                    type="single"
                    variant="segmented"
                    fullWidth
                    value={custom ? "" : preset}
                    onValueChange={(value) => {
                      if (!value) return
                      setPreset(value as string)
                      setCustom("")
                    }}
                    aria-labelledby="amount-label"
                    className="flex-wrap"
                  >
                    {PRESETS.map((value) => (
                      <ToggleGroupItem key={value} value={String(value)}>
                        {formatNumber(value)}
                      </ToggleGroupItem>
                    ))}
                  </ToggleGroup>
                  <TextField
                    aria-label="مبلغ دلخواه"
                    placeholder="یا مبلغ دلخواه"
                    inputMode="numeric" dir="rtl"
                    groupDigits
                    endContent="تومان"
                    value={custom}
                    onValueChange={setCustom}
                  />
                </div>
              </CardContent>
              <CardFooter>
                <Button type="submit" fullWidth disabled={mobile.length !== 11 || amount < 10_000}>
                  پرداخت {amount ? formatCurrency(amount) : ""}
                </Button>
              </CardFooter>
            </form>
          </ControlSizeProvider>
        </Card>
      </main>
    </ControlSizeProvider>
  )
}