develop #44

Merged
andrey.dekterev merged 9 commits from develop into master 3 years ago
  1. 73
      .drone.yml
  2. 128
      src/components/AccessTimer/index.tsx
  3. 124
      src/components/AccessTimer/styled.tsx
  4. 1
      src/components/ItemInfo/ItemInfo.tsx
  5. 5
      src/components/PictureInPicture/PiP.tsx
  6. 65
      src/components/SimplePopup/index.tsx
  7. 146
      src/components/SimplePopup/styled.tsx
  8. 8
      src/config/lexics/indexLexics.tsx
  9. 1
      src/config/procedures.tsx
  10. 36
      src/features/App/index.tsx
  11. 5
      src/features/AuthServiceApp/components/Login/hooks.tsx
  12. 5
      src/features/AuthServiceApp/components/Oauth/hooks.tsx
  13. 8
      src/features/AuthServiceApp/components/RegisterPopup/styled.tsx
  14. 9
      src/features/AuthServiceApp/requests/register.tsx
  15. 78
      src/features/AuthStore/hooks/useAuth.tsx
  16. 2
      src/features/FavoritesMobilePopup/components/GroupBlock/index.tsx
  17. 26
      src/features/HomePage/hooks.tsx
  18. 2
      src/features/ItemsList/index.tsx
  19. 3
      src/features/MatchCard/hooks.tsx
  20. 9
      src/features/MatchPage/components/FinishedMatch/index.tsx
  21. 9
      src/features/MatchPage/components/SubscriptionGuard/index.tsx
  22. 10
      src/features/MatchPage/index.tsx
  23. 88
      src/features/MatchPage/store/hooks/index.tsx
  24. 66
      src/features/MatchPage/store/hooks/useFitersPopup.tsx
  25. 88
      src/features/MatchSidePlaylists/components/FiltersPopup/index.tsx
  26. 57
      src/features/MatchSidePlaylists/components/FiltersPopup/styled.tsx
  27. 21
      src/features/Matches/helpers/getMatchClickAction/index.tsx
  28. 10
      src/features/Matches/helpers/prepareMatches.tsx
  29. 10
      src/features/Matches/hooks.tsx
  30. 13
      src/features/Menu/index.tsx
  31. 2
      src/features/Modal/styled.tsx
  32. 24
      src/features/MultiSourcePlayer/hooks/index.tsx
  33. 20
      src/features/MultiSourcePlayer/index.tsx
  34. 4
      src/features/PopupComponents/BaseButton/index.tsx
  35. 2
      src/features/PreferencesPopup/components/TournamentInfo/index.tsx
  36. 2
      src/features/ProfileCard/index.tsx
  37. 4
      src/features/StreamPlayer/components/YoutubePlayer/index.tsx
  38. 19
      src/features/StreamPlayer/hooks/index.tsx
  39. 12
      src/features/StreamPlayer/hooks/useVideoQuality.tsx
  40. 19
      src/features/StreamPlayer/index.tsx
  41. 11
      src/features/StreamPlayer/styled.tsx
  42. 4
      src/features/SystemSettings/hooks.tsx
  43. 2
      src/features/TournamentList/components/CollapseTournament/index.tsx
  44. 2
      src/features/TournamentList/components/TournamentMobile/index.tsx
  45. 4
      src/features/TournamentPage/hooks.tsx
  46. 3
      src/features/TournamentPage/index.tsx
  47. 2
      src/features/TournamentSubtitle/index.tsx
  48. 2
      src/features/UserAccount/components/LogoutButton/index.tsx
  49. 2
      src/features/UserFavorites/TooltipBlock/index.tsx
  50. 5
      src/helpers/callApi/index.tsx
  51. 7
      src/helpers/cookie/index.tsx
  52. 8
      src/helpers/languageUrlParam/index.tsx
  53. 1
      src/libs/index.ts
  54. 30
      src/libs/objects/ExclamationPoint.tsx
  55. 2
      src/pages/HighlightsPage/components/MatchesHighlights/index.tsx
  56. 3
      src/requests/getMatches/getHomeMatches.tsx
  57. 13
      src/requests/getTokenVirtualUser.tsx
  58. 43
      src/requests/getViewMatchDuration.tsx
  59. 1
      src/requests/index.tsx

