feat(#2341): add filters and tournaments for homepage in desktop

keep-around/26e96e2488f7881e90a1e330fecc8568ebc21871
Andrei Dekterev 4 years ago
parent 7e2d557f04
commit 556811823d
  1. 4
      src/config/lexics/indexLexics.tsx
  2. 25
      src/features/HeaderFilters/store/hooks/index.tsx
  3. 38
      src/features/HomePage/components/HeaderFilters/index.tsx
  4. 35
      src/features/HomePage/components/HeaderFilters/styled.tsx
  5. 2
      src/features/HomePage/index.tsx
  6. 2
      src/features/Matches/components/MatchesList/index.tsx
  7. 2
      src/features/Matches/helpers/prepareMatches.tsx
  8. 20
      src/features/MatchesGrid/index.tsx
  9. 6
      src/features/SportsFilter/components/SelectSport/index.tsx
  10. 11
      src/features/SportsFilter/components/SelectSportPopup/index.tsx
  11. 42
      src/features/SportsFilter/components/SelectSportPopup/styled.tsx
  12. 12
      src/features/SportsFilter/index.tsx
  13. 99
      src/features/TournamentList/components/CollapseTournament/index.tsx
  14. 215
      src/features/TournamentList/components/CollapseTournament/styled.tsx
  15. 2
      src/features/TournamentList/components/TournamentMobile/index.tsx
  16. 0
      src/features/TournamentList/components/TournamentMobile/styled.tsx
  17. 9
      src/features/TournamentList/hooks.tsx
  18. 70
      src/features/TournamentList/index.tsx
  19. 2
      src/requests/getMatches/types.tsx

@ -44,6 +44,7 @@ const buyMatchPopupLexics = {
buy_subscription: 13565,
change_card: 13564,
choose_subscription: 13563,
completed: 14072,
description_all_season_matches: 15069,
description_all_team_matches: 15070,
description_away_team_matches: 15072,
@ -86,6 +87,7 @@ const sportsPopup = {
export const indexLexics = {
add_to_favorites: 14967,
add_to_favorites_error: 12943,
all_competitions: 17926,
all_matches_shown: 13386,
all_sports: 13824,
available_matches_shown: 13385,
@ -100,6 +102,7 @@ export const indexLexics = {
futsal: 17670,
game_finished: 13026,
game_time: 13029,
games: 15773,
gender_female: 9648,
gender_female_long: 13374,
gender_male: 9647,
@ -133,6 +136,7 @@ export const indexLexics = {
team: 14973,
to_home: 13376,
tournament: 14974,
upcoming: 17925,
user_account: 12928,
volleyball: 9761,
watch_from_beginning: 13021,

@ -22,8 +22,15 @@ export const useFilters = () => {
serialize: serializeDate,
validator: isValidDate,
})
const [selectedSport, setSelectedSport] = useState('all_sports')
const [selectedLeague, setSelectedLeague] = useState('all_leagues')
const [allFilters, setAllFilters] = useState({
selectedFilters: [],
selectedLeague: [],
selectedSports: [],
})
const [selectedSport, setSelectedSport] = useState(['all_sports'])
const [selectedLeague, setSelectedLeague] = useState(['all_competitions'])
const [selectedFilters, setSelectedFilters] = useState<Array<string>>([])
const [isShowTournament, setIsShowTournament] = useState(true)
const isTodaySelected = isToday(selectedDate)
@ -42,16 +49,30 @@ export const useFilters = () => {
])
const store = useMemo(() => ({
allFilters,
isShowTournament,
isTodaySelected,
setAllFilters,
selectedDate,
selectedFilters,
selectedLeague,
selectedSport,
setIsShowTournament,
setSelectedDate,
setSelectedFilters,
setSelectedSport,
}), [
allFilters,
isShowTournament,
isTodaySelected,
setAllFilters,
selectedDate,
selectedFilters,
selectedLeague,
selectedSport,
setIsShowTournament,
setSelectedDate,
setSelectedFilters,
setSelectedSport,
])

@ -1,11 +1,23 @@
import { SportsFilter } from 'features/SportsFilter'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { T9n } from 'features/T9n'
import { SelectFilter } from 'components/SelectFilter'
import { ScHeaderFilters } from './styled'
import { ScHeaderFilters, ScFilterItemsWrap, ScFilterItem } from './styled'
export const HeaderFilters = () => {
const { selectedSport, setSelectedSport } = useHeaderFiltersStore()
const {
selectedFilters,
setSelectedFilters,
} = useHeaderFiltersStore()
const isActiveFilter = (filterItem: string) => selectedFilters.indexOf(filterItem) >= 0
const checkFilter = (item: string) => {
isActiveFilter(item)
? setSelectedFilters((prev) => prev.filter((check) => item !== check))
: setSelectedFilters([...selectedFilters, item])
}
return (
<ScHeaderFilters>
@ -13,8 +25,28 @@ export const HeaderFilters = () => {
<SelectFilter
onModalOpen={() => console.log(1)}
open={false}
selectItem='all_sports'
selectItem='all_competitions'
/>
<ScFilterItemsWrap>
<ScFilterItem
className={isActiveFilter('live') ? 'activeLive' : ''}
onClick={() => checkFilter('live')}
>
<T9n t='live' />
</ScFilterItem>
<ScFilterItem
className={isActiveFilter('upcoming') ? 'activeButton' : ''}
onClick={() => checkFilter('upcoming')}
>
<T9n t='upcoming' />
</ScFilterItem>
<ScFilterItem
className={isActiveFilter('completed') ? 'activeButton' : ''}
onClick={() => checkFilter('completed')}
>
<T9n t='completed' />
</ScFilterItem>
</ScFilterItemsWrap>
</ScHeaderFilters>
)
}

@ -3,4 +3,39 @@ import styled from 'styled-components/macro'
export const ScHeaderFilters = styled.div`
display: flex;
flex-direction: row;
margin-bottom: 23px;
.activeLive {
color: #ffffff;
background-color: #cc0000;
}
.activeButton {
color: #000000;
background-color: #ffffff;
}
`
export const ScFilterItemsWrap = styled.div`
display: flex;
flex-direction: row;
width: 100%;
justify-content: flex-end;
`
type Props = {
background?: string,
color?: string,
}
export const ScFilterItem = styled.div<Props>`
text-transform: uppercase;
font-weight: 700;
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
margin: 0 10px;
cursor: pointer;
border-radius: 20px;
text-align: center;
padding: 6px 16px;
`

@ -29,7 +29,7 @@ const Home = () => {
<Main>
<UserFavorites />
<Content>
<HeaderFilters />
{isMobileDevice ? null : <HeaderFilters />}
<Matches fetch={fetchMatches} />
</Content>
</Main>

@ -5,6 +5,7 @@ import isEmpty from 'lodash/isEmpty'
import type { Match } from 'features/Matches/hooks'
import { MatchesSlider } from 'features/MatchesSlider'
import { MatchesGrid } from 'features/MatchesGrid'
import { TournamentList } from 'features/TournamentList'
import { T9n } from 'features/T9n'
import { Title, Section } from '../../styled'
@ -12,6 +13,7 @@ import { Title, Section } from '../../styled'
const matchesComponents = {
grid: MatchesGrid,
slider: MatchesSlider,
tournaments: TournamentList,
}
type Props = {

@ -15,6 +15,7 @@ const prepareMatch = (match: Match) => {
date: matchDate,
has_video,
id,
is_finished,
live,
preview,
previewURL,
@ -33,6 +34,7 @@ const prepareMatch = (match: Match) => {
formattedDate: format(date, 'dd.MM.yy'),
hasVideo: has_video,
id,
is_finished,
live,
preview,
previewURL,

@ -2,11 +2,11 @@ import { memo } from 'react'
import { useRouteMatch } from 'react-router-dom'
import { PAGES } from 'config/pages'
import { isMobileDevice } from 'config/userAgent'
import { MatchCard } from 'features/MatchCard'
import { TournamentList } from 'features/TournamentList'
import type { Match } from 'features/Matches'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { Wrapper } from './styled'
@ -16,14 +16,24 @@ type MatchesGridProps = {
export const MatchesGrid = memo(({ matches }: MatchesGridProps) => {
const isHomePage = useRouteMatch(PAGES.home)?.isExact
const { selectedFilters, isShowTournament } = useHeaderFiltersStore()
const filteredMatches = selectedFilters?.length
? matches.filter((match) => (
match.live && selectedFilters.indexOf('live') >= 0)
|| (selectedFilters.indexOf('upcoming') >= 0 && match.date > new Date())
|| (selectedFilters.indexOf('completed') >= 0 && match.is_finished))
: matches
return (
<Wrapper>
{isHomePage && isMobileDevice
? <TournamentList matches={matches} />
: (matches.map((match) => (
{isHomePage && isShowTournament ? (
<TournamentList matches={filteredMatches} />
) : (
filteredMatches.map((match) => (
<MatchCard key={match.id} match={match} />
)))}
))
)}
</Wrapper>
)
})

@ -5,7 +5,7 @@ import { ScSportsFilter, ScArrow } from './styled'
type SportsFilterProps = {
onModalOpen: () => void,
open: boolean,
sport: string,
sport: Array<string>,
}
export const SelectSport = ({
@ -14,7 +14,9 @@ export const SelectSport = ({
sport,
}: SportsFilterProps) => (
<ScSportsFilter onClick={onModalOpen}>
<T9n t={sport} />
<T9n t={sport[0]} />
&nbsp;
{sport.length > 1 ? `+ ${sport.length - 1}` : ''}
<ScArrow refIcon='Arrow' color='#ffffff' direction={open ? 180 : 0} />
</ScSportsFilter>
)

@ -6,6 +6,7 @@ import {
ScHeaderTitle,
ScHeaderGroup,
ScSport,
ScSportName,
} from './styled'
const sports = [
@ -21,7 +22,7 @@ type Props = {
isOpen: boolean,
onModalClose: () => void,
onSportClick: (sport: string) => void,
selectedSport: string,
selectedSport: Array<string>,
}
export const SelectSportPopup = ({
isOpen,
@ -41,10 +42,12 @@ export const SelectSportPopup = ({
<ScSport
key={sport}
onClick={() => onSportClick(sport)}
className={selectedSport === sport ? 'active' : ''}
active={selectedSport === sport}
className={selectedSport.indexOf(sport) >= 0 ? 'active' : ''}
active={selectedSport.indexOf(sport) >= 0}
>
<T9n t={sport} />
<ScSportName>
<T9n t={sport} />
</ScSportName>
</ScSport>
))}
</ScBody>

@ -151,7 +151,10 @@ export const ScModal = styled(BaseModal)`
background-color: rgba(0, 0, 0, 0.7);
${ModalWindow} {
min-width: 286px;
position: absolute;
top: 200px;
left: 75px;
min-width: 200px;
max-height: 290px;
background-color: #333333;
border-radius: 0px;
@ -162,6 +165,7 @@ export const ScModal = styled(BaseModal)`
${isMobileDevice
? css`
position: static;
min-width: 280px;
max-height: 250px;
height: auto;
@ -175,8 +179,10 @@ export const ScHeaderTitle = styled.span`
font-size: 10px;
line-height: 24px;
color: #FFFFFF;
padding-left: 30px;
${isMobileDevice
? css`
padding: 0;
font-size: 10px;
line-height: 20px;
@ -190,12 +196,13 @@ export const ScHeaderTitle = styled.span`
export const ScHeaderGroup = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
padding: 10px;
padding: 10px 0;
border-bottom: 1px solid #505050;
${isMobileDevice
? css`
padding: 10px;
justify-content: center;
font-size: 10px;
line-height: 20px;
@ -208,7 +215,13 @@ export const ScHeaderGroup = styled.div`
export const ScBody = styled.div`
display: flex;
flex-direction: column;
padding: 0 15px 23px 15px;
padding-bottom: 10px;
${isMobileDevice
? css`
padding: 0 15px 23px 15px;
`
: ''};
`
type SportProps = {
@ -216,11 +229,28 @@ type SportProps = {
}
export const ScSport = styled.div<SportProps>`
display: flex;
justify-content: center;
text-transform: uppercase;
font-size: 10px;
font-weight: 700;
height: 30px;
width: 100%;
opacity: ${({ active }) => (active ? 1 : 0.5)};
margin-top: 18px;
/* margin-left: 30px; */
align-items: center;
cursor: pointer;
${isMobileDevice
? css`
justify-content: center;
`
: ''};
:hover {
background-color: rgba(255, 255, 255, 0.2);
color: #FFFFFF;
}
`
export const ScSportName = styled.span`
padding-left: 30px;
`

@ -6,11 +6,19 @@ import { SelectSport } from './components/SelectSport'
import { SelectSportPopup } from './components/SelectSportPopup'
export const SportsFilter = () => {
const { selectedSport, setSelectedSport } = useHeaderFiltersStore()
const { selectedSport, setSelectedSport, setIsShowTournament } = useHeaderFiltersStore()
const [isOpen, setIsOpen] = useState(false)
const onSportClick = (sport: string) => {
setSelectedSport(sport)
if (sport === 'all_sports') {
setSelectedSport([sport])
} else {
setSelectedSport((prev) => [
...prev.filter((item) => item !== 'all_sports'),
sport,
])
}
setIsShowTournament(true)
setIsOpen(false)
}

@ -0,0 +1,99 @@
import { ProfileTypes } from 'config'
import { getSportLexic } from 'helpers'
import { T9n } from 'features/T9n'
import { useUserFavoritesStore } from 'features/UserFavorites/store'
import { Icon } from 'features/Icon'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { SportIcon } from 'components/SportIcon/SportIcon'
import { TournamentProps } from '../TournamentMobile/index'
import {
CardWrapperOuter,
CardWrapper,
CountMatches,
FavoriteSign,
FirstInfo,
Info,
LiveSign,
MatchTimeInfo,
TournamentLogo,
PreviewWrapper,
TournamentName,
CountryFlag,
SecondaryInfo,
HoverFrame,
} from './styled'
export const CollapseTournament = ({
tournament,
tournamentMatches,
}: TournamentProps) => {
const { setSelectedSport, setIsShowTournament } = useHeaderFiltersStore()
const { isInFavorites } = useUserFavoritesStore()
const {
countryId,
live,
sportType,
} = tournament
const tournamentInFavorites = isInFavorites(
ProfileTypes.TOURNAMENTS,
tournament.id,
)
const handleClick = () => {
const sportName = getSportLexic(sportType)
setIsShowTournament(false)
setSelectedSport([sportName])
}
return (
<CardWrapperOuter onClick={() => handleClick()}>
<CardWrapper>
<HoverFrame />
<PreviewWrapper>
<TournamentLogo
id={tournament.id}
nameAsTitle
altNameObj={tournament}
sportType={sportType}
profileType={ProfileTypes.TOURNAMENTS}
/>
<MatchTimeInfo>
{live && (
<LiveSign>
<T9n t='live' />
</LiveSign>
)}
</MatchTimeInfo>
</PreviewWrapper>
<Info>
<FirstInfo>
<SportIcon fill='#ffffff' sport={sportType} />
<CountryFlag
src={`https://instatscout.com/images/flags/48/${countryId}.png`}
/>
</FirstInfo>
<SecondaryInfo>
<TournamentName nameObj={tournament} />
{tournamentInFavorites && (
<FavoriteSign marginLeft={12} color='rgba(255, 255, 255, 0.5)'>
<Icon refIcon='Star' />
</FavoriteSign>
)}
</SecondaryInfo>
<CountMatches>
{tournamentMatches.length}
&nbsp;
<T9n t='games' />
</CountMatches>
</Info>
</CardWrapper>
</CardWrapperOuter>
)
}

@ -0,0 +1,215 @@
import styled, { css } from 'styled-components/macro'
import { devices } from 'config/devices'
import { isMobileDevice } from 'config/userAgent'
import { Name } from 'features/Name'
import { ProfileLogo } from 'features/ProfileLogo'
export const CardWrapperOuter = styled.li.attrs({
tabIndex: 0,
})`
padding-top: 100%;
position: relative;
${isMobileDevice
? css`
width: 100%;
padding-top: 0;
height: 90px;
margin-bottom: 10px;
@media screen and (orientation: landscape){
width: 49%;
:nth-child(odd){
margin-right: 10px;
}
}
`
: ''};
`
export const CardWrapper = styled.div`
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding-bottom: 0.75rem;
border-radius: 2px;
background: linear-gradient(236.13deg, rgba(53, 96, 225, 0.56) -4.49%, rgba(0, 0, 0, 0) 98.29%), #3F3F3F;
cursor: pointer;
${isMobileDevice
? css`
width: 100%;
height: 90px;
padding-bottom: 0;
`
: ''};
`
export const HoverFrame = styled.div`
position: absolute;
min-width: 100%;
min-height: 100%;
border-radius: 2px;
border: 2px solid transparent;
transition: border 0.5s ease-out;
z-index: 2;
&:hover {
border: 2px solid #fff
}
`
export const PreviewWrapper = styled.div`
position: relative;
display: flex;
justify-content: center;
width: 100%;
height: 60%;
${isMobileDevice
? css`
width: 50%;
height: 100%;
`
: ''};
`
export const MatchTimeInfo = styled.div`
width: 100%;
position: absolute;
top: 0.519rem;
padding: 0 0.519rem;
display: flex;
flex-direction: row;
`
type MatchDateProps = {
isHomePage?: boolean,
}
export const MatchDate = styled.div<MatchDateProps>`
width: fit-content;
height: 0.9rem;
border-radius: 2px;
padding: ${({ isHomePage }) => (!isHomePage ? '0 0.27rem' : '')};
font-weight: bold;
font-size: 0.472rem;
line-height: 0.567rem;
letter-spacing: 0.05em;
text-transform: uppercase;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
color: white;
background-color: #6D6D6D;
@media ${devices.tablet} {
padding: ${({ isHomePage }) => (!isHomePage ? '0.27rem 0.36rem' : '')};
}
${isMobileDevice
? css`
height: 15px;
font-size: 8px;
`
: ''};
`
export const LiveSign = styled(MatchDate)`
background-color: #CC0000;
margin-left: auto;
`
export const Time = styled.span`
margin: 0 0.2rem;
`
export const Info = styled.div`
display: flex;
flex-direction: column;
padding: 0.85rem 0.472rem 0 0.519rem;
color: #fff;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
${isMobileDevice
? css`
position: absolute;
top: 0;
left: 50%;
width: 50%;
height: 100%;
padding: 13px 12px 13px 10px;
`
: ''};
`
export const FirstInfo = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin: 5px 0;
`
export const SecondaryInfo = styled.div`
display: flex;
justify-content: center;
align-items: center;
`
export const CountryFlag = styled.img`
width: 0.71rem;
height: 0.75rem;
margin-left: 0.567rem;
object-fit: contain;
object-position: bottom;
${isMobileDevice
? css`
width: 12px;
height: 8px;
margin-left: 3.5px;
`
: ''};
`
export const CountMatches = styled.span`
display: block;
text-align: center;
font-weight: 400;
font-size: 10px;
line-height: 16px;
color: #FFFFFF;
`
export const TournamentName = styled(Name)`
font-weight: 600;
font-size: 13px;
line-height: 24px;
color: #ffffff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`
export const TournamentLogo = styled(ProfileLogo)`
padding: 10px;
max-height: 100%;
`
type FavoriteSignProps = {
color?: string,
marginLeft?: number,
}
export const FavoriteSign = styled.span<FavoriteSignProps>`
margin-left: ${({ marginLeft = 9 }) => marginLeft}px;
color: ${({ color }) => color};
display: flex;
transform: translateY(25%);
justify-content: center;
align-items: center;
width: 8px;
height: 8px;
margin-bottom: 7px;
`

@ -35,7 +35,7 @@ export type TournamentProps = {
tournamentMatches: Array<Match>,
}
export const Tournament = ({
export const TournamentMobile = ({
tournament,
tournamentMatches,
}: TournamentProps) => {

@ -7,15 +7,12 @@ import { useHeaderFiltersStore } from 'features/HeaderFilters'
export const useTournaments = (matches: Array<Match>) => {
const { selectedSport } = useHeaderFiltersStore()
const compareSport = (match: Match, sportName: string) => {
if (sportName === 'all_sports') {
const compareSport = (match: Match, sportNames: Array<string>) => {
if (sportNames[0] === 'all_sports') {
return true
}
const sport = getSportLexic(match.sportType)
if (sportName.indexOf('_popup') !== -1) {
return sport === sportName.replace('_popup', '')
}
return sport === sportName
return (sportNames.indexOf(sport) >= 0 || sportNames.indexOf(`${sport}_popup`) >= 0)
}
const tournamentSort: Array<number> = []

@ -1,36 +1,68 @@
import { useRouteMatch } from 'react-router-dom'
import type { Match } from 'features/Matches'
import { MatchCard } from 'features/MatchCard'
import type { TournamentType } from 'requests/getMatches/types'
import { Tournament } from './components/Tournament'
import { PAGES } from 'config/pages'
import { isMobileDevice } from 'config/userAgent'
import { TournamentMobile } from './components/TournamentMobile'
import { useTournaments } from './hooks'
import { CollapseTournament } from './components/CollapseTournament'
type TournamentTypeProps = {
matches: Array<Match>,
}
export type TournamentListProps = {
[key: number]:{tournament: TournamentType & {
countryId: number,
live: boolean,
sportType: number,
},
tournamentMatches: Array<Match>,
},
[key: number]: {
tournament: TournamentType & {
countryId: number,
live: boolean,
sportType: number,
}
tournamentMatches: Array<Match>,
}
}
export const TournamentList = ({ matches }: TournamentTypeProps) => {
const { tournaments, tournamentSort } = useTournaments(matches)
const isHomePage = useRouteMatch(PAGES.home)?.isExact
return (
<>
{tournamentSort?.map((id) => (
<Tournament
key={id}
tournament={tournaments[id].tournament}
tournamentMatches={tournaments[id].tournamentMatches}
/>
))}
</>
)
switch (true) {
case isMobileDevice && isHomePage:
return (
<>
{tournamentSort?.map((id) => (
<TournamentMobile
key={id}
tournament={tournaments[id].tournament}
tournamentMatches={tournaments[id].tournamentMatches}
/>
))}
</>
)
case isHomePage && matches.length <= 12:
return (
<>
{tournamentSort?.map((id) => (
<CollapseTournament
key={id}
tournament={tournaments[id].tournament}
tournamentMatches={tournaments[id].tournamentMatches}
/>
))}
</>
)
default:
return (
<>
{matches.map((match) => (
<MatchCard key={match.id} match={match} />
))}
</>
)
}
}

@ -21,6 +21,8 @@ export type Match = {
/** наличие mp4 видео */
has_video: boolean,
id: number,
/** матч закончен */
is_finished?: boolean,
/** наличие hls стрима */
live: boolean,
preview?: string,

Loading…
Cancel
Save