classify web

This commit is contained in:
Husanjonazamov
2026-02-24 12:52:49 +05:00
commit 64af77101f
310 changed files with 45449 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
"use client";
import { useEffect, useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import "react-phone-input-2/lib/style.css";
import { handleFirebaseAuthError, t } from "@/utils";
import { useSelector } from "react-redux";
import { logoutSuccess, userSignUpData } from "@/redux/reducer/authSlice";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import useAutoFocus from "../Common/useAutoFocus";
import {
deleteUser,
getAuth,
RecaptchaVerifier,
signInWithPhoneNumber,
} from "firebase/auth";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
import { deleteUserApi } from "@/utils/api";
const DeleteAccountVerifyOtpModal = ({
isOpen,
setIsOpen,
pathname,
navigate,
}) => {
const userData = useSelector(userSignUpData);
const auth = getAuth();
const countryCode = userData?.country_code;
const formattedNumber = userData?.mobile;
const otpInputRef = useAutoFocus();
const [showLoader, setShowLoader] = useState(false);
const [otp, setOtp] = useState("");
const [resendOtpLoader, setResendOtpLoader] = useState(false);
const [resendTimer, setResendTimer] = useState(0);
const [confirmationResult, setConfirmationResult] = useState(null);
useEffect(() => {
if (isOpen) {
const timer = setTimeout(() => {
sendOTP();
}, 100);
return () => clearTimeout(timer);
}
}, [isOpen]);
useEffect(() => {
let intervalId;
if (resendTimer > 0) {
intervalId = setInterval(() => {
setResendTimer((prevTimer) => prevTimer - 1);
}, 1000);
}
return () => {
if (intervalId) clearInterval(intervalId);
};
}, [resendTimer]);
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 sendOTP = async () => {
try {
const PhoneNumber = `${countryCode}${formattedNumber}`;
setShowLoader(true);
const appVerifier = await generateRecaptcha();
const confirmation = await signInWithPhoneNumber(
auth,
PhoneNumber,
appVerifier
);
setConfirmationResult(confirmation);
toast.success(t("otpSentSuccess"));
setResendTimer(60);
} catch (error) {
console.log(error);
handleFirebaseAuthError(error.code);
} finally {
setShowLoader(false);
}
};
const verifyOTPWithFirebase = async (e) => {
e.preventDefault();
try {
setShowLoader(true);
const result = await confirmationResult.confirm(otp);
const user = result.user;
await deleteUser(user);
await deleteUserApi.deleteUser();
logoutSuccess();
toast.success(t("userDeleteSuccess"));
setIsOpen(false);
if (pathname !== "/") {
navigate("/");
}
} catch (error) {
console.log(error);
const errorCode = error?.code;
handleFirebaseAuthError(errorCode);
} finally {
setShowLoader(false);
}
};
const resendOtp = async () => {
try {
setResendOtpLoader(true);
const PhoneNumber = `${countryCode}${formattedNumber}`;
const appVerifier = await generateRecaptcha();
const confirmation = await signInWithPhoneNumber(
auth,
PhoneNumber,
appVerifier
);
setConfirmationResult(confirmation);
toast.success(t("otpSentSuccess"));
} catch (error) {
const errorCode = error.code;
handleFirebaseAuthError(errorCode);
} finally {
setResendOtpLoader(false);
}
};
return (
<>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent
onInteractOutside={(e) => e.preventDefault()}
className="px-[40px] sm:py-[50px] sm:px-[90px]"
>
<DialogHeader>
<DialogTitle className="text-3xl sm:text-4xl font-light">
{t("verifyOtp")}
</DialogTitle>
<DialogDescription className="text-base text-black font-light">
{t("sentTo")} {`${countryCode} ${formattedNumber}`}
</DialogDescription>
</DialogHeader>
<form
className="flex flex-col gap-6"
onSubmit={verifyOTPWithFirebase}
>
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("otp")}</Label>
<Input
type="text"
placeholder={t("enterOtp")}
id="otp"
name="otp"
value={otp}
maxLength={6}
onChange={(e) => setOtp(e.target.value)}
ref={otpInputRef}
/>
</div>
<Button
type="submit"
disabled={showLoader}
className="text-xl text-white font-light px-4 py-2"
size="big"
>
{showLoader ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
t("verify")
)}
</Button>
<Button
type="button"
className="text-lg text-black font-light bg-transparent"
size="big"
onClick={resendOtp}
disabled={resendOtpLoader || showLoader || resendTimer > 0}
>
{resendOtpLoader ? (
<Loader2 className="size-6 animate-spin" />
) : resendTimer > 0 ? (
`${t("resendOtp")} ${resendTimer}s`
) : (
t("resendOtp")
)}
</Button>
</form>
<div id="recaptcha-container" style={{ display: "none" }}></div>
</DialogContent>
</Dialog>
</>
);
};
export default DeleteAccountVerifyOtpModal;

View File

@@ -0,0 +1,382 @@
"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;

View File