@ -73,7 +73,7 @@ steps:
from_secret: AWS_DEFAULT_REGION from_secret: AWS_DEFAULT_REGION
AWS_MAX_ATTEMPTS: 10 AWS_MAX_ATTEMPTS: 10
commands: commands:
- aws s3 sync build s3://insports-auth --delete - aws s3 sync build_auth s3://insports-auth --delete
- aws cloudfront create-invalidation --distribution-id EERIKX9X2SRPJ --paths "/*" - aws cloudfront create-invalidation --distribution-id EERIKX9X2SRPJ --paths "/*"
depends_on: depends_on:
- make-auth - make-auth
@ -88,6 +88,8 @@ steps:
- eval $(ssh-agent -s) - eval $(ssh-agent -s)
- echo -n "$SSH_KEY_AUTH" | tr -d '\r' | ssh-add - - echo -n "$SSH_KEY_AUTH" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh - mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan auth.insports.tv >> ~/.ssh/known_hosts
- rsync -v -r -C build_auth/ ubuntu@auth.insports.tv:/home/ubuntu/ott-auth/src/frontend/
depends_on: depends_on:
- make-auth - make-auth
@ -681,3 +683,72 @@ steps:
- rsync -v -r -C build_auth/clients/* ubuntu@auth.test.insports.tv:/home/ubuntu/ott-auth/src/frontend/templates - rsync -v -r -C build_auth/clients/* ubuntu@auth.test.insports.tv:/home/ubuntu/ott-auth/src/frontend/templates
- aws s3 sync build_auth s3://auth-insports-test --delete - aws s3 sync build_auth s3://auth-insports-test --delete
- aws cloudfront create-invalidation --distribution-id E10YI3RFOZZDLZ --paths "/*" - aws cloudfront create-invalidation --distribution-id E10YI3RFOZZDLZ --paths "/*"
---
kind: pipeline
type: docker
name: deploy auth prod
concurrency:
limit: 1
platform:
os: linux
arch: amd64
trigger:
ref:
- refs/heads/auth
steps:
- name: npm-install
image: node:16-alpine
environment:
REACT_APP_STRIPE_PK:
from_secret: REACT_APP_STRIPE_PK
commands:
- apk add --no-cache make
- npm install --legacy-peer-deps
- name: make-auth
image: node:16-alpine
environment:
REACT_APP_STRIPE_PK:
from_secret: REACT_APP_STRIPE_PK
commands:
- apk add --no-cache make
- make auth-production-build
depends_on:
- npm-install
- name: deploy-S3-auth
image: amazon/aws-cli:latest
environment:
AWS_ACCESS_KEY_ID:
from_secret: AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
from_secret: AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION:
from_secret: AWS_DEFAULT_REGION
AWS_MAX_ATTEMPTS: 10
commands:
- aws s3 sync build_auth s3://insports-auth --delete
- aws cloudfront create-invalidation --distribution-id EERIKX9X2SRPJ --paths "/*"
depends_on:
- make-auth
- name: deploy-old-auth-server
image: node:16-alpine
environment:
SSH_KEY_AUTH:
from_secret: SSH_KEY_AUTH
commands:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo -n "$SSH_KEY_AUTH" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan auth.insports.tv >> ~/.ssh/known_hosts
- rsync -v -r -C build_auth/ ubuntu@auth.insports.tv:/home/ubuntu/ott-auth/src/frontend/
depends_on:
- make-auth

@ -0,0 +1,128 @@
import {
useEffect,
useState,
RefObject,
} 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,
playing: boolean,
videoRef?: RefObject<HTMLVideoElement> | null,
}
const ACCESS_TIME = 60
export const AccessTimer = ({
access,
isFullscreen,
onFullscreenClick,
playing,
videoRef,
}: 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)
videoRef?.current?.pause()
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)}&nbsp;</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'
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
`
: ''};
`

@ -13,7 +13,6 @@ type ItemInfoType = {
onClick: (val: any) => void, onClick: (val: any) => void,
type: ProfileTypes, type: ProfileTypes,
} }
export const ItemInfo = ({ export const ItemInfo = ({
active, active,
id, id,

@ -7,6 +7,7 @@ import {
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { Icon } from 'features/Icon' import { Icon } from 'features/Icon'
import { useAuthStore } from 'features/AuthStore'
const PipWrapper = styled.div` const PipWrapper = styled.div`
cursor: pointer; cursor: pointer;
@ -21,6 +22,7 @@ type PipProps = {
} }
export const PiP = memo(({ isPlaying, videoRef }: PipProps) => { export const PiP = memo(({ isPlaying, videoRef }: PipProps) => {
const { user } = useAuthStore()
const togglePip = async () => { const togglePip = async () => {
try { try {
if ( if (
@ -43,11 +45,12 @@ export const PiP = memo(({ isPlaying, videoRef }: PipProps) => {
&& videoRef.current !== document.pictureInPictureElement && videoRef.current !== document.pictureInPictureElement
&& videoRef.current?.hidden === false && videoRef.current?.hidden === false
&& isPlaying && isPlaying
&& user
) { ) {
await videoRef.current?.requestPictureInPicture() await videoRef.current?.requestPictureInPicture()
} }
}) })
}, [videoRef, isPlaying]) }, [videoRef, isPlaying, user])
return ( return (
<PipWrapper> <PipWrapper>

@ -0,0 +1,65 @@
import type { 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,146 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config'
import { ModalWindow } from 'features/Modal/styled'
import {
ApplyButton,
Body,
Modal as BaseModal,
HeaderTitle,
} from 'features/AuthServiceApp/components/RegisterPopup/styled'
import { Header as BaseHeader } from 'features/PopupComponents'
import { client } from 'features/AuthServiceApp/config/clients/index'
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;
`
: ''};
`

@ -9,11 +9,16 @@ const matchPopupLexics = {
apply: 13491, apply: 13491,
choose_fav_team: 19776, choose_fav_team: 19776,
commentators: 15424, commentators: 15424,
continue_watching: 20007,
current_stats: 19592,
display_all_stats: 19932,
display_stats_according_to_video: 19931,
episode_duration: 13410, episode_duration: 13410,
events: 1020, events: 1020,
from_end_match: 15396, from_end_match: 15396,
from_price: 3992, from_price: 3992,
from_start_match: 15395, from_start_match: 15395,
game_preview: 20005,
gk: 3515, gk: 3515,
go_back_to_match: 13405, go_back_to_match: 13405,
group: 7850, group: 7850,
@ -27,9 +32,12 @@ const matchPopupLexics = {
playlist_format_all_actions: 13408, playlist_format_all_actions: 13408,
playlist_format_all_match_time: 13407, playlist_format_all_match_time: 13407,
playlist_format_selected_acions: 13409, playlist_format_selected_acions: 13409,
sec_60: 20006,
sec_after: 13412, sec_after: 13412,
sec_before: 13411, sec_before: 13411,
selected_player_actions: 13413, selected_player_actions: 13413,
sign_in: 20003,
sign_in_full_game: 20004,
started_streaming_at: 16042, started_streaming_at: 16042,
streamed_live_on: 16043, streamed_live_on: 16043,
video: 1017, video: 1017,

@ -26,6 +26,7 @@ export const PROCEDURES = {
get_user_preferences: 'get_user_preferences', get_user_preferences: 'get_user_preferences',
get_user_subscribes: 'get_user_subscribes', get_user_subscribes: 'get_user_subscribes',
get_user_subscriptions: 'get_user_subscriptions', get_user_subscriptions: 'get_user_subscriptions',
get_view_user_match: 'get_view_user_match',
landing_get_match_info: 'landing_get_match_info', landing_get_match_info: 'landing_get_match_info',
lst_c_country: 'lst_c_country', lst_c_country: 'lst_c_country',
ott_match_events: 'ott_match_events', ott_match_events: 'ott_match_events',

@ -1,4 +1,8 @@
import { Suspense } from 'react' import {
Suspense,
useEffect,
useState,
} from 'react'
import { Router } from 'react-router-dom' import { Router } from 'react-router-dom'
import { MatomoProvider } from '@jonkoops/matomo-tracker-react' import { MatomoProvider } from '@jonkoops/matomo-tracker-react'
@ -8,40 +12,32 @@ import { client } from 'config/clients'
import { matomoInstance } from 'config/matomo' import { matomoInstance } from 'config/matomo'
import { isAvailable } from 'config/env' import { isAvailable } from 'config/env'
import { readToken } from 'helpers'
import { setClientTitleAndDescription } from 'helpers/setClientHeads' import { setClientTitleAndDescription } from 'helpers/setClientHeads'
import { isMatchPage, isMatchPageRFEF } from 'helpers/isMatchPage'
import { GlobalStores } from 'features/GlobalStores' import { GlobalStores } from 'features/GlobalStores'
import { useAuthStore } from 'features/AuthStore'
import { Background } from 'features/Background' import { Background } from 'features/Background'
import { GlobalStyles } from 'features/GlobalStyles' import { GlobalStyles } from 'features/GlobalStyles'
import { Theme } from 'features/Theme' import { Theme } from 'features/Theme'
import { JoinMatchPage } from 'features/JoinMatchPage'
import { JoinMatchPageRFEF } from 'features/JoinMatchPageRFEF'
import { UnavailableText } from 'components/UnavailableText' import { UnavailableText } from 'components/UnavailableText'
import { AuthenticatedApp } from './AuthenticatedApp' import { AuthenticatedApp } from './AuthenticatedApp'
import { checkPage } from '../../helpers/checkPage' import { useAuthStore } from '../AuthStore'
import { PAGES } from '../../config'
setClientTitleAndDescription(client.title, client.description) setClientTitleAndDescription(client.title, client.description)
const Main = () => { const Main = () => {
const { loadingUser, user } = useAuthStore() const [isToken, setIsToken] = useState(false)
const { userInfo } = useAuthStore()
if (!user && (isMatchPage() || checkPage(PAGES.tournament))) return <JoinMatchPage />
if (!user && isMatchPageRFEF()) return <JoinMatchPageRFEF />
if (user && isMatchPageRFEF()) { useEffect(() => {
window.location.href = 'https://instat.tv/football/tournaments/131' readToken() && setIsToken(true)
} }, [userInfo])
// юзер считывается из localstorage или
// access_token токен истек и запрашивается новый
if (loadingUser || user?.expired) return null
// имеется действующий токен // имеется действующий токен
return <AuthenticatedApp /> return isToken ? <AuthenticatedApp /> : null
} }
const date = new Date() const date = new Date()
@ -57,8 +53,8 @@ const OTTApp = () => (
<Background> <Background>
<Suspense fallback={null}> <Suspense fallback={null}>
{isAvailable {isAvailable
&& (date.getTime() < new Date(startDate).getTime() && (date.getTime() < new Date(startDate).getTime()
|| date.getTime() > new Date(stopDate).getTime()) || date.getTime() > new Date(stopDate).getTime())
? (<Main />) : (<UnavailableText />)} ? (<Main />) : (<UnavailableText />)}
</Suspense> </Suspense>
</Background> </Background>

@ -16,6 +16,8 @@ import { loginCheck } from 'features/AuthServiceApp/requests/auth'
import { getApiUrl } from 'features/AuthServiceApp/config/routes' import { getApiUrl } from 'features/AuthServiceApp/config/routes'
import { useAuthFields } from 'features/AuthServiceApp/hooks/useAuthFields' import { useAuthFields } from 'features/AuthServiceApp/hooks/useAuthFields'
import { addAccessTokenToUrl } from 'helpers/languageUrlParam'
import { AuthProviders } from '../../config/authProviders' import { AuthProviders } from '../../config/authProviders'
import { getAuthUrl } from '../../helpers/getAuthUrl' import { getAuthUrl } from '../../helpers/getAuthUrl'
import { useParamsUrl } from '../../hooks/useParamsUrl' import { useParamsUrl } from '../../hooks/useParamsUrl'
@ -38,7 +40,6 @@ export const useLoginForm = () => {
const [isRecoveryPopupOpen, setIsRecoveryPopupOpen] = useState(false) const [isRecoveryPopupOpen, setIsRecoveryPopupOpen] = useState(false)
const formRef = useRef<HTMLFormElement>(null) const formRef = useRef<HTMLFormElement>(null)
const { const {
email, email,
error: formError, error: formError,
@ -134,6 +135,6 @@ export const useLoginForm = () => {
response_type, response_type,
scope, scope,
setIsRecoveryPopupOpen, setIsRecoveryPopupOpen,
url, url: addAccessTokenToUrl(url),
} }
} }

@ -9,8 +9,9 @@ import {
} from 'react' } from 'react'
import { isValidEmail } from 'features/AuthServiceApp/helpers/isValidEmail' import { isValidEmail } from 'features/AuthServiceApp/helpers/isValidEmail'
import { API_ROOT } from 'features/AuthServiceApp/config/routes'
import { API_ROOT } from '../../config/routes' import { addAccessTokenToUrl } from 'helpers/languageUrlParam'
export const useOauth = () => { export const useOauth = () => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
@ -24,7 +25,7 @@ export const useOauth = () => {
const authorize = useCallback(async () => { const authorize = useCallback(async () => {
if (!formRef.current) return if (!formRef.current) return
const url = `${API_ROOT}/oauth` const url = addAccessTokenToUrl(`${API_ROOT}/oauth`)
const res = await fetch(url, { const res = await fetch(url, {
body: new FormData(formRef.current), body: new FormData(formRef.current),

@ -6,11 +6,10 @@ import { devices } from 'config/devices'
import { ModalWindow } from 'features/Modal/styled' import { ModalWindow } from 'features/Modal/styled'
import { Modal as BaseModal } from 'features/Modal' import { Modal as BaseModal } from 'features/Modal'
import { Header as BaseHeader } from 'features/PopupComponents' import { Header as BaseHeader } from 'features/PopupComponents'
import { client } from 'features/AuthServiceApp/config/clients'
import { ButtonSolid } from 'features/Common' import { ButtonSolid } from 'features/Common'
import { client } from '../../config/clients'
export const Modal = styled(BaseModal)` export const Modal = styled(BaseModal)`
background-color: rgba(0, 0, 0, 0.7); background-color: rgba(0, 0, 0, 0.7);
padding: 0 60px; padding: 0 60px;
@ -52,9 +51,12 @@ export const Wrapper = styled.div<WrapperProps>`
` `
export const Header = styled(BaseHeader)` export const Header = styled(BaseHeader)`
display: flex;
flex-direction: column;
align-items: center;
height: auto; height: auto;
padding-top: 60;
justify-content: center; justify-content: center;
gap: 30px;
${isMobileDevice ${isMobileDevice
? css` ? css`
@media ${devices.mobile}{ @media ${devices.mobile}{

@ -1,5 +1,7 @@
import type { ClientIds } from 'config/clients/types' import type { ClientIds } from 'config/clients/types'
import { checkCookie } from 'helpers/cookie'
import { getApiUrl } from 'features/AuthServiceApp/config/routes' import { getApiUrl } from 'features/AuthServiceApp/config/routes'
const errorLexics = { const errorLexics = {
@ -45,8 +47,10 @@ export const registerCheck = async ({
password, password,
urlParams, urlParams,
} : RegisterProps) => { } : RegisterProps) => {
const url = getApiUrl('/registration') const url = `
${getApiUrl('/registration')}${checkCookie('access_token')
? `&${checkCookie('access_token')}`
: ''}`
const init: RequestInit = { const init: RequestInit = {
body: new URLSearchParams({ body: new URLSearchParams({
email, email,
@ -56,7 +60,6 @@ export const registerCheck = async ({
method: 'POST', method: 'POST',
} }
const response = await fetch(url, init) const response = await fetch(url, init)
const body: SuccessResponse | FailedResponse = await response.json() const body: SuccessResponse | FailedResponse = await response.json()
if (body.ok) return Promise.resolve() if (body.ok) return Promise.resolve()

@ -17,9 +17,16 @@ import { PAGES } from 'config'
import { import {
addLanguageUrlParam, addLanguageUrlParam,
} from 'helpers/languageUrlParam' } from 'helpers/languageUrlParam'
import { writeToken, removeToken } from 'helpers/token'
import { setCookie, removeCookie } from 'helpers/cookie' import {
import { isMatchPage, isMatchPageRFEF } from 'helpers/isMatchPage' writeToken,
removeToken,
readToken,
} from 'helpers/token'
import {
setCookie,
removeCookie,
} from 'helpers/cookie'
import { useLocalStore, useToggle } from 'hooks' import { useLocalStore, useToggle } from 'hooks'
@ -30,7 +37,7 @@ import { getUserInfo, UserInfo } from 'requests/getUserInfo'
import { checkDevice, FailedResponse } from 'requests/checkDevice' import { checkDevice, FailedResponse } from 'requests/checkDevice'
import { getClientSettings, needCheckNewDeviсe } from '../helpers' import { getClientSettings, needCheckNewDeviсe } from '../helpers'
import { checkPage } from '../../../helpers/checkPage' import { getTokenVirtualUser } from '../../../requests'
export const useAuth = () => { export const useAuth = () => {
const { changeLang, lang } = useLexicsStore() const { changeLang, lang } = useLexicsStore()
@ -44,18 +51,25 @@ export const useAuth = () => {
const [userInfo, setUserInfo] = useState<UserInfo>() const [userInfo, setUserInfo] = useState<UserInfo>()
const userManager = useMemo(() => new UserManager(getClientSettings()), []) const userManager = useMemo(() => new UserManager(getClientSettings()), [])
const login = useCallback(async () => ( const login = useCallback(async () => {
userManager.signinRedirect({ extraQueryParams: { lang } }) userManager.signinRedirect({ extraQueryParams: { lang } })
), [userManager, lang]) }, [userManager, lang])
const logout = useCallback(() => { const logout = useCallback((key?: string) => {
if (history.location.pathname === '/') {
setSearch(history.location.search)
}
setPage(history.location.pathname)
userManager.clearStaleState() userManager.clearStaleState()
userManager.createSigninRequest().then(({ url }) => { userManager.createSigninRequest().then(({ url }) => {
const urlWithLang = addLanguageUrlParam(lang, url) const urlWithLang = addLanguageUrlParam(lang, url)
userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang }) userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang })
}) })
removeToken() if (key !== 'saveToken') {
removeCookie('access_token') removeToken()
removeCookie('access_token')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userManager, lang]) }, [userManager, lang])
const storeUser = useCallback((loadedUser: User) => { const storeUser = useCallback((loadedUser: User) => {
@ -70,11 +84,19 @@ export const useAuth = () => {
const checkUser = useCallback(async () => { const checkUser = useCallback(async () => {
const loadedUser = await userManager.getUser() const loadedUser = await userManager.getUser()
if (!loadedUser) return Promise.reject()
if (!loadedUser) {
if (!readToken()) {
const token = await getTemporaryToken()
token && await fetchUserInfo()
}
return Promise.resolve()
}
storeUser(loadedUser) storeUser(loadedUser)
markUserLoaded() markUserLoaded()
return loadedUser return loadedUser
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
userManager, userManager,
storeUser, storeUser,
@ -95,6 +117,23 @@ export const useAuth = () => {
validator: isString, validator: isString,
}) })
const getTemporaryToken = async () => {
try {
const { access_token } = await getTokenVirtualUser()
writeToken(access_token)
setCookie({
exdays: 1,
name: 'access_token',
value: access_token,
})
return access_token
// eslint-disable-next-line no-empty
} catch {
return ''
}
}
const signinRedirectCallback = useCallback(() => { const signinRedirectCallback = useCallback(() => {
userManager.signinRedirectCallback() userManager.signinRedirectCallback()
.then((loadedUser) => { .then((loadedUser) => {
@ -104,10 +143,7 @@ export const useAuth = () => {
markUserLoaded() markUserLoaded()
if (page) { if (page) {
const route = `${page}${page === '/' ? search : ''}` const route = `${page}${page === '/' ? search : ''}`
history.push(`${route}${( history.push(route)
page.includes('tournaments') || page.includes('matches'))
? '?from=landing'
: ''}`)
setPage('') setPage('')
setSearch('') setSearch('')
} }
@ -129,11 +165,7 @@ export const useAuth = () => {
if (isRedirectedBackFromAuthProvider) { if (isRedirectedBackFromAuthProvider) {
signinRedirectCallback() signinRedirectCallback()
} else { } else {
checkUser().catch(() => { checkUser().catch(async () => {
if (!isMatchPage() && !isMatchPageRFEF() && !checkPage(PAGES.tournament)) {
login()
}
if (history.location.pathname === '/') { if (history.location.pathname === '/') {
setSearch(history.location.search) setSearch(history.location.search)
} }
@ -212,14 +244,12 @@ export const useAuth = () => {
userInfoFetched.language.iso && changeLang(userInfoFetched.language.iso) userInfoFetched.language.iso && changeLang(userInfoFetched.language.iso)
// eslint-disable-next-line no-empty // eslint-disable-next-line no-empty
} catch (error) {} } catch (error) {}
}, [changeLang]) }, [changeLang])
useEffect(() => { useEffect(() => {
if (user) { fetchUserInfo()
fetchUserInfo()
}
}, [fetchUserInfo, user]) }, [fetchUserInfo, user])
const auth = useMemo(() => ({ const auth = useMemo(() => ({
@ -228,6 +258,7 @@ export const useAuth = () => {
loadingUser, loadingUser,
login, login,
logout, logout,
setUserInfo,
user, user,
userInfo, userInfo,
}), [ }), [
@ -238,6 +269,7 @@ export const useAuth = () => {
userInfo, userInfo,
login, login,
loadingUser, loadingUser,
setUserInfo,
]) ])
return auth return auth

@ -69,7 +69,7 @@ export const GroupBlock = ({ groupBlock }: Props) => {
<Name>{name}</Name> <Name>{name}</Name>
<CountryAndTeamInfo> <CountryAndTeamInfo>
<SportIcon sport={item.sport} size={10} /> <SportIcon sport={item.sport} size={10} />
<Flag src={`https://instatscout.com/images/flags/48/${item.info.country?.id}.png`} /> <Flag src={`https://cf-aws.insports.tv/media/flags/${item.info.country?.id}.png`} />
<EmptySpan>{countryOrTeam}</EmptySpan> <EmptySpan>{countryOrTeam}</EmptySpan>
</CountryAndTeamInfo> </CountryAndTeamInfo>
</EmptyDiv> </EmptyDiv>

@ -34,26 +34,29 @@ const getTimezoneOffset = (date: Date) => {
const getDate = (date: Date) => format(date, 'yyyy-MM-dd') const getDate = (date: Date) => format(date, 'yyyy-MM-dd')
export const useHomePage = () => { export const useHomePage = () => {
const { user, userInfo } = useAuthStore() const { userInfo } = useAuthStore()
const { selectedDate } = useHeaderFiltersStore() const { selectedDate } = useHeaderFiltersStore()
const [isOpenDownload, setIsOpenDownload] = useState(false) const [isOpenDownload, setIsOpenDownload] = useState(false)
const [isShowConfirmPopup, setIsShowConfirmPopup] = useState(false) const [isShowConfirmPopup, setIsShowConfirmPopup] = useState(false)
const setIsSportFilterShown = useSetRecoilState(isSportFilterShownAtom) const setIsSportFilterShown = useSetRecoilState(isSportFilterShownAtom)
const handleCloseConfirmPopup = useCallback(async () => { const handleCloseConfirmPopup = useCallback(async () => {
await setAgreements(user?.profile?.email || '') await setAgreements(`${userInfo?.email}` || '')
setIsShowConfirmPopup(false) setIsShowConfirmPopup(false)
}, [setIsShowConfirmPopup, user]) // eslint-disable-next-line react-hooks/exhaustive-deps
}, [setIsShowConfirmPopup, userInfo])
useEffect(() => { useEffect(() => {
(async () => { if (userInfo?.email) {
const agreement = await getAgreements(user?.profile?.email || '') (async () => {
if (!agreement?.is_agreement_privacy_policy && !agreement?.is_agreement_terms_conditions) { const agreement = await getAgreements(`${userInfo?.email}` || '')
setIsShowConfirmPopup(true) if (!agreement?.is_agreement_privacy_policy && !agreement?.is_agreement_terms_conditions) {
} setIsShowConfirmPopup(true)
})() }
})()
}
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [userInfo])
useEffect(() => { useEffect(() => {
const dateLastOpenSmartBanner = localStorage.getItem('dateLastOpenSmartBanner') const dateLastOpenSmartBanner = localStorage.getItem('dateLastOpenSmartBanner')
@ -71,7 +74,8 @@ export const useHomePage = () => {
offset, offset,
timezoneOffset: getTimezoneOffset(selectedDate), timezoneOffset: getTimezoneOffset(selectedDate),
}), }),
[selectedDate], // eslint-disable-next-line react-hooks/exhaustive-deps
[selectedDate, userInfo],
) )
useEffect(() => { useEffect(() => {

@ -55,7 +55,7 @@ export const ItemsList = ({
<SportIcon size={isMobileDevice ? 10 : '0.65rem'} sport={item.sport} /> <SportIcon size={isMobileDevice ? 10 : '0.65rem'} sport={item.sport} />
{item.additionalInfo && ( {item.additionalInfo && (
<Fragment> <Fragment>
<Flag src={`https://instatscout.com/images/flags/48/${item.additionalInfo.id}.png`} /> <Flag src={`https://cf-aws.insports.tv/media/flags/${item.additionalInfo.id}.png`} />
<TeamOrCountry nameObj={item.additionalInfo} /> <TeamOrCountry nameObj={item.additionalInfo} />
</Fragment> </Fragment>
)} )}

@ -34,6 +34,9 @@ export const useCard = (match: Match) => {
const onMatchClick = useCallback(() => { const onMatchClick = useCallback(() => {
switch (match.access) { switch (match.access) {
case MatchAccess.ViewMatchPopupWithoutUser:
redirectToMatchPage()
break
case MatchAccess.CanBuyMatch: case MatchAccess.CanBuyMatch:
openBuyMatchPopup(match) openBuyMatchPopup(match)
break break

@ -17,7 +17,11 @@ import { useMatchPageStore } from '../../store'
export const FinishedMatch = () => { export const FinishedMatch = () => {
const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation) const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation)
const { isOpenPopup, profile } = useMatchPageStore() const {
access,
isOpenFiltersPopup,
profile,
} = useMatchPageStore()
const { const {
chapters, chapters,
closeSettingsPopup, closeSettingsPopup,
@ -47,8 +51,9 @@ export const FinishedMatch = () => {
{!isEmpty(chapters) && ( {!isEmpty(chapters) && (
<Fragment> <Fragment>
<MultiSourcePlayer <MultiSourcePlayer
access={access}
setCircleAnimation={setCircleAnimation} setCircleAnimation={setCircleAnimation}
isOpenPopup={isOpenPopup} isOpenPopup={isOpenFiltersPopup}
chapters={chapters} chapters={chapters}
onPlayingChange={onPlayingChange} onPlayingChange={onPlayingChange}
profile={profile} profile={profile}

@ -6,8 +6,9 @@ import { usePageParams } from 'hooks/usePageParams'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup' import { useBuyMatchPopupStore } from 'features/BuyMatchPopup'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
import { checkUrlParams, getAllUrlParams } from 'helpers/parseUrlParams/parseUrlParams'
import { prepareMatchProfile } from '../../helpers/prepareMatchProfile' import { prepareMatchProfile } from '../../helpers/prepareMatchProfile'
import { checkUrlParams, getAllUrlParams } from '../../../../helpers/parseUrlParams/parseUrlParams' import { useAuthStore } from '../../../AuthStore'
type Props = { type Props = {
children: ReactNode, children: ReactNode,
@ -17,9 +18,10 @@ export const SubscriptionGuard = ({ children }: Props) => {
const { profile: matchProfile } = useMatchPageStore() const { profile: matchProfile } = useMatchPageStore()
const { open: openBuyMatchPopup } = useBuyMatchPopupStore() const { open: openBuyMatchPopup } = useBuyMatchPopupStore()
const { profileId: matchId, sportType } = usePageParams() const { profileId: matchId, sportType } = usePageParams()
const { user } = useAuthStore()
useEffect(() => { useEffect(() => {
if (matchProfile && ( if (user && matchProfile && (
!matchProfile.sub !matchProfile.sub
|| (checkUrlParams('subscribe') || (checkUrlParams('subscribe')
&& getAllUrlParams('id')))) { && getAllUrlParams('id')))) {
@ -35,11 +37,12 @@ export const SubscriptionGuard = ({ children }: Props) => {
openBuyMatchPopup, openBuyMatchPopup,
matchProfile, matchProfile,
sportType, sportType,
user,
]) ])
return ( return (
<Fragment> <Fragment>
{matchProfile?.sub ? children : null} {matchProfile?.sub || !user ? children : null}
</Fragment> </Fragment>
) )
} }

@ -31,8 +31,12 @@ const MatchPageComponent = () => {
const history = useHistory() const history = useHistory()
const { addRemoveFavorite, userFavorites } = useUserFavoritesStore() const { addRemoveFavorite, userFavorites } = useUserFavoritesStore()
const { isStarted, profile } = useMatchPageStore() const {
const isFavorite = profile && userFavorites.find((fav) => fav.id === profile?.tournament.id) isStarted,
profile,
user,
} = useMatchPageStore()
const isFavorite = profile && userFavorites?.find((fav) => fav.id === profile?.tournament.id)
const { const {
profileType, profileType,
@ -100,7 +104,7 @@ const MatchPageComponent = () => {
</SubscriptionGuard> </SubscriptionGuard>
</Main> </Main>
{ {
(profile?.tournament.id === 131 || profile?.tournament.id === 2032) user && (profile?.tournament.id === 131 || profile?.tournament.id === 2032)
&& <FavouriteTeamPopup /> && <FavouriteTeamPopup />
} }
</PageWrapper> </PageWrapper>

@ -9,9 +9,11 @@ import filter from 'lodash/filter'
import isEmpty from 'lodash/isEmpty' import isEmpty from 'lodash/isEmpty'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists' import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { useAuthStore } from 'features/AuthStore'
import type { MatchInfo } from 'requests/getMatchInfo' import type { MatchInfo } from 'requests/getMatchInfo'
import { getMatchInfo } from 'requests/getMatchInfo' import { getMatchInfo } from 'requests/getMatchInfo'
import { getViewMatchDuration } from 'requests/getViewMatchDuration'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks/usePageParams'
import { useToggle } from 'hooks/useToggle' import { useToggle } from 'hooks/useToggle'
@ -23,32 +25,68 @@ import { useMatchData } from './useMatchData'
import { useFiltersPopup } from './useFitersPopup' import { useFiltersPopup } from './useFitersPopup'
import { useTabEvents } from './useTabEvents' import { useTabEvents } from './useTabEvents'
const ACCESS_TIME = 60
export const useMatchPage = () => { export const useMatchPage = () => {
const [matchProfile, setMatchProfile] = useState<MatchInfo>(null) const [matchProfile, setMatchProfile] = useState<MatchInfo>(null)
const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false) const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false)
const [access, setAccess] = useState(true)
const { profileId: matchId, sportType } = usePageParams() const { profileId: matchId, sportType } = usePageParams()
const { user, userInfo } = useAuthStore()
const { const {
close: hideProfileCard, close: hideProfileCard,
isOpen: profileCardShown, isOpen: profileCardShown,
open: showProfileCard, open: showProfileCard,
} = useToggle(true) } = useToggle(true)
const {
events,
handlePlaylistClick,
matchPlaylists,
selectedPlaylist,
setFullMatchPlaylistDuration,
} = useMatchData(matchProfile)
const profile = matchProfile
const { const {
activeEvents, activeEvents,
activeFirstTeamPlayers, activeFirstTeamPlayers,
activeSecondTeamPlayers, activeSecondTeamPlayers,
allActionsToggle,
allPlayersToggle,
applyFilters, applyFilters,
close: closePopup, close: closePopup,
countOfFilters, countOfFilters,
filters, filters,
isAllActionsChecked,
isEmptyFilters, isEmptyFilters,
isOpen: isOpenPopup, isFirstTeamPlayersChecked,
resetEvents, isOpen: isOpenFiltersPopup,
resetPlayers, isSecondTeamPlayersChecked,
toggle: togglePopup, toggle: togglePopup,
toggleActiveEvents, toggleActiveEvents,
toggleActivePlayers, toggleActivePlayers,
} = useFiltersPopup() uniqEvents,
} = useFiltersPopup({
events,
matchPlaylists,
})
const getMatchViewDuration = (id: number) => (getViewMatchDuration({
matchId,
sportType,
userId: id,
}).then(({
duration,
error,
}) => {
if (error || (duration && Number(duration) > ACCESS_TIME)) {
setAccess(false)
}
}))
useEffect(() => { useEffect(() => {
getMatchInfo(sportType, matchId).then(setMatchProfile) getMatchInfo(sportType, matchId).then(setMatchProfile)
@ -62,7 +100,25 @@ export const useMatchPage = () => {
) )
} }
return () => clearInterval(getIntervalMatch) return () => clearInterval(getIntervalMatch)
}, [matchProfile, sportType, matchId]) }, [
matchProfile,
sportType,
matchId])
useEffect(() => {
if (user || !userInfo?.email) return
const counter = setInterval(
() => getMatchViewDuration(Number(userInfo?.email)), 1000 * 30,
)
// eslint-disable-next-line
return () => clearInterval(counter)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
sportType,
matchId,
userInfo,
])
useEffect(() => { useEffect(() => {
let getIntervalMatch: ReturnType<typeof setInterval> let getIntervalMatch: ReturnType<typeof setInterval>
@ -75,16 +131,6 @@ export const useMatchPage = () => {
return () => clearInterval(getIntervalMatch) return () => clearInterval(getIntervalMatch)
}) })
const {
events,
handlePlaylistClick,
matchPlaylists,
selectedPlaylist,
setFullMatchPlaylistDuration,
} = useMatchData(matchProfile)
const profile = matchProfile
const isStarted = useMemo(() => ( const isStarted = useMemo(() => (
profile?.date profile?.date
? parseDate(profile.date) < new Date() ? parseDate(profile.date) < new Date()
@ -159,10 +205,13 @@ export const useMatchPage = () => {
} }
return { return {
access,
activeEvents, activeEvents,
activeFirstTeamPlayers, activeFirstTeamPlayers,
activeSecondTeamPlayers, activeSecondTeamPlayers,
activeStatus, activeStatus,
allActionsToggle,
allPlayersToggle,
applyFilters, applyFilters,
closePopup, closePopup,
countOfFilters, countOfFilters,
@ -171,10 +220,13 @@ export const useMatchPage = () => {
filteredEvents, filteredEvents,
handlePlaylistClick, handlePlaylistClick,
hideProfileCard, hideProfileCard,
isAllActionsChecked,
isEmptyFilters, isEmptyFilters,
isFirstTeamPlayersChecked,
isLiveMatch, isLiveMatch,
isOpenPopup, isOpenFiltersPopup,
isPlayFilterEpisodes, isPlayFilterEpisodes,
isSecondTeamPlayersChecked,
isStarted, isStarted,
likeImage, likeImage,
likeToggle, likeToggle,
@ -184,8 +236,6 @@ export const useMatchPage = () => {
playNextEpisode, playNextEpisode,
profile, profile,
profileCardShown, profileCardShown,
resetEvents,
resetPlayers,
reversedGroupEvents, reversedGroupEvents,
selectedPlaylist, selectedPlaylist,
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
@ -199,6 +249,8 @@ export const useMatchPage = () => {
toggleActivePlayers, toggleActivePlayers,
togglePopup, togglePopup,
tournamentData, tournamentData,
uniqEvents,
user,
watchAllEpisodesTimer, watchAllEpisodesTimer,
} }
} }

@ -4,6 +4,13 @@ import includes from 'lodash/includes'
import filter from 'lodash/filter' import filter from 'lodash/filter'
import isEmpty from 'lodash/isEmpty' import isEmpty from 'lodash/isEmpty'
import size from 'lodash/size' import size from 'lodash/size'
import uniq from 'lodash/uniq'
import difference from 'lodash/difference'
import map from 'lodash/map'
import every from 'lodash/every'
import type { Events } from 'requests'
import type { Playlists } from 'features/MatchPage/types'
type TTogglePlayers = { type TTogglePlayers = {
id: Number, id: Number,
@ -15,13 +22,36 @@ type TFilters = {
players: Array<Number>, players: Array<Number>,
} }
export const useFiltersPopup = () => { type Props = {
events: Events,
matchPlaylists: Playlists,
}
export const useFiltersPopup = ({
events,
matchPlaylists,
}: Props) => {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const [activeEvents, setActiveEvents] = useState<Array<Number>>([]) const [activeEvents, setActiveEvents] = useState<Array<Number>>([])
const [activeFirstTeamPlayers, setActiveFirstTeamPlayers] = useState<Array<Number>>([]) const [activeFirstTeamPlayers, setActiveFirstTeamPlayers] = useState<Array<Number>>([])
const [activeSecondTeamPlayers, setActiveSecondTeamPlayers] = useState<Array<Number>>([]) const [activeSecondTeamPlayers, setActiveSecondTeamPlayers] = useState<Array<Number>>([])
const [activeFilters, setActiveFilters] = useState<TFilters>({ events: [], players: [] }) const [activeFilters, setActiveFilters] = useState<TFilters>({ events: [], players: [] })
const currentEvents = filter(events, (event) => event.pl !== undefined
&& event.t !== undefined)
const uniqEvents = uniq(map(currentEvents, ({ l }) => l))
const uniqPlayersTeam1 = uniq(map(matchPlaylists.players.team1, ({ id }) => id))
const uniqPlayersTeam2 = uniq(map(matchPlaylists.players.team2, ({ id }) => id))
const isAllActionsChecked = every(uniqEvents, (el) => (includes(activeEvents, el)))
const isFirstTeamPlayersChecked = every(
uniqPlayersTeam1, (el) => (includes(activeFirstTeamPlayers, el)),
)
const isSecondTeamPlayersChecked = every(
uniqPlayersTeam2, (el) => (includes(activeSecondTeamPlayers, el)),
)
const toggle = () => { const toggle = () => {
setIsOpen(!isOpen) setIsOpen(!isOpen)
} }
@ -50,22 +80,28 @@ export const useFiltersPopup = () => {
: [...teamState, id]) : [...teamState, id])
} }
const resetPlayers = (team: string) => () => { const allPlayersToggle = (team: string) => () => {
const teamState = team === 'team1'
? activeFirstTeamPlayers
: activeSecondTeamPlayers
const setterTeamState = team === 'team1' const setterTeamState = team === 'team1'
? setActiveFirstTeamPlayers ? setActiveFirstTeamPlayers
: setActiveSecondTeamPlayers : setActiveSecondTeamPlayers
if (isEmpty(teamState)) return const uniqValues = team === 'team1'
setterTeamState([]) ? uniqPlayersTeam1
: uniqPlayersTeam2
const isAllPlayersChecked = team === 'team1'
? isFirstTeamPlayersChecked
: isSecondTeamPlayersChecked
isAllPlayersChecked
? setterTeamState((currentValues) => difference(currentValues, uniqValues))
: setterTeamState((currentValues) => uniq([...currentValues, ...uniqValues]))
} }
const resetEvents = () => { const allActionsToggle = () => {
if (isEmpty(activeEvents)) return isAllActionsChecked
setActiveEvents([]) ? setActiveEvents((currentValues) => difference(currentValues, uniqEvents))
: setActiveEvents((currentValues) => uniq([...currentValues, ...uniqEvents]))
} }
const applyFilters = () => { const applyFilters = () => {
@ -86,16 +122,20 @@ export const useFiltersPopup = () => {
activeEvents, activeEvents,
activeFirstTeamPlayers, activeFirstTeamPlayers,
activeSecondTeamPlayers, activeSecondTeamPlayers,
allActionsToggle,
allPlayersToggle,
applyFilters, applyFilters,
close, close,
countOfFilters, countOfFilters,
filters: activeFilters, filters: activeFilters,
isAllActionsChecked,
isEmptyFilters, isEmptyFilters,
isFirstTeamPlayersChecked,
isOpen, isOpen,
resetEvents, isSecondTeamPlayersChecked,
resetPlayers,
toggle, toggle,
toggleActiveEvents, toggleActiveEvents,
toggleActivePlayers, toggleActivePlayers,
uniqEvents,
} }
} }

@ -1,8 +1,7 @@
import uniq from 'lodash/uniq'
import map from 'lodash/map' import map from 'lodash/map'
import isEmpty from 'lodash/isEmpty'
import includes from 'lodash/includes' import includes from 'lodash/includes'
import filter from 'lodash/filter'
import { isMobileDevice } from 'config/userAgent'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
@ -28,14 +27,18 @@ import {
} from './styled' } from './styled'
type TLabelProps = { type TLabelProps = {
checked: boolean,
player: PlayerPlaylistOption, player: PlayerPlaylistOption,
} }
const Label = ({ player }: TLabelProps) => { const Label = ({
checked,
player,
}: TLabelProps) => {
const { num } = player const { num } = player
return ( return (
<ItemText> <ItemText>
<PlayerNumber> <PlayerNumber checked={checked}>
{num} {num}
</PlayerNumber> </PlayerNumber>
<Name nameObj={player} /> <Name nameObj={player} />
@ -48,20 +51,19 @@ export const FiltersPopup = () => {
activeEvents, activeEvents,
activeFirstTeamPlayers, activeFirstTeamPlayers,
activeSecondTeamPlayers, activeSecondTeamPlayers,
allActionsToggle,
allPlayersToggle,
applyFilters, applyFilters,
closePopup, closePopup,
events, isAllActionsChecked,
isFirstTeamPlayersChecked,
isSecondTeamPlayersChecked,
matchPlaylists, matchPlaylists,
profile, profile,
resetEvents,
resetPlayers,
toggleActiveEvents, toggleActiveEvents,
toggleActivePlayers, toggleActivePlayers,
uniqEvents,
} = useMatchPageStore() } = useMatchPageStore()
const currentEvents = filter(events, (event) => event.pl !== undefined
&& event.t !== undefined)
const uniqEvents = uniq(map(currentEvents, ({ l }) => l))
const team1Name = useName(profile!.team1) const team1Name = useName(profile!.team1)
const team2Name = useName(profile!.team2) const team2Name = useName(profile!.team2)
@ -70,7 +72,10 @@ export const FiltersPopup = () => {
<PopupContainer> <PopupContainer>
<PopupInner> <PopupInner>
<CloseButtonContainer> <CloseButtonContainer>
<CloseButton onClick={closePopup} /> <CloseButton
size={isMobileDevice ? 8 : 16}
onClick={closePopup}
/>
</CloseButtonContainer> </CloseButtonContainer>
<PopupHeader> <PopupHeader>
<HeaderText> <HeaderText>
@ -80,8 +85,9 @@ export const FiltersPopup = () => {
<PartBlock> <PartBlock>
<MainCheckboxContainer> <MainCheckboxContainer>
<Checkbox <Checkbox
onChange={resetEvents} isMainCheckbox
checked={isEmpty(activeEvents)} onChange={allActionsToggle}
checked={isAllActionsChecked}
labelLexic='all_actions' labelLexic='all_actions'
/> />
</MainCheckboxContainer> </MainCheckboxContainer>
@ -100,41 +106,49 @@ export const FiltersPopup = () => {
<PartBlock> <PartBlock>
<MainCheckboxContainer> <MainCheckboxContainer>
<Checkbox <Checkbox
checked={isEmpty(activeFirstTeamPlayers)} isMainCheckbox
checked={isFirstTeamPlayersChecked}
label={team1Name} label={team1Name}
onChange={resetPlayers('team1')} onChange={allPlayersToggle('team1')}
/> />
</MainCheckboxContainer> </MainCheckboxContainer>
<ItemsContainer> <ItemsContainer>
{map(matchPlaylists.players.team1, ((player) => ( {map(matchPlaylists.players.team1, ((player) => {
<ItemBlock key={player.id}> const isCheckboxChecked = includes(activeFirstTeamPlayers, player.id)
<Checkbox return (
onChange={toggleActivePlayers({ id: player.id, team: 'team1' })} <ItemBlock key={player.id}>
checked={includes(activeFirstTeamPlayers, player.id)} <Checkbox
label={(<Label player={player} />)} onChange={toggleActivePlayers({ id: player.id, team: 'team1' })}
/> checked={isCheckboxChecked}
</ItemBlock> label={(<Label checked={isCheckboxChecked} player={player} />)}
)))} />
</ItemBlock>
)
}))}
</ItemsContainer> </ItemsContainer>
</PartBlock> </PartBlock>
<PartBlock> <PartBlock>
<MainCheckboxContainer> <MainCheckboxContainer>
<Checkbox <Checkbox
checked={isEmpty(activeSecondTeamPlayers)} isMainCheckbox
checked={isSecondTeamPlayersChecked}
label={team2Name} label={team2Name}
onChange={resetPlayers('team2')} onChange={allPlayersToggle('team2')}
/> />
</MainCheckboxContainer> </MainCheckboxContainer>
<ItemsContainer> <ItemsContainer>
{map(matchPlaylists.players.team2, ((player) => ( {map(matchPlaylists.players.team2, ((player) => {
<ItemBlock key={player.id}> const isCheckboxChecked = includes(activeSecondTeamPlayers, player.id)
<Checkbox return (
onChange={toggleActivePlayers({ id: player.id, team: 'team2' })} <ItemBlock key={player.id}>
checked={includes(activeSecondTeamPlayers, player.id)} <Checkbox
label={(<Label player={player} />)} onChange={toggleActivePlayers({ id: player.id, team: 'team2' })}
/> checked={isCheckboxChecked}
</ItemBlock> label={(<Label checked={isCheckboxChecked} player={player} />)}
)))} />
</ItemBlock>
)
}))}
</ItemsContainer> </ItemsContainer>
</PartBlock> </PartBlock>
</PopupInner> </PopupInner>

@ -8,6 +8,15 @@ import { NameStyled } from 'features/Name'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
import { BaseButton } from 'features/PopupComponents' import { BaseButton } from 'features/PopupComponents'
type CheckboxProps = {
checked: boolean,
isMainCheckbox?: boolean,
}
type PlayerNumberProps = {
checked: boolean,
}
export const PopupContainer = styled.div` export const PopupContainer = styled.div`
background: #333333; background: #333333;
box-shadow: 0px 2px 40px rgba(0, 0, 0, 0.6); box-shadow: 0px 2px 40px rgba(0, 0, 0, 0.6);
@ -35,6 +44,8 @@ export const PopupInner = styled.div`
max-height: 100%; max-height: 100%;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
padding-bottom: ${(isMobileDevice ? '20%' : '')};
${customScrollbar} ${customScrollbar}
` `
@ -81,8 +92,16 @@ export const HeaderText = styled.div`
export const PartBlock = styled.div` export const PartBlock = styled.div`
flex: 1 0 auto; flex: 1 0 auto;
border-bottom: 1px solid #505050;
padding: 17px 22px 17px 22px; padding: 17px 22px 17px 22px;
${isMobileDevice
? css`
border-bottom: 1px solid #505050;`
: css`
:not(:last-child) {
border-bottom: 1px solid #505050;
}
`};
` `
export const ItemsContainer = styled.ul` export const ItemsContainer = styled.ul`
@ -92,7 +111,7 @@ export const ItemsContainer = styled.ul`
` `
export const MainCheckboxContainer = styled.div` export const MainCheckboxContainer = styled.div`
margin-bottom: 15px; margin-bottom: ${(isMobileDevice ? '10px' : '15px')};
display: flex; display: flex;
` `
@ -107,14 +126,13 @@ export const ItemBlock = styled.div`
flex: 0 0 33.33%;`} flex: 0 0 33.33%;`}
` `
export const Checkbox = styled(BaseCheckbox)` export const Checkbox = styled(BaseCheckbox)<CheckboxProps>`
height: 28px; height: 28px;
display: block; display: block;
${Label} { ${Label} {
height: 100%; height: 100%;
font-style: normal; font-style: normal;
font-weight: 400;
${isMobileDevice ${isMobileDevice
? css` ? css`
@ -124,17 +142,27 @@ export const Checkbox = styled(BaseCheckbox)`
font-size: 14px; font-size: 14px;
line-height: 16px;`} line-height: 16px;`}
${({ checked }) => (checked ${({ checked, isMainCheckbox }) => (isMainCheckbox
? css`` ? css`
font-weight: 500;`
: css` : css`
color: rgba(255, 255, 255, 0.6);` color: ${checked ? 'rgba(255, 255, 255, 0.6)' : 'rgba(255, 255, 255, 0.3)'};
font-weight: 400;`
)} )}
} }
${CheckboxSvg} { ${CheckboxSvg} {
${({ checked, isMainCheckbox }) => (isMainCheckbox
? css`
width: ${(isMobileDevice ? '23px' : '20px')};
height: ${(isMobileDevice ? '23px' : '20px')};`
: css`
fill: ${checked ? 'rgba(255, 255, 255, 0.6)' : 'rgba(255, 255, 255, 0.3)'};
width: ${(isMobileDevice ? '18px' : '14px')};
height: ${(isMobileDevice ? '18px' : '14px')};
margin-left: ${(isMobileDevice ? '2px' : '3px')};`
)}
margin-right: 8px; margin-right: 8px;
width: 20px;
height: 20px;
} }
` `
@ -157,8 +185,6 @@ export const ItemText = styled.div`
width: 130px;`} width: 130px;`}
` `
export const TeamBlock = styled.div``
export const ButtonConatiner = styled.div` export const ButtonConatiner = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
@ -171,7 +197,8 @@ export const ButtonConatiner = styled.div`
bottom: 14px; bottom: 14px;
left: 50%; left: 50%;
transform: translate(-50%, 0);` transform: translate(-50%, 0);`
: css``} : css`
border-top: 1px solid #505050;`}
` `
export const Button = styled.button` export const Button = styled.button`
@ -191,12 +218,12 @@ export const Button = styled.button`
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 301px;` width: 190px;`
: css` : css`
width: 167px;`} width: 167px;`}
` `
export const PlayerNumber = styled.span` export const PlayerNumber = styled.span<PlayerNumberProps>`
color: rgba(255, 255, 255, 0.7); color: ${({ checked }) => (checked ? '#fff' : 'rgba(255, 255, 255, 0.3)')};
margin-right: 5px; margin-right: 5px;
min-width: 14px; min-width: 14px;
font-size: 11px; font-size: 11px;

@ -1,26 +1,29 @@
import type { Match } from 'requests' import type { Match } from 'requests'
import type { User } from 'oidc-client'
export enum MatchAccess { export enum MatchAccess {
CanBuyMatch = 'CanBuyMatch', CanBuyMatch = 'CanBuyMatch',
NoAccess = 'NoAccess', NoAccess = 'NoAccess',
NoCountryAccess = 'NoCountryAccess', NoCountryAccess = 'NoCountryAccess',
RedirectToProfile = 'RedirectToProfile', RedirectToProfile = 'RedirectToProfile',
ViewMatchPopup = 'ViewMatchPopup', ViewMatchPopup = 'ViewMatchPopup',
ViewMatchPopupWithoutUser = 'ViewMatchPopupWithoutUser',
} }
export const getMatchAccess = ({ export const getMatchAccess = (match: Match, user: User | undefined) => {
access, const {
calc, access,
date, date,
has_video, live,
live, sub,
storage, } = match
sub,
}: Match) => {
const dateToMs = Date.parse(date?.replace(/ /, 'T')) // без замены не будет работать в сафари const dateToMs = Date.parse(date?.replace(/ /, 'T')) // без замены не будет работать в сафари
const dateNowMin10 = dateToMs - 10 * 60 * 1000 const dateNowMin10 = dateToMs - 10 * 60 * 1000
switch (true) { switch (true) {
case !user:
return MatchAccess.ViewMatchPopupWithoutUser
case !sub: case !sub:
return MatchAccess.CanBuyMatch return MatchAccess.CanBuyMatch
case !access: case !access:

@ -2,13 +2,15 @@ import map from 'lodash/map'
import orderBy from 'lodash/orderBy' import orderBy from 'lodash/orderBy'
import format from 'date-fns/format' import format from 'date-fns/format'
import type { User } from 'oidc-client'
import type { Match } from 'requests' import type { Match } from 'requests'
import { parseDate } from 'helpers/parseDate' import { parseDate } from 'helpers/parseDate'
import { getMatchAccess } from './getMatchClickAction' import { getMatchAccess } from './getMatchClickAction'
const prepareMatch = (match: Match) => { const prepareMatch = (match: Match, user?: User | undefined) => {
const { const {
calc, calc,
country, country,
@ -31,7 +33,7 @@ const prepareMatch = (match: Match) => {
const date = parseDate(matchDate) const date = parseDate(matchDate)
return { return {
access: getMatchAccess(match), access: getMatchAccess(match, user),
calc, calc,
countryId: country_id, countryId: country_id,
countryInfo: country, countryInfo: country,
@ -54,10 +56,10 @@ const prepareMatch = (match: Match) => {
} }
} }
export const prepareMatches = (matches: Array<Match>) => { export const prepareMatches = (matches: Array<Match>, user?: User | undefined) => {
const preparedMatches = map( const preparedMatches = map(
matches, matches,
prepareMatch, (match) => prepareMatch(match, user),
) )
return orderBy( return orderBy(
preparedMatches, preparedMatches,

@ -15,6 +15,8 @@ import { isMobileDevice } from 'config/userAgent'
import { prepareMatches } from './helpers/prepareMatches' import { prepareMatches } from './helpers/prepareMatches'
import { useAuthStore } from '../AuthStore'
export type Match = ReturnType<typeof prepareMatches>[number] export type Match = ReturnType<typeof prepareMatches>[number]
export type Props = { export type Props = {
@ -33,6 +35,7 @@ const initialState = {
export const useMatches = ({ fetch }: Props) => { export const useMatches = ({ fetch }: Props) => {
const { userPreferences } = usePreferencesStore() const { userPreferences } = usePreferencesStore()
const { user } = useAuthStore()
const { const {
isFetching, isFetching,
request: requestMatches, request: requestMatches,
@ -81,10 +84,11 @@ export const useMatches = ({ fetch }: Props) => {
}, [fetchMatches, userPreferences]) }, [fetchMatches, userPreferences])
const preparedMatches = useMemo(() => ({ const preparedMatches = useMemo(() => ({
broadcast: prepareMatches(matches.broadcast), broadcast: prepareMatches(matches.broadcast, user),
features: prepareMatches(matches.features), features: prepareMatches(matches.features, user),
highlights: prepareMatches(matches.highlights), highlights: prepareMatches(matches.highlights, user),
isVideoSections: matches.isVideoSections, isVideoSections: matches.isVideoSections,
// eslint-disable-next-line react-hooks/exhaustive-deps
}), [matches]) }), [matches])
return { return {

@ -6,6 +6,8 @@ import { PAGES } from 'config/pages'
import { usePreferencesStore } from 'features/PreferencesPopup' import { usePreferencesStore } from 'features/PreferencesPopup'
import { useTournamentPopupStore } from 'features/TournamentsPopup/store' import { useTournamentPopupStore } from 'features/TournamentsPopup/store'
import { useAuthStore } from 'features/AuthStore'
import { FavoritesMobilePopup } from '../FavoritesMobilePopup' import { FavoritesMobilePopup } from '../FavoritesMobilePopup'
import { import {
@ -18,6 +20,7 @@ export const Menu = () => {
const { openPopup } = usePreferencesStore() const { openPopup } = usePreferencesStore()
const { open } = useTournamentPopupStore() const { open } = useTournamentPopupStore()
const isHomePage = useRouteMatch(PAGES.home)?.isExact const isHomePage = useRouteMatch(PAGES.home)?.isExact
const { logout, user } = useAuthStore()
return ( return (
<MenuList> <MenuList>
@ -29,8 +32,14 @@ export const Menu = () => {
</MenuItem> </MenuItem>
) )
} }
<MenuItem> <MenuItem onClick={(e) => {
<Link to={`${PAGES.useraccount}/personal-info`}> e.preventDefault()
!user && logout('saveToken')
}}
>
<Link
to={`${PAGES.useraccount}/personal-info`}
>
<Icon src='userAccount' size='0.95rem' /> <Icon src='userAccount' size='0.95rem' />
</Link> </Link>
</MenuItem> </MenuItem>

@ -11,7 +11,7 @@ export const ModalContainer = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 15; z-index: 51;
color: white; color: white;
font-weight: 600; font-weight: 600;
` `

@ -53,6 +53,7 @@ const initialState = {
export type PlayerState = typeof initialState export type PlayerState = typeof initialState
export type Props = { export type Props = {
access: boolean,
chapters: Chapters, chapters: Chapters,
isOpenPopup?: boolean, isOpenPopup?: boolean,
onError?: () => void, onError?: () => void,
@ -214,6 +215,10 @@ export const useMultiSourcePlayer = ({
onPlayingChange(playing) onPlayingChange(playing)
}, [playing, onPlayingChange]) }, [playing, onPlayingChange])
useEffect(() => {
setPlayerState({ ...initialState })
}, [profileId, setPlayerState])
useEffect(() => { useEffect(() => {
setCircleAnimation((state) => ({ setCircleAnimation((state) => ({
...state, ...state,
@ -286,11 +291,13 @@ export const useMultiSourcePlayer = ({
// ведем статистику просмотра матча // ведем статистику просмотра матча
const { start: startCollectingStats, stop: stopCollectingStats } = useInterval({ const { start: startCollectingStats, stop: stopCollectingStats } = useInterval({
callback: () => { callback: () => {
saveMatchStats({ if (timeForStatistics.current !== 0) {
matchId: profileId, saveMatchStats({
matchSecond: timeForStatistics.current, matchId: profileId,
sportType, matchSecond: timeForStatistics.current,
}) sportType,
})
}
}, },
intervalDuration: VIEW_INTERVAL_MS, intervalDuration: VIEW_INTERVAL_MS,
startImmediate: false, startImmediate: false,
@ -302,7 +309,12 @@ export const useMultiSourcePlayer = ({
} else { } else {
stopCollectingStats() stopCollectingStats()
} }
}, [playing, startCollectingStats, stopCollectingStats]) }, [
playing,
startCollectingStats,
stopCollectingStats,
profileId,
])
return { return {
activeChapterIndex, activeChapterIndex,

@ -14,16 +14,24 @@ import { Controls } from 'features/StreamPlayer/components/Controls'
import { Name } from 'features/Name' import { Name } from 'features/Name'
import RewindMobile from 'features/StreamPlayer/components/RewindMobile' import RewindMobile from 'features/StreamPlayer/components/RewindMobile'
import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup' import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup'
import { useAuthStore } from 'features/AuthStore'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
import { AccessTimer } from 'components/AccessTimer'
import type { Props } from './hooks' import type { Props } from './hooks'
import { useMultiSourcePlayer } from './hooks' import { useMultiSourcePlayer } from './hooks'
import { Players } from './types' import { Players } from './types'
import { REWIND_SECONDS } from './config' import { REWIND_SECONDS } from './config'
export const MultiSourcePlayer = (props: Props) => { export const MultiSourcePlayer = (props: Props) => {
const { isOpenPopup, profile } = props const {
access,
isOpenPopup,
profile,
} = props
const { const {
activeChapterIndex, activeChapterIndex,
activePlayer, activePlayer,
@ -78,6 +86,7 @@ export const MultiSourcePlayer = (props: Props) => {
} = useMultiSourcePlayer(props) } = useMultiSourcePlayer(props)
const firstPlayerActive = activePlayer === Players.PLAYER1 const firstPlayerActive = activePlayer === Players.PLAYER1
const currentVideo = firstPlayerActive ? video1Ref : video2Ref const currentVideo = firstPlayerActive ? video1Ref : video2Ref
const { user } = useAuthStore()
return ( return (
<PlayerWrapper <PlayerWrapper
@ -210,6 +219,15 @@ export const MultiSourcePlayer = (props: Props) => {
volumeInPercent={volumeInPercent} volumeInPercent={volumeInPercent}
/> />
<ControlsGradient isVisible={mainControlsVisible} /> <ControlsGradient isVisible={mainControlsVisible} />
{!user && (
<AccessTimer
access={access}
isFullscreen={isFullscreen}
onFullscreenClick={onFullscreenClick}
playing={ready && playing && !!playedProgress}
videoRef={currentVideo}
/>
)}
</PlayerWrapper> </PlayerWrapper>
) )
} }

@ -31,8 +31,8 @@ export const BaseButton = styled.button<Props>`
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 18px; width: 20px;
height: 18px; height: 20px;
padding: 4px; padding: 4px;
position: absolute; position: absolute;
top: -20px; top: -20px;

@ -51,7 +51,7 @@ export const TournamentInfo = ({
<StyledName nameObj={tournament} className='title' prefix={prefix} /> <StyledName nameObj={tournament} className='title' prefix={prefix} />
<ItemInfo> <ItemInfo>
{isIcon && <SportIcon size={isMobileDevice ? 10 : '0.65rem'} sport={tournament.sport} />} {isIcon && <SportIcon size={isMobileDevice ? 10 : '0.65rem'} sport={tournament.sport} />}
<Flag src={`https://instatscout.com/images/flags/48/${tournament.country.id}.png`} /> <Flag src={`https://cf-aws.insports.tv/media/flags/${tournament.country.id}.png`} />
<TeamOrCountry nameObj={tournament.country} /> <TeamOrCountry nameObj={tournament.country} />
</ItemInfo> </ItemInfo>
</Wrapper> </Wrapper>

@ -75,7 +75,7 @@ export const ProfileCard = ({ profile }: ProfileType) => {
<Details> <Details>
<ProfileName>{name}</ProfileName> <ProfileName>{name}</ProfileName>
<InfoItems> <InfoItems>
<InfoFlag src={`https://instatscout.com/images/flags/48/${profile.additionalInfo.id}.png`} /> <InfoFlag src={`https://cf-aws.insports.tv/media/flags/${profile.additionalInfo.id}.png`} />
{tournamentId {tournamentId
? ( ? (
<StyledLink <StyledLink

@ -7,7 +7,7 @@ import { PlayerWrapper } from '../../styled'
import { useVideoPlayer, Props } from '../../hooks' import { useVideoPlayer, Props } from '../../hooks'
export const YoutubePlayer = (props: Props) => { export const YoutubePlayer = (props: Props) => {
const { isOpenPopup, profile } = useMatchPageStore() const { isOpenFiltersPopup, profile } = useMatchPageStore()
const { const {
onMouseMove, onMouseMove,
@ -34,7 +34,7 @@ export const YoutubePlayer = (props: Props) => {
onTouchStart={onTouchStart} onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd} onTouchEnd={onTouchEnd}
> >
{isOpenPopup && <FiltersPopup />} {isOpenFiltersPopup && <FiltersPopup />}
<YouTube <YouTube
videoId={key} videoId={key}
opts={{ opts={{

@ -446,11 +446,13 @@ export const useVideoPlayer = ({
// ведем статистику просмотра матча // ведем статистику просмотра матча
const { start: startCollectingStats, stop: stopCollectingStats } = useInterval({ const { start: startCollectingStats, stop: stopCollectingStats } = useInterval({
callback: () => { callback: () => {
saveMatchStats({ if (timeForStatistics.current !== 0) {
matchId: profileId, saveMatchStats({
matchSecond: timeForStatistics.current, matchId: profileId,
sportType, matchSecond: timeForStatistics.current,
}) sportType,
})
}
}, },
intervalDuration: VIEW_INTERVAL_MS, intervalDuration: VIEW_INTERVAL_MS,
startImmediate: false, startImmediate: false,
@ -462,7 +464,12 @@ export const useVideoPlayer = ({
} else { } else {
stopCollectingStats() stopCollectingStats()
} }
}, [playing, startCollectingStats, stopCollectingStats]) }, [
playing,
startCollectingStats,
stopCollectingStats,
profileId,
])
return { return {
activeChapterIndex, activeChapterIndex,

@ -15,6 +15,7 @@ import isString from 'lodash/isString'
import filter from 'lodash/fp/filter' import filter from 'lodash/fp/filter'
import { useLocalStore } from 'hooks' import { useLocalStore } from 'hooks'
import { isMobileDevice } from 'config/userAgent'
const autoQuality = { const autoQuality = {
label: 'Auto', label: 'Auto',
@ -43,7 +44,7 @@ const getVideoQualities = (levels: Array<Level>) => {
Number, Number,
'desc', 'desc',
) )
return uniqBy([...sorted], 'label') return uniqBy([...sorted, autoQuality], 'label')
} }
export const useVideoQuality = (hls: Hls | null) => { export const useVideoQuality = (hls: Hls | null) => {
@ -73,11 +74,18 @@ export const useVideoQuality = (hls: Hls | null) => {
const listener = () => { const listener = () => {
const qualities = getVideoQualities(hls.levels) const qualities = getVideoQualities(hls.levels)
const quality = find(qualities, { label: selectedQuality }) || qualities[0] const quality = find(qualities, { label: selectedQuality }) || autoQuality
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
hls.currentLevel = quality.level hls.currentLevel = quality.level
setSelectedQuality(quality.label) setSelectedQuality(quality.label)
setVideoQualities(qualities) setVideoQualities(qualities)
if (isMobileDevice && quality.label === 'Auto') {
const mob720 = qualities.find((item) => item.label === '720')
// eslint-disable-next-line no-param-reassign
hls.autoLevelCapping = Number(mob720?.level)
}
} }
hls.on(Hls.Events.MANIFEST_PARSED, listener) hls.on(Hls.Events.MANIFEST_PARSED, listener)
return () => { return () => {

@ -5,6 +5,7 @@ import { Name } from 'features/Name'
import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup' import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopup'
import { WaterMark } from 'components/WaterMark' import { WaterMark } from 'components/WaterMark'
import { AccessTimer } from 'components/AccessTimer'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
@ -30,7 +31,11 @@ import RewindMobile from './components/RewindMobile'
* HLS плеер, применяется на лайв и завершенных матчах * HLS плеер, применяется на лайв и завершенных матчах
*/ */
export const StreamPlayer = (props: Props) => { export const StreamPlayer = (props: Props) => {
const { isOpenPopup, profile } = useMatchPageStore() const {
access,
isOpenFiltersPopup,
profile,
} = useMatchPageStore()
const { user } = useAuthStore() const { user } = useAuthStore()
const { const {
@ -85,6 +90,7 @@ export const StreamPlayer = (props: Props) => {
volumeInPercent, volumeInPercent,
wrapperRef, wrapperRef,
} = useVideoPlayer(props) } = useVideoPlayer(props)
return ( return (
<PlayerWrapper <PlayerWrapper
ref={wrapperRef} ref={wrapperRef}
@ -96,7 +102,7 @@ export const StreamPlayer = (props: Props) => {
onTouchStart={onTouchStart} onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd} onTouchEnd={onTouchEnd}
> >
{isOpenPopup && <FiltersPopup />} {isOpenFiltersPopup && <FiltersPopup />}
<LoaderWrapper buffering={buffering}> <LoaderWrapper buffering={buffering}>
<Loader color='#515151' /> <Loader color='#515151' />
</LoaderWrapper> </LoaderWrapper>
@ -191,6 +197,15 @@ export const StreamPlayer = (props: Props) => {
selectedAudioTrack={selectedAudioTrack} selectedAudioTrack={selectedAudioTrack}
/> />
<ControlsGradient isVisible={mainControlsVisible} /> <ControlsGradient isVisible={mainControlsVisible} />
{!user && (
<AccessTimer
access={access}
isFullscreen={isFullscreen}
onFullscreenClick={onFullscreenClick}
playing={ready && playing && !!playedProgress}
videoRef={videoRef}
/>
)}
</PlayerWrapper> </PlayerWrapper>
) )
} }

@ -96,6 +96,17 @@ export const ControlsGroup = styled.div`
const supportsAspectRatio = CSS.supports('aspect-ratio', '16 / 9') && isMobileDevice const supportsAspectRatio = CSS.supports('aspect-ratio', '16 / 9') && isMobileDevice
export const PlayerWrapper = styled.div<PlayStopProps>` export const PlayerWrapper = styled.div<PlayStopProps>`
:not(:root):fullscreen::backdrop {
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background: white;
z-index: 0;
}
width: 100%; width: 100%;
position: relative; position: relative;
background-color: #000; background-color: #000;

@ -9,6 +9,8 @@ import { SELECTED_API_KEY } from 'helpers/selectedApi'
import { useToggle } from 'hooks/useToggle' import { useToggle } from 'hooks/useToggle'
import { useLocalStore } from 'hooks/useStorage' import { useLocalStore } from 'hooks/useStorage'
import { removeToken } from '../../helpers'
import { removeCookie } from '../../helpers/cookie'
type FormElement = HTMLFormElement & { type FormElement = HTMLFormElement & {
api: HTMLInputElement & { api: HTMLInputElement & {
@ -34,6 +36,8 @@ export const useSystemSettings = () => {
const { api } = e.currentTarget const { api } = e.currentTarget
setSelectedApi(api.value) setSelectedApi(api.value)
removeToken()
removeCookie('access_token')
window.location.reload() window.location.reload()
} }

@ -93,7 +93,7 @@ export const CollapseTournament = ({
</SportWrapper> </SportWrapper>
<CountryWrapper> <CountryWrapper>
<CountryFlag <CountryFlag
src={`https://instatscout.com/images/flags/48/${countryId}.png`} src={`https://cf-aws.insports.tv/media/flags/${countryId}.png`}
/> />
{countryInfo && ( {countryInfo && (
<Tooltip> <Tooltip>

@ -54,7 +54,7 @@ export const TournamentMobile = ({
sport={sportType} sport={sportType}
/> />
<CountryFlag <CountryFlag
src={`https://instatscout.com/images/flags/48/${countryId}.png`} src={`https://cf-aws.insports.tv/media/flags/${countryId}.png`}
/> />
</Fragment> </Fragment>
)} )}

@ -18,6 +18,8 @@ import { checkUrlParams, getAllUrlParams } from 'helpers/parseUrlParams/parseUrl
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks/usePageParams'
import { useName } from 'features/Name' import { useName } from 'features/Name'
import { useAuthStore } from 'features/AuthStore'
import { isPermittedTournament } from '../../helpers/isPermittedTournament' import { isPermittedTournament } from '../../helpers/isPermittedTournament'
import { useProfileCard } from '../ProfileCard/hooks' import { useProfileCard } from '../ProfileCard/hooks'
import { useBuyMatchPopupStore } from '../BuyMatchPopup' import { useBuyMatchPopupStore } from '../BuyMatchPopup'
@ -30,6 +32,7 @@ export const useTournamentPage = () => {
const { open: openBuyMatchPopup } = useBuyMatchPopupStore() const { open: openBuyMatchPopup } = useBuyMatchPopupStore()
const country = useName(tournamentProfile?.country || {}) const country = useName(tournamentProfile?.country || {})
const history = useHistory() const history = useHistory()
const { user } = useAuthStore()
const { isFavorite, toggleFavorites } = useProfileCard() const { isFavorite, toggleFavorites } = useProfileCard()
@ -94,5 +97,6 @@ export const useTournamentPage = () => {
infoItems: [country], infoItems: [country],
profile, profile,
tournamentId, tournamentId,
user,
} }
} }

@ -21,6 +21,7 @@ const TournamentPage = () => {
headerImage, headerImage,
profile, profile,
tournamentId, tournamentId,
user,
} = useTournamentPage() } = useTournamentPage()
return ( return (
@ -37,7 +38,7 @@ const TournamentPage = () => {
<Matches fetch={fetchMatches} /> <Matches fetch={fetchMatches} />
</Content> </Content>
</Main> </Main>
{(tournamentId === 131 || tournamentId === 2032) && <FavouriteTeamPopup />} {user && (tournamentId === 131 || tournamentId === 2032) && <FavouriteTeamPopup />}
</PageWrapper> </PageWrapper>
) )
} }

