Preprod (#470)
* feat(ott-1503): connecting events (#464) * feat(ott-1503): connecting events * fix(ott-1503): pr fixes Co-authored-by: boyvanov <boyvanov.sergey@gmail.com> * feat(ott-1293): add matchpage for unauthenticated users (#465) * feat(ott-1293): add matchpage for unauthenticated users * feat(ott-1293): fix pr * Ott 1553 fix loading indicator in payment (#466) * fix(#1553): fix arrow loader in page cards * fix(#1553): delete env file * feat(#1576): added technical support widget (#468) Co-authored-by: boyvanov <50294488+boyvanov@users.noreply.github.com> Co-authored-by: boyvanov <boyvanov.sergey@gmail.com> Co-authored-by: PolyakovaM <55061222+PolyakovaM@users.noreply.github.com> Co-authored-by: Andrei Dekterev <57942757+dekterev@users.noreply.github.com> Co-authored-by: Иван Пиминов <61900450+ivan-piminov@users.noreply.github.com>keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
parent
88cdcb9d58
commit
bb756f4109
|
After Width: | Height: | Size: 343 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 326 KiB |
@ -0,0 +1,6 @@ |
|||||||
|
export const joinMatchLexics = { |
||||||
|
join_instat_tv: 15420, |
||||||
|
join_now: 15422, |
||||||
|
promo_text: 15421, |
||||||
|
watch_live: 15423, |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
import toNumber from 'lodash/toNumber' |
||||||
|
import isUndefined from 'lodash/isUndefined' |
||||||
|
|
||||||
|
export const isMatchPage = () => { |
||||||
|
const splitPath = window.location.pathname.split('/') |
||||||
|
const pageType = splitPath[2] |
||||||
|
const matchId = toNumber(splitPath[3]) |
||||||
|
|
||||||
|
return pageType === 'matches' && !isUndefined(matchId) |
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
import type { FormEvent } from 'react' |
||||||
|
import { |
||||||
|
useEffect, |
||||||
|
useState, |
||||||
|
} from 'react' |
||||||
|
|
||||||
|
import format from 'date-fns/format' |
||||||
|
|
||||||
|
import type { UnauthenticatedMatch } from 'requests' |
||||||
|
import { getUnauthenticatedMatch } from 'requests' |
||||||
|
|
||||||
|
import { usePageParams } from 'hooks/usePageParams' |
||||||
|
|
||||||
|
import { useLexicsStore } from 'features/LexicsStore' |
||||||
|
import { getName } from 'features/Name' |
||||||
|
import { useAuthStore } from 'features/AuthStore' |
||||||
|
|
||||||
|
export const useUnauthenticatedMatch = () => { |
||||||
|
const [ |
||||||
|
matchInfo, setMatchInfo, |
||||||
|
] = useState<UnauthenticatedMatch>(null) |
||||||
|
|
||||||
|
const { suffix } = useLexicsStore() |
||||||
|
const { profileId: matchId, sportType } = usePageParams() |
||||||
|
|
||||||
|
const teamName1 = getName({ nameObj: matchInfo?.team1 || {}, suffix }) |
||||||
|
const teamName2 = getName({ nameObj: matchInfo?.team2 || {}, suffix }) |
||||||
|
|
||||||
|
const date = matchInfo?.date |
||||||
|
const matchDate = (date && format(new Date(date), 'd MMM HH:mm')) || '' |
||||||
|
|
||||||
|
const { login } = useAuthStore() |
||||||
|
const handleSubmit = (event: FormEvent<HTMLFormElement>) => { |
||||||
|
event.preventDefault() |
||||||
|
login() |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getUnauthenticatedMatch(sportType, matchId).then(setMatchInfo) |
||||||
|
}, [sportType, matchId]) |
||||||
|
|
||||||
|
return { |
||||||
|
handleSubmit, |
||||||
|
matchDate, |
||||||
|
teamName1, |
||||||
|
teamName2, |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,90 @@ |
|||||||
|
import format from 'date-fns/format' |
||||||
|
|
||||||
|
import { joinMatchLexics } from 'config/lexics/joinMatch' |
||||||
|
|
||||||
|
import { usePageParams } from 'hooks/usePageParams' |
||||||
|
|
||||||
|
import { T9n } from 'features/T9n' |
||||||
|
import { useLexicsConfig } from 'features/LexicsStore' |
||||||
|
|
||||||
|
import { useUnauthenticatedMatch } from './hooks' |
||||||
|
|
||||||
|
import { |
||||||
|
Wrapper, |
||||||
|
MainLogo, |
||||||
|
HeaderWrapper, |
||||||
|
Footer, |
||||||
|
BlockWrapper, |
||||||
|
MatchInfo, |
||||||
|
SportImgWrapper, |
||||||
|
DateInfoWrapper, |
||||||
|
DateInfo, |
||||||
|
WatchLive, |
||||||
|
TeamsNameWrapper, |
||||||
|
MainInfoTitle, |
||||||
|
MainInfoButton, |
||||||
|
MainInfoText, |
||||||
|
EmptySpan, |
||||||
|
WatchLiveCircle, |
||||||
|
WatchLiveText, |
||||||
|
SportImgMobileDevice, |
||||||
|
FooterLogo, |
||||||
|
FooterRights, |
||||||
|
} from './styled' |
||||||
|
|
||||||
|
export const JoinMatchPage = () => { |
||||||
|
useLexicsConfig(joinMatchLexics) |
||||||
|
|
||||||
|
const { sportType } = usePageParams() |
||||||
|
const { |
||||||
|
handleSubmit, |
||||||
|
matchDate, |
||||||
|
teamName1, |
||||||
|
teamName2, |
||||||
|
} = useUnauthenticatedMatch() |
||||||
|
|
||||||
|
const currentYear = format(new Date(), 'Y') |
||||||
|
|
||||||
|
return ( |
||||||
|
<Wrapper> |
||||||
|
<HeaderWrapper> |
||||||
|
<MainLogo /> |
||||||
|
</HeaderWrapper> |
||||||
|
<BlockWrapper> |
||||||
|
<SportImgWrapper sportType={sportType} /> |
||||||
|
<MatchInfo> |
||||||
|
<DateInfoWrapper> |
||||||
|
<DateInfo>{matchDate}</DateInfo> |
||||||
|
<WatchLive> |
||||||
|
<WatchLiveCircle /> |
||||||
|
<WatchLiveText> |
||||||
|
<T9n t='watch_live' /> |
||||||
|
</WatchLiveText> |
||||||
|
</WatchLive> |
||||||
|
</DateInfoWrapper> |
||||||
|
<TeamsNameWrapper> |
||||||
|
<EmptySpan>{teamName1}</EmptySpan> |
||||||
|
<EmptySpan> — </EmptySpan> |
||||||
|
<EmptySpan>{teamName2}</EmptySpan> |
||||||
|
</TeamsNameWrapper> |
||||||
|
<MainInfoTitle> |
||||||
|
<T9n t='join_instat_tv' /> |
||||||
|
</MainInfoTitle> |
||||||
|
<SportImgMobileDevice sportType={sportType} /> |
||||||
|
<MainInfoText> |
||||||
|
<T9n t='promo_text' /> |
||||||
|
</MainInfoText> |
||||||
|
<form onSubmit={handleSubmit}> |
||||||
|
<MainInfoButton type='submit'> |
||||||
|
<T9n t='join_now' /> |
||||||
|
</MainInfoButton> |
||||||
|
</form> |
||||||
|
</MatchInfo> |
||||||
|
</BlockWrapper> |
||||||
|
<Footer> |
||||||
|
<FooterLogo /> |
||||||
|
<FooterRights>©InStat TV {currentYear}</FooterRights> |
||||||
|
</Footer> |
||||||
|
</Wrapper> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,258 @@ |
|||||||
|
import styled, { css } from 'styled-components/macro' |
||||||
|
|
||||||
|
import { devices, SportTypes } from 'config' |
||||||
|
import { isMobileDevice } from 'config/userAgent' |
||||||
|
|
||||||
|
import { ButtonSolid } from 'features/Common' |
||||||
|
import { Logo } from 'features/Logo' |
||||||
|
|
||||||
|
type Props = { |
||||||
|
sportType: SportTypes, |
||||||
|
} |
||||||
|
|
||||||
|
export const Wrapper = styled.div` |
||||||
|
width: 100vw; |
||||||
|
height: 100vh; |
||||||
|
color: white; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: space-between; |
||||||
|
` |
||||||
|
|
||||||
|
export const HeaderWrapper = styled.div` |
||||||
|
background-color: #13151B; |
||||||
|
padding: 20px 0; |
||||||
|
padding-left: 20%; |
||||||
|
width: 100%; |
||||||
|
@media ${devices.laptop} { |
||||||
|
padding-left: 5%; |
||||||
|
} |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
display: none; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const MainLogo = styled(Logo)` |
||||||
|
height: 20px; |
||||||
|
width: 80px; |
||||||
|
` |
||||||
|
|
||||||
|
export const BlockWrapper = styled.div` |
||||||
|
height: 100%; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
padding-left: 20%; |
||||||
|
width: 100%; |
||||||
|
@media ${devices.laptop} { |
||||||
|
padding-left: 5%; |
||||||
|
} |
||||||
|
@media ${devices.mobile} { |
||||||
|
padding-left: 5.4rem; |
||||||
|
} |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
padding: 5.4rem; |
||||||
|
overflow: scroll; |
||||||
|
@media screen and (orientation: landscape){ |
||||||
|
padding-top: 20px; |
||||||
|
height: auto; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const SportImgWrapper = styled.div<Props>` |
||||||
|
background-image: url(/images/landing_${({ sportType }) => sportType}.png); |
||||||
|
background-repeat: no-repeat; |
||||||
|
background-position: center; |
||||||
|
background-size: contain; |
||||||
|
width: 512px; |
||||||
|
height: 641px; |
||||||
|
margin-right: 5%; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
display: none; |
||||||
|
@media screen and (orientation: landscape){ |
||||||
|
display: block; |
||||||
|
height: 100%; |
||||||
|
width: 70%; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const MatchInfo = styled.div` |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
@media screen and (orientation: landscape){ |
||||||
|
padding-top: 0; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const DateInfoWrapper = styled.div` |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
font-size: 15px; |
||||||
|
font-weight: 600; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
justify-content: space-between; |
||||||
|
font-size: 14px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const DateInfo = styled.div` |
||||||
|
text-transform: uppercase; |
||||||
|
background-color: rgba(0,0,0,0.4); |
||||||
|
padding: 8px 25px; |
||||||
|
color: #B9B9B9; |
||||||
|
margin-right: 25px; |
||||||
|
border-radius: 5px; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
padding: 0.7em 2.5rem; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const WatchLive = styled.div` |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
` |
||||||
|
|
||||||
|
export const WatchLiveText = styled.div` |
||||||
|
color: #EB5757; |
||||||
|
letter-spacing: .2rem; |
||||||
|
` |
||||||
|
|
||||||
|
export const WatchLiveCircle = styled.div` |
||||||
|
width: 20px; |
||||||
|
height: 20px; |
||||||
|
border-radius: 50%; |
||||||
|
background-color: #CC0000; |
||||||
|
margin-right: 5px; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
width: 15px; |
||||||
|
height: 15px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const TeamsNameWrapper = styled.div` |
||||||
|
margin: 35px 0 40px; |
||||||
|
font-size: 20px; |
||||||
|
font-weight: 500; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
margin: 10px 0; |
||||||
|
font-size: 16px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const MainInfoTitle = styled.div` |
||||||
|
font-weight: 600; |
||||||
|
font-size: 46px; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 32px; |
||||||
|
margin-bottom: 15px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const MainInfoButton = styled(ButtonSolid)` |
||||||
|
width: 260px; |
||||||
|
height: 50px; |
||||||
|
font-size: 20px; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
width: 100%; |
||||||
|
border-radius: 10px; |
||||||
|
font-size: 17px; |
||||||
|
margin-bottom: 30px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const MainInfoText = styled.div` |
||||||
|
max-width: 400px; |
||||||
|
margin: 40px 0; |
||||||
|
font-size: 17px; |
||||||
|
line-height: 150%; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 15px; |
||||||
|
margin: 15px 0 25px; |
||||||
|
width: 85%; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const Footer = styled.div` |
||||||
|
font-size: 14px; |
||||||
|
background-color: black; |
||||||
|
padding: 16px 0; |
||||||
|
padding-left: 20%; |
||||||
|
width: 100%; |
||||||
|
@media ${devices.laptop} { |
||||||
|
padding-left: 5%; |
||||||
|
} |
||||||
|
@media ${devices.mobile} { |
||||||
|
padding-left: 35px; |
||||||
|
} |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
display: flex; |
||||||
|
padding: 15px 35px; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const FooterLogo = styled(Logo)` |
||||||
|
display: none; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
display: block; |
||||||
|
width: 48px; |
||||||
|
height: 11px; |
||||||
|
opacity: .4; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const FooterRights = styled.div` |
||||||
|
opacity: .5; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 12px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const SportImgMobileDevice = styled(SportImgWrapper)` |
||||||
|
display: none; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
display: block; |
||||||
|
height: 53%; |
||||||
|
width: 100%; |
||||||
|
margin-right: 0; |
||||||
|
@media screen and (orientation: landscape){
|
||||||
|
display: none; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const EmptySpan = styled.span`` |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
import { useEffect, useState } from 'react' |
||||||
|
|
||||||
|
import type { Events } from 'requests' |
||||||
|
import { getMatchEvents } from 'requests' |
||||||
|
|
||||||
|
import { usePageParams } from 'hooks/usePageParams' |
||||||
|
|
||||||
|
import { useEventsLexics } from './useEventsLexics' |
||||||
|
|
||||||
|
export const useEvents = () => { |
||||||
|
const [events, setEvents] = useState<Events>([]) |
||||||
|
const { fetchLexics } = useEventsLexics() |
||||||
|
const { profileId: matchId, sportType } = usePageParams() |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getMatchEvents({ |
||||||
|
matchId, |
||||||
|
sportType, |
||||||
|
}).then(fetchLexics) |
||||||
|
.then(setEvents) |
||||||
|
}, [ |
||||||
|
fetchLexics, |
||||||
|
matchId, |
||||||
|
sportType, |
||||||
|
]) |
||||||
|
|
||||||
|
return { events } |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
import { useCallback } from 'react' |
||||||
|
|
||||||
|
import isEmpty from 'lodash/isEmpty' |
||||||
|
import map from 'lodash/map' |
||||||
|
import uniq from 'lodash/uniq' |
||||||
|
|
||||||
|
import type { Events } from 'requests' |
||||||
|
|
||||||
|
import { useLexicsStore } from 'features/LexicsStore' |
||||||
|
|
||||||
|
export const useEventsLexics = () => { |
||||||
|
const { addLexicsConfig } = useLexicsStore() |
||||||
|
|
||||||
|
const fetchLexics = useCallback((events: Events) => { |
||||||
|
const lexics = uniq(map(events, ({ l }) => l)) |
||||||
|
|
||||||
|
if (!isEmpty(lexics)) { |
||||||
|
addLexicsConfig(lexics) |
||||||
|
} |
||||||
|
|
||||||
|
return events |
||||||
|
}, [addLexicsConfig]) |
||||||
|
|
||||||
|
return { fetchLexics } |
||||||
|
} |
||||||
@ -0,0 +1,88 @@ |
|||||||
|
import { ProfileTypes } from 'config' |
||||||
|
|
||||||
|
import type { Event, Team } from 'requests' |
||||||
|
|
||||||
|
import { usePageParams } from 'hooks/usePageParams' |
||||||
|
|
||||||
|
import type { PlayerPlaylistOption } from 'features/MatchPage/types' |
||||||
|
import { Name } from 'features/Name' |
||||||
|
import { T9n } from 'features/T9n' |
||||||
|
|
||||||
|
import { |
||||||
|
Avatar, |
||||||
|
Button, |
||||||
|
EventInfo, |
||||||
|
EventDesc, |
||||||
|
Title, |
||||||
|
SubTitle, |
||||||
|
EventTime, |
||||||
|
} from '../TabEvents/styled' |
||||||
|
|
||||||
|
type Props = { |
||||||
|
active?: boolean, |
||||||
|
disabled?: boolean, |
||||||
|
event: Event, |
||||||
|
isHomeTeam: boolean, |
||||||
|
onClick: () => void, |
||||||
|
player?: PlayerPlaylistOption, |
||||||
|
team?: Team, |
||||||
|
} |
||||||
|
|
||||||
|
export const EventButton = ({ |
||||||
|
active, |
||||||
|
disabled, |
||||||
|
event, |
||||||
|
isHomeTeam, |
||||||
|
onClick, |
||||||
|
player, |
||||||
|
team, |
||||||
|
}: Props) => { |
||||||
|
const { sportType } = usePageParams() |
||||||
|
const { |
||||||
|
c: clearTime, |
||||||
|
l: lexica, |
||||||
|
p: playerId, |
||||||
|
sc1: score1, |
||||||
|
sc2: score2, |
||||||
|
t: teamId, |
||||||
|
} = event |
||||||
|
|
||||||
|
const avatarId = playerId || teamId |
||||||
|
const profileType = playerId |
||||||
|
? ProfileTypes.PLAYERS |
||||||
|
: ProfileTypes.TEAMS |
||||||
|
const nameObj = player || team |
||||||
|
|
||||||
|
return ( |
||||||
|
<Button |
||||||
|
onClick={onClick} |
||||||
|
active={active} |
||||||
|
disabled={disabled} |
||||||
|
isHomeTeam={isHomeTeam} |
||||||
|
> |
||||||
|
{avatarId && ( |
||||||
|
<Avatar |
||||||
|
id={avatarId} |
||||||
|
isHomeTeam={isHomeTeam} |
||||||
|
isPlayer={Boolean(playerId)} |
||||||
|
profileType={profileType} |
||||||
|
sportType={sportType} |
||||||
|
/> |
||||||
|
)} |
||||||
|
<EventInfo> |
||||||
|
<EventTime> |
||||||
|
{clearTime} |
||||||
|
</EventTime> |
||||||
|
<EventDesc> |
||||||
|
<Title> |
||||||
|
<T9n t={lexica} /> |
||||||
|
{(score1 || score2) && ` (${score1}-${score2})`} |
||||||
|
</Title> |
||||||
|
<SubTitle> |
||||||
|
{(playerId || teamId) && nameObj && <Name nameObj={nameObj} />} |
||||||
|
</SubTitle> |
||||||
|
</EventDesc> |
||||||
|
</EventInfo> |
||||||
|
</Button> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,63 @@ |
|||||||
|
import find from 'lodash/find' |
||||||
|
import map from 'lodash/map' |
||||||
|
|
||||||
|
import type { Events, MatchInfo } from 'requests' |
||||||
|
|
||||||
|
import type { PlaylistOption, Playlists } from 'features/MatchPage/types' |
||||||
|
import { PlaylistTypes } from 'features/MatchPage/types' |
||||||
|
|
||||||
|
import { isEqual } from '../../helpers' |
||||||
|
import { EventButton } from '../EventButton' |
||||||
|
import { Event, List } from '../TabEvents/styled' |
||||||
|
|
||||||
|
type Props = { |
||||||
|
events: Events, |
||||||
|
onSelect: (option: PlaylistOption) => void, |
||||||
|
playlists: Playlists, |
||||||
|
profile: MatchInfo, |
||||||
|
selectedPlaylist?: PlaylistOption, |
||||||
|
} |
||||||
|
|
||||||
|
export const EventsList = ({ |
||||||
|
events, |
||||||
|
onSelect, |
||||||
|
playlists, |
||||||
|
profile, |
||||||
|
selectedPlaylist, |
||||||
|
}: Props) => ( |
||||||
|
<List> |
||||||
|
{map(events, (event) => { |
||||||
|
const eventPlaylist = { |
||||||
|
data: [{ |
||||||
|
e: event.e, |
||||||
|
h: event.h, |
||||||
|
s: event.s, |
||||||
|
}], |
||||||
|
id: event.n, |
||||||
|
type: PlaylistTypes.EVENT, |
||||||
|
} as PlaylistOption |
||||||
|
|
||||||
|
const isHomeTeam = event.t === profile?.team1.id |
||||||
|
const team = isHomeTeam |
||||||
|
? profile?.team1 |
||||||
|
: profile?.team2 |
||||||
|
const players = isHomeTeam |
||||||
|
? playlists.players.team1 |
||||||
|
: playlists.players.team2 |
||||||
|
const player = find(players, { id: event.p }) |
||||||
|
|
||||||
|
return ( |
||||||
|
<Event key={event.n}> |
||||||
|
<EventButton |
||||||
|
active={isEqual(eventPlaylist, selectedPlaylist)} |
||||||
|
event={event} |
||||||
|
isHomeTeam={isHomeTeam} |
||||||
|
onClick={() => onSelect(eventPlaylist)} |
||||||
|
player={player} |
||||||
|
team={team} |
||||||
|
/> |
||||||
|
</Event> |
||||||
|
) |
||||||
|
})} |
||||||
|
</List> |
||||||
|
) |
||||||
@ -0,0 +1,119 @@ |
|||||||
|
import { Fragment, useMemo } from 'react' |
||||||
|
|
||||||
|
import groupBy from 'lodash/groupBy' |
||||||
|
import isEmpty from 'lodash/isEmpty' |
||||||
|
import map from 'lodash/map' |
||||||
|
import reverse from 'lodash/reverse' |
||||||
|
import values from 'lodash/values' |
||||||
|
|
||||||
|
import type { Events, MatchInfo } from 'requests' |
||||||
|
|
||||||
|
import { useToggle } from 'hooks' |
||||||
|
|
||||||
|
import type { PlaylistOption, Playlists } from 'features/MatchPage/types' |
||||||
|
import { T9n } from 'features/T9n' |
||||||
|
|
||||||
|
import { EventsList } from '../EventsList' |
||||||
|
import { |
||||||
|
BlockTitle, |
||||||
|
Wrapper, |
||||||
|
HalfList, |
||||||
|
HalfEvents, |
||||||
|
Tabs, |
||||||
|
Tab, |
||||||
|
} from './styled' |
||||||
|
|
||||||
|
type Props = { |
||||||
|
events: Events, |
||||||
|
onSelect: (option: PlaylistOption) => void, |
||||||
|
playlists: Playlists, |
||||||
|
profile: MatchInfo, |
||||||
|
selectedPlaylist?: PlaylistOption, |
||||||
|
} |
||||||
|
|
||||||
|
export const TabEvents = ({ |
||||||
|
events, |
||||||
|
onSelect, |
||||||
|
playlists, |
||||||
|
profile, |
||||||
|
selectedPlaylist, |
||||||
|
}: Props) => { |
||||||
|
const { |
||||||
|
close: setUnreversed, |
||||||
|
isOpen: areEventsReversed, |
||||||
|
open: setReversed, |
||||||
|
} = useToggle() |
||||||
|
|
||||||
|
const groupedEvents = useMemo(() => values( |
||||||
|
groupBy( |
||||||
|
areEventsReversed ? reverse([...events]) : events, |
||||||
|
'h', |
||||||
|
), |
||||||
|
), [areEventsReversed, events]) |
||||||
|
|
||||||
|
if (!profile) return null |
||||||
|
|
||||||
|
return ( |
||||||
|
<Wrapper> |
||||||
|
{isEmpty(events) |
||||||
|
? ( |
||||||
|
<BlockTitle> |
||||||
|
<T9n t='no_data' /> |
||||||
|
</BlockTitle> |
||||||
|
) |
||||||
|
: ( |
||||||
|
<Fragment> |
||||||
|
<Tabs> |
||||||
|
<Tab |
||||||
|
active={!areEventsReversed} |
||||||
|
onClick={setUnreversed} |
||||||
|
> |
||||||
|
<T9n t='from_start_match' /> |
||||||
|
</Tab> |
||||||
|
<Tab |
||||||
|
active={areEventsReversed} |
||||||
|
onClick={setReversed} |
||||||
|
> |
||||||
|
<T9n t='from_end_match' /> |
||||||
|
</Tab> |
||||||
|
</Tabs> |
||||||
|
<HalfList> |
||||||
|
{ |
||||||
|
map( |
||||||
|
areEventsReversed |
||||||
|
? reverse(groupedEvents) |
||||||
|
: groupedEvents, |
||||||
|
(halfEvents, idx) => { |
||||||
|
const firstEvent = halfEvents[0] |
||||||
|
const isFirstBlock = idx === 0 |
||||||
|
|
||||||
|
return ( |
||||||
|
<HalfEvents key={firstEvent.h}> |
||||||
|
{ |
||||||
|
!isFirstBlock && ( |
||||||
|
<BlockTitle> |
||||||
|
<T9n t='half_time' /> |
||||||
|
</BlockTitle> |
||||||
|
) |
||||||
|
} |
||||||
|
<BlockTitle> |
||||||
|
<T9n t={firstEvent.l} /> |
||||||
|
</BlockTitle> |
||||||
|
<EventsList |
||||||
|
events={halfEvents} |
||||||
|
onSelect={onSelect} |
||||||
|
playlists={playlists} |
||||||
|
profile={profile} |
||||||
|
selectedPlaylist={selectedPlaylist} |
||||||
|
/> |
||||||
|
</HalfEvents> |
||||||
|
) |
||||||
|
}, |
||||||
|
) |
||||||
|
} |
||||||
|
</HalfList> |
||||||
|
</Fragment> |
||||||
|
)} |
||||||
|
</Wrapper> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,143 @@ |
|||||||
|
import styled, { css } from 'styled-components/macro' |
||||||
|
|
||||||
|
import { ProfileLogo } from 'features/ProfileLogo' |
||||||
|
|
||||||
|
import { Tabs as TabsBase, Tab as TabBase } from '../PlayersPlaylists/styled' |
||||||
|
import { |
||||||
|
BlockTitle as BlockTitleBase, |
||||||
|
Button as ButtonBase, |
||||||
|
} from '../../styled' |
||||||
|
|
||||||
|
export const Wrapper = styled.div` |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
` |
||||||
|
|
||||||
|
export const HalfList = styled.ul` |
||||||
|
width: 100%; |
||||||
|
` |
||||||
|
|
||||||
|
export const HalfEvents = styled.li` |
||||||
|
:not(:last-child) { |
||||||
|
margin-bottom: 12px; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
export const List = styled.ul` |
||||||
|
margin-top: 12px; |
||||||
|
` |
||||||
|
|
||||||
|
export const Event = styled.li` |
||||||
|
width: 100%; |
||||||
|
height: 48px; |
||||||
|
|
||||||
|
:not(:last-child) { |
||||||
|
margin-bottom: 12px; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
type AvatarProps = { |
||||||
|
isHomeTeam: boolean, |
||||||
|
isPlayer: boolean, |
||||||
|
} |
||||||
|
|
||||||
|
export const Avatar = styled(ProfileLogo)<AvatarProps>` |
||||||
|
position: absolute; |
||||||
|
width: 35px; |
||||||
|
height: 43px; |
||||||
|
object-fit: cover; |
||||||
|
|
||||||
|
${({ isHomeTeam }) => ( |
||||||
|
isHomeTeam |
||||||
|
? css`left: 5px;` |
||||||
|
: css`right: 5px;` |
||||||
|
)} |
||||||
|
|
||||||
|
${({ isPlayer }) => ( |
||||||
|
isPlayer |
||||||
|
? css`bottom: 0;` |
||||||
|
: css` |
||||||
|
top: 50%; |
||||||
|
transform: translateY(-50%); |
||||||
|
` |
||||||
|
)} |
||||||
|
` |
||||||
|
|
||||||
|
export const EventInfo = styled.div` |
||||||
|
display: flex; |
||||||
|
width: 100%; |
||||||
|
` |
||||||
|
|
||||||
|
export const EventDesc = styled.div` |
||||||
|
position: relative; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: flex-start; |
||||||
|
overflow: hidden; |
||||||
|
white-space: nowrap; |
||||||
|
text-overflow: ellipsis; |
||||||
|
` |
||||||
|
|
||||||
|
export const Title = styled.span` |
||||||
|
font-weight: 600; |
||||||
|
font-size: 14px; |
||||||
|
line-height: 15px; |
||||||
|
color: #ffffff; |
||||||
|
width: 100%; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
text-align: left; |
||||||
|
` |
||||||
|
|
||||||
|
export const SubTitle = styled(Title)` |
||||||
|
font-size: 10px; |
||||||
|
line-height: 16px; |
||||||
|
font-weight: normal; |
||||||
|
color: rgba(255, 255, 255, 0.7); |
||||||
|
margin-top: 2px; |
||||||
|
` |
||||||
|
|
||||||
|
export const EventTime = styled(Title)` |
||||||
|
font-size: 14px; |
||||||
|
font-weight: normal; |
||||||
|
margin-right: 10px; |
||||||
|
width: auto; |
||||||
|
overflow: initial; |
||||||
|
min-width: 29px; |
||||||
|
` |
||||||
|
|
||||||
|
export const Tabs = styled(TabsBase)` |
||||||
|
margin-top: 0px; |
||||||
|
margin-bottom: 18px; |
||||||
|
` |
||||||
|
|
||||||
|
export const Tab = styled(TabBase)` |
||||||
|
text-transform: none; |
||||||
|
|
||||||
|
:first-child { |
||||||
|
margin-right: 15px; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
export const BlockTitle = styled(BlockTitleBase)` |
||||||
|
text-align: center; |
||||||
|
width: 100%; |
||||||
|
display: inline-block; |
||||||
|
` |
||||||
|
|
||||||
|
type ButtonProps = { |
||||||
|
isHomeTeam: boolean, |
||||||
|
} |
||||||
|
|
||||||
|
export const Button = styled(ButtonBase)<ButtonProps>` |
||||||
|
&:hover ${EventDesc} { |
||||||
|
overflow: visible; |
||||||
|
} |
||||||
|
|
||||||
|
${({ isHomeTeam }) => ( |
||||||
|
isHomeTeam |
||||||
|
? css`padding-left: 60px;` |
||||||
|
: css`padding: 0 40px 0 60px;` |
||||||
|
)} |
||||||
|
` |
||||||
@ -0,0 +1,69 @@ |
|||||||
|
import { |
||||||
|
DATA_URL, |
||||||
|
PROCEDURES, |
||||||
|
SportTypes, |
||||||
|
} from 'config' |
||||||
|
|
||||||
|
import { Episode, Episodes } from 'requests' |
||||||
|
|
||||||
|
import { callApi, getSportLexic } from 'helpers' |
||||||
|
|
||||||
|
const proc = PROCEDURES.ott_match_events |
||||||
|
|
||||||
|
type Args = { |
||||||
|
matchId: number, |
||||||
|
sportType: SportTypes, |
||||||
|
} |
||||||
|
|
||||||
|
export type Event = Episode & { |
||||||
|
/** clear time */ |
||||||
|
c: string, |
||||||
|
|
||||||
|
/** event lexic */ |
||||||
|
l: number, |
||||||
|
|
||||||
|
/** event id */ |
||||||
|
n: number, |
||||||
|
|
||||||
|
/** player id */ |
||||||
|
p?: number, |
||||||
|
|
||||||
|
/** repeat */ |
||||||
|
rep?: Episodes, |
||||||
|
|
||||||
|
/** score team1 */ |
||||||
|
sc1?: number, |
||||||
|
|
||||||
|
/** score team2 */ |
||||||
|
sc2?: number, |
||||||
|
|
||||||
|
/** team id */ |
||||||
|
t?: number, |
||||||
|
} |
||||||
|
|
||||||
|
export type Events = Array<Event> |
||||||
|
|
||||||
|
type Response = { |
||||||
|
data?: Events, |
||||||
|
} |
||||||
|
|
||||||
|
export const getMatchEvents = async ({ |
||||||
|
matchId, |
||||||
|
sportType, |
||||||
|
}: Args) => { |
||||||
|
const config = { |
||||||
|
body: { |
||||||
|
params: { |
||||||
|
_p_match_id: matchId, |
||||||
|
}, |
||||||
|
proc, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const response: Response = await callApi({ |
||||||
|
config, |
||||||
|
url: `${DATA_URL}/${getSportLexic(sportType)}`, |
||||||
|
}) |
||||||
|
|
||||||
|
return response?.data || [] |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
import { |
||||||
|
DATA_URL, |
||||||
|
PROCEDURES, |
||||||
|
SportTypes, |
||||||
|
} from 'config' |
||||||
|
|
||||||
|
import { callApi } from 'helpers' |
||||||
|
|
||||||
|
const proc = PROCEDURES.landing_get_match_info |
||||||
|
|
||||||
|
type Team = { |
||||||
|
name_eng: string, |
||||||
|
name_rus: string, |
||||||
|
} |
||||||
|
|
||||||
|
export type UnauthenticatedMatch = { |
||||||
|
date: string, |
||||||
|
team1: Team, |
||||||
|
team2: Team, |
||||||
|
tournament: { |
||||||
|
name_eng: string, |
||||||
|
name_rus: string, |
||||||
|
}, |
||||||
|
} | null |
||||||
|
|
||||||
|
export const getUnauthenticatedMatch = (sportId: SportTypes, matchId: number) |
||||||
|
: Promise<UnauthenticatedMatch> => { |
||||||
|
const config = { |
||||||
|
body: { |
||||||
|
params: { |
||||||
|
_p_match_id: matchId, |
||||||
|
_p_sport: sportId, |
||||||
|
}, |
||||||
|
proc, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return callApi({ |
||||||
|
config, |
||||||
|
url: DATA_URL, |
||||||
|
}) |
||||||
|
} |
||||||
Loading…
Reference in new issue