@@ -0,0 +1,189 @@
import { FaRegEye, FaRegEyeSlash } from "react-icons/fa";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import useAutoFocus from "../Common/useAutoFocus";
import { toast } from "sonner";
import { handleFirebaseAuthError, t } from "@/utils";
import {
getAuth,
sendPasswordResetEmail,
signInWithEmailAndPassword,
} from "firebase/auth";
import { userSignUpApi } from "@/utils/api";
import { useSelector } from "react-redux";
import { Fcmtoken } from "@/redux/reducer/settingSlice";
import { loadUpdateData } from "@/redux/reducer/authSlice";
import { Loader2 } from "lucide-react";
import { useState } from "react";
const LoginWithEmailForm = ({ OnHide }) => {
const emailRef = useAutoFocus();
const auth = getAuth();
const fetchFCM = useSelector(Fcmtoken);
const [loginStates, setLoginStates] = useState({
email: "",
password: "",
IsPasswordVisible: false,
showLoader: false,
});
const { email, password, IsPasswordVisible, showLoader } = loginStates;
const signin = async (email, password) => {
try {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
if (!userCredential?.user) {
toast.error(t("userNotFound"));
return null;
}
return userCredential;
} catch (error) {
console.error("Error signing in:", error);
throw error;
}
};
const Signin = async (e) => {
e.preventDefault();
if (!email) {
toast.error(t("emailRequired"));
return;
} else if (!/\S+@\S+\.\S+/.test(email)) {
toast.error(t("emailInvalid"));
return;
} else if (!password) {
toast.error(t("passwordRequired"));
return;
} else if (password.length < 6) {
toast.error(t("passwordTooShort"));
return;
}
try {
setLoginStates((prev) => ({ ...prev, showLoader: true }));
const userCredential = await signin(email, password);
const user = userCredential.user;
if (user.emailVerified) {
try {
const response = await userSignUpApi.userSignup({
name: user?.displayName || "",
email: user?.email,
firebase_id: user?.uid,
fcm_id: fetchFCM ? fetchFCM : "",
type: "email",
is_login: 1,
});
const data = response.data;
if (data.error === false) {
loadUpdateData(data);
toast.success(data.message);
OnHide();
} else {
toast.error(data.message);
}
} catch (error) {
console.error("Error:", error);
}
// Add your logic here for verified users
} else {
toast.error(t("verifyEmailFirst"));
}
} catch (error) {
const errorCode = error.code;
console.log("Error code:", errorCode);
handleFirebaseAuthError(errorCode);
} finally {
setLoginStates((prev) => ({ ...prev, showLoader: false }));
}
};
const handleForgotModal = async (e) => {
e.preventDefault();
await sendPasswordResetEmail(auth, email)
.then(() => {
toast.success(t("resetPassword"));
})
.catch((error) => {
console.log("error", error);
handleFirebaseAuthError(error?.code);
});
};
return (
<>
<form className="flex flex-col gap-6" onSubmit={Signin}>
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("email")}</Label>
<Input
type="email"
placeholder={t("enterEmail")}
value={email}
onChange={(e) =>
setLoginStates((prev) => ({ ...prev, email: e.target.value }))
}
ref={emailRef}
/>
</div>
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("password")}</Label>
<div className="flex items-center relative">
<Input
type={IsPasswordVisible ? "text" : "password"}
placeholder={t("enterPassword")}
className="ltr:pr-9 rtl:pl-9"
value={password}
onChange={(e) =>
setLoginStates((prev) => ({
...prev,
password: e.target.value,
}))
}
/>
<button
type="button"
className="absolute ltr:right-3 rtl:left-3 cursor-pointer"
onClick={() =>
setLoginStates((prev) => ({
...prev,
IsPasswordVisible: !prev.IsPasswordVisible,
}))
}
>
{IsPasswordVisible ? (
<FaRegEye size={20} />
) : (
<FaRegEyeSlash size={20} />
)}
</button>
</div>
<button
className="text-right font-semibold text-primary"
onClick={handleForgotModal}
type="button"
>
{t("forgtPassword")}
</button>
</div>
<Button
className="text-xl text-white font-light px-4 py-2"
size="big"
disabled={showLoader}
>
{showLoader ? (
<Loader2 className="size-6 animate-spin" />
) : (
t("signIn")
)}
</Button>
</form>
</>
);
};
export default LoginWithEmailForm;

View File

