fix(#2526): add loaders for fetching matches and teams

keep-around/78e030bc4b652b05b2a3e2f86799c9eb3658419d
Andrei Dekterev 3 years ago
parent 58c96bda11
commit 0661e13e54
  1. 2
      Makefile
  2. 1
      src/config/lexics/indexLexics.tsx
  3. 100
      src/features/App/AuthenticatedApp.tsx
  4. 13
      src/features/BuyMatchPopup/components/CardStep/index.tsx
  5. 8
      src/features/Combobox/index.tsx
  6. 21
      src/features/Combobox/styled.tsx
  7. 1
      src/features/Combobox/types.tsx
  8. 6
      src/features/TournamentList/components/CollapseTournament/index.tsx
  9. 3
      src/features/UserAccount/components/ChangeCardPopup/index.tsx
  10. 57
      src/pages/HighlightsPage/components/FormHighlights/hooks.tsx
  11. 4
      src/pages/HighlightsPage/components/FormHighlights/index.tsx
  12. 25
      src/pages/HighlightsPage/components/MatchesHighlights/index.tsx
  13. 11
      src/pages/HighlightsPage/components/MatchesHighlights/styled.tsx
  14. 1
      src/pages/HighlightsPage/components/ThanksPopup/index.tsx
  15. 7
      src/pages/HighlightsPage/index.tsx
  16. 10
      src/pages/HighlightsPage/storeHighlightsAtoms.tsx

@ -166,7 +166,7 @@ lff-prod: clean
deploy-all: prod preprod facr-prod lff-prod deploy-all: prod preprod facr-prod lff-prod
stage: build-stage stage: build-stage
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@staging.instat.tv:/usr/local/www/ott-staging/wwwroot/ rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/wwwroot/
a-stage: build-a a-stage: build-a
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/a-wwwroot/ rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/a-wwwroot/

@ -100,6 +100,7 @@ export const indexLexics = {
available_matches_shown: 13385, available_matches_shown: 13385,
basketball: 6960, basketball: 6960,
broadcast: 13049, broadcast: 13049,
broadcasts: 2891,
by_clicking: 17879, by_clicking: 17879,
check_connection: 15700, check_connection: 15700,
choose_sport: 17927, choose_sport: 17927,

@ -41,58 +41,58 @@ export const AuthenticatedApp = () => {
useLexicsConfig(indexLexics) useLexicsConfig(indexLexics)
return ( return (
<StripeElements> <StripeElements>
<CardsStore> <RecoilRoot>
<MatchSwitchesStore> <CardsStore>
<UserFavoritesStore> <MatchSwitchesStore>
<ExtendedSearchStore> <UserFavoritesStore>
<PreferencesPopupStore> <ExtendedSearchStore>
<TournamentPopupStore> <PreferencesPopupStore>
<MatchPopupStore> <TournamentPopupStore>
<BuyMatchPopupStore> <MatchPopupStore>
<NoNetworkPopupStore> <BuyMatchPopupStore>
<MatchPopup /> <NoNetworkPopupStore>
<BuyMatchPopup /> <MatchPopup />
{ client.name === 'facr' ? <TournamentsPopup /> : <PreferencesPopup /> } <BuyMatchPopup />
<NoNetworkPopup /> { client.name === 'facr' ? <TournamentsPopup /> : <PreferencesPopup /> }
<NoNetworkPopup />
{/* в Switch как прямой children {/* в Switch как прямой children
можно рендерить только Route или Redirect */} можно рендерить только Route или Redirect */}
<Switch> <Switch>
<Route path={PAGES.useraccount}> <Route path={PAGES.useraccount}>
<UserAccount /> <UserAccount />
</Route> </Route>
<Route exact path={PAGES.home}> <Route exact path={PAGES.home}>
<HomePage /> <HomePage />
</Route> </Route>
<Route path={`/:sportName${PAGES.tournament}/:pageId`}> <Route path={`/:sportName${PAGES.tournament}/:pageId`}>
<TournamentPage /> <TournamentPage />
</Route> </Route>
<Route path={`/:sportName${PAGES.team}/:pageId`}> <Route path={`/:sportName${PAGES.team}/:pageId`}>
<TeamPage /> <TeamPage />
</Route> </Route>
<Route path={`/:sportName${PAGES.player}/:pageId`}> <Route path={`/:sportName${PAGES.player}/:pageId`}>
<PlayerPage /> <PlayerPage />
</Route> </Route>
<Route path={`/:sportName${PAGES.match}/:pageId`}> <Route path={`/:sportName${PAGES.match}/:pageId`}>
<MatchPage /> <MatchPage />
</Route> </Route>
<Route path={`${PAGES.highlights}`}> <Route path={`${PAGES.highlights}`}>
<RecoilRoot>
<HighlightsPage /> <HighlightsPage />
</RecoilRoot> </Route>
</Route> <Redirect to={PAGES.home} />
<Redirect to={PAGES.home} /> </Switch>
</Switch> {!isProduction && <SystemSettings />}
{!isProduction && <SystemSettings />} </NoNetworkPopupStore>
</NoNetworkPopupStore> </BuyMatchPopupStore>
</BuyMatchPopupStore> </MatchPopupStore>
</MatchPopupStore> </TournamentPopupStore>
</TournamentPopupStore> </PreferencesPopupStore>
</PreferencesPopupStore> </ExtendedSearchStore>
</ExtendedSearchStore> </UserFavoritesStore>
</UserFavoritesStore> </MatchSwitchesStore>
</MatchSwitchesStore> </CardsStore>
</CardsStore> </RecoilRoot>
</StripeElements> </StripeElements>
) )
} }

