Compare commits
4 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9397ba64b4 | 2 years ago |
|
|
b1f82cd1b5 | 2 years ago |
|
|
d1eb9868e9 | 2 years ago |
|
|
9271894154 | 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: 7.4 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
@ -1,12 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<browserconfig> |
|
||||||
<msapplication> |
|
||||||
<tile> |
|
||||||
<square70x70logo src="/mstile-70x70.png"/> |
|
||||||
<square144x144logo src="/mstile-144x144.png"/> |
|
||||||
<square150x150logo src="/mstile-150x150.png"/> |
|
||||||
<square310x310logo src="/mstile-310x310.png"/> |
|
||||||
<TileColor>#da532c</TileColor> |
|
||||||
</tile> |
|
||||||
</msapplication> |
|
||||||
</browserconfig> |
|
||||||
|
Before Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 15 KiB |
@ -1,19 +0,0 @@ |
|||||||
{ |
|
||||||
"name": "", |
|
||||||
"short_name": "", |
|
||||||
"icons": [ |
|
||||||
{ |
|
||||||
"src": "/android-chrome-192x192.png", |
|
||||||
"sizes": "192x192", |
|
||||||
"type": "image/png" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"src": "/android-chrome-512x512.png", |
|
||||||
"sizes": "512x512", |
|
||||||
"type": "image/png" |
|
||||||
} |
|
||||||
], |
|
||||||
"theme_color": "#ffffff", |
|
||||||
"background_color": "#ffffff", |
|
||||||
"display": "standalone" |
|
||||||
} |
|
||||||
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 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 |
|
Before Width: | Height: | Size: 2.8 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() |
||||||
// обновляем рефреш токен в локалсторадже |
.catch((err) => { |
||||||
// так как safari не дает доступ к кукам |
console.error('OIDC: silent refresh callback error', err); |
||||||
.then(() => { |
}); |
||||||
const refreshToken = localStorage.getItem('refresh_token'); |
|
||||||
if (refreshToken) { |
|
||||||
localStorage.setItem('refresh_token', new URLSearchParams(document.location.search).get('refresh_token')); |
|
||||||
} |
|
||||||
}) |
|
||||||
.catch((err) => { |
|
||||||
console.error('OIDC: silent refresh callback error', err); |
|
||||||
}); |
|
||||||
</script> |
</script> |
||||||
</body> |
</body> |
||||||
|
</html> |
||||||
</html> |
|
||||||
|
|||||||
@ -1,8 +1,12 @@ |
|||||||
export const matchDownload = { |
export const matchDownload = { |
||||||
|
can_close: 20238, |
||||||
choose_what_to_download: 20193, |
choose_what_to_download: 20193, |
||||||
download_files_for_periods: 20197, |
download_files_for_periods: 20197, |
||||||
download_full_match: 20198, |
download_full_match: 20198, |
||||||
download_single_file: 20196, |
download_single_file: 20196, |
||||||
entire_record: 20195, |
entire_record: 20195, |
||||||
|
error_message: 20240, |
||||||
in_game_time_only: 20194, |
in_game_time_only: 20194, |
||||||
|
processed: 20200, |
||||||
|
will_notified: 20239, |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,191 @@ |
|||||||
|
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 { |
||||||
|
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()) { |
||||||
|
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,58 +0,0 @@ |
|||||||
import { atom, selector } from 'recoil' |
|
||||||
|
|
||||||
export interface RawLikeEvent { |
|
||||||
e: number, |
|
||||||
episode: number, |
|
||||||
likes: number, |
|
||||||
s: number, |
|
||||||
} |
|
||||||
|
|
||||||
export interface LikeEvent extends RawLikeEvent { |
|
||||||
iLiked: boolean, |
|
||||||
} |
|
||||||
|
|
||||||
export const myLikesState = atom<Array<RawLikeEvent>>({ |
|
||||||
default: [], |
|
||||||
key: 'myLikesState', |
|
||||||
}) |
|
||||||
|
|
||||||
export const totalLikesState = atom<Array<RawLikeEvent>>({ |
|
||||||
default: [], |
|
||||||
key: 'totalLikesState', |
|
||||||
}) |
|
||||||
|
|
||||||
const transformedLikesState = selector({ |
|
||||||
get: ({ get }) => { |
|
||||||
const totalLikes = get(totalLikesState) |
|
||||||
const myLikes = get(myLikesState) |
|
||||||
|
|
||||||
const likes: Array<LikeEvent> = totalLikes.map((like) => ({ |
|
||||||
...like, |
|
||||||
iLiked: myLikes.findIndex(({ episode }) => episode === like.episode) !== -1, |
|
||||||
})) |
|
||||||
|
|
||||||
return likes |
|
||||||
}, |
|
||||||
key: 'transformedLikesState', |
|
||||||
}) |
|
||||||
|
|
||||||
export const sortTypeState = atom<'asc' | 'desc'>({ |
|
||||||
default: 'asc', |
|
||||||
key: 'sortTypeState', |
|
||||||
}) |
|
||||||
|
|
||||||
export const filterTypeState = atom<'myLikes' | 'totalLikes'>({ |
|
||||||
default: 'totalLikes', |
|
||||||
key: 'filterTypeState', |
|
||||||
}) |
|
||||||
|
|
||||||
export const filteredLikesState = selector<Array<LikeEvent>>({ |
|
||||||
get: ({ get }) => { |
|
||||||
const likes = get(transformedLikesState) |
|
||||||
const filterType = get(filterTypeState) |
|
||||||
|
|
||||||
return filterType === 'totalLikes' ? likes : likes.filter(({ iLiked }) => iLiked) |
|
||||||
}, |
|
||||||
key: 'filteredLikesState', |
|
||||||
}) |
|
||||||
|
|
||||||
@ -1,75 +0,0 @@ |
|||||||
import { useEffect } from 'react' |
|
||||||
import { useSetRecoilState } from 'recoil' |
|
||||||
|
|
||||||
import { LIKES_API_URL } from 'config' |
|
||||||
|
|
||||||
import { useAuthStore } from 'features/AuthStore' |
|
||||||
import { FULL_MATCH_BOUNDARY } from 'features/MatchPage/components/LiveMatch/helpers' |
|
||||||
|
|
||||||
import { |
|
||||||
usePageParams, |
|
||||||
useVideoBounds, |
|
||||||
useWebSocket, |
|
||||||
} from 'hooks' |
|
||||||
|
|
||||||
import { |
|
||||||
myLikesState, |
|
||||||
totalLikesState, |
|
||||||
type RawLikeEvent, |
|
||||||
} from '../atoms' |
|
||||||
import { useMatchPageStore } from '..' |
|
||||||
|
|
||||||
interface Data { |
|
||||||
data: Array<RawLikeEvent>, |
|
||||||
type: 'user_likes' | 'total_likes', |
|
||||||
} |
|
||||||
|
|
||||||
export const useLikes = () => { |
|
||||||
const setTotalLikes = useSetRecoilState(totalLikesState) |
|
||||||
const setMyLikes = useSetRecoilState(myLikesState) |
|
||||||
|
|
||||||
const { playingProgress } = useMatchPageStore() |
|
||||||
|
|
||||||
const videoBounds = useVideoBounds() |
|
||||||
|
|
||||||
const { profileId, sportType } = usePageParams() |
|
||||||
const { user } = useAuthStore() |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
setTotalLikes([]) |
|
||||||
setMyLikes([]) |
|
||||||
}, [setMyLikes, setTotalLikes, profileId, sportType]) |
|
||||||
|
|
||||||
const url = `${LIKES_API_URL}?sport_id=${sportType}&match_id=${profileId}&access_token=${user?.access_token}` |
|
||||||
|
|
||||||
const { sendMessage, webSocketState } = useWebSocket<Data>({ |
|
||||||
allowConnection: Boolean(user), |
|
||||||
autoReconnect: true, |
|
||||||
handlers: { |
|
||||||
onMessage: ({ data = [], type }) => { |
|
||||||
type === 'total_likes' && setTotalLikes(data) |
|
||||||
type === 'user_likes' && setMyLikes(data) |
|
||||||
}, |
|
||||||
}, |
|
||||||
maxReconnectAttempts: 10, |
|
||||||
url, |
|
||||||
}) |
|
||||||
|
|
||||||
const likeClick = () => { |
|
||||||
const startSecond = Number(videoBounds?.find(({ h }) => h === FULL_MATCH_BOUNDARY)?.s || 0) |
|
||||||
|
|
||||||
const message = { |
|
||||||
data: { |
|
||||||
second: playingProgress + startSecond, |
|
||||||
}, |
|
||||||
event: 'like', |
|
||||||
} |
|
||||||
|
|
||||||
sendMessage(message) |
|
||||||
} |
|
||||||
|
|
||||||
return { |
|
||||||
canLike: user && webSocketState === WebSocket.OPEN, |
|
||||||
likeClick, |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,62 @@ |
|||||||
|
import { useQueryClient } from 'react-query' |
||||||
|
|
||||||
|
import { T9n } from 'features/T9n' |
||||||
|
|
||||||
|
import { querieKeys } from 'config' |
||||||
|
|
||||||
|
import { DownloadResponse } from 'requests/downloadPlaylist' |
||||||
|
|
||||||
|
import { |
||||||
|
Container, |
||||||
|
Description, |
||||||
|
Title, |
||||||
|
CloseBtn, |
||||||
|
Header, |
||||||
|
MainContainer, |
||||||
|
Error, |
||||||
|
} from './styled' |
||||||
|
|
||||||
|
type TDownloadNotification = { |
||||||
|
close: () => void, |
||||||
|
} |
||||||
|
|
||||||
|
export const DownloadNotification = ({ close }:TDownloadNotification) => { |
||||||
|
const client = useQueryClient() |
||||||
|
|
||||||
|
const data = client.getQueryData<DownloadResponse>(querieKeys.downloadPlaylist) |
||||||
|
|
||||||
|
if (!data) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Container> |
||||||
|
<Header> |
||||||
|
<CloseBtn |
||||||
|
onClick={close} |
||||||
|
/> |
||||||
|
</Header> |
||||||
|
<MainContainer> |
||||||
|
<Title> |
||||||
|
<T9n t='processed' /> |
||||||
|
</Title> |
||||||
|
<Description> |
||||||
|
{data?.status === 'ERROR' |
||||||
|
? ( |
||||||
|
<Error> |
||||||
|
<T9n t='error_message' /> |
||||||
|
</Error> |
||||||
|
) : ( |
||||||
|
<> |
||||||
|
<T9n t='can_close' /> |
||||||
|
<T9n t='will_notified' /> |
||||||
|
{/* <LinkToDownload href='/useraccount/downloads'> */} |
||||||
|
{/* My Videos */} |
||||||
|
{/* </LinkToDownload> */} |
||||||
|
</> |
||||||
|
)} |
||||||
|
</Description> |
||||||
|
</MainContainer> |
||||||
|
</Container> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
import styled from 'styled-components/macro' |
||||||
|
|
||||||
|
import { CloseButton } from 'features/PopupComponents' |
||||||
|
|
||||||
|
export const Container = styled.section` |
||||||
|
width: 100%; |
||||||
|
border-radius: 0.125rem; |
||||||
|
background: #333; |
||||||
|
padding: 0.5rem 0.5rem ; |
||||||
|
color: #FFF; |
||||||
|
font-weight: 400; |
||||||
|
line-height: normal; |
||||||
|
font-size: 0.75rem; |
||||||
|
margin-bottom: 0.45rem; |
||||||
|
` |
||||||
|
|
||||||
|
export const Title = styled.h3` |
||||||
|
font-size: 0.875rem; |
||||||
|
font-style: normal; |
||||||
|
font-weight: 700; |
||||||
|
margin-bottom: 0.56rem; |
||||||
|
` |
||||||
|
|
||||||
|
export const Description = styled.span` |
||||||
|
` |
||||||
|
|
||||||
|
export const CloseBtn = styled(CloseButton)` |
||||||
|
padding: 4px; |
||||||
|
` |
||||||
|
|
||||||
|
export const Header = styled.header` |
||||||
|
display: flex; |
||||||
|
justify-content: end; |
||||||
|
` |
||||||
|
|
||||||
|
export const MainContainer = styled.main` |
||||||
|
padding: 0.4rem 1.1rem 2rem 1.3rem; |
||||||
|
` |
||||||
|
|
||||||
|
export const LinkToDownload = styled.a` |
||||||
|
text-decoration: underline; |
||||||
|
cursor: pointer; |
||||||
|
color: white; |
||||||
|
font-weight: 600; |
||||||
|
` |
||||||
|
export const Error = styled.span` |
||||||
|
color: red; |
||||||
|
` |
||||||
@ -1,126 +0,0 @@ |
|||||||
import { Fragment } from 'react' |
|
||||||
|
|
||||||
import { useVideoBounds } from 'hooks' |
|
||||||
|
|
||||||
import type { LikeEvent as LikeEventType } from 'features/MatchPage/store/atoms' |
|
||||||
import { T9n } from 'features/T9n' |
|
||||||
import { useMatchPageStore } from 'features/MatchPage/store' |
|
||||||
import { Tabs } from 'features/MatchSidePlaylists/config' |
|
||||||
import { FULL_MATCH_BOUNDARY } from 'features/MatchPage/components/LiveMatch/helpers' |
|
||||||
import { PlaylistTypes } from 'features/MatchPage/types' |
|
||||||
|
|
||||||
import { |
|
||||||
Title, |
|
||||||
LikeIcon, |
|
||||||
Time, |
|
||||||
Button, |
|
||||||
LikesCount, |
|
||||||
} from './styled' |
|
||||||
|
|
||||||
type Props = { |
|
||||||
groupTitle: string, |
|
||||||
likeEvent: LikeEventType, |
|
||||||
} |
|
||||||
|
|
||||||
const groupTitlesMap: Record<string, string> = { |
|
||||||
full_time: 'ft', |
|
||||||
half_time: 'ht', |
|
||||||
pre_match: 'pm', |
|
||||||
} |
|
||||||
|
|
||||||
export const LikeEvent = ({ |
|
||||||
groupTitle, |
|
||||||
likeEvent, |
|
||||||
}: Props) => { |
|
||||||
const videoBounds = useVideoBounds() |
|
||||||
const { handlePlaylistClick, selectedPlaylist } = useMatchPageStore() |
|
||||||
|
|
||||||
const firstBound = videoBounds?.find(({ h }) => h === FULL_MATCH_BOUNDARY) |
|
||||||
|
|
||||||
const half = groupTitle.match(/\d/) |
|
||||||
|
|
||||||
const startSecond = half |
|
||||||
? likeEvent.s - Number(videoBounds?.find(({ h }) => h === half[0])?.s || 0) |
|
||||||
: 0 |
|
||||||
const startMinute = Math.ceil(startSecond / 60) |
|
||||||
|
|
||||||
const active = selectedPlaylist.id === likeEvent.episode && selectedPlaylist.tab === Tabs.LIKES |
|
||||||
|
|
||||||
const handleClick = () => { |
|
||||||
handlePlaylistClick({ |
|
||||||
playlist: { |
|
||||||
episodes: [{ |
|
||||||
c: Math.ceil((likeEvent.e - likeEvent.s) / 12), |
|
||||||
e: likeEvent.e - Number(firstBound?.s || 0), |
|
||||||
h: 0, |
|
||||||
s: likeEvent.s - Number(firstBound?.s || 0), |
|
||||||
}], |
|
||||||
id: likeEvent.episode, |
|
||||||
type: PlaylistTypes.EVENT, |
|
||||||
}, |
|
||||||
tab: Tabs.LIKES, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
const getTitle = () => { |
|
||||||
switch (true) { |
|
||||||
case likeEvent.iLiked && likeEvent.likes === 1: |
|
||||||
return ( |
|
||||||
<T9n t='you_liked_this' /> |
|
||||||
) |
|
||||||
|
|
||||||
case likeEvent.iLiked && likeEvent.likes === 2: |
|
||||||
return ( |
|
||||||
<Fragment> |
|
||||||
<T9n t='you_and' /> <LikesCount>1</LikesCount> <T9n t='user_liked_this' /> |
|
||||||
</Fragment> |
|
||||||
) |
|
||||||
|
|
||||||
case likeEvent.iLiked && likeEvent.likes > 2: |
|
||||||
return ( |
|
||||||
<Fragment> |
|
||||||
<T9n t='you_and' /> <LikesCount>{likeEvent.likes - 1}</LikesCount> <T9n t='users_liked_this' /> |
|
||||||
</Fragment> |
|
||||||
) |
|
||||||
|
|
||||||
case !likeEvent.iLiked && likeEvent.likes === 1: |
|
||||||
return ( |
|
||||||
<Fragment> |
|
||||||
<LikesCount>1</LikesCount> <T9n t='user_liked_this' /> |
|
||||||
</Fragment> |
|
||||||
) |
|
||||||
|
|
||||||
case !likeEvent.iLiked && likeEvent.likes > 1: |
|
||||||
return ( |
|
||||||
<Fragment> |
|
||||||
<LikesCount>{likeEvent.likes}</LikesCount> <T9n t='users_liked_this' /> |
|
||||||
</Fragment> |
|
||||||
) |
|
||||||
|
|
||||||
default: return null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return ( |
|
||||||
<Button |
|
||||||
onClick={handleClick} |
|
||||||
active={active} |
|
||||||
groupTitle={groupTitle} |
|
||||||
> |
|
||||||
{likeEvent.iLiked && ( |
|
||||||
<LikeIcon |
|
||||||
alt='Like' |
|
||||||
src='/images/like-active-icon.svg' |
|
||||||
/> |
|
||||||
)} |
|
||||||
{groupTitle && ( |
|
||||||
<Fragment> |
|
||||||
{groupTitle in groupTitlesMap |
|
||||||
? <Time><T9n t={groupTitlesMap[groupTitle]} /></Time> |
|
||||||
: <Time>{startMinute}'</Time>} |
|
||||||
</Fragment> |
|
||||||
)} |
|
||||||
<Title>{getTitle()}</Title> |
|
||||||
</Button> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,35 +0,0 @@ |
|||||||
import styled from 'styled-components/macro' |
|
||||||
|
|
||||||
import { Button as ButtonBase } from 'features/MatchSidePlaylists/styled' |
|
||||||
|
|
||||||
type ButtonProps = { |
|
||||||
groupTitle: string, |
|
||||||
} |
|
||||||
|
|
||||||
export const Button = styled(ButtonBase)<ButtonProps>` |
|
||||||
justify-content: initial; |
|
||||||
padding-left: ${({ groupTitle }) => (groupTitle ? 77 : 42)}px; |
|
||||||
` |
|
||||||
|
|
||||||
export const Title = styled.div` |
|
||||||
font-size: 12px; |
|
||||||
color: ${({ theme }) => theme.colors.white}; |
|
||||||
` |
|
||||||
|
|
||||||
export const LikeIcon = styled.img` |
|
||||||
position: absolute; |
|
||||||
left: 20px; |
|
||||||
width: 11px; |
|
||||||
height: 11px; |
|
||||||
` |
|
||||||
|
|
||||||
export const Time = styled.span` |
|
||||||
position: absolute; |
|
||||||
right: calc(100% - 67px); |
|
||||||
font-size: 12px; |
|
||||||
color: ${({ theme }) => theme.colors.white}; |
|
||||||
` |
|
||||||
|
|
||||||
export const LikesCount = styled.span` |
|
||||||
font-weight: 600; |
|
||||||
` |
|
||||||
@ -1,45 +0,0 @@ |
|||||||
import { T9n } from 'features/T9n' |
|
||||||
import type { LikeEvent as LikeEventType } from 'features/MatchPage/store' |
|
||||||
|
|
||||||
import { |
|
||||||
BlockTitle, |
|
||||||
Event, |
|
||||||
TextEvent, |
|
||||||
List, |
|
||||||
} from '../TabEvents/styled' |
|
||||||
import { LikeEvent } from '../LikeEvent' |
|
||||||
|
|
||||||
type Props = { |
|
||||||
groupTitle: string, |
|
||||||
likeEvents: Array<LikeEventType>, |
|
||||||
} |
|
||||||
|
|
||||||
export const LikesList = ({ |
|
||||||
groupTitle, |
|
||||||
likeEvents, |
|
||||||
}: Props) => { |
|
||||||
if (!likeEvents.length) return null |
|
||||||
|
|
||||||
const title = groupTitle.startsWith('half') ? 'half_time' : groupTitle |
|
||||||
|
|
||||||
return ( |
|
||||||
<List> |
|
||||||
{title && ( |
|
||||||
<TextEvent> |
|
||||||
<BlockTitle> |
|
||||||
<T9n t={title} /> |
|
||||||
</BlockTitle> |
|
||||||
</TextEvent> |
|
||||||
)} |
|
||||||
|
|
||||||
{likeEvents.map((likeEvent) => ( |
|
||||||
<Event key={likeEvent.episode}> |
|
||||||
<LikeEvent |
|
||||||
likeEvent={likeEvent} |
|
||||||
groupTitle={title} |
|
||||||
/> |
|
||||||
</Event> |
|
||||||
))} |
|
||||||
</List> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,235 +0,0 @@ |
|||||||
/* eslint-disable sort-keys */ |
|
||||||
import { useEffect } from 'react' |
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil' |
|
||||||
|
|
||||||
import findKey from 'lodash/findKey' |
|
||||||
import orderBy from 'lodash/orderBy' |
|
||||||
import isNumber from 'lodash/isNumber' |
|
||||||
|
|
||||||
import { useVideoBounds } from 'hooks' |
|
||||||
|
|
||||||
import type { MatchInfo } from 'requests' |
|
||||||
import { T9n } from 'features/T9n' |
|
||||||
import { |
|
||||||
filterTypeState, |
|
||||||
filteredLikesState, |
|
||||||
sortTypeState, |
|
||||||
type LikeEvent, |
|
||||||
useMatchPageStore, |
|
||||||
} from 'features/MatchPage/store' |
|
||||||
import { Tabs } from 'features/MatchSidePlaylists/config' |
|
||||||
|
|
||||||
import { LikesList } from '../LikesList' |
|
||||||
import { |
|
||||||
Wrapper, |
|
||||||
Tabs as SortTabs, |
|
||||||
Tab as SortTab, |
|
||||||
ButtonsBlock, |
|
||||||
HalfEvents, |
|
||||||
HalfList, |
|
||||||
} from '../TabEvents/styled' |
|
||||||
import { |
|
||||||
Tab, |
|
||||||
TabList, |
|
||||||
TabTitle, |
|
||||||
} from './styled' |
|
||||||
|
|
||||||
type Props = { |
|
||||||
profile?: MatchInfo, |
|
||||||
} |
|
||||||
|
|
||||||
const groupTitles = [ |
|
||||||
'pre_match', |
|
||||||
'1st_half', |
|
||||||
'half1-2', |
|
||||||
'half1-3', |
|
||||||
'half1-4', |
|
||||||
'half1-5', |
|
||||||
'2nd_half', |
|
||||||
'half2-3', |
|
||||||
'half2-4', |
|
||||||
'half2-5', |
|
||||||
'3rd_half', |
|
||||||
'half3-4', |
|
||||||
'half3-5', |
|
||||||
'4th_half', |
|
||||||
'half4-5', |
|
||||||
'5th_half', |
|
||||||
'full_time', |
|
||||||
] |
|
||||||
|
|
||||||
export const TabLikes = ({ profile: matchProfile }:Props) => { |
|
||||||
const [filterType, setFilterType] = useRecoilState(filterTypeState) |
|
||||||
const [sortType, setSortType] = useRecoilState(sortTypeState) |
|
||||||
const likeEvents = useRecoilValue(filteredLikesState) |
|
||||||
|
|
||||||
const { selectedPlaylist, setEpisodeInfo } = useMatchPageStore() |
|
||||||
|
|
||||||
const videoBounds = useVideoBounds() |
|
||||||
|
|
||||||
const needUseGroups = videoBounds && videoBounds.findIndex(({ h }) => h === '1') !== -1 |
|
||||||
|
|
||||||
const getHalfInterval = (period: number) => { |
|
||||||
const bound = videoBounds?.find(({ h }) => h === String(period)) |
|
||||||
|
|
||||||
return [bound?.s && Number(bound.s), bound?.e && Number(bound.e)] |
|
||||||
} |
|
||||||
|
|
||||||
const getTimeoutInterval = (firstPeriod: number, secondPeriod: number) => { |
|
||||||
const firstBound = videoBounds?.find(({ h }) => h === String(firstPeriod)) |
|
||||||
const secondBound = videoBounds?.find(({ h }) => h === String(secondPeriod)) |
|
||||||
|
|
||||||
return [firstBound?.e && Number(firstBound.e) + 1, secondBound?.s && Number(secondBound.s) - 1] |
|
||||||
} |
|
||||||
|
|
||||||
const getGroupedLikes = () => { |
|
||||||
if (!needUseGroups) return {} |
|
||||||
|
|
||||||
const firstHalf = videoBounds?.find(({ h }) => h === '1') |
|
||||||
const lastHalf = videoBounds?.[videoBounds.length - 1] |
|
||||||
|
|
||||||
const groupedLikes: Record<string, Array<LikeEvent>> = { |
|
||||||
pre_match: [], |
|
||||||
'1st_half': [], |
|
||||||
'half1-2': [], |
|
||||||
'2nd_half': [], |
|
||||||
'half2-3': [], |
|
||||||
'3rd_half': [], |
|
||||||
'half3-4': [], |
|
||||||
'4th_half': [], |
|
||||||
'half4-5': [], |
|
||||||
'5th_half': [], |
|
||||||
full_time: [], |
|
||||||
'half1-3': [], |
|
||||||
'half1-4': [], |
|
||||||
'half1-5': [], |
|
||||||
'half2-4': [], |
|
||||||
'half2-5': [], |
|
||||||
'half3-5': [], |
|
||||||
} |
|
||||||
|
|
||||||
const intervals: Record<string, Array<number | undefined | string>> = { |
|
||||||
pre_match: [0, Number(firstHalf?.s || 0) - 1], |
|
||||||
'1st_half': getHalfInterval(1), |
|
||||||
'half1-2': getTimeoutInterval(1, 2), |
|
||||||
'2nd_half': getHalfInterval(2), |
|
||||||
'half2-3': getTimeoutInterval(2, 3), |
|
||||||
'3rd_half': getHalfInterval(3), |
|
||||||
'half3-4': getTimeoutInterval(3, 4), |
|
||||||
'4th_half': getHalfInterval(4), |
|
||||||
'half4-5': getTimeoutInterval(4, 5), |
|
||||||
'5th_half': getHalfInterval(5), |
|
||||||
full_time: [Number(lastHalf?.e || 0) + 1, 1e5], |
|
||||||
'half3-5': getTimeoutInterval(3, 5), |
|
||||||
'half2-4': getTimeoutInterval(2, 4), |
|
||||||
'half2-5': getTimeoutInterval(2, 5), |
|
||||||
'half1-3': getTimeoutInterval(1, 3), |
|
||||||
'half1-4': getTimeoutInterval(1, 4), |
|
||||||
'half1-5': getTimeoutInterval(1, 5), |
|
||||||
} |
|
||||||
|
|
||||||
likeEvents.forEach((likeEvent) => { |
|
||||||
const half = findKey(intervals, (interval) => { |
|
||||||
if (isNumber(interval[0]) && isNumber(interval[1])) { |
|
||||||
return likeEvent.s >= interval[0] && likeEvent.s <= interval[1] |
|
||||||
} |
|
||||||
|
|
||||||
if (isNumber(interval[0]) |
|
||||||
&& !interval[1] |
|
||||||
&& likeEvent.s >= interval[0] |
|
||||||
&& matchProfile?.live |
|
||||||
) return true |
|
||||||
|
|
||||||
return false |
|
||||||
}) |
|
||||||
|
|
||||||
half && groupedLikes[half].push(likeEvent) |
|
||||||
}) |
|
||||||
|
|
||||||
Object.keys(groupedLikes).forEach((key) => { |
|
||||||
groupedLikes[key] = orderBy( |
|
||||||
groupedLikes[key], |
|
||||||
's', |
|
||||||
sortType, |
|
||||||
) |
|
||||||
}) |
|
||||||
|
|
||||||
return groupedLikes |
|
||||||
} |
|
||||||
|
|
||||||
const groupedLikes = getGroupedLikes() |
|
||||||
|
|
||||||
const likesEntries = needUseGroups |
|
||||||
? orderBy( |
|
||||||
Object.entries(groupedLikes), |
|
||||||
([title]) => groupTitles.findIndex((key) => key === title), |
|
||||||
sortType, |
|
||||||
) |
|
||||||
: [] |
|
||||||
|
|
||||||
const sortedLikes = needUseGroups ? [] : orderBy( |
|
||||||
likeEvents, |
|
||||||
's', |
|
||||||
sortType, |
|
||||||
) |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
selectedPlaylist.tab === Tabs.LIKES && setEpisodeInfo({}) |
|
||||||
}, [setEpisodeInfo, selectedPlaylist.tab]) |
|
||||||
|
|
||||||
return ( |
|
||||||
<Wrapper> |
|
||||||
<ButtonsBlock> |
|
||||||
<SortTabs> |
|
||||||
<T9n t={sortType === 'asc' ? 'from_start_match' : 'from_end_match'} /> |
|
||||||
<SortTab |
|
||||||
active={sortType === 'asc'} |
|
||||||
onClick={() => setSortType('asc')} |
|
||||||
id='match_likes_sort_start' |
|
||||||
/> |
|
||||||
<SortTab |
|
||||||
active={sortType === 'desc'} |
|
||||||
onClick={() => setSortType('desc')} |
|
||||||
id='match_likes_sort_final' |
|
||||||
/> |
|
||||||
</SortTabs> |
|
||||||
<TabList> |
|
||||||
<Tab |
|
||||||
aria-pressed={filterType === 'totalLikes'} |
|
||||||
onClick={() => setFilterType('totalLikes')} |
|
||||||
> |
|
||||||
<TabTitle t='total_likes' /> |
|
||||||
</Tab> |
|
||||||
<Tab |
|
||||||
aria-pressed={filterType === 'myLikes'} |
|
||||||
onClick={() => setFilterType('myLikes')} |
|
||||||
> |
|
||||||
<TabTitle t='my_likes' /> |
|
||||||
</Tab> |
|
||||||
</TabList> |
|
||||||
</ButtonsBlock> |
|
||||||
{needUseGroups ? ( |
|
||||||
<HalfList> |
|
||||||
{likesEntries.map(([groupTitle, likesInGroup]) => ( |
|
||||||
<HalfEvents key={groupTitle}> |
|
||||||
<LikesList |
|
||||||
groupTitle={groupTitle} |
|
||||||
likeEvents={likesInGroup} |
|
||||||
/> |
|
||||||
</HalfEvents> |
|
||||||
))} |
|
||||||
</HalfList> |
|
||||||
) |
|
||||||
: ( |
|
||||||
<HalfList> |
|
||||||
<HalfEvents> |
|
||||||
<LikesList |
|
||||||
groupTitle='' |
|
||||||
likeEvents={sortedLikes} |
|
||||||
/> |
|
||||||
</HalfEvents> |
|
||||||
</HalfList> |
|
||||||
)} |
|
||||||
</Wrapper> |
|
||||||
) |
|
||||||
} |
|
||||||
@ -1,40 +0,0 @@ |
|||||||
import styled, { css } from 'styled-components/macro' |
|
||||||
|
|
||||||
import { isMobileDevice } from 'config' |
|
||||||
|
|
||||||
import { T9n } from 'features/T9n' |
|
||||||
|
|
||||||
export const TabList = styled.div.attrs({ role: 'tablist' })` |
|
||||||
display: flex; |
|
||||||
gap: 12px; |
|
||||||
|
|
||||||
${isMobileDevice && css` |
|
||||||
padding-right: 33px; |
|
||||||
`}
|
|
||||||
` |
|
||||||
|
|
||||||
export const TabTitle = styled(T9n)` |
|
||||||
position: relative; |
|
||||||
color: rgba(255, 255, 255, 0.5); |
|
||||||
` |
|
||||||
|
|
||||||
export const Tab = styled.button.attrs({ role: 'tab' })` |
|
||||||
position: relative; |
|
||||||
display: flex; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
padding: 0 0 5px; |
|
||||||
font-size: 12px; |
|
||||||
cursor: pointer; |
|
||||||
border: none; |
|
||||||
background: none; |
|
||||||
border-bottom: 2px solid transparent; |
|
||||||
|
|
||||||
&[aria-pressed="true"] { |
|
||||||
border-color: ${({ theme }) => theme.colors.white}; |
|
||||||
|
|
||||||
${TabTitle} { |
|
||||||
color: ${({ theme }) => theme.colors.white}; |
|
||||||
} |
|
||||||
} |
|
||||||
` |
|
||||||