@@ -0,0 +1,194 @@
import PhoneInput from "react-phone-input-2";
import { Label } from "../ui/label";
import { Button } from "../ui/button";
import useAutoFocus from "../Common/useAutoFocus";
import { Loader2 } from "lucide-react";
import { isValidPhoneNumber } from "libphonenumber-js/max";
import { toast } from "sonner";
import { t } from "@/utils";
import { Input } from "../ui/input";
import { useState } from "react";
import { FaRegEye, FaRegEyeSlash } from "react-icons/fa";
import { getUserExistsApi, userSignUpApi } from "@/utils/api";
import { Fcmtoken } from "@/redux/reducer/settingSlice";
import { useSelector } from "react-redux";
import { loadUpdateData } from "@/redux/reducer/authSlice";
const LoginWithMobileForm = ({
loginStates,
setLoginStates,
formattedNumber,
onForgotPassword,
OnHide,
}) => {
const numberInputRef = useAutoFocus();
const { number, countryCode, showLoader } = loginStates;
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const fcm_id = useSelector(Fcmtoken);
const [forgotPasswordLoading, setForgotPasswordLoading] = useState(false);
const handleInputChange = (value, data) => {
setLoginStates((prev) => ({
...prev,
number: value,
countryCode: "+" + (data?.dialCode || ""),
regionCode: data?.countryCode.toLowerCase() || "",
}));
};
const handleCountryChange = (code) => {
setLoginStates((prev) => ({
...prev,
countryCode: code,
}));
};
const handleLoginWithMobile = async (e) => {
e.preventDefault();
try {
if (!isValidPhoneNumber(`${countryCode}${formattedNumber}`)) {
toast.error(t("invalidPhoneNumber"));
return;
}
setLoginStates((prev) => ({
...prev,
showLoader: true,
}));
const params = {
mobile: formattedNumber,
password: loginStates.password,
country_code: countryCode,
type: "phone",
fcm_id: fcm_id ? fcm_id : "",
is_login: 1,
};
const response = await userSignUpApi.userSignup(params);
if (response?.data?.error === false) {
toast.success(response?.data?.message);
loadUpdateData(response?.data);
OnHide();
} else {
toast.error(response?.data?.message);
}
} catch (error) {
console.log(error);
} finally {
setLoginStates((prev) => ({
...prev,
showLoader: false,
}));
}
};
const checkIfUserExistsOrNot = async () => {
try {
const res = await getUserExistsApi.getUserExists({
mobile: formattedNumber,
country_code: countryCode,
forgot_password: 1
})
if (res?.data?.error === false) {
return true
} else {
toast.error(res?.data?.message)
return false
}
} catch (error) {
console.log(error)
return false;
}
}
// NEW: Handle forgot password with loading state
const handleForgotPasswordClick = async () => {
if (!isValidPhoneNumber(`${countryCode}${formattedNumber}`)) {
toast.error(t("invalidPhoneNumber"));
return;
}
setForgotPasswordLoading(true);
const isUserExists = await checkIfUserExistsOrNot()
if (!isUserExists) {
setForgotPasswordLoading(false);
return;
}
await onForgotPassword();
setForgotPasswordLoading(false);
};
return (
<form className="flex flex-col gap-6" onSubmit={handleLoginWithMobile}>
<div className="labelInputCont">
<Label className="font-semibold after:content-['*'] after:text-red-500">
{t("mobileNumber")}
</Label>
<PhoneInput
country={process.env.NEXT_PUBLIC_DEFAULT_COUNTRY}
value={number}
onChange={(phone, data) => handleInputChange(phone, data)}
onCountryChange={handleCountryChange}
inputProps={{
name: "phone",
required: true,
ref: numberInputRef,
}}
enableLongNumbers
/>
</div>
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("password")}</Label>
<div className="flex items-center relative">
<Input
type={isPasswordVisible ? "text" : "password"}
placeholder={t("enterPassword")}
className="ltr:pr-9 rtl:pl-9"
value={loginStates.password}
onChange={(e) =>
setLoginStates((prev) => ({ ...prev, password: e.target.value }))
}
required
/>
<button
type="button"
className="absolute ltr:right-3 rtl:left-3 cursor-pointer"
onClick={() => setIsPasswordVisible((prev) => !prev)}
>
{isPasswordVisible ? (
<FaRegEye size={20} />
) : (
<FaRegEyeSlash size={20} />
)}
</button>
</div>
<button
className="text-right font-semibold text-primary w-fit self-end"
onClick={handleForgotPasswordClick}
type="button"
disabled={forgotPasswordLoading}
>
{forgotPasswordLoading ? (
<>
<span className="flex items-center gap-2 justify-end">
<Loader2 className="size-4 animate-spin" />
<span>{t("loading")}</span>
</span>
</>
) : (
t("forgtPassword")
)}
</button>
</div>
<Button
type="submit"
disabled={showLoader}
className="text-xl text-white font-light px-4 py-2"
size="big"
>
{showLoader ? <Loader2 className="size-6 animate-spin" /> : t("login")}
</Button>
</form>
);
};
export default LoginWithMobileForm;

View File

@@ -0,0 +1,36 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import mainSentImg from "../../public/assets/Mail Verification.svg";
import { t } from "@/utils";
import CustomImage from "../Common/CustomImage";
const MailSentSuccessModal = ({ IsMailSentSuccess, setIsMailSentSuccess }) => {
return (
<Dialog open={IsMailSentSuccess} onOpenChange={setIsMailSentSuccess}>
<DialogContent onInteractOutside={(e) => e.preventDefault()}>
<DialogHeader>
<DialogTitle className="sr-only"></DialogTitle>
<DialogDescription className="sr-only"></DialogDescription>
<div className="flex flex-col gap-3 items-center justify-center">
<CustomImage
src={mainSentImg}
alt="Verification Mail sent"
width={300}
height={195}
className="aspect-[300/195] object-contain"
/>
<h1 className="text-2xl font-medium">{t("youveGotMail")}</h1>
<p className="opacity-65">{t("verifyAccount")}</p>
</div>
</DialogHeader>
</DialogContent>
</Dialog>
);
};
export default MailSentSuccessModal;

View File

