بلاک‌ها · ورود و ثبت‌نام

تأیید کد پیامکی (ساده)

پیش‌نمایش تمام‌صفحه

تأیید کد پیامکی، مدلِ ساده: همان فرم وسطِ صفحه، بدونِ پنلِ کناری. همان فرم، اعتبارسنجی و حالت‌های صفحهٔ اصلی.

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

این بلاک از ۶ آیتم دیگ استفاده می‌کند: 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.json
pnpm 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.json
yarn 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.json
bunx --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>
  )
}