From 17a2427ec5f8dde754d46761376ee2146aa6b013 Mon Sep 17 00:00:00 2001 From: Hossein Date: Mon, 17 Oct 2022 16:00:44 +0330 Subject: [PATCH 1/4] Add isInputVisible in login and register --- src/components/TextInput/TextInput.js | 17 +++++++++- src/main/Browser/Pages/Login/Login.js | 2 +- src/main/Browser/Pages/Login/Login.module.css | 15 ++++++++- .../Login/components/LoginForm/LoginForm.js | 20 +++++++++--- .../components/RegisterForm/RegisterForm.js | 32 ++++++++++++++++--- 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/components/TextInput/TextInput.js b/src/components/TextInput/TextInput.js index dc55ed96..031a50d2 100644 --- a/src/components/TextInput/TextInput.js +++ b/src/components/TextInput/TextInput.js @@ -4,7 +4,21 @@ import Select from "react-select"; import * as classes from "./TextInput.module.css"; const TextInput = (props) => { - const {customRef,readOnly,onchange,customClass,options, lead , after ,select ,alerts ,max , ...other} = props + const { + customRef, + readOnly, + onchange, + customClass, + options, + lead, + after, + select, + alerts, + max, + ltr, + info, + ...other + } = props let leadSection = null let afterSection = null @@ -15,6 +29,7 @@ const TextInput = (props) => { readOnly={readOnly} onChange={onchange} max={max} + style={{direction: ltr && 'ltr'}} {...other} /> diff --git a/src/main/Browser/Pages/Login/Login.js b/src/main/Browser/Pages/Login/Login.js index f226eb36..68dca2ab 100644 --- a/src/main/Browser/Pages/Login/Login.js +++ b/src/main/Browser/Pages/Login/Login.js @@ -23,7 +23,7 @@ const Login = () => { style={{backgroundImage: `url("${images.spaceStar}")`}}>
- +
diff --git a/src/main/Browser/Pages/Login/Login.module.css b/src/main/Browser/Pages/Login/Login.module.css index 46fc1593..93ddb195 100644 --- a/src/main/Browser/Pages/Login/Login.module.css +++ b/src/main/Browser/Pages/Login/Login.module.css @@ -39,6 +39,7 @@ .content :global(.accordion-header){ /*background-color: #242633fa;*/ background-color: var(--cardHeaderAlpha); + padding-top: 3vh; /*color: #ecececc7;*/ } /*.content :global(.accordion-body){ @@ -64,10 +65,22 @@ border-color: var(--cardHeader); color: var(--textColor); } -.loginInput.captcha :global(.lead){ + + +.loginInput.passwordInput :global(.before){ width: 50%; +} +.loginInput.passwordInput :global(input){ + width: 40%; +} +.loginInput.passwordInput :global(.after){ + width: 10%; +} +.loginInput.captcha :global(.lead){ + width: 50%; } + .loginInput.captcha :global(.lead) span{ width: 100%; height: 100%; diff --git a/src/main/Browser/Pages/Login/components/LoginForm/LoginForm.js b/src/main/Browser/Pages/Login/components/LoginForm/LoginForm.js index fe18767f..4a6b1a5b 100644 --- a/src/main/Browser/Pages/Login/components/LoginForm/LoginForm.js +++ b/src/main/Browser/Pages/Login/components/LoginForm/LoginForm.js @@ -1,5 +1,5 @@ -import {useDispatch, useSelector} from "react-redux"; import React, {useEffect, useState} from "react"; +import {useDispatch, useSelector} from "react-redux"; import classes from "../../Login.module.css"; import TextInput from "../../../../../../components/TextInput/TextInput"; import LoginFormLoading from "../LoginLoading/LoginFormLoading"; @@ -14,12 +14,15 @@ import ForgetPassword from "../ForgetPassword/ForgetPassword"; import {setUserAccountInfoInitiate, setUserInfo, setUserTokensInitiate} from "../../../../../../store/actions"; import {useGetKycStatus} from "../../../../../../queries"; import {login, parseToken} from "js-api-client"; +import Icon from "../../../../../../components/Icon/Icon"; const LoginForm = () => { const {t} = useTranslation(); const navigate = useNavigate(); const dispatch = useDispatch(); const location = useLocation(); + + const [isInputVisible, setIsInputVisible] = useState(false); const [isLoading, setLoading] = useState(false); const [loginError, setLoginError] = useState(false); const [needOTP, setNeedOTP] = useState(undefined); @@ -106,6 +109,8 @@ const LoginForm = () => { setCredential({...credential, otp: ""}) } + + return
submit(e)} className={`column ai-center jc-between ${classes.form}`}>
{!needOTP && { setCredential({...credential, username: e.target.value})} /> setCredential({...credential, password: e.target.value})} + type={isInputVisible ? "text" : "password"} + after={ + setIsInputVisible(!isInputVisible)} + /> + } + /> } diff --git a/src/main/Browser/Pages/Login/components/RegisterForm/RegisterForm.js b/src/main/Browser/Pages/Login/components/RegisterForm/RegisterForm.js index 6fb82d74..17324c04 100644 --- a/src/main/Browser/Pages/Login/components/RegisterForm/RegisterForm.js +++ b/src/main/Browser/Pages/Login/components/RegisterForm/RegisterForm.js @@ -28,6 +28,10 @@ const RegisterForm = () => { password: {value: "", error: []}, confirmPassword: {value: "", error: []}, }); + const [isInputVisible, setIsInputVisible] = useState({ + password: false, + confirmPassword: false, + }); const clientSecret = window.env.REACT_APP_CLIENT_SECRET const clientId = window.env.REACT_APP_CLIENT_ID @@ -224,22 +228,42 @@ const RegisterForm = () => { /> inputHandler(e)} alerts={userData.password.error} data-min={8} + type={isInputVisible.password ? "text" : "password"} + after={ + setIsInputVisible({ + ...isInputVisible, + password: !isInputVisible.password + })} + /> + } /> inputHandler(e)} alerts={userData.confirmPassword.error} + type={isInputVisible.confirmPassword ? "text" : "password"} + after={ + setIsInputVisible({ + ...isInputVisible, + confirmPassword: !isInputVisible.confirmPassword + })} + /> + } /> Date: Tue, 18 Oct 2022 12:30:22 +0330 Subject: [PATCH 2/4] #121, Add email verification component --- package.json | 2 +- public/assets/locales/en/translation.json | 6 + public/assets/locales/fa/translation.json | 6 + src/main/Browser/Pages/Login/Login.module.css | 15 ++ .../EmailVerification/EmailVerification.js | 214 ++++++++++++++++++ .../Login/components/LoginForm/LoginForm.js | 5 +- .../components/RegisterForm/RegisterForm.js | 30 ++- src/store/actions/actionTypes.js | 3 + src/store/actions/exchange.js | 13 ++ src/store/actions/index.js | 4 +- src/store/reducers/exchangeReducer.js | 6 + src/store/sagas/global.js | 9 +- src/store/sagas/index.js | 3 +- yarn.lock | 8 +- 14 files changed, 311 insertions(+), 13 deletions(-) create mode 100644 src/main/Browser/Pages/Login/components/EmailVerification/EmailVerification.js diff --git a/package.json b/package.json index 11cad540..7c1b8386 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "i18next": "^21.8.0", "i18next-browser-languagedetector": "^6.1.4", "i18next-http-backend": "^1.4.0", - "js-api-client": "https://github.com/opexdev/js-api-client.git#v1.0.1-beta.2", + "js-api-client": "https://github.com/opexdev/js-api-client.git#develop", "jwt-decode": "^3.1.2", "lightweight-charts": "^3.8.0", "moment-jalaali": "^0.9.2", diff --git a/public/assets/locales/en/translation.json b/public/assets/locales/en/translation.json index ae45da0f..20f70e1c 100644 --- a/public/assets/locales/en/translation.json +++ b/public/assets/locales/en/translation.json @@ -474,6 +474,12 @@ "wrongOTP": "Entered OTP is incorrect!", "wrongPasswordConfirmation": "Password and confirm password don't match.", "forgetPassword": "Forget Password", + "verifyEmail": "Didn't get email ?", + "resendVerifyEmail": "Resend verification email", + "sendEmail": "Send email", + "verifyEmailServerError": "Server error to send verify email, try again!", + "verifyEmailFinished": "Verify email sent successfully!", + "userAlreadyVerified": "User already verified", "accountNotActive": "Your account has not been activated yet.", "enter": "Enter", "resetPassword": "Reset Password", diff --git a/public/assets/locales/fa/translation.json b/public/assets/locales/fa/translation.json index f9dfdb5a..a21b2bb0 100644 --- a/public/assets/locales/fa/translation.json +++ b/public/assets/locales/fa/translation.json @@ -474,6 +474,12 @@ "wrongOTP": "رمز دو مرحله ای صحیح نمی باشد!", "wrongPasswordConfirmation": "رمز ورود و تکرار آن باهم مطابقت ندارند.!", "forgetPassword": "فراموشی رمز عبور", + "verifyEmail": "ایمیل دریافت نکرده اید؟", + "resendVerifyEmail": "ارسال دوباره ایمیل فعال‌سازی", + "sendEmail": "ارسال ایمیل", + "verifyEmailServerError": "خطا در ارسال ایمیل فعال‌سازی، دوباره تلاش کنید", + "verifyEmailFinished": "ایمیل فعال‌سازی با موفقیت ارسال شد.", + "userAlreadyVerified": "ایمیل شما فعال شده است.", "accountNotActive": "اکانت شما هنوز فعال نشده است", "enter": "ورود", "resetPassword": "بازیابی رمز", diff --git a/src/main/Browser/Pages/Login/Login.module.css b/src/main/Browser/Pages/Login/Login.module.css index 93ddb195..21188ea8 100644 --- a/src/main/Browser/Pages/Login/Login.module.css +++ b/src/main/Browser/Pages/Login/Login.module.css @@ -156,6 +156,21 @@ direction: ltr !important; } + +.disable:disabled,.button[disabled] { + border: 0.3vh solid var(--cardHeader); + background: var(--cardHeader); + color: var(--textColor); + cursor: not-allowed; +} + +.thisButton:disabled,.button[disabled] { + border: 0.3vh solid var(--cardHeader); + background: var(--cardHeader); + color: var(--textColor); + cursor: not-allowed; +} + .twinkle{ -webkit-animation: twinkle 4s linear infinite; -moz-animation: twinkle 4s linear infinite; diff --git a/src/main/Browser/Pages/Login/components/EmailVerification/EmailVerification.js b/src/main/Browser/Pages/Login/components/EmailVerification/EmailVerification.js new file mode 100644 index 00000000..3231e116 --- /dev/null +++ b/src/main/Browser/Pages/Login/components/EmailVerification/EmailVerification.js @@ -0,0 +1,214 @@ +import React, {useEffect, useState} from 'react'; +import classes from "../../Login.module.css"; +import Button from "../../../../../../components/Button/Button"; +import {useTranslation} from "react-i18next"; +import {getCaptchaImage, getPanelToken, requestForVerifyEmail} from "js-api-client"; +import ReactTooltip from "react-tooltip"; +import LoginFormLoading from "../LoginLoading/LoginFormLoading"; +import {validateEmail} from "../../../../../../utils/utils"; +import {images} from "../../../../../../assets/images"; +import TextInput from "../../../../../../components/TextInput/TextInput"; +import Icon from "../../../../../../components/Icon/Icon"; +import Countdown from "react-countdown"; +import {useSelector} from "react-redux"; + +const EmailVerification = ({returnFunc, email, disable, returnFuncDisable}) => { + + const clientSecret = window.env.REACT_APP_CLIENT_SECRET + const clientId = window.env.REACT_APP_CLIENT_ID + + const {t} = useTranslation(); + + const [loading, setLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [success, setSuccess] = useState(false); + + const verifyEmailLock = useSelector((state) => state.exchange.verifyEmailLock) + + const [activeEmail, setActiveEmail] = useState({ + email: {value: "", error: []}, + captchaAnswer: {value: "", error: []}, + }); + const [captcha, setCaptcha] = useState({ + image: {value: "", error: []}, + SessionKey: {value: "", error: []}, + expireTime: {value: "", error: []}, + }); + + const captchaReq = () => { + setIsLoading(true) + getCaptchaImage() + .then((res) => { + setCaptcha({ + image: { + value: `data:${res.headers['content-type']};base64,${Buffer.from(res.data).toString('base64')}`, + error: [] + }, + SessionKey: {value: res.headers['captcha-session-key'], error: []}, + expireTime: {value: res.headers['captcha-expire-timestamp'], error: []}, + }) + }).catch(() => { + setActiveEmail({...activeEmail, captchaAnswer: {value: "", error: [t("login.captchaServerError")]}}) + setCaptcha({...captcha, image: {value: undefined, error: []}}) + }).finally(() => { + setIsLoading(false) + }); + } + + useEffect(() => { + captchaReq() + if (email.length > 0) return setActiveEmail({...activeEmail, email: {...activeEmail.email, value: email}}) + }, []) + + + useEffect(() => { + ReactTooltip.rebuild(); + }); + + if (loading) return + + const submit = async (e) => { + e.preventDefault(); + if (activeEmail.email.value === "") { + setActiveEmail({...activeEmail, email: {value: "", error: [t('login.emptyEmail')]}}) + return; + } + if (!validateEmail(activeEmail.email.value)) { + setActiveEmail({...activeEmail, email: {value: "", error: [t('login.forgetPassEmailForgetError')]}}) + return; + } + if (activeEmail.captchaAnswer.value === "") { + setActiveEmail({...activeEmail, captchaAnswer: {value: "", error: [t('login.emptyCaptcha')]}}) + return; + } + if (activeEmail.captchaAnswer.value.length < 5) { + setActiveEmail({...activeEmail, captchaAnswer: {value: "", error: [t('login.minCaptcha')]}}) + return; + } + setLoading(true); + + const {data: {access_token: panelToken}} = await getPanelToken(clientId, clientSecret); + const captchaValue = `${captcha.SessionKey.value}-${activeEmail.captchaAnswer.value}` + requestForVerifyEmail(activeEmail.email.value, captchaValue) + .then(() => { + setSuccess(true) + }) + .catch((err) => { + if (err?.response?.data?.code === 10001 && err?.response?.data?.message === "Captcha is not valid") { + console.log("in captcha erro") + return setActiveEmail({...activeEmail, captchaAnswer: {value: "", error: [t("login.InvalidCaptcha")]}}) + } + if (err?.response?.data?.code === 1002 && err?.response?.data?.message === "User already verified") { + return setActiveEmail({...activeEmail, captchaAnswer: {value: "", error: [t("login.userAlreadyVerified")]}}) + } else { + return setActiveEmail({ + ...activeEmail, + captchaAnswer: {value: "", error: [t('login.verifyEmailServerError')]} + }) + } + }) + .finally(() => { + setLoading(false); + }); + + } + + const LeadCaptchaHandler = () => { + if (isLoading) return linearLoading + + if (captcha.image.value === undefined) return {t('captchaAnswer')} + + return + } + + const sendEmailButtonTitle = () => { + if (disable) { + return {t('login.sendEmail')} (
{props.minutes}:{props.seconds}
} + onComplete={returnFuncDisable} + />)
+ } + return {t('login.sendEmail')} + } + + + const FormBody = () => { + if (success) return {t('login.verifyEmailFinished')} + + return <> + {t('login.resendVerifyEmail')} + + setActiveEmail({...activeEmail, email: {value: e.target.value, error: []}}) + } + alerts={activeEmail.email.error} + /> + ${t("login.refreshCaptcha")}
`}>
} + type="text" + data-name="captchaAnswer" + data-type="input" + data-min={5} + customClass={`${classes.loginInput} ${classes.captcha}`} + value={activeEmail.captchaAnswer.value} + onchange={(e) => + setActiveEmail({...activeEmail, captchaAnswer: {value: e.target.value, error: []}}) + } + alerts={activeEmail.captchaAnswer.error} + maxLength="5" + /> + + } + + + const FormFooter = () => { + if (success) return
diff --git a/src/main/Browser/Pages/Login/components/RegisterForm/RegisterForm.js b/src/main/Browser/Pages/Login/components/RegisterForm/RegisterForm.js index 17324c04..79825edb 100644 --- a/src/main/Browser/Pages/Login/components/RegisterForm/RegisterForm.js +++ b/src/main/Browser/Pages/Login/components/RegisterForm/RegisterForm.js @@ -9,11 +9,19 @@ import Icon from "../../../../../../components/Icon/Icon"; import {images} from "../../../../../../assets/images"; import ReactTooltip from "react-tooltip"; import {getCaptchaImage, getPanelToken, userRegister} from "js-api-client"; +import EmailVerification from "../EmailVerification/EmailVerification"; +import {setVerifyEmailLockInitiate} from "../../../../../../store/actions"; +import {useDispatch, useSelector} from "react-redux"; const RegisterForm = () => { const {t} = useTranslation(); + const dispatch = useDispatch(); + const verifyEmailLock = useSelector((state) => state.exchange.verifyEmailLock) + const [registerStatus, setRegisterStatus] = useState("") + const [verifyEmail, setVerifyEmail] = useState(false); + const [disable, setDisable] = useState(false); const [isLoading, setIsLoading] = useState(true); const [captcha, setCaptcha] = useState({ image: {value: "", error: []}, @@ -65,8 +73,14 @@ const RegisterForm = () => { ReactTooltip.rebuild(); }); + useEffect(() => { + if (verifyEmailLock && new Date().getTime() < verifyEmailLock) setDisable(true) + }, [verifyEmailLock]); + if (registerStatus === "loading") return + if (verifyEmail) return setVerifyEmail(false)} email={userData?.email?.value} disable={disable} returnFuncDisable={() => setDisable(false)}/> + if (registerStatus === "finish") { return
{t('login.registerFinished')} @@ -82,9 +96,14 @@ const RegisterForm = () => { i18nKey="login.registerFinishedSpamMail" values={{email: window.env.REACT_APP_SYSTEM_EMAIL_ADDRESS,}} /> + +
+ setVerifyEmail(true)}>{t('login.verifyEmail')} +
} + if (registerStatus === "finishedWithError") { return
{t('login.finishedWithError')} @@ -111,6 +130,8 @@ const RegisterForm = () => { userRegister(user, panelToken) .then(() => { setRegisterStatus("finish"); + setDisable(true) + dispatch(setVerifyEmailLockInitiate(new Date().getTime() + 2 * 60 * 1000)) }).catch((e) => { if (e?.response?.data?.error === "InvalidCaptcha") { setUserData({...userData, captchaAnswer: {value: "", error: [t("login.InvalidCaptcha")]}}) @@ -238,7 +259,7 @@ const RegisterForm = () => { type={isInputVisible.password ? "text" : "password"} after={ setIsInputVisible({ ...isInputVisible, password: !isInputVisible.password @@ -257,7 +278,7 @@ const RegisterForm = () => { type={isInputVisible.confirmPassword ? "text" : "password"} after={ setIsInputVisible({ ...isInputVisible, confirmPassword: !isInputVisible.confirmPassword @@ -283,6 +304,11 @@ const RegisterForm = () => { alerts={userData.captchaAnswer.error} maxLength="5" /> + +
+
setVerifyEmail(true)}>{t('login.verifyEmail')}
+
+
diff --git a/src/main/Mobile/Mobile.js b/src/main/Mobile/Mobile.js index 3968fc25..f2cd13b0 100644 --- a/src/main/Mobile/Mobile.js +++ b/src/main/Mobile/Mobile.js @@ -5,11 +5,13 @@ import "./Mobille.css"; import FullWidthLoading from "../../components/FullWidthLoading/FullWidthLoading"; import i18n from "i18next"; import Radium from "radium"; -import Button from "../../components/Button/Button"; +import {useTranslation} from "react-i18next"; +import {toAbsoluteUrl} from "../../utils/utils"; const Mobile = () => { + const {t} = useTranslation(); const isLoading = useSelector((state) => state.global.isLoading) const isDark = useSelector((state) => state.global.isDark) const dispatch = useDispatch(); @@ -29,18 +31,25 @@ const Mobile = () => { } const Style = { - "@media (max-width: 480px)": { - - } + "@media (max-width: 480px)": {} } return (
-
+ + {/*