@@ -0,0 +1,244 @@
import { getAuth, signInWithPhoneNumber } from "firebase/auth";
import useAutoFocus from "../Common/useAutoFocus";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { toast } from "sonner";
import { handleFirebaseAuthError, t } from "@/utils";
import { getOtpApi, userSignUpApi, verifyOtpApi } from "@/utils/api";
import { loadUpdateData } from "@/redux/reducer/authSlice";
import { useSelector } from "react-redux";
import { Fcmtoken, getOtpServiceProvider } from "@/redux/reducer/settingSlice";
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useNavigate } from "../Common/useNavigate";
const OtpScreen = ({
generateRecaptcha,
countryCode,
formattedNumber,
confirmationResult,
setConfirmationResult,
OnHide,
resendTimer,
setResendTimer,
regionCode,
isDemoMode,
isRegister = false,
onOtpVerified,
password
}) => {
const { navigate } = useNavigate();
const otpInputRef = useAutoFocus();
const fetchFCM = useSelector(Fcmtoken);
const auth = getAuth();
const [resendOtpLoader, setResendOtpLoader] = useState(false);
const [showLoader, setShowLoader] = useState(false);
const [otp, setOtp] = useState(isDemoMode && !isRegister ? "123456" : "");
const otp_service_provider = useSelector(getOtpServiceProvider);
useEffect(() => {
let intervalId;
if (resendTimer > 0) {
intervalId = setInterval(() => {
setResendTimer((prevTimer) => prevTimer - 1);
}, 1000);
}
return () => {
if (intervalId) clearInterval(intervalId);
};
}, [resendTimer]);
const verifyOTPWithTwillio = async () => {
try {
const payload = {
number: formattedNumber,
country_code: countryCode,
otp: otp,
}
if (isRegister && password) {
payload.password = password;
}
const response = await verifyOtpApi.verifyOtp(payload);
if (response?.data?.error === false) {
// If callback provided, use it (for forgot password)
if (onOtpVerified) {
onOtpVerified();
return;
}
// Otherwise, do normal login
loadUpdateData(response?.data);
toast.success(response?.data?.message);
if (
response?.data?.data?.email === "" ||
response?.data?.data?.name === ""
) {
navigate("/profile");
}
OnHide();
} else {
toast.error(response?.data?.message);
}
} catch (error) {
console.log(error);
} finally {
setShowLoader(false);
}
};
const verifyOTPWithFirebase = async () => {
try {
const result = await confirmationResult.confirm(otp);
// Access user information from the result
const user = result.user;
const firebase_id = user?.uid;
// If callback provided, use it (for forgot password)
if (onOtpVerified) {
onOtpVerified(firebase_id);
return;
}
const payload = {
mobile: formattedNumber,
firebase_id: user.uid, // Accessing UID directly from the user object
fcm_id: fetchFCM ? fetchFCM : "",
country_code: countryCode,
type: "phone",
region_code: regionCode?.toUpperCase() || "",
}
if (isRegister && password) {
payload.password = password;
}
// Otherwise, do normal login
const response = await userSignUpApi.userSignup(payload);
const data = response.data;
loadUpdateData(data);
toast.success(data.message);
OnHide();
if (data?.data?.email === "" || data?.data?.name === "") {
navigate("/profile");
}
} catch (error) {
console.log(error);
const errorCode = error?.code;
handleFirebaseAuthError(errorCode);
} finally {
setShowLoader(false);
}
};
const verifyOTP = async (e) => {
e.preventDefault();
if (otp === "") {
toast.error(t("otpmissing"));
return;
}
setShowLoader(true);
if (otp_service_provider === "twilio") {
await verifyOTPWithTwillio();
} else {
await verifyOTPWithFirebase();
}
};
const resendOtpWithTwillio = async () => {
try {
const response = await getOtpApi.getOtp({ number: formattedNumber, country_code: countryCode });
if (response?.data?.error === false) {
toast.success(t("otpSentSuccess"));
setResendTimer(60); // Start the 60-second timer
} else {
toast.error(t("failedToSendOtp"));
}
} catch (error) {
console.log(error);
} finally {
setResendOtpLoader(false);
}
};
const resendOtpWithFirebase = async (PhoneNumber) => {
try {
const appVerifier = await generateRecaptcha();
const confirmation = await signInWithPhoneNumber(
auth,
PhoneNumber,
appVerifier
);
setConfirmationResult(confirmation);
toast.success(t("otpSentSuccess"));
} catch (error) {
const errorCode = error.code;
handleFirebaseAuthError(errorCode);
} finally {
setResendOtpLoader(false);
}
};
const resendOtp = async (e) => {
e.preventDefault();
setResendOtpLoader(true);
const PhoneNumber = `${countryCode}${formattedNumber}`;
if (otp_service_provider === "twilio") {
await resendOtpWithTwillio();
} else {
await resendOtpWithFirebase(PhoneNumber);
}
};
return (
<form className="flex flex-col gap-6" onSubmit={verifyOTP}>
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("otp")}</Label>
<Input
type="text"
placeholder={t("enterOtp")}
id="otp"
name="otp"
value={otp}
maxLength={6}
onChange={(e) => setOtp(e.target.value)}
ref={otpInputRef}
/>
</div>
<Button
type="submit"
disabled={showLoader}
className="text-xl text-white font-light px-4 py-2"
size="big"
>
{showLoader ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
t("verify")
)}
</Button>
<Button
type="button"
className="text-lg text-black font-light bg-transparent"
size="big"
onClick={resendOtp}
disabled={resendOtpLoader || showLoader || resendTimer > 0}
>
{resendOtpLoader ? (
<Loader2 className="size-6 animate-spin" />
) : resendTimer > 0 ? (
`${t("resendOtp")} ${resendTimer}s`
) : (
t("resendOtp")
)}
</Button>
</form>
);
};
export default OtpScreen;

