From 085879efc57e1d971d7adc54804e2075754f9a61 Mon Sep 17 00:00:00 2001 From: Hossein Date: Sun, 27 Aug 2023 16:22:59 +0330 Subject: [PATCH] #9: Refactor login with js-api-client --- src/components/SideMenu/SideMenu.js | 7 +- src/main/Mobile/Pages/Login/Login.js | 19 +- src/main/Mobile/Pages/Login/Login.module.css | 92 ++++---- src/main/Mobile/Pages/Login/api/auth.js | 148 ------------ .../EmailVerification/EmailVerification.js | 212 ++++++++++++++++++ .../ForgetPassword/ForgetPassword.js | 133 ++++++----- .../Login/components/LoginForm/LoginForm.js | 113 +++++----- .../LoginLoading/LoginFormLoading.js | 15 +- .../Pages/Login/components/OTPForm/OTPForm.js | 5 - .../components/RegisterForm/RegisterForm.js | 157 ++++++++----- 10 files changed, 514 insertions(+), 387 deletions(-) delete mode 100644 src/main/Mobile/Pages/Login/api/auth.js create mode 100644 src/main/Mobile/Pages/Login/components/EmailVerification/EmailVerification.js diff --git a/src/components/SideMenu/SideMenu.js b/src/components/SideMenu/SideMenu.js index 2df0f28..c5708c5 100644 --- a/src/components/SideMenu/SideMenu.js +++ b/src/components/SideMenu/SideMenu.js @@ -8,16 +8,13 @@ import {useTranslation} from "react-i18next"; import ReactTooltip from "react-tooltip"; import {setLogoutInitiate} from "../../store/actions"; import {toast} from "react-hot-toast"; -import {logOut} from "../../main/Mobile/Pages/Login/api/auth"; +import {logout} from "js-api-client"; import {images} from "../../assets/images"; import i18n from "i18next"; import ToggleSwitch from "../ToggleSwitch/ToggleSwitch"; import * as Routes from "../../main/Mobile/Routes/routes"; import {toAbsoluteUrl} from "../../utils/utils"; import packageJson from "../../../package.json" -import {Overview} from "../../main/Mobile/Routes/routes"; - - const SideMenu = () => { @@ -37,7 +34,7 @@ const SideMenu = () => { }); const logOutHandler = async () => { - logOut().then(()=>{ + logout().then(()=>{ toast.success(t("header.logOutSuccess")) dispatch(setLogoutInitiate()) }).catch(()=>{ diff --git a/src/main/Mobile/Pages/Login/Login.js b/src/main/Mobile/Pages/Login/Login.js index b965780..5137f55 100644 --- a/src/main/Mobile/Pages/Login/Login.js +++ b/src/main/Mobile/Pages/Login/Login.js @@ -25,22 +25,13 @@ const Login = () => { ]; return ( -
- {/*
-
- logo -

- {t('login.description')} -

-
-
*/} -
-
- -
+
+ +
+
+
); }; diff --git a/src/main/Mobile/Pages/Login/Login.module.css b/src/main/Mobile/Pages/Login/Login.module.css index ad4d5a2..a383f98 100644 --- a/src/main/Mobile/Pages/Login/Login.module.css +++ b/src/main/Mobile/Pages/Login/Login.module.css @@ -3,30 +3,12 @@ background-repeat: repeat; background-position: center center; background-size: 600px 1200px; - /*background-color: var(--mainContent);*/ background-color: var(--container); color: var(--textColor); } -.row { - height: 100vh; -} -.intro { - height: 100vh; - background-repeat: no-repeat repeat; - background-position: center center; - background-size: 600px 1200px; -} -.intro img { - width: 45vw; -} -.intro h1 { - line-height: 4.5vh; - text-align: center; - /*color: #f2f2f2;*/ -} + .content { - width: 80%; - /*height: 65%;*/ + width: 90%; background-color: var(--cardBodyAlpha); border-radius: 9px; color: var(--textColor); @@ -35,52 +17,49 @@ .content :global(.accordion-header) > div{ justify-content: center; + margin-top: 1.5vh; } .content :global(.accordion-header){ - /*background-color: #242633fa;*/ background-color: var(--cardHeaderAlpha); - /*color: #ecececc7;*/ } -/*.content :global(.accordion-body){ - background-color: rgba(206, 223, 255, 0.83); -}*/ + .forgetPasswordInput{ width: 75%; } .loginInput { - width: 85%; - margin-bottom: 1vh; + width: 93%; + margin-bottom: 2vh; } .loginInput > div{ border-color: var(--cardHeader); } .loginInput :global(.lead){ - /*background-color: #242633fa; - border-color: #242633fa; - color: #ecececc7;*/ background-color: var(--cardHeader); border-color: var(--cardHeader); color: var(--textColor); + width: 40%; } -.loginInput.captcha :global(.lead){ - width: 50%; - +.loginInput :global(input){ + width: 60% !important; } -.loginInput.captcha :global(.lead) span{ - width: 100%; - height: 100%; - background-position: center; - background-repeat: no-repeat; - /*background-size: cover;*/ + + + + +.loginInput.captcha :global(.lead){ + width: 40%; } + .loginInput.captcha :global(input){ - width: 40%; + width: 43% !important; } + + .loginInput.captcha :global(.after){ - width: 10%; + width: 17%; } .thisLoading{ @@ -139,10 +118,41 @@ background-color: var(--red); } +.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; +} + + + + + +.loginInput.passwordInput :global(.before){ + width: 40% !important; +} + +.loginInput.passwordInput :global(input){ + width: 43% !important; +} +.loginInput.passwordInput :global(.after){ + width: 17% !important; +} + + .ltrInput :global(input) { direction: ltr !important; } + .twinkle{ -webkit-animation: twinkle 4s linear infinite; -moz-animation: twinkle 4s linear infinite; diff --git a/src/main/Mobile/Pages/Login/api/auth.js b/src/main/Mobile/Pages/Login/api/auth.js deleted file mode 100644 index 8c054b9..0000000 --- a/src/main/Mobile/Pages/Login/api/auth.js +++ /dev/null @@ -1,148 +0,0 @@ -import axios from "axios"; - -const clientSecret = window.env.REACT_APP_CLIENT_SECRET -const clientId = window.env.REACT_APP_CLIENT_ID - - -export const getToken = async () => { - const params = new URLSearchParams(); - params.append('client_id', clientId); - params.append('client_secret', clientSecret); - params.append('grant_type', 'client_credentials'); - return await axios.post('/auth/realms/opex/protocol/openid-connect/token', params) - .then((res) => { - return res.data.access_token; - }).catch((e) => { - if (!e.response) { - return false; - } - return e.response; - }) -} - -export const login = async (credential , agent) => { - const params = new URLSearchParams(); - params.append('client_id', clientId); - params.append('username', credential.username); - params.append('password', credential.password); - params.append('otp', credential.otp); - params.append('agent', agent); - params.append('grant_type', 'password'); - params.append('client_secret', clientSecret); - return await axios.post('/auth/realms/opex/protocol/openid-connect/token', params) - .then((res) => { - return res; - }).catch((e) => { - if (!e.response) { - return false; - } - return e.response; - }) -}; - -export const logOut = () => { - return axios.post(`/auth/realms/opex/user-management/user/logout`) -} - -export const register = (user , panelToken) => { - return axios.post('/auth/realms/opex/user-management/user', user ,{ - headers : { - "Authorization" : "Bearer "+ panelToken - } - }) -} - -export const getCaptcha = async () => { - return await axios.post(`/captcha/session`, null ,{ - headers : { - 'Accept' : 'image/jpeg' - - }, - responseType: "arraybuffer" - }) - .then((res) => { - return res; - }).catch((e) => { - if (!e.response) { - return false; - } - return e.response; - }) -}; - - -//Todo Remove getUser -export const getUser = async (token, key , value) => { - axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; - return await axios.get(`/auth/admin/realms/opex/users?${key}=${value}`,) - .then((res) => { - return res; - }).catch((e) => { - if (!e.response) { - return false; - } - return e.response; - }) -} - -export const sendForgetPasswordEmail = async (panelToken , email ,captchaAnswer) => { - return await axios.post(`/auth/realms/opex/user-management/user/forgot?email=${email}&captcha-answer=${captchaAnswer}`,null ,{ - headers : { - "Authorization" : "Bearer "+ panelToken - } - }).then((res) => { - return res; - }).catch((e) => { - if (!e.response) { - return false; - } - return e.response; - }) -} - -export const parseToken = ( data ) => { - return { - accessToken: data.access_token, - accessTokenExpires: Date.now() + data.expires_in * 1000, - refreshToken: data.refresh_token, - refreshTokenExpires: Date.now() + data.refresh_expires_in * 1000, - } -} -export const parsePanelToken = ( data ) => { - return { - panelAccessToken: data.access_token, - panelAccessTokenExpires: Date.now() + data.expires_in * 1000, - } -} - -export const sendUpdateProfileReq = async (token, user, attributes ) => { - axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; - axios.defaults.headers.common['Content-Type'] = 'application/json'; - return await axios.put(`auth/admin/realms/opex/users/${user}`,{ - attributes , - }).then((res) => { - return res; - }).catch((e) => { - if (!e.response) { - return false; - } - return e.response; - }) -} - - - -export const addToKycGroup = async (token, userId) => { - axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; - axios.defaults.headers.common['Content-Type'] = 'application/json'; - return await axios.put(`/auth/admin/realms/opex/users/${userId}/groups/24200655-dfef-4ed0-a8b8-925918793552`) - .then((res) => { - return res; - }).catch((e) => { - if (!e.response) { - return false; - } - return e.response; - }) -}; - diff --git a/src/main/Mobile/Pages/Login/components/EmailVerification/EmailVerification.js b/src/main/Mobile/Pages/Login/components/EmailVerification/EmailVerification.js new file mode 100644 index 0000000..87e68e2 --- /dev/null +++ b/src/main/Mobile/Pages/Login/components/EmailVerification/EmailVerification.js @@ -0,0 +1,212 @@ +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, requestForVerifyEmail} from "js-api-client"; +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"; +import {Buffer} from 'buffer'; + + +const EmailVerification = ({returnFunc, email, disable, returnFuncDisableFalse, returnFuncDisableTrue}) => { + + 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}}) + }, []) + + + + + 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 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") { + 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 {t('captchaAnswer')}/ + } + + 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