fixed order, image carousel

This commit is contained in:
@jcarymuhammedow
2026-02-05 19:01:57 +05:00
parent b546deeac0
commit bf5980e3b3
12 changed files with 392 additions and 155 deletions

View File

@@ -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>