View File

@@ -0,0 +1,180 @@
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { t } from "@/utils";
import { settingsData } from "@/redux/reducer/settingSlice";
import { useSelector } from "react-redux";
import TermsAndPrivacyLinks from "./TermsAndPrivacyLinks";
import { setIsLoginOpen } from "@/redux/reducer/globalStateSlice";
import { MdOutlineEmail, MdOutlineLocalPhone } from "react-icons/md";
import RegisterWithEmailForm from "./RegisterWithEmailForm";
import RegisterWithMobileForm from "./RegisterWithMobileForm";
import { Button } from "../ui/button";
const RegisterModal = ({
setIsMailSentSuccess,
IsRegisterModalOpen,
setIsRegisterModalOpen,
}) => {
// Get Global data
const settings = useSelector(settingsData);
const [descriptionState, setDescriptionState] = useState({
type: "register", // "register" | "otp"
phoneNumber: "",
});
const [isOTPScreen, setIsOTPScreen] = useState(false);
// Active authentication methods
const mobile_authentication = Number(settings?.mobile_authentication);
const email_authentication = Number(settings?.email_authentication);
// Toggle between email and mobile registration
const [IsRegisterWithEmail, setIsRegisterWithEmail] = useState(
!mobile_authentication == 1
);
const OnHide = () => {
setIsRegisterModalOpen(false);
};
const handleLoginClick = () => {
OnHide();
setIsLoginOpen(true);
};
const handleChangeClick = () => {
setIsOTPScreen(false);
setDescriptionState({ type: "register", phoneNumber: "" });
};
// Show divider when alternative auth methods (email/mobile toggle) are available
const showOrSignInWith =
!isOTPScreen &&
((IsRegisterWithEmail && mobile_authentication == 1) ||
(!IsRegisterWithEmail && email_authentication == 1));
return (
<>
<Dialog open={IsRegisterModalOpen} onOpenChange={setIsRegisterModalOpen}>
<DialogContent
onInteractOutside={(e) => e.preventDefault()}
className="px-[40px] sm:py-[50px] sm:px-[90px]"
>
<DialogHeader>
<DialogTitle className="text-3xl sm:text-4xl font-light">
{descriptionState.type === "otp" ? (
t("verifyOtp")
) : (
<>
{t("welcomeTo")}{" "}
<span className="text-primary">{settings?.company_name}</span>
</>
)}
</DialogTitle>
<DialogDescription className="text-base text-black font-light">
{descriptionState.type === "otp" ? (
<>
{t("sentTo")} {descriptionState.phoneNumber}{" "}
<span
className="text-primary cursor-pointer underline"
onClick={handleChangeClick}
>
{t("change")}
</span>
</>
) : (
<>
{t("haveAccount")}{" "}
<span
className="text-primary cursor-pointer underline"
onClick={handleLoginClick}
>
{t("logIn")}
</span>
</>
)}
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-[30px] mt-3.5">
{/* Show RegisterWithEmailForm when email auth is enabled */}
{email_authentication === 1 &&
(mobile_authentication == 0 || IsRegisterWithEmail) && (
<RegisterWithEmailForm
OnHide={OnHide}
setIsMailSentSuccess={setIsMailSentSuccess}
key={IsRegisterWithEmail}
/>
)}
{/* Show RegisterWithMobileForm when mobile auth is enabled */}
{mobile_authentication == 1 &&
(email_authentication == 0 || !IsRegisterWithEmail) && (
<RegisterWithMobileForm
OnHide={OnHide}
setDescriptionState={setDescriptionState}
key={IsRegisterWithEmail}
isOTPScreen={isOTPScreen}
setIsOTPScreen={setIsOTPScreen}
/>
)}
{/* Show divider when alternative auth methods are available */}
{showOrSignInWith && (
<div className="flex items-center gap-2">
<hr className="w-full" />
<p className="text-nowrap text-sm">{t("orSignUpWith")}</p>
<hr className="w-full" />
</div>
)}
{/* Toggle buttons for switching between email and mobile */}
{showOrSignInWith && (
<div className="flex flex-col gap-4">
{/* Show "Continue with Mobile" button when email is selected and mobile is enabled */}
{IsRegisterWithEmail && mobile_authentication == 1 && (
<Button
variant="outline"
size="big"
className="flex items-center justify-center py-4 text-base h-auto"
onClick={() => setIsRegisterWithEmail(false)}
>
<MdOutlineLocalPhone className="!size-6" />
{t("continueWithMobile")}
</Button>
)}
{/* Show "Continue with Email" button when mobile is selected and email is enabled */}
{!IsRegisterWithEmail && email_authentication === 1 && (
<Button
variant="outline"
size="big"
className="flex items-center justify-center py-4 text-base h-auto"
onClick={() => setIsRegisterWithEmail(true)}
>
<MdOutlineEmail className="!size-6" />
{t("continueWithEmail")}
</Button>
)}
</div>
)}
{/* Terms and Privacy Links */}
{!isOTPScreen && (
<TermsAndPrivacyLinks t={t} settings={settings} OnHide={OnHide} />
)}
</div>
<div id="recaptcha-container" style={{ display: "none" }}></div>
</DialogContent>
</Dialog>
</>
);
};
export default RegisterModal;

