From 71eff354a95b9f9bce1eb96763da92ee75813a8a Mon Sep 17 00:00:00 2001 From: Rakov Date: Wed, 6 Sep 2023 12:09:21 +0300 Subject: [PATCH] fix(#720): timeline mode --- src/config/lexics/indexLexics.tsx | 1 + src/features/BuyMatchPopup/types.tsx | 2 +- src/features/Common/Image/index.tsx | 1 + .../components/DateFilter/helpers.tsx | 14 +- .../components/DateFilter/hooks/index.tsx | 10 +- .../components/DateFilter/index.tsx | 199 +++++++++++++----- .../components/DateFilter/styled.tsx | 117 ++++++---- .../components/FacrDateFilter/index.tsx | 193 ----------------- src/features/HeaderFilters/index.tsx | 1 - src/features/HeaderFilters/store/config.tsx | 1 + .../HeaderFilters/store/hooks/index.tsx | 16 +- src/features/HeaderMobile/index.tsx | 5 +- src/features/HeaderMobile/styled.tsx | 1 + .../HomePage/components/Header/index.tsx | 5 +- .../components/HeaderFilters/index.tsx | 3 +- .../components/HeaderFilters/styled.tsx | 3 +- src/features/HomePage/index.tsx | 8 +- src/features/Icon/index.tsx | 3 +- .../CardFrontside/MatchCardMobile/index.tsx | 2 +- .../MatchCard/CardFrontside/index.tsx | 8 +- src/features/MatchCard/config.tsx | 4 +- src/features/MatchCard/hooks.tsx | 2 +- src/features/MatchCard/index.tsx | 2 +- .../store/hooks/useTournamentData.tsx | 4 +- src/features/MatchPage/types.tsx | 3 +- src/features/MatchPopup/types.tsx | 2 +- .../Matches/components/MatchesList/index.tsx | 2 +- src/features/Matches/helpers/addSportType.tsx | 4 +- .../getMatchClickAction/__tests__/index.tsx | 4 +- .../helpers/getMatchClickAction/index.tsx | 4 +- src/features/Matches/hooks.tsx | 4 +- src/features/Matches/index.tsx | 2 - src/features/MatchesGrid/index.tsx | 7 +- src/features/MatchesSlider/hooks.tsx | 50 ++++- src/features/MatchesSlider/index.tsx | 100 ++++++++- src/features/MatchesSlider/styled.tsx | 108 +++++++--- src/features/MatchesTimeline/hooks.tsx | 111 ++++++++++ src/features/MatchesTimeline/index.tsx | 115 ++++++++++ src/features/MatchesTimeline/styled.tsx | 90 ++++++++ src/features/PageLayout/styled.tsx | 5 +- .../components/SelectSport/styled.tsx | 1 + .../components/TournamentMobile/index.tsx | 3 +- src/features/TournamentList/hooks.tsx | 7 +- src/features/TournamentList/index.tsx | 3 +- src/features/UserFavorites/styled.tsx | 3 +- src/helpers/index.tsx | 1 + .../prepareMatches/index.tsx} | 29 ++- .../components/FormHighlights/hooks.tsx | 4 +- .../HighlightsPage/storeHighlightsAtoms.tsx | 4 +- .../getMatches/getTimelineMatches.tsx | 35 +++ src/requests/getMatches/index.tsx | 1 + src/requests/getMatches/types.tsx | 16 +- 52 files changed, 907 insertions(+), 416 deletions(-) delete mode 100644 src/features/HeaderFilters/components/FacrDateFilter/index.tsx create mode 100644 src/features/MatchesTimeline/hooks.tsx create mode 100644 src/features/MatchesTimeline/index.tsx create mode 100644 src/features/MatchesTimeline/styled.tsx rename src/{features/Matches/helpers/prepareMatches.tsx => helpers/prepareMatches/index.tsx} (65%) create mode 100644 src/requests/getMatches/getTimelineMatches.tsx diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx index 03a8224b..3a3442cd 100644 --- a/src/config/lexics/indexLexics.tsx +++ b/src/config/lexics/indexLexics.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, diff --git a/src/features/BuyMatchPopup/types.tsx b/src/features/BuyMatchPopup/types.tsx index e8145b99..eef6d7d0 100644 --- a/src/features/BuyMatchPopup/types.tsx +++ b/src/features/BuyMatchPopup/types.tsx @@ -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 { diff --git a/src/features/Common/Image/index.tsx b/src/features/Common/Image/index.tsx index 310aa9a4..a9d3591c 100644 --- a/src/features/Common/Image/index.tsx +++ b/src/features/Common/Image/index.tsx @@ -37,6 +37,7 @@ export const Image = ({ onError={onError} onLoad={onLoad} title={title} + loading='lazy' /> ) } diff --git a/src/features/HeaderFilters/components/DateFilter/helpers.tsx b/src/features/HeaderFilters/components/DateFilter/helpers.tsx index 20e53193..ac78ccf0 100644 --- a/src/features/HeaderFilters/components/DateFilter/helpers.tsx +++ b/src/features/HeaderFilters/components/DateFilter/helpers.tsx @@ -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 = [ + const months: Array = [ getMonth(0), getMonth(1), getMonth(2), diff --git a/src/features/HeaderFilters/components/DateFilter/hooks/index.tsx b/src/features/HeaderFilters/components/DateFilter/hooks/index.tsx index ac6bc17f..46707a5a 100644 --- a/src/features/HeaderFilters/components/DateFilter/hooks/index.tsx +++ b/src/features/HeaderFilters/components/DateFilter/hooks/index.tsx @@ -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, diff --git a/src/features/HeaderFilters/components/DateFilter/index.tsx b/src/features/HeaderFilters/components/DateFilter/index.tsx index 4f65973b..7155c731 100644 --- a/src/features/HeaderFilters/components/DateFilter/index.tsx +++ b/src/features/HeaderFilters/components/DateFilter/index.tsx @@ -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 ( - - - - {date.month} {' '} {date.year} - - - - - + + + + {isWeekMode && ( + + + {date.month} {' '} {date.year} + + + + + + )} + + {isMonthMode && ( + + + + + + {selectedMonthModeDate.getFullYear()} + + + + + + )} - - - - - + + {!isInSportsClient && ( + setSelectedMode(Tabs.MONTH)} + > + + + + + )} + {!isMobileDevice && ( + setSelectedMode(Tabs.TIMELINE)} + > + + + + + )} + {(!isMobileDevice || !isInSportsClient) && ( + setSelectedMode(Tabs.WEEK)} + > + + + + + )} + + + + {isMonthMode && ( + { - map(week, (day) => ( - ( + { - if (day.date.getDate() !== selectedDate.getDate()) { - addAdsViews() - onWeekDayClick(day.date) + if (month.date.getMonth() !== selectedMonthModeDate.getMonth()) { + setSelectedMonthModeDate(month.date) } else { resetFilters() } }} > - {day.name.slice(0, 3)} - {day.date.getDate()} - + {month.name} + )) } - - - - - + + )} + + {isWeekMode && ( + + + + + + { + map(week, (day) => ( + { + if (day.date.getDate() !== selectedDate.getDate()) { + addAdsViews() + onWeekDayClick(day.date) + } else { + resetFilters() + } + }} + > + {day.name.slice(0, 3)} + {day.date.getDate()} + + )) + } + + + + + + )} + { isOpen && ( @@ -107,6 +208,6 @@ export const DateFilter = () => { ) } - + ) } diff --git a/src/features/HeaderFilters/components/DateFilter/styled.tsx b/src/features/HeaderFilters/components/DateFilter/styled.tsx index 528ef42d..3deda5b8 100644 --- a/src/features/HeaderFilters/components/DateFilter/styled.tsx +++ b/src/features/HeaderFilters/components/DateFilter/styled.tsx @@ -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` : ''}; ` -export const MonthModeWrapper = styled(WeekDaysWrapper)` +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)` margin-top: 0; overflow-y: auto; height: 100%; + + & > :not(:last-child) { + margin-right: 25px; + } ` : ''}; ` -export const FacrWrapper = styled(Wrapper)` - justify-content: space-between; - height: ${(isMobileDevice ? '100%' : 'fit-content')}; - width: ${({ isMonthMode }) => (isMonthMode ? '49.5%' : '26.3rem')}; +export const CalendarWrapper = styled(Wrapper)` + 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` 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; diff --git a/src/features/HeaderFilters/components/FacrDateFilter/index.tsx b/src/features/HeaderFilters/components/FacrDateFilter/index.tsx deleted file mode 100644 index a6ccd5e7..00000000 --- a/src/features/HeaderFilters/components/FacrDateFilter/index.tsx +++ /dev/null @@ -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 ( - - - - setSelectedMode(Tabs.MONTH)} - > - - - - - setSelectedMode(Tabs.WEEK)} - > - - - - - - {isMonthMode - ? ( - - - - - - {selectedMonthModeDate.getFullYear()} - - - - - - ) - : ( - - - {date.month} {' '} {date.year} - - - - - - )} - - - {isMonthMode - ? ( - - - - { - map(months, (day) => ( - setSelectedMonthModeDate(day.date)} - > - {day.name} - - )) - } - - - - ) - : ( - - - - - - { - map(week, (day) => ( - { - if (day.date.getDate() !== selectedDate.getDate()) { - addAdsViews() - onWeekDayClick(day.date) - } else { - resetFilters() - } - }} - > - {day.name.slice(0, 3)} - {day.date.getDate()} - - )) - } - - - - - - - )} - { - isOpen && ( - - - - - - - ) - } - - ) -} diff --git a/src/features/HeaderFilters/index.tsx b/src/features/HeaderFilters/index.tsx index 80de6b06..08cf91a0 100644 --- a/src/features/HeaderFilters/index.tsx +++ b/src/features/HeaderFilters/index.tsx @@ -1,3 +1,2 @@ export * from './components/DateFilter' -export * from './components/FacrDateFilter' export * from './store' diff --git a/src/features/HeaderFilters/store/config.tsx b/src/features/HeaderFilters/store/config.tsx index 1ba0f9c6..a631586a 100644 --- a/src/features/HeaderFilters/store/config.tsx +++ b/src/features/HeaderFilters/store/config.tsx @@ -8,4 +8,5 @@ export const filterKeys = { export enum Tabs { WEEK, MONTH, + TIMELINE } diff --git a/src/features/HeaderFilters/store/hooks/index.tsx b/src/features/HeaderFilters/store/hooks/index.tsx index 0035a0f7..be3255a2 100644 --- a/src/features/HeaderFilters/store/hooks/index.tsx +++ b/src/features/HeaderFilters/store/hooks/index.tsx @@ -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(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>(['all_competitions']) @@ -61,11 +63,11 @@ export const useFilters = () => { ) }, [selectedMode, selectedMonthModeDate]) - const compareSport = useCallback((match: Match, sportNames: Array) => { + const compareSport = useCallback((sportType: number, sportNames: Array) => { 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 diff --git a/src/features/HeaderMobile/index.tsx b/src/features/HeaderMobile/index.tsx index feffe855..939f6beb 100644 --- a/src/features/HeaderMobile/index.tsx +++ b/src/features/HeaderMobile/index.tsx @@ -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 = ({ } - {isFqtvClient ? : } + {!isLffClient && isSportFilterShown ? : null} diff --git a/src/features/HeaderMobile/styled.tsx b/src/features/HeaderMobile/styled.tsx index 4709bd59..926d12bd 100644 --- a/src/features/HeaderMobile/styled.tsx +++ b/src/features/HeaderMobile/styled.tsx @@ -74,6 +74,7 @@ export const HeaderStyled = styled.header` ? css` padding: 8px; margin-bottom: 50px; + justify-content: flex-start; ` : ''} ` diff --git a/src/features/HomePage/components/Header/index.tsx b/src/features/HomePage/components/Header/index.tsx index 58df0145..c01950cb 100644 --- a/src/features/HomePage/components/Header/index.tsx +++ b/src/features/HomePage/components/Header/index.tsx @@ -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 = () => { - {isFqtvClient ? : } + diff --git a/src/features/HomePage/components/HeaderFilters/index.tsx b/src/features/HomePage/components/HeaderFilters/index.tsx index 5d7e4c3d..b20d88c9 100644 --- a/src/features/HomePage/components/HeaderFilters/index.tsx +++ b/src/features/HomePage/components/HeaderFilters/index.tsx @@ -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 && } - {isShowTournament && ( + {isShowTournament && !isTimelineMode && ( ` 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; diff --git a/src/features/HomePage/index.tsx b/src/features/HomePage/index.tsx index 2b90ebc0..41eba88f 100644 --- a/src/features/HomePage/index.tsx +++ b/src/features/HomePage/index.tsx @@ -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 ( {isMobileDevice ? ( @@ -60,7 +64,9 @@ const Home = () => { } /> )} - + {isTimelineMode + ? + : } void, - refIcon: any, + refIcon: keyof typeof icons | string, size?: number | string, styles?: CSSProperties, } diff --git a/src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx b/src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx index f7850103..1ff45cae 100644 --- a/src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx +++ b/src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx @@ -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' diff --git a/src/features/MatchCard/CardFrontside/index.tsx b/src/features/MatchCard/CardFrontside/index.tsx index 0c1b047a..6606eb94 100644 --- a/src/features/MatchCard/CardFrontside/index.tsx +++ b/src/features/MatchCard/CardFrontside/index.tsx @@ -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 = ({ - {(isHomePage && !isMonthMode) || isMatchPage ? null : prepareDate} + {(isHomePage && !isMonthMode && !isTimelineMode) || isMatchPage ? null : prepareDate} {live && ( diff --git a/src/features/MatchCard/config.tsx b/src/features/MatchCard/config.tsx index 9930e6b5..56d89051 100644 --- a/src/features/MatchCard/config.tsx +++ b/src/features/MatchCard/config.tsx @@ -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 diff --git a/src/features/MatchCard/hooks.tsx b/src/features/MatchCard/hooks.tsx index 696d3272..0986c8f6 100644 --- a/src/features/MatchCard/hooks.tsx +++ b/src/features/MatchCard/hooks.tsx @@ -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' diff --git a/src/features/MatchCard/index.tsx b/src/features/MatchCard/index.tsx index db334df0..90630ef4 100644 --- a/src/features/MatchCard/index.tsx +++ b/src/features/MatchCard/index.tsx @@ -1,4 +1,4 @@ -import type { Match } from 'features/Matches' +import type { Match } from 'helpers' import { isMobileDevice } from 'config/userAgent' diff --git a/src/features/MatchPage/store/hooks/useTournamentData.tsx b/src/features/MatchPage/store/hooks/useTournamentData.tsx index e2450632..bab7afe3 100644 --- a/src/features/MatchPage/store/hooks/useTournamentData.tsx +++ b/src/features/MatchPage/store/hooks/useTournamentData.tsx @@ -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' diff --git a/src/features/MatchPage/types.tsx b/src/features/MatchPage/types.tsx index d39d9805..82308e11 100644 --- a/src/features/MatchPage/types.tsx +++ b/src/features/MatchPage/types.tsx @@ -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' diff --git a/src/features/MatchPopup/types.tsx b/src/features/MatchPopup/types.tsx index 00e05f3a..b444a732 100644 --- a/src/features/MatchPopup/types.tsx +++ b/src/features/MatchPopup/types.tsx @@ -1,5 +1,5 @@ -import type { Match } from 'features/Matches/hooks' +import type { Match } from 'helpers' export type MatchData = Pick ( +const addSportTypeToMatches = (matches: MatchesDto, sport: number) => ( map(matches, (match) => ({ ...match, sport })) ) diff --git a/src/features/Matches/helpers/getMatchClickAction/__tests__/index.tsx b/src/features/Matches/helpers/getMatchClickAction/__tests__/index.tsx index eff2f50c..4d96154d 100644 --- a/src/features/Matches/helpers/getMatchClickAction/__tests__/index.tsx +++ b/src/features/Matches/helpers/getMatchClickAction/__tests__/index.tsx @@ -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 diff --git a/src/features/Matches/helpers/getMatchClickAction/index.tsx b/src/features/Matches/helpers/getMatchClickAction/index.tsx index 9a799281..b2639c41 100644 --- a/src/features/Matches/helpers/getMatchClickAction/index.tsx +++ b/src/features/Matches/helpers/getMatchClickAction/index.tsx @@ -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, diff --git a/src/features/Matches/hooks.tsx b/src/features/Matches/hooks.tsx index ec59b014..a6a99fff 100644 --- a/src/features/Matches/hooks.tsx +++ b/src/features/Matches/hooks.tsx @@ -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[number] - export type Props = { fetch: (limit: number, offset: number) => Promise, } diff --git a/src/features/Matches/index.tsx b/src/features/Matches/index.tsx index 4bf7a1c5..bf446e65 100644 --- a/src/features/Matches/index.tsx +++ b/src/features/Matches/index.tsx @@ -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, diff --git a/src/features/MatchesGrid/index.tsx b/src/features/MatchesGrid/index.tsx index e8f6c451..5c78e3df 100644 --- a/src/features/MatchesGrid/index.tsx +++ b/src/features/MatchesGrid/index.tsx @@ -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 ( diff --git a/src/features/MatchesSlider/hooks.tsx b/src/features/MatchesSlider/hooks.tsx index 320149f3..dfd2ce02 100644 --- a/src/features/MatchesSlider/hooks.tsx +++ b/src/features/MatchesSlider/hooks.tsx @@ -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) => { const slidesRef = useRef(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) => { @@ -39,11 +44,32 @@ export const useMatchesSlider = (matches: Array) => { 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) => { }, SCROLLING_DELAY), []) return { + isLeftArrowDisabled, + isRightArrowDisabled, onScroll, + onWrapperMouseEnter, + onWrapperMouseLeave, showLeftArrow, showRightArrow, slideLeft, diff --git a/src/features/MatchesSlider/index.tsx b/src/features/MatchesSlider/index.tsx index b5b27988..f1b546bf 100644 --- a/src/features/MatchesSlider/index.tsx +++ b/src/features/MatchesSlider/index.tsx @@ -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 ( - + {showLeftArrow && ( - + disabled={isLeftArrowDisabled} + > + + )} - {map(matches, (match) => )} + {map(matches, (match) => ( + match_id === match.id + && sport_id === match.sportType, + )} + /> + ))} {showRightArrow && ( - + disabled={isRightArrowDisabled} + > + + )} ) diff --git a/src/features/MatchesSlider/styled.tsx b/src/features/MatchesSlider/styled.tsx index 99fab09d..eb341941 100644 --- a/src/features/MatchesSlider/styled.tsx +++ b/src/features/MatchesSlider/styled.tsx @@ -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` + 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` + 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; + } + ` : '')} + } ` diff --git a/src/features/MatchesTimeline/hooks.tsx b/src/features/MatchesTimeline/hooks.tsx new file mode 100644 index 00000000..d2cd0818 --- /dev/null +++ b/src/features/MatchesTimeline/hooks.tsx @@ -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 & { + matches: Array, +}> + +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>([]) + const [tournamentList, setTournamentList] = useState([]) + + const prepareMatchesDto = useCallback((matches: Array) => 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, + } +} + diff --git a/src/features/MatchesTimeline/index.tsx b/src/features/MatchesTimeline/index.tsx new file mode 100644 index 00000000..a1ec701f --- /dev/null +++ b/src/features/MatchesTimeline/index.tsx @@ -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([]) + + 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 ... + } + + return ( + + + {onlineUpcomingMatches.length > 0 && ( + + + + LIVE & UPCOMING + + + + + )} + {tournaments.map(({ + matches, + sport_id, + tournament, + tournament_id, + }) => ( + + + + + + + + + + + + ))} + + + ) +} diff --git a/src/features/MatchesTimeline/styled.tsx b/src/features/MatchesTimeline/styled.tsx new file mode 100644 index 00000000..0a8696cd --- /dev/null +++ b/src/features/MatchesTimeline/styled.tsx @@ -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; + } +` diff --git a/src/features/PageLayout/styled.tsx b/src/features/PageLayout/styled.tsx index 3c1b32ed..853f6722 100644 --- a/src/features/PageLayout/styled.tsx +++ b/src/features/PageLayout/styled.tsx @@ -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; ` diff --git a/src/features/SportsFilter/components/SelectSport/styled.tsx b/src/features/SportsFilter/components/SelectSport/styled.tsx index 6747afcc..32b16e22 100644 --- a/src/features/SportsFilter/components/SelectSport/styled.tsx +++ b/src/features/SportsFilter/components/SelectSport/styled.tsx @@ -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` diff --git a/src/features/TournamentList/components/TournamentMobile/index.tsx b/src/features/TournamentList/components/TournamentMobile/index.tsx index 8bed7cd4..51736d87 100644 --- a/src/features/TournamentList/components/TournamentMobile/index.tsx +++ b/src/features/TournamentList/components/TournamentMobile/index.tsx @@ -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, diff --git a/src/features/TournamentList/hooks.tsx b/src/features/TournamentList/hooks.tsx index 71fdd45a..373cd9c3 100644 --- a/src/features/TournamentList/hooks.tsx +++ b/src/features/TournamentList/hooks.tsx @@ -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.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) => { 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: { diff --git a/src/features/TournamentList/index.tsx b/src/features/TournamentList/index.tsx index 7407d515..9a2b67fb 100644 --- a/src/features/TournamentList/index.tsx +++ b/src/features/TournamentList/index.tsx @@ -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' diff --git a/src/features/UserFavorites/styled.tsx b/src/features/UserFavorites/styled.tsx index bb588989..6ce8a16e 100644 --- a/src/features/UserFavorites/styled.tsx +++ b/src/features/UserFavorites/styled.tsx @@ -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; diff --git a/src/helpers/index.tsx b/src/helpers/index.tsx index 814a4711..c56ad20e 100644 --- a/src/helpers/index.tsx +++ b/src/helpers/index.tsx @@ -17,3 +17,4 @@ export * from './languageUrlParam' export * from './bodyScrollLock' export * from './getLocalStorage' export * from './checkPage' +export * from './prepareMatches' diff --git a/src/features/Matches/helpers/prepareMatches.tsx b/src/helpers/prepareMatches/index.tsx similarity index 65% rename from src/features/Matches/helpers/prepareMatches.tsx rename to src/helpers/prepareMatches/index.tsx index bec2581b..944cf5e4 100644 --- a/src/features/Matches/helpers/prepareMatches.tsx +++ b/src/helpers/prepareMatches/index.tsx @@ -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 -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, user?: User | undefined) => { +export const prepareMatches = ( + matches: Array, + user?: User | undefined, + liveOrder: boolean = true, +): Array => { const preparedMatches = map( matches, (match) => prepareMatch(match, user), ) - return orderBy( - preparedMatches, - ['live'], - ['desc'], - ) + + if (liveOrder) { + return orderBy( + preparedMatches, + ['live'], + ['desc'], + ) + } + return preparedMatches } diff --git a/src/pages/HighlightsPage/components/FormHighlights/hooks.tsx b/src/pages/HighlightsPage/components/FormHighlights/hooks.tsx index 4d165f13..15490349 100644 --- a/src/pages/HighlightsPage/components/FormHighlights/hooks.tsx +++ b/src/pages/HighlightsPage/components/FormHighlights/hooks.tsx @@ -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)) diff --git a/src/pages/HighlightsPage/storeHighlightsAtoms.tsx b/src/pages/HighlightsPage/storeHighlightsAtoms.tsx index 542aed44..e6ee6eca 100644 --- a/src/pages/HighlightsPage/storeHighlightsAtoms.tsx +++ b/src/pages/HighlightsPage/storeHighlightsAtoms.tsx @@ -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, } diff --git a/src/requests/getMatches/getTimelineMatches.tsx b/src/requests/getMatches/getTimelineMatches.tsx new file mode 100644 index 00000000..e6bf2f55 --- /dev/null +++ b/src/requests/getMatches/getTimelineMatches.tsx @@ -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, + online_upcoming: Array<{matches: MatchesDto}>, + others: Array, + promo: Array, +} + +export const getTimelineMatches = (sportId?: number): Promise => { + 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, + }) +} diff --git a/src/requests/getMatches/index.tsx b/src/requests/getMatches/index.tsx index 3e17f564..607ec0a5 100644 --- a/src/requests/getMatches/index.tsx +++ b/src/requests/getMatches/index.tsx @@ -3,3 +3,4 @@ export * from './getHomeMatches' export * from './getTeamMatches' export * from './getPlayerMatches' export * from './getTournamentMatches' +export * from './getTimelineMatches' diff --git a/src/requests/getMatches/types.tsx b/src/requests/getMatches/types.tsx index 4afa6458..e6ba1640 100644 --- a/src/requests/getMatches/types.tsx +++ b/src/requests/getMatches/types.tsx @@ -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 +export type MatchesDto = Array 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, }