مرجوعیِ کالا
پیشنمایش تمامصفحهمرجوعی در سه قدم: انتخابِ اقلام با عکس، دلیل (و عکسِ آسیب در صورتِ نیاز) و روشِ برگشت؛ مبلغِ بازگشتی در ستونِ کنار و پایان با کدِ پیگیری.
۱. نصب کامپوننتها
این بلاک از ۱۱ آیتم دیگ استفاده میکند: buttoncheckboxcontrol-sizefile-uploadiconslabelpersianradio-groupselectsteppertextarea
پکیجمنیجر پیشفرض 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/file-upload.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/radio-group.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/textarea.jsonpnpm 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/file-upload.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/radio-group.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/textarea.jsonyarn 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/file-upload.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/radio-group.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/textarea.jsonbunx --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/file-upload.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/radio-group.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/textarea.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
returns.tsx
"use client"
import * as React from "react"
import { CheckIcon, TruckIcon } from "@/components/icons"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { FileUpload } from "@/components/ui/file-upload"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Stepper, StepperItem, StepperTitle } from "@/components/ui/stepper"
import { Textarea } from "@/components/ui/textarea"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
const ITEMS: { id: string; title: string; variant: string; image: StockImageName; price: number }[] = [
{ id: "1", title: "گلدانِ سرامیکی دوقلو", variant: "کرم", image: "productVase", price: 1_290_000 },
{ id: "2", title: "دفترِ یادداشتِ جلدسخت", variant: "A5", image: "coverNotebook", price: 180_000 },
]
const REASONS = ["آسیبدیده رسید", "با توضیحات فرق داشت", "اندازه یا رنگ اشتباه", "دیگر لازم ندارم"]
const METHODS = [
{ value: "courier", title: "پیک از درِ خانه", note: "فردا ۹ تا ۱۳، رایگان", icon: <TruckIcon /> },
{ value: "post", title: "تحویل به پست", note: "کدِ مرسوله پیامک میشود", icon: <CheckIcon /> },
]
const STEPS = ["انتخابِ کالا", "دلیل", "روشِ برگشت"]
const optionCard =
"flex items-center gap-3 rounded-xl border border-border p-4 transition-colors has-[[data-state=checked]]:border-primary has-[[data-state=checked]]:bg-primary-subtle"
/**
* مرجوعی کالا در سه قدم: انتخابِ اقلام با عکس، دلیل (و عکسِ آسیب اگر لازم شد)، و روشِ
* برگشت؛ مبلغِ بازگشتی همیشه در ستونِ کنار دیده میشود. پایان با کدِ پیگیری.
*/
export default function ReturnsBlock() {
const [step, setStep] = React.useState(0)
const [selected, setSelected] = React.useState<string[]>(["1"])
const [reason, setReason] = React.useState(REASONS[0])
const [method, setMethod] = React.useState("courier")
const [done, setDone] = React.useState(false)
const refund = ITEMS.filter((item) => selected.includes(item.id)).reduce((sum, item) => sum + item.price, 0)
if (done) {
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-md flex-col items-center gap-4 p-6 py-16 text-center" role="status">
<span aria-hidden className="flex size-16 items-center justify-center rounded-full bg-success-subtle text-success [&>svg]:size-8">
<CheckIcon />
</span>
<h1 className="text-2xl font-bold">درخواستِ مرجوعی ثبت شد</h1>
<p className="leading-7 text-muted-foreground">
کدِ پیگیری <bdi dir="ltr" className="font-semibold text-foreground">{toFaDigits("R-48120")}</bdi>. بعد از بررسی کالا، {formatCurrency(refund)} به کیف پولت برمیگردد.
</p>
<Button variant="outline" className="mt-4" onClick={() => setDone(false)}>
بازگشت به سفارشها
</Button>
</main>
</ControlSizeProvider>
)
}
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto grid grid-cols-1 w-full max-w-5xl items-start gap-8 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_18rem]">
<form
className="flex flex-col gap-8 rounded-xl border border-border bg-card p-6 sm:p-8"
onSubmit={(event) => {
event.preventDefault()
if (step < STEPS.length - 1) setStep((value) => value + 1)
else setDone(true)
}}
>
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-bold">مرجوعی کالا</h1>
<p className="text-muted-foreground">سفارشِ {toFaDigits("8821042")} · تا ۷ روز بعد از تحویل</p>
</div>
<Stepper value={step}>
{STEPS.map((title) => (
<StepperItem key={title}>
<StepperTitle>{title}</StepperTitle>
</StepperItem>
))}
</Stepper>
{step === 0 && (
<ul className="flex flex-col gap-3" aria-label="اقلامِ سفارش">
{ITEMS.map((item) => (
<li key={item.id} className={optionCard}>
<Checkbox
id={`ret-${item.id}`}
checked={selected.includes(item.id)}
onCheckedChange={(checked) => setSelected((list) => (checked ? [...list, item.id] : list.filter((id) => id !== item.id)))}
/>
{/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
<img src={stockImages[item.image].src} alt="" className="size-16 rounded-lg object-cover" />
{/* در موبایل قیمت زیرِ نام میآید تا نام در سه خط نشکند. */}
<Label htmlFor={`ret-${item.id}`} className="flex min-w-0 flex-1 flex-col items-start gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<span className="flex flex-col gap-1">
<span className="font-semibold">{item.title}</span>
<span className="text-sm font-normal text-muted-foreground">{item.variant}</span>
</span>
<span className="font-bold tabular-nums">{formatCurrency(item.price)}</span>
</Label>
</li>
))}
</ul>
)}
{step === 1 && (
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<Label id="ret-reason">دلیلِ مرجوعی</Label>
<Select value={reason} onValueChange={(value) => setReason(value as string)}>
<SelectTrigger aria-labelledby="ret-reason" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{REASONS.map((item) => (
<SelectItem key={item} value={item}>
{item}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="ret-note">توضیح</Label>
<Textarea id="ret-note" rows={3} placeholder="هر چه دقیقتر بنویسید، سریعتر بررسی میشود." />
</div>
{reason === REASONS[0] && <FileUpload name="photos" multiple maxFiles={3} accept="image/*" title="عکسِ آسیب" hint="تا ۳ عکس، هر کدام تا ۵ مگابایت" />}
</div>
)}
{step === 2 && (
<RadioGroup value={method} onValueChange={setMethod} aria-label="روشِ برگشت" className="gap-3">
{METHODS.map((item) => (
<div key={item.value} className={optionCard}>
<RadioGroupItem id={`method-${item.value}`} value={item.value} />
<span aria-hidden className="flex size-10 items-center justify-center rounded-lg bg-subtle [&>svg]:size-5">
{item.icon}
</span>
<Label htmlFor={`method-${item.value}`} className="flex flex-1 flex-col items-start gap-1">
<span className="font-semibold">{item.title}</span>
<span className="text-sm font-normal text-muted-foreground">{item.note}</span>
</Label>
</div>
))}
</RadioGroup>
)}
<div className="flex justify-between gap-3 border-t border-border pt-8">
<Button type="button" variant="outline" disabled={step === 0} onClick={() => setStep((value) => value - 1)}>
قبلی
</Button>
<Button type="submit" disabled={selected.length === 0}>
{step === STEPS.length - 1 ? "ثبتِ درخواست" : "ادامه"}
</Button>
</div>
</form>
<aside className="flex flex-col gap-3 rounded-xl bg-subtle p-6 lg:sticky lg:top-6">
<span className="text-sm text-muted-foreground">مبلغِ بازگشتی</span>
<span className="text-3xl font-black tabular-nums">{formatCurrency(refund)}</span>
<span className="text-sm leading-6 text-muted-foreground">
{toFaDigits(selected.length)} کالا · بعد از بررسی، ظرفِ ۲۴ ساعت به کیف پول برمیگردد.
</span>
</aside>
</main>
</ControlSizeProvider>
)
}