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 React from 'react' |
||||||
|
|
||||||
import { InfiniteScroll } from 'features/InfiniteScroll' |
|
||||||
import { Matches } from 'features/Matches' |
import { Matches } from 'features/Matches' |
||||||
|
|
||||||
import { useHomePage } from './hooks' |
import { useHomePage } from './hooks' |
||||||
import { Content, Loading } from './styled' |
import { Content } from './styled' |
||||||
|
|
||||||
export const HomePage = () => { |
export const HomePage = () => { |
||||||
const { |
const { requestArgs } = useHomePage() |
||||||
fetchMoreMatches, |
|
||||||
isFetching, |
|
||||||
matches, |
|
||||||
} = useHomePage() |
|
||||||
return ( |
return ( |
||||||
<Content> |
<Content> |
||||||
<InfiniteScroll onFetchMore={fetchMoreMatches}> |
<Matches requestArgs={requestArgs} /> |
||||||
<Matches matches={matches} /> |
|
||||||
</InfiniteScroll> |
|
||||||
{isFetching && <Loading>Loading...</Loading>} |
|
||||||
</Content> |
</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 type { Matches } from 'requests' |
||||||
import flatten from 'lodash/flatten' |
import { getMatches } from 'requests' |
||||||
import pipe from 'lodash/fp/pipe' |
|
||||||
import fpMap from 'lodash/fp/map' |
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 { 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 = { |
export type Props = { |
||||||
matches: Matches, |
requestArgs: RequestArgs, |
||||||
} |
} |
||||||
|
|
||||||
type Name = 'name_rus' | 'name_eng' |
const MATCHES_LIMIT = 60 |
||||||
|
|
||||||
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 prepareMatches = (content: Array<Content>, suffix: string) => pipe( |
export const useMatches = ({ requestArgs }: Props) => { |
||||||
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) => { |
|
||||||
const { suffix } = useLexicsStore() |
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), |
broadcast: prepareMatches(matches.broadcast, suffix), |
||||||
features: prepareMatches(matches.features, suffix), |
features: prepareMatches(matches.features, suffix), |
||||||
highlights: prepareMatches(matches.highlights, suffix), |
highlights: prepareMatches(matches.highlights, suffix), |
||||||
isVideoSections: matches.isVideoSections, |
isVideoSections: matches.isVideoSections, |
||||||
}), [suffix, matches]) |
}), [suffix, matches]) |
||||||
|
|
||||||
|
return { |
||||||
|
fetchMoreMatches, |
||||||
|
isFetching, |
||||||
|
matches: preparedMatches, |
||||||
|
} |
||||||
} |
} |
||||||
|
|||||||
@ -1,60 +1,56 @@ |
|||||||
import React, { Fragment } from 'react' |
import React, { Fragment } from 'react' |
||||||
|
|
||||||
import isEmpty from 'lodash/isEmpty' |
import { InfiniteScroll } from 'features/InfiniteScroll' |
||||||
|
import { Loading } from 'features/HomePage/styled' |
||||||
import { MatchesSlider } from 'features/MatchesSlider' |
|
||||||
import { MatchesGrid } from 'features/MatchesGrid' |
|
||||||
import { T9n } from 'features/T9n' |
|
||||||
|
|
||||||
import type { Props } from './hooks' |
import type { Props } from './hooks' |
||||||
import { useMatches } from './hooks' |
import { useMatches } from './hooks' |
||||||
import { Title, Section } from './styled' |
import { MatchesList } from './components/MatchesList' |
||||||
|
|
||||||
export type { Match } from './hooks' |
export type { Match } from './hooks' |
||||||
|
|
||||||
export const Matches = (props: Props) => { |
export const Matches = (props: Props) => { |
||||||
const { |
const { |
||||||
|
fetchMoreMatches, |
||||||
|
isFetching, |
||||||
|
matches: { |
||||||
broadcast, |
broadcast, |
||||||
features, |
features, |
||||||
highlights, |
highlights, |
||||||
isVideoSections, |
isVideoSections, |
||||||
|
}, |
||||||
} = useMatches(props) |
} = useMatches(props) |
||||||
|
|
||||||
if (isVideoSections) { |
if (isVideoSections) { |
||||||
return ( |
return ( |
||||||
<Fragment> |
<Fragment> |
||||||
{!isEmpty(broadcast) && ( |
<MatchesList |
||||||
<Section> |
as='slider' |
||||||
<Title><T9n t='broadcast' /></Title> |
title='broadcast' |
||||||
<MatchesSlider matches={broadcast} /> |
matches={broadcast} |
||||||
</Section> |
/> |
||||||
)} |
<MatchesList |
||||||
|
as='slider' |
||||||
{!isEmpty(highlights) && ( |
title='round_highilights' |
||||||
<Section> |
matches={highlights} |
||||||
<Title><T9n t='round_highilights' /></Title> |
/> |
||||||
<MatchesSlider matches={highlights} /> |
<MatchesList |
||||||
</Section> |
as='slider' |
||||||
)} |
title='features' |
||||||
|
matches={features} |
||||||
{!isEmpty(features) && ( |
/> |
||||||
<Section> |
|
||||||
<Title><T9n t='features' /></Title> |
|
||||||
<MatchesSlider matches={highlights} /> |
|
||||||
</Section> |
|
||||||
)} |
|
||||||
</Fragment> |
</Fragment> |
||||||
) |
) |
||||||
} |
} |
||||||
|
|
||||||
return ( |
return ( |
||||||
<Fragment> |
<InfiniteScroll onFetchMore={fetchMoreMatches}> |
||||||
{!isEmpty(broadcast) && ( |
<MatchesList |
||||||
<Section> |
as='grid' |
||||||
<Title><T9n t='broadcast' /></Title> |
title='broadcast' |
||||||
<MatchesGrid matches={broadcast} /> |
matches={broadcast} |
||||||
</Section> |
/> |
||||||
)} |
{isFetching && <Loading>Loading...</Loading>} |
||||||
</Fragment> |
</InfiniteScroll> |
||||||
) |
) |
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue