feat(in-565): ads on home and match pages

pull/265/head
Margarita 3 years ago committed by Gitea
parent e5552e4d8b
commit 0170d0fb06
  1. 4
      Makefile
  2. 5
      package-lock.json
  3. 1
      package.json
  4. 156
      src/components/Ads/components/AdComponent/hooks.tsx
  5. 85
      src/components/Ads/components/AdComponent/index.tsx
  6. 109
      src/components/Ads/components/AdComponent/styled.tsx
  7. 71
      src/components/Ads/components/MobileAd/index.tsx
  8. 112
      src/components/Ads/components/MobileAd/styled.tsx
  9. 16
      src/components/Ads/helpers/calcMaxDurationAds.tsx
  10. 1
      src/components/Ads/helpers/index.tsx
  11. 2
      src/components/Ads/helpers/isVideo.tsx
  12. 65
      src/components/Ads/hooks.tsx
  13. 31
      src/components/Ads/index.tsx
  14. 16
      src/components/Ads/styled.tsx
  15. 57
      src/components/Ads/types.tsx
  16. 1
      src/config/index.tsx
  17. 1
      src/config/localStorageKeys.tsx
  18. 1
      src/config/queries.tsx
  19. 8
      src/config/userAgent.tsx
  20. 19
      src/features/HeaderFilters/components/DateFilter/hooks/index.tsx
  21. 31
      src/features/HomePage/hooks.tsx
  22. 18
      src/features/HomePage/index.tsx
  23. 36
      src/features/MatchPage/index.tsx
  24. 12
      src/features/MatchPage/store/hooks/index.tsx
  25. 25
      src/features/MatchSidePlaylists/components/EventsList/index.tsx
  26. 18
      src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx
  27. 1
      src/features/MatchSidePlaylists/components/TabEvents/index.tsx
  28. 5
      src/features/MatchSidePlaylists/components/TabEvents/styled.tsx
  29. 18
      src/features/MatchSidePlaylists/components/TabWatch/index.tsx
  30. 26
      src/features/MatchSidePlaylists/index.tsx
  31. 15
      src/features/MatchSidePlaylists/styled.tsx
  32. 23
      src/features/MatchesGrid/index.tsx
  33. 1
      src/features/MatchesGrid/styled.tsx
  34. 8
      src/features/StreamPlayer/hooks/index.tsx
  35. 27
      src/features/StreamPlayer/index.tsx
  36. 9
      src/features/StreamPlayer/styled.tsx
  37. 43
      src/features/TournamentList/components/TournamentMobile/index.tsx
  38. 7
      src/features/TournamentList/components/TournamentMobile/styled.tsx
  39. 7
      src/pages/HighlightsPage/storeHighlightsAtoms.tsx
  40. 65
      src/requests/getAds/getAds.tsx
  41. 2
      src/requests/getAds/index.tsx
  42. 29
      src/requests/getAds/updateAdsView.tsx
  43. 4
      src/requests/index.tsx
  44. 31
      src/utilits/mirage/Mirage.tsx
  45. 592
      src/utilits/mirage/fixtures/getAds.tsx
  46. 2
      src/utilits/mirage/fixtures/index.tsx
  47. 8
      src/utilits/mirage/models/index.tsx
  48. 44
      src/utilits/mirage/server.ts

@ -46,7 +46,7 @@ build-c: clean
build-d: clean
REACT_APP_TYPE=ott \
REACT_APP_ENV=staging \
REACT_APP_CLIENT=facr \
REACT_APP_CLIENT=insports \
REACT_APP_STAGE=test-d \
npm run build
@ -135,7 +135,7 @@ tunisia-build: clean
REACT_APP_ENV=staging \
REACT_APP_CLIENT=tunisia \
npm run build
fqtv-build: clean
REACT_APP_TYPE=ott \
REACT_APP_ENV=staging \

5
package-lock.json generated

@ -21000,6 +21000,11 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
"react-ga": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/react-ga/-/react-ga-3.3.1.tgz",
"integrity": "sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ=="
},
"react-inspector": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-5.1.1.tgz",

@ -39,6 +39,7 @@
"react": "^17.0.2",
"react-datepicker": "^3.1.3",
"react-dom": "^17.0.2",
"react-ga": "^3.3.1",
"react-query": "^3.39.3",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",