@ -23,11 +23,16 @@ import {
type CardStepType = { type CardStepType = {
btnName?: string, btnName?: string,
closeHandle?: () => void,
title?: string, title?: string,
} }
export const CardStep = ({ btnName, title }: CardStepType) => { export const CardStep = ({
const { cards } = useCardsStore() btnName,
closeHandle,
title,
}: CardStepType) => {
const { cards, isHighlightsPage } = useCardsStore()
const { close, goBack } = useBuyMatchPopupStore() const { close, goBack } = useBuyMatchPopupStore()
const emptyCards = isEmpty(cards) const emptyCards = isEmpty(cards)
@ -36,13 +41,13 @@ export const CardStep = ({ btnName, title }: CardStepType) => {
<Wrapper width={642}> <Wrapper width={642}>
<Header> <Header>
<ButtonPrevious onClick={goBack}> <ButtonPrevious onClick={goBack}>
<Arrow direction='left' /> {isHighlightsPage ? '' : <Arrow direction='left' />}
</ButtonPrevious> </ButtonPrevious>
<HeaderTitle> <HeaderTitle>
<T9n t={title ?? 'pay'} /> <T9n t={title ?? 'pay'} />
</HeaderTitle> </HeaderTitle>
<HeaderActions position='right'> <HeaderActions position='right'>
<CloseButton onClick={close} /> <CloseButton onClick={isHighlightsPage && closeHandle ? closeHandle : close} />
</HeaderActions> </HeaderActions>
</Header> </Header>
<Body padding='12px 40px 0 40px'> <Body padding='12px 40px 0 40px'>

@ -10,6 +10,7 @@ import { PAGES } from 'config'
import { AudioPlayer } from 'components/AudioPlayer' import { AudioPlayer } from 'components/AudioPlayer'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { Loader } from 'features/Loader'
import { Icon } from 'features/Icon' import { Icon } from 'features/Icon'
import { OutsideClick } from 'features/OutsideClick' import { OutsideClick } from 'features/OutsideClick'
import { import {
@ -29,6 +30,7 @@ import {
ListOption, ListOption,
WrapperIcon, WrapperIcon,
ScAudioWrap, ScAudioWrap,
ScLoaderWrapper,
} from './styled' } from './styled'
import { Arrow } from './components/Arrow' import { Arrow } from './components/Arrow'
@ -43,6 +45,7 @@ export const Combobox = <T extends Option>(props: Props<T>) => {
labelAfter, labelAfter,
labelLexic, labelLexic,
labelWidth, labelWidth,
loading,
maxLength, maxLength,
noSearch, noSearch,
title, title,
@ -98,6 +101,11 @@ export const Combobox = <T extends Option>(props: Props<T>) => {
/> />
{labelAfter && query && <LabelAfter>{labelAfter}</LabelAfter>} {labelAfter && query && <LabelAfter>{labelAfter}</LabelAfter>}
</Label> </Label>
{loading ? (
<ScLoaderWrapper>
<Loader color='#363636' />
</ScLoaderWrapper>
) : ''}
{iconName ? ( {iconName ? (
<WrapperIcon> <WrapperIcon>
<Icon refIcon={iconName} /> <Icon refIcon={iconName} />

@ -1,4 +1,6 @@
import styled from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { customScrollbar, customStylesMixin } from 'features/Common' import { customScrollbar, customStylesMixin } from 'features/Common'
@ -62,3 +64,20 @@ export const WrapperIcon = styled.span`
export const ScAudioWrap = styled.div` export const ScAudioWrap = styled.div`
margin-left: 20px; margin-left: 20px;
` `
export const ScLoaderWrapper = styled.div`
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
${isMobileDevice
? css`
max-width: 95vw;
left: 0;
border-radius: 4px;
@media screen and (orientation: landscape){
max-width: 368px;
}
`
: ''};
`

@ -27,6 +27,7 @@ export type Props<T> = Pick<InputHTMLAttributes<HTMLInputElement>, (
labelAfter?: string | ReactNode, labelAfter?: string | ReactNode,
labelLexic?: string, labelLexic?: string,
labelWidth?: number, labelWidth?: number,
loading?: boolean,
maxLength?: number, maxLength?: number,
noSearch?: boolean, noSearch?: boolean,
onChange?: (event: ChangeEvent<HTMLInputElement>) => void, onChange?: (event: ChangeEvent<HTMLInputElement>) => void,

@ -86,11 +86,7 @@ export const CollapseTournament = ({
<CountMatches> <CountMatches>
{tournamentMatches.length} {tournamentMatches.length}
&nbsp; &nbsp;
<T9n t={ <T9n t='broadcasts' />
tournamentMatches.length > 1
? 'games' : 'game'
}
/>
</CountMatches> </CountMatches>
</Info> </Info>
</CardWrapper> </CardWrapper>

@ -30,12 +30,11 @@ export const ChangeCardPopup = ({
return ( return (
<Modal <Modal
isOpen={changeCardPopupOpen} isOpen={changeCardPopupOpen}
close={() => setChangeCardPopupOpen(false)}
withCloseButton={closeButton}
> >
<CardStep <CardStep
title={title ?? 'change_card'} title={title ?? 'change_card'}
btnName={btnName ?? 'change'} btnName={btnName ?? 'change'}
closeHandle={() => setChangeCardPopupOpen(false)}
/> />
</Modal> </Modal>
) )

@ -24,7 +24,11 @@ import {
import { getPlayerMatches } from 'requests/getMatches/getPlayerMatches' import { getPlayerMatches } from 'requests/getMatches/getPlayerMatches'
import { getTeamPlayers, Player } from 'requests/getTeamPlayers' import { getTeamPlayers, Player } from 'requests/getTeamPlayers'
import { playerMatchesState, dataForPayHighlights } from '../../storeHighlightsAtoms' import {
playerMatchesState,
dataForPayHighlights,
fetchingMatches,
} from '../../storeHighlightsAtoms'
export type SportType = { export type SportType = {
id: number, id: number,
@ -73,6 +77,7 @@ const sounds = [
{ {
id: 0, id: 0,
name: 'No', name: 'No',
src: '',
}, },
{ {
id: 1, id: 1,
@ -171,12 +176,14 @@ export const useHighlightsForm = () => {
const { playerHighlight } = useUserFavoritesStore() const { playerHighlight } = useUserFavoritesStore()
const [sports, setSports] = useState<Array<SportTypeName>>([]) const [sports, setSports] = useState<Array<SportTypeName>>([])
const [isFetchingTeams, setIsFetchingTeams] = useState(false)
const [teams, setTeams] = useState<Array<TeamType>>([]) const [teams, setTeams] = useState<Array<TeamType>>([])
const [playersData, setPlayersData] = useState<Array<PlayerType>>([]) const [playersData, setPlayersData] = useState<Array<PlayerType>>([])
const [players, setPlayers] = useState<Array<PlayerType>>([]) const [players, setPlayers] = useState<Array<PlayerType>>([])
const [formState, setFormState] = useState<FormType>(defaultValues) const [formState, setFormState] = useState<FormType>(defaultValues)
const [playerMatches, setPlayerMatches] = useRecoilState(playerMatchesState) const [playerMatches, setPlayerMatches] = useRecoilState(playerMatchesState)
const setDatahighlights = useSetRecoilState(dataForPayHighlights) const setDatahighlights = useSetRecoilState(dataForPayHighlights)
const setIsFetching = useSetRecoilState(fetchingMatches)
const formRef = useRef<HTMLFormElement>(null) const formRef = useRef<HTMLFormElement>(null)
@ -298,18 +305,25 @@ export const useHighlightsForm = () => {
}, [sports, playerHighlight, teams]) }, [sports, playerHighlight, teams])
const fetchTeams = useMemo( const fetchTeams = useMemo(
() => debounce(() => formState?.sport?.id && getSportTeams( () => debounce(() => {
formState?.sport?.id, setIsFetchingTeams(true)
-1, // eslint-disable-next-line @typescript-eslint/no-unused-expressions
formState.teamValue, formState?.sport?.id
).then( && getSportTeams(
({ data }: SportTeamsType) => setTeams( formState?.sport?.id,
data?.map((team: Team) => ({ -1,
...team, formState.teamValue,
name: team.name_eng, )
})), .then(({ data }: SportTeamsType) => {
), setTeams(
), 1000), data?.map((team: Team) => ({
...team,
name: team.name_eng,
})),
)
})
.finally(() => setIsFetchingTeams(false))
}, 1000),
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[formState.teamValue], [formState.teamValue],
) )
@ -325,7 +339,8 @@ export const useHighlightsForm = () => {
if (formState?.selectedPlayer?.id && formState?.sport) { if (formState?.selectedPlayer?.id && formState?.sport) {
setDatahighlights({ setDatahighlights({
data: { data: {
duration: Number(formState?.duration), background_music: formState?.selectedSound?.src,
duration: Number(formState?.duration) * 60,
lang: 'en', lang: 'en',
matches: playerMatches?.filter(({ isChecked }) => (isChecked)).map(({ id }) => id), matches: playerMatches?.filter(({ isChecked }) => (isChecked)).map(({ id }) => id),
player_id: formState?.selectedPlayer?.id, player_id: formState?.selectedPlayer?.id,
@ -346,16 +361,19 @@ export const useHighlightsForm = () => {
formState?.sport?.id || playerHighlight.sportType, formState?.selectedTeam?.id formState?.sport?.id || playerHighlight.sportType, formState?.selectedTeam?.id
|| playerHighlight?.profile?.additionalInfo?.id, || playerHighlight?.profile?.additionalInfo?.id,
) )
.then((state) => setPlayersData(state?.map((player: Player) => ({ .then((state) => {
...player, setPlayersData(state?.map((player: Player) => ({
name: `${player?.firstname_eng} ${player?.lastname_eng}`, ...player,
})))) name: `${player?.firstname_eng} ${player?.lastname_eng}`,
})))
})
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [formState?.selectedTeam, playerHighlight]) }, [formState?.selectedTeam, playerHighlight])
useEffect(() => { useEffect(() => {
if (!formState?.selectedPlayer || !formState?.sport) return if (!formState?.selectedPlayer || !formState?.sport) return
setIsFetching(true)
formState?.selectedPlayer formState?.selectedPlayer
&& getPlayerMatches({ && getPlayerMatches({
limit: 1000, limit: 1000,
@ -367,6 +385,8 @@ export const useHighlightsForm = () => {
.then(({ broadcast }) => setPlayerMatches( .then(({ broadcast }) => setPlayerMatches(
broadcast.map((match: Match) => ({ ...match, isChecked: false })), broadcast.map((match: Match) => ({ ...match, isChecked: false })),
)) ))
.finally(() => setIsFetching(false))
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [formState?.selectedPlayer, formState?.selectedTeam]) }, [formState?.selectedPlayer, formState?.selectedTeam])
@ -374,6 +394,7 @@ export const useHighlightsForm = () => {
formRef, formRef,
formState, formState,
handleSubmit, handleSubmit,
isFetchingTeams,
onChangeMaxDuration, onChangeMaxDuration,
onChangePlayer, onChangePlayer,
onChangeTeam, onChangeTeam,

@ -37,6 +37,7 @@ export const FormHighlights = ({ price }: PriceInfoType) => {
stats, stats,
teamValue, teamValue,
}, },
isFetchingTeams,
onChangeMaxDuration, onChangeMaxDuration,
onChangePlayer, onChangePlayer,
onChangeTeam, onChangeTeam,
@ -86,7 +87,7 @@ export const FormHighlights = ({ price }: PriceInfoType) => {
wrapperHeight={wrapperHeight} wrapperHeight={wrapperHeight}
/> />
<Combobox <Combobox
disabled={!sport} disabled={!sport || isFetchingTeams}
selected={Boolean(selectedTeam?.name_eng)} selected={Boolean(selectedTeam?.name_eng)}
labelLexic='team_highlight' labelLexic='team_highlight'
labelWidth={labelWidth} labelWidth={labelWidth}
@ -99,6 +100,7 @@ export const FormHighlights = ({ price }: PriceInfoType) => {
wrapperHeight={wrapperHeight} wrapperHeight={wrapperHeight}
iconName='Search' iconName='Search'
className='FormHighlights__select__teams' className='FormHighlights__select__teams'
loading={isFetchingTeams}
/> />
<Combobox <Combobox
disabled={!sport || !selectedTeam} disabled={!sport || !selectedTeam}

@ -1,9 +1,15 @@
import { useRecoilValue } from 'recoil'
import { format } from 'date-fns'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { Checkbox } from 'features/Common/Checkbox' import { Checkbox } from 'features/Common/Checkbox'
import { SportIcon } from 'features/SportIcon' import { SportIcon } from 'features/SportIcon'
import { ArrowLoader } from 'features/ArrowLoader'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
import { format } from 'date-fns'
import { MatchType, fetchingMatches } from '../../storeHighlightsAtoms'
import { useHighlighMatches } from './hooks' import { useHighlighMatches } from './hooks'
@ -22,10 +28,16 @@ import {
ScFakeCheckbox, ScFakeCheckbox,
ScFakeWrapper, ScFakeWrapper,
ScCountMatches, ScCountMatches,
ScLoaderWrapper,
} from './styled' } from './styled'
export const MatchesHighlights = () => { export const MatchesHighlights = () => {
const { onChangeSelectedMatches, playerMatches } = useHighlighMatches() const {
onChangeSelectedMatches,
playerMatches,
} = useHighlighMatches()
const isFetching = useRecoilValue(fetchingMatches)
return ( return (
<ScMatchesWrapper> <ScMatchesWrapper>
@ -45,10 +57,10 @@ export const MatchesHighlights = () => {
team1, team1,
team2, team2,
tournament, tournament,
}: any) => ( }: MatchType) => (
<Checkbox <Checkbox
key={id} key={id}
id={id} id={id.toString()}
checked={isChecked} checked={isChecked}
onChange={onChangeSelectedMatches} onChange={onChangeSelectedMatches}
label={( label={(
@ -76,6 +88,11 @@ export const MatchesHighlights = () => {
</ScFakeTournament> </ScFakeTournament>
</ScFakeWrapper> </ScFakeWrapper>
)))} )))}
{isFetching ? (
<ScLoaderWrapper>
<ArrowLoader />
</ScLoaderWrapper>
) : '' }
</ScMatchesList> </ScMatchesList>
</ScMatchesWrapper> </ScMatchesWrapper>
) )

@ -120,13 +120,18 @@ export const ScFakeWrapper = styled.div`
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
margin-bottom: 14px; margin-bottom: 14px;
position: relative;
` `
export const ScLoaderWrapper = styled.div` export const ScLoaderWrapper = styled.div`
position: absolute; position: absolute;
left: 30%; top: 0;
top: 50%; left: 0;
z-index: 1; width: 75%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
` `
export const ScCountMatches = styled.span` export const ScCountMatches = styled.span`

@ -20,6 +20,7 @@ export const ThanksPopup = () => {
<ScModal <ScModal
isOpen={dataHighlights.isOpenThanksPopup} isOpen={dataHighlights.isOpenThanksPopup}
close={() => setDataHighlights({ ...dataHighlights, isOpenThanksPopup: false })} close={() => setDataHighlights({ ...dataHighlights, isOpenThanksPopup: false })}
withCloseButton={false}
> >
<ScHeader> <ScHeader>
<T9n t='thank_you' /> <T9n t='thank_you' />

@ -44,6 +44,7 @@ const HighlightsPage = () => {
|| !data?.matches.length || !data?.matches.length
|| !isBoolean(data?.stats) || !isBoolean(data?.stats)
|| data?.matches.length > 10 || data?.matches.length > 10
|| data.background_music === undefined
const price = playerMatches?.filter( const price = playerMatches?.filter(
({ isChecked }: MatchType) => isChecked, ({ isChecked }: MatchType) => isChecked,
@ -61,7 +62,10 @@ const HighlightsPage = () => {
<FormHighlights price={price} /> <FormHighlights price={price} />
<MatchesHighlights /> <MatchesHighlights />
</ScWrapperContent> </ScWrapperContent>
<ScButtonWrap disabled={isNotEmpty} onClick={() => setIsOpenPopupChangeCard(true)}> <ScButtonWrap
disabled={isNotEmpty}
onClick={() => !isNotEmpty && setIsOpenPopupChangeCard(true)}
>
<ScButton> <ScButton>
<T9n t='order_and_buy' /> <T9n t='order_and_buy' />
<ScPrice> <ScPrice>
@ -75,7 +79,6 @@ const HighlightsPage = () => {
changeCardPopupOpen={isOpenPopupChangeCard} changeCardPopupOpen={isOpenPopupChangeCard}
setChangeCardPopupOpen={setIsOpenPopupChangeCard} setChangeCardPopupOpen={setIsOpenPopupChangeCard}
title='payment' title='payment'
closeButton={false}
/> />
<ThanksPopup /> <ThanksPopup />
</ScWrapper> </ScWrapper>

@ -2,14 +2,15 @@ import { atom } from 'recoil'
import type { Match } from 'requests' import type { Match } from 'requests'
export type PlayerMatchesType = Array<MatchType>
export type MatchType = Match & { export type MatchType = Match & {
isChecked: boolean, isChecked: boolean,
} }
export type PlayerMatchesType = Array<MatchType>
type DataForm = { type DataForm = {
data: { data: {
background_music: string | undefined,
duration: number, duration: number,
lang: string, lang: string,
matches: Array<number>, matches: Array<number>,
@ -36,3 +37,8 @@ export const dataForPayHighlights = atom({
default: {} as DataForm, default: {} as DataForm,
key: 'dataForPayHighlights', key: 'dataForPayHighlights',
}) })
export const fetchingMatches = atom({
default: false,
key: 'fetchingMatches',
})

Loading…
Cancel
Save