خرید شارژ
پیشنمایش تمامصفحهشارژ و بستهٔ اینترنت: موبایل، اپراتور، مبلغهای آماده و پرداخت.
۱. نصب کامپوننتها
این بلاک از ۸ آیتم دیگ استفاده میکند: 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.jsonpnpm 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.jsonyarn 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.jsonbunx --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>
)
}