158 lines
6.1 KiB
TypeScript
158 lines
6.1 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useTransition } from "react";
|
||
import Link from "next/link";
|
||
import { useRouter } from "next/navigation";
|
||
import { submitCheckout } from "@/lib/api/checkout";
|
||
import { Button } from "@/components/ui/button";
|
||
import type { Address, Cart, DeliveryType, PaymentMethod } from "@/types/api";
|
||
|
||
const PAYMENT_LABELS_FA: Record<PaymentMethod, string> = {
|
||
wallet: "کیف پول وینوفای",
|
||
online: "درگاه بانکی",
|
||
cash_on_delivery: "پرداخت در محل",
|
||
};
|
||
|
||
export function CheckoutForm({ cart, addresses }: { cart: Cart; addresses: Address[] }) {
|
||
const router = useRouter();
|
||
const [addressUuid, setAddressUuid] = useState(addresses.find((a) => a.is_default)?.uuid ?? addresses[0]?.uuid ?? "");
|
||
const [deliveryType, setDeliveryType] = useState<DeliveryType>("express");
|
||
const [scheduledAt, setScheduledAt] = useState("");
|
||
const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>("cash_on_delivery");
|
||
const [notes, setNotes] = useState("");
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [isPending, startTransition] = useTransition();
|
||
|
||
function handleSubmit(e: React.FormEvent) {
|
||
e.preventDefault();
|
||
setError(null);
|
||
|
||
if (!addressUuid) {
|
||
setError("لطفاً یک آدرس انتخاب کنید.");
|
||
return;
|
||
}
|
||
if (deliveryType === "scheduled" && !scheduledAt) {
|
||
setError("لطفاً زمان ارسال را مشخص کنید.");
|
||
return;
|
||
}
|
||
|
||
startTransition(async () => {
|
||
const result = await submitCheckout({
|
||
address_uuid: addressUuid,
|
||
delivery_type: deliveryType,
|
||
scheduled_at: deliveryType === "scheduled" ? new Date(scheduledAt).toISOString() : null,
|
||
payment_method: paymentMethod,
|
||
notes,
|
||
});
|
||
if (result.ok) {
|
||
router.push(`/order-groups/${result.data.uuid}`);
|
||
} else {
|
||
setError(result.message);
|
||
}
|
||
});
|
||
}
|
||
|
||
if (addresses.length === 0) {
|
||
return (
|
||
<div className="flex flex-col items-center gap-3 rounded-2xl border border-border p-6 text-center">
|
||
<p className="text-sm text-muted-foreground">برای ثبت سفارش ابتدا یک آدرس اضافه کنید.</p>
|
||
<Link href="/addresses">
|
||
<Button>افزودن آدرس</Button>
|
||
</Link>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
|
||
<section className="flex flex-col gap-2">
|
||
<h2 className="text-sm font-semibold text-muted-foreground">آدرس تحویل</h2>
|
||
{addresses.map((address) => (
|
||
<label
|
||
key={address.uuid}
|
||
className={`flex cursor-pointer items-start gap-3 rounded-2xl border p-3 text-sm ${addressUuid === address.uuid ? "border-primary" : "border-border"}`}
|
||
>
|
||
<input
|
||
type="radio"
|
||
name="address"
|
||
checked={addressUuid === address.uuid}
|
||
onChange={() => setAddressUuid(address.uuid)}
|
||
className="mt-1"
|
||
/>
|
||
<span>
|
||
<span className="block font-medium">{address.title || address.full_address}</span>
|
||
<span className="block text-muted-foreground">{address.full_address}</span>
|
||
</span>
|
||
</label>
|
||
))}
|
||
<Link href="/addresses" className="text-xs text-primary underline underline-offset-4">
|
||
مدیریت آدرسها
|
||
</Link>
|
||
</section>
|
||
|
||
<section className="flex flex-col gap-2">
|
||
<h2 className="text-sm font-semibold text-muted-foreground">زمان ارسال</h2>
|
||
<div className="flex gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => setDeliveryType("express")}
|
||
className={`flex-1 rounded-xl border px-3 py-2 text-sm ${deliveryType === "express" ? "border-primary bg-primary/10" : "border-border"}`}
|
||
>
|
||
ارسال فوری
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setDeliveryType("scheduled")}
|
||
className={`flex-1 rounded-xl border px-3 py-2 text-sm ${deliveryType === "scheduled" ? "border-primary bg-primary/10" : "border-border"}`}
|
||
>
|
||
زمانبندی شده
|
||
</button>
|
||
</div>
|
||
{deliveryType === "scheduled" && (
|
||
<input
|
||
type="datetime-local"
|
||
value={scheduledAt}
|
||
onChange={(e) => setScheduledAt(e.target.value)}
|
||
className="h-11 rounded-xl border border-border bg-background px-3 text-sm"
|
||
/>
|
||
)}
|
||
</section>
|
||
|
||
<section className="flex flex-col gap-2">
|
||
<h2 className="text-sm font-semibold text-muted-foreground">روش پرداخت</h2>
|
||
<div className="flex flex-col gap-2">
|
||
{(Object.keys(PAYMENT_LABELS_FA) as PaymentMethod[]).map((method) => (
|
||
<label
|
||
key={method}
|
||
className={`flex cursor-pointer items-center gap-3 rounded-2xl border p-3 text-sm ${paymentMethod === method ? "border-primary" : "border-border"}`}
|
||
>
|
||
<input type="radio" name="payment" checked={paymentMethod === method} onChange={() => setPaymentMethod(method)} />
|
||
{PAYMENT_LABELS_FA[method]}
|
||
</label>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<section className="flex flex-col gap-2">
|
||
<h2 className="text-sm font-semibold text-muted-foreground">توضیحات (اختیاری)</h2>
|
||
<textarea
|
||
value={notes}
|
||
onChange={(e) => setNotes(e.target.value)}
|
||
rows={2}
|
||
className="rounded-xl border border-border bg-background px-3 py-2 text-sm"
|
||
/>
|
||
</section>
|
||
|
||
{error && <p className="rounded-lg bg-destructive/10 px-3 py-2 text-sm text-destructive">{error}</p>}
|
||
|
||
<div className="flex items-center justify-between rounded-2xl border border-border p-4 font-semibold">
|
||
<span>مبلغ قابل پرداخت</span>
|
||
<span>{cart.grand_total.toLocaleString("fa-IR")} تومان</span>
|
||
</div>
|
||
|
||
<Button type="submit" size="lg" disabled={isPending}>
|
||
{isPending ? "در حال ثبت سفارش..." : "ثبت نهایی سفارش"}
|
||
</Button>
|
||
</form>
|
||
);
|
||
}
|