diff --git a/app/[locale]/cart/page.tsx b/app/[locale]/cart/page.tsx index 42965f2..759ada1 100644 --- a/app/[locale]/cart/page.tsx +++ b/app/[locale]/cart/page.tsx @@ -26,7 +26,7 @@ export default function CartPage() { const [selectedRegion, setSelectedRegion] = useState(""); const [selectedProvince, setSelectedProvince] = useState(null); const [note, setNote] = useState(""); - const [phone, setPhone] = useState(""); + const [phone, setPhone] = useState("+993 "); const [name, setName] = useState(""); const [lastName, setLastName] = useState(""); const router = useRouter(); @@ -88,41 +88,45 @@ export default function CartPage() { setSelectedProvince(null); }; - const handleCompleteOrder = () => { - if ( - !selectedRegion || - !selectedProvince || - !paymentType || - !phone || - !name - ) { - console.warn("Missing required fields for order"); - return; - } - - const selectedProvinceData = provinces.find( - (p) => p.id === selectedProvince - ); - if (!selectedProvinceData) return; - - createOrder( - { - customer_name: name, - customer_phone: phone, - customer_address: selectedProvinceData.name, - shipping_method: "standart", - payment_type_id: paymentType.id, - region: selectedRegion, - note: note || undefined, - }, - { - onSuccess: () => { - router.push(`/orders`); - }, - } - ); + + const formatPhoneForBackend = (phoneNumber: string): string => { + + return phoneNumber.replace(/^\+993\s*/, "").replace(/\s+/g, ""); }; + const handleCompleteOrder = () => { + if (!selectedRegion || !selectedProvince || !paymentType || !phone || !name) { + console.warn("Missing required fields for order"); + return; + } + + const phoneDigits = formatPhoneForBackend(phone); + if (phoneDigits.length !== 8) { + console.warn("Phone number must be exactly 8 digits"); + return; + } + + const selectedProvinceData = provinces.find((p) => p.id === selectedProvince); + if (!selectedProvinceData) return; + + createOrder( + { + customer_name: `${name} ${lastName}`.trim(), + customer_phone: parseInt(phoneDigits, 10), + customer_address: selectedProvinceData.name, + shipping_method: "standart", + payment_type_id: paymentType.id, + region: selectedRegion, + note: note || undefined, + }, + { + onSuccess: () => { + router.push(`/orders`); + }, + } + ); +}; + if (!isClient) return null; if (isLoading) { @@ -254,4 +258,4 @@ export default function CartPage() { ); -} +} \ No newline at end of file diff --git a/components/layout/ui/AuthDialog.tsx b/components/layout/ui/AuthDialog.tsx index c24c584..871aad6 100644 --- a/components/layout/ui/AuthDialog.tsx +++ b/components/layout/ui/AuthDialog.tsx @@ -4,7 +4,12 @@ import { useState, useCallback } from "react"; import Image from "next/image"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { toast } from "sonner"; import Logo from "@/public/logo.webp"; import { useLogin, useVerifyToken } from "@/lib/hooks/useAuth"; @@ -16,10 +21,9 @@ interface AuthDialogProps { } export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { - const [phone, setPhone] = useState("993"); + const [phone, setPhone] = useState("+993 "); const [otp, setOtp] = useState(""); const [otpSent, setOtpSent] = useState(false); - const [rawPhone, setRawPhone] = useState(""); const t = useTranslations(); const { mutate: login, isPending: isLoginLoading } = useLogin(); @@ -27,25 +31,55 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { const resetDialog = useCallback(() => { setOtpSent(false); - setPhone("993"); + setPhone("+993 "); setOtp(""); - setRawPhone(""); onClose(); }, [onClose]); + const formatPhoneForBackend = (phoneNumber: string): string => { + return phoneNumber.replace(/^\+993\s*/, "").replace(/\s+/g, ""); + }; + + const handlePhoneChange = (e: React.ChangeEvent) => { + const input = e.target.value; + const prefix = "+993 "; + + if (input.length < prefix.length) { + setPhone(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); + } + } + + setPhone(formattedPhone); + }; + + const isPhoneValid = (): boolean => { + const phoneDigits = formatPhoneForBackend(phone); + return phoneDigits.length === 8; + }; + const handleSendOtp = useCallback(() => { - const cleanPhone = phone.replace(/\D/g, ""); - - if (cleanPhone.length !== 11 || !cleanPhone.startsWith("993")) { + if (!isPhoneValid()) { toast.error(t("invalid_phone")); return; } - const phoneNumber = cleanPhone.substring(3); - setRawPhone(phoneNumber); + const phoneNumber = formatPhoneForBackend(phone); login( - { phone_number: phoneNumber }, + { phone_number: parseInt(phoneNumber, 10) }, { onSuccess: () => { toast.success(t("code_sent")); @@ -64,10 +98,12 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { return; } + const phoneNumber = formatPhoneForBackend(phone); + verifyToken( { - phone_number: rawPhone, - code: otp, + phone_number: parseInt(phoneNumber, 10), + code: parseInt(otp, 10), }, { onSuccess: () => { @@ -80,21 +116,16 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { }, } ); - }, [otp, rawPhone, verifyToken, resetDialog, t]); + }, [otp, phone, verifyToken, resetDialog, t]); - const handleKeyPress = useCallback((e: React.KeyboardEvent, action: () => void) => { - if (e.key === "Enter") { - action(); - } - }, []); - - const formatPhoneInput = useCallback((value: string) => { - const cleaned = value.replace(/\D/g, ""); - if (!cleaned.startsWith("993")) { - return "993"; - } - return cleaned.substring(0, 11); - }, []); + const handleKeyPress = useCallback( + (e: React.KeyboardEvent, action: () => void) => { + if (e.key === "Enter") { + action(); + } + }, + [] + ); return ( @@ -105,21 +136,24 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { Logo - {t("common.enterPhone")} -

{t("common.weWillSendCode")}

+ + {t("common.enterPhone")} + +

+ {t("common.weWillSendCode")} +

setPhone(formatPhoneInput(e.target.value))} + onChange={handlePhoneChange} className="h-12 rounded-xl" onKeyDown={(e) => handleKeyPress(e, handleSendOtp)} disabled={otpSent || isLoginLoading} - maxLength={11} />

{t("phone_format")}

@@ -129,7 +163,9 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { type="text" placeholder={t("common.code")} value={otp} - onChange={(e) => setOtp(e.target.value.replace(/\D/g, "").substring(0, 6))} + onChange={(e) => + setOtp(e.target.value.replace(/\D/g, "").substring(0, 6)) + } className="h-12 rounded-xl" onKeyDown={(e) => handleKeyPress(e, handleLogin)} disabled={isVerifyLoading} @@ -142,18 +178,20 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { onClick={otpSent ? handleLogin : handleSendOtp} className="w-full cursor-pointer h-12 rounded-xl font-bold text-base bg-[#005bff] hover:bg-[#0041c4]" size="lg" - disabled={isLoginLoading || isVerifyLoading} + disabled={ + isLoginLoading || isVerifyLoading || (!otpSent && !isPhoneValid()) + } > {isLoginLoading ? t("sending") : isVerifyLoading - ? t("verifying") - : otpSent - ? t("verify") - : t("common.send")} + ? t("verifying") + : otpSent + ? t("verify") + : t("common.send")}
); -} \ No newline at end of file +} diff --git a/features/cart/components/CartItemCard.tsx b/features/cart/components/CartItemCard.tsx index ef8524b..cd9aa93 100644 --- a/features/cart/components/CartItemCard.tsx +++ b/features/cart/components/CartItemCard.tsx @@ -326,7 +326,7 @@ export default function CartItemCard({ item, onUpdate }: CartItemCardProps) {
-
+
{item.product.name}) => { + 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 ( {/* Customer Information */} -
+

{t("customer_information")}

@@ -107,8 +150,13 @@ export default function OrderSummary({ value={name} onChange={(e) => onNameChange(e.target.value)} placeholder={t("name")} - className="rounded-lg" + className={`rounded-lg ${ + showValidation && name.trim() === "" ? "border-red-500" : "" + }`} /> + {showValidation && name.trim() === "" && ( +

Bu alan zorunludur

+ )}
{/* Payment Type */} -
+

{t("payment_type")}

{paymentTypes.map((type) => ( @@ -147,6 +207,8 @@ export default function OrderSummary({ className={`flex-1 cursor-pointer transition-all ${ paymentType?.id === type.id ? "border-2 border-[#005bff] bg-blue-50" + : showValidation && !paymentType + ? "border-2 border-red-500" : "border-2 border-gray-200" }`} onClick={() => onPaymentTypeChange(type)} @@ -163,16 +225,13 @@ export default function OrderSummary({ ))}
+ {showValidation && !paymentType && ( +

Ödeme türü seçiniz

+ )}
- {/* Delivery Type */} - {/* */} - {/* Region Selection */} -
+
@@ -189,7 +248,11 @@ export default function OrderSummary({
{/* Province Selection */} {selectedRegion && provincesForSelectedRegion.length > 0 && ( -
+
@@ -212,7 +278,11 @@ export default function OrderSummary({ value={selectedProvince?.toString() || ""} onValueChange={(value) => onProvinceChange(parseInt(value))} > - + @@ -223,11 +293,14 @@ export default function OrderSummary({ ))} + {showValidation && !selectedProvince && ( +

Adres seçiniz

+ )}
)} {/* Note */} -
+