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

فراموشی رمز (عکس)

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

فراموشی رمز، مدلِ عکس: فرم در یک ستون و عکسِ واقعیِ تمام‌قد با جملهٔ برند در ستونِ دیگر. همان فرم، اعتبارسنجی و حالت‌های صفحهٔ اصلی.

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

این بلاک از ۹ آیتم دیگ استفاده می‌کند: buttoncontrol-sizeiconsinput-otplinkpassword-fieldpersiansteppertext-field

پکیج‌منیجر پیش‌فرض 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/password-field.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/stepper.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/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/password-field.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/stepper.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/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/password-field.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/stepper.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/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/password-field.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/text-field.json

۲. کد صفحه

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

forgot-password-image.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 { ControlSizeProvider } from "@/components/ui/control-size"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"
import { Link } from "@/components/ui/link"
import { PasswordField } from "@/components/ui/password-field"
import { Stepper, StepperItem, StepperTitle } from "@/components/ui/stepper"
import { TextField } from "@/components/ui/text-field"
import { isValidMobile, toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"

/** نشانِ برند بالای فرم. */
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 ForgotPasswordImageBlock() {
  const [step, setStep] = React.useState(0)
  const [mobile, setMobile] = React.useState("")
  const [password, setPassword] = React.useState("")
  const [confirm, setConfirm] = React.useState("")
  const [loading, setLoading] = React.useState(false)
  const mismatch = confirm.length > 0 && confirm !== password

  const next = async () => {
    setLoading(true)
    await new Promise((resolve) => setTimeout(resolve, 600))
    setLoading(false)
    setStep((value) => value + 1)
  }

  const titles = [
    { title: "فراموشی رمز", text: "شمارهٔ موبایلِ حساب را بنویسید تا کدِ بازیابی بفرستیم." },
    { title: "کد تأیید", text: "" },
    { title: "رمز تازه", text: "رمزی بسازید که جای دیگری استفاده نکرده‌اید." },
  ]

  return (
    <main className="grid grid-cols-1 min-h-svh gap-4 bg-background p-4 lg:grid-cols-2">
      <div className="flex 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 < 3 ? (
            <>
              <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">{titles[step].title}</h1>
                  <p className="text-sm leading-6 text-muted-foreground">
                    {step === 1 ? (
                      <>
                        کدِ ۶ رقمی به <bdi dir="ltr" className="font-medium text-foreground">{toFaDigits(mobile)}</bdi> پیامک شد.
                      </>
                    ) : (
                      titles[step].text
                    )}
                  </p>
                </div>
              </header>

              <Stepper value={step} className="w-full">
                <StepperItem>
                  <StepperTitle>شماره</StepperTitle>
                </StepperItem>
                <StepperItem>
                  <StepperTitle>کد تأیید</StepperTitle>
                </StepperItem>
                <StepperItem>
                  <StepperTitle>رمز تازه</StepperTitle>
                </StepperItem>
              </Stepper>

              {step === 0 && (
                <form
                  onSubmit={(event) => {
                    event.preventDefault()
                    if (isValidMobile(mobile)) void next()
                  }}
                  className="flex flex-col gap-4"
                >
                  <TextField
                    label="شماره موبایل"
                    type="tel"
                    dir="ltr"
                    maxLength={11}
                    autoComplete="tel"
                    placeholder="۰۹۱۲۳۴۵۶۷۸۹"
                    value={mobile}
                    onValueChange={setMobile}
                    required
                    validate={(value) => (isValidMobile(value) ? undefined : "شماره موبایل باید با ۰۹ شروع شود و ۱۱ رقم باشد.")}
                  />
                  <Button type="submit" fullWidth loading={loading} className="mt-4">
                    ارسال کد
                  </Button>
                </form>
              )}

              {step === 1 && (
                <div className="flex flex-col items-center gap-6">
                  <InputOTP aria-label="کد تأیید" onComplete={next} disabled={loading}>
                    <InputOTPGroup>
                      {[0, 1, 2, 3, 4, 5].map((index) => (
                        <InputOTPSlot key={index} index={index} />
                      ))}
                    </InputOTPGroup>
                  </InputOTP>
                  <p className="text-sm text-muted-foreground">
                    شماره اشتباه است؟{" "}
                    <Button variant="link" className="h-auto p-0" onClick={() => setStep(0)}>
                      تغییر شماره
                    </Button>
                  </p>
                </div>
              )}

              {step === 2 && (
                <form
                  onSubmit={(event) => {
                    event.preventDefault()
                    if (password && !mismatch) void next()
                  }}
                  className="flex flex-col gap-4"
                >
                  <PasswordField label="رمز تازه" autoComplete="new-password" value={password} onValueChange={setPassword} strength requirements required />
                  <PasswordField
                    label="تکرار رمز تازه"
                    autoComplete="new-password"
                    value={confirm}
                    onValueChange={setConfirm}
                    invalid={mismatch}
                    errorMessage="با رمز بالا یکی نیست."
                    required
                  />
                  <Button type="submit" fullWidth loading={loading} disabled={!password || mismatch} className="mt-4">
                    ذخیرهٔ رمز
                  </Button>
                </form>
              )}

              <p className="text-center text-sm text-muted-foreground">
                رمز یادتان آمد؟ <Link href="/login">وارد شوید</Link>
              </p>
            </>
          ) : (
            <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="/login">ورود به حساب</NextLink>
              </Button>
            </div>
          )}
        </ControlSizeProvider>
        <p className="text-xs text-muted-foreground">
          <Link href="#" size="sm">
            قوانین
          </Link>{" "}
          و{" "}
          <Link href="#" size="sm">
            حریم خصوصی
          </Link>
        </p>
      </div>
      <aside className="relative hidden overflow-hidden rounded-xl bg-muted lg:block">
        {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
        <img src={stockImages.coverNotebook.src} alt="" className="absolute inset-0 size-full object-cover" />
        <div className="absolute inset-x-0 bottom-0 flex flex-col gap-2 bg-linear-to-t from-[var(--dig-graphite-950)] to-transparent p-8 pt-24 text-[var(--dig-white)] sm:p-12 sm:pt-32">
          <p className="max-w-md text-2xl leading-relaxed font-bold sm:text-3xl sm:leading-relaxed">نگران نباش، پیش می‌آید.</p>
          <p className="text-sm text-[var(--dig-neutral-300)]">با چند قدمِ ساده دوباره به حسابت برمی‌گردی.</p>
        </div>
      </aside>
    </main>
  )
}