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. 59
      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
AWS_MAX_ATTEMPTS: 10
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 "/*"
depends_on:
- make-auth
@ -88,6 +88,8 @@ steps:
- 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
@ -681,3 +683,72 @@ steps:
- 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 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,
type: ProfileTypes,
}
export const ItemInfo = ({
active,
id,

@ -7,6 +7,7 @@ import {
import styled from 'styled-components/macro'
import { Icon } from 'features/Icon'
import { useAuthStore } from 'features/AuthStore'
const PipWrapper = styled.div`
cursor: pointer;
@ -21,6 +22,7 @@ type PipProps = {
}
export const PiP = memo(({ isPlaying, videoRef }: PipProps) => {
const { user } = useAuthStore()
const togglePip = async () => {
try {
if (
@ -43,11 +45,12 @@ export const PiP = memo(({ isPlaying, videoRef }: PipProps) => {
&& videoRef.current !== document.pictureInPictureElement
&& videoRef.current?.hidden === false
&& isPlaying
&& user
) {
await videoRef.current?.requestPictureInPicture()
}
})
}, [videoRef, isPlaying])
}, [videoRef, isPlaying, user])
return (
<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,
choose_fav_team: 19776,
commentators: 15424,
continue_watching: 20007,
current_stats: 19592,
display_all_stats: 19932,
display_stats_according_to_video: 19931,
episode_duration: 13410,
events: 1020,
from_end_match: 15396,
from_price: 3992,
from_start_match: 15395,
game_preview: 20005,
gk: 3515,
go_back_to_match: 13405,
group: 7850,
@ -27,9 +32,12 @@ const matchPopupLexics = {
playlist_format_all_actions: 13408,
playlist_format_all_match_time: 13407,
playlist_format_selected_acions: 13409,
sec_60: 20006,
sec_after: 13412,
sec_before: 13411,
selected_player_actions: 13413,
sign_in: 20003,
sign_in_full_game: 20004,
started_streaming_at: 16042,
streamed_live_on: 16043,
video: 1017,

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

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

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

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

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

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

@ -69,7 +69,7 @@ export const GroupBlock = ({ groupBlock }: Props) => {
<Name>{name}</Name>
<CountryAndTeamInfo>
<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>
</CountryAndTeamInfo>
</EmptyDiv>

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

@ -55,7 +55,7 @@ export const ItemsList = ({
<SportIcon size={isMobileDevice ? 10 : '0.65rem'} sport={item.sport} />
{item.additionalInfo && (
<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} />
</Fragment>
)}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -51,7 +51,7 @@ export const TournamentInfo = ({
<StyledName nameObj={tournament} className='title' prefix={prefix} />
<ItemInfo>
{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} />
</ItemInfo>
</Wrapper>

@ -75,7 +75,7 @@ export const ProfileCard = ({ profile }: ProfileType) => {
<Details>
<ProfileName>{name}</ProfileName>
<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
? (
<StyledLink

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

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

@ -15,6 +15,7 @@ import isString from 'lodash/isString'
import filter from 'lodash/fp/filter'
import { useLocalStore } from 'hooks'
import { isMobileDevice } from 'config/userAgent'
const autoQuality = {
label: 'Auto',
@ -43,7 +44,7 @@ const getVideoQualities = (levels: Array<Level>) => {
Number,
'desc',
)
return uniqBy([...sorted], 'label')
return uniqBy([...sorted, autoQuality], 'label')
}
export const useVideoQuality = (hls: Hls | null) => {
@ -73,11 +74,18 @@ export const useVideoQuality = (hls: Hls | null) => {
const listener = () => {
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
hls.currentLevel = quality.level
setSelectedQuality(quality.label)
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)
return () => {

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

@ -96,6 +96,17 @@ export const ControlsGroup = styled.div`
const supportsAspectRatio = CSS.supports('aspect-ratio', '16 / 9') && isMobileDevice
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%;
position: relative;
background-color: #000;

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

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

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

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

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

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

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

@ -32,7 +32,7 @@ export const TooltipBlock = ({
</TooltipBlockItem>
<TooltipBlockItemThin>
{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>
</TooltipBlockWrapper>
)

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

@ -22,6 +22,13 @@ export const removeCookie = (name: string, domain = getDomain()) => {
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 = () => (
process.env.NODE_ENV === 'development'
? 'localhost'

@ -2,6 +2,7 @@ import isNull from 'lodash/isNull'
import { history } from 'config/history'
import { client } from 'config/clients'
import { checkCookie } from '../cookie'
const KEY = 'lang'
@ -15,3 +16,10 @@ export const addLanguageUrlParam = (lang: string, url: string) => {
urlObject.searchParams.set(KEY, lang)
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 { Date } from './objects/Date'
export { Edit } from './objects/Edit'
export { ExclamationPoint } from './objects/ExclamationPoint'
export { Calendar } from './objects/Calendar'
export { Check } from './objects/Check'
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>
<SportIcon sport={sport} />
<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>
</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 './buySubscription'
export * from './saveMatchStats'
export * from './getTokenVirtualUser'

Loading…
Cancel
Save