diff --git a/package.json b/package.json index 11cad540..aa40fe6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opex", - "version": "v1.0.2-beta.4", + "version": "v1.0.2-beta.5", "homepage": "", "private": true, "dependencies": { @@ -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..c384889e 100644 --- a/public/assets/locales/en/translation.json +++ b/public/assets/locales/en/translation.json @@ -45,6 +45,7 @@ "close": "Close", "unit": "Unit", "offline": "Check your connection!", + "improperMobileView ": "Not optimized for mobile view.", "country": { "iran" : "I. R. IRAN", "germany" : "Germany", @@ -469,11 +470,17 @@ "save": "Save" }, "login": { - "title": "Login/Register", + "title": "Login/RegistimproperMobileView er", "wrongPassword": "Username or password is incorrect!", "wrongOTP": "Entered OTP is incorrect!", "wrongPasswordConfirmation": "Password and confirm password don't match.", "forgetPassword": "Forget Password", + "verificationEmail": "Didn't receive the email?", + "resendVerifyEmail": "Resend Email", + "sendEmail": "Send Email", + "verifyEmailServerError": "Server error, try again!", + "verifyEmailFinished": "Verification email has been sent successfully!", + "emailAlreadyVerified": "Email 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..bb0bd03b 100644 --- a/public/assets/locales/fa/translation.json +++ b/public/assets/locales/fa/translation.json @@ -45,6 +45,7 @@ "close": "بستن", "unit": "واحد", "offline": "اتصال اینترنت را بررسی کنید!", + "improperMobileView ": "فعلاً برای موبایل بهینه نشده است.", "country": { "iran" : "جمهوری اسلامی ایران", "germany" : "آلمان", @@ -474,6 +475,12 @@ "wrongOTP": "رمز دو مرحله ای صحیح نمی باشد!", "wrongPasswordConfirmation": "رمز ورود و تکرار آن باهم مطابقت ندارند.!", "forgetPassword": "فراموشی رمز عبور", + "verificationEmail": " آیا ایمیل فعال‌سازی را دریافت نکرده‌اید؟", + "resendVerifyEmail": "ارسال دوباره ایمیل فعال‌سازی", + "sendEmail": "ارسال ایمیل", + "verifyEmailServerError": "خطا در ارسال ایمیل فعال‌سازی، دوباره تلاش کنید", + "verifyEmailFinished": "ایمیل فعال‌سازی با موفقیت ارسال شد", + "emailAlreadyVerified": "ایمیل شما قبلاً فعال شده است.", "accountNotActive": "اکانت شما هنوز فعال نشده است", "enter": "ورود", "resetPassword": "بازیابی رمز", 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..21188ea8 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%; @@ -143,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..8e6c2b16 --- /dev/null +++ b/src/main/Browser/Pages/Login/components/EmailVerification/EmailVerification.js @@ -0,0 +1,218 @@ +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 {useDispatch, useSelector} from "react-redux"; +import {setVerifyEmailLockInitiate} from "../../../../../../store/actions"; + +const EmailVerification = ({returnFunc, email, disable, returnFuncDisableFalse, returnFuncDisableTrue}) => { + + const clientSecret = window.env.REACT_APP_CLIENT_SECRET + const clientId = window.env.REACT_APP_CLIENT_ID + + const {t} = useTranslation(); + const dispatch = useDispatch(); + + 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) + returnFuncDisableTrue() + dispatch(setVerifyEmailLockInitiate(new Date().getTime() + 2 * 60 * 1000)) + }) + .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.emailAlreadyVerified")]}}) + } 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={returnFuncDisableFalse} + />)
+ } + 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 6fb82d74..ebd30025 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: []}, @@ -28,6 +36,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 @@ -61,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} returnFuncDisableFalse={() => setDisable(false)} returnFuncDisableTrue={() => setDisable(true)}/> + if (registerStatus === "finish") { return
{t('login.registerFinished')} @@ -78,9 +96,14 @@ const RegisterForm = () => { i18nKey="login.registerFinishedSpamMail" values={{email: window.env.REACT_APP_SYSTEM_EMAIL_ADDRESS,}} /> + +
+ setVerifyEmail(true)}>{t('login.verificationEmail')} +
} + if (registerStatus === "finishedWithError") { return
{t('login.finishedWithError')} @@ -107,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")]}}) @@ -224,22 +249,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 + })} + /> + } /> { alerts={userData.captchaAnswer.error} maxLength="5" /> + +
+
setVerifyEmail(true)}>{t('login.verificationEmail')}
+
+
+ + {/*