Files
web/components/Auth/LoginModal.jsx
Husanjonazamov 64af77101f classify web
2026-02-24 12:52:49 +05:00

383 lines
13 KiB
JavaScript

"use client";
import { formatPhoneNumber, handleFirebaseAuthError, t } from "@/utils";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import {
Fcmtoken,
getIsDemoMode,
getOtpServiceProvider,
settingsData,
} from "@/redux/reducer/settingSlice";
import "react-phone-input-2/lib/style.css";
import { Button } from "../ui/button";
import { FcGoogle } from "react-icons/fc";
import { MdOutlineEmail, MdOutlineLocalPhone } from "react-icons/md";
import {
getAuth,
GoogleAuthProvider,
RecaptchaVerifier,
signInWithPhoneNumber,
signInWithPopup,
} from "firebase/auth";
import { toast } from "sonner";
import { getOtpApi, userSignUpApi } from "@/utils/api";
import { loadUpdateData } from "@/redux/reducer/authSlice";
import LoginWithEmailForm from "./LoginWithEmailForm";
import LoginWithMobileForm from "./LoginWithMobileForm";
import OtpScreen from "./OtpScreen";
import TermsAndPrivacyLinks from "./TermsAndPrivacyLinks";
import { setIsLoginOpen } from "@/redux/reducer/globalStateSlice";
import ResetPasswordScreen from "./ResetPasswordScreen";
const LoginModal = ({ IsLoginOpen, setIsRegisterModalOpen }) => {
const settings = useSelector(settingsData);
const auth = getAuth();
const fetchFCM = useSelector(Fcmtoken);
const isDemoMode = useSelector(getIsDemoMode);
const [IsOTPScreen, setIsOTPScreen] = useState(null);
const [resendTimer, setResendTimer] = useState(0);
const [loginStates, setLoginStates] = useState({
number: isDemoMode ? "919876598765" : "",
countryCode: isDemoMode ? "+91" : '',
showLoader: false,
regionCode: "",
password: isDemoMode ? "123456" : "",
});
const [confirmationResult, setConfirmationResult] = useState(null);
const [FirebaseId, setFirebaseId] = useState("");
const { number, countryCode } = loginStates;
const formattedNumber = formatPhoneNumber(number, countryCode);
const otp_service_provider = useSelector(getOtpServiceProvider);
// Active authentication methods
const mobile_authentication = Number(settings?.mobile_authentication);
const google_authentication = Number(settings?.google_authentication);
const email_authentication = Number(settings?.email_authentication);
const [IsLoginWithEmail, setIsLoginWithEmail] = useState(
mobile_authentication === 0 && email_authentication === 1 ? true : false
);
const IsShowOrSignIn =
!(
mobile_authentication === 0 &&
email_authentication === 0 &&
google_authentication === 1
) && google_authentication === 1;
const OnHide = async () => {
setIsOTPScreen(null);
setIsLoginOpen(false);
setConfirmationResult(null);
setResendTimer(0);
};
const generateRecaptcha = async () => {
// Reuse existing verifier if it's still valid
if (window.recaptchaVerifier && !window.recaptchaVerifier.destroyed) {
return window.recaptchaVerifier;
}
const recaptchaContainer = document.getElementById("recaptcha-container");
if (!recaptchaContainer) {
console.error("Container element 'recaptcha-container' not found.");
return null;
}
// Clear container and reset reference
recaptchaContainer.innerHTML = "";
window.recaptchaVerifier = undefined;
try {
window.recaptchaVerifier = new RecaptchaVerifier(
auth,
recaptchaContainer,
{ size: "invisible" }
);
return window.recaptchaVerifier;
} catch (error) {
console.error("Error initializing RecaptchaVerifier:", error.message);
return null;
}
};
useEffect(() => {
return () => {
recaptchaClear();
};
}, []);
const recaptchaClear = async () => {
if (window.recaptchaVerifier && !window.recaptchaVerifier.destroyed) {
try {
await window.recaptchaVerifier.clear();
} catch (error) {
// Ignore errors - verifier might already be cleared
}
}
window.recaptchaVerifier = undefined;
const recaptchaContainer = document.getElementById("recaptcha-container");
if (recaptchaContainer) {
recaptchaContainer.innerHTML = "";
}
};
const handleGoogleSignup = async () => {
const provider = new GoogleAuthProvider();
try {
const res = await signInWithPopup(auth, provider);
const user = res.user;
try {
const response = await userSignUpApi.userSignup({
name: user.displayName ? user.displayName : "",
email: user?.email,
firebase_id: user?.uid, // Accessing UID directly from the user object
fcm_id: fetchFCM ? fetchFCM : "",
type: "google",
});
const data = response.data;
if (data.error === true) {
toast.error(data.message);
} else {
loadUpdateData(data);
toast.success(data.message);
}
OnHide();
} catch (error) {
console.error("Error:", error);
toast.error("Failed to complete signup");
}
} catch (error) {
const errorCode = error.code;
handleFirebaseAuthError(errorCode);
}
};
const handleCreateAnAccount = () => {
OnHide();
setIsRegisterModalOpen(true);
};
// Handle forgot password - send OTP and show OTP screen
const handleForgotPassword = async () => {
const PhoneNumber = `${loginStates.countryCode}${formattedNumber}`;
if (otp_service_provider === "twilio") {
try {
const response = await getOtpApi.getOtp({ number: formattedNumber, country_code: countryCode });
if (response?.data?.error === false) {
toast.success(t("otpSentSuccess"));
setResendTimer(60);
setIsOTPScreen("otp");
} else {
toast.error(t("failedToSendOtp"));
}
} catch (error) {
console.log(error);
}
} else {
try {
const appVerifier = await generateRecaptcha();
const confirmation = await signInWithPhoneNumber(
auth,
PhoneNumber,
appVerifier
);
setConfirmationResult(confirmation);
toast.success(t("otpSentSuccess"));
setResendTimer(60);
setIsOTPScreen("otp");
} catch (error) {
console.log(error)
handleFirebaseAuthError(error.code);
}
}
};
// Handle OTP verification success - move to reset password screen
const handleForgotPasswordOtpVerified = (firebase_id) => {
setFirebaseId(firebase_id);
setIsOTPScreen("reset");
toast.success(t("otpVerified"));
};
// Handle successful password reset - go back to login
const handleResetPasswordSuccess = () => {
setIsOTPScreen(null);
setConfirmationResult(null);
setResendTimer(0);
};
return (
<>
<Dialog open={IsLoginOpen} onOpenChange={setIsLoginOpen}>
<DialogContent
onInteractOutside={(e) => e.preventDefault()}
className="px-[40px] sm:py-[50px] sm:px-[90px]"
>
<DialogHeader>
<DialogTitle className="text-3xl sm:text-4xl font-light">
{IsOTPScreen === "otp" ? (
t("verifyOtp")
) : IsOTPScreen === "reset" ? (
t("resetYourPassword")
) : (
<>
{t("loginTo")}{" "}
<span className="text-primary">{settings?.company_name}</span>
</>
)}
</DialogTitle>
<DialogDescription className="text-base text-black font-light">
{IsOTPScreen === "otp" ? (
<>
{t("sentTo")} {`${countryCode}${formattedNumber}`}{" "}
<span
onClick={() => setIsOTPScreen(false)}
className="text-primary underline cursor-pointer"
>
{t("change")}
</span>
</>
) : IsOTPScreen === "reset" ? (
t("enterNewPassword")
) : (
<>
{t("newto")} {settings?.company_name}?{" "}
<span
className="text-primary cursor-pointer underline"
onClick={handleCreateAnAccount}
>
{t("createAccount")}
</span>
</>
)}
</DialogDescription>
</DialogHeader>
{IsOTPScreen === "otp" ? (
<OtpScreen
OnHide={OnHide}
generateRecaptcha={generateRecaptcha}
countryCode={countryCode}
formattedNumber={formattedNumber}
confirmationResult={confirmationResult}
setConfirmationResult={setConfirmationResult}
resendTimer={resendTimer}
setResendTimer={setResendTimer}
regionCode={loginStates.regionCode}
isDemoMode={isDemoMode}
onOtpVerified={handleForgotPasswordOtpVerified}
key="forgot-password-otp"
/>
) : IsOTPScreen === "reset" ? (
<ResetPasswordScreen
FirebaseId={FirebaseId}
formattedNumber={formattedNumber}
countryCode={loginStates.countryCode}
onSuccess={handleResetPasswordSuccess}
onCancel={() => setIsOTPScreen(null)}
/>
) : (
<div className="flex flex-col gap-[30px] mt-3.5">
{!(
mobile_authentication === 0 &&
email_authentication === 0 &&
google_authentication === 1
) &&
mobile_authentication === 1 &&
email_authentication === 1 &&
(IsLoginWithEmail ? (
<LoginWithEmailForm OnHide={OnHide} key={IsLoginWithEmail} />
) : (
<LoginWithMobileForm
formattedNumber={formattedNumber}
loginStates={loginStates}
setLoginStates={setLoginStates}
onForgotPassword={handleForgotPassword}
OnHide={OnHide}
key={IsLoginWithEmail}
/>
))}
{email_authentication === 1 && mobile_authentication === 0 && (
<LoginWithEmailForm OnHide={OnHide} key={IsLoginWithEmail} />
)}
{mobile_authentication === 1 && email_authentication === 0 && (
<LoginWithMobileForm
OnHide={OnHide}
formattedNumber={formattedNumber}
loginStates={loginStates}
setLoginStates={setLoginStates}
onForgotPassword={handleForgotPassword}
key={IsLoginWithEmail}
/>
)}
{IsShowOrSignIn && (
<div className="flex items-center gap-2">
<hr className="w-full" />
<p className="text-nowrap text-sm">{t("orSignInWith")}</p>
<hr className="w-full" />
</div>
)}
<div className="flex flex-col gap-4">
{google_authentication === 1 && (
<Button
variant="outline"
size="big"
className="flex items-center justify-center py-4 text-base"
onClick={handleGoogleSignup}
>
<FcGoogle className="!size-6" />
<span>{t("continueWithGoogle")}</span>
</Button>
)}
{IsLoginWithEmail && mobile_authentication === 1 ? (
<Button
variant="outline"
size="big"
className="flex items-center justify-center py-4 text-base h-auto"
onClick={() => setIsLoginWithEmail(false)}
>
<MdOutlineLocalPhone className="!size-6" />
{t("continueWithMobile")}
</Button>
) : (
!IsLoginWithEmail &&
email_authentication === 1 && (
<Button
variant="outline"
size="big"
className="flex items-center justify-center py-4 text-base h-auto"
onClick={() => setIsLoginWithEmail(true)}
>
<MdOutlineEmail className="!size-6" />
{t("continueWithEmail")}
</Button>
)
)}
</div>
<TermsAndPrivacyLinks t={t} settings={settings} OnHide={OnHide} />
</div>
)}
<div id="recaptcha-container" style={{ display: "none" }}></div>
</DialogContent>
</Dialog>
</>
);
};
export default LoginModal;