Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
fc86d8ee44 | 2 years ago |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 793 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 802 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 598 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 102 KiB |
@ -1,28 +1,15 @@ |
|||||||
<!DOCTYPE html> |
<!DOCTYPE html> |
||||||
<html lang="en"> |
<html lang="en"> |
||||||
|
|
||||||
<head> |
<head> |
||||||
<title></title> |
<title></title> |
||||||
</head> |
</head> |
||||||
|
|
||||||
<body> |
<body> |
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client/1.11.5/oidc-client.min.js" |
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client/1.11.5/oidc-client.min.js" integrity="sha512-pGtU1n/6GJ8fu6bjYVGIOT9Dphaw5IWPwVlqkpvVgqBxFkvdNbytUh0H8AP15NYF777P4D3XEeA/uDWFCpSQ1g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> |
||||||
integrity="sha512-pGtU1n/6GJ8fu6bjYVGIOT9Dphaw5IWPwVlqkpvVgqBxFkvdNbytUh0H8AP15NYF777P4D3XEeA/uDWFCpSQ1g==" |
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script> |
|
||||||
<script> |
<script> |
||||||
new Oidc.UserManager().signinSilentCallback() |
new Oidc.UserManager().signinSilentCallback() |
||||||
// обновляем рефреш токен в локалсторадже |
|
||||||
// так как safari не дает доступ к кукам |
|
||||||
.then(() => { |
|
||||||
const refreshToken = localStorage.getItem('refresh_token'); |
|
||||||
if (refreshToken) { |
|
||||||
localStorage.setItem('refresh_token', new URLSearchParams(document.location.search).get('refresh_token')); |
|
||||||
} |
|
||||||
}) |
|
||||||
.catch((err) => { |
.catch((err) => { |
||||||
console.error('OIDC: silent refresh callback error', err); |
console.error('OIDC: silent refresh callback error', err); |
||||||
}); |
}); |
||||||
</script> |
</script> |
||||||
</body> |
</body> |
||||||
|
|
||||||
</html> |
</html> |
||||||
@ -0,0 +1,193 @@ |
|||||||
|
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,2 +1,3 @@ |
|||||||
export * from './components/DateFilter' |
export * from './components/DateFilter' |
||||||
|
export * from './components/FacrDateFilter' |
||||||
export * from './store' |
export * from './store' |
||||||
|
|||||||
@ -1,3 +1,3 @@ |
|||||||
export const MATCH_CARD_WIDTH = 22 |
export const MATCH_CARD_WIDTH = 12 |
||||||
|
|
||||||
export const MATCH_CARD_GAP = 40 |
export const MATCH_CARD_GAP = 20 |
||||||
|
|||||||
@ -1,94 +1,53 @@ |
|||||||
import styled, { css } from 'styled-components/macro' |
import styled from 'styled-components/macro' |
||||||
|
|
||||||
import { CardWrapper, CardWrapperOuter } from '../MatchCard/styled' |
import { devices } from 'config/devices' |
||||||
|
|
||||||
|
import { CardWrapper } from '../MatchCard/styled' |
||||||
|
|
||||||
export const Wrapper = styled.div` |
export const Wrapper = styled.div` |
||||||
position: relative; |
position: relative; |
||||||
overflow: hidden; |
margin-bottom: 16px; |
||||||
padding-right: 5px; |
|
||||||
` |
` |
||||||
|
|
||||||
export const Slides = styled.ul<{ |
export const Slides = styled.ul` |
||||||
size?: number, |
|
||||||
}>` |
|
||||||
display: flex; |
display: flex; |
||||||
scroll-behavior: smooth; |
scroll-behavior: smooth; |
||||||
overflow-x: auto; |
overflow-x: auto; |
||||||
gap: 0.9rem; |
|
||||||
padding: 10px 10px 10px 7px; |
|
||||||
|
|
||||||
&::-webkit-scrollbar { |
&::-webkit-scrollbar { |
||||||
display: none; |
display: none; |
||||||
} |
} |
||||||
scrollbar-width: none;
|
|
||||||
|
|
||||||
${CardWrapperOuter} { |
|
||||||
padding-top: 0; |
|
||||||
} |
|
||||||
|
|
||||||
${CardWrapper} { |
${CardWrapper} { |
||||||
position: relative; |
width: 283px; |
||||||
height: ${({ size }) => (size ? `${size}px` : '12.9rem')}; |
|
||||||
width: ${({ size }) => (size ? `${size}px` : '12.9rem')}; |
|
||||||
transition: scale 0.2s; |
|
||||||
|
|
||||||
&:hover { |
@media ${devices.laptop} { |
||||||
scale: 1.04; |
min-width: auto; |
||||||
|
width: 279px; |
||||||
} |
} |
||||||
} |
} |
||||||
` |
|
||||||
|
|
||||||
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: 50%; |
|
||||||
border-radius: 3px; |
|
||||||
|
|
||||||
${({ direction }) => ( |
@media ${devices.mobile} { |
||||||
direction === 'left' |
flex-direction: column; |
||||||
? 'transform: translate(-50%, -50%) rotate(45deg);' |
|
||||||
: 'transform: translate(-50%, -50%) rotate(225deg);' |
|
||||||
)} |
|
||||||
|
|
||||||
${({ disabled }) => (disabled ? css` |
${CardWrapper} { |
||||||
border-left: 0.25rem solid gray; |
width: 100%; |
||||||
border-bottom: 0.25rem solid gray; |
} |
||||||
` : '')}
|
} |
||||||
` |
` |
||||||
|
|
||||||
export const ArrowButton = styled.button<ArrowProps>` |
export const Arrow = styled.div<{ type: 'arrowLeft' | 'arrowRight' }>` |
||||||
border: none; |
|
||||||
outline: none; |
|
||||||
padding: 0; |
|
||||||
background-color: transparent; |
|
||||||
cursor: pointer; |
|
||||||
position: absolute; |
position: absolute; |
||||||
width: 2.28rem; |
|
||||||
height: 2.28rem; |
|
||||||
z-index: 3; |
|
||||||
top: 50%; |
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')}); |
||||||
|
cursor: pointer; |
||||||
transform: translate(-50%, -50%); |
transform: translate(-50%, -50%); |
||||||
left: ${({ direction }) => (direction === 'left' ? '1.6rem' : 'calc(100% - 2.3rem)')}; |
z-index: 1; |
||||||
|
|
||||||
&:hover { |
|
||||||
${({ direction, disabled }) => (!disabled ? css` |
|
||||||
width: 3rem; |
|
||||||
height: 3rem; |
|
||||||
left: ${direction === 'left' ? '2rem' : 'calc(100% - 2.8rem)'}; |
|
||||||
|
|
||||||
${Arrow} { |
|
||||||
width: 1.5rem; |
|
||||||
height: 1.5rem; |
|
||||||
} |
|
||||||
` : '')}
|
|
||||||
} |
|
||||||
` |
` |
||||||
|
|||||||
@ -1,168 +0,0 @@ |
|||||||
/* eslint-disable no-param-reassign */ |
|
||||||
import { |
|
||||||
useCallback, |
|
||||||
useEffect, |
|
||||||
useMemo, |
|
||||||
useState, |
|
||||||
} from 'react' |
|
||||||
|
|
||||||
import { useQuery } from 'react-query' |
|
||||||
import { querieKeys, PAGES } from 'config' |
|
||||||
|
|
||||||
import { useRouteMatch } from 'react-router-dom' |
|
||||||
|
|
||||||
import { |
|
||||||
MatchDto, |
|
||||||
MatchesTimeline, |
|
||||||
TimelineTournamentDto, |
|
||||||
getLiveScores, |
|
||||||
getTimelineMatches, |
|
||||||
} from 'requests' |
|
||||||
|
|
||||||
import { Match, prepareMatches } from 'helpers' |
|
||||||
|
|
||||||
import { useAuthStore } from 'features/AuthStore' |
|
||||||
import { useHeaderFiltersStore } from 'features/HeaderFilters' |
|
||||||
import { useMatchSwitchesStore } from 'features/MatchSwitches' |
|
||||||
|
|
||||||
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 { isScoreHidden } = useMatchSwitchesStore() |
|
||||||
|
|
||||||
const [isTimelineFetching, setIsTimelineFetching] = useState(true) |
|
||||||
const [onlineUpcomingMatches, setOnlineUpcomingMatches] = useState<Array<Match>>([]) |
|
||||||
const [tournamentList, setTournamentList] = useState<TimelineTournamentList>([]) |
|
||||||
const [timeline, setTimeline] = useState<MatchesTimeline>() |
|
||||||
|
|
||||||
const prepareMatchesDto = useCallback((matches: Array<MatchDto>) => prepareMatches( |
|
||||||
matches, |
|
||||||
user, |
|
||||||
false, |
|
||||||
), [user]) |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
(async () => { |
|
||||||
setIsTimelineFetching(true) |
|
||||||
try { |
|
||||||
const timelineFetched = await getTimelineMatches() |
|
||||||
setTimeline(timelineFetched) |
|
||||||
const convertedMatches = timelineFetched.online_upcoming[0].matches |
|
||||||
const preparedMatches = prepareMatchesDto(convertedMatches) |
|
||||||
setOnlineUpcomingMatches(preparedMatches) |
|
||||||
|
|
||||||
setTournamentList([ |
|
||||||
...timelineFetched.favorite.map((item) => ({ |
|
||||||
...item, |
|
||||||
matches: prepareMatchesDto(item.matches), |
|
||||||
})), |
|
||||||
...timelineFetched.promo.map((item) => ({ |
|
||||||
...item, |
|
||||||
matches: prepareMatchesDto(item.matches), |
|
||||||
})), |
|
||||||
...timelineFetched.others.map((item) => ({ |
|
||||||
...item, |
|
||||||
matches: prepareMatchesDto(item.matches), |
|
||||||
})), |
|
||||||
]) |
|
||||||
} catch (error) { /* empty */ } finally { |
|
||||||
setIsTimelineFetching(false) |
|
||||||
} |
|
||||||
})() |
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []) |
|
||||||
|
|
||||||
useQuery({ |
|
||||||
queryFn: async () => { |
|
||||||
let isLiveMatch = false |
|
||||||
if (timeline) { |
|
||||||
for (const [, value] of Object.entries(timeline)) { |
|
||||||
if (isLiveMatch) break |
|
||||||
// eslint-disable-next-line no-loop-func
|
|
||||||
value.forEach((t) => { |
|
||||||
const liveMatch = t.matches.find((match) => match.live) |
|
||||||
if (liveMatch) { |
|
||||||
isLiveMatch = true |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
if (!isScoreHidden && isLiveMatch) { |
|
||||||
const scores = await getLiveScores() |
|
||||||
tournamentList.forEach((tournament) => { |
|
||||||
tournament.matches.forEach((match) => { |
|
||||||
const score = scores.find((s) => ( |
|
||||||
s.match_id === match.id && s.sport_id === match.sportType |
|
||||||
)) |
|
||||||
if (score) { |
|
||||||
match.team1.score = score?.team1.score ?? match.team1.score |
|
||||||
match.team1.penalty_score = score.team1.penalty_score ?? match.team1.penalty_score |
|
||||||
match.team2.score = score?.team2.score ?? match.team2.score |
|
||||||
match.team2.penalty_score = score.team2.penalty_score ?? match.team2.penalty_score |
|
||||||
} |
|
||||||
}) |
|
||||||
}) |
|
||||||
onlineUpcomingMatches.forEach((upcomingMatch) => { |
|
||||||
const score = scores.find((s) => ( |
|
||||||
s.match_id === upcomingMatch.id && s.sport_id === upcomingMatch.sportType |
|
||||||
)) |
|
||||||
if (score) { |
|
||||||
upcomingMatch.team1.score = score?.team1.score ?? upcomingMatch.team1.score |
|
||||||
upcomingMatch.team1.penalty_score = score.team1.penalty_score |
|
||||||
?? upcomingMatch.team1.penalty_score |
|
||||||
upcomingMatch.team2.score = score?.team2.score ?? upcomingMatch.team2.score |
|
||||||
upcomingMatch.team2.penalty_score = score.team2.penalty_score |
|
||||||
?? upcomingMatch.team2.penalty_score |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
}, |
|
||||||
queryKey: querieKeys.liveMatchScores, |
|
||||||
refetchInterval: 30000, |
|
||||||
}) |
|
||||||
|
|
||||||
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 sportIds = Array.from(new Set(tournamentList.map((t) => t.sport_id))) |
|
||||||
setSportIds(sportIds) |
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [tournamentList, selectedMode]) |
|
||||||
|
|
||||||
return { |
|
||||||
isTimelineFetching, |
|
||||||
onlineUpcomingMatches: filteredOnlineUpcomingBySport, |
|
||||||
tournamentList: filteredTournamentsBySport, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@ -1,151 +0,0 @@ |
|||||||
import { |
|
||||||
useCallback, |
|
||||||
useEffect, |
|
||||||
useLayoutEffect, |
|
||||||
useRef, |
|
||||||
useState, |
|
||||||
} from 'react' |
|
||||||
|
|
||||||
import isEmpty from 'lodash/isEmpty' |
|
||||||
|
|
||||||
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 = 10 |
|
||||||
const MATCH_COUNT = 6 |
|
||||||
const PADDING = 30 |
|
||||||
const GAP_COUNT = 5 |
|
||||||
const SLIDER_ITEM_GAP = 0.9 // rem
|
|
||||||
|
|
||||||
export const MatchesTimeline = () => { |
|
||||||
const { |
|
||||||
isTimelineFetching, |
|
||||||
onlineUpcomingMatches, |
|
||||||
tournamentList, |
|
||||||
} = useTimeline() |
|
||||||
|
|
||||||
const [tournaments, setTournaments] = useState<TimelineTournamentList>([]) |
|
||||||
|
|
||||||
const pageRef = useRef(0) |
|
||||||
const isLastPageRef = useRef(false) |
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null) |
|
||||||
|
|
||||||
const [cardSize, setCardSize] = useState(0) |
|
||||||
|
|
||||||
// вычисляем размеры плитки
|
|
||||||
useLayoutEffect(() => { |
|
||||||
const offsetWidth = wrapperRef.current?.offsetWidth |
|
||||||
const ulItemGapFromRem = SLIDER_ITEM_GAP * parseFloat( |
|
||||||
getComputedStyle(document.documentElement).fontSize, |
|
||||||
) |
|
||||||
|
|
||||||
if (offsetWidth) { |
|
||||||
const size = Math.round( |
|
||||||
((offsetWidth - (ulItemGapFromRem * GAP_COUNT) - PADDING) / MATCH_COUNT), |
|
||||||
) |
|
||||||
|
|
||||||
setCardSize(size) |
|
||||||
} |
|
||||||
}, []) |
|
||||||
|
|
||||||
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 |
|
||||||
isLastPageRef.current = false |
|
||||||
tournamentList.length > 0 && setTournaments(getTournaments()) |
|
||||||
pageRef.current = 1 |
|
||||||
} |
|
||||||
}, [getTournaments, tournamentList]) |
|
||||||
|
|
||||||
return ( |
|
||||||
<InfiniteScroll fullPageScroll onFetchMore={getMoreTournaments}> |
|
||||||
<Wrapper ref={wrapperRef}> |
|
||||||
{isTimelineFetching && <Loading><T9n t='loading' />...</Loading>} |
|
||||||
{(!isEmpty(onlineUpcomingMatches)) && ( |
|
||||||
<RowWrapper> |
|
||||||
<Content> |
|
||||||
<Tournament |
|
||||||
size={cardSize} |
|
||||||
isOnlineUpcoming |
|
||||||
> |
|
||||||
LIVE & UPCOMING |
|
||||||
</Tournament> |
|
||||||
<MatchesSlider |
|
||||||
cardSize={cardSize} |
|
||||||
matches={onlineUpcomingMatches} |
|
||||||
/> |
|
||||||
</Content> |
|
||||||
</RowWrapper> |
|
||||||
)} |
|
||||||
{tournaments.map(({ |
|
||||||
matches, |
|
||||||
sport_id, |
|
||||||
tournament, |
|
||||||
tournament_id, |
|
||||||
}) => ( |
|
||||||
<RowWrapper key={`${tournament_id}_${sport_id}`}> |
|
||||||
<TournamentSubtitleWrapper> |
|
||||||
<TournamentSubtitle |
|
||||||
sportInfo={matches[0].sportInfo} |
|
||||||
countryId={matches[0].countryId} |
|
||||||
sportType={sport_id} |
|
||||||
tournament={tournament} |
|
||||||
/> |
|
||||||
</TournamentSubtitleWrapper> |
|
||||||
<Content> |
|
||||||
<Tournament |
|
||||||
size={cardSize} |
|
||||||
gradientColor={tournament.color} |
|
||||||
> |
|
||||||
<TournamentLogo |
|
||||||
id={tournament_id} |
|
||||||
profileType={ProfileTypes.TOURNAMENTS} |
|
||||||
sportType={sport_id} |
|
||||||
/> |
|
||||||
</Tournament> |
|
||||||
<MatchesSlider |
|
||||||
cardSize={cardSize} |
|
||||||
matches={matches} |
|
||||||
/> |
|
||||||
</Content> |
|
||||||
</RowWrapper> |
|
||||||
))} |
|
||||||
</Wrapper> |
|
||||||
</InfiniteScroll> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,72 +0,0 @@ |
|||||||
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; |
|
||||||
` |
|
||||||
|
|
||||||
export const Tournament = styled.div<{ |
|
||||||
gradientColor?: string, |
|
||||||
isOnlineUpcoming?: boolean, |
|
||||||
size?: number, |
|
||||||
}>` |
|
||||||
${({ 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: ${({ size }) => (size ? `${size}px` : '12.9rem')}; |
|
||||||
width: ${({ size }) => (size ? `${size}px` : '12.9rem')}; |
|
||||||
flex-shrink: 0; |
|
||||||
margin: 10px 0.93rem 10px 0px; |
|
||||||
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; |
|
||||||
` |
|
||||||
|
|
||||||
export const TournamentLogo = styled(ProfileLogo)` |
|
||||||
position: absolute; |
|
||||||
padding: 20px; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
object-fit: scale-down; |
|
||||||
` |
|
||||||
|
|
||||||
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; |
|
||||||
} |
|
||||||
` |
|
||||||
@ -1,42 +0,0 @@ |
|||||||
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 getTimezoneOffset = () => { |
|
||||||
const offset = new Date().getTimezoneOffset() |
|
||||||
if (offset === 0) return offset |
|
||||||
return -(offset) |
|
||||||
} |
|
||||||
const timeZoneOffset = getTimezoneOffset() |
|
||||||
|
|
||||||
const url = new URL(`${API_ROOT}/v1/broadcasts/timeline/${timeZoneOffset}`) |
|
||||||
|
|
||||||
if (sportId) { |
|
||||||
url.searchParams.append('sport_id', `${sportId}`) |
|
||||||
} |
|
||||||
|
|
||||||
const config = { |
|
||||||
method: 'GET', |
|
||||||
} |
|
||||||
|
|
||||||
return callApi({ |
|
||||||
config, |
|
||||||
url: url.href, |
|
||||||
}) |
|
||||||
} |
|
||||||