feat: 🎸 ott-2614-newDevice-WarningPopup

keep-around/fdb88b04b32b9392e76795099e2ec47c9856b38b
Zoia R 3 years ago
parent 45dd18d39e
commit a72041e807
  1. 6
      public/images/exclamationInCircle.svg
  2. 8
      src/config/lexics/indexLexics.tsx
  3. 2
      src/features/App/AuthenticatedApp.tsx
  4. 2
      src/features/AuthServiceApp/components/ChangePassword/hooks.tsx
  5. 33
      src/features/AuthStore/hooks/useAuth.tsx
  6. 23
      src/features/NewDevicePopup/components/Notification/index.tsx
  7. 23
      src/features/NewDevicePopup/hooks/index.tsx
  8. 32
      src/features/NewDevicePopup/index.tsx
  9. 107
      src/features/NewDevicePopup/styled.tsx
  10. 26
      src/requests/checkDevice.tsx

@ -0,0 +1,6 @@
<svg width="88" height="88" viewBox="0 0 88 88" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="44" cy="44" r="44" fill="white"/>
<rect x="45" y="56" width="3" height="30" rx="1.5" transform="rotate(-180 45 56)" fill="black"/>
<rect x="45" y="56" width="3" height="30" rx="1.5" transform="rotate(-180 45 56)" fill="black"/>
<circle cx="43.5" cy="60.5" r="2.5" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 394 B

@ -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,

@ -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 (
<StripeElements>
<NewDevicePopup />
<RecoilRoot>
<CardsStore>
<MatchSwitchesStore>

@ -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)

@ -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<User>()
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,

@ -0,0 +1,23 @@
import { T9n } from 'features/T9n'
import {
NotificationWrapper,
Title,
Text,
ExclamationInCircle,
} from '../../styled'
export const Notification = () => (
<NotificationWrapper>
<ExclamationInCircle />
<Title>
<T9n t='logged_out_soon' />
</Title>
<Text>
<T9n t='logged_another_device' />
</Text>
<Text>
<T9n t='log_with_one_device' />
</Text>
</NotificationWrapper>
)

@ -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,
}
}

@ -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 (
<Modal
isOpen={isOpen}
close={close}
withCloseButton={false}
>
<Wrapper>
<Notification />
<Button onClick={close}>
<T9n t='ok' />
</Button>
</Wrapper>
</Modal>
)
}

@ -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;
}
`
: ''};
`

@ -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)
}
Loading…
Cancel
Save