View File

@@ -0,0 +1,183 @@
import { useState } from "react";
import { FaRegEye, FaRegEyeSlash } from "react-icons/fa";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
import { handleFirebaseAuthError, t } from "@/utils";
import {
createUserWithEmailAndPassword,
getAuth,
sendEmailVerification,
} from "firebase/auth";
import { userSignUpApi } from "@/utils/api";
import useAutoFocus from "../Common/useAutoFocus";
const RegisterWithEmailForm = ({ OnHide, setIsMailSentSuccess }) => {
const auth = getAuth();
const emailRef = useAutoFocus();
// Form state management
const [formData, setFormData] = useState({
email: "",
username: "",
password: "",
IsPasswordVisible: false,
showLoader: false,
});
const { email, username, password, IsPasswordVisible, showLoader } = formData;
// Handle input changes
const handleInputChange = (field, value) => {
setFormData((prev) => ({ ...prev, [field]: value }));
};
// Toggle password visibility
const togglePasswordVisibility = () => {
setFormData((prev) => ({
...prev,
IsPasswordVisible: !prev.IsPasswordVisible,
}));
};
// Handle form submission
const handleSignup = async (e) => {
e.preventDefault();
// Validate email
if (!email) {
toast.error(t("emailRequired"));
return;
} else if (!/\S+@\S+\.\S+/.test(email)) {
toast.error(t("emailInvalid"));
return;
}
// Validate username
if (username?.trim() === "") {
toast.error(t("usernameRequired"));
return;
}
// Validate password
if (!password) {
toast.error(t("passwordRequired"));
return;
} else if (password.length < 6) {
toast.error(t("passwordTooShort"));
return;
}
try {
setFormData((prev) => ({ ...prev, showLoader: true }));
// Create user with email and password
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
// Send email verification
await sendEmailVerification(user);
// Register user in backend
try {
const response = await userSignUpApi.userSignup({
name: username ? username : "",
email: email ? email : "",
firebase_id: user?.uid,
type: "email",
registration: true,
});
// Close modal and show success message
OnHide();
setIsMailSentSuccess(true);
} catch (error) {
console.log("error", error);
toast.error(t("registrationFailed"));
}
} catch (error) {
const errorCode = error.code;
console.log(error);
handleFirebaseAuthError(errorCode);
} finally {
setFormData((prev) => ({ ...prev, showLoader: false }));
}
};
return (
<form className="flex flex-col gap-6" onSubmit={handleSignup}>
{/* Email Input */}
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("email")}</Label>
<Input
type="email"
placeholder={t("enterEmail")}
value={email}
onChange={(e) => handleInputChange("email", e.target.value)}
ref={emailRef}
required
/>
</div>
{/* Username Input */}
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("username")}</Label>
<Input
type="text"
placeholder={t("typeUsername")}
value={username}
onChange={(e) => handleInputChange("username", e.target.value)}
required
/>
</div>
{/* Password Input */}
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("password")}</Label>
<div className="flex items-center relative">
<Input
type={IsPasswordVisible ? "text" : "password"}
placeholder={t("enterPassword")}
className="ltr:pr-9 rtl:pl-9"
value={password}
onChange={(e) => handleInputChange("password", e.target.value)}
required
/>
<button
type="button"
className="absolute ltr:right-3 rtl:left-3 cursor-pointer"
onClick={togglePasswordVisibility}
>
{IsPasswordVisible ? (
<FaRegEye size={20} />
) : (
<FaRegEyeSlash size={20} />
)}
</button>
</div>
</div>
{/* Submit Button */}
<Button
type="submit"
disabled={showLoader}
className="text-xl text-white font-light px-4 py-2"
size="big"
>
{showLoader ? (
<Loader2 className="size-4 animate-spin" />
) : (
t("verifyEmail")
)}
</Button>
</form>
);
};
export default RegisterWithEmailForm;

View File

