تأیید کد پیامکی (ساده)
پیشنمایش تمامصفحهتأیید کد پیامکی، مدلِ ساده: همان فرم وسطِ صفحه، بدونِ پنلِ کناری. همان فرم، اعتبارسنجی و حالتهای صفحهٔ اصلی.
۱. نصب کامپوننتها
این بلاک از ۶ آیتم دیگ استفاده میکند: buttoncontrol-sizeiconsinput-otplinkpersian
پکیجمنیجر پیشفرض 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/input-otp.json https://docs.digdesign.ir/r/link.json https://docs.digdesign.ir/r/persian.jsonpnpm 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/input-otp.json https://docs.digdesign.ir/r/link.json https://docs.digdesign.ir/r/persian.jsonyarn 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/input-otp.json https://docs.digdesign.ir/r/link.json https://docs.digdesign.ir/r/persian.jsonbunx --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/input-otp.json https://docs.digdesign.ir/r/link.json https://docs.digdesign.ir/r/persian.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
otp-verify-simple.tsx
"use client"
import * as React from "react"
import { SparklesIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"
import { Link } from "@/components/ui/link"
import { toFaDigits } from "@/lib/persian"
const RESEND_SECONDS = 60
/** نشانِ برند بالای فرم. */
function BrandMark() {
return (
<span aria-hidden className="flex size-12 items-center justify-center rounded-xl bg-[var(--dig-violet-700)] text-[var(--dig-white)] [&>svg]:size-6">
<SparklesIcon />
</span>
)
}
/**
* تأییدِ شماره با همان ساختارِ ورود و ثبتنام: فرم در ستونِ شروع، پنلِ برند در
* ستونِ پایان. خطای کدِ نادرست زیرِ خانهها و شمارشِ معکوسِ ارسالِ دوباره.
*/
/** مدلِ ساده: همان فرم وسطِ صفحه، بدونِ پنلِ کناری؛ برای اپهایی که برند را در خودِ فرم نشان میدهند. */
export default function OtpVerifySimpleBlock() {
const [code, setCode] = React.useState("")
const [seconds, setSeconds] = React.useState(RESEND_SECONDS)
const [checking, setChecking] = React.useState(false)
const [invalid, setInvalid] = React.useState(false)
React.useEffect(() => {
if (seconds === 0) return
const timer = setTimeout(() => setSeconds((value) => value - 1), 1000)
return () => clearTimeout(timer)
}, [seconds])
async function verify(value: string) {
setChecking(true)
// جای بررسی واقعی کد؛ این نمونه فقط ۱۲۳۴۵۶ را میپذیرد.
await new Promise((resolve) => setTimeout(resolve, 700))
setChecking(false)
setInvalid(value !== "123456")
}
const countdown = toFaDigits(`${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`)
return (
<main className="flex min-h-svh flex-col items-center bg-background p-4">
<div className="flex w-full flex-1 flex-col items-center px-4 py-10">
<ControlSizeProvider size="xl" className="flex w-full max-w-sm flex-1 flex-col justify-center gap-8">
<header className="flex flex-col items-center gap-4 text-center">
<BrandMark />
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-bold">تأیید شمارهٔ موبایل</h1>
<p className="text-sm leading-6 text-muted-foreground">
کدِ ۶ رقمی پیامکشده به <bdi dir="ltr" className="font-medium text-foreground">{toFaDigits("0912 *** 4567")}</bdi> را وارد کنید.
</p>
</div>
</header>
<div className="flex flex-col items-center gap-3">
<InputOTP
aria-label="کد تأیید"
value={code}
onValueChange={(value) => {
setCode(value)
setInvalid(false)
}}
onComplete={verify}
disabled={checking}
invalid={invalid}
aria-describedby="otp-status"
>
<InputOTPGroup>
{[0, 1, 2, 3, 4, 5].map((index) => (
<InputOTPSlot key={index} index={index} />
))}
</InputOTPGroup>
</InputOTP>
<p id="otp-status" className="min-h-5 text-sm text-destructive" aria-live="polite">
{invalid ? "کد واردشده درست نیست." : ""}
</p>
<p className="text-sm text-muted-foreground">
{seconds > 0 ? (
<>
ارسالِ دوباره تا <span className="font-medium text-foreground tabular-nums">{countdown}</span>
</>
) : (
<Button variant="link" className="h-auto p-0" onClick={() => setSeconds(RESEND_SECONDS)}>
ارسالِ دوبارهٔ کد
</Button>
)}
</p>
</div>
<Button fullWidth loading={checking} disabled={code.length < 6} onClick={() => verify(code)}>
تأیید و ادامه
</Button>
<p className="text-center text-sm text-muted-foreground">
شماره اشتباه است؟ <Link href="/signup">ویرایش شماره</Link>
</p>
</ControlSizeProvider>
<p className="text-xs text-muted-foreground">
<Link href="#" size="sm">
قوانین
</Link>{" "}
و{" "}
<Link href="#" size="sm">
حریم خصوصی
</Link>
</p>
</div>
</main>
)
}