کیف پول
پیشنمایش تمامصفحهموجودی، افزایش اعتبار و آخرین تراکنشهای کیف پول.
۱. نصب کامپوننتها
این بلاک از ۱۲ آیتم دیگ استفاده میکند: badgebank-logobuttoncardcontrol-sizedialogiconspersianstat-tiletext-fieldtoasttoggle-group
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/bank-logo.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/dialog.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/stat-tile.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/badge.json https://docs.digdesign.ir/r/bank-logo.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/dialog.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/stat-tile.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/badge.json https://docs.digdesign.ir/r/bank-logo.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/dialog.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/stat-tile.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/badge.json https://docs.digdesign.ir/r/bank-logo.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/dialog.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/stat-tile.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 بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
wallet.tsx
"use client"
import * as React from "react"
import { BankLogo, getBankBySheba } from "@/components/ui/bank-logo"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Badge } from "@/components/ui/badge"
import { WalletIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { StatTile, StatTileHeader, StatTileIcon, StatTileLabel, StatTileUnit, StatTileValue } from "@/components/ui/stat-tile"
import { TextField } from "@/components/ui/text-field"
import { toast } from "@/components/ui/toast"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, formatJalaliDateTime, formatNumber } from "@/lib/persian"
type Entry = { id: number; title: string; date: string; amount: number }
const INITIAL: Entry[] = [
{ id: 1, title: "افزایش موجودی", date: "2026-09-29T10:20:00", amount: 500_000 },
{ id: 2, title: "خرید از فروشگاه دیگ", date: "2026-09-28T18:05:00", amount: -245_000 },
{ id: 3, title: "بازگشت وجه سفارش ۱۰۴۰", date: "2026-09-26T09:41:00", amount: 120_000 },
{ id: 4, title: "خرید شارژ", date: "2026-09-25T21:12:00", amount: -50_000 },
]
const PRESETS = [100_000, 200_000, 500_000]
const MIN_AMOUNT = 10_000
/** مبلغِ علامتدار: عدد و علامت یک تکهٔ چپبهراستاند تا علامت به آن سرِ عدد نپرد. */
function SignedAmount({ value }: { value: number }) {
return (
<span>
<bdi dir="ltr">
{value > 0 ? "+" : "−"}
{formatNumber(Math.abs(value))}
</bdi>{" "}
تومان
</span>
)
}
export default function WalletBlock() {
const [balance, setBalance] = React.useState(1_345_000)
const [history, setHistory] = React.useState(INITIAL)
const [dialog, setDialog] = React.useState<"charge" | "withdraw" | null>(null)
const [charge, setCharge] = React.useState("200000")
const [withdraw, setWithdraw] = React.useState("")
const [sheba, setSheba] = React.useState("")
const withdrawValue = Number(withdraw || 0)
const overBalance = withdrawValue > balance
const shebaBank = getBankBySheba(sheba)
const canWithdraw = withdrawValue >= MIN_AMOUNT && !overBalance && sheba.length === 24 && Boolean(shebaBank)
function record(title: string, amount: number) {
setBalance((value) => value + amount)
setHistory((list) => [{ id: Date.now(), title, date: new Date().toISOString(), amount }, ...list])
}
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-2xl flex-col gap-4 p-4 sm:p-6">
<h1 className="sr-only">کیف پول</h1>
<StatTile>
<StatTileHeader>
<StatTileIcon tone="accent">
<WalletIcon />
</StatTileIcon>
<StatTileLabel>موجودی کیف پول</StatTileLabel>
</StatTileHeader>
<StatTileValue className="text-4xl">
{formatNumber(balance)}
<StatTileUnit>تومان</StatTileUnit>
</StatTileValue>
<div className="flex gap-2">
<Button onClick={() => setDialog("charge")}>افزایش موجودی</Button>
<Button variant="outline" onClick={() => setDialog("withdraw")} disabled={balance < MIN_AMOUNT}>
برداشت به حساب
</Button>
</div>
</StatTile>
<Card>
<CardHeader>
<CardTitle>تراکنشهای اخیر</CardTitle>
</CardHeader>
<CardContent className="flex flex-col divide-y divide-border">
{history.map((item) => (
<div key={item.id} className="flex items-center justify-between gap-3 py-3">
<div className="flex min-w-0 flex-col gap-1">
<span className="truncate text-sm font-medium">{item.title}</span>
<span className="text-xs text-muted-foreground">{formatJalaliDateTime(item.date)}</span>
</div>
<Badge variant="soft" color={item.amount > 0 ? "success" : "default"} className="shrink-0">
<SignedAmount value={item.amount} />
</Badge>
</div>
))}
</CardContent>
</Card>
<Dialog open={dialog === "charge"} onOpenChange={(open) => !open && setDialog(null)}>
<ControlSizeProvider size="xl" asChild>
<DialogContent>
<DialogHeader>
<DialogTitle>افزایش موجودی</DialogTitle>
<DialogDescription>مبلغ از درگاه بانکی پرداخت میشود.</DialogDescription>
</DialogHeader>
<ToggleGroup
type="single"
variant="segmented"
fullWidth
value={charge}
onValueChange={(value) => value && setCharge(value as string)}
aria-label="مبلغ پیشنهادی"
>
{PRESETS.map((value) => (
<ToggleGroupItem key={value} value={String(value)}>
{formatNumber(value)}
</ToggleGroupItem>
))}
</ToggleGroup>
<TextField
label="مبلغ"
inputMode="numeric" dir="rtl"
groupDigits
endContent="تومان"
value={charge}
onValueChange={setCharge}
/>
<DialogFooter>
<Button
fullWidth
disabled={Number(charge) < MIN_AMOUNT}
onClick={() => {
record("افزایش موجودی", Number(charge))
setDialog(null)
toast.success("موجودی افزایش یافت.", {
description: `${formatCurrency(Number(charge))} به کیف پول اضافه شد.`,
})
}}
>
پرداخت {formatCurrency(Number(charge || 0))}
</Button>
</DialogFooter>
</DialogContent>
</ControlSizeProvider>
</Dialog>
<Dialog open={dialog === "withdraw"} onOpenChange={(open) => !open && setDialog(null)}>
<ControlSizeProvider size="xl" asChild>
<DialogContent>
<DialogHeader>
<DialogTitle>برداشت به حساب</DialogTitle>
<DialogDescription>مبلغ تا پایان روز کاری بعد به حساب شما واریز میشود.</DialogDescription>
</DialogHeader>
<TextField
label="مبلغ"
inputMode="numeric" dir="rtl"
groupDigits
endContent="تومان"
value={withdraw}
onValueChange={setWithdraw}
invalid={overBalance}
errorMessage="مبلغ از موجودی کیف پول بیشتر است."
description={`موجودی: ${formatCurrency(balance)}`}
/>
<TextField
label="شماره شبا"
inputMode="numeric"
dir="ltr"
startContent="IR"
digitGroups={[2, 4, 4, 4, 4, 4, 2]}
value={sheba}
onValueChange={setSheba}
endContent={<BankLogo sheba={sheba} size="sm" showName />}
invalid={sheba.length >= 5 && !shebaBank}
errorMessage="کد بانک در این شبا شناخته نشد."
/>
<DialogFooter>
<Button
fullWidth
disabled={!canWithdraw}
onClick={() => {
record(`برداشت به ${shebaBank?.name ?? "حساب"}`, -withdrawValue)
setDialog(null)
setWithdraw("")
setSheba("")
toast.success("درخواست برداشت ثبت شد.", { description: "تا پایان روز کاری بعد واریز میشود." })
}}
>
برداشت {formatCurrency(withdrawValue)}
</Button>
</DialogFooter>
</DialogContent>
</ControlSizeProvider>
</Dialog>
</main>
</ControlSizeProvider>
)
}