Ott 432 matches request (#138)
* refactor(#432): added saving callback in ref in intersection observer hook * refactor(#432): removed filters from matches request * refactor(#432): moved matches state from HomePage into Matches feature * feat(#432): added infinite scroll grid matches to other profiles Co-authored-by: mirlan.maksitaliev <mirlan.maksitaliev@instatsport.com>keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
parent
88e5dd9fb5
commit
73c0fbfe82
@ -1,23 +1,15 @@ |
||||
import React from 'react' |
||||
|
||||
import { InfiniteScroll } from 'features/InfiniteScroll' |
||||
import { Matches } from 'features/Matches' |
||||
|
||||
import { useHomePage } from './hooks' |
||||
import { Content, Loading } from './styled' |
||||
import { Content } from './styled' |
||||
|
||||
export const HomePage = () => { |
||||
const { |
||||
fetchMoreMatches, |
||||
isFetching, |
||||
matches, |
||||
} = useHomePage() |
||||
const { requestArgs } = useHomePage() |
||||
return ( |
||||
<Content> |
||||
<InfiniteScroll onFetchMore={fetchMoreMatches}> |
||||
<Matches matches={matches} /> |
||||
</InfiniteScroll> |
||||
{isFetching && <Loading>Loading...</Loading>} |
||||
<Matches requestArgs={requestArgs} /> |
||||
</Content> |
||||
) |
||||
} |
||||
|
||||
@ -0,0 +1,39 @@ |
||||
import React, { Fragment } from 'react' |
||||
|
||||
import isEmpty from 'lodash/isEmpty' |
||||
|
||||
import type { Match } from 'features/Matches/hooks' |
||||
import { MatchesSlider } from 'features/MatchesSlider' |
||||
import { MatchesGrid } from 'features/MatchesGrid' |
||||
import { T9n } from 'features/T9n' |
||||
|
||||
import { Title, Section } from '../../styled' |
||||
|
||||
const matchesComponents = { |
||||
grid: MatchesGrid, |
||||
slider: MatchesSlider, |
||||
} |
||||
|
||||
type Props = { |
||||
as: keyof typeof matchesComponents, |
||||
matches: Array<Match>, |
||||
title: string, |
||||
} |
||||
|
||||
export const MatchesList = ({ |
||||
as, |
||||
matches, |
||||
title, |
||||
}: Props) => { |
||||
const Component = matchesComponents[as] |
||||
return ( |
||||
<Fragment> |
||||
{!isEmpty(matches) && ( |
||||
<Section> |
||||
<Title><T9n t={title} /></Title> |
||||
<Component matches={matches} /> |
||||
</Section> |
||||
)} |
||||
</Fragment> |
||||
) |
||||
} |
||||
@ -0,0 +1,49 @@ |
||||
import map from 'lodash/map' |
||||
import flatten from 'lodash/flatten' |
||||
import pipe from 'lodash/fp/pipe' |
||||
import fpMap from 'lodash/fp/map' |
||||
|
||||
import type { Content } from 'requests' |
||||
import { ProfileTypes } from 'config' |
||||
import { getProfileLogo, getSportLexic } from 'helpers' |
||||
|
||||
type Name = 'name_rus' | 'name_eng' |
||||
|
||||
const prepareMatch = ({ |
||||
matches: matchesList, |
||||
sport, |
||||
...rest |
||||
}: Content, suffix: string) => map(matchesList, ({ |
||||
date, |
||||
id, |
||||
stream_status, |
||||
team1, |
||||
team2, |
||||
}) => ({ |
||||
date, |
||||
id, |
||||
preview: '/images/preview.png', |
||||
sportName: getSportLexic(sport), |
||||
sportType: sport, |
||||
streamStatus: stream_status, |
||||
team1Logo: getProfileLogo({ |
||||
id: team1.id, |
||||
profileType: ProfileTypes.TEAMS, |
||||
sportType: sport, |
||||
}), |
||||
team1Name: team1[`name_${suffix}` as Name], |
||||
team1Score: team1.score, |
||||
team2Logo: getProfileLogo({ |
||||
id: team2.id, |
||||
profileType: ProfileTypes.TEAMS, |
||||
sportType: sport, |
||||
}), |
||||
team2Name: team2[`name_${suffix}` as Name], |
||||
team2Score: team2.score, |
||||
tournamentName: rest[`name_${suffix}` as Name], |
||||
})) |
||||
|
||||
export const prepareMatches = (content: Array<Content>, suffix: string) => pipe( |
||||
fpMap((items: Content) => prepareMatch(items, suffix)), |
||||
flatten, |
||||
)(content) |
||||
@ -1,82 +1,93 @@ |
||||
import { useMemo } from 'react' |
||||
import { |
||||
useCallback, |
||||
useEffect, |
||||
useState, |
||||
useMemo, |
||||
useRef, |
||||
} from 'react' |
||||
|
||||
import map from 'lodash/map' |
||||
import flatten from 'lodash/flatten' |
||||
import pipe from 'lodash/fp/pipe' |
||||
import fpMap from 'lodash/fp/map' |
||||
import type { Matches } from 'requests' |
||||
import { getMatches } from 'requests' |
||||
|
||||
import { useRequest } from 'hooks' |
||||
|
||||
import type { Matches, Content } from 'requests' |
||||
import { ProfileTypes } from 'config' |
||||
import { getProfileLogo, getSportLexic } from 'helpers' |
||||
import { useLexicsStore } from 'features/LexicsStore' |
||||
import { MatchStatuses } from 'features/HeaderFilters' |
||||
|
||||
import { prepareMatches } from './helpers' |
||||
|
||||
export type Match = ReturnType<typeof prepareMatches>[number] |
||||
|
||||
type RequestArgs = Omit<Parameters<typeof getMatches>[0], 'limit' | 'offset'> |
||||
|
||||
export type Props = { |
||||
matches: Matches, |
||||
requestArgs: RequestArgs, |
||||
} |
||||
|
||||
type Name = 'name_rus' | 'name_eng' |
||||
|
||||
export type Match = { |
||||
date: string, |
||||
id: number, |
||||
preview: string, |
||||
sportName: string, |
||||
sportType: number, |
||||
streamStatus: MatchStatuses, |
||||
team1Logo: string, |
||||
team1Name: string, |
||||
team1Score: number, |
||||
team2Logo: string, |
||||
team2Name: string, |
||||
team2Score: number, |
||||
tournamentName: string, |
||||
} |
||||
const MATCHES_LIMIT = 60 |
||||
|
||||
const prepareMatches = (content: Array<Content>, suffix: string) => pipe( |
||||
fpMap<Content, Array<Match>>(({ |
||||
matches: matchesList, |
||||
sport, |
||||
...rest |
||||
}) => map(matchesList, ({ |
||||
date, |
||||
id, |
||||
stream_status, |
||||
team1, |
||||
team2, |
||||
}) => ({ |
||||
date, |
||||
id, |
||||
preview: '/images/preview.png', |
||||
sportName: getSportLexic(sport), |
||||
sportType: sport, |
||||
streamStatus: stream_status, |
||||
team1Logo: getProfileLogo({ |
||||
id: team1.id, |
||||
profileType: ProfileTypes.TEAMS, |
||||
sportType: sport, |
||||
}), |
||||
team1Name: team1[`name_${suffix}` as Name], |
||||
team1Score: team1.score, |
||||
team2Logo: getProfileLogo({ |
||||
id: team2.id, |
||||
profileType: ProfileTypes.TEAMS, |
||||
sportType: sport, |
||||
}), |
||||
team2Name: team2[`name_${suffix}` as Name], |
||||
team2Score: team2.score, |
||||
tournamentName: rest[`name_${suffix}` as Name], |
||||
}))), |
||||
flatten, |
||||
)(content) as Array<Match> |
||||
|
||||
export const useMatches = ({ matches }: Props) => { |
||||
export const useMatches = ({ requestArgs }: Props) => { |
||||
const { suffix } = useLexicsStore() |
||||
const { |
||||
isFetching, |
||||
request: requestMatches, |
||||
} = useRequest(getMatches) |
||||
|
||||
const pageRef = useRef(0) |
||||
const [matches, setMatches] = useState<Matches>({ |
||||
broadcast: [], |
||||
features: [], |
||||
hasNextPage: true, |
||||
highlights: [], |
||||
isVideoSections: false, |
||||
}) |
||||
|
||||
return useMemo(() => ({ |
||||
const { hasNextPage } = matches |
||||
|
||||
const fetchMatches = useCallback((page: number) => ( |
||||
requestMatches({ |
||||
...requestArgs, |
||||
limit: MATCHES_LIMIT, |
||||
offset: page * MATCHES_LIMIT, |
||||
}) |
||||
), [ |
||||
requestMatches, |
||||
requestArgs, |
||||
]) |
||||
|
||||
const fetchMoreMatches = useCallback(async () => { |
||||
if (!hasNextPage || isFetching) return |
||||
|
||||
const newMatches = await fetchMatches(pageRef.current) |
||||
setMatches((oldMatches): Matches => { |
||||
const broadcast = [...oldMatches.broadcast, ...newMatches.broadcast] |
||||
return { |
||||
...oldMatches, |
||||
broadcast, |
||||
hasNextPage: newMatches.hasNextPage, |
||||
} |
||||
}) |
||||
pageRef.current += 1 |
||||
}, [ |
||||
fetchMatches, |
||||
hasNextPage, |
||||
isFetching, |
||||
]) |
||||
|
||||
useEffect(() => { |
||||
fetchMatches(0).then(setMatches) |
||||
pageRef.current = 1 |
||||
}, [fetchMatches]) |
||||
|
||||
const preparedMatches = useMemo(() => ({ |
||||
broadcast: prepareMatches(matches.broadcast, suffix), |
||||
features: prepareMatches(matches.features, suffix), |
||||
highlights: prepareMatches(matches.highlights, suffix), |
||||
isVideoSections: matches.isVideoSections, |
||||
}), [suffix, matches]) |
||||
|
||||
return { |
||||
fetchMoreMatches, |
||||
isFetching, |
||||
matches: preparedMatches, |
||||
} |
||||
} |
||||
|
||||
@ -1,60 +1,56 @@ |
||||
import React, { Fragment } from 'react' |
||||
|
||||
import isEmpty from 'lodash/isEmpty' |
||||
|
||||
import { MatchesSlider } from 'features/MatchesSlider' |
||||
import { MatchesGrid } from 'features/MatchesGrid' |
||||
import { T9n } from 'features/T9n' |
||||
import { InfiniteScroll } from 'features/InfiniteScroll' |
||||
import { Loading } from 'features/HomePage/styled' |
||||
|
||||
import type { Props } from './hooks' |
||||
import { useMatches } from './hooks' |
||||
import { Title, Section } from './styled' |
||||
import { MatchesList } from './components/MatchesList' |
||||
|
||||
export type { Match } from './hooks' |
||||
|
||||
export const Matches = (props: Props) => { |
||||
const { |
||||
broadcast, |
||||
features, |
||||
highlights, |
||||
isVideoSections, |
||||
fetchMoreMatches, |
||||
isFetching, |
||||
matches: { |
||||
broadcast, |
||||
features, |
||||
highlights, |
||||
isVideoSections, |
||||
}, |
||||
} = useMatches(props) |
||||
|
||||
if (isVideoSections) { |
||||
return ( |
||||
<Fragment> |
||||
{!isEmpty(broadcast) && ( |
||||
<Section> |
||||
<Title><T9n t='broadcast' /></Title> |
||||
<MatchesSlider matches={broadcast} /> |
||||
</Section> |
||||
)} |
||||
|
||||
{!isEmpty(highlights) && ( |
||||
<Section> |
||||
<Title><T9n t='round_highilights' /></Title> |
||||
<MatchesSlider matches={highlights} /> |
||||
</Section> |
||||
)} |
||||
|
||||
{!isEmpty(features) && ( |
||||
<Section> |
||||
<Title><T9n t='features' /></Title> |
||||
<MatchesSlider matches={highlights} /> |
||||
</Section> |
||||
)} |
||||
<MatchesList |
||||
as='slider' |
||||
title='broadcast' |
||||
matches={broadcast} |
||||
/> |
||||
<MatchesList |
||||
as='slider' |
||||
title='round_highilights' |
||||
matches={highlights} |
||||
/> |
||||
<MatchesList |
||||
as='slider' |
||||
title='features' |
||||
matches={features} |
||||
/> |
||||
</Fragment> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<Fragment> |
||||
{!isEmpty(broadcast) && ( |
||||
<Section> |
||||
<Title><T9n t='broadcast' /></Title> |
||||
<MatchesGrid matches={broadcast} /> |
||||
</Section> |
||||
)} |
||||
</Fragment> |
||||
<InfiniteScroll onFetchMore={fetchMoreMatches}> |
||||
<MatchesList |
||||
as='grid' |
||||
title='broadcast' |
||||
matches={broadcast} |
||||
/> |
||||
{isFetching && <Loading>Loading...</Loading>} |
||||
</InfiniteScroll> |
||||
) |
||||
} |
||||
|
||||
Loading…
Reference in new issue