From a72041e807eefe574748a31267e3d6698d929ee9 Mon Sep 17 00:00:00 2001 From: Zoia R Date: Fri, 12 Aug 2022 06:53:25 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20ott-2614-newDevice-Warni?= =?UTF-8?q?ngPopup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/images/exclamationInCircle.svg | 6 + src/config/lexics/indexLexics.tsx | 8 ++ src/features/App/AuthenticatedApp.tsx | 2 + .../components/ChangePassword/hooks.tsx | 2 +- src/features/AuthStore/hooks/useAuth.tsx | 33 +++++- .../components/Notification/index.tsx | 23 ++++ src/features/NewDevicePopup/hooks/index.tsx | 23 ++++ src/features/NewDevicePopup/index.tsx | 32 ++++++ src/features/NewDevicePopup/styled.tsx | 107 ++++++++++++++++++ src/requests/checkDevice.tsx | 26 +++++ 10 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 public/images/exclamationInCircle.svg create mode 100644 src/features/NewDevicePopup/components/Notification/index.tsx create mode 100644 src/features/NewDevicePopup/hooks/index.tsx create mode 100644 src/features/NewDevicePopup/index.tsx create mode 100644 src/features/NewDevicePopup/styled.tsx create mode 100644 src/requests/checkDevice.tsx diff --git a/public/images/exclamationInCircle.svg b/public/images/exclamationInCircle.svg new file mode 100644 index 00000000..68bba8bf --- /dev/null +++ b/public/images/exclamationInCircle.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx index 06dad8e7..f58b3483 100644 --- a/src/config/lexics/indexLexics.tsx +++ b/src/config/lexics/indexLexics.tsx @@ -91,6 +91,13 @@ const sportsPopup = { hockey_popup: 15217, } +const newDevicePopup = { + log_with_one_device: 19599, + logged_another_device: 19597, + logged_out_soon: 19596, + ok: 724, +} + export const indexLexics = { add_to_favorites: 14967, add_to_favorites_error: 12943, @@ -161,6 +168,7 @@ export const indexLexics = { ...preferencesPopupLexics, ...proceduresLexics, ...matchPopupLexics, + ...newDevicePopup, ...buyMatchPopupLexics, ...publicLexics, ...paymentLexics, diff --git a/src/features/App/AuthenticatedApp.tsx b/src/features/App/AuthenticatedApp.tsx index 26ba2ccb..b25f8484 100644 --- a/src/features/App/AuthenticatedApp.tsx +++ b/src/features/App/AuthenticatedApp.tsx @@ -27,6 +27,7 @@ import { TournamentsPopup } from 'features/TournamentsPopup' import { TournamentPopupStore } from 'features/TournamentsPopup/store' import { CardsStore } from 'features/CardsStore' import { NoNetworkPopup, NoNetworkPopupStore } from 'features/NoNetworkPopup' +import { NewDevicePopup } from 'features/NewDevicePopup' const HomePage = lazy(() => import('features/HomePage')) const UserAccount = lazy(() => import('features/UserAccount')) @@ -43,6 +44,7 @@ export const AuthenticatedApp = () => { useLexicsConfig(indexLexics) return ( + diff --git a/src/features/AuthServiceApp/components/ChangePassword/hooks.tsx b/src/features/AuthServiceApp/components/ChangePassword/hooks.tsx index ae9525e5..7201d339 100644 --- a/src/features/AuthServiceApp/components/ChangePassword/hooks.tsx +++ b/src/features/AuthServiceApp/components/ChangePassword/hooks.tsx @@ -29,7 +29,7 @@ export const useChangePasswordForm = () => { await changePassword(client_id, password) setError('') setIsFetching(false) - history.push(`/autorize?client_id=${client_id}`) + history.push(`/authorize?client_id=${client_id}`) } catch (err) { setError(String(err)) setIsFetching(false) diff --git a/src/features/AuthStore/hooks/useAuth.tsx b/src/features/AuthStore/hooks/useAuth.tsx index 24801bdd..2d7d0cb2 100644 --- a/src/features/AuthStore/hooks/useAuth.tsx +++ b/src/features/AuthStore/hooks/useAuth.tsx @@ -16,7 +16,11 @@ import { PAGES } from 'config' import { addLanguageUrlParam, } from 'helpers/languageUrlParam' -import { writeToken, removeToken } from 'helpers/token' +import { + writeToken, + removeToken, + readToken, +} from 'helpers/token' import { setCookie, removeCookie } from 'helpers/cookie' import { isMatchPage } from 'helpers/isMatchPage' @@ -27,7 +31,8 @@ import { queryParamStorage, } from 'features/QueryParamsStorage' -import { getUserInfo } from 'requests' +import { getUserInfo } from 'requests/getUserInfo' +import { checkDevice, FailedResponse } from 'requests/checkDevice' import { getClientSettings } from '../helpers' @@ -39,6 +44,7 @@ export const useAuth = () => { isOpen: loadingUser, } = useToggle(true) const [user, setUser] = useState() + const [isNewDeviceLogin, setIsNewDeviceLogin] = useState(false) const userManager = useMemo(() => new UserManager(getClientSettings()), []) const login = useCallback(async () => ( @@ -142,6 +148,27 @@ export const useAuth = () => { history, ]) + const checkNewDevice = useCallback(async () => { + const accessToken = readToken() + if (!accessToken) return + + await checkDevice(accessToken).catch((er:FailedResponse) => { + if (er.error) { + logout() + return + } + userManager.clearStaleState() + setIsNewDeviceLogin(true) + setTimeout(logout, 10000) + }) + }, [logout, userManager]) + + useEffect(() => { + const startCheckDevice = setInterval(checkNewDevice, 20000) + isNewDeviceLogin && clearInterval(startCheckDevice) + return () => clearInterval(startCheckDevice) + }, [checkNewDevice, isNewDeviceLogin, setIsNewDeviceLogin]) + useEffect(() => { // попытаемся обновить токен используя refresh_token const tryRenewToken = () => { @@ -177,11 +204,13 @@ export const useAuth = () => { }, [fetchUserInfo, user]) const auth = useMemo(() => ({ + isNewDeviceLogin, loadingUser, login, logout, user, }), [ + isNewDeviceLogin, logout, user, login, diff --git a/src/features/NewDevicePopup/components/Notification/index.tsx b/src/features/NewDevicePopup/components/Notification/index.tsx new file mode 100644 index 00000000..6895f64f --- /dev/null +++ b/src/features/NewDevicePopup/components/Notification/index.tsx @@ -0,0 +1,23 @@ +import { T9n } from 'features/T9n' +import { + NotificationWrapper, + Title, + Text, + ExclamationInCircle, +} from '../../styled' + +export const Notification = () => ( + + + + <T9n t='logged_out_soon' /> + + + + + + + + + +) diff --git a/src/features/NewDevicePopup/hooks/index.tsx b/src/features/NewDevicePopup/hooks/index.tsx new file mode 100644 index 00000000..1d6d6543 --- /dev/null +++ b/src/features/NewDevicePopup/hooks/index.tsx @@ -0,0 +1,23 @@ +import { useAuthStore } from 'features/AuthStore' +import { useEffect, useState } from 'react' + +export const useNewDevicePopup = () => { + const { isNewDeviceLogin } = useAuthStore() + + const [isOpen, setIsOpen] = useState(isNewDeviceLogin) + + useEffect(() => { + setIsOpen(isNewDeviceLogin) + }, [isNewDeviceLogin]) + + const close = () => { + setIsOpen(false) + } + + return { + close, + isOpen, + setIsOpen, + + } +} diff --git a/src/features/NewDevicePopup/index.tsx b/src/features/NewDevicePopup/index.tsx new file mode 100644 index 00000000..9d147e16 --- /dev/null +++ b/src/features/NewDevicePopup/index.tsx @@ -0,0 +1,32 @@ +import { T9n } from 'features/T9n' + +import { Notification } from './components/Notification' +import { useNewDevicePopup } from './hooks' + +import { + Button, + Modal, + Wrapper, +} from './styled' + +export const NewDevicePopup = () => { + const { + close, + isOpen, + } = useNewDevicePopup() + + return ( + + + + + + + ) +} diff --git a/src/features/NewDevicePopup/styled.tsx b/src/features/NewDevicePopup/styled.tsx new file mode 100644 index 00000000..5e16b0a5 --- /dev/null +++ b/src/features/NewDevicePopup/styled.tsx @@ -0,0 +1,107 @@ +import styled, { css } from 'styled-components/macro' + +import { isMobileDevice } from 'config/userAgent' +import { Modal as BaseModal } from 'features/Modal' +import { ButtonSolid } from 'features/Common/Button' +import { ModalWindow } from 'features/Modal/styled' + +export const Modal = styled(BaseModal)` + background-color: rgba(0, 0, 0, 0.7); + text-align: center; + + ${ModalWindow} { + border-radius: 4px; + padding: 60px 54px 50px; + max-width: 700px; + + ${isMobileDevice + ? css` + width: calc(100vw - 24px); + padding: 38px 20px 24px; + @media screen and (orientation: landscape){ + padding: 20px; + } + ` + : ''}; + } +` + +export const Wrapper = styled.div`` + +export const NotificationWrapper = styled.div` + width: 100%; +` + +export const Title = styled.div` + font-style: normal; + font-size: 24px; + font-weight: 700; + margin-bottom: 30px; + ${isMobileDevice + ? css` + font-size: 20px; + @media screen and (orientation: landscape){ + font-size: 17px; + margin-bottom: 20px; + } + } + ` + : ''}; +` + +export const Text = styled.p` + font-style: normal; + font-weight: normal; + font-size: 20px; + text-align: center; + margin-bottom: 30px; + ${isMobileDevice + ? css` + font-size: 14px; + line-height: 1.5; + margin-bottom: 20px; + ` + : ''}; +` + +export const Button = styled(ButtonSolid)` + min-width: 134px; + width: auto; + height: 50px; + padding: 0 20px; + background-color: ${({ theme: { colors } }) => colors.button}; + color: ${({ theme: { colors } }) => colors.white}; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3); + border-radius: 5px; + font-weight: 600; + font-size: 20px; + ${isMobileDevice + ? css` + height: 50px; + width: 100%; + font-size: 12px; + @media screen and (orientation: landscape){ + max-width: 290px; + } + ` + : ''}; + + +` + +export const ExclamationInCircle = styled.div` + width: 88px; + height: 88px; + background: url(/images/exclamationInCircle.svg) no-repeat; + background-size: cover; + margin: 0 auto 35px; + ${isMobileDevice + ? css` + @media screen and (orientation: landscape){ + width: 40px; + height: 40px; + margin: 0 auto 15px; + } + ` + : ''}; +` diff --git a/src/requests/checkDevice.tsx b/src/requests/checkDevice.tsx new file mode 100644 index 00000000..905cac6a --- /dev/null +++ b/src/requests/checkDevice.tsx @@ -0,0 +1,26 @@ +import { AUTH_SERVICE } from '../config/routes' + +export type FailedResponse = { + error?: string, + ok: false, +} + +export type SuccessResponse = { + ok: true, +} + +export const checkDevice = async (token: string) => { + const url = `${AUTH_SERVICE}/authorize/check-device?access_token=${token}` + + const config = { + method: 'GET', + } + + const response = await fetch(url, config) + + const body: SuccessResponse | FailedResponse = await response.json() + + if (body.ok) return Promise.resolve() + + return Promise.reject(body) +}