Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions public/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions public/assets/locales/fa/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,12 @@
"wrongOTP": "رمز دو مرحله ای صحیح نمی باشد!",
"wrongPasswordConfirmation": "رمز ورود و تکرار آن باهم مطابقت ندارند.!",
"forgetPassword": "فراموشی رمز عبور",
"verifyEmail": "ایمیل دریافت نکرده اید؟",
"resendVerifyEmail": "ارسال دوباره ایمیل فعال‌سازی",
"sendEmail": "ارسال ایمیل",
"verifyEmailServerError": "خطا در ارسال ایمیل فعال‌سازی، دوباره تلاش کنید",
"verifyEmailFinished": "ایمیل فعال‌سازی با موفقیت ارسال شد.",
"userAlreadyVerified": "ایمیل شما فعال شده است.",
"accountNotActive": "اکانت شما هنوز فعال نشده است",
"enter": "ورود",
"resetPassword": "بازیابی رمز",
Expand Down
15 changes: 15 additions & 0 deletions src/main/Browser/Pages/Login/Login.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <LoginFormLoading/>

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 <img className={`${classes.thisLoading}`} src={images.linearLoadingBgOrange}
alt="linearLoading"/>

if (captcha.image.value === undefined) return <span>{t('captchaAnswer')}</span>

return <span style={{backgroundImage: `url("${captcha.image.value}")`}}/>
}

const sendEmailButtonTitle = () => {
if (disable) {
return <span className={`flex row jc-center`}>{t('login.sendEmail')} ( <Countdown
date={verifyEmailLock && new Date().getTime() < verifyEmailLock ? new Date(parseInt(verifyEmailLock)) : Date.now() + 120000}
renderer={props => <div> {props.minutes}:{props.seconds} </div>}
onComplete={returnFuncDisable}
/>)</span>
}
return <span>{t('login.sendEmail')}</span>
}


const FormBody = () => {
if (success) return <span>{t('login.verifyEmailFinished')}</span>

return <>
<span className={`mb-4`}>{t('login.resendVerifyEmail')}</span>
<TextInput
lead={t('email')}
type="text"
data-name="email"
data-type="email"
customClass={`${classes.forgetPasswordInput} ${classes.loginInput}`}
value={activeEmail.email.value}
onchange={(e) =>
setActiveEmail({...activeEmail, email: {value: e.target.value, error: []}})
}
alerts={activeEmail.email.error}
/>
<TextInput
lead={LeadCaptchaHandler()}
after={<span data-html={true} data-place="left" data-effect="float"
data-tip={`<span class="column jc-between col-100">${t("login.refreshCaptcha")}</span>`}><Icon
iconName="icon-arrows-cw flex fs-01"
onClick={captchaReq}
customClass={`hover-text cursor-pointer`}
/></span>}
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 <Button
type="button"
buttonClass={`${classes.thisButton} ${classes.backButton} cursor-pointer ml-1`}
buttonTitle={t('login.back')}
onClick={returnFunc}
/>

return <>
<Button
type="button"
buttonClass={`${classes.thisButton} ${classes.backButton} cursor-pointer ml-1`}
buttonTitle={t('login.back')}
onClick={returnFunc}
/>
<Button
type="submit"
buttonClass={`${classes.thisButton} ${classes.forgetPassButton} ${classes.disable} cursor-pointer mr-1`}
buttonTitle={sendEmailButtonTitle()}
disabled={disable}
/>
</>
}

return (
<form onSubmit={(e) => submit(e)} className={`column ai-center jc-between ${classes.form}`}>
<div className={`width-100 column jc-center ai-center ${classes.formBody} py-4`}>
{FormBody()}
</div>
<div className={`width-100 flex jc-center ai-center ${classes.formFooter}`}>
{FormFooter()}
</div>
</form>
);
};