@ -0,0 +1,156 @@
import { useEffect, useState } from 'react'
import ReactGA from 'react-ga'
import { updateAdsView } from 'requests'
import { useToggle } from 'hooks'
import { getLocalStorageItem, isMatchPage } from 'helpers'
import {
device,
COUNTRY,
} from 'config'
import { useMatchPageStore } from 'features/MatchPage/store'
import type { AdComponentType } from './index'
import { checkVideo } from '../../helpers'
import {
adsViews,
EventGA,
ViewsType,
} from '../../types'
const countryCode = getLocalStorageItem(COUNTRY)
export const useAd = ({ ad }: AdComponentType) => {
const [isOpenAd, setIsOpenAd] = useState(true)
const [isNeedToShow, setIsNeedToShow] = useState(true)
const [shownTime, setShownTime] = useState(0)
const { isFullscreen } = useMatchPageStore()
const views = getLocalStorageItem(adsViews) as ViewsType
const {
duration,
frequency,
id,
media,
name,
time_close,
} = ad
const {
close,
isOpen: isOpenCloseBtn,
open: showCloseBtn,
} = useToggle()
const isNeedBanner = Number(views?.HOME) % frequency === 0
const isVideo = checkVideo(media.url)
const currentAdsTime = duration - shownTime
useEffect(() => {
if (!isFullscreen) {
if (currentAdsTime === 0) {
setShownTime(0)
} else {
const stopWatch = setInterval(() => {
setShownTime((prev) => prev + 1)
}, 1000)
return () => clearInterval(stopWatch)
}
}
return undefined
}, [
isFullscreen,
currentAdsTime,
])
const handleClose = async () => {
setIsOpenAd(false)
isMatchPage() && setIsNeedToShow(false)
sendBannerClickEvent(EventGA.CLOSE)
}
const sendBannerClickEvent = (event: EventGA) => {
ReactGA.initialize('Advertisement')
ReactGA.event({
action: event,
category: 'Advertisement',
label: `${name}_${countryCode ?? ''}_${device}`,
value: id,
})
}
useEffect(() => {
setShownTime(0)
if (isMatchPage()) {
const interval = setInterval(() => {
setIsNeedToShow(true)
setIsOpenAd(true)
}, frequency * 1000)
return () => clearInterval(interval)
}
setIsNeedToShow(isNeedBanner)
return setIsOpenAd(isNeedBanner)
}, [
frequency,
isNeedToShow,
views?.HOME,
isNeedBanner,
])
useEffect(() => {
if (isFullscreen || !isOpenAd) return undefined
const timeoutCloseAd = setTimeout(handleClose, currentAdsTime * 1000)
return () => {
clearTimeout(timeoutCloseAd)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isNeedToShow,
isOpenAd,
isFullscreen,
currentAdsTime,
])
useEffect(() => {
close()
const timeoutCloseBtn = time_close && setTimeout(showCloseBtn, time_close * 1000)
return () => {
time_close && clearTimeout(timeoutCloseBtn)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isNeedToShow,
isOpenAd,
views?.HOME,
])
useEffect(() => {
if (!isNeedToShow || (!isMatchPage() && !isNeedBanner)) return
(async () => {
await updateAdsView({ adv_id: id })
})()
sendBannerClickEvent(EventGA.DISPLAY)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, isNeedToShow])
return {
handleClose,
isNeedToShow,
isOpenAd,
isOpenCloseBtn,
isVideo,
sendBannerClickEvent,
}
}

@ -0,0 +1,85 @@
import { memo, MouseEvent } from 'react'
import type { AdType } from 'requests'
import { useAd } from './hooks'
import { EventGA } from '../../types'
import {
AdImg,
AdVideo,
AdWrapper,
LinkWrapper,
AdsCloseButton,
} from './styled'
export type AdComponentType = {
ad: AdType,
}
export const AdComponent = memo(({ ad }: AdComponentType) => {
const {
link,
media,
position,
} = ad
const {
handleClose,
isNeedToShow,
isOpenAd,
isOpenCloseBtn,
isVideo,
sendBannerClickEvent,
} = useAd({ ad })
const close = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
return handleClose()
}
const onLinkClick = () => {
link && sendBannerClickEvent(EventGA.CLICK)
}
return (
position && isOpenAd && isNeedToShow
? (
<AdWrapper
position={position.id}
isOpenAd={isOpenAd}
>
{isOpenCloseBtn && (
<AdsCloseButton
onClick={close}
size={12}
position={position.id}
/>
)}
<LinkWrapper
href={link}
target='_blank'
rel='noreferrer'
onClick={onLinkClick}
>
{isVideo
? (
<AdVideo
muted={isVideo}
autoPlay={isVideo}
loop={isVideo}
src={media.url}
position={position.id}
/>
)
: (
<AdImg
src={media.url}
position={position.id}
/>
)}
</LinkWrapper>
</AdWrapper>
) : null
)
})

@ -0,0 +1,109 @@
import styled, { css } from 'styled-components/macro'
import includes from 'lodash/includes'
import { CloseButton } from 'features/PopupComponents'
import {
MATCH_ADS,
PLAYER_ADS,
VIEW_ADS,
} from '../../types'
type Props = {
position: number,
}
const header = [7, 8, 9]
const chooseStyle = (type: number) => {
switch (true) {
case VIEW_ADS.COLUMN === type:
return 'grid-row: 1 / 3; img {max-height: none;}'
case VIEW_ADS.ROW === type:
return 'grid-column: 1 / 3'
case VIEW_ADS.SQUARE === type:
return 'grid-row: 1 / 3; grid-column: 1 / 3; img {max-height: none;}'
case VIEW_ADS.SECOND_COLUMN === type:
return 'grid-column: 2 / 3; grid-row: 1 / 1'
case VIEW_ADS.SECOND_ROW === type:
return 'grid-column: 1 / 2; grid-row: 2 / 3;'
case MATCH_ADS.PLAYS_TOP === type:
return 'margin-left: 14px; height: 48px'
case MATCH_ADS.PLAYS_BOTTOM === type:
return 'grid-row: 4; margin-bottom: 12px; height: 48px;'
case PLAYER_ADS.LEFT_BOTTOM === type:
return css`
height: auto;
width: 42.4%;
bottom: 100px;
left: 20px;
`
case PLAYER_ADS.CENTER_BOTTOM === type:
return css`
height: 18.3%;
width: 81.3%;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
`
case PLAYER_ADS.RIGHT === type:
return css`
height: 87.2%;
width: 18.3%;
bottom: 90px;
right: 18px;
`
case PLAYER_ADS.FULL_SCREEN === type:
return 'bottom: 0; left: 0;'
default:
return ''
}
}
export const AdImg = styled.img<Props>`
width: 100%;
min-height: ${({ position }) => (!includes(header, position) && '100%')};
max-height: ${({ position }) => (includes(header, position) ? '13rem' : '100%')};
cursor: pointer;
border-radius: 3px;
`
export const AdVideo = styled.video<Props>`
object-fit: contain;
width: 100%;
cursor: pointer;
max-height: ${({ position }) => (includes(header, position) ? '283px' : '100%')};
background-color: black;
border-radius: 3px;
`
export const AdWrapper = styled.div<Props & {isOpenAd: boolean}>`
position: ${({ position }) => (includes(PLAYER_ADS, position) ? 'absolute' : 'relative')};
width: 100%;
height: 100%;
z-index: 1;
${({ position }) => chooseStyle(position)};
display: ${({ isOpenAd }) => (isOpenAd ? '' : 'none')};
`
export const AdsCloseButton = styled(CloseButton)<Props>`
position: absolute;
right: ${({ position }) => (position === PLAYER_ADS.FULL_SCREEN ? '10px' : '0')};
top: ${({ position }) => (position === PLAYER_ADS.FULL_SCREEN ? '10px' : '0')};
background: none;
border-radius: 0;
z-index: 2;
cursor: pointer;
color: #9B9B9B;
width: 28px;
height: 28px;
:hover {
background: none;
}
`
export const LinkWrapper = styled.a``

@ -0,0 +1,71 @@
import type { MouseEvent } from 'react'
import includes from 'lodash/includes'
import type { AdType } from 'requests'
import { useAd } from '../AdComponent/hooks'
import { EventGA, PLAYER_MOBILE_FULL_SCREEN } from '../../types'
import {
AdsCloseButton,
Img,
MobileAdWrapper,
Video,
} from './styled'
type MobileAdTypes = {
ad: AdType,
}
export const MobileAd = ({ ad }: MobileAdTypes) => {
const {
link,
media,
position,
} = ad
const {
handleClose,
isNeedToShow,
isOpenAd,
isOpenCloseBtn,
isVideo,
sendBannerClickEvent,
} = useAd({ ad })
const close = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
return handleClose()
}
const onLinkClick = () => {
if (link) {
sendBannerClickEvent(EventGA.CLICK)
window.open(link, '_blank')
}
}
return (
position && isOpenAd && isNeedToShow ? (
<MobileAdWrapper
position={position.id}
onClick={onLinkClick}
>
{isOpenCloseBtn
&& (
<AdsCloseButton
position={position.id}
onClick={close}
size={includes(PLAYER_MOBILE_FULL_SCREEN, position.id) ? 12 : 8}
/>
)}
{isVideo
? <Video position={position.id} src={media.url} />
: (
<Img position={position.id} src={media.url} />
)}
</MobileAdWrapper>
) : null
)
}

@ -0,0 +1,112 @@
import styled, { css } from 'styled-components/macro'
import includes from 'lodash/includes'
import { CloseButton } from 'features/PopupComponents'
import {
MATCH_ADS,
PLAYER_MOBILE_FULL_SCREEN,
PLAYER_MOBILE_ADS,
} from '../../types'
type Props = {
position: number,
}
const matchMobileAds = [16, 17, 22, 23, 24]
const chooseStyle = (type: number) => {
switch (true) {
case MATCH_ADS.PLAYS_BOTTOM_MOBILE === type:
return css`
grid-row: 4;
margin-bottom: 12px;
height: 48px;
`
case PLAYER_MOBILE_ADS === type:
return css`
position: absolute;
width: 92%;
bottom: 50px;
left: 15px;
`
case PLAYER_MOBILE_FULL_SCREEN.VERTICAL_FULL_SCREEN === type:
case PLAYER_MOBILE_FULL_SCREEN.HORIZONTAL_FULL_SCREEN === type:
return css`
position: absolute;
top: 0;
left: 0;
height: 100%;
padding: 5px;
background-color: rgba(0, 0, 0, 0.7);
`
default:
return ''
}
}
export const MobileAdWrapper = styled.div<Props>`
position: relative;
width: 100%;
z-index: ${({ position }) => (includes(PLAYER_MOBILE_FULL_SCREEN, position) ? '101' : '100')};
${({ position }) => chooseStyle(position)};
`
export const AdsCloseButton = styled(CloseButton)<Props>`
position: absolute;
right: ${({ position }) => (includes(PLAYER_MOBILE_FULL_SCREEN, position) ? '-5px' : '-10px')};
top: ${({ position }) => (includes(PLAYER_MOBILE_FULL_SCREEN, position) ? '15px' : '10px')};
background: none;
border-radius: 0;
transform: translate(-50%, -50%);
z-index: 2;
color: #9B9B9B;
`
export const Img = styled.img<Props>`
border-radius: 2px;
width: 100%;
object-fit: ${({ position }) => {
switch (true) {
case position === PLAYER_MOBILE_FULL_SCREEN.VERTICAL_FULL_SCREEN:
return 'fill'
case position === MATCH_ADS.PLAYS_TOP_MOBILE:
return 'contain'
default:
return 'cover'
}
}};
height: ${({ position }) => {
switch (true) {
case position === 10:
return '50px'
case includes(matchMobileAds, position):
return '100%'
default:
return '75px'
}
}}
`
export const Video = styled.video<Props>`
max-height: 100%;
object-fit: cover;
min-width: 100%;
height: ${({ position }) => (position === 10 ? '50px' : '75px')};
border-radius: 2px;
height: ${({ position }) => {
switch (true) {
case position === 10:
return '50px'
case position === MATCH_ADS.PLAYS_TOP_MOBILE:
return '48px'
default:
return '75px'
}
}}
`

@ -0,0 +1,16 @@
import type { AdResponse, AdsListType } from 'requests'
export const calcMaxAdDurationAds = (advertisements: AdResponse) => {
const allAds = Object.values(advertisements)
const combineAds = allAds.reduce((result, currentAd) => {
result.push(...currentAd)
return result
}, [] as AdsListType)
const maxDuration = combineAds
.reduce((result, { duration }) => Math.max(result, duration), 0)
return maxDuration
}

@ -0,0 +1 @@
export * from './isVideo'

@ -0,0 +1,2 @@
const regexp = /^https?:\/\/\S+(?:mp4)$/
export const checkVideo = (url: string) => regexp.test(url)