@ -72,7 +72,7 @@ export const TournamentSubtitle = ({
)} )}
</SportWrapper> </SportWrapper>
<CountryWrapper> <CountryWrapper>
<CountryFlag src={`https://instatscout.com/images/flags/48/${countryId}.png`} /> <CountryFlag src={`https://cf-aws.insports.tv/media/flags/${countryId}.png`} />
{countryInfo && ( {countryInfo && (
<Tooltip> <Tooltip>
<Name nameObj={countryInfo} /> <Name nameObj={countryInfo} />

@ -44,7 +44,7 @@ export const LogoutButton = () => {
const { logout } = useAuthStore() const { logout } = useAuthStore()
return ( return (
<Button onClick={logout}> <Button onClick={() => logout()}>
<ExitIcon /> <ExitIcon />
<T9n t='logout' /> <T9n t='logout' />
</Button> </Button>

@ -32,7 +32,7 @@ export const TooltipBlock = ({
</TooltipBlockItem> </TooltipBlockItem>
<TooltipBlockItemThin> <TooltipBlockItemThin>
{info?.team && <Name nameObj={info.team} />}{' '} {info?.team && <Name nameObj={info.team} />}{' '}
{info?.country && <Flag src={`https://instatscout.com/images/flags/48/${info.country.id}.png`} />} {info?.country && <Flag src={`https://cf-aws.insports.tv/media/flags/${info.country.id}.png`} />}
</TooltipBlockItemThin> </TooltipBlockItemThin>
</TooltipBlockWrapper> </TooltipBlockWrapper>
) )

@ -2,7 +2,6 @@ import type { CallApiArgs } from './types'
import { parseJSON } from './parseJSON' import { parseJSON } from './parseJSON'
import { checkStatus } from './checkStatus' import { checkStatus } from './checkStatus'
import { getRequestConfig } from './getRequestConfig' import { getRequestConfig } from './getRequestConfig'
import { logoutIfUnauthorized } from './logoutIfUnauthorized'
export const callApiBase = ({ export const callApiBase = ({
abortSignal, abortSignal,
@ -39,7 +38,7 @@ const callApiWithTimeout = (args: CallApiArgs) => {
}) })
.then(checkStatus) .then(checkStatus)
.then(parseJSON) .then(parseJSON)
.catch(logoutIfUnauthorized) // .catch(logoutIfUnauthorized)
.finally(() => clearTimeout(timeoutId)) .finally(() => clearTimeout(timeoutId))
} }
@ -49,5 +48,5 @@ export const callApi = (args: CallApiArgs) => {
return callApiBase(args) return callApiBase(args)
.then(checkStatus) .then(checkStatus)
.then(parseJSON) .then(parseJSON)
.catch(logoutIfUnauthorized) // .catch(logoutIfUnauthorized)
} }

@ -22,6 +22,13 @@ export const removeCookie = (name: string, domain = getDomain()) => {
document.cookie = `${name}=;${expires};path=/;domain=${domain}` document.cookie = `${name}=;${expires};path=/;domain=${domain}`
} }
export const checkCookie = (name: string) => {
const cookies = document.cookie
const token = cookies?.split('; ')
?.filter((cookie: string) => cookie?.includes(name))
return token[0]
}
const getDomain = () => ( const getDomain = () => (
process.env.NODE_ENV === 'development' process.env.NODE_ENV === 'development'
? 'localhost' ? 'localhost'

@ -2,6 +2,7 @@ import isNull from 'lodash/isNull'
import { history } from 'config/history' import { history } from 'config/history'
import { client } from 'config/clients' import { client } from 'config/clients'
import { checkCookie } from '../cookie'
const KEY = 'lang' const KEY = 'lang'
@ -15,3 +16,10 @@ export const addLanguageUrlParam = (lang: string, url: string) => {
urlObject.searchParams.set(KEY, lang) urlObject.searchParams.set(KEY, lang)
return urlObject.toString() return urlObject.toString()
} }
export const addAccessTokenToUrl = (url: string) => {
const urlObject = new URL(url)
const token = checkCookie('access_token')?.split('=')
token && urlObject.searchParams.set(token[0], token[1])
return urlObject.toString()
}

@ -2,6 +2,7 @@ export { Arrow } from './objects/Arrow'
export { Boxing } from './objects/Boxing' export { Boxing } from './objects/Boxing'
export { Date } from './objects/Date' export { Date } from './objects/Date'
export { Edit } from './objects/Edit' export { Edit } from './objects/Edit'
export { ExclamationPoint } from './objects/ExclamationPoint'
export { Calendar } from './objects/Calendar' export { Calendar } from './objects/Calendar'
export { Check } from './objects/Check' export { Check } from './objects/Check'
export { CheckCircle } from './objects/CheckCircle' export { CheckCircle } from './objects/CheckCircle'

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

@ -75,7 +75,7 @@ export const MatchesHighlights = () => {
<ScTournament> <ScTournament>
<SportIcon sport={sport} /> <SportIcon sport={sport} />
<ScCountryFlag <ScCountryFlag
src={`https://instatscout.com/images/flags/48/${country_id}.png`} src={`https://cf-aws.insports.tv/media/flags/${country_id}.png`}
/> />
<ScTournamentName>{tournament.name_eng}</ScTournamentName> <ScTournamentName>{tournament.name_eng}</ScTournamentName>
</ScTournament> </ScTournament>

@ -33,5 +33,6 @@ export const getHomeMatches = async ({
}, },
} }
return requestMatches(config, url).then(getMatchesPreviews) return requestMatches(config, url)
.then(getMatchesPreviews)
} }

@ -0,0 +1,13 @@
import { AUTH_SERVICE } from '../config/routes'
import { client } from '../config/clients'
import { callApi } from '../helpers'
export const getTokenVirtualUser = async () => {
const url = `${AUTH_SERVICE}/v1/user/create?client_id=${client.auth.clientId}`
const config = {
method: 'POST',
}
return callApi({ config, url })
}

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

@ -25,3 +25,4 @@ export * from './getPlayerPlaylists'
export * from './getSubscriptions' export * from './getSubscriptions'
export * from './buySubscription' export * from './buySubscription'
export * from './saveMatchStats' export * from './saveMatchStats'
export * from './getTokenVirtualUser'

Loading…
Cancel
Save