Files
reui-blocks/blocks/checkout2.tsx
T

1044 lines
31 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.
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { Building2, Trash } from "lucide-react";
import { useCallback } from "react";
import type { UseFormReturn } from "react-hook-form";
import {
Controller,
FormProvider,
useFieldArray,
useForm,
useFormContext,
} from "react-hook-form";
import z from "zod";
import { Logo, LogoImage } from "@/components/shadcnblocks/logo";
import QuantityInput from "@/components/shadcnblocks/quantity-input";
import { AspectRatio } from "@/components/ui/aspect-ratio";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import {
Field,
FieldContent,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
FieldTitle,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { cn } from "@/lib/utils";
interface ProductPrice {
regular: number;
sale?: number;
currency: string;
}
type CartItem = {
product_id: string;
link: string;
name: string;
image: string;
price: ProductPrice;
quantity: number;
details: {
label: string;
value: string;
}[];
};
interface CartItemProps extends CartItem {
index: number;
onRemoveClick: () => void;
onQuantityChange: (newQty: number) => void;
}
interface CartProps {
cartItems: CartItem[];
form: UseFormReturn<CheckoutFormType>;
}
const formatPrice = (price: number, currency = "IRR") =>
new Intl.NumberFormat("fa-IR", {
style: "currency",
currency,
}).format(price);
const PAYMENT_METHODS = {
creditCard: "creditCard",
paypal: "paypal",
onlineBankTransfer: "onlineBankTransfer",
};
type PaymentMethod = keyof typeof PAYMENT_METHODS;
const CreditCardPayment = z.object({
method: z.literal(PAYMENT_METHODS.creditCard),
cardholderName: z.string(),
cardNumber: z.string(),
expiryDate: z
.string()
.regex(/^(0[1-9]|1[0-2])\/\d{2}$/, "فرمت نامعتبر (MM/YY)")
.refine((value) => {
const [mm, yy] = value.split("/").map(Number);
const now = new Date();
const currentMonth = now.getMonth() + 1;
const currentYear = now.getFullYear() % 100;
if (yy < currentYear) return false;
if (yy === currentYear && mm < currentMonth) return false;
return true;
}, "کارت منقضی شده است"),
cvc: z.string(),
});
const PayPalPayment = z.object({
method: z.literal(PAYMENT_METHODS.paypal),
payPalEmail: z.string(),
});
const BankTransferPayment = z.object({
method: z.literal(PAYMENT_METHODS.onlineBankTransfer),
bankName: z.string(),
accountNumber: z.string(),
});
const PaymentSchema = z.discriminatedUnion("method", [
CreditCardPayment,
PayPalPayment,
BankTransferPayment,
]);
const checkoutFormSchema = z.object({
contactInfo: z.object({
email: z.string(),
subscribe: z.boolean().optional(),
}),
address: z.object({
country: z.string(),
firstName: z.string(),
lastName: z.string(),
address: z.string(),
postalCode: z.string(),
city: z.string(),
phone: z.string(),
}),
shippingMethod: z.string(),
payment: PaymentSchema,
products: z
.object({
product_id: z.string(),
quantity: z.number(),
price: z.number(),
})
.array(),
});
type CheckoutFormType = z.infer<typeof checkoutFormSchema>;
const CART_ITEMS: CartItem[] = [
{
product_id: "product-1",
link: "#",
name: "کفش اسپرت زرشکی",
image:
"/block-media/shadcnblocks/block/ecommerce/clothes/stylish-maroon-sneaker.png",
price: {
regular: 354.0,
currency: "IRR",
},
quantity: 1,
details: [
{ label: "رنگ", value: "قرمز" },
{ label: "سایز", value: "36" },
],
},
{
product_id: "product-2",
link: "#",
name: "سوئیشرت دو رنگ با لوگوی گلدوزی",
image:
"/block-media/shadcnblocks/block/ecommerce/clothes/bicolor-crewneck-sweatshirt-with-embroidered-logo.png",
price: {
regular: 499.0,
currency: "IRR",
},
quantity: 1,
details: [
{ label: "رنگ", value: "آبی و سفید" },
{ label: "سایز", value: "L" },
],
},
{
product_id: "product-4",
link: "#",
name: "کیف چرمی زرشکی",
image:
"/block-media/shadcnblocks/block/ecommerce/clothes/maroon-leather-handbag.png",
price: {
regular: 245.0,
currency: "IRR",
},
quantity: 1,
details: [{ label: "رنگ", value: "زرشکی" }],
},
];
interface Checkout2Props {
cartItems?: CartItem[];
className?: string;
}
const Checkout2 = ({ cartItems = CART_ITEMS, className }: Checkout2Props) => {
const defaultProducts = cartItems.map((item) => ({
product_id: item.product_id,
quantity: item.quantity,
price: item.price.sale ?? item.price.regular,
}));
const form = useForm({
resolver: zodResolver(checkoutFormSchema),
defaultValues: {
payment: {
method: PAYMENT_METHODS.creditCard,
},
products: defaultProducts,
},
});
const onSubmit = (data: CheckoutFormType) => {
console.log(data);
};
return (
<section dir="rtl" lang="fa" className={cn("py-32", className)}>
<div className="container">
<div className="mb-10">
<Logo url="#">
<LogoImage
src="/block-media/shadcnblocks/block/logos/ShadcnBlocks-logo.svg"
alt="لوگو"
className="h-8"
/>
</Logo>
<h1 className="mt-6 text-3xl font-bold tracking-tight md:text-xl">
تسویه حساب
</h1>
<p className="mt-2 text-muted-foreground">
اطلاعات زیر را تکمیل کنید تا سفارش شما در کوتاه‌ترین زمان پردازش و ارسال شود.
</p>
</div>
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="grid grid-cols-1 gap-8 lg:grid-cols-5">
<div className="space-y-6 lg:col-span-3">
<Card className="shadow-none">
<CardHeader>
<CardTitle>اطلاعات تماس</CardTitle>
</CardHeader>
<CardContent>
<ContactFields />
</CardContent>
</Card>
<Card className="shadow-none">
<CardHeader>
<CardTitle>آدرس ارسال</CardTitle>
</CardHeader>
<CardContent>
<AddressFields />
</CardContent>
</Card>
<Card className="shadow-none">
<CardHeader>
<CardTitle>روش ارسال</CardTitle>
</CardHeader>
<CardContent>
<ShippingMethodFields />
</CardContent>
</Card>
<Card className="shadow-none">
<CardHeader>
<CardTitle>پرداخت</CardTitle>
</CardHeader>
<CardContent>
<PaymentFields />
</CardContent>
</Card>
<Button type="submit" size="lg" className="w-full">
ثبت و پرداخت سفارش
</Button>
</div>
<div className="lg:col-span-2">
<div className="sticky top-8">
<Cart form={form} cartItems={cartItems} />
</div>
</div>
</div>
</form>
</FormProvider>
</div>
</section>
);
};
const ContactFields = () => {
const form = useFormContext();
return (
<FieldGroup className="gap-4">
<Controller
name="contactInfo.email"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-email"
>
ایمیل
</FieldLabel>
<Input
{...field}
id="checkout-email"
type="email"
dir="rtl"
placeholder="example@email.com"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<Controller
name="contactInfo.subscribe"
control={form.control}
render={({ field }) => (
<Field orientation="horizontal">
<Checkbox
id="checkout-subscribe"
name={field.name}
// @ts-ignore
checked={field.value}
onCheckedChange={field.onChange}
/>
<FieldLabel htmlFor="checkout-subscribe" className="font-normal">
مایلم از تخفیف‌ها و پیشنهادهای ویژه از طریق ایمیل مطلع شوم.
</FieldLabel>
</Field>
)}
/>
</FieldGroup>
);
};
const AddressFields = () => {
const form = useFormContext();
return (
<FieldGroup className="gap-4">
<Controller
name="address.country"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-country"
>
کشور
</FieldLabel>
<Input
{...field}
id="checkout-country"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<div className="grid gap-4 sm:grid-cols-2">
<Controller
name="address.firstName"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-firstName"
>
نام
</FieldLabel>
<Input
{...field}
id="checkout-firstName"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<Controller
name="address.lastName"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-lastName"
>
نام خانوادگی
</FieldLabel>
<Input
{...field}
id="checkout-lastName"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</div>
<Controller
name="address.address"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-address"
>
آدرس
</FieldLabel>
<Input
{...field}
id="checkout-address"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<div className="grid gap-4 sm:grid-cols-2">
<Controller
name="address.postalCode"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-postalCode"
>
کد پستی
</FieldLabel>
<Input
{...field}
id="checkout-postalCode"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<Controller
name="address.city"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-city"
>
شهر
</FieldLabel>
<Input
{...field}
id="checkout-city"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</div>
<Controller
name="address.phone"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-phone"
>
تلفن
</FieldLabel>
<Input
{...field}
id="checkout-phone"
type="tel"
dir="rtl"
placeholder="09123456789"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</FieldGroup>
);
};
const ShippingMethodFields = () => {
const form = useFormContext();
return (
<Controller
name="shippingMethod"
control={form.control}
render={({ field, fieldState }) => (
<Field>
<RadioGroup
name={field.name}
value={field.value}
onValueChange={field.onChange}
className="gap-3"
>
<FieldLabel htmlFor="checkout-shippingMethod-1">
<Field
orientation="horizontal"
data-invalid={fieldState.invalid}
className="rounded-lg border p-4"
>
<RadioGroupItem
value="UPS"
id="checkout-shippingMethod-1"
aria-invalid={fieldState.invalid}
/>
<FieldContent className="flex-1">
<FieldTitle>ارسال سریع</FieldTitle>
<FieldDescription>تحویل در اولین روز کاری</FieldDescription>
</FieldContent>
<p className="text-sm font-medium tabular-nums">
{formatPrice(10, "IRR")}
</p>
</Field>
</FieldLabel>
<FieldLabel htmlFor="checkout-shippingMethod-2">
<Field
orientation="horizontal"
data-invalid={fieldState.invalid}
className="rounded-lg border p-4"
>
<RadioGroupItem
value="FedEx"
id="checkout-shippingMethod-2"
aria-invalid={fieldState.invalid}
/>
<FieldContent className="flex-1">
<FieldTitle>ارسال استاندارد</FieldTitle>
<FieldDescription>
تحویل: ۳ تا ۵ روز کاری
</FieldDescription>
</FieldContent>
<p className="text-sm font-medium tabular-nums">
{formatPrice(2.99, "IRR")}
</p>
</Field>
</FieldLabel>
</RadioGroup>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
);
};
const PaymentFields = () => {
const form = useFormContext();
const paymentMethod = form.watch("payment.method") as PaymentMethod;
return (
<div className="space-y-6">
<Controller
name="payment.method"
control={form.control}
render={({ field, fieldState }) => (
<Field>
<RadioGroup
name={field.name}
value={field.value}
onValueChange={field.onChange}
className="gap-3"
>
<FieldLabel htmlFor="checkout-payment-method-1">
<Field
orientation="horizontal"
data-invalid={fieldState.invalid}
className="rounded-lg border p-4"
>
<RadioGroupItem
value="creditCard"
id="checkout-payment-method-1"
aria-invalid={fieldState.invalid}
/>
<FieldContent className="flex-1">
<FieldTitle>کارت بانکی</FieldTitle>
</FieldContent>
<img
src="/block-media/shadcnblocks/block/logos/visa-icon.svg"
alt="کارت بانکی"
className="size-5"
/>
</Field>
</FieldLabel>
<FieldLabel htmlFor="checkout-payment-method-2">
<Field
orientation="horizontal"
data-invalid={fieldState.invalid}
className="rounded-lg border p-4"
>
<RadioGroupItem
value="paypal"
id="checkout-payment-method-2"
aria-invalid={fieldState.invalid}
/>
<FieldContent className="flex-1">
<FieldTitle>کیف پول دیجیتال</FieldTitle>
</FieldContent>
<img
src="/block-media/shadcnblocks/block/logos/paypal-icon.svg"
alt="PayPal"
className="size-5"
/>
</Field>
</FieldLabel>
<FieldLabel htmlFor="checkout-payment-method-3">
<Field
orientation="horizontal"
data-invalid={fieldState.invalid}
className="rounded-lg border p-4"
>
<RadioGroupItem
value="onlineBankTransfer"
id="checkout-payment-method-3"
aria-invalid={fieldState.invalid}
/>
<FieldContent className="flex-1">
<FieldTitle>انتقال بانکی</FieldTitle>
</FieldContent>
<Building2 className="size-5 text-muted-foreground" />
</Field>
</FieldLabel>
</RadioGroup>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<PaymentFieldsByMethod method={paymentMethod} />
</div>
);
};
const PaymentFieldsByMethod = ({ method }: { method: PaymentMethod }) => {
const form = useFormContext();
if (!method) return null;
switch (method) {
case PAYMENT_METHODS.creditCard:
return (
<div className="space-y-4">
<Controller
name="payment.cardholderName"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-payment-cardholderName"
>
نام روی کارت
</FieldLabel>
<Input
{...field}
id="checkout-payment-cardholderName"
placeholder="نام و نام خانوادگی"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
<Controller
name="payment.cardNumber"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-payment-cardNumber"
>
شماره کارت
</FieldLabel>
<Input
{...field}
id="checkout-payment-cardNumber"
dir="rtl"
placeholder="1234 5678 9012 3456"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
<div className="grid gap-4 sm:grid-cols-2">
<DateInput />
<Controller
name="payment.cvc"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-payment-cvc"
>
کد امنیتی (CVC)
</FieldLabel>
<Input
{...field}
id="checkout-payment-cvc"
dir="rtl"
placeholder="123"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
</div>
</div>
);
case PAYMENT_METHODS.paypal:
return (
<Controller
name="payment.payPalEmail"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-payment-payPalEmail"
>
ایمیل کیف پول دیجیتال
</FieldLabel>
<Input
{...field}
type="email"
dir="rtl"
placeholder="example@email.com"
id="checkout-payment-payPalEmail"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
);
case PAYMENT_METHODS.onlineBankTransfer:
return (
<div className="space-y-4">
<Controller
name="payment.bankName"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-payment-bankName"
>
نام بانک
</FieldLabel>
<Input
{...field}
placeholder="نام بانک"
id="checkout-payment-bankName"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
<Controller
name="payment.accountNumber"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-payment-accountNumber"
>
شماره حساب
</FieldLabel>
<Input
{...field}
id="checkout-payment-accountNumber"
dir="rtl"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
</div>
);
default:
return null;
}
};
const DateInput = () => {
const form = useFormContext();
return (
<Controller
name="payment.expiryDate"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel
className="text-sm font-normal"
htmlFor="checkout-payment-expiryDate"
>
تاریخ انقضا
</FieldLabel>
<Input
{...field}
onChange={(e) => {
let val = e.target.value;
val = val.replace(/[^0-9/]/g, "");
const prev = field.value ?? "";
const isDeleting = val.length < prev.length;
if (!isDeleting) {
if (val.length === 2 && !val.includes("/")) {
val = val + "/";
}
}
if (val.length > 5) {
val = val.slice(0, 5);
}
field.onChange(val);
}}
pattern="^(0[1-9]|1[0-2])/[0-9]{2}$"
placeholder="MM/YY"
dir="rtl"
id="checkout-payment-expiryDate"
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
);
};
const Cart = ({ cartItems, form }: CartProps) => {
const { fields, remove, update } = useFieldArray({
control: form.control,
name: "products",
});
const formItems = form.watch("products");
const totalPrice = formItems?.reduce(
(sum, p) => sum + p.price * p.quantity,
0,
);
const handleRemove = useCallback(
(index: number) => () => {
remove(index);
},
[remove],
);
const handleQuantityChange = useCallback(
(index: number) => (newQty: number) =>
update(index, { ...fields[index], quantity: newQty }),
[update, fields],
);
return (
<Card className="shadow-none">
<CardHeader>
<CardTitle>خلاصه خرید</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<ul className="space-y-4">
{fields.map((field, index) => {
const cartItem = cartItems.find(
(p) => p.product_id === field.product_id,
);
if (!cartItem) return null;
return (
<li key={field.id}>
<CartItemComponent
{...cartItem}
onRemoveClick={() => handleRemove(index)()}
onQuantityChange={(newQty: number) =>
handleQuantityChange(index)(newQty)
}
index={index}
/>
</li>
);
})}
</ul>
<div className="space-y-3 border-t pt-4">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">جمع سفارش</span>
<span className="tabular-nums">
{formatPrice(totalPrice ?? 0, cartItems[0]?.price.currency)}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">هزینه ارسال</span>
<span>پس از انتخاب روش ارسال محاسبه می‌شود</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">مالیات بر ارزش افزوده و عوارض</span>
<span className="tabular-nums">
{formatPrice(35.8, cartItems[0]?.price.currency)}
</span>
</div>
</div>
<div className="flex justify-between border-t pt-4">
<span className="text-lg font-semibold">مبلغ قابل پرداخت</span>
<span className="text-lg font-semibold tabular-nums">
{formatPrice(totalPrice ?? 0, cartItems[0]?.price.currency)}
</span>
</div>
</CardContent>
</Card>
);
};
const CartItemComponent = ({
image,
name,
link,
details,
price,
index,
onQuantityChange,
onRemoveClick,
}: CartItemProps) => {
const { regular, currency } = price;
return (
<div className="flex gap-4">
<div className="w-16 shrink-0">
<AspectRatio ratio={1} className="overflow-hidden rounded-md bg-muted">
<img src={image} alt={name} className="size-full object-cover" />
</AspectRatio>
</div>
<div className="min-w-0 flex-1">
<div className="flex justify-between gap-2">
<div>
<a
href={link}
className="line-clamp-2 text-sm font-medium hover:underline"
>
{name}
</a>
<ProductDetails details={details} />
</div>
<span className="shrink-0 text-sm font-medium tabular-nums">
{formatPrice(regular, currency)}
</span>
</div>
<div className="mt-2 flex items-center justify-between gap-2">
<QuantityField index={index} onQuantityChange={onQuantityChange} />
<Button
size="icon"
variant="ghost"
className="size-8"
onClick={onRemoveClick}
aria-label={`حذف ${name} از سبد`}
>
<Trash className="size-4" />
<span className="sr-only">حذف از سبد</span>
</Button>
</div>
</div>
</div>
);
};
const ProductDetails = ({
details,
}: {
details?: { label: string; value: string }[];
}) => {
if (!details) return null;
return (
<p className="text-xs text-muted-foreground">
{details.map((item, index) => (
<span key={index}>
{item.value}
{index < details.length - 1 && " / "}
</span>
))}
</p>
);
};
const QuantityField = ({
index,
onQuantityChange,
}: {
index: number;
onQuantityChange: (n: number) => void;
}) => {
const { control } = useFormContext();
return (
<Controller
name={`products.${index}.quantity`}
control={control}
render={({ field }) => (
<Field className="w-auto">
<QuantityInput
inputProps={field}
onValueChange={(newQty) => {
field.onChange(newQty);
onQuantityChange(newQty);
}}
className="h-8 rounded-none"
/>
</Field>
)}
/>
);
};
export { Checkout2 };