346 lines
10 KiB
TypeScript
346 lines
10 KiB
TypeScript
"use client";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Label } from "@/components/ui/label";
|
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import { Separator } from "@/components/ui/separator";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Input } from "@/components/ui/input";
|
|
import DeliveryTypeSelector from "./DeliveryTypeSelector";
|
|
import { useTranslations } from "next-intl";
|
|
import type { DeliveryType, PaymentType, Province } from "@/lib/types/api";
|
|
import { useState } from "react";
|
|
|
|
interface OrderBillingItem {
|
|
title: string;
|
|
value: string;
|
|
}
|
|
|
|
interface OrderBilling {
|
|
body: OrderBillingItem[];
|
|
footer: {
|
|
title: string;
|
|
value: string;
|
|
};
|
|
}
|
|
|
|
interface OrderSummaryProps {
|
|
order: {
|
|
id: number;
|
|
billing: OrderBilling;
|
|
};
|
|
paymentType: PaymentType | null;
|
|
deliveryType: DeliveryType;
|
|
selectedRegion: string;
|
|
selectedProvince: number | null;
|
|
note: string;
|
|
regionGroups: Record<string, Province[]>;
|
|
availableRegions: string[];
|
|
paymentTypes: PaymentType[];
|
|
phone: string;
|
|
name: string;
|
|
lastName: string;
|
|
onPhoneChange: (phone: string) => void;
|
|
onNameChange: (name: string) => void;
|
|
onLastNameChange: (lastName: string) => void;
|
|
onPaymentTypeChange: (type: PaymentType) => void;
|
|
onDeliveryTypeChange: (type: DeliveryType) => void;
|
|
onRegionChange: (regionCode: string) => void;
|
|
onProvinceChange: (provinceId: number) => void;
|
|
onNoteChange: (note: string) => void;
|
|
onCompleteOrder: () => void;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
export default function OrderSummary({
|
|
order,
|
|
paymentType,
|
|
deliveryType,
|
|
selectedRegion,
|
|
selectedProvince,
|
|
note,
|
|
regionGroups,
|
|
availableRegions,
|
|
paymentTypes,
|
|
phone,
|
|
name,
|
|
lastName,
|
|
onPhoneChange,
|
|
onNameChange,
|
|
onLastNameChange,
|
|
onPaymentTypeChange,
|
|
onDeliveryTypeChange,
|
|
onRegionChange,
|
|
onProvinceChange,
|
|
onNoteChange,
|
|
onCompleteOrder,
|
|
isLoading,
|
|
}: OrderSummaryProps) {
|
|
const t = useTranslations();
|
|
const [showValidation, setShowValidation] = useState(false);
|
|
|
|
const provincesForSelectedRegion = selectedRegion
|
|
? regionGroups[selectedRegion] || []
|
|
: [];
|
|
|
|
const phoneDigits = phone.replace(/\D/g, "");
|
|
const isPhoneValid = phoneDigits.length === 11;
|
|
|
|
const isFormValid =
|
|
selectedRegion &&
|
|
selectedProvince &&
|
|
paymentType &&
|
|
isPhoneValid &&
|
|
name.trim() !== "" &&
|
|
lastName.trim() !== "";
|
|
|
|
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const input = e.target.value;
|
|
const prefix = "+993 ";
|
|
|
|
if (input.length < prefix.length) {
|
|
onPhoneChange(prefix);
|
|
return;
|
|
}
|
|
|
|
const digitsOnly = input.substring(prefix.length).replace(/\D/g, "");
|
|
const limitedDigits = digitsOnly.substring(0, 8);
|
|
|
|
let formattedPhone = prefix;
|
|
if (limitedDigits.length > 0) {
|
|
formattedPhone += limitedDigits.substring(0, 2);
|
|
|
|
if (limitedDigits.length > 2) {
|
|
formattedPhone += " " + limitedDigits.substring(2);
|
|
}
|
|
}
|
|
|
|
onPhoneChange(formattedPhone);
|
|
};
|
|
|
|
const handleCompleteOrderClick = () => {
|
|
setShowValidation(true);
|
|
if (isFormValid) {
|
|
onCompleteOrder();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card className="w-full lg:w-[340px] md:w-[300px] p-6 rounded-lg border border-gray-200 shadow-lg h-fit sticky top-20">
|
|
{/* Customer Information */}
|
|
<div className="mb-8">
|
|
<h3 className="text-xl font-bold mb-5 text-gray-900">
|
|
{t("customer_information")}
|
|
</h3>
|
|
<div className="space-y-5">
|
|
<div>
|
|
<Label className="text-sm font-semibold mb-2 block text-gray-700">
|
|
{t("name")}
|
|
</Label>
|
|
<Input
|
|
type="text"
|
|
value={name}
|
|
onChange={(e) => onNameChange(e.target.value)}
|
|
placeholder={t("name")}
|
|
className={`rounded-[10px] h-12 border-2 transition-colors ${
|
|
showValidation && name.trim() === ""
|
|
? "border-red-500"
|
|
: "border-gray-200 focus:border-gray-900"
|
|
}`}
|
|
/>
|
|
{showValidation && name.trim() === "" && (
|
|
<p className="text-xs text-red-500 mt-2 font-medium">
|
|
{t("requiredField")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="text-sm font-semibold mb-2 block text-gray-700">
|
|
{t("last_name")}
|
|
</Label>
|
|
<Input
|
|
type="text"
|
|
value={lastName}
|
|
onChange={(e) => onLastNameChange(e.target.value)}
|
|
placeholder={t("last_name")}
|
|
className={`rounded-[10px] h-12 border-2 transition-colors ${
|
|
showValidation && lastName.trim() === ""
|
|
? "border-red-500"
|
|
: "border-gray-200 focus:border-gray-900"
|
|
}`}
|
|
/>
|
|
{showValidation && lastName.trim() === "" && (
|
|
<p className="text-xs text-red-500 mt-2 font-medium">
|
|
{t("requiredField")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="text-sm font-semibold mb-2 block text-gray-700">
|
|
{t("phone")}
|
|
</Label>
|
|
<Input
|
|
type="tel"
|
|
value={phone}
|
|
onChange={handlePhoneChange}
|
|
placeholder="+993 61 097651"
|
|
className={`rounded-[10px] h-12 border-2 transition-colors ${
|
|
showValidation && !isPhoneValid
|
|
? "border-red-500"
|
|
: "border-gray-200 focus:border-gray-900"
|
|
}`}
|
|
/>
|
|
{showValidation && !isPhoneValid && (
|
|
<p className="text-xs text-red-500 mt-2 font-medium">
|
|
{t("requiredField")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Region Selection */}
|
|
<div className="mb-8">
|
|
<Label className="text-xl font-bold mb-4 block text-gray-900">
|
|
{t("choose_region")}
|
|
</Label>
|
|
<RadioGroup
|
|
value={selectedRegion}
|
|
onValueChange={(value) => {
|
|
onRegionChange(value);
|
|
onProvinceChange(null as any);
|
|
}}
|
|
className="flex flex-wrap gap-4"
|
|
>
|
|
{availableRegions.map((regionCode) => (
|
|
<div key={regionCode} className="flex items-center space-x-3">
|
|
<RadioGroupItem
|
|
value={regionCode}
|
|
id={`region-${regionCode}`}
|
|
className={`border-2 h-5 w-5 ${
|
|
showValidation && !selectedRegion
|
|
? "border-red-500"
|
|
: "border-gray-400"
|
|
} data-[state=checked]:border-gray-900 data-[state=checked]:bg-gray-900`}
|
|
/>
|
|
<Label
|
|
htmlFor={`region-${regionCode}`}
|
|
className="cursor-pointer uppercase font-semibold text-gray-700 hover:text-gray-900 transition-colors"
|
|
>
|
|
{regionCode}
|
|
</Label>
|
|
</div>
|
|
))}
|
|
</RadioGroup>
|
|
{showValidation && !selectedRegion && (
|
|
<p className="text-xs text-red-500 mt-3 font-medium">
|
|
{t("requiredField")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Province Selection */}
|
|
{selectedRegion && provincesForSelectedRegion.length > 0 && (
|
|
<div className="mb-8">
|
|
<Label className="text-xl font-bold mb-4 block text-gray-900">
|
|
{t("choose_address")}
|
|
</Label>
|
|
<Select
|
|
value={selectedProvince?.toString() || ""}
|
|
onValueChange={(value) => onProvinceChange(parseInt(value))}
|
|
>
|
|
<SelectTrigger
|
|
className={`rounded-[10px] h-12 w-full border-2 font-medium ${
|
|
showValidation && !selectedProvince
|
|
? "border-red-500"
|
|
: "border-gray-200 hover:border-gray-900"
|
|
}`}
|
|
>
|
|
<SelectValue placeholder={t("choose_address")} />
|
|
</SelectTrigger>
|
|
<SelectContent className="rounded-lg">
|
|
{provincesForSelectedRegion.map((province) => (
|
|
<SelectItem
|
|
key={province.id}
|
|
value={province.id.toString()}
|
|
className="rounded-lg"
|
|
>
|
|
{province.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
{showValidation && !selectedProvince && (
|
|
<p className="text-xs text-red-500 mt-3 font-medium">
|
|
{t("requiredField")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Note */}
|
|
<div className="mb-8">
|
|
<Label className="text-xl font-bold mb-4 block text-gray-900">
|
|
{t("note")}
|
|
</Label>
|
|
<Textarea
|
|
value={note}
|
|
onChange={(e) => onNoteChange(e.target.value)}
|
|
className="rounded-[10px] resize-none border-2 border-gray-200 focus:border-gray-900 transition-colors"
|
|
rows={3}
|
|
placeholder={t("note")}
|
|
/>
|
|
</div>
|
|
|
|
{/* Billing */}
|
|
<div className="space-y-3 mb-6">
|
|
{order.billing.body.map((item, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex justify-between text-base font-semibold text-gray-700"
|
|
>
|
|
<span>{item.title}:</span>
|
|
<span>{item.value}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<Separator className="my-6 bg-gray-200" />
|
|
|
|
<div className="flex justify-between items-center mb-8">
|
|
<span className="text-xl font-bold text-gray-900">
|
|
{order.billing.footer.title}:
|
|
</span>
|
|
<span className="text-2xl font-bold text-emerald-600">
|
|
{order.billing.footer.value}
|
|
</span>
|
|
</div>
|
|
|
|
<Button
|
|
onClick={handleCompleteOrderClick}
|
|
disabled={isLoading}
|
|
className="w-full rounded-[10px] cursor-pointer bg-gradient-to-r from-gray-900 to-gray-800 hover:from-gray-800 hover:to-gray-700 h-14 text-lg font-bold disabled:opacity-50 shadow-md hover:shadow-lg transition-all duration-300"
|
|
size="lg"
|
|
>
|
|
{isLoading ? (
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
<span>{t("order")}...</span>
|
|
</div>
|
|
) : (
|
|
t("order")
|
|
)}
|
|
</Button>
|
|
</Card>
|
|
);
|
|
}
|