fix(#720): timeline mode

pull/294/head
Rakov 2 years ago
parent d06425df94
commit 71eff354a9
  1. 1
      src/config/lexics/indexLexics.tsx
  2. 2
      src/features/BuyMatchPopup/types.tsx
  3. 1
      src/features/Common/Image/index.tsx
  4. 14
      src/features/HeaderFilters/components/DateFilter/helpers.tsx
  5. 10
      src/features/HeaderFilters/components/DateFilter/hooks/index.tsx
  6. 199
      src/features/HeaderFilters/components/DateFilter/index.tsx
  7. 117
      src/features/HeaderFilters/components/DateFilter/styled.tsx
  8. 193
      src/features/HeaderFilters/components/FacrDateFilter/index.tsx
  9. 1
      src/features/HeaderFilters/index.tsx
  10. 1
      src/features/HeaderFilters/store/config.tsx
  11. 16
      src/features/HeaderFilters/store/hooks/index.tsx
  12. 5
      src/features/HeaderMobile/index.tsx
  13. 1
      src/features/HeaderMobile/styled.tsx
  14. 5
      src/features/HomePage/components/Header/index.tsx
  15. 3
      src/features/HomePage/components/HeaderFilters/index.tsx
  16. 3
      src/features/HomePage/components/HeaderFilters/styled.tsx
  17. 8
      src/features/HomePage/index.tsx
  18. 3
      src/features/Icon/index.tsx
  19. 2
      src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx
  20. 8
      src/features/MatchCard/CardFrontside/index.tsx
  21. 4
      src/features/MatchCard/config.tsx
  22. 2
      src/features/MatchCard/hooks.tsx
  23. 2
      src/features/MatchCard/index.tsx
  24. 4
      src/features/MatchPage/store/hooks/useTournamentData.tsx
  25. 3
      src/features/MatchPage/types.tsx
  26. 2
      src/features/MatchPopup/types.tsx
  27. 2
      src/features/Matches/components/MatchesList/index.tsx
  28. 4
      src/features/Matches/helpers/addSportType.tsx
  29. 4
      src/features/Matches/helpers/getMatchClickAction/__tests__/index.tsx
  30. 4
      src/features/Matches/helpers/getMatchClickAction/index.tsx
  31. 4
      src/features/Matches/hooks.tsx
  32. 2
      src/features/Matches/index.tsx
  33. 7
      src/features/MatchesGrid/index.tsx
  34. 50
      src/features/MatchesSlider/hooks.tsx
  35. 100
      src/features/MatchesSlider/index.tsx
  36. 108
      src/features/MatchesSlider/styled.tsx
  37. 111
      src/features/MatchesTimeline/hooks.tsx
  38. 115
      src/features/MatchesTimeline/index.tsx
  39. 90
      src/features/MatchesTimeline/styled.tsx
  40. 5
      src/features/PageLayout/styled.tsx
  41. 1
      src/features/SportsFilter/components/SelectSport/styled.tsx
  42. 3
      src/features/TournamentList/components/TournamentMobile/index.tsx
  43. 7
      src/features/TournamentList/hooks.tsx
  44. 3
      src/features/TournamentList/index.tsx
  45. 3
      src/features/UserFavorites/styled.tsx
  46. 1
      src/helpers/index.tsx
  47. 29
      src/helpers/prepareMatches/index.tsx
  48. 4
      src/pages/HighlightsPage/components/FormHighlights/hooks.tsx
  49. 4
      src/pages/HighlightsPage/storeHighlightsAtoms.tsx
  50. 35
      src/requests/getMatches/getTimelineMatches.tsx
  51. 1
      src/requests/getMatches/index.tsx
  52. 16
      src/requests/getMatches/types.tsx

@ -224,6 +224,7 @@ export const indexLexics = {
sport: 12993,
team: 14973,
terms_and_conditions: 15738,
timeline_title: 20266,
to_home: 13376,
total_likes: 20253,
tournament: 14974,

@ -1,7 +1,7 @@
import type { SubscriptionResponse, Subscription } from 'requests/getSubscriptions'
import type { LexicsId, Values } from 'features/LexicsStore/types'
import type { Match as MatchBase } from 'features/Matches/hooks'
import type { Match as MatchBase } from 'helpers/prepareMatches'
import { MatchAccess } from 'features/Matches/helpers/getMatchClickAction'
export enum Steps {

@ -37,6 +37,7 @@ export const Image = ({
onError={onError}
onLoad={onLoad}
title={title}
loading='lazy'
/>
)
}

@ -3,6 +3,8 @@ import addDays from 'date-fns/addDays'
import addMonths from 'date-fns/addMonths'
import startOfYear from 'date-fns/startOfYear'
import { isMobileDevice } from 'config'
type Args = {
date: Date,
lang: string,
@ -20,7 +22,7 @@ const getMonthName = ({
export const getDisplayDate = ({
date,
lang,
monthType = 'long',
monthType = isMobileDevice ? 'long' : 'short',
}: Args) => ({
day: date.getDate(),
month: getMonthName({
@ -48,6 +50,8 @@ type Week = {
name: string,
}
type Month = Week
export const getWeeks = (date: Date, locale: string) => {
const weekStart = startOfWeek(date, { weekStartsOn: 1 })
const getWeekDay = createDayGetter(weekStart, locale)
@ -64,17 +68,17 @@ export const getWeeks = (date: Date, locale: string) => {
}
const createMonthGetter = (yearStart: Date, locale: string) => (month: number) => {
const dayDate = addMonths(yearStart, month)
const monthDate = addMonths(yearStart, month)
return {
date: dayDate,
name: new Intl.DateTimeFormat(locale || 'en', { month: 'short' }).format(dayDate),
date: monthDate,
name: new Intl.DateTimeFormat(locale || 'en', { month: 'short' }).format(monthDate),
}
}
export const getMonths = (locale: string, date: Date) => {
const yearStart = startOfYear(date)
const getMonth = createMonthGetter(yearStart, locale)
const months: Array<Week> = [
const months: Array<Month> = [
getMonth(0),
getMonth(1),
getMonth(2),

@ -29,6 +29,8 @@ import {
export const useDateFilter = () => {
const {
isMonthMode,
isTimelineMode,
isWeekMode,
selectedDate,
selectedMode,
selectedMonthModeDate,
@ -62,8 +64,11 @@ export const useDateFilter = () => {
lang,
})
const filters = localStorage.getItem('filters')
const dateMode = localStorage.getItem('dateMode')
const parseFilters = filters && JSON.parse(filters)
const parseMode = dateMode && JSON.parse(dateMode)
const lastDate = parseFilters?.selectedDate
const lastMonthDate = new Date(parseMode?.selectedMonthModeDate)
const weekName = getWeekName(selectedDate, 'en')
const validator = (value: unknown) => Boolean(value) && isObject(value)
@ -83,6 +88,7 @@ export const useDateFilter = () => {
useEffect(() => {
if (lastDate === selectedDate.getDate()
&& lastMonthDate === selectedMonthModeDate
&& parseFilters
&& parseFilters.selectedLeague[0] !== 'all_competitions') {
setIsShowTournament(false)
@ -94,7 +100,7 @@ export const useDateFilter = () => {
setSelectedLeague(['all_competitions'])
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedDate])
}, [selectedDate, selectedMonthModeDate])
const onPreviousClick = () => {
addAdsViews()
@ -130,6 +136,8 @@ export const useDateFilter = () => {
date,
isMonthMode,
isOpen,
isTimelineMode,
isWeekMode,
months,
onDateChange,
onNextClick,

@ -2,40 +2,62 @@ import { Fragment } from 'react'
import map from 'lodash/map'
import { T9n } from 'features/T9n'
import { Icon } from 'features/Icon'
import { OutsideClick } from 'features/OutsideClick'
import { BodyBackdrop } from 'features/PageLayout'
import { Icon } from 'features/Icon'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { isInSportsClient, isMobileDevice } from 'config'
import { useDateFilter } from './hooks'
import { DatePicker } from '../DatePicker'
import { Tabs } from '../../store/config'
import {
Wrapper,
MonthWrapper,
WeekDaysWrapper,
TabsList,
Tab,
TabTitle,
YearWrapper,
ArrowButton,
Arrow,
DateButton,
MonthYear,
Week,
MonthModeYear,
MonthModeWrapper,
Month,
MonthName,
CalendarWrapper,
DateWrapper,
MonthArrow,
WeekDaysWrapper,
WeekDay,
WeekName,
WeekNumber,
WeekModeButton,
WeekModeYear,
Week,
MonthButtonWrapper,
} from './styled'
import { useHeaderFiltersStore } from '../../store'
export const DateFilter = () => {
const {
addAdsViews,
close,
date,
isMonthMode,
isOpen,
isWeekMode,
months,
onDateChange,
onNextClick,
onNextYearClick,
onPreviousClick,
onPrevYearClick,
onWeekDayClick,
openDatePicker,
selectedDate,
selectedMode,
selectedMonthModeDate,
setSelectedMode,
setSelectedMonthModeDate,
week,
} = useDateFilter()
@ -44,55 +66,134 @@ export const DateFilter = () => {
} = useHeaderFiltersStore()
return (
<Wrapper>
<MonthWrapper>
<MonthYear onClick={openDatePicker}>
{date.month} {' '} {date.year}
</MonthYear>
<DateButton
isActive={isOpen}
onClick={openDatePicker}
id='main_calendar'
>
<Icon refIcon='Calendar' color='#fff' />
</DateButton>
</MonthWrapper>
<CalendarWrapper isMonthMode={isMonthMode}>
<DateWrapper isMonthMode={isMonthMode}>
{isWeekMode && (
<MonthButtonWrapper>
<WeekModeYear onClick={openDatePicker}>
{date.month} {' '} {date.year}
</WeekModeYear>
<WeekModeButton isActive={isOpen} onClick={openDatePicker}>
<Icon refIcon='Calendar' color='#fff' />
</WeekModeButton>
</MonthButtonWrapper>
)}
{isMonthMode && (
<YearWrapper>
<MonthArrow
aria-label='Previous year'
onClick={onPrevYearClick}
>
<Arrow direction='left' />
</MonthArrow>
<MonthModeYear>
{selectedMonthModeDate.getFullYear()}
</MonthModeYear>
<MonthArrow
aria-label='Next year'
onClick={onNextYearClick}
>
<Arrow direction='right' />
</MonthArrow>
</YearWrapper>
)}
<WeekDaysWrapper>
<ArrowButton
aria-label='Previous week'
onClick={onPreviousClick}
>
<Arrow direction='left' />
</ArrowButton>
<Week>
<TabsList>
{!isInSportsClient && (
<Tab
aria-pressed={selectedMode === Tabs.MONTH}
onClick={() => setSelectedMode(Tabs.MONTH)}
>
<TabTitle>
<T9n t='month_title' />
</TabTitle>
</Tab>
)}
{!isMobileDevice && (
<Tab
aria-pressed={selectedMode === Tabs.TIMELINE}
onClick={() => setSelectedMode(Tabs.TIMELINE)}
>
<TabTitle>
<T9n t='timeline_title' />
</TabTitle>
</Tab>
)}
{(!isMobileDevice || !isInSportsClient) && (
<Tab
aria-pressed={selectedMode === Tabs.WEEK}
onClick={() => setSelectedMode(Tabs.WEEK)}
>
<TabTitle>
<T9n t='week_title' />
</TabTitle>
</Tab>
)}
</TabsList>
</DateWrapper>
{isMonthMode && (
<MonthModeWrapper>
{
map(week, (day) => (
<WeekDay
key={day.name}
selected={day.date.getDate() === selectedDate.getDate()}
map(months, (month) => (
<Month
key={month.name}
selected={month.date.getMonth() === selectedMonthModeDate.getMonth()}
onClick={() => {
if (day.date.getDate() !== selectedDate.getDate()) {
addAdsViews()
onWeekDayClick(day.date)
if (month.date.getMonth() !== selectedMonthModeDate.getMonth()) {
setSelectedMonthModeDate(month.date)
} else {
resetFilters()
}
}}
>
<WeekName>{day.name.slice(0, 3)}</WeekName>
<WeekNumber>{day.date.getDate()}</WeekNumber>
</WeekDay>
<MonthName>{month.name}</MonthName>
</Month>
))
}
</Week>
<ArrowButton
aria-label='Next week'
onClick={onNextClick}
>
<Arrow direction='right' />
</ArrowButton>
</WeekDaysWrapper>
</MonthModeWrapper>
)}
{isWeekMode && (
<WeekDaysWrapper>
<ArrowButton
aria-label='Previous week'
onClick={onPreviousClick}
>
<Arrow direction='left' />
</ArrowButton>
<Week>
{
map(week, (day) => (
<WeekDay
key={day.name}
selected={day.date.getDate() === selectedDate.getDate()}
onClick={() => {
if (day.date.getDate() !== selectedDate.getDate()) {
addAdsViews()
onWeekDayClick(day.date)
} else {
resetFilters()
}
}}
>
<WeekName>{day.name.slice(0, 3)}</WeekName>
<WeekNumber>{day.date.getDate()}</WeekNumber>
</WeekDay>
))
}
</Week>
<ArrowButton
aria-label='Next week'
onClick={onNextClick}
>
<Arrow direction='right' />
</ArrowButton>
</WeekDaysWrapper>
)}
{
isOpen && (
<Fragment>
@ -107,6 +208,6 @@ export const DateFilter = () => {
</Fragment>
)
}
</Wrapper>
</CalendarWrapper>
)
}

@ -2,6 +2,7 @@ import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { devices } from 'config/devices'
import { isInSportsClient } from 'config'
type Props = {
isMonthMode: boolean,
@ -17,7 +18,6 @@ export const BaseButton = styled.button`
export const Wrapper = styled.div`
position: relative;
/* width: 32.8rem; */
display: flex;
flex-direction: column;
justify-content: center;
@ -29,8 +29,6 @@ export const Wrapper = styled.div`
${isMobileDevice
? css`
/* padding-top: 4px; */
/* min-height: 84px; */
justify-content: space-between;
@media (max-width: 450px){
@ -108,7 +106,6 @@ export const WeekDaysWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-top: 0.567rem;
${isMobileDevice
? css`
@ -119,7 +116,6 @@ export const WeekDaysWrapper = styled.div`
`
export const Week = styled.div`
margin: 0 0.95rem;
display: flex;
@media (max-width: 600px) {
@ -225,11 +221,8 @@ export const Arrow = styled.span<ArrowProps>`
: ''};
`
export const MonthModeWrapper = styled(WeekDaysWrapper)<Props>`
export const MonthModeWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-top: ${({ isMonthMode }) => (isMonthMode ? '0' : '0.2rem')};;
::-webkit-scrollbar {
display: none;
@ -241,47 +234,64 @@ export const MonthModeWrapper = styled(WeekDaysWrapper)<Props>`
margin-top: 0;
overflow-y: auto;
height: 100%;
& > :not(:last-child) {
margin-right: 25px;
}
`
: ''};
`
export const FacrWrapper = styled(Wrapper)<Props>`
justify-content: space-between;
height: ${(isMobileDevice ? '100%' : 'fit-content')};
width: ${({ isMonthMode }) => (isMonthMode ? '49.5%' : '26.3rem')};
export const CalendarWrapper = styled(Wrapper)<Props>`
display: flex;
flex-direction: column;
height: fit-content;
padding-top: 2rem;
position: relative;
@media (max-width: 450px){
width: 100%;
justify-content: flex-start;
padding-top: 0;
};
`
export const FacrDateButton = styled(DateButton)`
left: 24rem;
top: 0;
`
export const FacrMonthWrapper = styled(MonthWrapper)`
position: static;
width: auto;
align-self: auto;
export const WeekModeButton = styled(DateButton)`
right: 4.5rem;
top: -5px;
${isMobileDevice
? css`
position: absolute;
left: 50%;
transform: translateX(-50%);
position: static;
`
: ''};
`
export const MonthButtonWrapper = styled.div`
${() => {
if (isMobileDevice) {
if (isInSportsClient) {
return css`
position: static;
display: flex;
align-items: baseline;
justify-content: center;
`
}
return css`
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
`
}
return ''
}}
`
export const DateWrapper = styled.div<Props>`
position: relative;
display: flex;
align-items: flex-end;
justify-content: space-between;
width: ${({ isMonthMode }) => (isMonthMode ? '25rem' : '16.9rem')};
margin: ${({ isMonthMode }) => (isMonthMode ? '3.7rem 0 0.7rem' : '3rem 0 0')};
margin-bottom: 0.7rem;
${isMobileDevice
? css`
@ -300,24 +310,18 @@ export const MonthArrow = styled(ArrowButton)`
height: auto;
`
export const Months = styled(Week)`
height: 100%;
width: 100%;
margin: 0;
justify-content: space-between;
`
export const Months = styled(Week)``
export const FacrWeek = styled(Week)`
margin: 0 1.8rem;
`
export const Month = styled(WeekDay)`
width: auto;
width: 3.5rem;
${isMobileDevice
? css`
margin-top: 10px;
margin-right: 25px;
min-width: fit-content;
`
: ''};
@ -335,13 +339,36 @@ export const MonthModeYear = styled(MonthYear)`
: ''};
`
export const WeekModeYear = styled(MonthYear)`
line-height: normal;
font-size: 1rem;
position: absolute;
left: 2.5rem;
cursor: pointer;
display: flex;
align-items: center;
height: 100%;
${isMobileDevice
? css`
position: static;
font-size: 10px;
`
: ''};
`
export const TabsList = styled.div`
display: flex;
justify-content: space-between;
height: fit-content;
justify-content: center;
margin: 0 auto 0;
& > :not(:last-child) {
margin-right: 2rem;
}
${isMobileDevice
? css`
justify-content: flex-start;
top: 3px;
padding: 0;
`
@ -349,6 +376,9 @@ export const TabsList = styled.div`
`
export const YearWrapper = styled.div`
position: absolute;
right: 0;
top: -5px;
display: flex;
align-items: center;
justify-content: center;
@ -360,6 +390,7 @@ export const YearWrapper = styled.div`
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -1px;
`
: ''};
`
@ -387,17 +418,13 @@ export const Tab = styled.button`
display: flex;
justify-content: space-between;
align-items: center;
font-size: .75rem;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
border: none;
background: none;
padding: 0;
:first-child {
margin-right: 2rem;
}
${isMobileDevice
? css`
font-size: 10px;

@ -1,193 +0,0 @@
import map from 'lodash/map'
import { T9n } from 'features/T9n'
import { Icon } from 'features/Icon'
import { OutsideClick } from 'features/OutsideClick'
import { BodyBackdrop } from 'features/PageLayout'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { Fragment } from 'react'
import { useDateFilter } from '../DateFilter/hooks'
import { DatePicker } from '../DatePicker'
import { Tabs } from '../../store/config'
import {
TabsList,
Tab,
TabTitle,
MonthsMode,
YearWrapper,
ArrowButton,
Arrow,
MonthModeYear,
MonthModeWrapper,
Months,
Month,
MonthName,
FacrWrapper,
DateWrapper,
MonthArrow,
WeekDaysWrapper,
FacrMonthWrapper,
WeekDay,
WeekName,
WeekNumber,
FacrDateButton,
FacrWeek,
} from '../DateFilter/styled'
export const FacrDateFilter = () => {
const {
addAdsViews,
close,
date,
isMonthMode,
isOpen,
months,
onDateChange,
onNextClick,
onNextYearClick,
onPreviousClick,
onPrevYearClick,
onWeekDayClick,
openDatePicker,
selectedDate,
selectedMode,
selectedMonthModeDate,
setSelectedMode,
setSelectedMonthModeDate,
week,
} = useDateFilter()
const {
resetFilters,
} = useHeaderFiltersStore()
return (
<FacrWrapper isMonthMode={isMonthMode}>
<DateWrapper isMonthMode={isMonthMode}>
<TabsList>
<Tab
aria-pressed={selectedMode === Tabs.MONTH}
onClick={() => setSelectedMode(Tabs.MONTH)}
>
<TabTitle>
<T9n t='month_title' />
</TabTitle>
</Tab>
<Tab
aria-pressed={selectedMode === Tabs.WEEK}
onClick={() => setSelectedMode(Tabs.WEEK)}
>
<TabTitle>
<T9n t='week_title' />
</TabTitle>
</Tab>
</TabsList>
{isMonthMode
? (
<YearWrapper>
<MonthArrow
aria-label='Previous year'
onClick={onPrevYearClick}
>
<Arrow direction='left' />
</MonthArrow>
<MonthModeYear>
{selectedMonthModeDate.getFullYear()}
</MonthModeYear>
<MonthArrow
aria-label='Next year'
onClick={onNextYearClick}
>
<Arrow direction='right' />
</MonthArrow>
</YearWrapper>
)
: (
<FacrMonthWrapper>
<MonthModeYear onClick={openDatePicker}>
{date.month} {' '} {date.year}
</MonthModeYear>
<FacrDateButton isActive={isOpen} onClick={openDatePicker}>
<Icon refIcon='Calendar' color='#fff' />
</FacrDateButton>
</FacrMonthWrapper>
)}
</DateWrapper>
{isMonthMode
? (
<MonthsMode>
<MonthModeWrapper isMonthMode={isMonthMode}>
<Months>
{
map(months, (day) => (
<Month
key={day.name}
selected={day.date.getMonth() === selectedMonthModeDate.getMonth()}
onClick={() => setSelectedMonthModeDate(day.date)}
>
<MonthName>{day.name}</MonthName>
</Month>
))
}
</Months>
</MonthModeWrapper>
</MonthsMode>
)
: (
<WeekDaysWrapper>
<ArrowButton
aria-label='Previous week'
onClick={onPreviousClick}
>
<Arrow direction='left' />
</ArrowButton>
<FacrWeek>
{
map(week, (day) => (
<WeekDay
key={day.name}
selected={day.date.getDate() === selectedDate.getDate()}
onClick={() => {
if (day.date.getDate() !== selectedDate.getDate()) {
addAdsViews()
onWeekDayClick(day.date)
} else {
resetFilters()
}
}}
>
<WeekName>{day.name.slice(0, 3)}</WeekName>
<WeekNumber>{day.date.getDate()}</WeekNumber>
</WeekDay>
))
}
</FacrWeek>
<ArrowButton
aria-label='Next week'
onClick={onNextClick}
>
<Arrow direction='right' />
</ArrowButton>
</WeekDaysWrapper>
)}
{
isOpen && (
<Fragment>
<OutsideClick onClick={close}>
<DatePicker
open
selected={selectedDate}
onChange={onDateChange}
/>
</OutsideClick>
<BodyBackdrop />
</Fragment>
)
}
</FacrWrapper>
)
}

@ -1,3 +1,2 @@
export * from './components/DateFilter'
export * from './components/FacrDateFilter'
export * from './store'

@ -8,4 +8,5 @@ export const filterKeys = {
export enum Tabs {
WEEK,
MONTH,
TIMELINE
}

@ -14,12 +14,12 @@ import { useQueryParamStore } from 'hooks'
import { getSportLexic } from 'helpers'
import { querieKeys } from 'config'
import { isMobileDevice, querieKeys } from 'config'
import { getLocalStorageItem } from 'helpers/getLocalStorage'
import type { Match } from 'helpers'
import { filterKeys, Tabs } from '../config'
import { isValidDate } from '../helpers/isValidDate'
import type { Match } from '../../../Matches'
export const useFilters = () => {
const { search } = useLocation()
@ -31,9 +31,11 @@ export const useFilters = () => {
})
const sportList = getLocalStorageItem(querieKeys.sportsList)
const [selectedMode, setSelectedMode] = useState(0)
const [selectedMode, setSelectedMode] = useState<Tabs>(isMobileDevice ? Tabs.WEEK : Tabs.TIMELINE)
const [selectedMonthModeDate, setSelectedMonthModeDate] = useState(startOfMonth(new Date()))
const isMonthMode = selectedMode === Tabs.MONTH
const isTimelineMode = selectedMode === Tabs.TIMELINE && !isMobileDevice
const isWeekMode = selectedMode === Tabs.WEEK
const [selectedSport, setSelectedSport] = useState(['all_sports'])
const [selectedLeague, setSelectedLeague] = useState<Array<string | number>>(['all_competitions'])
@ -61,11 +63,11 @@ export const useFilters = () => {
)
}, [selectedMode, selectedMonthModeDate])
const compareSport = useCallback((match: Match, sportNames: Array<string>) => {
const compareSport = useCallback((sportType: number, sportNames: Array<string>) => {
if (sportNames[0] === 'all_sports') {
return true
}
const sport = getSportLexic(match.sportType)
const sport = getSportLexic(sportType)
return (sportNames.indexOf(sport) >= 0 || sportNames.indexOf(`${sport}_popup`) >= 0)
}, [])
@ -117,7 +119,9 @@ export const useFilters = () => {
compareSport,
isMonthMode,
isShowTournament,
isTimelineMode,
isTodaySelected,
isWeekMode,
resetFilters,
selectTournament,
selectedDate,
@ -163,6 +167,8 @@ export const useFilters = () => {
setSelectedMonthModeDate,
selectedMonthModeDate,
isMonthMode,
isTimelineMode,
isWeekMode,
])
return store

@ -3,12 +3,11 @@ import { useRecoilValue } from 'recoil'
import { isAndroid, isIOS } from 'config/userAgent'
import {
client,
isFqtvClient,
isLffClient,
} from 'config/clients'
import { HeaderMenu } from 'features/HeaderMenu'
import { DateFilter, FacrDateFilter } from 'features/HeaderFilters'
import { DateFilter } from 'features/HeaderFilters'
import { ScoreSwitch } from 'features/MatchSwitches'
import { SportsFilter } from 'features/SportsFilter'
import { isSportFilterShownAtom } from 'features/HomePage/Atoms/HomePageAtoms'
@ -45,7 +44,7 @@ export const HeaderMobile = ({
}
<HeaderStyled>
<HeaderMenu />
{isFqtvClient ? <FacrDateFilter /> : <DateFilter />}
<DateFilter />
<ScSportsWrapper>
{!isLffClient && isSportFilterShown ? <SportsFilter /> : null}
<ScoreSwitchWrapper>

@ -74,6 +74,7 @@ export const HeaderStyled = styled.header<HeaderProps>`
? css`
padding: 8px;
margin-bottom: 50px;
justify-content: flex-start;
`
: ''}
`

@ -1,7 +1,7 @@
import { Link } from 'react-router-dom'
import { PAGES } from 'config/pages'
import { client, isFqtvClient } from 'config/clients'
import { client } from 'config/clients'
import { Menu } from 'features/Menu'
import { ScoreSwitch } from 'features/MatchSwitches'
@ -9,7 +9,6 @@ import { Search } from 'features/Search'
import {
DateFilter,
useHeaderFiltersStore,
FacrDateFilter,
} from 'features/HeaderFilters'
import {
@ -45,7 +44,7 @@ export const Header = () => {
<Search />
</HeaderGroup>
</Position>
{isFqtvClient ? <FacrDateFilter /> : <DateFilter />}
<DateFilter />
<Position right={0.71}>
<HeaderGroup>
<ScoreSwitch />

@ -22,6 +22,7 @@ import { isSportFilterShownAtom } from '../../Atoms/HomePageAtoms'
export const HeaderFilters = () => {
const {
isShowTournament,
isTimelineMode,
selectedFilters,
selectTournament,
setIsShowTournament,
@ -71,7 +72,7 @@ export const HeaderFilters = () => {
)}
{!isLffClient && isShowTournament && isSportFilterShown && <SportsFilter />}
{isShowTournament && (
{isShowTournament && !isTimelineMode && (
<ScFilterItemsWrap>
<ScFilterItem
className={isActiveFilter('live') ? 'activeLive' : ''}

@ -6,7 +6,6 @@ export const ScHeaderFilters = styled.div`
display: flex;
flex-direction: row;
margin-bottom: 23px;
align-items: center;
.activeLive {
color: #ffffff;
@ -34,7 +33,7 @@ type Props = {
export const ScFilterItem = styled.div<Props>`
text-transform: uppercase;
font-weight: 700;
font-size: 18px;
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.5);
margin: 0 10px;
cursor: pointer;

@ -6,6 +6,7 @@ import { ConfirmPopup } from 'features/AuthServiceApp/components/ConfirmPopup'
import { Matches } from 'features/Matches'
import {
HeaderFiltersStore,
useHeaderFiltersStore,
} from 'features/HeaderFilters'
import {
PageWrapper,
@ -14,6 +15,7 @@ import {
} from 'features/PageLayout'
import { UserFavorites } from 'features/UserFavorites'
import { BuyMatchPopup } from 'features/BuyMatchPopup'
import { MatchesTimeline } from 'features/MatchesTimeline'
import { HEADER_MOBILE_ADS } from 'components/Ads/types'
import { HeaderAds } from 'components/Ads'
@ -35,6 +37,8 @@ const Home = () => {
userInfo,
} = useHomePage()
const { isTimelineMode } = useHeaderFiltersStore()
return (
<PageWrapper>
{isMobileDevice ? (
@ -60,7 +64,9 @@ const Home = () => {
}
/>
)}
<Matches fetch={fetchMatches} />
{isTimelineMode
? <MatchesTimeline />
: <Matches fetch={fetchMatches} />}
<ConfirmPopup
isModalOpen={isShowConfirmPopup}
handleModalClose={handleCloseConfirmPopup}

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { CSSProperties } from 'react'
import * as icons from '../../libs/index'
@ -7,7 +6,7 @@ export type IconProps = {
color?: string,
direction?: number,
onClick?: () => void,
refIcon: any,
refIcon: keyof typeof icons | string,
size?: number | string,
styles?: CSSProperties,
}

@ -9,7 +9,7 @@ import {
client,
} from 'config'
import type { Match } from 'features/Matches'
import type { Match } from 'helpers'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { useName } from 'features/Name'
import { T9n } from 'features/T9n'

@ -8,7 +8,7 @@ import { client } from 'config/clients'
import type { LiveScore } from 'requests'
import type { Match } from 'features/Matches'
import type { Match } from 'helpers'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { useName } from 'features/Name'
import { T9n } from 'features/T9n'
@ -82,7 +82,7 @@ export const CardFrontside = ({
const tournamentName = useName(tournament)
const { isInFavorites } = useUserFavoritesStore()
const { isScoreHidden } = useMatchSwitchesStore()
const { isMonthMode } = useHeaderFiltersStore()
const { isMonthMode, isTimelineMode } = useHeaderFiltersStore()
const { color } = tournament
const isInFuture = getUnixTime(date) > getUnixTime(new Date())
const showScore = !(
@ -179,9 +179,9 @@ export const CardFrontside = ({
<MatchDate
isHomePage={isHomePage}
isMatchPage={isMatchPage}
isMonthMode={isMonthMode}
isMonthMode={isMonthMode || isTimelineMode}
>
{(isHomePage && !isMonthMode) || isMatchPage ? null : prepareDate}
{(isHomePage && !isMonthMode && !isTimelineMode) || isMatchPage ? null : prepareDate}
<Time>{prepareTime}</Time>
</MatchDate>
{live && (

@ -1,3 +1,3 @@
export const MATCH_CARD_WIDTH = 12
export const MATCH_CARD_WIDTH = 22
export const MATCH_CARD_GAP = 20
export const MATCH_CARD_GAP = 40

@ -10,7 +10,7 @@ import {
ProfileTypes,
} from 'config'
import type { Match } from 'features/Matches'
import type { Match } from 'helpers'
import { useMatchPopupStore } from 'features/MatchPopup'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup'
import { useAuthStore } from 'features/AuthStore'

@ -1,4 +1,4 @@
import type { Match } from 'features/Matches'
import type { Match } from 'helpers'
import { isMobileDevice } from 'config/userAgent'

@ -9,8 +9,8 @@ import sortedUniq from 'lodash/sortedUniq'
import isNull from 'lodash/isNull'
import sortBy from 'lodash/sortBy'
import type { Match } from 'features/Matches'
import { prepareMatches } from 'features/Matches/helpers/prepareMatches'
import type { Match } from 'helpers'
import { prepareMatches } from 'helpers'
import { useAuthStore } from 'features/AuthStore'
import type { MatchInfo } from 'requests'

@ -1,6 +1,7 @@
import type { Lexics, Episodes } from 'requests'
import type { Match } from 'features/Matches'
import type { Match } from 'helpers'
import { Tabs } from 'features/MatchSidePlaylists/config'
import type { MatchPlaylistIds } from './helpers/buildPlaylists'

@ -1,5 +1,5 @@
import type { Match } from 'features/Matches/hooks'
import type { Match } from 'helpers'
export type MatchData = Pick<Match, (
'calc'

@ -2,7 +2,7 @@ import { Fragment } from 'react'
import isEmpty from 'lodash/isEmpty'
import type { Match } from 'features/Matches/hooks'
import type { Match } from 'helpers'
import { MatchesSlider } from 'features/MatchesSlider'
import { MatchesGrid } from 'features/MatchesGrid'
import { TournamentList } from 'features/TournamentList'

@ -1,8 +1,8 @@
import map from 'lodash/map'
import type { MatchesBySection, Matches } from 'requests'
import type { MatchesBySection, MatchesDto } from 'requests'
const addSportTypeToMatches = (matches: Matches, sport: number) => (
const addSportTypeToMatches = (matches: MatchesDto, sport: number) => (
map(matches, (match) => ({ ...match, sport }))
)

@ -1,4 +1,4 @@
import type { Match } from 'requests'
import type { MatchDto } from 'requests'
import { getMatchAccess, MatchAccess } from '..'
@ -14,7 +14,7 @@ type Args = {
const createMatch = (args: Args) => ({
...args,
} as Match)
} as MatchDto)
const user = undefined

@ -1,4 +1,4 @@
import type { Match } from 'requests'
import type { MatchDto } from 'requests'
import type { User } from 'oidc-client'
import { isFuture } from 'date-fns'
@ -13,7 +13,7 @@ export enum MatchAccess {
ViewMatchPopupWithoutUser = 'ViewMatchPopupWithoutUser',
}
export const getMatchAccess = (match: Match, user: User | undefined) => {
export const getMatchAccess = (match: MatchDto, user: User | undefined) => {
const {
access,
date,

@ -13,12 +13,10 @@ import { useRequest } from 'hooks'
import { usePreferencesStore } from 'features/PreferencesPopup'
import { isMobileDevice } from 'config/userAgent'
import { prepareMatches } from './helpers/prepareMatches'
import { prepareMatches } from 'helpers'
import { useAuthStore } from '../AuthStore'
export type Match = ReturnType<typeof prepareMatches>[number]
export type Props = {
fetch: (limit: number, offset: number) => Promise<MatchesBySection>,
}

@ -14,8 +14,6 @@ import { useMatches } from './hooks'
import { MatchesList } from './components/MatchesList'
import { Loading } from './styled'
export type { Match } from './hooks'
export const Matches = memo((props: Props) => {
const {
fetchMoreMatches,

@ -11,10 +11,10 @@ import { getLiveScores } from 'requests'
import { MatchCard } from 'features/MatchCard'
import { TournamentList } from 'features/TournamentList'
import type { Match } from 'features/Matches'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { readToken } from 'helpers'
import type { Match } from 'helpers'
import { useMatchSwitchesStore } from '../MatchSwitches'
import { useHomePage } from '../HomePage/hooks'
@ -41,6 +41,7 @@ export const MatchesGrid = memo(({ matches }: MatchesGridProps) => {
selectedDate,
selectedFilters,
selectedLeague,
selectedMode,
selectedSport,
updateSportIds,
} = useHeaderFiltersStore()
@ -60,7 +61,7 @@ export const MatchesGrid = memo(({ matches }: MatchesGridProps) => {
}
if (isHomePage && selectedSport) {
return matches.filter((match) => compareSport(match, selectedSport))
return matches.filter((match) => compareSport(match.sportType, selectedSport))
}
return matches
@ -83,7 +84,7 @@ export const MatchesGrid = memo(({ matches }: MatchesGridProps) => {
updateSportIds(matches)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedDate, matches])
}, [selectedDate, matches, selectedMode])
return (
<Wrapper>

@ -9,28 +9,33 @@ import {
import throttle from 'lodash/throttle'
import type { Match } from 'features/Matches'
import { MATCH_CARD_WIDTH, MATCH_CARD_GAP } from 'features/MatchCard/config'
const MATCHES_TO_SCROLL = 6
import type { Match } from 'helpers'
const SCROLLING_DELAY = 750
const MATCHES_TO_SCROLL = 12
const SCROLLING_DELAY = 350
export const useMatchesSlider = (matches: Array<Match>) => {
const slidesRef = useRef<HTMLUListElement>(null)
const [showLeftArrow, setShowLeftArrow] = useState(false)
const [showRightArrow, setShowRigthArrow] = useState(false)
const [isLeftArrowDisabled, setIsLeftArrowDisabled] = useState(false)
const [isRightArrowDisabled, setIsRightArrowDisabled] = useState(false)
useEffect(() => {
const {
clientWidth = 0,
// clientWidth = 0,
scrollLeft = 0,
scrollWidth = 0,
// scrollWidth = 0,
} = slidesRef.current || {}
const scrollRight = scrollWidth - (scrollLeft + clientWidth)
// const scrollRight = scrollWidth - (scrollLeft + clientWidth)
setShowRigthArrow(scrollRight > 1)
// setShowRigthArrow(scrollRight > 1)
// setShowLeftArrow(scrollRight > 1)
setIsLeftArrowDisabled(scrollLeft <= 0)
}, [matches, slidesRef])
const onScroll = useCallback((e: SyntheticEvent<HTMLUListElement>) => {
@ -39,11 +44,32 @@ export const useMatchesSlider = (matches: Array<Match>) => {
scrollLeft: targetScrollLeft,
scrollWidth: targetScrollWidth,
} = e.currentTarget
setShowLeftArrow(targetScrollLeft > 1)
setShowRigthArrow((targetScrollWidth - (targetScrollLeft + targetClientWidth)) > 1)
const targetScrollRight = targetScrollWidth - (targetScrollLeft + targetClientWidth)
// setShowLeftArrow(targetScrollLeft > 1)
// setShowRigthArrow(targetScrollRight > 1)
setIsLeftArrowDisabled(targetScrollLeft === 0)
setIsRightArrowDisabled(Math.round(targetScrollRight) <= 1)
}, [])
const onWrapperMouseEnter = () => {
const {
clientWidth = 0,
scrollLeft = 0,
scrollWidth = 0,
} = slidesRef.current || {}
const scrollRight = scrollWidth - (scrollLeft + clientWidth)
if (scrollRight > 1 || scrollLeft > 1) {
setShowLeftArrow(true)
setShowRigthArrow(true)
}
}
const onWrapperMouseLeave = () => {
setShowLeftArrow(false)
setShowRigthArrow(false)
}
const slideLeft = useMemo(() => throttle(() => {
slidesRef.current!.scrollBy(-((MATCH_CARD_WIDTH + MATCH_CARD_GAP) * MATCHES_TO_SCROLL), 0)
}, SCROLLING_DELAY), [])
@ -53,7 +79,11 @@ export const useMatchesSlider = (matches: Array<Match>) => {
}, SCROLLING_DELAY), [])
return {
isLeftArrowDisabled,
isRightArrowDisabled,
onScroll,
onWrapperMouseEnter,
onWrapperMouseLeave,
showLeftArrow,
showRightArrow,
slideLeft,

@ -1,14 +1,25 @@
import { useEffect } from 'react'
import { useQuery } from 'react-query'
import map from 'lodash/map'
import isEmpty from 'lodash/isEmpty'
import type { Match } from 'features/Matches'
import type { Match } from 'helpers'
import { querieKeys } from 'config'
import { MatchCard } from 'features/MatchCard'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { LiveScore, getLiveScores } from 'requests'
import { useMatchesSlider } from './hooks'
import {
Wrapper,
Arrow,
Slides,
ArrowButton,
} from './styled'
type MatchesSliderProps = {
@ -17,7 +28,11 @@ type MatchesSliderProps = {
export const MatchesSlider = ({ matches }: MatchesSliderProps) => {
const {
isLeftArrowDisabled,
isRightArrowDisabled,
onScroll,
onWrapperMouseEnter,
onWrapperMouseLeave,
showLeftArrow,
showRightArrow,
slideLeft,
@ -25,26 +40,89 @@ export const MatchesSlider = ({ matches }: MatchesSliderProps) => {
slidesRef,
} = useMatchesSlider(matches)
const { isScoreHidden } = useMatchSwitchesStore()
const { data: liveMatchScores } = useQuery({
queryFn: async () => {
if (!isScoreHidden && matches.filter(({ live }) => live)?.length > 0) {
const scores = await getLiveScores()
return scores
}
return []
},
queryKey: querieKeys.liveMatchScores,
refetchInterval: 5000,
})
const scrollToMatchByIndex = (index: number) => {
const PADDING_PARENT = 10
const offsetLeft = slidesRef.current!.querySelectorAll('li')[index].offsetLeft - PADDING_PARENT
slidesRef.current!.scrollBy(offsetLeft, 0)
}
// скролл к лайв матчам или сегодняшней дате
useEffect(() => {
const matchIndexLive = matches.findIndex(({ live }) => live)
if (matchIndexLive !== -1) {
scrollToMatchByIndex(matchIndexLive)
return
}
const matchIndex = matches.findIndex((item) => new Date() <= item.date)
if (matchIndex !== -1) {
scrollToMatchByIndex(matchIndex)
return
}
const slidesRefClientWidth = slidesRef.current!.clientWidth
const slidesRefScrollWidth = slidesRef.current!.scrollWidth
slidesRef.current!.scrollBy(slidesRefScrollWidth - slidesRefClientWidth, 0)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
if (isEmpty(matches)) return null
return (
<Wrapper>
<Wrapper
onMouseEnter={onWrapperMouseEnter}
onMouseLeave={onWrapperMouseLeave}
>
{showLeftArrow && (
<Arrow
type='arrowLeft'
aria-label='Slide left'
<ArrowButton
direction='left'
onClick={slideLeft}
/>
disabled={isLeftArrowDisabled}
>
<Arrow
disabled={isLeftArrowDisabled}
direction='left'
/>
</ArrowButton>
)}
<Slides ref={slidesRef} onScroll={onScroll}>
{map(matches, (match) => <MatchCard match={match} key={match.id} />)}
{map(matches, (match) => (
<MatchCard
match={match}
key={match.id}
score={liveMatchScores?.find(
({ match_id, sport_id }: LiveScore) => match_id === match.id
&& sport_id === match.sportType,
)}
/>
))}
</Slides>
{showRightArrow && (
<Arrow
type='arrowRight'
aria-label='Slide right'
<ArrowButton
direction='right'
onClick={slideRight}
/>
disabled={isRightArrowDisabled}
>
<Arrow
disabled={isRightArrowDisabled}
direction='right'
/>
</ArrowButton>
)}
</Wrapper>
)

@ -1,53 +1,111 @@
import styled from 'styled-components/macro'
import styled, { css } from 'styled-components/macro'
import { devices } from 'config/devices'
import { CardWrapper } from '../MatchCard/styled'
import { CardWrapper, CardWrapperOuter } from '../MatchCard/styled'
export const Wrapper = styled.div`
position: relative;
margin-bottom: 16px;
overflow: hidden;
padding-right: 5px;
`
export const Slides = styled.ul`
display: flex;
scroll-behavior: smooth;
overflow-x: auto;
gap: 0.9rem;
padding: 10px 10px 10px 7px;
&::-webkit-scrollbar {
display: none;
}
scrollbar-width: none;
${CardWrapperOuter} {
padding-top: 0;
}
${CardWrapper} {
width: 283px;
position: relative;
height: 12.9rem;
width: 12.9rem;
&:hover {
scale: 1.04;
}
@media ${devices.laptop} {
min-width: auto;
width: 279px;
@media screen and (min-width: 1920px) {
height: 13.8rem;
width: 13.8rem;
}
}
@media ${devices.mobile} {
flex-direction: column;
@media screen and (max-width: 1920px) {
height: 13.4rem;
width: 13.4rem;
}
${CardWrapper} {
width: 100%;
}
@media screen and (max-width: 1440px) {
height: 12.8rem;
width: 12.8rem;
}
@media screen and (max-width: 1280px) {
height: 12.6rem;
width: 12.6rem;
}
}
`
export const Arrow = styled.div<{ type: 'arrowLeft' | 'arrowRight' }>`
type ArrowProps = {
direction: 'left' | 'right',
disabled?: boolean,
}
export const Arrow = styled.span<ArrowProps>`
width: 1rem;
height: 1rem;
position: absolute;
border-left: 0.25rem solid #fff;
border-bottom: 0.25rem solid #fff;
top: 50%;
left: ${({ type }) => (type === 'arrowLeft' ? '10px' : 'calc(100% - 10px)')};
width: 40px;
height: 40px;
background-position: center;
background-repeat: no-repeat;
background-image: url(${({ type }) => (type === 'arrowLeft'
? '/images/slideLeft.svg'
: '/images/slideRight.svg')});
left: 50%;
border-radius: 3px;
${({ direction }) => (
direction === 'left'
? 'transform: translate(-50%, -50%) rotate(45deg);'
: 'transform: translate(-50%, -50%) rotate(225deg);'
)}
${({ disabled }) => (disabled ? css`
border-left: 0.25rem solid gray;
border-bottom: 0.25rem solid gray;
` : '')}
`
export const ArrowButton = styled.button<ArrowProps>`
border: none;
outline: none;
padding: 0;
background-color: transparent;
cursor: pointer;
position: absolute;
width: 2.28rem;
height: 2.28rem;
z-index: 3;
top: 50%;
transform: translate(-50%, -50%);
z-index: 1;
left: ${({ direction }) => (direction === 'left' ? '1.6rem' : 'calc(100% - 1.6rem)')};
&:hover {
${({ direction, disabled }) => (!disabled ? css`
width: 3rem;
height: 3rem;
left: ${direction === 'left' ? '2rem' : 'calc(100% - 2rem)'};
${Arrow} {
width: 1.5rem;
height: 1.5rem;
}
` : '')}
}
`

@ -0,0 +1,111 @@
import {
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import { useRouteMatch } from 'react-router-dom'
import {
MatchDto,
TimelineTournamentDto,
getTimelineMatches,
} from 'requests'
import { Match, prepareMatches } from 'helpers'
import { useAuthStore } from 'features/AuthStore'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { PAGES } from 'config'
export type TimelineTournamentList = Array<Omit<TimelineTournamentDto, 'matches'> & {
matches: Array<Match>,
}>
export const useTimeline = () => {
const isHomePage = useRouteMatch(PAGES.home)?.isExact
const { user } = useAuthStore()
const {
compareSport,
selectedMode,
selectedSport,
setSportIds,
} = useHeaderFiltersStore()
const [isTimelineFetching, setIsTimelineFetching] = useState(true)
const [onlineUpcomingMatches, setOnlineUpcomingMatches] = useState<Array<Match>>([])
const [tournamentList, setTournamentList] = useState<TimelineTournamentList>([])
const prepareMatchesDto = useCallback((matches: Array<MatchDto>) => prepareMatches(
matches,
user,
false,
), [user])
useEffect(() => {
(async () => {
setIsTimelineFetching(true)
try {
const timeline = await getTimelineMatches()
const convertedMatches = timeline.online_upcoming[0].matches
const preparedMatches = prepareMatchesDto(convertedMatches)
setOnlineUpcomingMatches(preparedMatches)
setTournamentList([
...timeline.favorite.map((item) => ({
...item,
matches: prepareMatchesDto(item.matches),
})),
...timeline.promo.map((item) => ({
...item,
matches: prepareMatchesDto(item.matches),
})),
...timeline.others.map((item) => ({
...item,
matches: prepareMatchesDto(item.matches),
})),
])
} catch (error) { /* empty */ } finally {
setIsTimelineFetching(false)
}
})()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const filteredTournamentsBySport = useMemo(() => {
if (isHomePage && selectedSport) {
return tournamentList.filter((t) => compareSport(t.sport_id, selectedSport))
}
return tournamentList
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tournamentList, selectedSport])
const filteredOnlineUpcomingBySport = useMemo(() => {
if (isHomePage && selectedSport) {
return onlineUpcomingMatches.filter((m) => compareSport(m.sportType, selectedSport))
}
return onlineUpcomingMatches
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [onlineUpcomingMatches, selectedSport])
useEffect(() => {
if (!isHomePage) return
const qwe = Array.from(new Set(tournamentList.map((t) => t.sport_id)))
setSportIds(qwe)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tournamentList, selectedMode])
return {
isTimelineFetching,
onlineUpcomingMatches: filteredOnlineUpcomingBySport,
tournamentList: filteredTournamentsBySport,
}
}

@ -0,0 +1,115 @@
import {
useCallback,
useEffect,
useRef,
useState,
} from 'react'
import { MatchesSlider } from 'features/MatchesSlider'
import { TimelineTournamentList, useTimeline } from 'features/MatchesTimeline/hooks'
import { InfiniteScroll } from 'features/InfiniteScroll'
import { T9n } from 'features/T9n'
import { TournamentSubtitle } from 'features/TournamentSubtitle'
import { ProfileTypes } from 'config'
import {
Content,
Tournament,
RowWrapper,
Wrapper,
TournamentLogo,
Loading,
TournamentSubtitleWrapper,
} from './styled'
const TOURNAMENT_LIMIT = 6
export const MatchesTimeline = () => {
const {
isTimelineFetching,
onlineUpcomingMatches,
tournamentList,
} = useTimeline()
const [tournaments, setTournaments] = useState<TimelineTournamentList>([])
const pageRef = useRef(0)
const isLastPageRef = useRef(false)
const getTournaments = useCallback(() => tournamentList.slice(
pageRef.current * TOURNAMENT_LIMIT,
pageRef.current * TOURNAMENT_LIMIT + TOURNAMENT_LIMIT,
), [tournamentList])
const getMoreTournaments = () => {
if (isLastPageRef.current) return
const res = getTournaments()
if (res.length) {
setTournaments((prev) => ([
...prev,
...res,
]))
pageRef.current++
} else {
isLastPageRef.current = true
}
}
useEffect(() => {
if (tournamentList.length) {
pageRef.current = 0
tournamentList.length && setTournaments(getTournaments())
pageRef.current = 1
}
}, [getTournaments, tournamentList])
if (isTimelineFetching) {
return <Loading><T9n t='loading' />...</Loading>
}
return (
<InfiniteScroll fullPageScroll onFetchMore={getMoreTournaments}>
<Wrapper>
{onlineUpcomingMatches.length > 0 && (
<RowWrapper>
<Content>
<Tournament isOnlineUpcoming>
LIVE & UPCOMING
</Tournament>
<MatchesSlider matches={onlineUpcomingMatches} />
</Content>
</RowWrapper>
)}
{tournaments.map(({
matches,
sport_id,
tournament,
tournament_id,
}) => (
<RowWrapper key={tournament_id}>
<TournamentSubtitleWrapper>
<TournamentSubtitle
sportInfo={matches[0].sportInfo}
countryId={matches[0].countryId}
sportType={sport_id}
tournament={tournament}
/>
</TournamentSubtitleWrapper>
<Content>
<Tournament gradientColor={tournament.color}>
<TournamentLogo
id={tournament_id}
profileType={ProfileTypes.TOURNAMENTS}
sportType={sport_id}
/>
</Tournament>
<MatchesSlider matches={matches} />
</Content>
</RowWrapper>
))}
</Wrapper>
</InfiniteScroll>
)
}

@ -0,0 +1,90 @@
import { ProfileLogo } from 'features/ProfileLogo'
import { StyledLink } from 'features/TournamentSubtitle/styled'
import styled, { css } from 'styled-components/macro'
export const Wrapper = styled.div`
& > * {
margin-bottom: 20px;
}
`
export const RowWrapper = styled.div``
export const Content = styled.div`
display: flex;
gap: 10px;
`
export const Tournament = styled.div<{
gradientColor?: string,
isOnlineUpcoming?: boolean,
}>`
${({ gradientColor }) => (gradientColor
? css`background: linear-gradient(187deg, ${gradientColor} -4.49%, #000000 68.29%), #000000;`
: css`background-color: ${({ theme }) => theme.colors.matchCardBackground};`)}
// в будущем от этого нужно будет избавиться
${({ isOnlineUpcoming }) => isOnlineUpcoming && css`
background: linear-gradient(270deg, #C00 0%, #6A2131 100%);
`}
position: relative;
height: 12.9rem;
width: 12.9rem;
flex-shrink: 0;
margin: 10px 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 3px;
font-size: 1.9rem;
font-weight: 700;
color: #FFFFFF;
text-align: start;
padding: 10px;
@media screen and (min-width: 1920px) {
height: 13.8rem;
width: 13.8rem;
}
@media screen and (max-width: 1920px) {
height: 13.4rem;
width: 13.4rem;
}
@media screen and (max-width: 1440px) {
height: 12.8rem;
width: 12.8rem;
}
@media screen and (max-width: 1280px) {
height: 12.6rem;
width: 12.6rem;
}
`
export const TournamentLogo = styled(ProfileLogo)`
position: absolute;
max-height: 100%;
padding: 20px;
`
export const Loading = styled.div`
height: 30px;
margin-top: 20px;
font-size: 24px;
color: #fff;
text-align: center;
`
export const TournamentSubtitleWrapper = styled.div`
margin-bottom: 10px;
${StyledLink} {
font-weight: 700;
color: #FFFFFF;
font-size: 1rem;
text-transform: uppercase;
}
`

@ -59,10 +59,11 @@ export const Content = styled.div`
export const BodyBackdrop = styled.div`
position: fixed;
top: 8.5rem;
// высота хедера
top: ${isMobileDevice ? '124px' : '8.5rem'} ;
left: 0;
background-color: rgba(0, 0, 0, 0.7);
width: 100vw;
height: calc(100vh - 8.5rem);
height: 100vh;
z-index: 2;
`

@ -21,6 +21,7 @@ export const ScSportsFilter = styled.div`
justify-content: space-between;
min-width: 15.4%;
padding-left: 5px;
align-items: flex-start;
${isMobileDevice
? css`

@ -10,9 +10,10 @@ import {
import { AdType } from 'requests'
import type { Match } from 'helpers'
import { T9n } from 'features/T9n'
import { Icon } from 'features/Icon'
import type { Match } from 'features/Matches'
import { MatchCard } from 'features/MatchCard'
import {
CountryFlag,

@ -4,10 +4,11 @@ import orderBy from 'lodash/orderBy'
import { ProfileTypes } from 'config'
import { TournamentListProps } from 'features/TournamentList'
import type { Match } from 'features/Matches'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { useUserFavoritesStore } from 'features/UserFavorites/store'
import type { Match } from 'helpers'
interface TournamentsSortProps {
id: number,
isFavorite: boolean,
@ -45,7 +46,7 @@ export const useTournaments = (matches: Array<Match>) => {
match.tournament.is_super_tournament ? match.group.id : match.tournament.id,
)
if (!acc[`${match.sportType}_${match.tournament.id}`] && compareSport(match, selectedSport)
if (!acc[`${match.sportType}_${match.tournament.id}`] && compareSport(match.sportType, selectedSport)
&& compareLeague(uniqTournamentId)) {
const tournament = {
...match.tournament,
@ -68,7 +69,7 @@ export const useTournaments = (matches: Array<Match>) => {
isSuperTournament: Boolean(match.tournament.is_super_tournament),
sportType: match.sportType,
})
} else if (compareSport(match, selectedSport) && compareLeague(uniqTournamentId)) {
} else if (compareSport(match.sportType, selectedSport) && compareLeague(uniqTournamentId)) {
acc[uniqTournamentId] = {
...acc[uniqTournamentId],
tournament: {

@ -1,6 +1,7 @@
import { useRouteMatch } from 'react-router-dom'
import type { Match } from 'features/Matches'
import type { Match } from 'helpers'
import { MatchCard } from 'features/MatchCard'
import type { TournamentType } from 'requests/getMatches/types'

@ -14,7 +14,6 @@ export const UserSportFavWrapper = styled.aside`
flex-direction: column;
align-items: center;
margin-top: 1rem;
padding-bottom: 2.75rem;
left: 0;
top: 0;
bottom: 0;
@ -29,7 +28,7 @@ export const UserSportFavWrapper = styled.aside`
export const ScrollWrapper = styled.div`
width: 4.35rem;
padding: 0.472rem 0.30rem 0 0.15rem;
padding: 0.472rem 0.30rem 0.472rem 0.15rem;
height: calc(100vh - 14rem);
display: flex;
flex-direction: column;

@ -17,3 +17,4 @@ export * from './languageUrlParam'
export * from './bodyScrollLock'
export * from './getLocalStorage'
export * from './checkPage'
export * from './prepareMatches'

@ -1,16 +1,19 @@
import map from 'lodash/map'
import orderBy from 'lodash/orderBy'
import format from 'date-fns/format'
import type { User } from 'oidc-client'
import type { Match } from 'requests'
import type { MatchDto } from 'requests'
import { parseDate } from 'helpers/parseDate'
import { getMatchAccess } from './getMatchClickAction'
import { getMatchAccess } from 'features/Matches/helpers/getMatchClickAction'
export type Match = ReturnType<typeof prepareMatch>
const prepareMatch = (match: Match, user?: User | undefined) => {
const prepareMatch = (match: MatchDto, user?: User | undefined) => {
const {
calc,
country,
@ -56,14 +59,22 @@ const prepareMatch = (match: Match, user?: User | undefined) => {
}
}
export const prepareMatches = (matches: Array<Match>, user?: User | undefined) => {
export const prepareMatches = (
matches: Array<MatchDto>,
user?: User | undefined,
liveOrder: boolean = true,
): Array<Match> => {
const preparedMatches = map(
matches,
(match) => prepareMatch(match, user),
)
return orderBy(
preparedMatches,
['live'],
['desc'],
)
if (liveOrder) {
return orderBy(
preparedMatches,
['live'],
['desc'],
)
}
return preparedMatches
}

@ -16,7 +16,7 @@ import {
import { useUserFavoritesStore } from 'features/UserFavorites/store'
import type { Match } from 'requests/getMatches/types'
import type { MatchDto } from 'requests'
import type { Sport } from 'requests/getSportList'
import { getSounds } from 'requests/getSounds'
@ -409,7 +409,7 @@ export const useHighlightsForm = () => {
sub_only: true,
})
.then(({ broadcast }) => setPlayerMatches(
broadcast.map((match: Match) => ({ ...match, isChecked: false })),
broadcast.map((match: MatchDto) => ({ ...match, isChecked: false })),
))
.finally(() => setIsFetching(false))

@ -1,8 +1,8 @@
import { atom, selector } from 'recoil'
import type { AdResponse, Match } from 'requests'
import type { AdResponse, MatchDto } from 'requests'
export type MatchType = Match & {
export type MatchType = MatchDto & {
isChecked: boolean,
}

@ -0,0 +1,35 @@
import { API_ROOT } from 'config'
import { callApi } from 'helpers'
import type { MatchesDto, TournamentType } from 'requests'
export type TimelineTournamentDto = {
matches: MatchesDto,
sport_id: number,
tournament: TournamentType,
tournament_id: number,
}
export type MatchesTimeline = {
favorite: Array<TimelineTournamentDto>,
online_upcoming: Array<{matches: MatchesDto}>,
others: Array<TimelineTournamentDto>,
promo: Array<TimelineTournamentDto>,
}
export const getTimelineMatches = (sportId?: number): Promise<MatchesTimeline> => {
const url = new URL(`${API_ROOT}/v1/matches/timeline`)
if (sportId) {
url.searchParams.append('sport_id', `${sportId}`)
}
const config = {
method: 'GET',
}
return callApi({
config,
url: url.href,
})
}

@ -3,3 +3,4 @@ export * from './getHomeMatches'
export * from './getTeamMatches'
export * from './getPlayerMatches'
export * from './getTournamentMatches'
export * from './getTimelineMatches'

@ -32,7 +32,7 @@ export type SportInfo = {
lexic: number,
}
export type Match = {
export type MatchDto = {
access: boolean,
/** тип трансляции */
c_type_broadcast: number,
@ -61,12 +61,12 @@ export type Match = {
tournament: TournamentType,
}
export type Matches = Array<Match>
export type MatchesDto = Array<MatchDto>
type VideoContent = {
broadcast: Matches,
features: Matches,
highlights: Matches,
broadcast: MatchesDto,
features: MatchesDto,
highlights: MatchesDto,
}
export type MatchesResponse = {
@ -76,9 +76,9 @@ export type MatchesResponse = {
}
export type MatchesBySection = {
broadcast: Matches,
features: Matches,
broadcast: MatchesDto,
features: MatchesDto,
hasNextPage: boolean,
highlights: Matches,
highlights: MatchesDto,
isVideoSections: boolean,
}

Loading…
Cancel
Save