From a652ec055ca8957e1f6f0d06e96695175d74ea2f Mon Sep 17 00:00:00 2001
From: RAHEEL <mraheeliftikhar1994@gmail.com>
Date: Fri, 20 Jun 2025 15:19:30 +0500
Subject: [PATCH] added email verification on signup

---
 .../src/components/CustomModal.tsx            |  2 +
 .../lowcoder/src/api/emailVerifyApi.ts        | 35 +++++++++
 .../packages/lowcoder/src/i18n/locales/en.ts  |  5 +-
 .../lowcoder/src/pages/userAuth/register.tsx  | 78 ++++++++++++++-----
 4 files changed, 99 insertions(+), 21 deletions(-)
 create mode 100644 client/packages/lowcoder/src/api/emailVerifyApi.ts

diff --git a/client/packages/lowcoder-design/src/components/CustomModal.tsx b/client/packages/lowcoder-design/src/components/CustomModal.tsx
index e7101cefb..4a845ae65 100644
--- a/client/packages/lowcoder-design/src/components/CustomModal.tsx
+++ b/client/packages/lowcoder-design/src/components/CustomModal.tsx
@@ -290,6 +290,7 @@ CustomModal.confirm = (props: {
   type?: "info" | "warn" | "error" | "success";
   width?: number | string;
   customStyles?:React.CSSProperties;
+  showCancelButton?: boolean;
 }): any => {
 
   const fixedWidth = typeof props.width === "object" ? undefined : props.width;
@@ -350,6 +351,7 @@ CustomModal.confirm = (props: {
         footer={props.footer}
         width={props.width}
         customStyles={props.customStyles}
+        showCancelButton={props.showCancelButton !== false}
       />
     ),
   });