export default EmailVerification;
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ const LoginForm = () => {
type={isInputVisible ? "text" : "password"}
after={
<Icon
iconName={`${isInputVisible ? ' icon-eye-2' : 'icon-eye-off'} fs-02 flex`}
iconName={`${isInputVisible ? ' icon-eye-2' : 'icon-eye-off'} fs-02 flex cursor-pointer hover-text`}
onClick={() => setIsInputVisible(!isInputVisible)}
/>
}
Expand All @@ -153,8 +153,7 @@ const LoginForm = () => {
<span className="cursor-pointer flex ai-center fs-0-8"
onClick={returnToLogin}>{t('login.back')}</span>
:
<span className="cursor-pointer flex ai-center fs-0-8"
onClick={() => setForgetPassword(true)}>{t('login.forgetPassword')}</span>
<div className="flex ai-center mt-2"><span className={`cursor-pointer fs-0-8 hover-text`} onClick={() => setForgetPassword(true)}>{t('login.forgetPassword')}</span></div>
}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: []},
Expand Down Expand Up @@ -65,8 +73,14 @@ const RegisterForm = () => {
ReactTooltip.rebuild();
});

useEffect(() => {
if (verifyEmailLock && new Date().getTime() < verifyEmailLock) setDisable(true)
}, [verifyEmailLock]);

if (registerStatus === "loading") return <LoginFormLoading/>

if (verifyEmail) return <EmailVerification returnFunc={() => setVerifyEmail(false)} email={userData?.email?.value} disable={disable} returnFuncDisable={() => setDisable(false)}/>

if (registerStatus === "finish") {
return <div className={`column jc-center ai-center text-center px-4`} style={{height: "35vh"}}>
<span className={`text-green mb-2`}>{t('login.registerFinished')}</span>
Expand All @@ -82,9 +96,14 @@ const RegisterForm = () => {
i18nKey="login.registerFinishedSpamMail"
values={{email: window.env.REACT_APP_SYSTEM_EMAIL_ADDRESS,}}
/></span>

<div className={`column mt-3 hover-text text-orange`}>
<span className="cursor-pointer flex ai-center fs-0-8" onClick={() => setVerifyEmail(true)}>{t('login.verifyEmail')}</span>
</div>
</div>
}


if (registerStatus === "finishedWithError") {
return <div className={`column jc-center ai-center text-red`} style={{height: "30vh"}}>
<span>{t('login.finishedWithError')}</span>
Expand All @@ -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")]}})
Expand Down Expand Up @@ -238,7 +259,7 @@ const RegisterForm = () => {
type={isInputVisible.password ? "text" : "password"}
after={
<Icon
iconName={`${isInputVisible.password ? ' icon-eye-2' : 'icon-eye-off'} fs-02 flex`}
iconName={`${isInputVisible.password ? ' icon-eye-2' : 'icon-eye-off'} fs-02 flex cursor-pointer hover-text`}
onClick={() => setIsInputVisible({
...isInputVisible,
password: !isInputVisible.password
Expand All @@ -257,7 +278,7 @@ const RegisterForm = () => {
type={isInputVisible.confirmPassword ? "text" : "password"}
after={
<Icon
iconName={`${isInputVisible.confirmPassword ? ' icon-eye-2' : 'icon-eye-off'} fs-02 flex`}
iconName={`${isInputVisible.confirmPassword ? ' icon-eye-2' : 'icon-eye-off'} fs-02 flex cursor-pointer hover-text`}
onClick={() => setIsInputVisible({
...isInputVisible,
confirmPassword: !isInputVisible.confirmPassword
Expand All @@ -283,6 +304,11 @@ const RegisterForm = () => {
alerts={userData.captchaAnswer.error}
maxLength="5"
/>

<div className={`column ${classes.forgetPassword} mt-1`}>
<div className="flex ai-center fs-0-8"><span className={`cursor-pointer hover-text`} onClick={() => setVerifyEmail(true)}>{t('login.verifyEmail')}</span></div>
</div>

</div>
<div className={`width-100 flex jc-center ai-center ${classes.formFooter}`}>
<Button
Expand Down
3 changes: 3 additions & 0 deletions src/store/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const SET_THEME_INITIATE = "SET_THEME_INITIATE";
export const SET_IPG = "SET_IPG";
export const SET_IPG_INITIATE = "SET_IPG_INITIATE";

export const SET_VERIFY_EMAIL_LOCK = "SET_VERIFY_EMAIL_LOCK";
export const SET_VERIFY_EMAIL_LOCK_INITIATE = "SET_VERIFY_EMAIL_LOCK_INITIATE";

export const SET_INFO_MESSAGE = "SET_INFO_MESSAGE";

export const SET_ACTIVE_PAIR = "SET_ACTIVE_PAIR";
Expand Down
Loading