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

ثبت‌نام (ساده)

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

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

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

این بلاک از ۱۰ آیتم دیگ استفاده می‌کند: buttoncheckboxcontrol-sizeiconsinput-otplabellinkpassword-fieldpersiantext-field

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

۲. کد صفحه

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

signup-simple.tsx
"use client"

import * as React from "react"
import NextLink from "next/link"

import { CheckIcon, SparklesIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"
import { Label } from "@/components/ui/label"
import { Link } from "@/components/ui/link"
import { PasswordField } from "@/components/ui/password-field"
import { TextField } from "@/components/ui/text-field"
import { isValidMobile, toFaDigits } from "@/lib/persian"

/** نشانِ برند بالای فرم. */
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 SignupSimpleBlock() {
  const [password, setPassword] = React.useState("")
  const [confirm, setConfirm] = React.useState("")
  const [accepted, setAccepted] = React.useState(false)
  const [sending, setSending] = React.useState(false)
  const [mobile, setMobile] = React.useState("")
  // بعد از ثبت‌نام کاربر باید بداند قدمِ بعد چیست: تأییدِ شماره، بعد ورود.
  const [step, setStep] = React.useState<"form" | "verify" | "done">("form")
  const mismatch = confirm.length > 0 && confirm !== password

  async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    if (mismatch || !accepted || !isValidMobile(mobile)) return
    setSending(true)
    // جای درخواستِ واقعی ثبت‌نام و ارسالِ پیامک.
    await new Promise((resolve) => setTimeout(resolve, 900))
    setSending(false)
    setStep("verify")
  }

  async function verify() {
    setSending(true)
    await new Promise((resolve) => setTimeout(resolve, 700))
    setSending(false)
    setStep("done")
  }

  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">
          {step === "form" && (
            <>
              <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 text-muted-foreground">۱۴ روز رایگان، بدونِ کارتِ بانکی.</p>
                </div>
              </header>
              <form onSubmit={onSubmit} className="flex flex-col gap-4">
                <TextField label="نام و نام خانوادگی" name="fullName" autoComplete="name" required />
                <TextField
                  label="شماره موبایل"
                  name="mobile"
                  type="tel"
                  dir="ltr"
                  maxLength={11}
                  autoComplete="tel"
                  placeholder="۰۹۱۲۳۴۵۶۷۸۹"
                  value={mobile}
                  onValueChange={setMobile}
                  description="کد تأیید به همین شماره پیامک می‌شود."
                  required
                  validate={(value) => (isValidMobile(value) ? undefined : "شماره موبایل باید با ۰۹ شروع شود و ۱۱ رقم باشد.")}
                />
                <PasswordField label="رمز عبور" name="password" autoComplete="new-password" value={password} onValueChange={setPassword} strength requirements required />
                <PasswordField
                  label="تکرار رمز عبور"
                  name="confirm"
                  autoComplete="new-password"
                  value={confirm}
                  onValueChange={setConfirm}
                  invalid={mismatch}
                  errorMessage="با رمز بالا یکی نیست."
                  required
                />
                <div className="flex items-start gap-2">
                  <Checkbox id="terms" name="terms" checked={accepted} onCheckedChange={(checked) => setAccepted(checked === true)} required />
                  <Label htmlFor="terms" className="leading-6 font-normal">
                    <span>
                      <Link href="#">قوانین و مقررات</Link> را خوانده‌ام و می‌پذیرم.
                    </span>
                  </Label>
                </div>
                <Button type="submit" fullWidth loading={sending} disabled={!accepted || mismatch} className="mt-4">
                  ساخت حساب
                </Button>
              </form>
              <p className="text-center text-sm text-muted-foreground">
                حساب دارید؟ <Link href="/login">وارد شوید</Link>
              </p>
            </>
          )}

          {step === "verify" && (
            <>
              <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(mobile)}</bdi> پیامک شد.
                  </p>
                </div>
              </header>
              <InputOTP aria-label="کد تأیید" onComplete={verify} disabled={sending} className="mx-auto">
                <InputOTPGroup>
                  {[0, 1, 2, 3, 4, 5].map((index) => (
                    <InputOTPSlot key={index} index={index} />
                  ))}
                </InputOTPGroup>
              </InputOTP>
              <p className="text-center text-sm text-muted-foreground">
                شماره اشتباه است؟{" "}
                <Button variant="link" className="h-auto p-0" onClick={() => setStep("form")}>
                  ویرایش شماره
                </Button>
              </p>
            </>
          )}

          {step === "done" && (
            <div className="flex flex-col items-center gap-4 text-center" role="status">
              <span aria-hidden className="flex size-12 items-center justify-center rounded-xl bg-success-subtle text-success [&_svg]:size-6">
                <CheckIcon />
              </span>
              <div className="flex flex-col gap-2">
                <h1 className="text-2xl font-bold">حساب شما ساخته شد</h1>
                <p className="text-sm text-muted-foreground">خوش آمدید! حالا می‌توانید از همهٔ امکانات استفاده کنید.</p>
              </div>
              <Button asChild fullWidth className="mt-4">
                <NextLink href="/">رفتن به داشبورد</NextLink>
              </Button>
            </div>
          )}
        </ControlSizeProvider>
        <p className="pt-10 text-xs text-muted-foreground">
          <Link href="#" size="sm">
            قوانین
          </Link>{" "}
          و{" "}
          <Link href="#" size="sm">
            حریم خصوصی
          </Link>
        </p>
      </div>
    </main>
  )
}