diff --git a/client/packages/lowcoder/src/api/emailVerifyApi.ts b/client/packages/lowcoder/src/api/emailVerifyApi.ts
new file mode 100644
index 000000000..37e73f2b9
--- /dev/null
+++ b/client/packages/lowcoder/src/api/emailVerifyApi.ts
@@ -0,0 +1,35 @@
+import axios, { AxiosInstance, AxiosPromise, AxiosRequestConfig } from "axios";
+import Api from "./api";
+
+const key = "";
+
+let axiosIns: AxiosInstance | null = null;
+
+const getAxiosInstance = (clientSecret?: string) => {
+  if (axiosIns && !clientSecret) {
+    return axiosIns;
+  }
+
+  const apiRequestConfig: AxiosRequestConfig = {
+    baseURL: "https://emailverifier.reoon.com/api/v1/verify",
+  };
+
+  axiosIns = axios.create(apiRequestConfig);
+  return axiosIns;
+};
+
+export class EmailVerifyApi extends Api {
+  static quickVerification(email: string): AxiosPromise<any> {
+    const requestConfig: AxiosRequestConfig = {
+      method: "GET",
+      withCredentials: false,
+      params: {
+        email,
+        key,
+        mode: 'quick',
+      },
+    };
+
+    return getAxiosInstance().request(requestConfig);
+  }
+}
diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts
index 79eb3619e..6928da2e1 100644
--- a/client/packages/lowcoder/src/i18n/locales/en.ts
+++ b/client/packages/lowcoder/src/i18n/locales/en.ts
@@ -3821,7 +3821,10 @@ export const en = {
     "enterPassword": "Enter your password",
     "selectAuthProvider": "Select Authentication Provider",
     "selectWorkspace": "Select your workspace",
-    "userNotFound": "User not found. Please make sure you entered the correct email."
+    "userNotFound": "User not found. Please make sure you entered the correct email.",
+    "emailAlreadyExist": "Email is already registered",
+    "emailVerificationFailed": "Email Verification Failed",
+    "emailVerificationFailedText": "We couldn't verify your email address. A valid email is required to receive important notifications such as password resets and account updates. Please ensure you're using a valid email and try again."
   },
   "preLoad": {
     "jsLibraryHelpText": "Add JavaScript Libraries to Your Current Application via URL Addresses. lodash, day.js, uuid, numbro are Built into the System for Immediate Use. JavaScript Libraries are Loaded Before the Application is Initialized, Which Can Have an Impact on Application Performance.",
diff --git a/client/packages/lowcoder/src/pages/userAuth/register.tsx b/client/packages/lowcoder/src/pages/userAuth/register.tsx
index 2a0f3386d..701de5e70 100644
--- a/client/packages/lowcoder/src/pages/userAuth/register.tsx
+++ b/client/packages/lowcoder/src/pages/userAuth/register.tsx
@@ -7,7 +7,7 @@ import {
   StyledRouteLinkLogin,
   TermsAndPrivacyInfo,
 } from "pages/userAuth/authComponents";
-import { FormInput, messageInstance, PasswordInput } from "lowcoder-design";
+import { CustomModal, FormInput, messageInstance, PasswordInput } from "lowcoder-design";
 import { AUTH_LOGIN_URL, ORG_AUTH_LOGIN_URL } from "constants/routesURL";
 import UserApi from "api/userApi";
 import { useRedirectUrl } from "util/hooks";
@@ -31,6 +31,8 @@ import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelec
 import { useEnterpriseContext } from "@lowcoder-ee/util/context/EnterpriseContext";
 import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions";
 import { fetchOrgPaginationByEmail } from "@lowcoder-ee/util/pagination/axios";
+import { EmailVerifyApi } from "@lowcoder-ee/api/emailVerifyApi";
+import Typography from "antd/es/typography";
 
 const StyledFormInput = styled(FormInput)`
   margin-bottom: 16px;
@@ -57,6 +59,7 @@ function UserRegister() {
   const [password, setPassword] = useState("");
   const [orgLoading, setOrgLoading] = useState(false);
   const [lastEmailChecked, setLastEmailChecked] = useState("");
+  const [emailVerified, setEmailVerified] = useState(true);
   const [signupEnabled, setSignupEnabled] = useState<boolean>(true);
   const [signinEnabled, setSigninEnabled] = useState<boolean>(true);
   const [defaultOrgId, setDefaultOrgId] = useState<string|undefined>();
@@ -159,27 +162,62 @@ function UserRegister() {
     afterLoginSuccess,
   );
 
-  const checkEmailExist = () => {
+  const checkEmailExist = async () => {
     if (!Boolean(account.length) || lastEmailChecked === account || isEnterpriseMode) return;
-
     setOrgLoading(true);
-    OrgApi.fetchOrgsByEmail(account)
-      .then((resp) => {
-        if (validateResponse(resp)) {
-          const orgList = resp.data.data;
-          if (orgList.length) {
-            messageInstance.error('Email is already registered');
-            history.push(
-              AUTH_LOGIN_URL,
-              {...location.state || {}, email: account},
-            )
-          }
+    try {
+      const resp = await OrgApi.fetchOrgsByEmail(account);
+      if (validateResponse(resp)) {
+        const orgList = resp.data.data;
+        if (orgList.length) {
+          messageInstance.error(trans('userAuth.emailAlreadyExist'));
+          history.push(
+            AUTH_LOGIN_URL,
+            {...location.state || {}, email: account},
+          )
+          throw new Error(trans('userAuth.emailAlreadyExist'));
         }
-      })
-      .finally(() => {
-        setLastEmailChecked(account)
-        setOrgLoading(false);
+      }
+    } finally {
+      setLastEmailChecked(account)
+      setOrgLoading(false);
+    }
+  }
+
+  const verifyEmail = async () => {
+    if (!Boolean(account.length) || lastEmailChecked === account) return;
+    try {
+      const resp = await EmailVerifyApi.quickVerification(account);
+      if (resp?.data?.status === "valid") return;
+
+      setEmailVerified(false);
+      CustomModal.confirm({
+        title: trans("userAuth.emailVerificationFailed"),
+        content: trans("userAuth.emailVerificationFailedText"),
+        confirmBtnType: "normal",
+        okText: trans("componentDoc.close"),
+        showCancelButton: false,
       });
+      throw new Error(trans("userAuth.emailVerificationFailed"));
+    } catch (error) {
+      throw error;
+    }
+  }
+
+  const handleEmailBlur = async () => {
+    try {
+      await checkEmailExist();
+    } catch(error) {
+      console.error(error);
+      return;
+    }
+  
+    try {
+      await verifyEmail();
+    } catch(error) {
+      console.error(error);
+      return;
+    }
   }
 
   const registerHeading = trans("userAuth.register")
@@ -201,7 +239,7 @@ function UserRegister() {
                 label={trans("userAuth.email")}
                 defaultValue={account}
                 onChange={(value, valid) => setAccount(valid ? value : "")}
-                onBlur={checkEmailExist}
+                onBlur={handleEmailBlur}
                 placeholder={trans("userAuth.inputEmail")}
                 checkRule={{
                   check: checkEmailValid,
@@ -217,7 +255,7 @@ function UserRegister() {
                 doubleCheck
               />
               <ConfirmButton
-                disabled={!account || !password || submitBtnDisable}
+                disabled={!account || !password || submitBtnDisable || !emailVerified}
                 onClick={onSubmit}
                 loading={loading}
               >