@@ -0,0 +1,310 @@
import { useEffect, useState } from "react";
import PhoneInput from "react-phone-input-2";
import "react-phone-input-2/lib/style.css";
import { Label } from "../ui/label";
import { Button } from "../ui/button";
import { Loader2 } from "lucide-react";
import { isValidPhoneNumber } from "libphonenumber-js/max";
import { toast } from "sonner";
import { handleFirebaseAuthError, t } from "@/utils";
import {
getAuth,
RecaptchaVerifier,
signInWithPhoneNumber,
} from "firebase/auth";
import { getOtpApi, getUserExistsApi } from "@/utils/api";
import { useSelector } from "react-redux";
import {
getOtpServiceProvider,
settingsData,
} from "@/redux/reducer/settingSlice";
import useAutoFocus from "../Common/useAutoFocus";
import OtpScreen from "./OtpScreen";
import { Input } from "../ui/input";
import { FaRegEye, FaRegEyeSlash } from "react-icons/fa";
const RegisterWithMobileForm = ({
OnHide,
setDescriptionState,
isOTPScreen,
setIsOTPScreen,
}) => {
const auth = getAuth();
const settings = useSelector(settingsData);
const isDemoMode = settings?.demo_mode;
const otp_service_provider = useSelector(getOtpServiceProvider);
const phoneInputRef = useAutoFocus();
// Mobile registration states
const [number, setNumber] = useState(isDemoMode ? "919876598765" : "");
const [countryCode, setCountryCode] = useState("");
const [regionCode, setRegionCode] = useState("");
const [confirmationResult, setConfirmationResult] = useState(null);
const [showLoader, setShowLoader] = useState(false);
const [resendTimer, setResendTimer] = useState(0);
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [password, setPassword] = useState("");
// Remove any non-digit characters from the country code
const countryCodeDigitsOnly = countryCode.replace(/\D/g, "");
// Check if the entered number starts with the selected country code
const startsWithCountryCode = number.startsWith(countryCodeDigitsOnly);
// If the number starts with the country code, remove it
const formattedNumber = startsWithCountryCode
? number.substring(countryCodeDigitsOnly.length)
: number;
// Generate reCAPTCHA verifier
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 = "";
}
};
// Send OTP with Twilio
const sendOtpWithTwillio = async (PhoneNumber) => {
try {
const response = await getOtpApi.getOtp({
number: formattedNumber,
country_code: countryCode,
});
if (response?.data?.error === false) {
toast.success(t("otpSentSuccess"));
setIsOTPScreen(true);
setResendTimer(60);
setDescriptionState({
type: "otp",
phoneNumber: PhoneNumber,
});
} else {
toast.error(t("failedToSendOtp"));
}
} catch (error) {
console.error("error", error);
} finally {
setShowLoader(false);
}
};
// Send OTP with Firebase
const sendOtpWithFirebase = async (PhoneNumber) => {
try {
const appVerifier = await generateRecaptcha();
const confirmation = await signInWithPhoneNumber(
auth,
PhoneNumber,
appVerifier
);
setConfirmationResult(confirmation);
toast.success(t("otpSentSuccess"));
setIsOTPScreen(true);
setResendTimer(60);
setDescriptionState({
type: "otp",
phoneNumber: PhoneNumber,
});
} catch (error) {
console.log(error);
const errorCode = error.code;
handleFirebaseAuthError(errorCode);
} finally {
setShowLoader(false);
}
};
// Handle phone input change
const handleInputChange = (value, data) => {
setNumber(value);
setCountryCode("+" + (data?.dialCode || ""));
setRegionCode(data?.countryCode.toLowerCase() || "");
};
// Handle country change
const handleCountryChange = (code) => {
setCountryCode(code);
};
const checkIfUserExistsOrNot = async () => {
try {
const res = await getUserExistsApi.getUserExists({
mobile: formattedNumber,
country_code: countryCode,
});
if (res?.data?.error === false) {
toast.error(res?.data?.message);
return true;
} else {
return false;
}
} catch (error) {
console.log(error);
return false;
}
};
// Handle form submission
const handleMobileSubmit = async (e) => {
e.preventDefault();
// Validate phone number
const PhoneNumber = `${countryCode}${formattedNumber}`;
if (!isValidPhoneNumber(PhoneNumber)) {
toast.error(t("invalidPhoneNumber"));
return;
}
// Validate password
if (!password) {
toast.error(t("passwordRequired"));
return;
}
if (password.length < 6) {
toast.error(t("passwordTooShort"));
return;
}
// Send OTP
setShowLoader(true);
const isUserExists = await checkIfUserExistsOrNot();
if (isUserExists) {
setShowLoader(false);
return;
}
if (otp_service_provider === "twilio") {
await sendOtpWithTwillio(PhoneNumber);
} else {
await sendOtpWithFirebase(PhoneNumber);
}
};
// Toggle password visibility
const togglePasswordVisibility = () => {
setIsPasswordVisible((prev) => !prev);
};
// Show OTP screen if OTP was sent
if (isOTPScreen) {
return (
<OtpScreen
OnHide={OnHide}
generateRecaptcha={generateRecaptcha}
countryCode={countryCode}
formattedNumber={formattedNumber}
confirmationResult={confirmationResult}
setConfirmationResult={setConfirmationResult}
setResendTimer={setResendTimer}
resendTimer={resendTimer}
regionCode={regionCode}
password={password}
isDemoMode={isDemoMode}
isRegister={true}
key="register-otp"
/>
);
}
// Show mobile registration form
return (
<form className="flex flex-col gap-6" onSubmit={handleMobileSubmit}>
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("phoneNumber")}</Label>
<PhoneInput
country={process.env.NEXT_PUBLIC_DEFAULT_COUNTRY}
value={number}
onChange={(phone, data) => handleInputChange(phone, data)}
onCountryChange={handleCountryChange}
inputProps={{
name: "phone",
required: true,
ref: phoneInputRef,
}}
enableLongNumbers
/>
</div>
{/* Password Input */}
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("password")}</Label>
<div className="flex items-center relative">
<Input
type={isPasswordVisible ? "text" : "password"}
placeholder={t("enterPassword")}
className="ltr:pr-9 rtl:pl-9"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button
type="button"
className="absolute ltr:right-3 rtl:left-3 cursor-pointer"
onClick={togglePasswordVisibility}
>
{isPasswordVisible ? (
<FaRegEye size={20} />
) : (
<FaRegEyeSlash size={20} />
)}
</button>
</div>
</div>
<Button
type="submit"
disabled={showLoader}
className="text-xl text-white font-light px-4 py-2"
size="big"
>
{showLoader ? (
<Loader2 className="size-4 animate-spin" />
) : (
t("continue")
)}
</Button>
</form>
);
};
export default RegisterWithMobileForm;

