پیگیری بدونِ ورود
پیشنمایش تمامصفحهپیگیریِ مهمان با کدِ سفارش و موبایلِ خرید، خطای روشن اگر پیدا نشد و نتیجه در همان صفحه با نوارِ چهارمرحلهای و جملهٔ وضعیت.
۱. نصب کامپوننتها
این بلاک از ۵ آیتم دیگ استفاده میکند: buttoncontrol-sizeiconspersiantext-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/persian.json https://docs.digdesign.ir/r/text-field.jsonpnpm 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/persian.json https://docs.digdesign.ir/r/text-field.jsonyarn 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/persian.json https://docs.digdesign.ir/r/text-field.jsonbunx --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/persian.json https://docs.digdesign.ir/r/text-field.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
order-tracking-lookup.tsx
"use client";
import * as React from "react";
import { CheckIcon, TruckIcon } from "@/components/icons";
import { Button } from "@/components/ui/button";
import { ControlSizeProvider } from "@/components/ui/control-size";
import { TextField } from "@/components/ui/text-field";
import { isValidMobile, toFaDigits } from "@/lib/persian";
const STEPS = ["ثبت", "آمادهسازی", "ارسال", "تحویل"];
/**
* پیگیری سفارش بدونِ ورود (برای مهمان): کدِ سفارش و موبایلِ خرید، خطای روشن اگر نخواند، و
* نتیجه در همان صفحه با نوارِ چهارمرحلهای افقی و جملهٔ وضعیت؛ «پیگیری سفارشِ دیگر» برمیگرداند.
*/
export default function OrderTrackingLookupBlock() {
const [code, setCode] = React.useState("");
const [mobile, setMobile] = React.useState("");
const [state, setState] = React.useState<
"idle" | "loading" | "found" | "missing"
>("idle");
const current = 2;
return (
<ControlSizeProvider size="xl" asChild>
<main className="flex min-h-svh items-start justify-center bg-subtle p-4 sm:items-center">
<div className="flex w-full max-w-lg flex-col">
{/* بنر: گرادیانِ برند با مسیرِ خطچین و بستهٔ تزئینی؛ کارتِ فرم روی لبهاش مینشیند. */}
<header className="relative isolate flex flex-col gap-2 overflow-hidden rounded-xl bg-[linear-gradient(135deg,var(--dig-violet-700),var(--dig-indigo-800))] px-6 pt-8 pb-24 text-[var(--dig-white)] sm:px-8">
<span
aria-hidden
className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(circle_at_90%_0%,var(--dig-violet-400),transparent_50%)]"
/>
<svg
aria-hidden
viewBox="0 0 200 90"
className="pointer-events-none absolute end-4 top-4 -z-10 hidden w-36 sm:block text-[color-mix(in_oklab,var(--dig-white)_45%,transparent)]"
fill="none"
>
<path
d="M6 70 C 50 70, 70 20, 120 30 S 170 60, 190 40"
stroke="currentColor"
strokeWidth="2"
strokeDasharray="2 7"
strokeLinecap="round"
/>
<circle cx="6" cy="70" r="5" fill="currentColor" />
<g transform="translate(166 18)">
<rect width="30" height="26" rx="4" fill="var(--dig-white)" />
<path
d="M0 9h30M15 0v9"
stroke="var(--dig-violet-500)"
strokeWidth="2"
/>
</g>
</svg>
<h1 className="text-2xl leading-snug font-black">سفارشت کجاست؟</h1>
<p className="max-w-[15rem] text-sm text-[var(--dig-violet-100)]">
کدِ سفارش در پیامکِ خرید آمده؛ بدونِ ورود هم پیگیری کن.
</p>
</header>
<section
aria-label="پیگیری"
className="relative mx-3 -mt-12 flex flex-col gap-8 rounded-xl border border-border bg-card p-6 shadow-card sm:mx-5 sm:p-8"
>
{state === "found" ? (
<div className="flex flex-col gap-8" aria-live="polite">
<div className="flex items-center gap-4 rounded-xl bg-subtle p-4">
<span
aria-hidden
className="flex size-12 items-center justify-center rounded-xl bg-card text-brand-secondary [&>svg]:size-6"
>
<TruckIcon />
</span>
<span className="flex flex-col">
<span className="font-bold">در راه است</span>
<span className="text-sm text-muted-foreground">
سفارشِ {toFaDigits(code)} · تحویل تا فردا ساعتِ ۱۸
</span>
</span>
</div>
<ol className="grid grid-cols-4 gap-2">
{STEPS.map((step, index) => (
<li
key={step}
aria-current={index === current ? "step" : undefined}
className="flex flex-col gap-2"
>
<span
className={`h-1.5 rounded-full ${index <= current ? "bg-success" : "bg-muted"}`}
/>
<span
className={`flex items-center gap-1 text-xs ${index <= current ? "font-semibold" : "text-muted-foreground"}`}
>
{index < current && (
<CheckIcon
aria-hidden
className="size-3.5 text-success"
/>
)}
{step}
</span>
</li>
))}
</ol>
<Button variant="outline" onClick={() => setState("idle")}>
پیگیری سفارشِ دیگر
</Button>
</div>
) : (
<form
className="flex flex-col gap-5"
onSubmit={(event) => {
event.preventDefault();
setState("loading");
window.setTimeout(
() => setState(code.length >= 6 ? "found" : "missing"),
700,
);
}}
>
<TextField
label="کدِ سفارش"
inputMode="numeric"
dir="ltr"
value={code}
onValueChange={setCode}
required
invalid={state === "missing"}
errorMessage="سفارشی با این کد و موبایل پیدا نشد؛ کد را از پیامکِ خرید دوباره نگاه کن."
/>
<TextField
label="موبایلِ خرید"
type="tel"
dir="ltr"
maxLength={11}
value={mobile}
onValueChange={setMobile}
required
validate={(value) =>
isValidMobile(value)
? undefined
: "شماره موبایل باید با ۰۹ شروع شود و ۱۱ رقم باشد."
}
/>
<Button
type="submit"
loading={state === "loading"}
className="mt-3"
>
پیگیری
</Button>
</form>
)}
</section>
<p className="mt-5 text-center text-sm text-muted-foreground">
کد را پیدا نمیکنی؟{" "}
<a
href="#"
className="font-medium text-foreground underline underline-offset-4"
>
با پشتیبانی تماس بگیر
</a>
</p>
</div>
</main>
</ControlSizeProvider>
);
}