Files
reui-blocks/blocks/order-summary2.tsx
T

440 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
CheckCircle,
CreditCard,
Download,
MapPin,
Package,
Printer,
Truck,
} from "lucide-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 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;
/** ISO 8601 date range for estimated delivery */
estimatedDeliveryFrom: string;
estimatedDeliveryTo?: string;
paymentMethod: PaymentMethod;
}
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 formatOrderDate = (isoDate: string) =>
dateFormatter.format(new Date(isoDate + "T12:00:00"));
const formatDeliveryEstimate = (from: string, to?: string) => {
if (to && to !== from) {
return `${formatOrderDate(from)} تا ${formatOrderDate(to)}`;
}
return formatOrderDate(from);
};
const DEFAULT_ORDER: OrderSummaryData = {
orderNumber: "ORD-2024-78432",
orderDate: "2024-12-14",
status: "confirmed",
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",
},
};
interface OrderSummary2Props {
order?: OrderSummaryData;
className?: string;
}
const formatItemDetails = (details: OrderItem["details"]) => {
if (!details?.length) return null;
return details.map((d) => `${d.label} ${d.value}`).join(" · ");
};
const OrderSummary2 = ({
order = DEFAULT_ORDER,
className,
}: OrderSummary2Props) => {
const getStatusBadge = (status: OrderSummaryData["status"]) => {
const variants: Record<
OrderSummaryData["status"],
{ label: string; className: string }
> = {
confirmed: {
label: "سفارش تأیید شد",
className: "bg-emerald-500/10 text-emerald-600 hover:bg-emerald-500/10",
},
processing: {
label: "در حال پردازش",
className: "bg-amber-500/10 text-amber-600 hover:bg-amber-500/10",
},
shipped: {
label: "ارسال‌شده",
className: "bg-blue-500/10 text-blue-600 hover:bg-blue-500/10",
},
delivered: {
label: "تحویل‌شده",
className: "bg-emerald-500/10 text-emerald-600 hover:bg-emerald-500/10",
},
};
return variants[status];
};
const statusBadge = getStatusBadge(order.status);
return (
<section
dir="rtl"
lang="fa"
className={cn("py-16 md:py-24", className)}
>
<div className="container max-w-xl">
<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-2 text-xl font-bold tracking-tight">
از خرید شما سپاسگزاریم!
</h1>
<p className="text-sm text-muted-foreground">
تأییدیه به{" "}
<span className="font-medium text-foreground" dir="rtl">
{order.email}
</span>{" "}
ارسال شد.
</p>
</div>
<Card className="mb-4 shadow-none">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground">سفارش</p>
<p className="font-semibold" dir="rtl">
{order.orderNumber}
</p>
</div>
<Badge className={statusBadge.className}>
{statusBadge.label}
</Badge>
</div>
<p className="mt-2 text-xs text-muted-foreground">
ثبت‌شده در {formatOrderDate(order.orderDate)}
</p>
</CardContent>
</Card>
<Card className="mb-4 shadow-none">
<CardHeader className="p-4 pb-2">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Package className="size-4" aria-hidden="true" />
اقلام (
{order.items.length.toLocaleString("fa-IR")})
</CardTitle>
</CardHeader>
<CardContent className="p-4 pt-2">
<div className="space-y-3">
{order.items.map((item, index) => (
<div key={item.id}>
<div className="flex gap-3">
<div className="w-14 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">
<h3 className="text-sm leading-tight font-medium">
{item.name}
</h3>
{item.details && (
<p className="text-xs text-muted-foreground">
{formatItemDetails(item.details)}
</p>
)}
<div className="mt-1 flex items-center justify-between">
<span className="text-xs text-muted-foreground">
تعداد: {item.quantity.toLocaleString("fa-IR")}
</span>
<span className="text-sm font-medium tabular-nums">
{currencyFormatter.format(
item.price * item.quantity,
)}
</span>
</div>
</div>
</div>
{index < order.items.length - 1 && (
<Separator className="mt-3" />
)}
</div>
))}
</div>
</CardContent>
</Card>
<Card className="mb-4 shadow-none">
<CardContent className="p-4">
<div className="space-y-2 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>
)}
<Separator className="my-2" />
<div className="flex justify-between font-semibold">
<span>مجموع</span>
<span className="tabular-nums">
{currencyFormatter.format(order.total)}
</span>
</div>
</div>
</CardContent>
</Card>
<Card className="mb-4 shadow-none">
<CardContent className="p-4">
<div className="space-y-4">
<div>
<div className="mb-2 flex items-center gap-2">
<MapPin
className="size-4 text-muted-foreground"
aria-hidden="true"
/>
<span className="text-sm font-medium">تحویل</span>
</div>
<div className="ms-6 text-sm">
<p className="font-medium">{order.shippingAddress.name}</p>
<p className="text-muted-foreground">
{order.shippingAddress.street}
</p>
<p className="text-muted-foreground">
{order.shippingAddress.city}،{" "}
{order.shippingAddress.state} کد پستی{" "}
<span dir="rtl" className="tabular-nums">
{order.shippingAddress.zipCode}
</span>
</p>
</div>
<div className="mt-2 ms-6 flex items-center gap-2 text-sm">
<Truck
className="size-3.5 text-muted-foreground"
aria-hidden="true"
/>
<span className="text-muted-foreground">
{order.shippingMethod} · تحویل تقریبی{" "}
{formatDeliveryEstimate(
order.estimatedDeliveryFrom,
order.estimatedDeliveryTo,
)}
</span>
</div>
</div>
<Separator />
<div>
<div className="mb-2 flex items-center gap-2">
<CreditCard
className="size-4 text-muted-foreground"
aria-hidden="true"
/>
<span className="text-sm font-medium">پرداخت</span>
</div>
{order.paymentMethod.type === "card" && (
<div className="ms-6 flex items-center gap-2">
<img
src="/block-media/shadcnblocks/block/logos/visa-icon.svg"
alt="ویزا"
className="size-6"
/>
<span className="text-sm">
{order.paymentMethod.cardBrand} ····{" "}
<span dir="rtl" className="tabular-nums">
{order.paymentMethod.lastFour}
</span>
</span>
</div>
)}
{order.paymentMethod.type === "paypal" && (
<div className="ms-6 flex items-center gap-2">
<img
src="/block-media/shadcnblocks/block/logos/paypal-icon.svg"
alt="پی‌پال"
className="size-6"
/>
<span className="text-sm" dir="rtl">
{order.paymentMethod.email}
</span>
</div>
)}
</div>
</div>
</CardContent>
</Card>
<div className="space-y-2">
<Button className="w-full">
<Package className="me-2 size-4" aria-hidden="true" />
پیگیری سفارش
</Button>
<div className="grid grid-cols-2 gap-2">
<Button variant="outline">
<Download className="me-2 size-4" aria-hidden="true" />
رسید
</Button>
<Button variant="outline">
<Printer className="me-2 size-4" aria-hidden="true" />
چاپ
</Button>
</div>
</div>
<p className="mt-6 text-center text-xs text-muted-foreground">
سؤالی دارید؟{" "}
<a href="#" className="underline underline-offset-2">
تماس با پشتیبانی
</a>
</p>
</div>
</section>
);
};
export { OrderSummary2 };