feat(#175): access to platform without authorization
parent
e3f4f737b2
commit
20d5c3eae4
@ -0,0 +1,124 @@ |
|||||||
|
import { useEffect, useState } from 'react' |
||||||
|
|
||||||
|
import { T9n } from 'features/T9n' |
||||||
|
import { Icon } from 'features/Icon' |
||||||
|
import { useAuthStore } from 'features/AuthStore' |
||||||
|
|
||||||
|
import { |
||||||
|
AccessTimerContainer, |
||||||
|
PreviewInfo, |
||||||
|
SignInBtn, |
||||||
|
SignText, |
||||||
|
Timer, |
||||||
|
TimerContainer, |
||||||
|
} from './styled' |
||||||
|
|
||||||
|
import { SimplePopup } from '../SimplePopup' |
||||||
|
import { secondsToHms } from '../../helpers' |
||||||
|
import { getViewMatchDuration } from '../../requests/getViewMatchDuration' |
||||||
|
import { usePageParams } from '../../hooks' |
||||||
|
import { getUserInfo } from '../../requests' |
||||||
|
|
||||||
|
type AccessTimerType = { |
||||||
|
access: boolean, |
||||||
|
isFullscreen: boolean, |
||||||
|
onFullscreenClick: () => void, |
||||||
|
onPause: () => void, |
||||||
|
playing: boolean, |
||||||
|
} |
||||||
|
|
||||||
|
const ACCESS_TIME = 60 |
||||||
|
|
||||||
|
export const AccessTimer = ({ |
||||||
|
access, |
||||||
|
isFullscreen, |
||||||
|
onFullscreenClick, |
||||||
|
onPause, |
||||||
|
playing, |
||||||
|
}: AccessTimerType) => { |
||||||
|
const { |
||||||
|
logout, |
||||||
|
setUserInfo, |
||||||
|
userInfo, |
||||||
|
} = useAuthStore() |
||||||
|
|
||||||
|
const { profileId, sportType } = usePageParams() |
||||||
|
|
||||||
|
const [timeIsFinished, setTimeIsFinished] = useState(true) |
||||||
|
const [time, setTime] = useState(ACCESS_TIME) |
||||||
|
const isTimeExpired = time <= 0 && !access |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (isTimeExpired) { |
||||||
|
document.pictureInPictureEnabled && document.exitPictureInPicture() |
||||||
|
setTimeIsFinished(true) |
||||||
|
onPause() |
||||||
|
setTime(0) |
||||||
|
if (isFullscreen) onFullscreenClick() |
||||||
|
} |
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [access, playing, profileId]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
let stopWatch: ReturnType<typeof setInterval> | null = null |
||||||
|
if (playing) { |
||||||
|
stopWatch = setInterval(() => { |
||||||
|
setTime((prev) => prev - 1) |
||||||
|
}, 1000) |
||||||
|
} else { |
||||||
|
stopWatch && clearInterval(stopWatch) |
||||||
|
} |
||||||
|
return () => { |
||||||
|
stopWatch && clearInterval(stopWatch) |
||||||
|
} |
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [playing, profileId]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
(async () => { |
||||||
|
setTime(ACCESS_TIME) |
||||||
|
const updatedUserInfo = await getUserInfo() |
||||||
|
setUserInfo(updatedUserInfo) |
||||||
|
const timeViewed = await getViewMatchDuration({ |
||||||
|
matchId: profileId, |
||||||
|
sportType, |
||||||
|
userId: Number(updatedUserInfo?.email), |
||||||
|
}) |
||||||
|
setTime(isTimeExpired ? 0 : (ACCESS_TIME - Number(timeViewed.duration))) |
||||||
|
})() |
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [profileId]) |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
{(!userInfo || Number(userInfo?.email) < 0) && access && time > 0 |
||||||
|
? ( |
||||||
|
<AccessTimerContainer isFullscreen={isFullscreen}> |
||||||
|
<TimerContainer> |
||||||
|
<PreviewInfo> |
||||||
|
<Timer>{secondsToHms(time)} </Timer> |
||||||
|
<T9n t='game_preview' className='gamePreview' /> |
||||||
|
</PreviewInfo> |
||||||
|
<SignText> |
||||||
|
<T9n t='sign_in_full_game' /> |
||||||
|
</SignText> |
||||||
|
</TimerContainer> |
||||||
|
<SignInBtn onClick={() => logout('saveToken')}> |
||||||
|
<T9n t='sign_in' /> |
||||||
|
</SignInBtn> |
||||||
|
</AccessTimerContainer> |
||||||
|
) |
||||||
|
: ( |
||||||
|
<SimplePopup |
||||||
|
isModalOpen={timeIsFinished} |
||||||
|
withCloseButton={false} |
||||||
|
headerName='sec_60' |
||||||
|
mainText='continue_watching' |
||||||
|
buttonName='sign_in' |
||||||
|
onHandle={() => logout('saveToken')} |
||||||
|
icon={<Icon refIcon='ExclamationPoint' />} |
||||||
|
/> |
||||||
|
)} |
||||||
|
</> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,124 @@ |
|||||||
|
import styled, { css } from 'styled-components/macro' |
||||||
|
|
||||||
|
import { isMobileDevice } from 'config/userAgent' |
||||||
|
|
||||||
|
import { ButtonSolid } from 'features/Common' |
||||||
|
|
||||||
|
export const AccessTimerContainer = styled.div<{isFullscreen?: boolean}>` |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
position: absolute; |
||||||
|
left: 50%; |
||||||
|
bottom: 5.7rem; |
||||||
|
transform: translateX(-50%); |
||||||
|
background-color: #333333; |
||||||
|
border-radius: 5px; |
||||||
|
padding: 20px 50px; |
||||||
|
|
||||||
|
gap: 40px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
padding: 9px 7px 9px 15px; |
||||||
|
bottom: 6.5rem; |
||||||
|
|
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
max-width: 95%; |
||||||
|
bottom: 4rem; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
|
||||||
|
${({ isFullscreen }) => isFullscreen && css` |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
padding: 9px 7px 9px 15px; |
||||||
|
bottom: 12rem; |
||||||
|
|
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
max-width: 95%; |
||||||
|
bottom: 6rem; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
`}
|
||||||
|
` |
||||||
|
|
||||||
|
export const SignInBtn = styled(ButtonSolid)` |
||||||
|
width: 246px; |
||||||
|
border-radius: 5px; |
||||||
|
height: 50px; |
||||||
|
font-weight: 600; |
||||||
|
font-size: 20px; |
||||||
|
white-space: nowrap; |
||||||
|
padding: 0 35px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 12px; |
||||||
|
white-space: nowrap; |
||||||
|
padding: 0px 16px; |
||||||
|
width: 140px; |
||||||
|
height: 30px; |
||||||
|
|
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
font-size: 12px; |
||||||
|
padding: 0px 8px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
|
||||||
|
` |
||||||
|
|
||||||
|
export const TimerContainer = styled.div` |
||||||
|
color: white; |
||||||
|
font-weight: 700; |
||||||
|
font-size: 20px; |
||||||
|
line-height: 24px; |
||||||
|
white-space: nowrap; |
||||||
|
max-width: 215px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 12px; |
||||||
|
line-height: 28px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const PreviewInfo = styled.div` |
||||||
|
display: flex; |
||||||
|
line-height: 24px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
line-height: 14px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const Timer = styled.div` |
||||||
|
min-width: 67px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
min-width: 40px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const SignText = styled.span` |
||||||
|
display: block; |
||||||
|
font-size: 16px; |
||||||
|
line-height: 20px; |
||||||
|
font-weight: 400; |
||||||
|
white-space: break-spaces; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 9px; |
||||||
|
line-height: 14px |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
@ -0,0 +1,65 @@ |
|||||||
|
import { ReactNode } from 'react' |
||||||
|
|
||||||
|
import { T9n } from 'features/T9n' |
||||||
|
|
||||||
|
import { |
||||||
|
Header, |
||||||
|
Footer, |
||||||
|
ScBody, |
||||||
|
Modal, |
||||||
|
ScApplyButton, |
||||||
|
ScHeaderTitle, |
||||||
|
ScText, |
||||||
|
Wrapper, |
||||||
|
} from './styled' |
||||||
|
|
||||||
|
type Props = { |
||||||
|
buttonName?: string, |
||||||
|
headerName?: string, |
||||||
|
icon?: ReactNode, |
||||||
|
isModalOpen: boolean, |
||||||
|
mainText: string, |
||||||
|
onHandle?: () => void, |
||||||
|
withCloseButton: boolean, |
||||||
|
} |
||||||
|
|
||||||
|
export const SimplePopup = (props: Props) => { |
||||||
|
const { |
||||||
|
buttonName, |
||||||
|
headerName, |
||||||
|
icon, |
||||||
|
isModalOpen, |
||||||
|
mainText, |
||||||
|
onHandle, |
||||||
|
withCloseButton, |
||||||
|
} = props |
||||||
|
|
||||||
|
return ( |
||||||
|
<Modal |
||||||
|
isOpen={isModalOpen} |
||||||
|
withCloseButton={withCloseButton} |
||||||
|
> |
||||||
|
<Wrapper> |
||||||
|
<Header> |
||||||
|
{icon} |
||||||
|
<ScHeaderTitle> |
||||||
|
<T9n t={headerName ?? ''} /> |
||||||
|
</ScHeaderTitle> |
||||||
|
</Header> |
||||||
|
<ScBody> |
||||||
|
<ScText> |
||||||
|
<T9n t={mainText} /> |
||||||
|
</ScText> |
||||||
|
</ScBody> |
||||||
|
{buttonName |
||||||
|
&& ( |
||||||
|
<Footer> |
||||||
|
<ScApplyButton onClick={onHandle}> |
||||||
|
<T9n t={buttonName} /> |
||||||
|
</ScApplyButton> |
||||||
|
</Footer> |
||||||
|
)} |
||||||
|
</Wrapper> |
||||||
|
</Modal> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,150 @@ |
|||||||
|
import styled, { css } from 'styled-components/macro' |
||||||
|
|
||||||
|
import { isMobileDevice } from 'config/userAgent' |
||||||
|
|
||||||
|
import { ModalWindow } from 'features/Modal/styled' |
||||||
|
import { |
||||||
|
ApplyButton, |
||||||
|
Body, |
||||||
|
Modal as BaseModal, |
||||||
|
HeaderTitle, |
||||||
|
} from 'features/AuthServiceApp/components/RegisterPopup/styled' |
||||||
|
|
||||||
|
import { client } from 'features/AuthServiceApp/config/clients/index' |
||||||
|
import { Header as BaseHeader } from '../../features/PopupComponents' |
||||||
|
|
||||||
|
export const Modal = styled(BaseModal)` |
||||||
|
|
||||||
|
|
||||||
|
${ModalWindow} { |
||||||
|
width: 705px; |
||||||
|
height: 472px; |
||||||
|
|
||||||
|
padding: 60px 100px; |
||||||
|
min-height: auto; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
max-width: 95vw; |
||||||
|
padding: 50px 20px 80px 20px; |
||||||
|
|
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
max-height: 80vh; |
||||||
|
max-width: 95vw; |
||||||
|
padding: 24px 100px 60px 100px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
export const Wrapper = styled.div` |
||||||
|
gap: 30px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
max-height: 80vh; |
||||||
|
gap: 10px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScHeaderTitle = styled(HeaderTitle)` |
||||||
|
text-align: center; |
||||||
|
font-weight: 700; |
||||||
|
font-size: 34px; |
||||||
|
line-height: 34px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 20px; |
||||||
|
line-height: 24px; |
||||||
|
|
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
font-size: 17px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScBody = styled(Body)` |
||||||
|
padding: 16px 0 0 0; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
max-width: 491px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScText = styled.span` |
||||||
|
text-align: center; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 14px; |
||||||
|
line-height: 22px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScApplyButton = styled(ApplyButton)` |
||||||
|
width: 300px; |
||||||
|
height: 60px; |
||||||
|
margin: 0; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 14px; |
||||||
|
margin: 20px 0; |
||||||
|
width: 293px; |
||||||
|
height: 50px; |
||||||
|
|
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
margin: 0; |
||||||
|
width: 225px; |
||||||
|
height: 44px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
${client.styles.popupApplyButton} |
||||||
|
` |
||||||
|
|
||||||
|
export const Header = styled(BaseHeader)` |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
height: auto; |
||||||
|
justify-content: center; |
||||||
|
gap: 30px; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
gap: 10px; |
||||||
|
|
||||||
|
svg { |
||||||
|
width: 40px; |
||||||
|
height: 40px; |
||||||
|
} |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const Footer = styled.div` |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
padding: 33px 0 65px 0; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
padding-top: 30px; |
||||||
|
|
||||||
|
@media screen and (orientation: landscape) { |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
export const ExclamationPoint = (): JSX.Element => ( |
||||||
|
<svg |
||||||
|
xmlns='http://www.w3.org/2000/svg' |
||||||
|
width='88' |
||||||
|
height='88' |
||||||
|
fill='none' |
||||||
|
viewBox='0 0 88 88' |
||||||
|
> |
||||||
|
<circle cx='44' cy='44' r='44' fill='#fff' /> |
||||||
|
<rect |
||||||
|
width='3' |
||||||
|
height='30' |
||||||
|
x='45' |
||||||
|
y='56' |
||||||
|
fill='#000' |
||||||
|
rx='1.5' |
||||||
|
transform='rotate(-180 45 56)' |
||||||
|
/> |
||||||
|
<rect |
||||||
|
width='3' |
||||||
|
height='30' |
||||||
|
x='45' |
||||||
|
y='56' |
||||||
|
fill='#000' |
||||||
|
rx='1.5' |
||||||
|
transform='rotate(-180 45 56)' |
||||||
|
/> |
||||||
|
<circle cx='43.5' cy='60.5' r='2.5' fill='#000' /> |
||||||
|
</svg> |
||||||
|
) |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
import { AUTH_SERVICE } from '../config/routes' |
||||||
|
import { client } from '../config/clients' |
||||||
|
|
||||||
|
export const getTokenVirtualUser = async () => { |
||||||
|
const url = `${AUTH_SERVICE}/v1/user/create?client_id=${client.auth.clientId}` |
||||||
|
|
||||||
|
const config = { |
||||||
|
method: 'POST', |
||||||
|
} |
||||||
|
|
||||||
|
const response = await fetch(url, config) |
||||||
|
|
||||||
|
const body = await response.json() |
||||||
|
|
||||||
|
return body |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
import { |
||||||
|
DATA_URL, |
||||||
|
PROCEDURES, |
||||||
|
SportTypes, |
||||||
|
} from 'config' |
||||||
|
|
||||||
|
import { callApi } from 'helpers' |
||||||
|
|
||||||
|
const proc = PROCEDURES.get_view_user_match |
||||||
|
|
||||||
|
type ResponseType = { |
||||||
|
duration?: number, |
||||||
|
error?: string, |
||||||
|
status: 1 | 2, |
||||||
|
} |
||||||
|
|
||||||
|
type ViewMatchDurationType = { |
||||||
|
matchId: number, |
||||||
|
sportType: SportTypes, |
||||||
|
userId: number, |
||||||
|
} |
||||||
|
|
||||||
|
export const getViewMatchDuration = ({ |
||||||
|
matchId, |
||||||
|
sportType, |
||||||
|
userId, |
||||||
|
}: ViewMatchDurationType): Promise<ResponseType> => { |
||||||
|
const config = { |
||||||
|
body: { |
||||||
|
params: { |
||||||
|
_p_match_id: matchId, |
||||||
|
_p_sport_id: sportType, |
||||||
|
_p_user_id: userId, |
||||||
|
}, |
||||||
|
proc, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return callApi({ |
||||||
|
config, |
||||||
|
url: DATA_URL, |
||||||
|
}) |
||||||
|
} |
||||||
Loading…
Reference in new issue