diff --git a/src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx b/src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx index 04a2edaa..152638cf 100644 --- a/src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx +++ b/src/features/MatchPage/components/FinishedMatch/hooks/useEpisodes.tsx @@ -41,7 +41,7 @@ export const useEpisodes = () => { }).then(setEpisodes) } else if (playlistOption.type === PlaylistTypes.MATCH || playlistOption.type === PlaylistTypes.EVENT) { - setEpisodes(playlistOption.data) + setEpisodes(playlistOption.episodes) } }, [matchId, sportType]) diff --git a/src/features/MatchPage/components/LiveMatch/index.tsx b/src/features/MatchPage/components/LiveMatch/index.tsx index 00492228..72846631 100644 --- a/src/features/MatchPage/components/LiveMatch/index.tsx +++ b/src/features/MatchPage/components/LiveMatch/index.tsx @@ -1,38 +1,42 @@ import { Fragment } from 'react' -import type { Events } from 'requests/getMatchEvents' -import type { MatchInfo } from 'requests/getMatchInfo' +import isEmpty from 'lodash/isEmpty' +import { useMatchPageStore } from 'features/MatchPage/store' import { StreamPlayer } from 'features/StreamPlayer' import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer' import { MatchSidePlaylists } from 'features/MatchSidePlaylists' -import { isMobileDevice } from 'config/userAgent' - import { Container } from '../../styled' import { useLiveMatch } from './hooks' import { TournamentData } from '../../types' -import { MatchProfileCardMobile } from '../MatchProfileCardMobile' type Props = { - events: Events, - profile: MatchInfo, + // events: Events, + // profile: MatchInfo, tournamentData: TournamentData, } export const LiveMatch = ({ - events, - profile, + // events, + // profile, tournamentData, }: Props) => { const { + events, matchPlaylists, + profile, + selectedPlaylist, + } = useMatchPageStore() + + const { + chapters, + onDurationChange, onPlayerProgressChange, onPlayingChange, onPlaylistSelect, resume, - selectedPlaylist, streamUrl, } = useLiveMatch(profile) @@ -41,14 +45,26 @@ export const LiveMatch = ({ return ( - - {isMobileDevice ? : null} + {profile?.youtube_link ? ( + + ) : ( + !isEmpty(chapters) && ( + + ) + )} { +export const SubscriptionGuard = ({ children }: Props) => { + const { profile: matchProfile } = useMatchPageStore() const { open: openBuyMatchPopup } = useBuyMatchPopupStore() const { profileId: matchId, sportType } = usePageParams() diff --git a/src/features/MatchPage/helpers/buildPlaylists.tsx b/src/features/MatchPage/helpers/buildPlaylists.tsx index 89b17c0d..5e68480f 100644 --- a/src/features/MatchPage/helpers/buildPlaylists.tsx +++ b/src/features/MatchPage/helpers/buildPlaylists.tsx @@ -1,7 +1,7 @@ import map from 'lodash/map' import sortBy from 'lodash/sortBy' -import type { MatchPlaylists, Players } from 'requests' +import type { MatchPlaylists, Players } from 'requests/getMatchPlaylists' import type { Playlists, @@ -27,8 +27,8 @@ const getMatchPlaylists = (matchPlaylists: MatchPlaylists | null): MatchPlaylist const playlist = matchPlaylists[key] const lexic = matchPlaylists.lexics[key] return { - data: sortBy(playlist?.data, ['h', 's']), duration: playlist?.dur, + episodes: sortBy(playlist?.data, ['h', 's']), id: key, lexic, type: PlaylistTypes.MATCH, @@ -39,6 +39,7 @@ const getMatchPlaylists = (matchPlaylists: MatchPlaylists | null): MatchPlaylist const getPlayerPlaylists = (players?: Players): PlayerPlaylistOptions => ( map(players, (player) => ({ ...player, + episodes: [], type: PlaylistTypes.PLAYER, })) ) diff --git a/src/features/MatchPage/index.tsx b/src/features/MatchPage/index.tsx index 703245a7..4779b83b 100644 --- a/src/features/MatchPage/index.tsx +++ b/src/features/MatchPage/index.tsx @@ -11,20 +11,19 @@ import { import { FavoritesActions } from 'requests' import { ProfileTypes } from 'config' -import { isMobileDevice } from 'config/userAgent' import { usePageLogger } from 'hooks/usePageLogger' import { usePageParams } from 'hooks/usePageParams' +import { MatchPageStore } from './store' import { SubscriptionGuard } from './components/SubscriptionGuard' import { MatchProfileCard } from './components/MatchProfileCard' -import { FinishedMatch } from './components/FinishedMatch' import { LiveMatch } from './components/LiveMatch' +import { FinishedMatch } from './components/FinishedMatch' import { useMatchProfile } from './hooks/useMatchProfile' import { Wrapper } from './styled' -import { MatchPageStore } from './store' -const MatchPage = () => { +const MatchPageComponent = () => { usePageLogger() const history = useHistory() const { addRemoveFavorite, userFavorites } = useUserFavoritesStore() @@ -72,35 +71,40 @@ const MatchPage = () => { } return ( - - - - {isMobileDevice ? null : } - -
- - - - {playFromOTT && ( - - )} - {playFromScout && ( - - )} - - -
-
-
+ + + + +
+ + + + {playFromOTT && ( + + )} + {playFromScout && ( + + )} + + + +
+
) } +const MatchPage = () => ( + + + +) + export default MatchPage diff --git a/src/features/MatchPage/store/hooks/index.tsx b/src/features/MatchPage/store/hooks/index.tsx new file mode 100644 index 00000000..20ab2710 --- /dev/null +++ b/src/features/MatchPage/store/hooks/index.tsx @@ -0,0 +1,83 @@ +import { + useEffect, + useState, + useMemo, +} from 'react' + +import type { MatchInfo } from 'requests/getMatchInfo' +import { getMatchInfo } from 'requests/getMatchInfo' + +import { usePageParams } from 'hooks/usePageParams' + +import { parseDate } from 'helpers/parseDate' + +import { useMatchData } from './useMatchData' + +import type { Playlists } from '../../types' + +const addScoresFromPlaylists = ( + profile: MatchInfo, + playlists: Playlists, +): MatchInfo => ( + profile + ? { + ...profile, + team1: { + ...profile?.team1, + score: playlists.score1, + }, + team2: { + ...profile?.team2, + score: playlists.score2, + }, + } + : null +) + +export const useMatchPage = () => { + const [matchProfile, setMatchProfile] = useState(null) + const { profileId: matchId, sportType } = usePageParams() + + useEffect(() => { + getMatchInfo(sportType, matchId).then(setMatchProfile) + }, [sportType, matchId]) + + useEffect(() => { + let getIntervalMatch: ReturnType + if (matchProfile?.live && !matchProfile.youtube_link) { + getIntervalMatch = setInterval( + () => getMatchInfo(sportType, matchId).then(setMatchProfile), 1000 * 60 * 3, + ) + } + return () => clearInterval(getIntervalMatch) + }, [matchProfile, sportType, matchId]) + + const { + events, + handlePlaylistClick, + matchPlaylists, + selectedPlaylist, + setFullMatchPlaylistDuration, + } = useMatchData(matchProfile?.live) + + const profile = useMemo( + () => addScoresFromPlaylists(matchProfile, matchPlaylists), + [matchProfile, matchPlaylists], + ) + + const isStarted = useMemo(() => ( + profile?.date + ? parseDate(profile.date) < new Date() + : true + ), [profile?.date]) + + return { + events, + handlePlaylistClick, + isStarted, + matchPlaylists, + profile, + selectedPlaylist, + setFullMatchPlaylistDuration, + } +} diff --git a/src/features/MatchPage/store/hooks/useEvents.tsx b/src/features/MatchPage/store/hooks/useEvents.tsx new file mode 100644 index 00000000..14f7095c --- /dev/null +++ b/src/features/MatchPage/store/hooks/useEvents.tsx @@ -0,0 +1,28 @@ +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([]) + const { fetchLexics } = useEventsLexics() + const { profileId: matchId, sportType } = usePageParams() + + const fetchMatchEvents = useCallback(() => { + getMatchEvents({ + matchId, + sportType, + }).then(fetchLexics) + .then(setEvents) + }, [ + fetchLexics, + matchId, + sportType, + ]) + + return { events, fetchMatchEvents } +} diff --git a/src/features/MatchPage/store/hooks/useEventsLexics.tsx b/src/features/MatchPage/store/hooks/useEventsLexics.tsx new file mode 100644 index 00000000..cc886d0f --- /dev/null +++ b/src/features/MatchPage/store/hooks/useEventsLexics.tsx @@ -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 } +} diff --git a/src/features/MatchPage/store/hooks/useMatchData.tsx b/src/features/MatchPage/store/hooks/useMatchData.tsx new file mode 100644 index 00000000..016f24e7 --- /dev/null +++ b/src/features/MatchPage/store/hooks/useMatchData.tsx @@ -0,0 +1,72 @@ +import { useEffect, useMemo } from 'react' + +import debounce from 'lodash/debounce' + +import { usePageParams } from 'hooks/usePageParams' +import { useInterval } from 'hooks/useInterval' + +import { useMatchPlaylists } from './useMatchPlaylists' +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, + handlePlaylistClick, + matchPlaylists, + selectedPlaylist, + setFullMatchPlaylistDuration, + } = useMatchPlaylists() + const { events, fetchMatchEvents } = useEvents() + + const fetchPlaylistsDebounced = useMemo( + () => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY), + [fetchMatchPlaylists], + ) + + useEffect(() => { + fetchMatchPlaylists({ + id: matchId, + sportType, + }) + fetchMatchEvents() + }, [ + matchId, + sportType, + fetchMatchPlaylists, + fetchMatchEvents, + ]) + + const intervalCallback = () => { + fetchPlaylistsDebounced({ + id: matchId, + sportType, + }) + 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, + handlePlaylistClick, + matchPlaylists, + selectedPlaylist, + setFullMatchPlaylistDuration, + } +} diff --git a/src/features/MatchPage/store/hooks/useMatchPlaylists.tsx b/src/features/MatchPage/store/hooks/useMatchPlaylists.tsx new file mode 100644 index 00000000..70feee6b --- /dev/null +++ b/src/features/MatchPage/store/hooks/useMatchPlaylists.tsx @@ -0,0 +1,81 @@ +import { + useState, + useCallback, +} from 'react' + +import isEmpty from 'lodash/isEmpty' + +import type { SportTypes } from 'config/sportTypes' + +import { getMatchPlaylists } from 'requests/getMatchPlaylists' + +import type { Playlists } from 'features/MatchPage/types' +import { buildPlaylists } from 'features/MatchPage/helpers/buildPlaylists' + +import { usePlaylistLexics } from './usePlaylistLexics' +import { useSelectedPlaylist } from './useSelectedPlaylist' + +type ArgsFetchMatchPlaylists = { + id: number, + sportType: SportTypes, +} + +const initialPlaylists = buildPlaylists(null) + +export const useMatchPlaylists = () => { + const [matchPlaylists, setMatchPlaylists] = useState(initialPlaylists) + + const { fetchLexics } = usePlaylistLexics() + const { + handlePlaylistClick, + selectedPlaylist, + setSelectedPlaylist, + } = useSelectedPlaylist() + + const setInitialSeletedPlaylist = useCallback((playlists: Playlists) => { + setSelectedPlaylist((playlist) => { + if (!playlist && !isEmpty(playlists.match)) { + return playlists.match[0] + } + return playlist + }) + return playlists + }, [setSelectedPlaylist]) + + const fetchMatchPlaylists = useCallback(({ + id, + sportType, + }: ArgsFetchMatchPlaylists) => { + getMatchPlaylists({ + matchId: id, + selectedActions: [], + sportType, + }).then(fetchLexics) + .then(buildPlaylists) + .then(setInitialSeletedPlaylist) + .then(setMatchPlaylists) + }, [fetchLexics, setInitialSeletedPlaylist]) + + const setFullMatchPlaylistDuration = (duration: number) => { + const playlists = [...matchPlaylists.match] + if (!playlists[0]) return + + playlists[0].duration = duration + setMatchPlaylists({ + ...matchPlaylists, + match: playlists, + }) + + if (selectedPlaylist) { + setSelectedPlaylist({ ...selectedPlaylist }) + } + } + + return { + fetchMatchPlaylists, + handlePlaylistClick, + matchPlaylists, + selectedPlaylist, + setFullMatchPlaylistDuration, + } +} diff --git a/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx b/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx new file mode 100644 index 00000000..82c6f79a --- /dev/null +++ b/src/features/MatchPage/store/hooks/usePlaylistLexics.tsx @@ -0,0 +1,21 @@ +import { useCallback } from 'react' + +import isEmpty from 'lodash/isEmpty' +import values from 'lodash/values' + +import type { MatchPlaylists } from 'requests' + +import { useLexicsStore } from 'features/LexicsStore' + +export const usePlaylistLexics = () => { + const { addLexicsConfig } = useLexicsStore() + const fetchLexics = useCallback((playlist: MatchPlaylists | null) => { + const lexics = values(playlist?.lexics) + if (!isEmpty(lexics)) { + addLexicsConfig(lexics) + } + return playlist + }, [addLexicsConfig]) + + return { fetchLexics } +} diff --git a/src/features/MatchPage/store/hooks/useSelectedPlaylist.tsx b/src/features/MatchPage/store/hooks/useSelectedPlaylist.tsx new file mode 100644 index 00000000..ee732c5b --- /dev/null +++ b/src/features/MatchPage/store/hooks/useSelectedPlaylist.tsx @@ -0,0 +1,49 @@ +import type { MouseEvent } from 'react' +import { useState, useCallback } from 'react' + +import { getPlayerPlaylists } from 'requests/getPlayerPlaylists' + +import { usePageParams } from 'hooks/usePageParams' + +import { + PlayerPlaylistOption, + PlaylistOption, + PlaylistTypes, +} from 'features/MatchPage/types' +import { defaultSettings } from 'features/MatchPopup/types' + +export const useSelectedPlaylist = () => { + const { profileId: matchId, sportType } = usePageParams() + const [selectedPlaylist, setSelectedPlaylist] = useState() + + const fetchPlayerEpisodes = useCallback((playlistOption: PlayerPlaylistOption) => ( + getPlayerPlaylists({ + matchId, + playerId: playlistOption.id, + settings: defaultSettings, + sportType, + }) + ), [matchId, sportType]) + + const handlePlaylistClick = useCallback((playlist: PlaylistOption, e?: MouseEvent) => { + e?.stopPropagation() + if (playlist === selectedPlaylist) return + + if (playlist.type === PlaylistTypes.PLAYER) { + fetchPlayerEpisodes(playlist).then((episodes) => { + setSelectedPlaylist({ + ...playlist, + episodes, + }) + }) + } else { + setSelectedPlaylist(playlist) + } + }, [fetchPlayerEpisodes, selectedPlaylist]) + + return { + handlePlaylistClick, + selectedPlaylist, + setSelectedPlaylist, + } +} diff --git a/src/features/MatchPage/store/index.tsx b/src/features/MatchPage/store/index.tsx index 674efdc2..2f5e34bd 100644 --- a/src/features/MatchPage/store/index.tsx +++ b/src/features/MatchPage/store/index.tsx @@ -1,7 +1,10 @@ import type { ReactNode } from 'react' -import { createContext, useContext } from 'react' +import { + createContext, + useContext, +} from 'react' -import { useMatchPage } from '../hooks' +import { useMatchPage } from './hooks' type Context = ReturnType type Props = { children: ReactNode } @@ -9,12 +12,8 @@ type Props = { children: ReactNode } const MatchPageContext = createContext({} as Context) export const MatchPageStore = ({ children }: Props) => { - const value = useMatchPage() - return ( - - {children} - - ) + const lexics = useMatchPage() + return {children} } export const useMatchPageStore = () => useContext(MatchPageContext) diff --git a/src/features/MatchPage/types.tsx b/src/features/MatchPage/types.tsx index 8a5b7892..8d19fd04 100644 --- a/src/features/MatchPage/types.tsx +++ b/src/features/MatchPage/types.tsx @@ -12,8 +12,8 @@ export enum PlaylistTypes { } export type MatchPlaylistOption = { - data: Episodes, duration?: number, + episodes: Episodes, id: MatchPlaylistIds, lexic: number | string, type: PlaylistTypes.MATCH, @@ -21,6 +21,7 @@ export type MatchPlaylistOption = { export type PlayerPlaylistOption = { dur: number, + episodes: Episodes, gk?: boolean, id: number, name_eng: string, @@ -31,6 +32,7 @@ export type PlayerPlaylistOption = { } export type InterviewPlaylistOption = { + episodes: Episodes, id: number, name_eng: string, name_rus: string, @@ -38,7 +40,7 @@ export type InterviewPlaylistOption = { } export type EventPlaylistOption = { - data: Episodes, + episodes: Episodes, id: number, type: PlaylistTypes.EVENT, } diff --git a/src/features/MatchSidePlaylists/components/EventsList/index.tsx b/src/features/MatchSidePlaylists/components/EventsList/index.tsx index 18cdfc5b..3a1eed3a 100644 --- a/src/features/MatchSidePlaylists/components/EventsList/index.tsx +++ b/src/features/MatchSidePlaylists/components/EventsList/index.tsx @@ -45,7 +45,7 @@ export const EventsList = ({ const repeatedEpisodes = event.rep || [] const eventPlaylist = { - data: [{ + episodes: [{ e: event.e, h: event.h, s: event.s,