fixed order, image carousel
This commit is contained in:
@@ -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<AuthStep>("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 (
|
||||
<Dialog open={isOpen} onOpenChange={resetDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
@@ -137,14 +221,15 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) {
|
||||
</div>
|
||||
</div>
|
||||
<DialogTitle className="text-2xl text-center">
|
||||
{t("common.enterPhone")}
|
||||
{getTitle()}
|
||||
</DialogTitle>
|
||||
<p className="text-center text-sm text-gray-600">
|
||||
{t("common.weWillSendCode")}
|
||||
{getDescription()}
|
||||
</p>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 mt-4">
|
||||
{/* Phone Input - Always shown but disabled after first step */}
|
||||
<div>
|
||||
<Input
|
||||
type="tel"
|
||||
@@ -152,13 +237,40 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) {
|
||||
value={phone}
|
||||
onChange={handlePhoneChange}
|
||||
className="h-12 rounded-xl"
|
||||
onKeyDown={(e) => handleKeyPress(e, handleSendOtp)}
|
||||
disabled={otpSent || isLoginLoading}
|
||||
onKeyDown={(e) => handleKeyPress(e, handleCheckPhone)}
|
||||
disabled={authStep !== "phone" || isLoginLoading}
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">{t("phone_format")}</p>
|
||||
</div>
|
||||
|
||||
{otpSent && (
|
||||
{/* Registration Form */}
|
||||
{authStep === "register" && (
|
||||
<>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t("name_placeholder") || "Adyňyz"}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="h-12 rounded-xl"
|
||||
disabled={isRegisterLoading}
|
||||
autoFocus
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={
|
||||
t("address_placeholder") || "Salgyňyz (mysal: Tejen)"
|
||||
}
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
className="h-12 rounded-xl"
|
||||
onKeyDown={(e) => handleKeyPress(e, handleRegister)}
|
||||
disabled={isRegisterLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Verification Code Input */}
|
||||
{authStep === "verify" && (
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t("common.code")}
|
||||
@@ -167,29 +279,64 @@ export default function AuthDialog({ isOpen, onClose }: AuthDialogProps) {
|
||||
setOtp(e.target.value.replace(/\D/g, "").substring(0, 6))
|
||||
}
|
||||
className="h-12 rounded-xl"
|
||||
onKeyDown={(e) => handleKeyPress(e, handleLogin)}
|
||||
onKeyDown={(e) => handleKeyPress(e, handleVerify)}
|
||||
disabled={isVerifyLoading}
|
||||
autoFocus
|
||||
maxLength={6}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Action Button */}
|
||||
<Button
|
||||
onClick={otpSent ? handleLogin : handleSendOtp}
|
||||
onClick={
|
||||
authStep === "phone"
|
||||
? handleCheckPhone
|
||||
: authStep === "register"
|
||||
? handleRegister
|
||||
: handleVerify
|
||||
}
|
||||
className="w-full cursor-pointer h-12 rounded-xl font-bold text-base bg-[#005bff] hover:bg-[#0041c4]"
|
||||
size="lg"
|
||||
disabled={
|
||||
isLoginLoading || isVerifyLoading || (!otpSent && !isPhoneValid())
|
||||
isLoginLoading ||
|
||||
isRegisterLoading ||
|
||||
isVerifyLoading ||
|
||||
(authStep === "phone" && !isPhoneValid())
|
||||
}
|
||||
>
|
||||
{isLoginLoading
|
||||
? t("sending")
|
||||
: isVerifyLoading
|
||||
? t("verifying")
|
||||
: otpSent
|
||||
? t("verify")
|
||||
: t("common.send")}
|
||||
? t("checking") || "Barlanýar..."
|
||||
: isRegisterLoading
|
||||
? t("registering") || "Hasaba alynýar..."
|
||||
: isVerifyLoading
|
||||
? t("verifying")
|
||||
: authStep === "phone"
|
||||
? t("common.send")
|
||||
: authStep === "register"
|
||||
? t("register_button") || "Hasaba al"
|
||||
: t("verify")}
|
||||
</Button>
|
||||
|
||||
{/* Back Button for Register and Verify steps */}
|
||||
{authStep !== "phone" && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (authStep === "register") {
|
||||
setAuthStep("phone");
|
||||
setName("");
|
||||
setAddress("");
|
||||
} else if (authStep === "verify") {
|
||||
setAuthStep(isNewUser ? "register" : "phone");
|
||||
setOtp("");
|
||||
}
|
||||
}}
|
||||
variant="ghost"
|
||||
className="w-full"
|
||||
disabled={isLoginLoading || isRegisterLoading || isVerifyLoading}
|
||||
>
|
||||
{t("back") || "Yza"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user