بلاک‌ها · حساب کاربری

امنیت: امتیازِ امنیت با کارِ پیشنهادی، ورودِ دومرحله‌ای با سوییچ، رمز با تاریخِ آخرین تغییر و دستگاه‌های فعال با خروجِ تکی یا همه (با تأیید).

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

این بلاک از ۱۰ آیتم دیگ استفاده می‌کند: alert-dialogbadgebuttoncardcontrol-sizeiconslabelpersianswitchtoast

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

۲. کد صفحه

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

account-security.tsx
"use client"

import * as React from "react"

import { GlobeIcon, MobileIcon, ShieldCheckIcon } from "@/components/icons"
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import { toast } from "@/components/ui/toast"
import { formatRelativeTime } from "@/lib/persian"

type Session = { id: string; device: string; place: string; at: string; mobile: boolean; current?: boolean }

const SESSIONS: Session[] = [
  { id: "1", device: "کروم روی مک", place: "تهران", at: "2026-10-06T09:10:00", mobile: false, current: true },
  { id: "2", device: "اپِ آوند، آیفون", place: "تهران", at: "2026-10-05T21:40:00", mobile: true },
  { id: "3", device: "فایرفاکس روی ویندوز", place: "اصفهان", at: "2026-09-28T13:05:00", mobile: false },
]

/**
 * امنیتِ حساب: نوارِ امتیازِ امنیت با کارِ بعدی پیشنهادی، ورودِ دومرحله‌ای با کلید و
 * توضیح، رمز با تاریخِ آخرین تغییر، و دستگاه‌های فعال با خروجِ تکی یا همه (با تأیید).
 */
export default function AccountSecurityBlock() {
  const [twoFactor, setTwoFactor] = React.useState(false)
  const [sessions, setSessions] = React.useState(SESSIONS)
  const score = twoFactor ? 100 : 60

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-3xl flex-col gap-8 p-4 sm:p-6">
        <header className="flex flex-col gap-1">
          <h1 className="text-2xl font-bold">امنیتِ حساب</h1>
          <p className="text-muted-foreground">رمز، ورودِ دومرحله‌ای و دستگاه‌هایی که با آن‌ها وارد شده‌ای.</p>
        </header>

        <section aria-label="وضعیتِ امنیت" className={`flex items-center gap-4 rounded-xl p-5 ${twoFactor ? "bg-success-subtle" : "bg-[color-mix(in_oklab,var(--dig-orange-500)_12%,var(--background))]"}`}>
          <span aria-hidden className={`flex size-12 shrink-0 items-center justify-center rounded-xl bg-card [&>svg]:size-6 ${twoFactor ? "text-success" : "text-[var(--dig-orange-600)]"}`}>
            <ShieldCheckIcon />
          </span>
          <div className="flex flex-1 flex-col gap-0.5">
            <span className="font-bold">{twoFactor ? "حسابت کاملاً امن است" : "یک قدم تا امنیتِ کامل"}</span>
            <span className="text-sm text-muted-foreground">{twoFactor ? "ورودِ دومرحله‌ای روشن است." : "ورودِ دومرحله‌ای را روشن کن تا رمزِ لورفته هم کافی نباشد."}</span>
          </div>
          <span className="text-2xl font-black tabular-nums">{score === 100 ? "۱۰۰٪" : "۶۰٪"}</span>
        </section>

        <Card>
          <CardContent className="flex flex-col divide-y divide-border">
            <div className="flex items-start justify-between gap-4 pb-5">
              <div className="flex flex-col gap-1">
                <Label htmlFor="two-factor" className="font-semibold">
                  ورودِ دومرحله‌ای
                </Label>
                <span className="text-sm leading-6 text-muted-foreground">بعد از رمز، یک کدِ پیامکی هم لازم می‌شود.</span>
              </div>
              <Switch
                id="two-factor"
                checked={twoFactor}
                onCheckedChange={(checked) => {
                  setTwoFactor(checked)
                  toast.success(checked ? "ورودِ دومرحله‌ای روشن شد." : "ورودِ دومرحله‌ای خاموش شد.")
                }}
              />
            </div>
            <div className="flex items-center justify-between gap-4 pt-5">
              <div className="flex flex-col gap-1">
                <span className="font-semibold">رمز عبور</span>
                <span className="text-sm text-muted-foreground">آخرین تغییر {formatRelativeTime("2026-06-12T10:00:00")}</span>
              </div>
              <Button variant="outline">تغییرِ رمز</Button>
            </div>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle className="text-base">دستگاه‌های فعال</CardTitle>
            <CardDescription>دستگاهی را نمی‌شناسی؟ همین حالا از آن خارج شو.</CardDescription>
          </CardHeader>
          <CardContent className="flex flex-col gap-6">
            <ul className="flex flex-col divide-y divide-border">
              {sessions.map((session) => (
                <li key={session.id} className="flex items-center gap-4 py-4 first:pt-0 last:pb-0">
                  <span aria-hidden className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-subtle [&>svg]:size-5">
                    {session.mobile ? <MobileIcon /> : <GlobeIcon />}
                  </span>
                  <span className="flex flex-1 flex-col gap-0.5">
                    <span className="flex items-center gap-2 font-medium">
                      {session.device}
                      {session.current && (
                        <Badge variant="soft" color="success">
                          همین دستگاه
                        </Badge>
                      )}
                    </span>
                    <span className="text-sm text-muted-foreground">
                      {session.place} · {session.current ? "الان" : formatRelativeTime(session.at)}
                    </span>
                  </span>
                  {!session.current && (
                    <Button variant="ghost" color="destructive" onClick={() => setSessions((list) => list.filter((item) => item.id !== session.id))}>
                      خروج
                    </Button>
                  )}
                </li>
              ))}
            </ul>
            {sessions.length > 1 && (
              <AlertDialog>
                <AlertDialogTrigger asChild>
                  <Button variant="outline" color="destructive" className="self-start">
                    خروج از همهٔ دستگاه‌های دیگر
                  </Button>
                </AlertDialogTrigger>
                <ControlSizeProvider size="xl" asChild>
                  <AlertDialogContent>
                    <AlertDialogHeader>
                      <AlertDialogTitle>از {sessions.length - 1 === 1 ? "یک دستگاهِ دیگر" : "همهٔ دستگاه‌های دیگر"} خارج شوی؟</AlertDialogTitle>
                      <AlertDialogDescription>روی آن دستگاه‌ها باید دوباره وارد شوی. همین دستگاه وصل می‌ماند.</AlertDialogDescription>
                    </AlertDialogHeader>
                    <AlertDialogFooter>
                      <AlertDialogCancel>انصراف</AlertDialogCancel>
                      <AlertDialogAction
                        onClick={() => {
                          setSessions((list) => list.filter((item) => item.current))
                          toast.success("از دستگاه‌های دیگر خارج شدی.")
                        }}
                      >
                        خروج از همه
                      </AlertDialogAction>
                    </AlertDialogFooter>
                  </AlertDialogContent>
                </ControlSizeProvider>
              </AlertDialog>
            )}
          </CardContent>
        </Card>
      </main>
    </ControlSizeProvider>
  )
}