بلاک‌ها · فروشگاهی

سفارشِ داروی نسخه‌ای با عکسِ نسخه یا کدِ رهگیریِ نسخهٔ الکترونیک و کدِ ملی، برای چه کسی، نشانیِ تحویل و راهنمای مراحلِ بعد از ثبت.

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

این بلاک از ۱۰ آیتم دیگ استفاده می‌کند: buttoncontrol-sizefile-uploadiconspersianradio-cardradio-grouptext-fieldtoasttoggle-group

پکیج‌منیجر پیش‌فرض 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/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/radio-card.json https://docs.digdesign.ir/r/radio-group.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.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/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/radio-card.json https://docs.digdesign.ir/r/radio-group.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.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/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/radio-card.json https://docs.digdesign.ir/r/radio-group.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.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/file-upload.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/radio-card.json https://docs.digdesign.ir/r/radio-group.json https://docs.digdesign.ir/r/text-field.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

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

pharmacy-prescription.tsx
"use client"

import * as React from "react"

import { ClockIcon, FileTextIcon, LocationIcon, TruckIcon, WalletIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { FileUpload } from "@/components/ui/file-upload"
import { RadioCard } from "@/components/ui/radio-card"
import { RadioGroup } from "@/components/ui/radio-group"
import { TextField } from "@/components/ui/text-field"
import { toast } from "@/components/ui/toast"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { isValidNationalId, toEnDigits, toFaDigits } from "@/lib/persian"

const HOW = [
  { icon: <FileTextIcon />, title: "نسخه را ثبت کن", text: "عکسِ نسخهٔ کاغذی یا کدِ رهگیریِ نسخهٔ الکترونیک." },
  { icon: <ClockIcon />, title: "داروخانه قیمت می‌دهد", text: "معمولاً تا ۱۵ دقیقه، با سهمِ بیمه و داروهای جایگزین." },
  { icon: <WalletIcon />, title: "پرداخت می‌کنی", text: "بعد از تأییدِ تو و فقط مبلغِ نهایی." },
  { icon: <TruckIcon />, title: "پیک می‌آورد", text: "داروی یخچالی با کیفِ سرد." },
]

/**
 * سفارشِ داروی نسخه‌ای: یک انتخابِ تکی بینِ «عکسِ نسخه» و «نسخهٔ الکترونیک» (سگمنت، نه دو فرم کنارِ هم)،
 * بارگذاریِ تا ۳ عکس یا کدِ ملی و کدِ رهگیری با خطای کنارِ فیلد، برای چه کسی، و نشانیِ تحویل. ستونِ
 * کنار مراحل را می‌گوید تا کاربر بداند بعد از ثبت چه می‌شود و الان پولی پرداخت نمی‌کند.
 */
export default function PharmacyPrescriptionBlock() {
  const [method, setMethod] = React.useState("photo")
  const [patient, setPatient] = React.useState("me")
  const [nationalId, setNationalId] = React.useState("")
  const [code, setCode] = React.useState("")
  const [blurred, setBlurred] = React.useState({ id: false, code: false })

  const idDigits = toEnDigits(nationalId).replace(/\D/g, "")
  const idError = !blurred.id || isValidNationalId(idDigits)
    ? undefined
    : !idDigits
      ? "کدِ ملی را بنویس؛ ۱۰ رقم است."
      : idDigits.length !== 10
        ? `کدِ ملی ۱۰ رقم است، ${toFaDigits(idDigits.length)} رقم وارد شده.`
        : "این کدِ ملی معتبر نیست؛ یک بار دیگر از روی کارت نگاه کن."
  const codeError = blurred.code && toEnDigits(code).replace(/\D/g, "").length < 6 ? "کدِ رهگیری در پیامکِ پزشک آمده؛ دست‌کم ۶ رقم." : undefined

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto grid w-full max-w-5xl grid-cols-1 items-start gap-6 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_20rem] lg:gap-8">
        <form
          className="flex min-w-0 flex-col gap-6"
          noValidate
          onSubmit={(event) => {
            event.preventDefault()
            if (method === "code") {
              setBlurred({ id: true, code: true })
              if (!isValidNationalId(toEnDigits(nationalId).replace(/\D/g, "")) || toEnDigits(code).replace(/\D/g, "").length < 6) return
            }
            toast.success("نسخه ثبت شد؛ قیمت را تا ۱۵ دقیقهٔ دیگر پیامک می‌کنیم.")
          }}
        >
          <header className="flex flex-col gap-1">
            <h1 className="text-2xl leading-snug font-bold">سفارش با نسخه</h1>
            <p className="text-sm text-muted-foreground">الان پولی پرداخت نمی‌کنی؛ اول قیمت را می‌بینی.</p>
          </header>

          <section aria-labelledby="method-title" className="flex flex-col gap-4">
            <h2 id="method-title" className="sr-only">
              روشِ ثبتِ نسخه
            </h2>
            <ControlSizeProvider size="lg" mobileSize="default" asChild>
              <ToggleGroup type="single" variant="segmented" fullWidth value={method} onValueChange={(value) => value && setMethod(value)} aria-label="روشِ ثبتِ نسخه" className="sm:w-max">
                <ToggleGroupItem value="photo">عکسِ نسخه</ToggleGroupItem>
                <ToggleGroupItem value="code">نسخهٔ الکترونیک</ToggleGroupItem>
              </ToggleGroup>
            </ControlSizeProvider>

            {method === "photo" ? (
              <FileUpload name="prescription" multiple maxFiles={3} accept="image/*" title="عکسِ نسخه را بکش یا انتخاب کن" hint="تا ۳ عکس، هر کدام تا ۵ مگابایت؛ نامِ پزشک و مهر خوانا باشد." />
            ) : (
              <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
                <TextField
                  label="کدِ ملیِ بیمار"
                  inputMode="numeric"
                  dir="ltr"
                  maxLength={10}
                  value={nationalId}
                  onValueChange={setNationalId}
                  onBlur={() => setBlurred((value) => ({ ...value, id: true }))}
                  invalid={Boolean(idError)}
                  errorMessage={idError}
                  autoComplete="off"
                />
                <TextField
                  label="کدِ رهگیریِ نسخه"
                  inputMode="numeric"
                  dir="ltr"
                  value={code}
                  onValueChange={setCode}
                  onBlur={() => setBlurred((value) => ({ ...value, code: true }))}
                  invalid={Boolean(codeError)}
                  errorMessage={codeError}
                  description="در پیامکی که بعد از ویزیت گرفتی."
                  autoComplete="one-time-code"
                />
              </div>
            )}
          </section>

          <section aria-labelledby="patient-title" className="flex flex-col gap-3">
            <h2 id="patient-title" className="text-lg leading-snug font-bold">
              برای چه کسی؟
            </h2>
            <RadioGroup value={patient} onValueChange={setPatient} aria-labelledby="patient-title" className="grid grid-cols-2 gap-3">
              <RadioCard layout="tile" value="me" title="خودم" description="آرمیتا رستگار" />
              <RadioCard layout="tile" value="other" title="شخصِ دیگر" description="کدِ ملیِ او را بنویس" />
            </RadioGroup>
          </section>

          <section aria-labelledby="address-title" className="flex flex-col gap-3">
            <h2 id="address-title" className="text-lg leading-snug font-bold">
              تحویل به
            </h2>
            <div className="flex items-start gap-3 rounded-xl border border-border bg-card p-4">
              <LocationIcon aria-hidden className="mt-0.5 size-5 shrink-0 text-muted-foreground" />
              <span className="flex min-w-0 flex-1 flex-col gap-0.5">
                <span className="font-semibold">خانه</span>
                <span className="text-sm text-muted-foreground">تهران، خیابانِ آزادی، کوچهٔ نسترن، پلاکِ ۱۲، واحدِ ۴</span>
              </span>
              <ControlSizeProvider size="sm" asChild>
                <Button type="button" variant="ghost" className="-me-2 text-brand-secondary">
                  تغییر
                </Button>
              </ControlSizeProvider>
            </div>
          </section>

          <Button type="submit" fullWidth className="mt-2">
            ثبتِ نسخه و دریافتِ قیمت
          </Button>
        </form>

        <aside aria-labelledby="how-title" className="flex flex-col gap-4 rounded-xl border border-border bg-card p-5 lg:sticky lg:top-6">
          <h2 id="how-title" className="font-bold">
            بعد از ثبت چه می‌شود؟
          </h2>
          <ol className="flex flex-col gap-4">
            {HOW.map((item) => (
              <li key={item.title} className="flex gap-3">
                <span aria-hidden className="relative flex size-9 shrink-0 items-center justify-center rounded-full bg-primary-subtle text-primary-subtle-foreground [&>svg]:size-4">
                  {item.icon}
                </span>
                <span className="flex flex-col gap-0.5">
                  <span className="text-sm font-semibold">{item.title}</span>
                  <span className="text-xs leading-5 text-muted-foreground">{item.text}</span>
                </span>
              </li>
            ))}
          </ol>
        </aside>
      </main>
    </ControlSizeProvider>
  )
}