diff --git a/app/[locale]/cart/page.tsx b/app/[locale]/cart/page.tsx index 4a8d9c4..34c2ab3 100644 --- a/app/[locale]/cart/page.tsx +++ b/app/[locale]/cart/page.tsx @@ -25,7 +25,7 @@ export default function CartPage() { useState("SELECTED_DELIVERY"); const [selectedRegion, setSelectedRegion] = useState(""); const [selectedProvince, setSelectedProvince] = useState(null); - const [note, setNote] = useState(""); + const [notes, setNote] = useState(""); const [phone, setPhone] = useState("+993 "); const [name, setName] = useState(""); const [lastName, setLastName] = useState(""); @@ -117,7 +117,7 @@ export default function CartPage() { shipping_method: "standart", payment_type_id: paymentType.id, region: selectedRegion, - note: note || undefined, + notes: notes || undefined, }, { onSuccess: () => { @@ -171,7 +171,7 @@ export default function CartPage() { {Object.entries(itemsBySeller).map( ([sellerId, { seller, items }]) => (
- {/*

{seller.name}

*/} +

{seller.name}

{items.map((item) => { const price = parseFloat( @@ -237,7 +237,7 @@ export default function CartPage() { deliveryType={deliveryType} selectedRegion={selectedRegion} selectedProvince={selectedProvince} - note={note} + notes={notes} regionGroups={regionGroups} availableRegions={availableRegions} paymentTypes={paymentTypes} diff --git a/components/layout/ui/AuthDialog.tsx b/components/layout/ui/AuthDialog.tsx index 871aad6..54dd46e 100644 --- a/components/layout/ui/AuthDialog.tsx +++ b/components/layout/ui/AuthDialog.tsx @@ -12,7 +12,7 @@ import { } from "@/components/ui/dialog"; import { toast } from "sonner"; import Logo from "@/public/logo.webp"; -import { useLogin, useVerifyToken } from "@/lib/hooks/useAuth"; +import { useLogin, useRegister, useVerifyToken } from "@/lib/hooks/useAuth"; import { useTranslations } from "next-intl"; interface AuthDialogProps { @@ -20,19 +20,28 @@ interface AuthDialogProps { onClose: () => void; } +type AuthStep = "phone" | "register" | "verify"; + export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { const [phone, setPhone] = useState("+993 "); + const [name, setName] = useState(""); + const [address, setAddress] = useState(""); const [otp, setOtp] = useState(""); - const [otpSent, setOtpSent] = useState(false); + const [authStep, setAuthStep] = useState("phone"); + const [isNewUser, setIsNewUser] = useState(false); const t = useTranslations(); const { mutate: login, isPending: isLoginLoading } = useLogin(); + const { mutate: register, isPending: isRegisterLoading } = useRegister(); const { mutate: verifyToken, isPending: isVerifyLoading } = useVerifyToken(); const resetDialog = useCallback(() => { - setOtpSent(false); + setAuthStep("phone"); setPhone("+993 "); + setName(""); + setAddress(""); setOtp(""); + setIsNewUser(false); onClose(); }, [onClose]); @@ -50,7 +59,6 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { } const digitsOnly = input.substring(prefix.length).replace(/\D/g, ""); - const limitedDigits = digitsOnly.substring(0, 8); let formattedPhone = prefix; @@ -70,7 +78,7 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { return phoneDigits.length === 8; }; - const handleSendOtp = useCallback(() => { + const handleCheckPhone = useCallback(() => { if (!isPhoneValid()) { toast.error(t("invalid_phone")); return; @@ -78,21 +86,71 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { const phoneNumber = formatPhoneForBackend(phone); + // Try to login first to check if user exists login( { phone_number: parseInt(phoneNumber, 10) }, { onSuccess: () => { toast.success(t("code_sent")); - setOtpSent(true); + setIsNewUser(false); + setAuthStep("verify"); + }, + onError: (error: any) => { + // Check if error indicates user not found + const errorMessage = error?.response?.data?.message || ""; + const lowerMessage = errorMessage.toLowerCase(); + if ( + lowerMessage.includes("tapylmady") || + lowerMessage.includes("not found") || + lowerMessage.includes("does not exist") || + lowerMessage.includes("not exist") || + error?.response?.status === 404 + ) { + // User doesn't exist, show registration form + setIsNewUser(true); + setAuthStep("register"); + } else { + toast.error(errorMessage || t("error_occurred")); + } + }, + }, + ); + }, [phone, login, t]); + + const handleRegister = useCallback(() => { + if (!name.trim()) { + toast.error(t("name_required") || "Adyňyzy giriziň"); + return; + } + + if (!address.trim()) { + toast.error(t("address_required") || "Salgyňyzy giriziň"); + return; + } + + const phoneNumber = formatPhoneForBackend(phone); + + register( + { + phone_number: phoneNumber, + name: name.trim(), + address: address.trim(), + }, + { + onSuccess: () => { + toast.success( + t("registration_success") || "Hasaba alyndy! Kody giriziň", + ); + setAuthStep("verify"); }, onError: (error: any) => { toast.error(error?.response?.data?.message || t("error_occurred")); }, - } + }, ); - }, [phone, login, t]); + }, [phone, name, address, register, t]); - const handleLogin = useCallback(() => { + const handleVerify = useCallback(() => { if (otp.length < 4) { toast.error(t("invalid_code")); return; @@ -114,7 +172,7 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { onError: (error: any) => { toast.error(error?.response?.data?.message || t("wrong_code")); }, - } + }, ); }, [otp, phone, verifyToken, resetDialog, t]); @@ -124,9 +182,35 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) { action(); } }, - [] + [], ); + const getTitle = () => { + switch (authStep) { + case "phone": + return t("common.enterPhone"); + case "register": + return t("register_title") || "Hasaba alyş"; + case "verify": + return t("verify_title") || "Kody giriziň"; + default: + return t("common.enterPhone"); + } + }; + + const getDescription = () => { + switch (authStep) { + case "phone": + return t("common.weWillSendCode"); + case "register": + return t("register_description") || "Maglumatyňyzy dolduryň"; + case "verify": + return t("verify_description") || "Telefonyňyza gelen kody giriziň"; + default: + return t("common.weWillSendCode"); + } + }; + return ( @@ -137,14 +221,15 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) {
- {t("common.enterPhone")} + {getTitle()}

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

+ {/* Phone Input - Always shown but disabled after first step */}
handleKeyPress(e, handleSendOtp)} - disabled={otpSent || isLoginLoading} + onKeyDown={(e) => handleKeyPress(e, handleCheckPhone)} + disabled={authStep !== "phone" || isLoginLoading} />

{t("phone_format")}

- {otpSent && ( + {/* Registration Form */} + {authStep === "register" && ( + <> + setName(e.target.value)} + className="h-12 rounded-xl" + disabled={isRegisterLoading} + autoFocus + /> + setAddress(e.target.value)} + className="h-12 rounded-xl" + onKeyDown={(e) => handleKeyPress(e, handleRegister)} + disabled={isRegisterLoading} + /> + + )} + + {/* Verification Code Input */} + {authStep === "verify" && ( handleKeyPress(e, handleLogin)} + onKeyDown={(e) => handleKeyPress(e, handleVerify)} disabled={isVerifyLoading} autoFocus maxLength={6} /> )} + {/* Action Button */} + + {/* Back Button for Register and Verify steps */} + {authStep !== "phone" && ( + + )}
diff --git a/context/AuthWrapper.tsx b/context/AuthWrapper.tsx index e92f757..87642cb 100644 --- a/context/AuthWrapper.tsx +++ b/context/AuthWrapper.tsx @@ -32,10 +32,12 @@ export default function AuthWrapper({ useEffect(() => { if (isLoading) return; + // Only fetch guest token once on initial mount if no token exists if (!TokenStorage.hasAnyToken() && !isGettingGuestToken) { getGuestToken(); } - }, [isLoading, getGuestToken, isGettingGuestToken]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading]); // Only run when isLoading changes (initial mount) useEffect(() => { if (isLoading || isGettingGuestToken) return; diff --git a/features/cart/components/OrderSummary.tsx b/features/cart/components/OrderSummary.tsx index 2d2a627..cf7485b 100644 --- a/features/cart/components/OrderSummary.tsx +++ b/features/cart/components/OrderSummary.tsx @@ -40,7 +40,7 @@ interface OrderSummaryProps { deliveryType: DeliveryType; selectedRegion: string; selectedProvince: number | null; - note: string; + notes: string; regionGroups: Record; availableRegions: string[]; paymentTypes: PaymentType[]; @@ -54,7 +54,7 @@ interface OrderSummaryProps { onDeliveryTypeChange: (type: DeliveryType) => void; onRegionChange: (regionCode: string) => void; onProvinceChange: (provinceId: number) => void; - onNoteChange: (note: string) => void; + onNoteChange: (notes: string) => void; onCompleteOrder: () => void; isLoading: boolean; } @@ -65,7 +65,7 @@ export default function OrderSummary({ deliveryType, selectedRegion, selectedProvince, - note, + notes, regionGroups, availableRegions, paymentTypes, @@ -303,7 +303,7 @@ export default function OrderSummary({