fix(#2639): bug fixing for match page mobile
parent
d5c7379b1b
commit
7d3185dc1d
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@ -1,34 +0,0 @@ |
||||
import { useMemo } from 'react' |
||||
|
||||
import { format, parseISO } from 'date-fns' |
||||
|
||||
import { MatchInfo } from 'requests' |
||||
|
||||
import { ScDate, ScDateMonth } from './styled' |
||||
|
||||
type Props = { |
||||
profile: MatchInfo, |
||||
} |
||||
|
||||
export const MatchDate = ({ profile }: Props) => { |
||||
const { |
||||
date, |
||||
} = { ...profile! } |
||||
|
||||
const month = useMemo(() => ( |
||||
format(parseISO(date), 'MMM dd,') |
||||
), [date]) |
||||
|
||||
const hour = useMemo(() => ( |
||||
format(parseISO(date), ' HH:mm') |
||||
), [date]) |
||||
|
||||
return ( |
||||
<ScDate> |
||||
<ScDateMonth> |
||||
{month} |
||||
</ScDateMonth> |
||||
{hour} |
||||
</ScDate> |
||||
) |
||||
} |
||||
@ -1,10 +0,0 @@ |
||||
import styled from 'styled-components' |
||||
|
||||
export const ScDate = styled.div` |
||||
font-size: 10px; |
||||
color: rgba(255, 255, 255, 0.7); |
||||
` |
||||
|
||||
export const ScDateMonth = styled.span` |
||||
font-weight: 600; |
||||
` |
||||
@ -1,73 +0,0 @@ |
||||
import { useCallback } from 'react' |
||||
|
||||
import { ProfileTypes } from 'config' |
||||
|
||||
import { getName } from 'features/Name' |
||||
import { useMatchSwitchesStore } from 'features/MatchSwitches' |
||||
import { useLexicsStore } from 'features/LexicsStore' |
||||
|
||||
import { usePageParams } from 'hooks/usePageParams' |
||||
|
||||
import { MatchInfo, Team } from 'requests' |
||||
|
||||
import { |
||||
Score, |
||||
ScoreWrapper, |
||||
StyledLink, |
||||
ScTeam, |
||||
Wrapper, |
||||
} from './styled' |
||||
|
||||
type Props = { |
||||
profile: MatchInfo, |
||||
teamNameLimit?: number, |
||||
} |
||||
|
||||
export const TeamsDetails = ({ profile, teamNameLimit }: Props) => { |
||||
const { sportType } = usePageParams() |
||||
const { isScoreHidden } = useMatchSwitchesStore() |
||||
const { suffix } = useLexicsStore() |
||||
|
||||
const { |
||||
team1, |
||||
team2, |
||||
} = { ...profile! } |
||||
|
||||
const getTeamName = useCallback((team: Team) => { |
||||
let name = getName({ nameObj: team, suffix }) |
||||
if (!!teamNameLimit && name.length > teamNameLimit) name = name.substring(0, teamNameLimit).concat('...') |
||||
return name |
||||
}, [suffix, teamNameLimit]) |
||||
|
||||
return ( |
||||
<Wrapper> |
||||
<ScTeam> |
||||
<StyledLink |
||||
id={team1.id} |
||||
profileType={ProfileTypes.TEAMS} |
||||
sportType={sportType} |
||||
> |
||||
{getTeamName(team1)} |
||||
</StyledLink> |
||||
</ScTeam> |
||||
<ScoreWrapper> |
||||
<Score> |
||||
{ |
||||
isScoreHidden |
||||
? '-' |
||||
: `${team1.score} - ${team2.score}` |
||||
} |
||||
</Score> |
||||
</ScoreWrapper> |
||||
<ScTeam> |
||||
<StyledLink |
||||
id={team2.id} |
||||
profileType={ProfileTypes.TEAMS} |
||||
sportType={sportType} |
||||
> |
||||
{getTeamName(team2)} |
||||
</StyledLink> |
||||
</ScTeam> |
||||
</Wrapper> |
||||
) |
||||
} |
||||
@ -1,70 +0,0 @@ |
||||
import styled, { css } from 'styled-components' |
||||
|
||||
import { isMobileDevice } from 'config/userAgent' |
||||
|
||||
import { ProfileLink } from 'features/ProfileLink' |
||||
import { ProfileLogo } from 'features/ProfileLogo' |
||||
|
||||
export const Wrapper = styled.div` |
||||
display: flex; |
||||
font-weight: 600; |
||||
margin-bottom: 5px; |
||||
` |
||||
|
||||
export const ScTeam = styled.div` |
||||
font-size: 21px; |
||||
|
||||
${isMobileDevice |
||||
? css` |
||||
font-size: 16px; |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
export const StyledLink = styled(ProfileLink)` |
||||
display: flex; |
||||
align-items: center; |
||||
color: white; |
||||
font-size: 14px; |
||||
|
||||
&:hover { |
||||
text-decoration: underline; |
||||
} |
||||
` |
||||
|
||||
export const ScoreWrapper = styled.div` |
||||
margin: 0 10px; |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
` |
||||
|
||||
export const Score = styled.span` |
||||
font-size: 14px; |
||||
white-space: nowrap; |
||||
` |
||||
|
||||
export const MatchStatus = styled.span` |
||||
text-align: center; |
||||
background-color: #CC0000; |
||||
border-radius: 1.3px; |
||||
font-weight: 600; |
||||
font-size: 13px; |
||||
line-height: 16px; |
||||
letter-spacing: 0.05em; |
||||
text-transform: uppercase; |
||||
padding: 2.5px 14px; |
||||
margin-top: 6px; |
||||
` |
||||
|
||||
export const Logo = styled(ProfileLogo)` |
||||
width: 41px; |
||||
height: 41px; |
||||
margin: 0 9px; |
||||
${isMobileDevice |
||||
? css` |
||||
width: 30px; |
||||
height: 30px; |
||||
` |
||||
: ''}; |
||||
` |
||||
@ -1,28 +0,0 @@ |
||||
import { MatchInfo } from 'requests' |
||||
import { TournamentSubtitle } from 'features/TournamentSubtitle' |
||||
|
||||
import { Wrapper, WrapperTop } from './styled' |
||||
|
||||
import { TeamsDetails } from './components/TeamsDetails' |
||||
import { MatchDate } from './components/MatchDate' |
||||
import { useMatchPageStore } from '../../store' |
||||
|
||||
type Props = { |
||||
profile: MatchInfo, |
||||
} |
||||
|
||||
export const MatchProfileCardMobile = ({ profile }: Props) => { |
||||
const { profileCardShown } = useMatchPageStore() |
||||
|
||||
if (!profile) return null |
||||
|
||||
return ( |
||||
<Wrapper isHidden={!profileCardShown}> |
||||
<WrapperTop> |
||||
<TeamsDetails profile={profile} teamNameLimit={14} /> |
||||
<MatchDate profile={profile} /> |
||||
</WrapperTop> |
||||
<TournamentSubtitle countryId={profile!.country_id} tournament={profile!.tournament} /> |
||||
</Wrapper> |
||||
) |
||||
} |
||||
@ -1,19 +0,0 @@ |
||||
import styled, { css } from 'styled-components/macro' |
||||
|
||||
export const Wrapper = styled.div<{isHidden?: boolean}>` |
||||
transition: 0.3s linear;
|
||||
padding: 0 5px; |
||||
color: white; |
||||
margin-bottom: 15px; |
||||
|
||||
${({ isHidden }) => (isHidden ? css` |
||||
height: 0; |
||||
opacity: 0; |
||||
margin-bottom: 0; |
||||
` : '')}
|
||||
` |
||||
|
||||
export const WrapperTop = styled.div` |
||||
display: flex; |
||||
justify-content: space-between; |
||||
` |
||||
@ -1,15 +0,0 @@ |
||||
import { useToggle } from 'hooks' |
||||
|
||||
export const useMatchPage = () => { |
||||
const { |
||||
close: hideProfileCard, |
||||
isOpen: profileCardShown, |
||||
open: showProfileCard, |
||||
} = useToggle(true) |
||||
|
||||
return { |
||||
hideProfileCard, |
||||
profileCardShown, |
||||
showProfileCard, |
||||
} |
||||
} |
||||
@ -1,28 +0,0 @@ |
||||
import { useCallback, 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() |
||||
|
||||
const fetchMatchEvents = useCallback(() => { |
||||
getMatchEvents({ |
||||
matchId, |
||||
sportType, |
||||
}).then(fetchLexics) |
||||
.then(setEvents) |
||||
}, [ |
||||
fetchLexics, |
||||
matchId, |
||||
sportType, |
||||
]) |
||||
|
||||
return { events, fetchMatchEvents } |
||||
} |
||||
@ -1,25 +0,0 @@ |
||||
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 } |
||||
} |
||||
@ -1,64 +0,0 @@ |
||||
import { useEffect, useMemo } from 'react' |
||||
|
||||
import debounce from 'lodash/debounce' |
||||
|
||||
import { usePageParams } from 'hooks/usePageParams' |
||||
import { useInterval } from 'hooks/useInterval' |
||||
|
||||
import { useMatchPopupStore } from 'features/MatchPopup' |
||||
|
||||
import { useEvents } from './useEvents' |
||||
|
||||
const MATCH_DATA_POLL_INTERVAL = 60000 |
||||
const MATCH_PLAYLISTS_DELAY = 5000 |
||||
|
||||
export const useMatchData = (live: boolean = false) => { |
||||
const { profileId: matchId, sportType } = usePageParams() |
||||
const { fetchMatchPlaylists, matchPlaylists } = useMatchPopupStore() |
||||
const { events, fetchMatchEvents } = useEvents() |
||||
|
||||
const fetchPlaylistsDebounced = useMemo( |
||||
() => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY), |
||||
[fetchMatchPlaylists], |
||||
) |
||||
|
||||
useEffect(() => { |
||||
fetchMatchPlaylists({ |
||||
id: matchId, |
||||
sportType, |
||||
withFullMatchDuration: !live, |
||||
}) |
||||
fetchMatchEvents() |
||||
}, [ |
||||
live, |
||||
matchId, |
||||
sportType, |
||||
fetchMatchPlaylists, |
||||
fetchMatchEvents, |
||||
]) |
||||
|
||||
const intervalCallback = () => { |
||||
fetchPlaylistsDebounced({ |
||||
id: matchId, |
||||
sportType, |
||||
withFullMatchDuration: !live, |
||||
}) |
||||
fetchMatchEvents() |
||||
} |
||||
|
||||
const { start, stop } = useInterval({ |
||||
callback: intervalCallback, |
||||
intervalDuration: MATCH_DATA_POLL_INTERVAL, |
||||
startImmediate: false, |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
if (live) { |
||||
start() |
||||
} else { |
||||
stop() |
||||
} |
||||
}, [live, start, stop]) |
||||
|
||||
return { events, matchPlaylists } |
||||
} |
||||
@ -1,76 +0,0 @@ |
||||
import { |
||||
useEffect, |
||||
useState, |
||||
useMemo, |
||||
} from 'react' |
||||
|
||||
import type { MatchInfo } from 'requests' |
||||
import { getMatchInfo } from 'requests' |
||||
|
||||
import { usePageParams } from 'hooks/usePageParams' |
||||
|
||||
import { parseDate } from 'helpers/parseDate' |
||||
|
||||
import type { Playlists } from '../types' |
||||
import { useMatchData } from './useMatchData' |
||||
import { useTournamentData } from './useTournamentData' |
||||
|
||||
const addScoresFromPlaylists = ( |
||||
profile: MatchInfo, |
||||
playlists: Playlists, |
||||
): MatchInfo => ( |
||||
profile |
||||
? { |
||||
...profile, |
||||
team1: { |
||||
...profile?.team1, |
||||
score: playlists.score1, |
||||
}, |
||||
team2: { |
||||
...profile?.team2, |
||||
score: playlists.score2, |
||||
}, |
||||
} |
||||
: null |
||||
) |
||||
|
||||
export const useMatchProfile = () => { |
||||
const [matchProfile, setMatchProfile] = useState<MatchInfo>(null) |
||||
const { profileId: matchId, sportType } = usePageParams() |
||||
|
||||
useEffect(() => { |
||||
getMatchInfo(sportType, matchId).then(setMatchProfile) |
||||
}, [sportType, matchId]) |
||||
|
||||
useEffect(() => { |
||||
let getIntervalMatch: ReturnType<typeof setInterval> |
||||
if (matchProfile?.live && !matchProfile.youtube_link) { |
||||
getIntervalMatch = setInterval( |
||||
() => getMatchInfo(sportType, matchId).then(setMatchProfile), 1000 * 60 * 3, |
||||
) |
||||
} |
||||
return () => clearInterval(getIntervalMatch) |
||||
}, [matchProfile, sportType, matchId]) |
||||
|
||||
const { events, matchPlaylists } = useMatchData(matchProfile?.live) |
||||
|
||||
const profile = useMemo( |
||||
() => addScoresFromPlaylists(matchProfile, matchPlaylists), |
||||
[matchProfile, matchPlaylists], |
||||
) |
||||
|
||||
const { tournamentData } = useTournamentData(matchProfile?.tournament.id ?? null) |
||||
|
||||
const isStarted = useMemo(() => ( |
||||
profile?.date |
||||
? parseDate(profile.date) < new Date() |
||||
: true |
||||
), [profile?.date]) |
||||
|
||||
return { |
||||
events, |
||||
isStarted, |
||||
profile, |
||||
tournamentData, |
||||
} |
||||
} |
||||
@ -1,14 +1,20 @@ |
||||
import styled from 'styled-components/macro' |
||||
import styled, { css } from 'styled-components/macro' |
||||
import { customScrollbar } from 'features/Common' |
||||
import { isMobileDevice } from '../../../../config/userAgent' |
||||
|
||||
export const MatchesWrapper = styled.div` |
||||
overflow-y: scroll; |
||||
overflow-y: auto; |
||||
max-height: calc(100vh - 170px); |
||||
|
||||
padding-right: 5px; |
||||
|
||||
> * { |
||||
margin-bottom: 15px; |
||||
} |
||||
|
||||
|
||||
${customScrollbar} |
||||
|
||||
${isMobileDevice ? css` |
||||
overflow: hidden; |
||||
max-height: initial; |
||||
` : ''}
|
||||
` |
||||
|
||||
@ -1,3 +1,5 @@ |
||||
export const REWIND_SECONDS = 5 |
||||
import { isMobileDevice } from 'config/userAgent' |
||||
|
||||
export const REWIND_SECONDS = isMobileDevice ? 10 : 5 |
||||
|
||||
export const HOUR_IN_MILLISECONDS = 60 * 60 * 1000 |
||||
|
||||
@ -0,0 +1,67 @@ |
||||
import { |
||||
useCallback, |
||||
useMemo, |
||||
useState, |
||||
} from 'react' |
||||
|
||||
import debounce from 'lodash/debounce' |
||||
|
||||
import { |
||||
Background, |
||||
RewindIcon, |
||||
Time, |
||||
} from './styled' |
||||
|
||||
export type RewindProps = { |
||||
isBackward?: boolean, |
||||
isForward?: boolean, |
||||
rewindCallback: () => void, |
||||
} |
||||
|
||||
const RewindMobile = ({ |
||||
isBackward, |
||||
isForward, |
||||
rewindCallback, |
||||
}: RewindProps) => { |
||||
const [isActive, setIsActive] = useState(false) |
||||
const [time, setTime] = useState(0) |
||||
|
||||
const resetData = useCallback(() => { |
||||
setIsActive(false) |
||||
setTime(0) |
||||
}, []) |
||||
|
||||
const resetDataDebounced = useMemo(() => ( |
||||
debounce(resetData, 1500) |
||||
), [resetData]) |
||||
|
||||
const handleDoubleClick = useCallback(() => { |
||||
setIsActive(true) |
||||
rewindCallback() |
||||
setTime(time + 10) |
||||
resetDataDebounced() |
||||
}, [resetDataDebounced, rewindCallback, time]) |
||||
|
||||
const TimeContent = useMemo(() => ( |
||||
<Time |
||||
isBackward={isBackward} |
||||
isForward={isForward} |
||||
> |
||||
<span>{isBackward ? '-' : '+'}{time}</span> |
||||
<RewindIcon src={`/images/rewind-${isBackward ? 'left' : 'right'}.svg`} /> |
||||
</Time> |
||||
), [isBackward, isForward, time]) |
||||
|
||||
return ( |
||||
<Background |
||||
isActive={isActive} |
||||
isBackward={isBackward} |
||||
isForward={isForward} |
||||
onDoubleClick={handleDoubleClick} |
||||
> |
||||
{isActive && TimeContent} |
||||
</Background> |
||||
) |
||||
} |
||||
|
||||
export default RewindMobile |
||||
@ -0,0 +1,41 @@ |
||||
import styled, { css } from 'styled-components/macro' |
||||
|
||||
type RewindProps = { |
||||
isActive?: boolean, |
||||
isBackward?: boolean, |
||||
isForward?: boolean, |
||||
} |
||||
|
||||
export const Background = styled.div<RewindProps>` |
||||
width: 920px; |
||||
height: 920px; |
||||
border-radius: 50%; |
||||
background-color: ${({ isActive }) => (isActive ? 'rgba(0, 0, 0, 0.4)' : 'rgba(0, 0, 0, 0)')}; |
||||
position: absolute; |
||||
${({ isBackward }) => isBackward && css`right: 60%;`} |
||||
${({ isForward }) => isForward && css`left: 60%;`} |
||||
transition: all 0.3s ease-in-out; |
||||
` |
||||
|
||||
export const Time = styled.div<RewindProps>` |
||||
position: absolute; |
||||
color: #FFFFFF; |
||||
top: 50%; |
||||
font-size: 20px; |
||||
transform: translate(0, -50%); |
||||
display: flex; |
||||
align-items: center; |
||||
user-select: none; |
||||
|
||||
${({ isBackward }) => isBackward && css`right: 2%;`} |
||||
${({ isForward }) => isForward && css`left: 2%;`} |
||||
|
||||
> :first-child { |
||||
margin-right: 10px; |
||||
} |
||||
` |
||||
|
||||
export const RewindIcon = styled.img` |
||||
width: 43px; |
||||
height: 28px; |
||||
` |
||||
@ -1,40 +1,56 @@ |
||||
import { |
||||
useMemo, |
||||
useState, |
||||
useCallback, |
||||
} from 'react' |
||||
import { useCallback, useMemo } from 'react' |
||||
|
||||
import debounce from 'lodash/debounce' |
||||
|
||||
export const useControlsVisibility = (isFullscreen: boolean) => { |
||||
const [controlsVisible, setControlsVisibility] = useState(true) |
||||
import { useToggle } from 'hooks' |
||||
|
||||
const show = useCallback(() => { |
||||
setControlsVisibility(true) |
||||
}, []) |
||||
export const useControlsVisibility = (isFullscreen: boolean, playing: boolean) => { |
||||
const { |
||||
close: hideCenterControls, |
||||
isOpen: centerControlsVisible, |
||||
open: showCenterControls, |
||||
} = useToggle(playing) |
||||
|
||||
const hide = useCallback(() => { |
||||
setControlsVisibility(false) |
||||
}, []) |
||||
const hideCenterDebounced = useMemo(() => ( |
||||
debounce(hideCenterControls, 2000) |
||||
), [hideCenterControls]) |
||||
|
||||
const showCenter = useCallback(() => { |
||||
if (playing) { |
||||
showCenterControls() |
||||
hideCenterDebounced() |
||||
} else { |
||||
showCenterControls() |
||||
} |
||||
}, [hideCenterDebounced, playing, showCenterControls]) |
||||
|
||||
const { |
||||
close: hideMainControls, |
||||
isOpen: mainControlsVisible, |
||||
open: showMainControls, |
||||
} = useToggle(true) |
||||
|
||||
const hideDebounced = useMemo( |
||||
() => debounce(hide, 3000), |
||||
[hide], |
||||
() => debounce(hideMainControls, 3000), |
||||
[hideMainControls], |
||||
) |
||||
|
||||
const onMouseMove = () => { |
||||
show() |
||||
showMainControls() |
||||
if (isFullscreen) { |
||||
hideDebounced() |
||||
} |
||||
} |
||||
|
||||
return { |
||||
controlsVisible, |
||||
onMouseEnter: show, |
||||
onMouseLeave: hide, |
||||
centerControlsVisible, |
||||
hideCenterControls, |
||||
mainControlsVisible, |
||||
onMouseEnter: showMainControls, |
||||
onMouseLeave: hideMainControls, |
||||
onMouseMove, |
||||
onTouchEnd: hideDebounced, |
||||
onTouchStart: show, |
||||
onTouchStart: showMainControls, |
||||
showCenterControls: showCenter, |
||||
} |
||||
} |
||||
|
||||
Loading…
Reference in new issue