View File

@@ -0,0 +1,127 @@
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { Loader2 } from "lucide-react";
import { FaRegEye, FaRegEyeSlash } from "react-icons/fa";
import { toast } from "sonner";
import { t } from "@/utils";
import { resetPasswordApi, userSignUpApi } from "@/utils/api";
import { useState } from "react";
const ResetPasswordScreen = ({
formattedNumber,
countryCode,
onSuccess,
onCancel,
FirebaseId,
}) => {
const [newPassword, setNewPassword] = useState("");
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [resetPasswordLoader, setResetPasswordLoader] = useState(false);
const handleResetPassword = async (e) => {
e.preventDefault();
if (!newPassword) {
toast.error(t("passwordRequired"));
return;
}
if (newPassword.length < 6) {
toast.error(t("passwordTooShort"));
return;
}
setResetPasswordLoader(true);
try {
// Step 1: Get token by calling userSignUpApi
const loginResponse = await userSignUpApi.userSignup({
mobile: formattedNumber,
country_code: countryCode,
type: "phone",
firebase_id: FirebaseId,
});
// Extract token from response
const token = loginResponse?.data?.token;
if (!token) {
toast.error(t("errorOccurred"));
return;
}
const response = await resetPasswordApi.resetPassword({
number: formattedNumber,
country_code: countryCode,
new_password: newPassword,
token: token,
});
if (response?.data?.error === false) {
toast.success(response?.data?.message);
onSuccess(); // Go back to login screen
} else {
toast.error(response?.data?.message);
}
} catch (error) {
console.log(error);
toast.error(t("errorOccurred"));
} finally {
setResetPasswordLoader(false);
}
};
return (
<form className="flex flex-col gap-6 mt-3.5" onSubmit={handleResetPassword}>
<div className="labelInputCont">
<Label className="requiredInputLabel">{t("newPassword")}</Label>
<div className="flex items-center relative">
<Input
type={isPasswordVisible ? "text" : "password"}
placeholder={t("enterNewPassword")}
className="ltr:pr-9 rtl:pl-9"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
/>
<button
type="button"
className="absolute ltr:right-3 rtl:left-3 cursor-pointer"
onClick={() => setIsPasswordVisible(!isPasswordVisible)}
>
{isPasswordVisible ? (
<FaRegEye size={20} />
) : (
<FaRegEyeSlash size={20} />
)}
</button>
</div>
</div>
<Button
type="submit"
disabled={resetPasswordLoader}
className="text-xl text-white font-light px-4 py-2"
size="big"
>
{resetPasswordLoader ? (
<Loader2 className="size-6 animate-spin" />
) : (
t("submitResetPassword")
)}
</Button>
{onCancel && (
<Button
type="button"
variant="outline"
className="text-lg text-black font-light px-4 py-2"
size="big"
onClick={onCancel}
>
{t("cancel")}
</Button>
)}
</form>
);
};
export default ResetPasswordScreen;

View File

@@ -0,0 +1,26 @@
import CustomLink from "@/components/Common/CustomLink";
const TermsAndPrivacyLinks = ({ t, settings, OnHide }) => {
return (
<div className="text-center">
{t("agreeSignIn")} {settings?.company_name} <br />
<CustomLink
href="/terms-and-condition"
className="text-primary underline"
onClick={OnHide}
>
{t("termsService")}
</CustomLink>{" "}
{t("and")}{" "}
<CustomLink
href="/privacy-policy"
className="text-primary underline"
onClick={OnHide}
>
{t("privacyPolicy")}
</CustomLink>
</div>
);
};
export default TermsAndPrivacyLinks;

View File

@@ -0,0 +1,39 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { getIsUnauthorized, setIsUnauthorized } from "@/redux/reducer/globalStateSlice";
import { useDispatch, useSelector } from "react-redux";
const UnauthorizedModal = () => {
const dispatch = useDispatch();
const open = useSelector(getIsUnauthorized);
const handleOk = () => {
dispatch(setIsUnauthorized(false));
};
return (
<AlertDialog open={open}>
<AlertDialogContent onInteractOutside={(e) => e.preventDefault()}>
<AlertDialogHeader>
<AlertDialogTitle>Unauthorized</AlertDialogTitle>
<AlertDialogDescription>
You do not have permission to access this resource. Please log in or
contact the administrator.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction onClick={handleOk}>OK</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
export default UnauthorizedModal;