@ -0,0 +1,65 @@
import { useMemo } from 'react'
import { useQuery } from 'react-query'
import { useRecoilState } from 'recoil'
import { isMobileDevice, querieKeys } from 'config'
import { getAds } from 'requests'
import { isMatchPage } from 'helpers/isMatchPage'
import { useLang } from 'features/LexicsStore/hooks/useLang'
import { useAuthStore } from 'features/AuthStore'
import {
DeviceType,
PageType,
} from './types'
import { calcMaxAdDurationAds } from './helpers/calcMaxDurationAds'
import { adsStore } from '../../pages/HighlightsPage/storeHighlightsAtoms'
type Props = {
matchId?: number,
sportType?: number,
tournamentId?: number,
}
export const useAds = ({
matchId,
sportType,
tournamentId,
}: Props) => {
const [ads, setAds] = useRecoilState(adsStore)
const { lang } = useLang()
const { user } = useAuthStore()
useQuery({
enabled: isMatchPage() ? (!!user && !!tournamentId) : !!user,
queryFn: async () => {
const adsList = await getAds({
client_type: isMobileDevice ? DeviceType.MOBILE : DeviceType.WEB,
language: lang,
type_id: isMatchPage() ? PageType.MATCH : PageType.HOME,
...isMatchPage() && {
matches: [{
match_id: matchId,
sport_id: sportType,
}],
tournaments: [{
sport_id: sportType,
tournament_id: tournamentId,
}],
},
})
adsList && setAds(adsList)
return adsList
},
queryKey: [querieKeys.ads, matchId],
staleTime: useMemo(() => Math.max(calcMaxAdDurationAds(ads), 60 * 1000), [ads]),
})
return {
ads,
}
}

@ -0,0 +1,31 @@
import type { AdType } from 'requests'
import { isMobileDevice } from 'config'
import { AdComponent } from './components/AdComponent'
import { AdsPropsType } from './types'
import { MobileAd } from './components/MobileAd'
import {
HeaderWrapAd,
} from './styled'
export const HeaderAds = ({ ads }: AdsPropsType) => (
ads?.length ? (
<HeaderWrapAd column={ads?.length}>
{ads.map((ad: AdType) => (
!isMobileDevice ? (
<AdComponent
ad={ad}
key={ad.id}
/>
) : (
<MobileAd
ad={ad}
key={ad.id}
/>
)
))}
</HeaderWrapAd>
) : null
)

@ -0,0 +1,16 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config'
export const HeaderWrapAd = styled.div<{column: number}>`
width: 100%;
margin-bottom: 0.7rem;
display: grid;
grid-column-gap: 0.9rem;
grid-template-columns: ${({ column }) => (column > 1 ? `repeat(${column},${16.3 * 6 / column}%)` : 'repeat(1, 98.7%)')};
${isMobileDevice && css`
padding: 0 0.71rem;
grid-template-columns: none;
`}}
`

@ -0,0 +1,57 @@
import { AdsListType } from 'requests'
export enum PageType {
HOME = 1,
MATCH = 2,
}
export enum DeviceType {
MOBILE = 'mobile',
WEB = 'web'
}
export type ViewsType = Partial<Record<keyof typeof PageType, number>>
export enum EventGA {
CLICK = 'banner_click',
CLOSE = 'banner_close',
DISPLAY = 'banner_display'
}
export enum VIEW_ADS {
ROW = 4,
COLUMN = 5,
SQUARE = 6,
SECOND_COLUMN = 2,
SECOND_ROW = 3,
MOBILE_IN_COLLAPSE_HEADER = 12,
MOBILE_IN_COLLAPSE_FOOTER = 25
}
export const HEADER_MOBILE_ADS = [10, 11]
export enum MATCH_ADS {
WATCH_TOP = 13,
PLAYS_TOP = 14,
PLAYS_BOTTOM = 15,
PLAYS_TOP_MOBILE = 16,
PLAYS_BOTTOM_MOBILE = 17,
}
export enum PLAYER_ADS {
LEFT_BOTTOM = 18,
CENTER_BOTTOM = 19,
RIGHT = 20,
FULL_SCREEN = 21,
}
export const PLAYER_MOBILE_ADS = 22
export enum PLAYER_MOBILE_FULL_SCREEN {
VERTICAL_FULL_SCREEN = 23,
HORIZONTAL_FULL_SCREEN = 24,
}
export type AdsPropsType = Record<'ads', AdsListType | undefined>
export const adsViews = 'adsViews'

@ -12,3 +12,4 @@ export * from './userAgent'
export * from './queries'
export * from './keyboardKeys'
export * from './clients'
export * from './localStorageKeys'

@ -0,0 +1 @@
export const COUNTRY = 'COUNTRY'

