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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opex",
"version": "v1.0.2-beta.4",
"version": "v1.0.2-beta.5",
"homepage": "",
"private": true,
"dependencies": {
Expand All @@ -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
9 changes: 8 additions & 1 deletion public/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions public/assets/locales/fa/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"close": "بستن",
"unit": "واحد",
"offline": "اتصال اینترنت را بررسی کنید!",
"improperMobileView ": "فعلاً برای موبایل بهینه نشده است.",
"country": {
"iran" : "جمهوری اسلامی ایران",
"germany" : "آلمان",
Expand Down Expand Up @@ -474,6 +475,12 @@
"wrongOTP": "رمز دو مرحله ای صحیح نمی باشد!",
"wrongPasswordConfirmation": "رمز ورود و تکرار آن باهم مطابقت ندارند.!",
"forgetPassword": "فراموشی رمز عبور",
"verificationEmail": " آیا ایمیل فعال‌سازی را دریافت نکرده‌اید؟",
"resendVerifyEmail": "ارسال دوباره ایمیل فعال‌سازی",
"sendEmail": "ارسال ایمیل",
"verifyEmailServerError": "خطا در ارسال ایمیل فعال‌سازی، دوباره تلاش کنید",
"verifyEmailFinished": "ایمیل فعال‌سازی با موفقیت ارسال شد",
"emailAlreadyVerified": "ایمیل شما قبلاً فعال شده است.",
"accountNotActive": "اکانت شما هنوز فعال نشده است",
"enter": "ورود",
"resetPassword": "بازیابی رمز",
Expand Down
17 changes: 16 additions & 1 deletion src/components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,6 +29,7 @@ const TextInput = (props) => {
readOnly={readOnly}
onChange={onchange}
max={max}
style={{direction: ltr && 'ltr'}}
{...other}
/>

Expand Down
2 changes: 1 addition & 1 deletion src/main/Browser/Pages/Login/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const Login = () => {
style={{backgroundImage: `url("${images.spaceStar}")`}}>
<div className={`col-60 flex jc-center ai-center `} style={{height: "100%"}}>
<div className={`${classes.content}`}>
<AccordionBox content={data}/>
<AccordionBox content={data}/>
</div>
</div>
<div className={`col-40 column ai-center jc-center ${classes.intro} move-image`}>
Expand Down
30 changes: 29 additions & 1 deletion src/main/Browser/Pages/Login/Login.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
.content :global(.accordion-header){
/*background-color: #242633fa;*/
background-color: var(--cardHeaderAlpha);
padding-top: 3vh;
/*color: #ecececc7;*/
}
/*.content :global(.accordion-body){
Expand All @@ -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%;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <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)
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 <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={returnFuncDisableFalse}
/>)</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;
Loading