710 lines
25 KiB
TypeScript
710 lines
25 KiB
TypeScript
"use client";
|
||||
|
|
|
|||
|
|
import {
|
|||
|
|
Box,
|
|||
|
|
Check,
|
|||
|
|
CheckCircle,
|
|||
|
|
CreditCard,
|
|||
|
|
Download,
|
|||
|
|
Home,
|
|||
|
|
MapPin,
|
|||
|
|
Package,
|
|||
|
|
Printer,
|
|||
|
|
Receipt,
|
|||
|
|
Truck,
|
|||
|
|
} from "lucide-react";
|
|||
|
|
import { useState } from "react";
|
|||
|
|
|
|||
|
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
|||
|
|
import { Badge } from "@/components/ui/badge";
|
|||
|
|
import { Button } from "@/components/ui/button";
|
|||
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||
|
|
import { Separator } from "@/components/ui/separator";
|
|||
|
|
import { cn } from "@/lib/utils";
|
|||
|
|
|
|||
|
|
interface OrderItem {
|
|||
|
|
id: string;
|
|||
|
|
name: string;
|
|||
|
|
image: string;
|
|||
|
|
price: number;
|
|||
|
|
quantity: number;
|
|||
|
|
details?: { label: string; value: string }[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface ShippingAddress {
|
|||
|
|
name: string;
|
|||
|
|
street: string;
|
|||
|
|
city: string;
|
|||
|
|
state: string;
|
|||
|
|
zipCode: string;
|
|||
|
|
country: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface PaymentMethod {
|
|||
|
|
type: "card" | "paypal" | "bank";
|
|||
|
|
lastFour?: string;
|
|||
|
|
cardBrand?: string;
|
|||
|
|
email?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface StepContent {
|
|||
|
|
title: string;
|
|||
|
|
description: string;
|
|||
|
|
details: { label: string; value: string }[];
|
|||
|
|
icon: "receipt" | "box" | "truck" | "home";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface TimelineStep {
|
|||
|
|
id: string;
|
|||
|
|
label: string;
|
|||
|
|
description: string;
|
|||
|
|
/** ISO 8601 datetime */
|
|||
|
|
date: string;
|
|||
|
|
completed: boolean;
|
|||
|
|
current: boolean;
|
|||
|
|
content: StepContent;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface OrderSummaryData {
|
|||
|
|
orderNumber: string;
|
|||
|
|
/** ISO 8601 date (YYYY-MM-DD) */
|
|||
|
|
orderDate: string;
|
|||
|
|
status: "confirmed" | "processing" | "shipped" | "delivered";
|
|||
|
|
email: string;
|
|||
|
|
items: OrderItem[];
|
|||
|
|
subtotal: number;
|
|||
|
|
shipping: number;
|
|||
|
|
tax: number;
|
|||
|
|
discount?: number;
|
|||
|
|
total: number;
|
|||
|
|
shippingAddress: ShippingAddress;
|
|||
|
|
shippingMethod: string;
|
|||
|
|
estimatedDeliveryFrom: string;
|
|||
|
|
estimatedDeliveryTo?: string;
|
|||
|
|
paymentMethod: PaymentMethod;
|
|||
|
|
timeline: TimelineStep[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const currencyFormatter = new Intl.NumberFormat("fa-IR", {
|
|||
|
|
style: "currency",
|
|||
|
|
currency: "IRR",
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const dateFormatter = new Intl.DateTimeFormat("fa-IR", {
|
|||
|
|
year: "numeric",
|
|||
|
|
month: "long",
|
|||
|
|
day: "numeric",
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const dateTimeFormatter = new Intl.DateTimeFormat("fa-IR", {
|
|||
|
|
year: "numeric",
|
|||
|
|
month: "short",
|
|||
|
|
day: "numeric",
|
|||
|
|
hour: "numeric",
|
|||
|
|
minute: "2-digit",
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const formatOrderDate = (isoDate: string) =>
|
|||
|
|
dateFormatter.format(new Date(isoDate + "T12:00:00"));
|
|||
|
|
|
|||
|
|
const formatStepDate = (isoDateTime: string) =>
|
|||
|
|
dateTimeFormatter.format(new Date(isoDateTime));
|
|||
|
|
|
|||
|
|
const formatDeliveryEstimate = (from: string, to?: string) => {
|
|||
|
|
if (to && to !== from) {
|
|||
|
|
return `${formatOrderDate(from)} تا ${formatOrderDate(to)}`;
|
|||
|
|
}
|
|||
|
|
return formatOrderDate(from);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const formatItemDetails = (details: OrderItem["details"]) => {
|
|||
|
|
if (!details?.length) return null;
|
|||
|
|
return details.map((d) => `${d.label} ${d.value}`).join(" · ");
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const DEFAULT_ORDER: OrderSummaryData = {
|
|||
|
|
orderNumber: "ORD-2024-78432",
|
|||
|
|
orderDate: "2024-12-14",
|
|||
|
|
status: "delivered",
|
|||
|
|
email: "customer@example.com",
|
|||
|
|
items: [
|
|||
|
|
{
|
|||
|
|
id: "1",
|
|||
|
|
name: "کفش بژ مینیمال",
|
|||
|
|
image:
|
|||
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Minimalist-Beige-Sneakers-2.png",
|
|||
|
|
price: 120.0,
|
|||
|
|
quantity: 1,
|
|||
|
|
details: [
|
|||
|
|
{ label: "سایز", value: "42" },
|
|||
|
|
{ label: "رنگ", value: "بژ" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "2",
|
|||
|
|
name: "تاپ آبی گلدوزی",
|
|||
|
|
image:
|
|||
|
|
"/block-media/shadcnblocks/block/ecommerce/clothes/Woman-in-Embroidered-Blue-Top-2.png",
|
|||
|
|
price: 140.0,
|
|||
|
|
quantity: 2,
|
|||
|
|
details: [
|
|||
|
|
{ label: "سایز", value: "M" },
|
|||
|
|
{ label: "رنگ", value: "آبی" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "3",
|
|||
|
|
name: "کلاه فدورا کلاسیک",
|
|||
|
|
image:
|
|||
|
|
"/block-media/shadcnblocks/block/ecommerce/accessories/Classic-Fedora-Hat-2.png",
|
|||
|
|
price: 84.0,
|
|||
|
|
quantity: 1,
|
|||
|
|
details: [{ label: "سایز", value: "یک سایز" }],
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
subtotal: 484.0,
|
|||
|
|
shipping: 12.0,
|
|||
|
|
tax: 38.72,
|
|||
|
|
discount: 50.0,
|
|||
|
|
total: 484.72,
|
|||
|
|
shippingAddress: {
|
|||
|
|
name: "علی محمدی",
|
|||
|
|
street: "خیابان ولیعصر، پلاک ۱۲۳۴، واحد ۵",
|
|||
|
|
city: "تهران",
|
|||
|
|
state: "تهران",
|
|||
|
|
zipCode: "1415783114",
|
|||
|
|
country: "ایران",
|
|||
|
|
},
|
|||
|
|
shippingMethod: "ارسال سریع",
|
|||
|
|
estimatedDeliveryFrom: "2024-12-18",
|
|||
|
|
estimatedDeliveryTo: "2024-12-20",
|
|||
|
|
paymentMethod: {
|
|||
|
|
type: "card",
|
|||
|
|
lastFour: "4242",
|
|||
|
|
cardBrand: "Visa",
|
|||
|
|
},
|
|||
|
|
timeline: [
|
|||
|
|
{
|
|||
|
|
id: "1",
|
|||
|
|
label: "ثبت سفارش",
|
|||
|
|
description: "سفارش شما دریافت شد",
|
|||
|
|
date: "2024-12-14T14:30:00",
|
|||
|
|
completed: true,
|
|||
|
|
current: false,
|
|||
|
|
content: {
|
|||
|
|
title: "سفارش تأیید شد",
|
|||
|
|
description:
|
|||
|
|
"سفارش شما دریافت شد و ایمیل تأیید ارسال گردید. پرداخت با موفقیت انجام شد.",
|
|||
|
|
icon: "receipt",
|
|||
|
|
details: [
|
|||
|
|
{ label: "شماره سفارش", value: "ORD-2024-78432" },
|
|||
|
|
{ label: "روش پرداخت", value: "کارت بانکی •••• 4242" },
|
|||
|
|
{
|
|||
|
|
label: "آدرس صورتحساب",
|
|||
|
|
value: "خیابان ولیعصر، تهران",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: "مجموع سفارش",
|
|||
|
|
value: currencyFormatter.format(484.72),
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "2",
|
|||
|
|
label: "آمادهسازی",
|
|||
|
|
description: "در حال آمادهسازی اقلام",
|
|||
|
|
date: "2024-12-14T16:15:00",
|
|||
|
|
completed: true,
|
|||
|
|
current: false,
|
|||
|
|
content: {
|
|||
|
|
title: "آمادهسازی سفارش",
|
|||
|
|
description:
|
|||
|
|
"تیم انبار در حال جمعآوری و بستهبندی اقلام شماست. قبل از ارسال، کنترل کیفیت انجام میشود.",
|
|||
|
|
icon: "box",
|
|||
|
|
details: [
|
|||
|
|
{ label: "انبار", value: "مرکز توزیع تهران" },
|
|||
|
|
{ label: "اقلام بستهبندیشده", value: "۳ از ۳ قلم" },
|
|||
|
|
{ label: "کنترل کیفیت", value: "تأیید شد" },
|
|||
|
|
{ label: "بستهبندی", value: "مواد دوستدار محیط زیست" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "3",
|
|||
|
|
label: "ارسالشده",
|
|||
|
|
description: "در مسیر تحویل",
|
|||
|
|
date: "2024-12-15T09:00:00",
|
|||
|
|
completed: true,
|
|||
|
|
current: false,
|
|||
|
|
content: {
|
|||
|
|
title: "ارسال به مقصد",
|
|||
|
|
description:
|
|||
|
|
"بسته شما در راه است! با شماره رهگیری زیر میتوانید وضعیت را لحظهای پیگیری کنید.",
|
|||
|
|
icon: "truck",
|
|||
|
|
details: [
|
|||
|
|
{ label: "شرکت حمل", value: "پست پیشتاز" },
|
|||
|
|
{
|
|||
|
|
label: "شماره رهگیری",
|
|||
|
|
value: "1Z999AA10123456784",
|
|||
|
|
},
|
|||
|
|
{ label: "تحویل تقریبی", value: "۱۷ آذر ۱۴۰۳" },
|
|||
|
|
{ label: "موقعیت فعلی", value: "تهران در حال انتقال" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: "4",
|
|||
|
|
label: "تحویلشده",
|
|||
|
|
description: "بسته تحویل داده شد",
|
|||
|
|
date: "2024-12-17T11:45:00",
|
|||
|
|
completed: true,
|
|||
|
|
current: true,
|
|||
|
|
content: {
|
|||
|
|
title: "تحویل موفق",
|
|||
|
|
description:
|
|||
|
|
"بسته شما تحویل داده شد! امیدواریم از خرید راضی باشید. ثبت نظر را فراموش نکنید.",
|
|||
|
|
icon: "home",
|
|||
|
|
details: [
|
|||
|
|
{ label: "محل تحویل", value: "درِ ورودی" },
|
|||
|
|
{ label: "تحویلگیرنده", value: "ع. محمدی" },
|
|||
|
|
{ label: "زمان تحویل", value: "۱۷ آذر ۱۴۰۳، ساعت ۱۱:۴۵" },
|
|||
|
|
{ label: "عکس تحویل", value: "در بخش رهگیری موجود است" },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
interface OrderSummary4Props {
|
|||
|
|
order?: OrderSummaryData;
|
|||
|
|
className?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const OrderSummary4 = ({
|
|||
|
|
order = DEFAULT_ORDER,
|
|||
|
|
className,
|
|||
|
|
}: OrderSummary4Props) => {
|
|||
|
|
const [selectedStepIndex, setSelectedStepIndex] = useState(
|
|||
|
|
order.timeline.findIndex((step) => step.current),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const selectStep = (index: number) => {
|
|||
|
|
setSelectedStepIndex(index);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const selectedStep = order.timeline[selectedStepIndex];
|
|||
|
|
const completedSteps = order.timeline.filter((step) => step.completed).length;
|
|||
|
|
const progressPercentage = (completedSteps / order.timeline.length) * 100;
|
|||
|
|
const itemCountLabel =
|
|||
|
|
order.items.length === 1
|
|||
|
|
? "یک قلم"
|
|||
|
|
: `${order.items.length.toLocaleString("fa-IR")} قلم`;
|
|||
|
|
|
|||
|
|
const getStepIcon = (iconType: StepContent["icon"]) => {
|
|||
|
|
switch (iconType) {
|
|||
|
|
case "receipt":
|
|||
|
|
return <Receipt className="size-5" aria-hidden="true" />;
|
|||
|
|
case "box":
|
|||
|
|
return <Box className="size-5" aria-hidden="true" />;
|
|||
|
|
case "truck":
|
|||
|
|
return <Truck className="size-5" aria-hidden="true" />;
|
|||
|
|
case "home":
|
|||
|
|
return <Home className="size-5" aria-hidden="true" />;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const isTrackingDetail = (label: string) =>
|
|||
|
|
label === "شماره رهگیری" || label === "شماره سفارش";
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<section
|
|||
|
|
dir="rtl"
|
|||
|
|
lang="fa"
|
|||
|
|
className={cn("py-16 md:py-24", className)}
|
|||
|
|
>
|
|||
|
|
<div className="container max-w-3xl">
|
|||
|
|
<div className="mb-8 text-center">
|
|||
|
|
<div className="mx-auto mb-4 flex size-14 items-center justify-center rounded-full bg-emerald-500/10">
|
|||
|
|
<CheckCircle
|
|||
|
|
className="size-7 text-emerald-600"
|
|||
|
|
aria-hidden="true"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<h1 className="mb-1 text-xl font-bold tracking-tight md:text-3xl">
|
|||
|
|
از خرید شما سپاسگزاریم!
|
|||
|
|
</h1>
|
|||
|
|
<p className="text-sm text-muted-foreground">
|
|||
|
|
سفارش{" "}
|
|||
|
|
<span className="font-medium" dir="rtl">
|
|||
|
|
{order.orderNumber}
|
|||
|
|
</span>{" "}
|
|||
|
|
· {formatOrderDate(order.orderDate)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<Card className="mb-6 shadow-none">
|
|||
|
|
<CardHeader className="pb-2">
|
|||
|
|
<CardTitle className="text-base">وضعیت سفارش</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent>
|
|||
|
|
<div className="mb-6">
|
|||
|
|
<div className="h-2 w-full overflow-hidden rounded-full bg-muted">
|
|||
|
|
<div
|
|||
|
|
className="h-full bg-emerald-500 transition-all duration-500"
|
|||
|
|
style={{ width: `${progressPercentage}%` }}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<p className="mt-2 text-xs text-muted-foreground">
|
|||
|
|
{completedSteps === order.timeline.length
|
|||
|
|
? "سفارش تحویل داده شد!"
|
|||
|
|
: `مرحله ${completedSteps.toLocaleString("fa-IR")} از ${order.timeline.length.toLocaleString("fa-IR")}`}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="hidden md:block">
|
|||
|
|
<div className="relative flex justify-between">
|
|||
|
|
{order.timeline.map((step, index) => (
|
|||
|
|
<div
|
|||
|
|
key={step.id}
|
|||
|
|
className={cn(
|
|||
|
|
"flex flex-col items-center text-center",
|
|||
|
|
index === 0 && "items-start text-start",
|
|||
|
|
index === order.timeline.length - 1 &&
|
|||
|
|
"items-end text-end",
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={() => selectStep(index)}
|
|||
|
|
aria-label={`${step.label}، ${formatStepDate(step.date)}`}
|
|||
|
|
aria-current={
|
|||
|
|
selectedStepIndex === index ? "step" : undefined
|
|||
|
|
}
|
|||
|
|
className={cn(
|
|||
|
|
"mb-2 flex size-10 items-center justify-center rounded-full border-2 transition-all hover:scale-110",
|
|||
|
|
step.completed
|
|||
|
|
? "border-emerald-500 bg-emerald-500 text-white"
|
|||
|
|
: "border-muted bg-muted hover:border-emerald-300",
|
|||
|
|
selectedStepIndex === index &&
|
|||
|
|
"ring-2 ring-emerald-500 ring-offset-2 ring-offset-background",
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
{step.completed ? (
|
|||
|
|
<Check className="size-5" aria-hidden="true" />
|
|||
|
|
) : (
|
|||
|
|
<span className="text-sm font-medium text-muted-foreground">
|
|||
|
|
{(index + 1).toLocaleString("fa-IR")}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</button>
|
|||
|
|
<p
|
|||
|
|
className={cn(
|
|||
|
|
"text-sm font-medium",
|
|||
|
|
selectedStepIndex === index
|
|||
|
|
? "text-emerald-600"
|
|||
|
|
: step.completed
|
|||
|
|
? "text-foreground"
|
|||
|
|
: "text-muted-foreground",
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
{step.label}
|
|||
|
|
</p>
|
|||
|
|
<p className="mt-0.5 text-xs text-muted-foreground">
|
|||
|
|
{formatStepDate(step.date)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="md:hidden">
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
{order.timeline.map((step, index) => (
|
|||
|
|
<button
|
|||
|
|
key={step.id}
|
|||
|
|
type="button"
|
|||
|
|
onClick={() => selectStep(index)}
|
|||
|
|
aria-label={`${step.label}، ${formatStepDate(step.date)}`}
|
|||
|
|
aria-current={
|
|||
|
|
selectedStepIndex === index ? "step" : undefined
|
|||
|
|
}
|
|||
|
|
className={cn(
|
|||
|
|
"flex w-full gap-3 rounded-lg p-2 text-start transition-colors",
|
|||
|
|
selectedStepIndex === index
|
|||
|
|
? "bg-emerald-500/10"
|
|||
|
|
: "hover:bg-muted",
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
<div className="flex flex-col items-center">
|
|||
|
|
<div
|
|||
|
|
className={cn(
|
|||
|
|
"flex size-8 shrink-0 items-center justify-center rounded-full border-2",
|
|||
|
|
step.completed
|
|||
|
|
? "border-emerald-500 bg-emerald-500 text-white"
|
|||
|
|
: "border-muted bg-muted",
|
|||
|
|
selectedStepIndex === index &&
|
|||
|
|
"ring-2 ring-emerald-500 ring-offset-1",
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
{step.completed ? (
|
|||
|
|
<Check className="size-4" aria-hidden="true" />
|
|||
|
|
) : (
|
|||
|
|
<span className="text-xs font-medium text-muted-foreground">
|
|||
|
|
{(index + 1).toLocaleString("fa-IR")}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
{index < order.timeline.length - 1 && (
|
|||
|
|
<div
|
|||
|
|
className={cn(
|
|||
|
|
"mt-1 h-6 w-0.5",
|
|||
|
|
step.completed ? "bg-emerald-500" : "bg-muted",
|
|||
|
|
)}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="min-w-0 flex-1 pb-2">
|
|||
|
|
<p
|
|||
|
|
className={cn(
|
|||
|
|
"font-medium",
|
|||
|
|
selectedStepIndex === index
|
|||
|
|
? "text-emerald-600"
|
|||
|
|
: step.completed
|
|||
|
|
? "text-foreground"
|
|||
|
|
: "text-muted-foreground",
|
|||
|
|
)}
|
|||
|
|
>
|
|||
|
|
{step.label}
|
|||
|
|
</p>
|
|||
|
|
<p className="text-sm text-muted-foreground">
|
|||
|
|
{step.description}
|
|||
|
|
</p>
|
|||
|
|
<p className="mt-0.5 text-xs text-muted-foreground">
|
|||
|
|
{formatStepDate(step.date)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="mt-6 rounded-lg border bg-muted/30 p-4">
|
|||
|
|
<div className="mb-4 flex items-center gap-3">
|
|||
|
|
<div className="flex size-10 items-center justify-center rounded-full bg-emerald-500/10 text-emerald-600">
|
|||
|
|
{getStepIcon(selectedStep.content.icon)}
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<h3 className="font-semibold">
|
|||
|
|
{selectedStep.content.title}
|
|||
|
|
</h3>
|
|||
|
|
<p className="text-xs text-muted-foreground">
|
|||
|
|
{formatStepDate(selectedStep.date)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<p className="mb-4 text-sm text-muted-foreground">
|
|||
|
|
{selectedStep.content.description}
|
|||
|
|
</p>
|
|||
|
|
<div className="grid gap-2 sm:grid-cols-2">
|
|||
|
|
{selectedStep.content.details.map((detail) => (
|
|||
|
|
<div
|
|||
|
|
key={detail.label}
|
|||
|
|
className="rounded-md bg-background p-3"
|
|||
|
|
>
|
|||
|
|
<p className="text-xs text-muted-foreground">
|
|||
|
|
{detail.label}
|
|||
|
|
</p>
|
|||
|
|
<p
|
|||
|
|
className="text-sm font-medium"
|
|||
|
|
dir={isTrackingDetail(detail.label) ? "ltr" : undefined}
|
|||
|
|
>
|
|||
|
|
{detail.value}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card className="mb-6 shadow-none">
|
|||
|
|
<CardHeader className="pb-4">
|
|||
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|||
|
|
<Package className="size-4" aria-hidden="true" />
|
|||
|
|
{itemCountLabel}
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-4">
|
|||
|
|
{order.items.map((item, index) => (
|
|||
|
|
<div key={item.id}>
|
|||
|
|
<div className="flex gap-4">
|
|||
|
|
<div className="w-16 shrink-0">
|
|||
|
|
<AspectRatio
|
|||
|
|
ratio={1}
|
|||
|
|
className="overflow-hidden rounded-md bg-muted"
|
|||
|
|
>
|
|||
|
|
<img
|
|||
|
|
src={item.image}
|
|||
|
|
alt={item.name}
|
|||
|
|
className="size-full object-cover"
|
|||
|
|
/>
|
|||
|
|
</AspectRatio>
|
|||
|
|
</div>
|
|||
|
|
<div className="min-w-0 flex-1">
|
|||
|
|
<div className="flex items-start justify-between gap-2">
|
|||
|
|
<div>
|
|||
|
|
<h3 className="text-sm font-medium">{item.name}</h3>
|
|||
|
|
{item.details && (
|
|||
|
|
<p className="text-xs text-muted-foreground">
|
|||
|
|
{formatItemDetails(item.details)}
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
<p className="text-xs text-muted-foreground">
|
|||
|
|
تعداد: {item.quantity.toLocaleString("fa-IR")}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<p className="text-sm font-medium tabular-nums">
|
|||
|
|
{currencyFormatter.format(item.price * item.quantity)}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{index < order.items.length - 1 && (
|
|||
|
|
<Separator className="mt-4" />
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<div className="mb-6 grid gap-6 md:grid-cols-2">
|
|||
|
|
<Card className="shadow-none">
|
|||
|
|
<CardHeader className="pb-3">
|
|||
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|||
|
|
<MapPin className="size-4" aria-hidden="true" />
|
|||
|
|
تحویل
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-3">
|
|||
|
|
<div>
|
|||
|
|
<p className="text-sm font-medium">
|
|||
|
|
{order.shippingAddress.name}
|
|||
|
|
</p>
|
|||
|
|
<p className="text-sm text-muted-foreground">
|
|||
|
|
{order.shippingAddress.street}
|
|||
|
|
</p>
|
|||
|
|
<p className="text-sm text-muted-foreground">
|
|||
|
|
{order.shippingAddress.city}،{" "}
|
|||
|
|
{order.shippingAddress.state} کد پستی{" "}
|
|||
|
|
<span dir="rtl" className="tabular-nums">
|
|||
|
|
{order.shippingAddress.zipCode}
|
|||
|
|
</span>
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<Separator />
|
|||
|
|
<div className="flex items-center gap-2 text-sm">
|
|||
|
|
<Truck className="size-4 text-muted-foreground" aria-hidden="true" />
|
|||
|
|
<span>{order.shippingMethod}</span>
|
|||
|
|
</div>
|
|||
|
|
<Badge variant="secondary" className="text-xs">
|
|||
|
|
تحویل تقریبی:{" "}
|
|||
|
|
{formatDeliveryEstimate(
|
|||
|
|
order.estimatedDeliveryFrom,
|
|||
|
|
order.estimatedDeliveryTo,
|
|||
|
|
)}
|
|||
|
|
</Badge>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card className="shadow-none">
|
|||
|
|
<CardHeader className="pb-3">
|
|||
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|||
|
|
<CreditCard className="size-4" aria-hidden="true" />
|
|||
|
|
پرداخت
|
|||
|
|
</CardTitle>
|
|||
|
|
</CardHeader>
|
|||
|
|
<CardContent className="space-y-3">
|
|||
|
|
{order.paymentMethod.type === "card" && (
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<img
|
|||
|
|
src="/block-media/shadcnblocks/block/logos/visa-icon.svg"
|
|||
|
|
alt="ویزا"
|
|||
|
|
className="size-6"
|
|||
|
|
/>
|
|||
|
|
<span className="text-sm" dir="rtl">
|
|||
|
|
•••• {order.paymentMethod.lastFour}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
<Separator />
|
|||
|
|
<div className="space-y-1.5 text-sm">
|
|||
|
|
<div className="flex justify-between">
|
|||
|
|
<span className="text-muted-foreground">جمع جزء</span>
|
|||
|
|
<span className="tabular-nums">
|
|||
|
|
{currencyFormatter.format(order.subtotal)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between">
|
|||
|
|
<span className="text-muted-foreground">هزینه ارسال</span>
|
|||
|
|
<span className="tabular-nums">
|
|||
|
|
{order.shipping === 0
|
|||
|
|
? "رایگان"
|
|||
|
|
: currencyFormatter.format(order.shipping)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between">
|
|||
|
|
<span className="text-muted-foreground">مالیات</span>
|
|||
|
|
<span className="tabular-nums">
|
|||
|
|
{currencyFormatter.format(order.tax)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
{order.discount && order.discount > 0 && (
|
|||
|
|
<div className="flex justify-between text-emerald-600">
|
|||
|
|
<span>تخفیف</span>
|
|||
|
|
<span className="tabular-nums">
|
|||
|
|
−{currencyFormatter.format(order.discount)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<Separator />
|
|||
|
|
<div className="flex justify-between font-semibold">
|
|||
|
|
<span>مجموع</span>
|
|||
|
|
<span className="tabular-nums">
|
|||
|
|
{currencyFormatter.format(order.total)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex flex-wrap justify-center gap-3">
|
|||
|
|
<Button>
|
|||
|
|
<Package className="me-2 size-4" aria-hidden="true" />
|
|||
|
|
پیگیری سفارش
|
|||
|
|
</Button>
|
|||
|
|
<Button variant="outline">
|
|||
|
|
<Download className="me-2 size-4" aria-hidden="true" />
|
|||
|
|
دانلود رسید
|
|||
|
|
</Button>
|
|||
|
|
<Button variant="ghost">
|
|||
|
|
<Printer className="me-2 size-4" aria-hidden="true" />
|
|||
|
|
چاپ
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p className="mt-8 text-center text-sm text-muted-foreground">
|
|||
|
|
ایمیل تأیید به{" "}
|
|||
|
|
<span className="font-medium text-foreground" dir="rtl">
|
|||
|
|
{order.email}
|
|||
|
|
</span>{" "}
|
|||
|
|
ارسال شد.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export { OrderSummary4 };
|