@ -1,4 +1,5 @@
export const querieKeys = {
ads: 'ads',
liveMatchScores: 'liveMatchScores',
matchScore: 'matchScore',
sportsList: 'sportsList',

@ -1,5 +1,7 @@
export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
export const device = navigator.userAgent
export const isAndroid = /Android/.test(navigator.userAgent)
export const isIOS = /iPad|iPhone|iPod/.test(device)
export const isMobileDevice = /iPhone|Android/.test(navigator.userAgent)
export const isAndroid = /Android/.test(device)
export const isMobileDevice = /iPhone|Android/.test(device)

@ -10,11 +10,15 @@ import {
addDays,
} from 'date-fns'
import { useToggle } from 'hooks'
import isObject from 'lodash/isObject'
import { useLocalStore, useToggle } from 'hooks'
import { useLexicsStore } from 'features/LexicsStore'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { ViewsType } from 'components/Ads/types'
import {
getDisplayDate,
getMonths,
@ -61,6 +65,15 @@ export const useDateFilter = () => {
const parseFilters = filters && JSON.parse(filters)
const lastDate = parseFilters?.selectedDate
const weekName = getWeekName(selectedDate, 'en')
const validator = (value: unknown) => Boolean(value) && isObject(value)
const [adsViews, setAdsViews] = useLocalStore<ViewsType>({
clearOnUnmount: true,
defaultValue: { HOME: 0 },
key: 'adsViews',
validator,
})
useEffect(() => {
if (lastDate === selectedDate.getDate()
&& parseFilters
@ -72,6 +85,10 @@ export const useDateFilter = () => {
setIsShowTournament(true)
setSelectedFilters([])
setSelectedLeague(['all_competitions'])
setAdsViews({
...adsViews,
HOME: (adsViews.HOME ?? 0) + 1,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedDate])

@ -14,10 +14,19 @@ import { ClientNames } from 'config/clients/types'
import { useAuthStore } from 'features/AuthStore'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { getHomeMatches } from 'requests/getMatches'
import { getAgreements, setAgreements } from 'requests/getAgreements'
import {
getHomeMatches,
getAgreements,
setAgreements,
getCountryCode,
} from 'requests'
import { setLocalStorageItem } from 'helpers'
import { COUNTRY } from 'config'
import { isSportFilterShownAtom } from './Atoms/HomePageAtoms'
import { useAds } from '../../components/Ads/hooks'
/**
* возвращает смещение в минутах относительно UTC
@ -34,7 +43,7 @@ const getTimezoneOffset = (date: Date) => {
const getDate = (date: Date) => format(date, 'yyyy-MM-dd')
export const useHomePage = () => {
const { user, userInfo } = useAuthStore()
const { userInfo } = useAuthStore()
const {
isMonthMode,
selectedDate,
@ -44,6 +53,9 @@ export const useHomePage = () => {
const [isOpenDownload, setIsOpenDownload] = useState(false)
const [isShowConfirmPopup, setIsShowConfirmPopup] = useState(false)
const setIsSportFilterShown = useSetRecoilState(isSportFilterShownAtom)
const { ads } = useAds({})
const date = isMonthMode ? selectedMonthModeDate : selectedDate
const dateTo = isMonthMode
? `${getDate(endOfMonth(selectedMonthModeDate))} 00:00:00`
@ -55,6 +67,13 @@ export const useHomePage = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setIsShowConfirmPopup, userInfo])
const countryCode = async () => {
const { country_code } = await getCountryCode()
country_code && setLocalStorageItem(COUNTRY, country_code)
return country_code
}
useEffect(() => {
if (userInfo?.email) {
(async () => {
@ -74,7 +93,10 @@ export const useHomePage = () => {
) {
setIsOpenDownload(true)
}
countryCode()
}, [])
const fetchMatches = useCallback(
(limit: number, offset: number) => getHomeMatches({
date: getDate(date),
@ -86,7 +108,7 @@ export const useHomePage = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
[
selectedDate,
user,
userInfo?.email,
dateTo,
date,
],
@ -99,6 +121,7 @@ export const useHomePage = () => {
}, [setIsSportFilterShown])
return {
ads,
fetchMatches,
handleCloseConfirmPopup,
isOpenDownload,

@ -8,7 +8,6 @@ import { Matches } from 'features/Matches'
import {
HeaderFiltersStore,
} from 'features/HeaderFilters'
import {
PageWrapper,
Main,
@ -16,6 +15,9 @@ import {
} from 'features/PageLayout'
import { UserFavorites } from 'features/UserFavorites'
import { HEADER_MOBILE_ADS } from 'components/Ads/types'
import { HeaderAds } from 'components/Ads'
import { useHomePage } from './hooks'
import { Header } from './components/Header'
import { HeaderMobile } from '../HeaderMobile'
@ -24,6 +26,7 @@ import { HeaderFilters } from './components/HeaderFilters'
const Home = () => {
usePageLogger(PAGES.home)
const {
ads,
fetchMatches,
handleCloseConfirmPopup,
isOpenDownload,
@ -46,8 +49,17 @@ const Home = () => {
<Main>
<UserFavorites />
<Content>
{isMobileDevice ? null : <HeaderFilters />}
{/* {userInfo?.email && <Matches fetch={fetchMatches} />} */}
{!isMobileDevice && <HeaderFilters />}
{userInfo?.email
&& ads
&& (
<HeaderAds ads={
isMobileDevice
? ads?.mobile?.filter(({ position }) => HEADER_MOBILE_ADS.includes(position.id))
: ads?.header
}
/>
)}
<Matches fetch={fetchMatches} />
<ConfirmPopup
isModalOpen={isShowConfirmPopup}

@ -1,10 +1,16 @@
import { useEffect } from 'react'
import {
useEffect,
useMemo,
useState,
} from 'react'
import { useHistory } from 'react-router'
import { useTour } from '@reactour/tour'
import { useTheme } from 'styled-components'
import find from 'lodash/find'
import { ProfileHeader } from 'features/ProfileHeader'
import { UserFavorites } from 'features/UserFavorites'
import { useUserFavoritesStore } from 'features/UserFavorites/store'
@ -19,6 +25,7 @@ import {
ProfileTypes,
isIOS,
client,
isMobileDevice,
} from 'config'
import { usePageLogger, usePageParams } from 'hooks'
@ -31,6 +38,9 @@ import { MatchPageStore, useMatchPageStore } from './store'
import { SubscriptionGuard } from './components/SubscriptionGuard'
import { LiveMatch } from './components/LiveMatch'
import { FavouriteTeamPopup } from './components/FavouriteTeam'
import { PLAYER_MOBILE_FULL_SCREEN } from '../../components/Ads/types'
import { MobileAd } from '../../components/Ads/components/MobileAd'
import { Wrapper } from './styled'
const MatchPageComponent = () => {
@ -40,6 +50,7 @@ const MatchPageComponent = () => {
const { colors } = useTheme()
const {
ads,
isStarted,
profile,
user,
@ -54,6 +65,11 @@ const MatchPageComponent = () => {
const { isOpen } = useTour()
const {
HORIZONTAL_FULL_SCREEN,
VERTICAL_FULL_SCREEN,
} = PLAYER_MOBILE_FULL_SCREEN
useEffect(() => {
let timer = 0
timer = window.setTimeout(() => {
@ -95,11 +111,29 @@ const MatchPageComponent = () => {
history.push(`/${sportName}/tournaments/${profile.tournament.id}`)
}
const [orientation, setOrientation] = useState(window.orientation)
useEffect(() => {
const handleOrientationChange = () => setOrientation(window.orientation)
window.addEventListener('orientationchange', handleOrientationChange)
return () => window.removeEventListener('orientationchange', handleOrientationChange)
}, [])
const currentAds = useMemo(() => (
window.orientation === 0
? find(ads.mobile, (ad) => ad.position.id === VERTICAL_FULL_SCREEN)
: find(ads.mobile, (ad) => ad.position.id === HORIZONTAL_FULL_SCREEN)
// eslint-disable-next-line react-hooks/exhaustive-deps
), [ads.mobile, orientation])
return (
<PageWrapper
isIOS={isIOS}
isTourOpen={Boolean(isOpen)}
>
{isMobileDevice
&& currentAds
&& <MobileAd ad={currentAds} />}
<ProfileHeader color={colors.matchHeaderBackground} height={client.name === 'facr' ? 5 : 4.5} />
<Main>
<UserFavorites />

@ -29,6 +29,8 @@ import { usePageParams, useToggle } from 'hooks'
import { redirectToUrl } from 'helpers/redirectToUrl'
import { parseDate } from 'helpers/parseDate'
import { useAds } from 'components/Ads/hooks'
import { useTournamentData } from './useTournamentData'
import { useMatchData } from './useMatchData'
import { useFiltersPopup } from './useFitersPopup'
@ -71,6 +73,7 @@ const ACCESS_TIME = 60
export const useMatchPage = () => {
const [matchProfile, setMatchProfile] = useState<MatchInfo>(null)
const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false)
const [isFullscreen, setIsFullScreen] = useState(false)
const [access, setAccess] = useState(true)
const [playingProgress, setPlayingProgress] = useState(0)
const [playingData, setPlayingData] = useState<PlayingData>(initPlayingData)
@ -87,6 +90,12 @@ export const useMatchPage = () => {
const { profileId: matchId, sportType } = usePageParams()
const { ads } = useAds({
matchId,
sportType,
tournamentId: matchProfile?.tournament?.id,
})
useEffect(() => {
sessionStorage.removeItem('isFromLanding')
}, [])
@ -401,6 +410,7 @@ export const useMatchPage = () => {
activeFirstTeamPlayers,
activeSecondTeamPlayers,
activeStatus,
ads,
allActionsToggle,
allPlayersToggle,
applyFilters,
@ -421,6 +431,7 @@ export const useMatchPage = () => {
isEmptyFilters,
isExpanded,
isFirstTeamPlayersChecked,
isFullscreen,
isLiveMatch,
isOpenFiltersPopup,
isPlayFilterEpisodes: isStatsPlaylist ? isStatsPlayFilterEpisodes : isPlayFilterEpisodes,
@ -450,6 +461,7 @@ export const useMatchPage = () => {
setCircleAnimation: isStatsPlaylist ? setStatsCircleAnimation : setCircleAnimation,
setEpisodeInfo,
setFullMatchPlaylistDuration,
setIsFullScreen,
setIsPlayingFiltersEpisodes: isStatsPlaylist
? setStatsIsPlayinFiltersEpisodes
: setIsPlayersStatsFetching,

@ -3,9 +3,14 @@ import { useEffect } from 'react'
import map from 'lodash/map'
import find from 'lodash/find'
import type { Events, MatchInfo } from 'requests'
import type {
Events,
MatchInfo,
AdType,
} from 'requests'
import { isLffClient } from 'config/clients'
import { isMobileDevice } from 'config/userAgent'
import { T9n } from 'features/T9n'
import type {
@ -13,11 +18,14 @@ import type {
PlaylistOption,
} from 'features/MatchPage/types'
import { PlaylistTypes } from 'features/MatchPage/types'
import { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsStore } from 'features/LexicsStore'
import { Tabs } from 'features/MatchSidePlaylists/config'
import { AdComponent } from 'components/Ads/components/AdComponent'
import { MobileAd } from 'components/Ads/components/MobileAd'
import { MATCH_ADS } from 'components/Ads/types'
import { isEqual } from '../../helpers'
import { EventButton } from '../EventButton'
import {
@ -30,6 +38,7 @@ import {
type Props = {
disablePlayingEpisodes?: () => void,
events: Events,
isFirstBlock?: boolean,
onSelect: (option: PlaylistOption) => void,
profile: MatchInfo,
selectedPlaylist?: PlaylistOption,
@ -39,12 +48,14 @@ type Props = {
export const EventsList = ({
disablePlayingEpisodes,
events,
isFirstBlock,
onSelect,
profile,
selectedPlaylist,
setWatchAllEpisodesTimer,
}: Props) => {
const {
ads,
filteredEvents,
isPlayingEpisode,
selectedTab,
@ -52,6 +63,8 @@ export const EventsList = ({
} = useMatchPageStore()
const { suffix, translate } = useLexicsStore()
const { PLAYS_BOTTOM, PLAYS_BOTTOM_MOBILE } = MATCH_ADS
useEffect(() => {
if (selectedPlaylist?.tab === Tabs.EVENTS && isPlayingEpisode) {
const {
@ -84,6 +97,14 @@ export const EventsList = ({
return (
<List>
{ads
&& isFirstBlock
&& (isMobileDevice
? map(ads?.mobile, (ad: AdType) => ad?.position.id === PLAYS_BOTTOM_MOBILE
&& <MobileAd ad={ad} key={ad.id} />)
: map(ads?.match, (ad: AdType) => ad?.position.id === PLAYS_BOTTOM
&& <AdComponent ad={ad} key={ad.id} />)
)}
{map(events, (event) => {
if (!event.t && !event.pl) {
return (

@ -5,6 +5,7 @@ import styled, { css } from 'styled-components/macro'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import some from 'lodash/some'
import { isMobileDevice } from 'config'
@ -19,6 +20,8 @@ import { T9n } from 'features/T9n'
import { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsStore } from 'features/LexicsStore'
import { MATCH_ADS } from 'components/Ads/types'
import { PlayButton } from '../PlayButton'
import { MatchDownloadButton } from '../MatchDownloadButton'
@ -31,8 +34,12 @@ type Props = {
selectedMathPlaylist?: PlaylistOption,
}
const List = styled.ul`
margin-bottom: ${LIST_INDENT}px;
type ListProps = {
isAdsExist?: boolean,
}
const List = styled.ul<ListProps>`
margin-bottom: ${({ isAdsExist }) => (isAdsExist ? '15px' : `${LIST_INDENT}px`)};
`
export const Item = styled.li`
@ -62,7 +69,7 @@ export const MatchPlaylists = forwardRef(
}: Props,
ref: ForwardedRef<HTMLUListElement>,
) => {
const { setEpisodeInfo } = useMatchPageStore()
const { ads, setEpisodeInfo } = useMatchPageStore()
const { translate } = useLexicsStore()
const handleButtonClick = (playlist: MatchPlaylistOption) => {
@ -76,7 +83,10 @@ export const MatchPlaylists = forwardRef(
}
return (
<List ref={ref}>
<List
ref={ref}
isAdsExist={some(ads?.match, ({ position }) => position.id === MATCH_ADS.WATCH_TOP)}
>
{
map(playlists, (playlist) => (
<Item

@ -129,6 +129,7 @@ export const TabEvents = ({
profile={profile}
selectedPlaylist={selectedPlaylist}
setWatchAllEpisodesTimer={setWatchAllEpisodesTimer}
isFirstBlock={isFirstBlock}
/>
</HalfEvents>
)

@ -25,7 +25,10 @@ export const HalfList = styled.ul`
export const HalfEvents = styled.li``
export const List = styled.ul``
export const List = styled.ul`
display: grid;
grid-template-columns: 100%;
`
export const Event = styled.li`
width: 100%;

@ -6,13 +6,21 @@ import {
import size from 'lodash/size'
import filter from 'lodash/filter'
import map from 'lodash/map'
import { isMobileDevice } from 'config/userAgent'
import type {
PlaylistOption,
Playlists,
TournamentData,
} from 'features/MatchPage/types'
import type { MatchInfo } from 'requests'
import { useMatchPageStore } from 'features/MatchPage/store'
import type { MatchInfo, AdType } from 'requests'
import { AdComponent } from 'components/Ads/components/AdComponent'
import { MATCH_ADS } from 'components/Ads/types'
import { DropdownSection } from '../DropdownSection'
import { MatchPlaylists, LIST_INDENT } from '../MatchPlaylists'
@ -34,6 +42,8 @@ export const TabWatch = ({
selectedPlaylist,
tournamentData,
}: Props) => {
const { ads } = useMatchPageStore()
const matchPlaylistsRef = useRef<HTMLUListElement>(null)
const additionalScrollHeight = (matchPlaylistsRef.current?.clientHeight || 0) + LIST_INDENT
@ -55,6 +65,12 @@ export const TabWatch = ({
onSelect={onSelect}
live={profile?.live}
/>
{!isMobileDevice
&& ads
&& (
map(ads?.match, (ad: AdType) => ad?.position.id === MATCH_ADS.WATCH_TOP
&& <AdComponent ad={ad} key={ad.id} />)
)}
<DropdownSection
itemsCount={size(playlists.interview)}
title={playlists.lexics?.interview}

@ -7,6 +7,8 @@ import { createPortal } from 'react-dom'
import { useTour } from '@reactour/tour'
import map from 'lodash/map'
import type { PlaylistOption } from 'features/MatchPage/types'
import { useMatchPageStore } from 'features/MatchPage/store'
import {
@ -19,16 +21,23 @@ import { Overlay } from 'components/Overlay'
import { useEventListener, useModalRoot } from 'hooks'
import { isIOS } from 'config/userAgent'
import { AdType } from 'requests'
import { isIOS, isMobileDevice } from 'config/userAgent'
import { getLocalStorageItem } from 'helpers/getLocalStorage'
import { MATCH_ADS } from 'components/Ads/types'
import { MobileAd } from 'components/Ads/components/MobileAd'
import { AdComponent } from 'components/Ads/components/AdComponent'
import { Tabs } from './config'
import { TabEvents } from './components/TabEvents'
import { TabWatch } from './components/TabWatch'
import { TabPlayers } from './components/TabPlayers'
import { TabStats } from './components/TabStats'
import { useMatchSidePlaylists } from './hooks'
import {
Wrapper,
TabsWrapper,
@ -38,6 +47,7 @@ import {
TabTitle,
Container,
TabButton,
EventsAdsWrapper,
} from './styled'
const tabPanes = {
@ -57,6 +67,7 @@ export const MatchSidePlaylists = ({
selectedPlaylist,
}: Props) => {
const {
ads,
hideProfileCard,
matchPlaylists: playlists,
profile,
@ -86,6 +97,8 @@ export const MatchSidePlaylists = ({
const [hasTabPaneScroll, setTabPaneScroll] = useState(false)
const { PLAYS_TOP, PLAYS_TOP_MOBILE } = MATCH_ADS
useEffect(() => {
const {
clientHeight = 0,
@ -139,6 +152,17 @@ export const MatchSidePlaylists = ({
{showTabs
&& (
<TabsWrapper>
{selectedTab === Tabs.EVENTS
&& ads
&& (
<EventsAdsWrapper hasScroll={hasTabPaneScroll}>
{isMobileDevice
? map(ads?.mobile, (ad: AdType) => ad?.position.id === PLAYS_TOP_MOBILE
&& <MobileAd ad={ad} key={ad.id} />)
: map(ads?.match, (ad: AdType) => ad?.position.id === PLAYS_TOP
&& <AdComponent ad={ad} key={ad.id} />)}
</EventsAdsWrapper>
)}
<TabsGroup>
<Tab
aria-pressed={selectedTab === Tabs.WATCH}

@ -17,7 +17,6 @@ type WrapperProps = {
export const Wrapper = styled.div<WrapperProps>`
padding-right: 14px;
padding-top: 10px;
${({ highlighted }) => (highlighted
? css`
@ -58,6 +57,7 @@ export const TabsGroup = styled.div.attrs({ role: 'tablist' })`
display: flex;
justify-content: center;
gap: ${isMobileDevice ? 30 : 20}px;
padding-top: 10px;
`
export const TabTitle = styled(T9n)`
@ -129,7 +129,7 @@ export const Container = styled.div<TContainer>`
width: 320px;
margin-top: 14px;
max-height: calc(100vh - 130px);
overflow-y: ${({ forWatchTab }) => (forWatchTab ? 'hidden' : 'auto')};
overflow-y: auto;
padding-right: ${({ forWatchTab }) => (forWatchTab ? '0' : '')};
padding-left: 14px;
padding-right: ${({ hasScroll }) => (hasScroll ? '10px' : '')};
@ -253,3 +253,14 @@ export const BlockTitle = styled.span`
color: rgba(255, 255, 255, 0.5);
text-transform: uppercase;
`
export const EventsAdsWrapper = styled.div<TContainer>`
width: ${({ hasScroll }) => (hasScroll ? '288px' : '306px')};
margin-bottom: 0.6rem;
${isMobileDevice
? css`
width: 100%;
`
: ''};
`

@ -1,10 +1,12 @@
import { memo, useEffect } from 'react'
import { useRouteMatch } from 'react-router-dom'
import { useQuery } from 'react-query'
import { PAGES } from 'config/pages'
import { isMobileDevice, PAGES } from 'config'
import type { LiveScore } from 'requests'
import type { AdType, LiveScore } from 'requests'
import { getLiveScores } from 'requests'
import { MatchCard } from 'features/MatchCard'
@ -12,9 +14,14 @@ import { TournamentList } from 'features/TournamentList'
import type { Match } from 'features/Matches'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { Wrapper } from './styled'
import { readToken } from 'helpers'
import { useMatchSwitchesStore } from '../MatchSwitches'
import { useHomePage } from '../HomePage/hooks'
import { querieKeys } from '../../config'
import { AdComponent } from '../../components/Ads/components/AdComponent'
import { Wrapper } from './styled'
type MatchesGridProps = {
matches: Array<Match>,
@ -24,6 +31,10 @@ export const MatchesGrid = memo(({ matches }: MatchesGridProps) => {
const isHomePage = useRouteMatch(PAGES.home)?.isExact
const { isScoreHidden } = useMatchSwitchesStore()
const { ads } = useHomePage()
const currentAds = ads?.match_cell?.length ? ads?.match_cell : ads?.block
const {
compareSport,
isShowTournament,
@ -76,6 +87,12 @@ export const MatchesGrid = memo(({ matches }: MatchesGridProps) => {
return (
<Wrapper>
{!isMobileDevice
&& readToken()
&& currentAds
&& (
currentAds?.map((ad: AdType) => <AdComponent ad={ad} />)
)}
{isHomePage && isShowTournament ? (
<TournamentList matches={filteredMatches()} />
) : (

@ -6,6 +6,7 @@ export const Wrapper = styled.ul`
display: grid;
grid-gap: 0.9rem;
grid-template-columns: repeat(6, 15.7%);
${isMobileDevice
? css`
display: flex;

@ -118,6 +118,7 @@ export const useVideoPlayer = ({
playNextEpisode,
selectedPlaylist,
setCircleAnimation,
setIsFullScreen,
setPlayingProgress,
} = useMatchPageStore()
@ -673,6 +674,13 @@ export const useVideoPlayer = ({
profileId,
])
useEffect(() => {
setIsFullScreen(isFullscreen)
}, [
isFullscreen,
setIsFullScreen,
])
return {
activeChapterIndex,
allPlayedProgress: playedProgress + getActiveChapter().startMs,

@ -1,5 +1,7 @@
import { Fragment } from 'react'
import includes from 'lodash/includes'
import { isMobileDevice } from 'config'
import { Loader } from 'features/Loader'
@ -10,8 +12,15 @@ import { FiltersPopup } from 'features/MatchSidePlaylists/components/FiltersPopu
import { WaterMark } from 'components/WaterMark'
import { AccessTimer } from 'components/AccessTimer'
import { PLAYER_ADS, PLAYER_MOBILE_ADS } from 'components/Ads/types'
import { HeaderAds } from 'components/Ads'
import { REWIND_SECONDS } from './config'
import type { Props } from './hooks'
import { useVideoPlayer } from './hooks'
import { useAuthStore } from '../AuthStore'
import { Controls } from './components/Controls'
import RewindMobile from './components/RewindMobile'
import {
PlayerWrapper,
@ -27,12 +36,8 @@ import {
EpisodeInfoOrder,
EpisodeInfoDivider,
CloseButton,
PlayerAdsWrapper,
} from './styled'
import type { Props } from './hooks'
import { useVideoPlayer } from './hooks'
import { useAuthStore } from '../AuthStore'
import { Controls } from './components/Controls'
import RewindMobile from './components/RewindMobile'
const tournamentsWithWatermark = {
316: 'Tunisia',
@ -44,6 +49,7 @@ const tournamentsWithWatermark = {
export const StreamPlayer = (props: Props) => {
const {
access,
ads,
episodeInfo,
isOpenFiltersPopup,
isPlayingEpisode,
@ -120,6 +126,17 @@ export const StreamPlayer = (props: Props) => {
onTouchEnd={onTouchEnd}
isPlayingEpisode={isPlayingEpisode}
>
{ads
&& (
<PlayerAdsWrapper isFullscreen={isFullscreen}>
<HeaderAds ads={
isMobileDevice
? ads?.mobile?.filter(({ position }) => position.id === PLAYER_MOBILE_ADS)
: ads?.match?.filter(({ position }) => includes(PLAYER_ADS, position.id))
}
/>
</PlayerAdsWrapper>
)}
{isPlayingEpisode && (
<EpisodeInfo>
<EpisodeInfoName>

@ -17,6 +17,10 @@ type HoverStylesProps = {
visible: boolean,
}
type PlayerAdsProps = {
isFullscreen: boolean,
}
export const hoverStyles = css<HoverStylesProps>`
transition: opacity 0.3s ease-in-out;
${({ visible }) => (visible
@ -485,3 +489,8 @@ export const CloseButton = styled(CloseButtonBase)`
height: max(1.2rem, 23px);
}
`
export const PlayerAdsWrapper = styled.div<PlayerAdsProps>`
opacity: ${({ isFullscreen }) => (isFullscreen ? 0 : 1)};
`

@ -1,9 +1,15 @@
import { Fragment, useState } from 'react'
import { useRecoilValue } from 'recoil'
import { isLffClient } from 'config/clients'
import { URL_AWS } from 'config'
import {
isMobileDevice,
URL_AWS,
} from 'config'
import { AdType } from 'requests'
import { SportIcon } from 'components/SportIcon/SportIcon'
import { T9n } from 'features/T9n'
import { Icon } from 'features/Icon'
import type { Match } from 'features/Matches'
@ -14,6 +20,11 @@ import {
LiveSign,
} from 'features/MatchCard/CardFrontside/MatchCardMobile/styled'
import { VIEW_ADS } from 'components/Ads/types'
import { SportIcon } from 'components/SportIcon/SportIcon'
import { adsStore } from 'pages/HighlightsPage/storeHighlightsAtoms'
import {
CardWrapperOuter,
CardWrapper,
@ -22,6 +33,7 @@ import {
ScFirstInfo,
ScMatchesWrapper,
ScSecondInfo,
TournamentMobileAd,
} from './styled'
import { TournamentProps } from '../..'
@ -40,6 +52,8 @@ export const TournamentMobile = ({
const currentColor = open ? '#ffffff' : 'rgba(255, 255, 255, 0.5)'
const ads = useRecoilValue(adsStore)
return (
<CardWrapperOuter>
<CardWrapper
@ -75,10 +89,27 @@ export const TournamentMobile = ({
</ScSecondInfo>
</CardWrapper>
<ScMatchesWrapper>
{open
&& tournamentMatches?.map((match: Match) => (
<MatchCard key={match.id} match={match} />
))}
{open && (
<>
{isMobileDevice && ads?.mobile?.map((ad: AdType) => (
ad.position.id === VIEW_ADS.MOBILE_IN_COLLAPSE_HEADER && (
<TournamentMobileAd
ad={ad}
key={ad.id}
/>
)))}
{tournamentMatches?.map((match: Match) => (
<MatchCard key={match.id} match={match} />
))}
{isMobileDevice && ads?.mobile?.map((ad: AdType) => (
ad.position.id === VIEW_ADS.MOBILE_IN_COLLAPSE_FOOTER && (
<TournamentMobileAd
ad={ad}
key={ad.id}
/>
)))}
</>
)}
</ScMatchesWrapper>
</CardWrapperOuter>
)

@ -1,7 +1,10 @@
import styled, { css } from 'styled-components/macro'
import { Name } from 'features/Name'
import { Icon } from 'features/Icon'
import { MobileAd } from 'components/Ads/components/MobileAd'
import { isMobileDevice } from 'config/userAgent'
export const CardWrapperOuter = styled.li.attrs({
@ -82,6 +85,10 @@ export const ScMatchesWrapper = styled.ul`
flex-direction: column;
`
export const TournamentMobileAd = styled(MobileAd)`
margin: 6px 0 0;
`
export const ScStar = styled(Icon)`
display: flex;
justify-content: center;

@ -1,6 +1,6 @@
import { atom, selector } from 'recoil'
import type { Match } from 'requests'
import type { AdResponse, Match } from 'requests'
export type MatchType = Match & {
isChecked: boolean,
@ -43,6 +43,11 @@ export const fetchingMatches = atom({
key: 'fetchingMatches',
})
export const adsStore = atom({
default: {} as AdResponse,
key: 'adsStore',
})
export const checkedMatches = selector({
get: ({ get }) => {
const matches = get(playerMatchesState)

@ -0,0 +1,65 @@
import { callApi } from 'helpers'
import { ADS_API_URL } from 'config'
import { DeviceType } from '../../components/Ads/types'
type MatchesParams = {
match_id?: number,
sport_id?: number,
}
type TournamentsParams = {
sport_id?: number,
tournament_id?: number,
}
export type AdsParams = {
client_type: DeviceType,
language: string,
matches?: Array<MatchesParams>,
tournaments?: Array<TournamentsParams>,
type_id: number,
}
export type AdType = {
duration: number,
frequency: number,
id: number,
impressions: number,
link: string,
media: {
url: string,
},
name: string,
position: {
id: number,
name_eng: string,
name_rus: string,
source_type: string,
},
remaining_views: number,
time_close: number,
type: {
id: number,
name_eng: string,
name_rus: string,
},
}
export type PositionName = 'header' | 'block' | 'match_cell' | 'mobile' | 'match'
export type AdResponse = Record<PositionName, AdsListType>
export type AdsListType = Array<AdType>
export const getAds = (params: AdsParams): Promise<AdResponse> => {
const config = {
body: params,
}
return callApi({
config,
url: ADS_API_URL,
})
}

@ -0,0 +1,2 @@
export * from './getAds'
export * from './updateAdsView'

@ -0,0 +1,29 @@
import { callApi } from 'helpers'
import { ADS_API_URL } from 'config'
export type AdsViewParams = {
adv_id: number,
}
type AdsViewResponse = {
data: string,
message: string,
reason: string,
status: 'failed' | 'success',
}
export const updateAdsView = (
{ adv_id }: AdsViewParams,
): Promise<AdsViewResponse> => {
const config = {
body: {
adv_id,
},
}
return callApi({
config,
url: `${ADS_API_URL}/${adv_id}/view`,
})
}

@ -36,3 +36,7 @@ export * from './getTokenVirtualUser'
export * from './checkDevice'
export * from './getPaymentOTTUrl'
export * from './getPaymentPayUrl'
export * from './getAds'
export * from './getFavouriteTeam'
export * from './getCountryCode'
export * from './getAgreements'

@ -1,31 +0,0 @@
/* eslint-disable */
import {
createServer,
Model,
} from 'miragejs'
import { ResponseType } from 'requests/getFavouriteTeam'
import { surveys } from './fixtures/surveys'
export function makeServer({ environment = 'test' } = {}) {
const server = createServer({
environment,
factories: {},
fixtures: {
surveys,
},
models: {
surveys: Model.extend<Partial<ResponseType>>({}),
},
routes() {
this.passthrough('https://api.insports.tv/***')
this.passthrough('https://insports.tv/***')
this.passthrough('https://images.insports.tv/***')
this.passthrough('https://auth.insports.tv/***')
this.passthrough('${URL_AWS}/***')
this.get('https://api.insports.tv/v1/survey/teams/1/131/30', (schema: any) => schema.all('surveys').models[0].attrs)
},
})
return server
}

@ -0,0 +1,592 @@
/* eslint-disable */
export const getAds = () => {
return {
"mobile": [
{
"id": 71,
"name": "Test 2",
"type": {
"id": 1,
"name_eng": "Main",
"name_rus": "Главная"
},
"position": {
"id": 12,
"source_type": "Web",
"name_eng": "Web main ad 2 (1x1)",
"name_rus": "Веб: главная 2 (1x1)"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 2,
"duration": 120,
"time_close": 150,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/71/en/web.png"
},
"remaining_views": 9
},{
"id": 71,
"name": "Test 2",
"type": {
"id": 1,
"name_eng": "Main",
"name_rus": "Главная"
},
"position": {
"id": 25,
"source_type": "Web",
"name_eng": "Web main ad 2 (1x1)",
"name_rus": "Веб: главная 2 (1x1)"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 2,
"duration": 120,
"time_close": 150,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/71/en/web.png"
},
"remaining_views": 9
},
{
"id": 72,
"name": "Best mens boots for sport advertise here",
"type": {
"id": 1,
"name_eng": "Main",
"name_rus": "Главная"
},
"position": {
"id": 11,
"source_type": "Web",
"name_eng": "Web main ad 7 (1x1)",
"name_rus": "Веб: главная 7 (1x1)"
},
"link": "https://www.google.com/",
"impressions": 2,
"frequency": 1,
"duration": 300,
"time_close": 500,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/72/en/web.png"
},
"remaining_views": 9
},
{
"id": 85,
"name": "Test 16",
"type": {
"id": 2,
"name_eng": "Match",
"name_rus": "Матч"
},
"position": {
"id": 16,
"source_type": "Mobile",
"name_eng": "Web top side ad horizontal",
"name_rus": "Веб: верхняя, боковая, по горизонтали"
},
"link": "https://www.google.com/",
"impressions": 60,
"frequency": 5,
"duration": 12000,
"time_close": 1,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/85/en/mobile.png"
},
"remaining_views": 60
},
{
"id": 86,
"name": "Test 17",
"type": {
"id": 2,
"name_eng": "Match",
"name_rus": "Матч"
},
"position": {
"id": 17,
"source_type": "Mobile",
"name_eng": "Web top side ad horizontal",
"name_rus": "Веб: верхняя, боковая, по горизонтали"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 5,
"duration": 12000,
"time_close": 1,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/86/en/mobile.png"
},
"remaining_views": 10
},
{
"id": 91,
"name": "Test 22",
"type": {
"id": 3,
"name_eng": "Player",
"name_rus": "Плеер"
},
"position": {
"id": 22,
"source_type": "Mobile",
"name_eng": "Web top side ad horizontal",
"name_rus": "Веб: верхняя, боковая, по горизонтали"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 10,
"duration": 12000,
"time_close": 1,
"media": {
"url": "https://i.imgur.com/5Tbygu1.png"
},
"remaining_views": 10
},
{
"id": 92,
"name": "Test 23",
"type": {
"id": 3,
"name_eng": "Player",
"name_rus": "Плеер"
},
"position": {
"id": 23,
"source_type": "Mobile",
"name_eng": "Web top side ad horizontal",
"name_rus": "Веб: верхняя, боковая, по горизонтали"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 5,
"duration": 12000,
"time_close": 1,
"media": {
"url": "https://i.imgur.com/PyZwEDq.png"
},
"remaining_views": 10
},
// {
// "id": 93,
// "name": "Test 24",
// "type": {
// "id": 3,
// "name_eng": "Player",
// "name_rus": "Плеер"
// },
// "position": {
// "id": 24,
// "source_type": "Mobile",
// "name_eng": "Web top side ad horizontal",
// "name_rus": "Веб: верхняя, боковая, по горизонтали"
// },
// "link": "https://www.google.com/",
// "impressions": 10,
// "frequency": 5,
// "duration": 12000,
// "time_close": 1,
// "media": {
// "url": "https://i.imgur.com/iwihMdx.png"
// },
// "remaining_views": 10
// },
],
"match_cell": [
{
"id": 71,
"name": "Test 2",
"type": {
"id": 1,
"name_eng": "Main",
"name_rus": "Главная"
},
"position": {
"id": 1,
"source_type": "Web",
"name_eng": "Web main ad 2 (1x1)",
"name_rus": "Веб: главная 2 (1x1)"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 2,
"duration": 15,
"time_close": 15,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/71/en/web.png"
},
"remaining_views": 9
},
{
"id": 71,
"name": "Test 2",
"type": {
"id": 1,
"name_eng": "Main",
"name_rus": "Главная"
},
"position": {
"id": 2,
"source_type": "Web",
"name_eng": "Web main ad 2 (1x1)",
"name_rus": "Веб: главная 2 (1x1)"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 2,
"duration": 12,
"time_close": 15,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/71/en/web.png"
},
"remaining_views": 9
},
{
"id": 72,
"name": "Test 3",
"type": {
"id": 1,
"name_eng": "Main",
"name_rus": "Главная"
},
"position": {
"id": 3,
"source_type": "Web",
"name_eng": "Web main ad 7 (1x1)",
"name_rus": "Веб: главная 7 (1x1)"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 3,
"duration": 15,
"time_close": 15,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/72/en/web.png"
},
"remaining_views": 9
}
],
"header": [
// {
// "id": 77,
// "name": "Test 8",
// "type": {
// "id": 1,
// "name_eng": "Main",
// "name_rus": "Главная"
// },
// "position": {
// "id": 8,
// "source_type": "Web",
// "name_eng": "Web main ad 2 (3x1)",
// "name_rus": "Веб: главная 2(3x1)"
// },
// "link": null,
// "impressions": 10,
// "frequency": 2,
// "duration": 120,
// "time_close": 15,
// "media": {
// "url": "https://cf-aws-staging.insports.tv/media/folder/77/en/web.png"
// },
// "remaining_views": 10
// },
// {
// "id": 78,
// "name": "Test 9",
// "type": {
// "id": 1,
// "name_eng": "Main",
// "name_rus": "Главная"
// },
// "position": {
// "id": 9,
// "source_type": "Web",
// "name_eng": "Web main ad (6x1)",
// "name_rus": "Веб: главная (6x1)"
// },
// "link": null,
// "impressions": 10,
// "frequency": 2,
// "duration": 10,
// "time_close": 15,
// "media": {
// "url": "https://cf-aws-staging.insports.tv/media/folder/78/en/web.png"
// },
// "remaining_views": 15
// },
// {
// "id": 76,
// "name": "Test 7",
// "type": {
// "id": 1,
// "name_eng": "Main",
// "name_rus": "Главная"
// },
// "position": {
// "id": 7,
// "source_type": "Web",
// "name_eng": "Web main ad (3x1)",
// "name_rus": "Веб: главная (3x1)"
// },
// "link": null,
// "impressions": 10,
// "frequency": 4,
// "duration": 10,
// "time_close": 15,
// "media": {
// "url": "https://cf-aws-staging.insports.tv/media/folder/76/en/web.png"
// },
// "remaining_views": 15
// }
],
"block": [
// {
// "id": 75,
// "name": "Test 6",
// "type": {
// "id": 1,
// "name_eng": "Main",
// "name_rus": "Главная"
// },
// "position": {
// "id": 6,
// "source_type": "Web",
// "name_eng": "Web main ad (2x2)",
// "name_rus": "Веб: главная (2x2)"
// },
// "link": null,
// "impressions": 10,
// "frequency": 3,
// "duration": 120,
// "time_close": null,
// "media": {
// "url": "https://cf-aws-staging.insports.tv/media/folder/75/en/web.png"
// },
// "remaining_views": 10
// },
// {
// "id": 73,
// "name": "Test 4",
// "type": {
// "id": 1,
// "name_eng": "Main",
// "name_rus": "Главная"
// },
// "position": {
// "id": 4,
// "source_type": "Web",
// "name_eng": "Web main ad (2x1)",
// "name_rus": "Веб: главная (2x1)"
// },
// "link": null,
// "impressions": 10,
// "frequency": 1,
// "duration": 120,
// "time_close": null,
// "media": {
// "url": "https://cf-aws-staging.insports.tv/media/folder/73/en/web.png"
// },
// "remaining_views": 9
// },
// {
// "id": 74,
// "name": "Test 5",
// "type": {
// "id": 1,
// "name_eng": "Main",
// "name_rus": "Главная"
// },
// "position": {
// "id": 5,
// "source_type": "Web",
// "name_eng": "Web main ad (1x2)",
// "name_rus": "Веб: главная (1x2)"
// },
// "link": null,
// "impressions": 10,
// "frequency": 2,
// "duration": 120000,
// "time_close": 1,
// "media": {
// "url": "https://cf-aws-staging.insports.tv/media/folder/74/en/web.png"
// },
// "remaining_views": 9
// }
],
"match": [
// {
// "id": 82,
// "name": "Test 13",
// "type": {
// "id": 2,
// "name_eng": "Match",
// "name_rus": "Матч"
// },
// "position": {
// "id": 13,
// "source_type": "Web",
// "name_eng": "Web top side ad horizontal",
// "name_rus": "Веб: верхняя, боковая, по горизонтали"
// },
// "link": "https://www.google.com/",
// "impressions": 10,
// "frequency": 10,
// "duration": 10,
// "time_close": 3,
// "media": {
// "url": "https://i.imgur.com/B0odUkf.png"
// },
// "remaining_views": 10
// },
// {
// "id": 83,
// "name": "Test 14",
// "type": {
// "id": 2,
// "name_eng": "Match",
// "name_rus": "Матч"
// },
// "position": {
// "id": 14,
// "source_type": "Web",
// "name_eng": "Web top side ad horizontal",
// "name_rus": "Веб: верхняя, боковая, по горизонтали"
// },
// "link": "https://www.google.com/",
// "impressions": 10,
// "frequency": 5,
// "duration": 12000,
// "time_close": 1,
// "media": {
// "url": "https://i.imgur.com/KcJhEcu.jpg"
// },
// "remaining_views": 10
// },
{
"id": 84,
"name": "Test 15",
"type": {
"id": 2,
"name_eng": "Match",
"name_rus": "Матч"
},
"position": {
"id": 15,
"source_type": "Web",
"name_eng": "Web top side ad horizontal",
"name_rus": "Веб: верхняя, боковая, по горизонтали"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 10,
"duration": 5,
"time_close": 1,
"media": {
"url": "https://cf-aws-staging.insports.tv/media/folder/84/en/web.png"
},
"remaining_views": 10
},
// {
// "id": 87,
// "name": "Test 18",
// "type": {
// "id": 3,
// "name_eng": "Player",
// "name_rus": "Плеер"
// },
// "position": {
// "id": 18,
// "source_type": "Web",
// "name_eng": "Web top side ad horizontal",
// "name_rus": "Веб: верхняя, боковая, по горизонтали"
// },
// "link": "https://www.google.com/",
// "impressions": 10,
// "frequency": 100,
// "duration": 12000,
// "time_close": 1,
// "media": {
// "url": "https://i.imgur.com/xWlogZt.png"
// },
// "remaining_views": 10
// },
// {
// "id": 88,
// "name": "Test 19",
// "type": {
// "id": 3,
// "name_eng": "Player",
// "name_rus": "Плеер"
// },
// "position": {
// "id": 19,
// "source_type": "Web",
// "name_eng": "Web top side ad horizontal",
// "name_rus": "Веб: верхняя, боковая, по горизонтали"
// },
// "link": "https://www.google.com/",
// "impressions": 10,
// "frequency": 100,
// "duration": 12000,
// "time_close": 1,
// "media": {
// "url": "https://i.imgur.com/u9cfGGs.png"
// },
// "remaining_views": 10
// },
// {
// "id": 89,
// "name": "Test 20",
// "type": {
// "id": 3,
// "name_eng": "Player",
// "name_rus": "Плеер"
// },
// "position": {
// "id": 20,
// "source_type": "Web",
// "name_eng": "Web top side ad horizontal",
// "name_rus": "Веб: верхняя, боковая, по горизонтали"
// },
// "link": "https://www.google.com/",
// "impressions": 10,
// "frequency": 100,
// "duration": 12000,
// "time_close": 1,
// "media": {
// "url": "https://i.imgur.com/XP1ZIQC.png"
// },
// "remaining_views": 10
// },
//
{
"id": 90,
"name": "Test 21",
"type": {
"id": 3,
"name_eng": "Player",
"name_rus": "Плеер"
},
"position": {
"id": 21,
"source_type": "Web",
"name_eng": "Web top side ad horizontal",
"name_rus": "Веб: верхняя, боковая, по горизонтали"
},
"link": "https://www.google.com/",
"impressions": 10,
"frequency": 5,
"duration": 10,
"time_close": 3,
"media": {
"url": "https://i.imgur.com/PaiZdTd.png"
},
"remaining_views": 10
},
],
}
}

@ -0,0 +1,2 @@
export * from './getAds'
export * from './surveys'

@ -0,0 +1,8 @@
/* eslint-disable */
import { Model } from 'miragejs'
import type { ResponseType, AdResponse } from 'requests'
export const models = {
ads: Model.extend<Partial<AdResponse>>({}),
surveys: Model.extend<Partial<ResponseType>>({}),
}

@ -0,0 +1,44 @@
/* eslint-disable */
import {
createServer,
} from 'miragejs'
import {
ADS_API_URL,
APIS,
AUTH_SERVICE,
STATS_API_URL,
URL_AWS,
VIEWS_API,
} from 'config'
import { API_ROOT } from 'features/AuthServiceApp/config/routes'
import { surveys, getAds } from './fixtures'
import { models } from './models'
const mainDomain = 'insports.tv'
export function makeServer({ environment = 'test' } = {}) {
const server = createServer({
environment,
fixtures: {
surveys,
ads: getAds(),
},
models: models,
routes() {
this.passthrough(`${API_ROOT}/***`)
this.passthrough(`${VIEWS_API}/***`)
this.passthrough(`${STATS_API_URL}/***`)
this.passthrough(`${APIS.production.api}/***`)
this.passthrough(`${APIS.staging.api}/***`)
this.passthrough(`${AUTH_SERVICE}/***`)
this.passthrough(`https://${mainDomain}/***`)
this.passthrough(`https://images.${mainDomain}/***`)
this.passthrough(`${URL_AWS}/***`)
this.post(`${ADS_API_URL}`, getAds)
this.logging = true;
},
})
return server
}
Loading…
Cancel
Save