diff --git a/public/images/back-icon.svg b/public/images/back-icon.svg
new file mode 100644
index 00000000..a5e9fc9b
--- /dev/null
+++ b/public/images/back-icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/checkboxChecked.svg b/public/images/checkboxChecked.svg
deleted file mode 100644
index bcf8ad02..00000000
--- a/public/images/checkboxChecked.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
diff --git a/public/images/checkboxUnchecked.svg b/public/images/checkboxUnchecked.svg
deleted file mode 100644
index e69c317e..00000000
--- a/public/images/checkboxUnchecked.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/public/images/preview2.png b/public/images/preview2.png
new file mode 100644
index 00000000..a3fbe286
Binary files /dev/null and b/public/images/preview2.png differ
diff --git a/public/images/radioChecked.svg b/public/images/radioChecked.svg
deleted file mode 100644
index 27cf636c..00000000
--- a/public/images/radioChecked.svg
+++ /dev/null
@@ -1,34 +0,0 @@
-
diff --git a/public/images/radioUnchecked.svg b/public/images/radioUnchecked.svg
deleted file mode 100644
index e14a682f..00000000
--- a/public/images/radioUnchecked.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/public/images/settings.svg b/public/images/settings.svg
new file mode 100644
index 00000000..e04a2b84
--- /dev/null
+++ b/public/images/settings.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx
index d22be254..0240c147 100644
--- a/src/config/lexics/indexLexics.tsx
+++ b/src/config/lexics/indexLexics.tsx
@@ -1,5 +1,25 @@
import { proceduresLexics } from './procedures'
+const matchPopupLexics = {
+ apply: 13491,
+ episode_duration: 13410,
+ go_back_to_match: 13405,
+ match_interviews: 13031,
+ match_playlist_ball_in_play: 2489,
+ match_playlist_full_game: 13028,
+ match_playlist_goals: 3559,
+ match_playlist_highlights: 13033,
+ match_settings: 13490,
+ playlist_format: 13406,
+ playlist_format_all_actions: 13408,
+ playlist_format_all_match_time: 13407,
+ playlist_format_selected_acions: 13409,
+ sec_after: 13412,
+ sec_before: 13411,
+ selected_player_actions: 13413,
+ team_players: 13398,
+}
+
export const indexLexics = {
add_to_favorites: 1701,
add_to_favorites_error: 12943,
@@ -49,4 +69,5 @@ export const indexLexics = {
watch_now: 13020,
...proceduresLexics,
+ ...matchPopupLexics,
}
diff --git a/src/config/procedures.tsx b/src/config/procedures.tsx
index f152ea96..df44e040 100644
--- a/src/config/procedures.tsx
+++ b/src/config/procedures.tsx
@@ -20,6 +20,8 @@ export const PROCEDURES = {
get_user_subscriptions: 'get_user_subscriptions',
logout_user: 'logout_user',
lst_c_country: 'lst_c_country',
+ ott_match_popup: 'ott_match_popup',
+ ott_match_popup_actions: 'ott_match_popup_actions',
param_lexical: 'param_lexical',
save_user_custom_subscription: 'save_user_custom_subscription',
save_user_favorite: 'save_user_favorite',
diff --git a/src/features/App/AuthenticatedApp.tsx b/src/features/App/AuthenticatedApp.tsx
index e9edf544..e803a398 100644
--- a/src/features/App/AuthenticatedApp.tsx
+++ b/src/features/App/AuthenticatedApp.tsx
@@ -19,6 +19,7 @@ import { UserAccountForm } from 'features/UserAccount'
import { MatchSwitchesStore } from 'features/MatchSwitches'
import { UserFavoritesStore } from 'features/UserFavorites/store'
+import { MatchPopupStore } from 'features/MatchPopup'
export const AuthenticatedApp = () => {
useLexicsConfig(indexLexics)
@@ -27,31 +28,34 @@ export const AuthenticatedApp = () => {
- {/* в Switch как прямой children можно рендерить только Route или Redirect */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {/* в Switch как прямой children можно рендерить только Route или Redirect */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/features/Common/Button/styled.tsx b/src/features/Common/Button/styled.tsx
index 9cfa7f83..b7543b77 100644
--- a/src/features/Common/Button/styled.tsx
+++ b/src/features/Common/Button/styled.tsx
@@ -35,6 +35,10 @@ export const solidButtonStyles = css`
border-color: transparent;
background: ${({ theme: { colors } }) => colors.primary};
+ /* TODO: удалить медиа запросы из стайледа,
+ добавить специфичные юз кейсу правила
+ через враппер компоненты или др способом
+ */
@media ${devices.mobile} {
width: 335px;
height: 40px;
diff --git a/src/features/Common/Checkbox/Icon.tsx b/src/features/Common/Checkbox/Icon.tsx
new file mode 100644
index 00000000..40632c80
--- /dev/null
+++ b/src/features/Common/Checkbox/Icon.tsx
@@ -0,0 +1,53 @@
+import styled, { css } from 'styled-components/macro'
+
+import { devices } from 'config'
+
+type SvgColorStylesProps = {
+ checked?: boolean,
+}
+
+export const svgColorStyles = css`
+ fill: ${({ checked }) => (checked ? '#ffffff' : '#B8C1CC')};
+
+ @media ${devices.mobile} {
+ fill: ${({ checked }) => (checked ? '#294FC4' : '#B8C1CC')}
+ }
+`
+
+export const CheckboxSvg = styled.svg`
+ margin-right: 22px;
+
+ ${svgColorStyles}
+`
+
+type Props = {
+ checked?: boolean,
+}
+
+export const Icon = ({ checked }: Props) => {
+ const id = checked ? '#checkbox-checked' : '#checkbox-unchecked'
+ return (
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/features/Common/Checkbox/index.tsx b/src/features/Common/Checkbox/index.tsx
index f4d1324d..9b41bd03 100644
--- a/src/features/Common/Checkbox/index.tsx
+++ b/src/features/Common/Checkbox/index.tsx
@@ -1,5 +1,9 @@
import { InputHTMLAttributes } from 'react'
+import type { LexicsId } from 'features/LexicsStore/types'
+import { T9n } from 'features/T9n'
+
+import { Icon } from './Icon'
import {
Wrapper,
Input,
@@ -8,28 +12,37 @@ import {
type Props = Pick, (
| 'checked'
+ | 'className'
| 'id'
| 'name'
| 'onClick'
+ | 'onChange'
)> & {
label?: string,
+ labelLexic?: LexicsId,
}
export const Checkbox = ({
checked,
+ className,
id,
label,
+ labelLexic,
name,
+ onChange,
onClick,
}: Props) => (
-
-
-
+
+
)
diff --git a/src/features/Common/Checkbox/styled.tsx b/src/features/Common/Checkbox/styled.tsx
index b58f3d0b..ae0f8266 100644
--- a/src/features/Common/Checkbox/styled.tsx
+++ b/src/features/Common/Checkbox/styled.tsx
@@ -1,62 +1,27 @@
import styled from 'styled-components/macro'
-import { devices } from 'config/devices'
-
-export const Wrapper = styled.div`
- @media ${devices.tablet} {
- position: absolute;
- left: 0;
- top: 0;
- width: 162px;
- height: 100px;
- }
-`
+export const Wrapper = styled.span.attrs(() => ({
+ role: 'checkbox',
+ tabIndex: 0,
+}))``
export const Label = styled.label`
display: flex;
+ align-items: center;
color: ${({ theme: { colors } }) => colors.text};
font-style: normal;
font-weight: bold;
font-size: 18px;
line-height: 21px;
+ cursor: pointer;
`
-export const Input = styled.input`
+export const Input = styled.input.attrs(() => ({
+ 'aria-hidden': true,
+ tabIndex: -1,
+ type: 'checkbox',
+}))`
position: absolute;
z-index: -1;
opacity: 0;
-
- &+${Label} {
- display: inline-flex;
- align-items: center;
- user-select: none;
- }
-
- &+${Label}::before {
- content: '';
- display: inline-block;
- width: 24px;
- height: 24px;
- margin-right: 22px;
- background-repeat: no-repeat;
- background-position: center center;
- background-image: url(/images/checkboxUnchecked.svg);
- cursor: pointer;
- }
-
- &:checked+${Label}::before {
- background-image: url(/images/checkboxChecked.svg);
- }
-
- @media ${devices.tablet} {
- &+${Label}::before {
- width: 288px;
- height: 100px;
- background-image: none;
- }
- &:checked+${Label}::before {
- background-image: none;
- }
-
- }
`
diff --git a/src/features/Common/CloseButton/index.tsx b/src/features/Common/CloseButton/index.tsx
deleted file mode 100644
index d4cb9d3e..00000000
--- a/src/features/Common/CloseButton/index.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import styled from 'styled-components/macro'
-
-export const CloseButton = styled.button.attrs({
- 'aria-label': 'Close',
-})`
- position: absolute;
- top: 0;
- right: 0;
- width: 10px;
- height: 10px;
- cursor: pointer;
- border-style: none;
- outline: none;
- background: none;
- background-image: url(/images/closeIcon.svg);
- background-repeat: no-repeat;
- background-size: contain;
-`
diff --git a/src/features/Common/Radio/Icon.tsx b/src/features/Common/Radio/Icon.tsx
new file mode 100644
index 00000000..f2af3365
--- /dev/null
+++ b/src/features/Common/Radio/Icon.tsx
@@ -0,0 +1,39 @@
+import styled from 'styled-components/macro'
+
+import { svgColorStyles } from '../Checkbox/Icon'
+
+export const RadioSvg = styled.svg`
+ margin-right: 22px;
+
+ ${svgColorStyles}
+`
+
+type Props = {
+ checked?: boolean,
+}
+
+export const Icon = ({ checked }: Props) => {
+ const id = checked ? '#radio-checked' : '#radio-unchecked'
+ return (
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/features/Common/Radio/index.tsx b/src/features/Common/Radio/index.tsx
index b7d91f8f..c34f028e 100644
--- a/src/features/Common/Radio/index.tsx
+++ b/src/features/Common/Radio/index.tsx
@@ -1,8 +1,8 @@
import { InputHTMLAttributes } from 'react'
-import { useRouteMatch } from 'react-router-dom'
-import { PAGES } from 'config'
+import { T9n } from 'features/T9n'
+import { Icon } from './Icon'
import {
Wrapper,
Input,
@@ -11,35 +11,37 @@ import {
type Props = Pick, (
| 'checked'
+ | 'className'
| 'id'
| 'name'
+ | 'onChange'
| 'onClick'
)> & {
label?: string,
+ labelLexic?: string,
}
export const Radio = ({
checked,
+ className,
id,
label = '',
+ labelLexic,
name,
+ onChange,
onClick,
-}: Props) => {
- const isUserAccountPage = useRouteMatch(PAGES.useraccount)?.isExact || false
-
- return (
-
+}: Props) => (
+
+
- )
-}
+
+ {labelLexic ? : label}
+
+
+)
diff --git a/src/features/Common/Radio/styled.tsx b/src/features/Common/Radio/styled.tsx
index 0b6e23f2..beb17351 100644
--- a/src/features/Common/Radio/styled.tsx
+++ b/src/features/Common/Radio/styled.tsx
@@ -1,81 +1,27 @@
-import styled, { css } from 'styled-components/macro'
+import styled from 'styled-components/macro'
-import { devices } from 'config/devices'
-
-type WrapperProps = {
- isUserAccountPage?: boolean,
-}
-
-export const Wrapper = styled.div`
-
- @media ${devices.tablet} {
- ${({ isUserAccountPage }) => (!isUserAccountPage
- ? css`
- position: absolute;
- left: 0;
- top: 0;
- width: 163px;
- height: 100px;
- border-radius: 10px;
- `
- : '')}
- }
-`
+export const Wrapper = styled.span.attrs(() => ({
+ role: 'radio',
+ tabIndex: 0,
+}))``
export const Label = styled.label`
+ display: flex;
+ align-items: center;
color: ${({ theme: { colors } }) => colors.text};
font-style: normal;
font-weight: bold;
font-size: 18px;
line-height: 21px;
+ cursor: pointer;
`
-type InputProps = {
- isUserAccountPage?: boolean,
-}
-export const Input = styled.input`
+export const Input = styled.input.attrs(() => ({
+ 'aria-hidden': true,
+ tabIndex: -1,
+ type: 'radio',
+}))`
position: absolute;
z-index: -1;
opacity: 0;
-
- &+${Label} {
- display: inline-flex;
- align-items: center;
- user-select: none;
- }
-
- &+${Label}::before {
- content: '';
- display: inline-block;
- width: 26px;
- height: 26px;
- margin-right: 22px;
- background-repeat: no-repeat;
- background-position: center center;
- background-image: url(/images/radioUnchecked.svg);
- cursor: pointer;
- }
-
- &:checked+${Label}::before {
- background-image: url(/images/radioChecked.svg);
- }
-
- @media ${devices.tablet} {
- ${({ isUserAccountPage }) => (!isUserAccountPage
- ? css`
- &+${Label}::before {
- width: 163px;
- height: 100px;
- border-radius: 10px;
- margin-right: 0;
- background-image: none;
- }
-
- &:checked+${Label}::before {
- background-image: none;
- }
- `
- : '')}
- }
-
`
diff --git a/src/features/Common/index.tsx b/src/features/Common/index.tsx
index 8e64ad19..a88b337d 100644
--- a/src/features/Common/index.tsx
+++ b/src/features/Common/index.tsx
@@ -3,7 +3,6 @@ export * from './Button'
export * from './Radio'
export * from './Checkbox'
export * from './Arrows'
-export * from './CloseButton'
export * from './SportName'
export * from './StarIcon'
export * from './customScrollbar'
diff --git a/src/features/HomePage/index.tsx b/src/features/HomePage/index.tsx
index 65460d67..415bc96a 100644
--- a/src/features/HomePage/index.tsx
+++ b/src/features/HomePage/index.tsx
@@ -15,6 +15,7 @@ import {
import { SportFilterWrapper } from 'features/ProfileHeader/styled'
import { MainWrapper } from 'features/MainWrapper'
import { UserFavorites } from 'features/UserFavorites'
+import { MatchPopup } from 'features/MatchPopup'
import { useHomePage } from './hooks'
import { Header } from './components/Header'
@@ -24,6 +25,7 @@ const Home = () => {
const { fetchMatches } = useHomePage()
return (
+
diff --git a/src/features/Icons/Close/index.tsx b/src/features/Icons/Close/index.tsx
new file mode 100644
index 00000000..54f86f76
--- /dev/null
+++ b/src/features/Icons/Close/index.tsx
@@ -0,0 +1,14 @@
+export const Close = () => (
+
+)
diff --git a/src/features/LexicsStore/hooks/useLexicsConfig.tsx b/src/features/LexicsStore/hooks/useLexicsConfig.tsx
index 99a9640d..296f04c8 100644
--- a/src/features/LexicsStore/hooks/useLexicsConfig.tsx
+++ b/src/features/LexicsStore/hooks/useLexicsConfig.tsx
@@ -1,5 +1,7 @@
import { useState, useCallback } from 'react'
+import isEmpty from 'lodash/isEmpty'
+
import { getObjectConfig } from 'features/LexicsStore/helpers'
import type { LexicsConfig, LexicsId } from '../types'
@@ -9,6 +11,8 @@ export const useLexicsConfig = () => {
const addLexicsConfig = useCallback(
(ids: Array | LexicsConfig) => {
+ if (isEmpty(ids)) return
+
const config = getObjectConfig(ids)
setLexicsConfig((state) => ({ ...state, ...config }))
},
diff --git a/src/features/LexicsStore/index.tsx b/src/features/LexicsStore/index.tsx
index 9622b850..8089d8c4 100644
--- a/src/features/LexicsStore/index.tsx
+++ b/src/features/LexicsStore/index.tsx
@@ -5,7 +5,7 @@ import {
useEffect,
} from 'react'
-import { LexicsConfig } from './types'
+import type { LexicsConfig, LexicsId } from './types'
import { useLexics } from './hooks'
type Context = ReturnType
@@ -20,7 +20,7 @@ export const LexicsStore = ({ children }: Props) => {
export const useLexicsStore = () => useContext(LexicsContext)
-export const useLexicsConfig = (config: LexicsConfig) => {
+export const useLexicsConfig = (config: Array | LexicsConfig) => {
const { addLexicsConfig } = useLexicsStore()
useEffect(() => {
diff --git a/src/features/LexicsStore/types.tsx b/src/features/LexicsStore/types.tsx
index aaa77b05..f98655d0 100644
--- a/src/features/LexicsStore/types.tsx
+++ b/src/features/LexicsStore/types.tsx
@@ -1,3 +1,3 @@
-export type LexicsId = string
+export type LexicsId = string | number
export type LexicsConfig = {[lexicsId: string]: number}
diff --git a/src/features/MatchCard/CardBackside/index.tsx b/src/features/MatchCard/CardBackside/index.tsx
deleted file mode 100644
index a8ccfb1a..00000000
--- a/src/features/MatchCard/CardBackside/index.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { MouseEvent } from 'react'
-
-import { Link } from 'react-router-dom'
-
-import type { Match } from 'features/Matches'
-import { OutsideClick } from 'features/OutsideClick'
-
-import {
- CardHoverInner,
- CardHoverTitle,
- CardHoverWrapper,
- MoreVideo,
- Row,
- Rows,
-} from '../styled'
-
-type Props = {
- match: Match,
- onClose: () => void,
-}
-
-const stopProp = (e: MouseEvent) => {
- e.stopPropagation()
-}
-
-export const CardBackside = ({
- match: {
- id,
- sportName,
- },
- onClose,
-}: Props) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-)
diff --git a/src/features/MatchCard/hooks.tsx b/src/features/MatchCard/hooks.tsx
index f3c1a49c..94f6e79f 100644
--- a/src/features/MatchCard/hooks.tsx
+++ b/src/features/MatchCard/hooks.tsx
@@ -1,33 +1,31 @@
import type { KeyboardEvent } from 'react'
import { useCallback } from 'react'
-import { useToggle } from 'hooks'
-
import type { Match } from 'features/Matches'
+import { useMatchPopupStore } from 'features/MatchPopup'
export const useCard = (match: Match) => {
- const {
- close,
- isOpen,
- open,
- } = useToggle()
+ const { openPopup, setMatch } = useMatchPopupStore()
- const flipCard = useCallback(() => {
+ const openMatchPopup = useCallback(() => {
if (match.isClickable) {
- open()
+ setMatch(match)
+ openPopup()
}
- }, [match, open])
+ }, [
+ match,
+ openPopup,
+ setMatch,
+ ])
const onKeyPress = useCallback((e: KeyboardEvent) => {
if (e.key === 'Enter') {
- flipCard()
+ openMatchPopup()
}
- }, [flipCard])
+ }, [openMatchPopup])
return {
- close,
- flipCard,
- isOpen,
onKeyPress,
+ openMatchPopup,
}
}
diff --git a/src/features/MatchCard/index.tsx b/src/features/MatchCard/index.tsx
index 9846122f..46fdd3c4 100644
--- a/src/features/MatchCard/index.tsx
+++ b/src/features/MatchCard/index.tsx
@@ -5,7 +5,6 @@ import { PAGES } from 'config'
import type { Match } from 'features/Matches'
import { CardFrontside } from './CardFrontside'
-import { CardBackside } from './CardBackside'
import { useCard } from './hooks'
type Props = {
@@ -15,26 +14,15 @@ type Props = {
export const MatchCard = ({ match }: Props) => {
const isHomePage = useRouteMatch(PAGES.home)?.isExact
const {
- close,
- flipCard,
- isOpen,
onKeyPress,
+ openMatchPopup,
} = useCard(match)
- if (isOpen) {
- return (
-
- )
- }
-
return (
)
diff --git a/src/features/MatchCard/styled.tsx b/src/features/MatchCard/styled.tsx
index 13381153..24c7ad2e 100644
--- a/src/features/MatchCard/styled.tsx
+++ b/src/features/MatchCard/styled.tsx
@@ -2,7 +2,6 @@ import styled from 'styled-components/macro'
import { devices } from 'config/devices'
-import { T9n } from 'features/T9n'
import { Name } from 'features/Name'
import { ProfileLogo } from 'features/ProfileLogo'
@@ -142,65 +141,6 @@ export const Score = styled.div`
width: 10%;
`
-export const Rows = styled.div`
- width: fit-content;
- margin-top: 20px;
-`
-
-export const Row = styled.div`
- white-space: nowrap;
-`
-
-export const MoreVideo = styled(T9n)`
- display: inline-block;
- margin: 0 8px 8px 0;
- padding: 8px;
- border-radius: 2px;
- font-weight: 500;
- font-size: 11px;
- text-align: center;
- color: rgba(255, 255, 255, 0.5);
- background: linear-gradient(
- 180deg,
- rgba(255, 255, 255, 0.1) 0%,
- rgba(255, 255, 255, 0) 100%
- ),
- #5C5C5C;
- box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
- cursor: pointer;
-
- :hover {
- background: rgba(153, 153, 153, 0.9);
- color: #fff;
- }
-`
-
-export const CardHoverWrapper = styled(CardWrapper)`
- padding: 16px 24px;
- cursor: pointer;
- height: 288px;
-
- @media ${devices.laptop} {
- height: 279px;
- }
- @media ${devices.tablet} {
- height: 299px;
- }
-`
-
-export const CardHoverInner = styled.div`
- position: relative;
- overflow: hidden;
- width: fit-content;
-`
-
-export const CardHoverTitle = styled(T9n)`
- font-size: 10px;
- letter-spacing: 0.03em;
- text-transform: uppercase;
- color: rgba(255, 255, 255, 0.5);
-`
-
export const TeamLogos = styled.div`
display: flex;
padding-left: 24px;
diff --git a/src/features/MatchPage/hooks/useVideoData.tsx b/src/features/MatchPage/hooks/useVideoData.tsx
index 7c905f4e..92709609 100644
--- a/src/features/MatchPage/hooks/useVideoData.tsx
+++ b/src/features/MatchPage/hooks/useVideoData.tsx
@@ -4,9 +4,6 @@ import {
useState,
} from 'react'
-import isEmpty from 'lodash/isEmpty'
-import filter from 'lodash/filter'
-
import type { LiveVideos, Videos } from 'requests'
import { getLiveVideos, getVideos } from 'requests'
@@ -14,21 +11,14 @@ import { useSportNameParam, usePageId } from 'hooks'
import { useLastPlayPosition } from './useLastPlayPosition'
-const filterByIds = (videos: Videos) => {
- const zeroIdVideos = filter(videos, { abc: '0' })
- return isEmpty(zeroIdVideos) ? videos : zeroIdVideos
-}
-
export const useVideoData = () => {
const [videos, setVideos] = useState([])
const [liveVideos, setLiveVideos] = useState([])
const { sportType } = useSportNameParam()
const matchId = usePageId()
- const fetchMatchVideos = useCallback(async () => {
- const videosResponse = await getVideos(sportType, matchId)
- const filteredVideosResponseByAbc = filterByIds(videosResponse)
- setVideos(filteredVideosResponseByAbc)
+ const fetchMatchVideos = useCallback(() => {
+ getVideos(sportType, matchId).then(setVideos)
}, [sportType, matchId])
useEffect(() => {
diff --git a/src/features/MatchPopup/components/ApplyButton/index.tsx b/src/features/MatchPopup/components/ApplyButton/index.tsx
new file mode 100644
index 00000000..42af40d9
--- /dev/null
+++ b/src/features/MatchPopup/components/ApplyButton/index.tsx
@@ -0,0 +1,28 @@
+import styled from 'styled-components/macro'
+
+import { T9n } from 'features/T9n'
+import { ButtonSolid } from 'features/Common'
+
+const Button = styled(ButtonSolid)`
+ position: absolute;
+ bottom: 46px;
+ left: 12px;
+ width: calc(100% - 24px);
+ height: 44px;
+ background-color: #294FC4;
+ border-radius: 5px;
+ font-weight: 600;
+ font-size: 17px;
+ line-height: 22px;
+ letter-spacing: -0.408px;
+`
+
+type Props = {
+ onClick: () => void,
+}
+
+export const ApplyButton = ({ onClick }: Props) => (
+
+)
diff --git a/src/features/MatchPopup/components/BackButton/index.tsx b/src/features/MatchPopup/components/BackButton/index.tsx
new file mode 100644
index 00000000..c495cf6a
--- /dev/null
+++ b/src/features/MatchPopup/components/BackButton/index.tsx
@@ -0,0 +1,16 @@
+import styled from 'styled-components/macro'
+
+import { useMatchPopupStore } from 'features/MatchPopup/store'
+
+import { BaseButton } from '../../styled'
+
+const Button = styled(BaseButton)`
+ background-image: url(/images/back-icon.svg);
+`
+
+export const BackButton = () => {
+ const { goBack } = useMatchPopupStore()
+ return (
+
+ )
+}
diff --git a/src/features/MatchPopup/components/CloseButton/index.tsx b/src/features/MatchPopup/components/CloseButton/index.tsx
new file mode 100644
index 00000000..cdc8fb18
--- /dev/null
+++ b/src/features/MatchPopup/components/CloseButton/index.tsx
@@ -0,0 +1,13 @@
+import { Close } from 'features/Icons/Close'
+import { useMatchPopupStore } from 'features/MatchPopup/store'
+
+import { BaseButton } from '../../styled'
+
+export const CloseButton = () => {
+ const { closePopup } = useMatchPopupStore()
+ return (
+
+
+
+ )
+}
diff --git a/src/features/MatchPopup/components/EpisodeDurationInputs/hooks.tsx b/src/features/MatchPopup/components/EpisodeDurationInputs/hooks.tsx
new file mode 100644
index 00000000..61ac52af
--- /dev/null
+++ b/src/features/MatchPopup/components/EpisodeDurationInputs/hooks.tsx
@@ -0,0 +1,49 @@
+import type { ChangeEvent } from 'react'
+import { useState } from 'react'
+
+import isNaN from 'lodash/isNaN'
+
+import type { EpisodeDuration } from '../../store/hooks/useSettingsState'
+
+const LIMITS = {
+ max: 30,
+ min: 1,
+}
+
+const isValidDuration = (value: number) => (
+ isFinite(value)
+ && value >= LIMITS.min
+ && value <= LIMITS.max
+)
+
+export type Props = {
+ onChange: (duration: EpisodeDuration) => void,
+ value: EpisodeDuration,
+}
+
+export const useInputHandlers = ({ onChange, value: initialValue }: Props) => {
+ const [duration, setDuration] = useState(initialValue)
+ const handleChange = (key: 'before' | 'after') => (
+ (e: ChangeEvent) => {
+ const seconds = Number(e.target.value)
+ setDuration({
+ ...duration,
+ [key]: isNaN(seconds) ? '' : seconds,
+ })
+ }
+ )
+
+ const handleBlur = () => {
+ const { after, before } = duration
+ if (isValidDuration(before) && isValidDuration(after)) {
+ onChange(duration)
+ } else {
+ setDuration(initialValue)
+ }
+ }
+ return {
+ duration,
+ handleBlur,
+ handleChange,
+ }
+}
diff --git a/src/features/MatchPopup/components/EpisodeDurationInputs/index.tsx b/src/features/MatchPopup/components/EpisodeDurationInputs/index.tsx
new file mode 100644
index 00000000..a0516163
--- /dev/null
+++ b/src/features/MatchPopup/components/EpisodeDurationInputs/index.tsx
@@ -0,0 +1,44 @@
+import { BlockTitle } from 'features/MatchPopup/styled'
+import { T9n } from 'features/T9n'
+
+import {
+ Wrapper,
+ InputsWrapper,
+ Input,
+ Label,
+} from './styled'
+import type { Props } from './hooks'
+import { useInputHandlers } from './hooks'
+
+export const EpisodeDurationInputs = (props: Props) => {
+ const {
+ duration,
+ handleBlur,
+ handleChange,
+ } = useInputHandlers(props)
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/features/MatchPopup/components/EpisodeDurationInputs/styled.tsx b/src/features/MatchPopup/components/EpisodeDurationInputs/styled.tsx
new file mode 100644
index 00000000..4ccef703
--- /dev/null
+++ b/src/features/MatchPopup/components/EpisodeDurationInputs/styled.tsx
@@ -0,0 +1,55 @@
+import styled from 'styled-components/macro'
+
+import { devices } from 'config'
+
+export const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin-top: 40px;
+ padding-left: 35px;
+ padding-right: 20px;
+
+ @media ${devices.mobile} {
+ padding: 0 12px;
+ margin-top: 0;
+ }
+`
+
+export const InputsWrapper = styled.div`
+ margin-top: 18px;
+
+ @media ${devices.mobile} {
+ padding: 0 2px;
+ }
+`
+
+export const Input = styled.input`
+ width: 38px;
+ height: 24px;
+ background-color: #FFFFFF;
+ box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.25);
+ border: 0.2px solid #D0D0D0;
+ border-radius: 4px;
+ text-align: center;
+ margin: 0 15px;
+
+ @media ${devices.mobile} {
+ color: #FFFFFF;
+ background-color: #3F3F3F;
+ border: none;
+ }
+`
+
+export const Label = styled.label`
+ font-weight: normal;
+ font-size: 16px;
+ line-height: 24px;
+ letter-spacing: -0.01em;
+ color: rgba(255, 255, 255, 0.5);
+
+ @media ${devices.mobile} {
+ line-height: 20px;
+ letter-spacing: 0.1px;
+ color: #FFFFFF;
+ }
+`
diff --git a/src/features/MatchPopup/components/InterviewCard/index.tsx b/src/features/MatchPopup/components/InterviewCard/index.tsx
new file mode 100644
index 00000000..bd23d3ab
--- /dev/null
+++ b/src/features/MatchPopup/components/InterviewCard/index.tsx
@@ -0,0 +1,73 @@
+import styled from 'styled-components/macro'
+
+import { devices } from 'config'
+
+const Card = styled.div.attrs({
+ tabIndex: 0,
+})`
+ width: 100%;
+ height: 100%;
+ border: 1px solid transparent;
+ transition: border 0.5s ease-out;
+ background: linear-gradient(
+ 180deg,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0) 100%
+ ),
+ #5c5c5c;
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.3);
+ border-radius: 2px;
+
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: stretch;
+
+ &:hover {
+ border: 1px solid #A4A4A4;
+ }
+
+ @media ${devices.mobile} {
+ background-color: #3F3F3F;
+ }
+`
+
+const Image = styled.img`
+ width: 100%;
+ height: 145px;
+ object-fit: cover;
+
+ @media ${devices.mobile} {
+ height: 225px;;
+ background-color: #3F3F3F;
+ }
+`
+
+const Title = styled.span`
+ font-style: normal;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 16px;
+ padding: 0 20px;
+ flex-grow: 1;
+ display: flex;
+ align-items: center;
+ color: #fff;
+
+ @media ${devices.mobile} {
+ padding: 12px;
+ }
+`
+
+type Props = {
+ imgSrc: string,
+ title: string,
+}
+
+export const InterviewCard = ({ imgSrc, title }: Props) => (
+
+
+ {title}
+
+)
diff --git a/src/features/MatchPopup/components/Interviews/index.tsx b/src/features/MatchPopup/components/Interviews/index.tsx
new file mode 100644
index 00000000..f659cf3f
--- /dev/null
+++ b/src/features/MatchPopup/components/Interviews/index.tsx
@@ -0,0 +1,84 @@
+import styled from 'styled-components/macro'
+
+import { devices } from 'config'
+
+import { T9n } from 'features/T9n'
+
+import { InterviewCard } from '../InterviewCard'
+import { BlockTitle } from '../../styled'
+
+const Wrapper = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`
+
+const List = styled.ul`
+ margin: 17px auto;
+ width: 860px;
+ display: flex;
+ flex-wrap: wrap;
+
+ @media ${devices.mobile} {
+ width: 100%;
+ margin: 12px auto;
+ }
+`
+
+const Item = styled.li`
+ width: 200px;
+ height: 200px;
+ margin-bottom: 15px;
+
+ :not(:last-child) {
+ margin-right: 20px;
+
+ @media ${devices.mobile} {
+ margin-right: 0px;
+ }
+ }
+
+ @media ${devices.mobile} {
+ width: 100%;
+ height: 287px;
+ font-size: 14px;
+ line-height: 18px;
+ color: rgba(255, 255, 255, 0.5);
+ text-transform: none;
+ }
+`
+
+export const Interviews = () => (
+
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+)
diff --git a/src/features/MatchPopup/components/MatchPlaylist/index.tsx b/src/features/MatchPopup/components/MatchPlaylist/index.tsx
new file mode 100644
index 00000000..7f692c48
--- /dev/null
+++ b/src/features/MatchPopup/components/MatchPlaylist/index.tsx
@@ -0,0 +1,80 @@
+import { useLocation } from 'react-router-dom'
+
+import styled from 'styled-components/macro'
+
+import { devices, PAGES } from 'config'
+import { getSportLexic } from 'helpers'
+
+import { useMatchPopupStore } from 'features/MatchPopup/store'
+
+import { PlaylistButton } from '../PlaylistButton'
+
+const List = styled.ul`
+ margin: 40px auto 24px auto;
+ width: 855px;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+
+ @media ${devices.mobile} {
+ width: 100%;
+ margin: 12px auto;
+ padding: 0 12px;
+ }
+`
+
+const Item = styled.li`
+ margin-bottom: 15px;
+ width: 420px;
+ height: 50px;
+
+ @media ${devices.mobile} {
+ width: 100%;
+ height: 36px;
+ margin-bottom: 12px;
+ }
+`
+
+export const MatchPlaylist = () => {
+ const { pathname, search } = useLocation()
+ const { match, matchPlaylists } = useMatchPopupStore()
+
+ if (!match || !matchPlaylists) return null
+
+ // не меняем url при клике ссылки временно
+ // до добавления плейлистов для голов, обзоров
+ const currentRoute = `${pathname}${search}`
+ const sport = getSportLexic(match.sportType)
+ return (
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+ )
+}
diff --git a/src/features/MatchPopup/components/PlayerActions/index.tsx b/src/features/MatchPopup/components/PlayerActions/index.tsx
new file mode 100644
index 00000000..afd7fbf7
--- /dev/null
+++ b/src/features/MatchPopup/components/PlayerActions/index.tsx
@@ -0,0 +1,56 @@
+import includes from 'lodash/includes'
+import filter from 'lodash/filter'
+import map from 'lodash/map'
+
+import type { Actions } from 'requests'
+
+import { T9n } from 'features/T9n'
+import { BlockTitle } from 'features/MatchPopup/styled'
+
+import type { SelectedActions } from '../../store/hooks/useSettingsState'
+import {
+ Wrapper,
+ Checkbox,
+ List,
+ Item,
+} from './styled'
+
+type Props = {
+ actions: Actions,
+ onActionClick: (actions: SelectedActions) => void,
+ selectedActions: SelectedActions,
+}
+
+export const PlayerActions = ({
+ actions,
+ onActionClick,
+ selectedActions,
+}: Props) => {
+ const handleActionClick = (id: number) => {
+ const newSelectedActions = includes(selectedActions, id)
+ ? filter(selectedActions, (actionId) => actionId !== id)
+ : [...selectedActions, id]
+
+ onActionClick(newSelectedActions)
+ }
+ return (
+
+
+
+
+
+ {
+ map(actions, (action) => (
+ -
+ handleActionClick(action.id)}
+ labelLexic={action.lexic}
+ />
+
+ ))
+ }
+
+
+ )
+}
diff --git a/src/features/MatchPopup/components/PlayerActions/styled.tsx b/src/features/MatchPopup/components/PlayerActions/styled.tsx
new file mode 100644
index 00000000..d4963860
--- /dev/null
+++ b/src/features/MatchPopup/components/PlayerActions/styled.tsx
@@ -0,0 +1,102 @@
+import styled, { css } from 'styled-components/macro'
+
+import { devices } from 'config'
+
+import { Checkbox as BaseCheckbox } from 'features/Common'
+import { Label } from 'features/Common/Checkbox/styled'
+import { CheckboxSvg } from 'features/Common/Checkbox/Icon'
+
+export const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin-top: 56px;
+ padding-left: 35px;
+
+ @media ${devices.mobile} {
+ margin-top: 30px;
+ padding: 0 12px;
+ }
+`
+
+const scrollBarStyles = css`
+ ::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background-color: rgba(196, 196, 196, 0.3);
+ border-radius: 3px;
+ }
+
+ ::-webkit-scrollbar-button {
+ display: none;
+ }
+
+ ::-webkit-scrollbar-track,
+ ::-webkit-scrollbar-corner {
+ border-radius: 3px;
+ background: rgba(103, 103, 103, 0.3);
+ }
+`
+
+export const List = styled.ul`
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 20px;
+ height: 262px;
+ overflow-y: auto;
+
+ @media ${devices.mobile} {
+ height: 100%;
+ margin: 8px 0;
+ padding: 0 2px 100px 2px;
+ overflow-y: initial;
+ flex-direction: column;
+ }
+
+ ${scrollBarStyles}
+`
+
+export const Item = styled.li`
+ width: calc(100% / 3);
+
+ :not(:last-child) {
+ margin-bottom: 24px;
+ }
+
+ @media ${devices.mobile} {
+ width: 100%;
+ height: 44px;
+
+ :not(:last-child) {
+ margin-bottom: 0;
+ }
+ }
+`
+
+export const Checkbox = styled(BaseCheckbox)`
+ height: 100%;
+
+ ${Label} {
+ color: #ffffff;
+ font-weight: normal;
+ font-size: 20px;
+ line-height: 21px;
+
+ @media ${devices.mobile} {
+ font-size: 16px;
+ line-height: 20px;
+ letter-spacing: 0.1px;
+ }
+ }
+
+ ${CheckboxSvg} {
+ margin-right: 15px;
+ }
+
+ @media ${devices.mobile} {
+ display: flex;
+ align-items: center;
+ }
+`
diff --git a/src/features/MatchPopup/components/PlayersList/index.tsx b/src/features/MatchPopup/components/PlayersList/index.tsx
new file mode 100644
index 00000000..940a5e4b
--- /dev/null
+++ b/src/features/MatchPopup/components/PlayersList/index.tsx
@@ -0,0 +1,46 @@
+import map from 'lodash/map'
+
+import { ProfileTypes, SportTypes } from 'config'
+
+import type { Players } from 'requests'
+
+import { Teams } from '../../types'
+import {
+ List,
+ Item,
+ Logo,
+ PlayerName,
+} from './styled'
+
+type Props = {
+ players: Players,
+ sportType: SportTypes,
+ team: Teams,
+}
+
+export const PlayersList = ({
+ players,
+ sportType,
+ team,
+}: Props) => (
+
+ {
+ map(players, (player) => (
+ -
+
+
+
+ ))
+ }
+
+)
diff --git a/src/features/MatchPopup/components/PlayersList/styled.tsx b/src/features/MatchPopup/components/PlayersList/styled.tsx
new file mode 100644
index 00000000..09660c90
--- /dev/null
+++ b/src/features/MatchPopup/components/PlayersList/styled.tsx
@@ -0,0 +1,90 @@
+import styled from 'styled-components/macro'
+
+import { devices } from 'config'
+
+import { Name } from 'features/Name'
+import { ProfileLogo } from 'features/ProfileLogo'
+
+import { Teams } from '../../types'
+
+type ListProps = {
+ team: Teams,
+}
+
+export const List = styled.ul`
+ width: calc((100% - 30px) / 2);
+ height: 230px;
+ overflow: auto;
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: ${({ team }) => (
+ team === Teams.TEAM1
+ ? 'row-reverse'
+ : 'row'
+ )};
+
+ @media ${devices.mobile} {
+ width: 100%;
+ height: auto;
+ flex-direction: column;
+ }
+`
+
+export const Item = styled.li.attrs(() => ({
+ tabIndex: 0,
+}))`
+ width: 76px;
+ height: 95px;
+ margin: 10px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+
+ @media ${devices.mobile} {
+ width: 100%;
+ height: 60px;
+ flex-direction: row;
+ justify-content: flex-start;
+ margin: 0px;
+ padding: 10px;
+ }
+`
+
+type LogoProps = {
+ team: Teams,
+}
+
+export const Logo = styled(ProfileLogo)`
+ width: 65px;
+ height: 65px;
+ border-radius: 50%;
+ background-color: ${({ team }) => (
+ team === Teams.TEAM1
+ ? '#EB5757'
+ : '#2F80ED'
+ )};
+
+ @media ${devices.mobile} {
+ width: 49px;
+ height: 49px;
+ margin-right: 11px;
+ }
+`
+
+export const PlayerName = styled(Name)`
+ width: 100%;
+ font-weight: bold;
+ font-size: 10px;
+ line-height: 10px;
+ text-align: center;
+ letter-spacing: 0.02em;
+
+ @media ${devices.mobile} {
+ text-align: start;
+ font-size: 16px;
+ line-height: 20px;
+ letter-spacing: 0.1px;
+ }
+`
diff --git a/src/features/MatchPopup/components/PlayersListDesktop/index.tsx b/src/features/MatchPopup/components/PlayersListDesktop/index.tsx
new file mode 100644
index 00000000..8d80bffe
--- /dev/null
+++ b/src/features/MatchPopup/components/PlayersListDesktop/index.tsx
@@ -0,0 +1,49 @@
+import styled from 'styled-components/macro'
+
+import { T9n } from 'features/T9n'
+import { useMatchPopupStore } from 'features/MatchPopup'
+
+import { Teams } from '../../types'
+import { BlockTitle } from '../../styled'
+import { PlayersList } from '../PlayersList'
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 7px;
+`
+
+const ListsWrapper = styled.div`
+ width: 100%;
+ margin-top: 10px;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+`
+
+export const PlayersListDesktop = () => {
+ const { match, matchPlaylists } = useMatchPopupStore()
+
+ if (!match || !matchPlaylists) return null
+
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/features/MatchPopup/components/PlayersListMobile/index.tsx b/src/features/MatchPopup/components/PlayersListMobile/index.tsx
new file mode 100644
index 00000000..5ebd757e
--- /dev/null
+++ b/src/features/MatchPopup/components/PlayersListMobile/index.tsx
@@ -0,0 +1,52 @@
+import { useState } from 'react'
+
+import { T9n } from 'features/T9n'
+import { Name } from 'features/Name'
+import { useMatchPopupStore } from 'features/MatchPopup'
+
+import { Teams } from '../../types'
+import { BlockTitle } from '../../styled'
+import { PlayersList } from '../PlayersList'
+import {
+ Wrapper,
+ Tabs,
+ Tab,
+} from './styled'
+
+export const PlayersListMobile = () => {
+ const { match, matchPlaylists } = useMatchPopupStore()
+ const [selectedTeam, setSelectedTeam] = useState(Teams.TEAM1)
+
+ if (!match || !matchPlaylists) return null
+
+ const players = selectedTeam === Teams.TEAM1
+ ? matchPlaylists.players1
+ : matchPlaylists.players2
+
+ return (
+
+
+
+
+
+ setSelectedTeam(Teams.TEAM1)}
+ >
+
+
+ setSelectedTeam(Teams.TEAM2)}
+ >
+
+
+
+
+
+ )
+}
diff --git a/src/features/MatchPopup/components/PlayersListMobile/styled.tsx b/src/features/MatchPopup/components/PlayersListMobile/styled.tsx
new file mode 100644
index 00000000..2e008a60
--- /dev/null
+++ b/src/features/MatchPopup/components/PlayersListMobile/styled.tsx
@@ -0,0 +1,47 @@
+import styled, { css } from 'styled-components/macro'
+
+export const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 8px;
+`
+
+export const Tabs = styled.ul`
+ width: calc(100% - 24px);
+ margin: 20px 0 12px 0;
+ height: 32px;
+ display: flex;
+ border: 1px solid rgba(255, 255, 255, 0.5);
+ border-radius: 10px;
+ overflow: hidden;
+`
+
+type TabProps = {
+ selected?: boolean,
+}
+
+export const Tab = styled.li.attrs(() => ({
+ tabIndex: 0,
+}))`
+ width: 50%;
+ padding: 0 12px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 18px;
+ cursor: pointer;
+
+ ${({ selected }) => (
+ selected
+ ? css`
+ color: #000000;
+ background-color: #ffffff;
+ `
+ : css`
+ color: #ffffff;
+ `
+ )}
+`
diff --git a/src/features/MatchPopup/components/PlaylistButton/index.tsx b/src/features/MatchPopup/components/PlaylistButton/index.tsx
new file mode 100644
index 00000000..45b98c83
--- /dev/null
+++ b/src/features/MatchPopup/components/PlaylistButton/index.tsx
@@ -0,0 +1,87 @@
+import type { MouseEvent } from 'react'
+import { Link } from 'react-router-dom'
+
+import styled from 'styled-components/macro'
+
+import { devices } from 'config'
+import { secondsToHms } from 'helpers'
+
+import { T9n } from 'features/T9n'
+
+const StyledLink = styled(Link)`
+ border: none;
+
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ padding: 0 25px;
+ background: linear-gradient(
+ 180deg,
+ rgba(255, 255, 255, 0.1) 0%,
+ rgba(255, 255, 255, 0) 100%
+ ),
+ #5c5c5c;
+ box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3);
+ border-radius: 2px;
+
+ :hover {
+ background-color: #555555;
+ }
+
+ @media ${devices.mobile} {
+ justify-content: center;
+ border-radius: 5px;
+ }
+`
+
+const Title = styled.span`
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 50px;
+ letter-spacing: 0.03em;
+ text-transform: uppercase;
+ color: #ffffff;
+
+ @media ${devices.mobile} {
+ font-size: 17px;
+ line-height: 16px;
+ margin-right: 16px;
+ text-transform: none;
+ }
+`
+
+const Duration = styled(Title)`
+ font-weight: 300;
+ font-size: 24px;
+ letter-spacing: 0.05em;
+
+ @media ${devices.mobile} {
+ font-size: 17px;
+ line-height: 16px;
+ text-transform: none;
+ }
+`
+
+const stopPropagation = (e: MouseEvent) => e.stopPropagation()
+
+type Props = {
+ duration: number,
+ title: string,
+ to: string,
+}
+
+export const PlaylistButton = ({
+ duration,
+ title,
+ to,
+}: Props) => (
+
+
+
+
+ {secondsToHms(duration)}
+
+)
diff --git a/src/features/MatchPopup/components/PlaylistFormats/index.tsx b/src/features/MatchPopup/components/PlaylistFormats/index.tsx
new file mode 100644
index 00000000..4f07c65b
--- /dev/null
+++ b/src/features/MatchPopup/components/PlaylistFormats/index.tsx
@@ -0,0 +1,52 @@
+import { T9n } from 'features/T9n'
+import { PlayerPlaylistFormats } from 'features/MatchPopup/types'
+import { BlockTitle } from 'features/MatchPopup/styled'
+
+import {
+ Wrapper,
+ List,
+ Item,
+ Radio,
+} from './styled'
+
+type Props = {
+ onFormatSelect: (format: PlayerPlaylistFormats) => void,
+ selectedFormat: PlayerPlaylistFormats,
+}
+
+export const PlaylistFormats = ({
+ onFormatSelect,
+ selectedFormat,
+}: Props) => (
+
+
+
+
+
+ -
+ onFormatSelect(PlayerPlaylistFormats.ALL_MATCH_TIME)}
+ />
+
+ -
+ onFormatSelect(PlayerPlaylistFormats.ALL_ACTIONS)}
+ />
+
+ -
+ onFormatSelect(PlayerPlaylistFormats.SELECTED_ACTIONS)}
+ />
+
+
+
+)
diff --git a/src/features/MatchPopup/components/PlaylistFormats/styled.tsx b/src/features/MatchPopup/components/PlaylistFormats/styled.tsx
new file mode 100644
index 00000000..d3b51e60
--- /dev/null
+++ b/src/features/MatchPopup/components/PlaylistFormats/styled.tsx
@@ -0,0 +1,69 @@
+import styled from 'styled-components/macro'
+
+import { devices } from 'config'
+
+import { Radio as BaseRadio } from 'features/Common'
+import { Label } from 'features/Common/Radio/styled'
+import { RadioSvg } from 'features/Common/Radio/Icon'
+
+export const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin-top: 62px;
+ padding-left: 35px;
+ padding-right: 20px;
+
+ @media ${devices.mobile} {
+ margin-top: 14px;
+ padding: 0 12px;
+ }
+`
+
+export const List = styled.ul`
+ display: flex;
+ flex-direction: column;
+ margin: 15px 0;
+
+ @media ${devices.mobile} {
+ padding: 0 2px;
+ }
+`
+
+export const Item = styled.li`
+ :not(:last-child) {
+ margin-bottom: 20px;
+ }
+
+ @media ${devices.mobile} {
+ height: 44px;
+
+ :not(:last-child) {
+ margin-bottom: 0;
+ }
+ }
+`
+
+export const Radio = styled(BaseRadio)`
+ ${Label} {
+ color: #ffffff;
+ font-weight: normal;
+ font-size: 20px;
+ line-height: 21px;
+ }
+
+ ${RadioSvg} {
+ margin-right: 15px;
+ }
+
+ @media ${devices.mobile} {
+ width: 100%;
+ height: 100%;
+
+ ${Label} {
+ height: 100%;
+ font-size: 16px;
+ line-height: 20px;
+ letter-spacing: 0.1px;
+ }
+ }
+`
diff --git a/src/features/MatchPopup/components/PlaylistPage/index.tsx b/src/features/MatchPopup/components/PlaylistPage/index.tsx
new file mode 100644
index 00000000..95ee7b92
--- /dev/null
+++ b/src/features/MatchPopup/components/PlaylistPage/index.tsx
@@ -0,0 +1,69 @@
+import { Fragment } from 'react'
+
+import { Name } from 'features/Name'
+import { MediaQuery } from 'features/MediaQuery'
+import { useMatchPopupStore } from 'features/MatchPopup'
+
+import { SettingsButton } from '../SettingsButton'
+import { CloseButton } from '../CloseButton'
+import { BackButton } from '../BackButton'
+import { MatchPlaylist } from '../MatchPlaylist'
+import { Interviews } from '../Interviews'
+import { PlayersListDesktop } from '../PlayersListDesktop'
+import { PlayersListMobile } from '../PlayersListMobile'
+import {
+ Content,
+ Header,
+ HeaderActions,
+ HeaderTitle,
+} from '../../styled'
+
+export const PlaylistPage = () => {
+ const { match, matchPlaylists } = useMatchPopupStore()
+ if (!match) return null
+
+ const { team1, team2 } = match
+ const score = ` ${team1.score}:${team2.score} `
+ return (
+
+
+
+
+
+
+
+
+
+
+ {score}
+
+
+
+
+
+
+
+
+
+
+
+ {
+ matchPlaylists && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+
+ )
+}
diff --git a/src/features/MatchPopup/components/SettingsButton/index.tsx b/src/features/MatchPopup/components/SettingsButton/index.tsx
new file mode 100644
index 00000000..832f78c4
--- /dev/null
+++ b/src/features/MatchPopup/components/SettingsButton/index.tsx
@@ -0,0 +1,16 @@
+import styled from 'styled-components/macro'
+
+import { useMatchPopupStore } from 'features/MatchPopup/store'
+
+import { BaseButton } from '../../styled'
+
+const Button = styled(BaseButton)`
+ background-image: url(/images/settings.svg);
+`
+
+export const SettingsButton = () => {
+ const { goToSettings } = useMatchPopupStore()
+ return (
+
+ )
+}
diff --git a/src/features/MatchPopup/components/SettingsDesktop/index.tsx b/src/features/MatchPopup/components/SettingsDesktop/index.tsx
new file mode 100644
index 00000000..36ce4496
--- /dev/null
+++ b/src/features/MatchPopup/components/SettingsDesktop/index.tsx
@@ -0,0 +1,39 @@
+import { Fragment } from 'react'
+
+import { useMatchPopupStore } from 'features/MatchPopup'
+
+import { PlaylistFormats } from '../PlaylistFormats'
+import { EpisodeDurationInputs } from '../EpisodeDurationInputs'
+import { PlayerActions } from '../PlayerActions'
+import { PlayerPlaylistFormats } from '../../types'
+
+export const SettingsDesktop = () => {
+ const {
+ actions,
+ episodeDuration,
+ onActionClick,
+ onDurationChange,
+ onFormatSelect,
+ selectedActions,
+ selectedPlaylistFormat,
+ } = useMatchPopupStore()
+ return (
+
+
+
+ {selectedPlaylistFormat === PlayerPlaylistFormats.SELECTED_ACTIONS && (
+
+ )}
+
+ )
+}
diff --git a/src/features/MatchPopup/components/SettingsMobile/hooks.tsx b/src/features/MatchPopup/components/SettingsMobile/hooks.tsx
new file mode 100644
index 00000000..44cea657
--- /dev/null
+++ b/src/features/MatchPopup/components/SettingsMobile/hooks.tsx
@@ -0,0 +1,38 @@
+import { useState } from 'react'
+
+import { useMatchPopupStore } from 'features/MatchPopup'
+
+export const useMobileSettings = () => {
+ const {
+ actions,
+ episodeDuration: initialDuration,
+ goBack,
+ onActionClick,
+ onDurationChange,
+ onFormatSelect,
+ selectedActions: initialActions,
+ selectedPlaylistFormat: initialFormat,
+ } = useMatchPopupStore()
+
+ const [selectedPlaylistFormat, setSelectedPlaylistFormat] = useState(initialFormat)
+ const [episodeDuration, setEpisodeDuration] = useState(initialDuration)
+ const [selectedActions, setSelectedActions] = useState(initialActions)
+
+ const applySettings = () => {
+ onFormatSelect(selectedPlaylistFormat)
+ onDurationChange(episodeDuration)
+ onActionClick(selectedActions)
+ goBack()
+ }
+
+ return {
+ actions,
+ applySettings,
+ episodeDuration,
+ selectedActions,
+ selectedPlaylistFormat,
+ setEpisodeDuration,
+ setSelectedActions,
+ setSelectedPlaylistFormat,
+ }
+}
diff --git a/src/features/MatchPopup/components/SettingsMobile/index.tsx b/src/features/MatchPopup/components/SettingsMobile/index.tsx
new file mode 100644
index 00000000..14c67f78
--- /dev/null
+++ b/src/features/MatchPopup/components/SettingsMobile/index.tsx
@@ -0,0 +1,41 @@
+import { Fragment } from 'react'
+
+import { PlaylistFormats } from '../PlaylistFormats'
+import { EpisodeDurationInputs } from '../EpisodeDurationInputs'
+import { PlayerActions } from '../PlayerActions'
+import { ApplyButton } from '../ApplyButton'
+import { PlayerPlaylistFormats } from '../../types'
+import { useMobileSettings } from './hooks'
+
+export const SettingsMobile = () => {
+ const {
+ actions,
+ applySettings,
+ episodeDuration,
+ selectedActions,
+ selectedPlaylistFormat,
+ setEpisodeDuration,
+ setSelectedActions,
+ setSelectedPlaylistFormat,
+ } = useMobileSettings()
+ return (
+
+
+
+ {selectedPlaylistFormat === PlayerPlaylistFormats.SELECTED_ACTIONS && (
+
+ )}
+
+
+ )
+}
diff --git a/src/features/MatchPopup/components/SettingsPage/index.tsx b/src/features/MatchPopup/components/SettingsPage/index.tsx
new file mode 100644
index 00000000..6d84dd49
--- /dev/null
+++ b/src/features/MatchPopup/components/SettingsPage/index.tsx
@@ -0,0 +1,60 @@
+import styled from 'styled-components/macro'
+
+import { MediaQuery } from 'features/MediaQuery'
+import { T9n } from 'features/T9n'
+
+import { CloseButton } from '../CloseButton'
+import { BackButton } from '../BackButton'
+import { SettingsDesktop } from '../SettingsDesktop'
+import { SettingsMobile } from '../SettingsMobile'
+import {
+ Content,
+ Header,
+ HeaderActions,
+ HeaderTitle,
+} from '../../styled'
+
+const ButtonLabel = styled(T9n)`
+ display: flex;
+ align-items: center;
+ font-weight: normal;
+ font-size: 18px;
+ line-height: 21px;
+ color: rgba(255, 255, 255, 0.5);
+`
+
+export const SettingsPage = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+)
diff --git a/src/features/MatchPopup/config.tsx b/src/features/MatchPopup/config.tsx
new file mode 100644
index 00000000..a7d7e28a
--- /dev/null
+++ b/src/features/MatchPopup/config.tsx
@@ -0,0 +1,4 @@
+export enum Teams {
+ TEAM1,
+ TEAM2,
+}
diff --git a/src/features/MatchPopup/index.tsx b/src/features/MatchPopup/index.tsx
new file mode 100644
index 00000000..91d55b75
--- /dev/null
+++ b/src/features/MatchPopup/index.tsx
@@ -0,0 +1,49 @@
+import { Fragment } from 'react'
+
+import { Background } from 'features/Background'
+import { MediaQuery } from 'features/MediaQuery'
+import { useMatchPopupStore } from 'features/MatchPopup'
+
+import { SettingsPage } from './components/SettingsPage'
+import { PlaylistPage } from './components/PlaylistPage'
+import { PopupPages } from './types'
+import { Modal } from './styled'
+
+export * from './store'
+
+export const MatchPopup = () => {
+ const {
+ closePopup,
+ isOpen,
+ page,
+ } = useMatchPopupStore()
+
+ const pageElement = page === PopupPages.PLAYLIST
+ ?
+ :
+
+ return (
+
+
+
+ {pageElement}
+
+
+
+
+
+
+ {pageElement}
+
+
+
+
+ )
+}
diff --git a/src/features/MatchPopup/store/hooks/index.tsx b/src/features/MatchPopup/store/hooks/index.tsx
new file mode 100644
index 00000000..d8bbeeb5
--- /dev/null
+++ b/src/features/MatchPopup/store/hooks/index.tsx
@@ -0,0 +1,102 @@
+import { useState, useEffect } from 'react'
+
+import isEmpty from 'lodash/isEmpty'
+
+import type { MatchPlaylists } from 'requests'
+import { getMatchPlaylists } from 'requests'
+
+import { useSettingsState } from './useSettingsState'
+import { useSportActions } from './useSportActions'
+import { usePopupNavigation } from './usePopupNavigation'
+
+import type { MatchData } from '../../types'
+import { PopupPages, PlayerPlaylistFormats } from '../../types'
+
+export const useMatchPopup = () => {
+ const [match, setMatch] = useState(null)
+ const [matchPlaylists, setMatchPlaylists] = useState(null)
+ const {
+ closePopup,
+ goBack,
+ goToSettings,
+ isOpen,
+ openPopup,
+ page,
+ } = usePopupNavigation()
+
+ const {
+ episodeDuration,
+ resetSelectedActions,
+ selectedActions,
+ selectedPlaylistFormat,
+ setEpisodeDuration,
+ setSelectedActions,
+ setSelectedPlaylistFormat,
+ } = useSettingsState(match?.sportType)
+
+ const { actions, fetchSportActions } = useSportActions(match?.sportType)
+
+ useEffect(() => {
+ if (!isOpen) {
+ setMatch(null)
+ setMatchPlaylists(null)
+ }
+ }, [isOpen])
+
+ useEffect(() => {
+ if (selectedPlaylistFormat !== PlayerPlaylistFormats.SELECTED_ACTIONS) {
+ resetSelectedActions()
+ }
+ }, [selectedPlaylistFormat, resetSelectedActions])
+
+ useEffect(() => {
+ const isSettingsPage = page === PopupPages.SETTINGS
+ const actionsFormatSelected = (
+ selectedPlaylistFormat === PlayerPlaylistFormats.SELECTED_ACTIONS
+ )
+ if (isSettingsPage && actionsFormatSelected) {
+ fetchSportActions()
+ }
+ }, [
+ selectedPlaylistFormat,
+ match,
+ page,
+ fetchSportActions,
+ resetSelectedActions,
+ ])
+
+ useEffect(() => {
+ if (!match || !isOpen || page !== PopupPages.PLAYLIST) return
+
+ getMatchPlaylists({
+ matchId: match.id,
+ // запрос с экшнами [1, 2, 3] временный
+ selectedActions: isEmpty(selectedActions) ? [1, 2, 3] : selectedActions,
+ sportType: match.sportType,
+ }).then(setMatchPlaylists)
+ }, [
+ isOpen,
+ match,
+ page,
+ selectedActions,
+ ])
+
+ return {
+ actions,
+ closePopup,
+ episodeDuration,
+ goBack,
+ goToSettings,
+ isOpen,
+ match,
+ matchPlaylists,
+ onActionClick: setSelectedActions,
+ onDurationChange: setEpisodeDuration,
+ onFormatSelect: setSelectedPlaylistFormat,
+ openPopup,
+ page,
+ selectedActions,
+ selectedPlaylistFormat,
+ setMatch,
+ }
+}
diff --git a/src/features/MatchPopup/store/hooks/usePopupNavigation.tsx b/src/features/MatchPopup/store/hooks/usePopupNavigation.tsx
new file mode 100644
index 00000000..3abd8e5e
--- /dev/null
+++ b/src/features/MatchPopup/store/hooks/usePopupNavigation.tsx
@@ -0,0 +1,44 @@
+import type { MouseEvent } from 'react'
+import { useState, useCallback } from 'react'
+
+import { useToggle } from 'hooks'
+
+import { PopupPages } from '../../types'
+
+export const usePopupNavigation = () => {
+ const {
+ close,
+ isOpen,
+ open,
+ } = useToggle()
+
+ const [page, setPage] = useState(PopupPages.PLAYLIST)
+
+ const closePopup = useCallback(() => {
+ close()
+ setPage(PopupPages.PLAYLIST)
+ }, [close])
+
+ const goBack = useCallback((e?: MouseEvent) => {
+ e?.stopPropagation()
+ if (page === PopupPages.PLAYLIST) {
+ closePopup()
+ } else {
+ setPage(PopupPages.PLAYLIST)
+ }
+ }, [page, closePopup])
+
+ const goToSettings = useCallback((e: MouseEvent) => {
+ e.stopPropagation()
+ setPage(PopupPages.SETTINGS)
+ }, [])
+
+ return {
+ closePopup,
+ goBack,
+ goToSettings,
+ isOpen,
+ openPopup: open,
+ page,
+ }
+}
diff --git a/src/features/MatchPopup/store/hooks/useSettingsState.tsx b/src/features/MatchPopup/store/hooks/useSettingsState.tsx
new file mode 100644
index 00000000..fdd48f36
--- /dev/null
+++ b/src/features/MatchPopup/store/hooks/useSettingsState.tsx
@@ -0,0 +1,92 @@
+import { useCallback, useMemo } from 'react'
+
+import isObject from 'lodash/isObject'
+
+import { SportTypes } from 'config'
+
+import { useLocalStore } from 'hooks'
+
+import { PlayerPlaylistFormats } from '../../types'
+
+export type SelectedActions = Array
+export type EpisodeDuration = {
+ after: number,
+ before: number,
+}
+type Settings = {
+ episodeDuration: EpisodeDuration,
+ selectedActions: SelectedActions,
+ selectedFormat: PlayerPlaylistFormats,
+}
+type SettingsBySport = Partial>
+
+const selectedActionsKey = 'playlist_settings'
+const defaultSettings: Settings = {
+ episodeDuration: {
+ after: 6,
+ before: 6,
+ },
+ selectedActions: [],
+ selectedFormat: PlayerPlaylistFormats.ALL_MATCH_TIME,
+}
+const validator = (value: unknown) => Boolean(value) && isObject(value)
+
+export const useSettingsState = (sportType?: SportTypes) => {
+ const [settingsObj, setSettingsObj] = useLocalStore({
+ defaultValue: {},
+ key: selectedActionsKey,
+ validator,
+ })
+
+ /**
+ * Сетит настройки определенного вида спорта,
+ * работает как setState классовых компонентов,
+ * то что передается в сеттер мержит со стейтом
+ */
+ const setSettings = useCallback((newSettings: Partial) => {
+ if (!sportType) return
+ setSettingsObj((state) => {
+ const oldSettings = state[sportType] || defaultSettings
+ return {
+ ...state,
+ [sportType]: { ...oldSettings, ...newSettings },
+ }
+ })
+ }, [sportType, setSettingsObj])
+
+ const getSettings = useCallback(() => {
+ if (!sportType) return defaultSettings
+ return settingsObj[sportType] || defaultSettings
+ }, [settingsObj, sportType])
+
+ const setSelectedPlaylistFormat = useCallback(
+ (value: PlayerPlaylistFormats) => setSettings({ selectedFormat: value }),
+ [setSettings],
+ )
+
+ const setSelectedActions = useCallback(
+ (value: SelectedActions) => setSettings({ selectedActions: value }),
+ [setSettings],
+ )
+
+ const setEpisodeDuration = useCallback(
+ (value: EpisodeDuration) => setSettings({ episodeDuration: value }),
+ [setSettings],
+ )
+
+ const resetSelectedActions = useCallback(() => {
+ setSelectedActions([])
+ }, [setSelectedActions])
+
+ const settings = useMemo(getSettings, [getSettings])
+
+ return {
+ episodeDuration: settings.episodeDuration,
+ resetSelectedActions,
+ selectedActions: settings.selectedActions,
+ selectedPlaylistFormat: settings.selectedFormat,
+ setEpisodeDuration,
+ setSelectedActions,
+ setSelectedPlaylistFormat,
+ }
+}
diff --git a/src/features/MatchPopup/store/hooks/useSportActions.tsx b/src/features/MatchPopup/store/hooks/useSportActions.tsx
new file mode 100644
index 00000000..f58aef97
--- /dev/null
+++ b/src/features/MatchPopup/store/hooks/useSportActions.tsx
@@ -0,0 +1,26 @@
+import { useCallback, useState } from 'react'
+
+import map from 'lodash/map'
+
+import { SportTypes } from 'config'
+
+import type { Actions } from 'requests'
+import { getSportActions } from 'requests'
+
+import { useLexicsStore } from 'features/LexicsStore'
+
+export const useSportActions = (sportType?: SportTypes) => {
+ const [actions, setActions] = useState([])
+ const { addLexicsConfig } = useLexicsStore()
+
+ const fetchSportActions = useCallback(() => {
+ if (!sportType) return
+
+ getSportActions(sportType).then((sportActions) => {
+ setActions(sportActions)
+ addLexicsConfig(map(sportActions, ({ lexic }) => lexic))
+ })
+ }, [sportType, addLexicsConfig])
+
+ return { actions, fetchSportActions }
+}
diff --git a/src/features/MatchPopup/store/index.tsx b/src/features/MatchPopup/store/index.tsx
new file mode 100644
index 00000000..4a0d73ce
--- /dev/null
+++ b/src/features/MatchPopup/store/index.tsx
@@ -0,0 +1,20 @@
+import type { ReactNode } from 'react'
+import { createContext, useContext } from 'react'
+
+import { useMatchPopup } from './hooks'
+
+type Context = ReturnType
+type Props = { children: ReactNode }
+
+const MatchPopupContext = createContext({} as Context)
+
+export const MatchPopupStore = ({ children }: Props) => {
+ const value = useMatchPopup()
+ return (
+
+ {children}
+
+ )
+}
+
+export const useMatchPopupStore = () => useContext(MatchPopupContext)
diff --git a/src/features/MatchPopup/styled.tsx b/src/features/MatchPopup/styled.tsx
new file mode 100644
index 00000000..156742c4
--- /dev/null
+++ b/src/features/MatchPopup/styled.tsx
@@ -0,0 +1,140 @@
+import styled, { css } from 'styled-components/macro'
+
+import { devices } from 'config'
+
+import { Modal as BaseModal } from 'features/Modal'
+import { ModalWindow } from 'features/Modal/styled'
+import { customScrollbar } from 'features/Common'
+
+export const Modal = styled(BaseModal)`
+ background-color: rgba(0, 0, 0, 0.7);
+
+ ${ModalWindow} {
+ width: 1222px;
+ height: 818px;
+ padding: 20px 0;
+ background-color: #3F3F3F;
+ border-radius: 5px;
+
+ @media ${devices.mobile} {
+ width: 100vw;
+ height: 100vh;
+ padding: 0;
+ background-color: transparent;
+ }
+ }
+`
+
+export const BaseButton = styled.button`
+ padding: 0;
+ border: none;
+ background: none;
+
+ cursor: pointer;
+ width: 34px;
+ height: 34px;
+ color: white;
+ background-color: rgba(255, 255, 255, 0.12);
+ background-position: center;
+ background-repeat: no-repeat;
+ border-radius: 50%;
+
+ :hover {
+ background-color: rgba(255, 255, 255, 0.22);
+ }
+
+ @media ${devices.mobile} {
+ width: 24px;
+ height: 24px;
+ background-color: transparent;
+ border-radius: 0;
+ }
+`
+
+export const Content = styled.div`
+ width: 100%;
+ height: 100%;
+ overflow-y: auto;
+
+ ${customScrollbar}
+
+ @media ${devices.mobile} {
+ height: 100vh;
+ background-color: transparent;
+ }
+`
+
+export const Header = styled.div`
+ position: relative;
+ height: 35px;
+ display: flex;
+ align-items: center;
+
+ @media ${devices.mobile} {
+ height: 52px;
+ background-color: rgba(255, 255, 255, 0.1);
+ padding: 0 12px;
+ }
+`
+
+type HeaderActionsProps = {
+ marginLeft?: number,
+ position: 'left' | 'right',
+}
+
+export const HeaderActions = styled.div`
+ position: absolute;
+ display: flex;
+
+ ${({ marginLeft = 0, position }) => css`
+ ${position}: 20px;
+ margin-left: ${marginLeft}px;
+ `}
+
+ @media ${devices.mobile} {
+ ${({ position }) => css`
+ ${position}: 12px;
+ margin-left: 0;
+ `}
+ }
+
+ ${BaseButton}:not(:last-child) {
+ margin-right: 20px;
+ }
+`
+
+export const HeaderTitle = styled.h2`
+ position: absolute;
+ width: 70%;
+ left: 50%;
+ transform: translateX(-50%);
+
+ font-weight: 600;
+ font-size: 24px;
+ line-height: 42px;
+ color: #FFFFFF;
+ text-align: center;
+
+ @media ${devices.mobile} {
+ font-size: 19px;
+ line-height: 28px;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+`
+
+export const BlockTitle = styled.h3`
+ font-weight: normal;
+ font-size: 20px;
+ line-height: 26px;
+ text-transform: uppercase;
+
+ @media ${devices.mobile} {
+ font-size: 14px;
+ line-height: 14px;
+ color: rgba(255, 255, 255, 0.5);
+ text-transform: none;
+ }
+`
diff --git a/src/features/MatchPopup/types.tsx b/src/features/MatchPopup/types.tsx
new file mode 100644
index 00000000..cdbe42a3
--- /dev/null
+++ b/src/features/MatchPopup/types.tsx
@@ -0,0 +1,24 @@
+import type { Match } from 'features/Matches/hooks'
+
+export type MatchData = Pick | null
+
+export enum PopupPages {
+ PLAYLIST,
+ SETTINGS,
+}
+
+export enum PlayerPlaylistFormats {
+ ALL_MATCH_TIME = 1,
+ ALL_ACTIONS = 2,
+ SELECTED_ACTIONS = 3,
+}
+
+export enum Teams {
+ TEAM1,
+ TEAM2,
+}
diff --git a/src/features/Matches/helpers/prepareMatches.tsx b/src/features/Matches/helpers/prepareMatches.tsx
index 42a245a3..f2c25234 100644
--- a/src/features/Matches/helpers/prepareMatches.tsx
+++ b/src/features/Matches/helpers/prepareMatches.tsx
@@ -10,7 +10,6 @@ const prepareMatch = ({
date,
has_video,
id,
- live,
preview,
sport,
storage,
@@ -26,7 +25,6 @@ const prepareMatch = ({
id,
isClickable: (sub && access && (
has_video
- || live
|| storage
)),
preview,
diff --git a/src/features/Modal/index.tsx b/src/features/Modal/index.tsx
index 7a5b53e1..77894921 100644
--- a/src/features/Modal/index.tsx
+++ b/src/features/Modal/index.tsx
@@ -1,8 +1,9 @@
import type { ReactNode } from 'react'
-import { useRef } from 'react'
+import { useMemo } from 'react'
import ReactDOM from 'react-dom'
import { OutsideClick } from 'features/OutsideClick'
+import { Close } from 'features/Icons/Close'
import {
ModalContainer,
@@ -10,30 +11,43 @@ import {
ModalCloseButton,
} from './styled'
+const defaultCloseHandler = () => {}
+
type Props = {
children: ReactNode,
- close: () => void,
+ className?: string,
+ close?: () => void,
isOpen: boolean,
+ withCloseButton?: boolean,
}
export const Modal = ({
children,
- close,
+ className,
+ close = defaultCloseHandler,
isOpen,
+ withCloseButton = true,
}: Props) => {
- const modalRoot = useRef(document.getElementById('modal-root'))
+ const modalRoot = useMemo(
+ () => document.getElementById('modal-root'),
+ [],
+ )
- return isOpen
+ return (isOpen && modalRoot)
? ReactDOM.createPortal(
-
+
-
+ {withCloseButton && (
+
+
+
+ )}
{children}
,
- modalRoot.current as Element,
+ modalRoot,
)
: null
}
diff --git a/src/features/Modal/styled.tsx b/src/features/Modal/styled.tsx
index a911e28a..0b90ce94 100644
--- a/src/features/Modal/styled.tsx
+++ b/src/features/Modal/styled.tsx
@@ -2,8 +2,6 @@ import styled from 'styled-components/macro'
import { devices } from 'config/devices'
-import { CloseButton } from 'features/Common/CloseButton'
-
export const ModalContainer = styled.div`
position: fixed;
top: 0;
@@ -17,25 +15,31 @@ export const ModalContainer = styled.div`
color: white;
font-weight: 600;
`
+
export const ModalWindow = styled.div`
background-color: #313131;
position: relative;
padding: 15px;
box-shadow: 0px 5px 30px rgba(0, 0, 0, 0.7);
border-radius: 10px;
-
+
@media ${devices.mobile} {
height: 100vh;
width: 100vw;
border-radius: 0;
}
-
`
-export const ModalCloseButton = styled(CloseButton)`
- margin-right: 19px;
- margin-top: 16px;
- width: 16px;
- height: 16px;
+export const ModalCloseButton = styled.button.attrs({
+ 'aria-label': 'Close',
+})`
+ position: absolute;
+ top: 0;
+ right: 0;
+ border-style: none;
+ outline: none;
+ padding: 16px 19px;
cursor: pointer;
+ color: rgba(255, 255, 255, 0.5);
+ background-color: transparent;
`
diff --git a/src/features/PlayerPage/index.tsx b/src/features/PlayerPage/index.tsx
index 28258806..4efb0cb0 100644
--- a/src/features/PlayerPage/index.tsx
+++ b/src/features/PlayerPage/index.tsx
@@ -6,6 +6,7 @@ import { Matches } from 'features/Matches'
import { UserFavorites } from 'features/UserFavorites'
import { MainWrapper } from 'features/MainWrapper'
import { MediaQuery } from 'features/MediaQuery'
+import { MatchPopup } from 'features/MatchPopup'
import { usePlayerPage } from './hooks'
import { Content } from './styled'
@@ -19,6 +20,7 @@ export const PlayerPage = () => {
return (
+
diff --git a/src/features/Register/components/AdditionalSubscription/index.tsx b/src/features/Register/components/AdditionalSubscription/index.tsx
index 9d9d5437..3105b923 100644
--- a/src/features/Register/components/AdditionalSubscription/index.tsx
+++ b/src/features/Register/components/AdditionalSubscription/index.tsx
@@ -1,4 +1,5 @@
import { Checkbox } from 'features/Common'
+import { MediaQuery } from 'features/MediaQuery'
import { Price } from '../Price'
import {
@@ -15,7 +16,9 @@ type Props = {
export const AdditionalSubscription = ({ price, title }: Props) => (
-
+
+
+
{title}
diff --git a/src/features/Register/components/MainSubscription/index.tsx b/src/features/Register/components/MainSubscription/index.tsx
index 419b1886..93170df4 100644
--- a/src/features/Register/components/MainSubscription/index.tsx
+++ b/src/features/Register/components/MainSubscription/index.tsx
@@ -1,4 +1,5 @@
import { Radio } from 'features/Common'
+import { MediaQuery } from 'features/MediaQuery'
import { Price } from '../Price'
import {
@@ -15,7 +16,9 @@ type Props = {
export const MainSubscription = ({ price, title }: Props) => (
-
+
+
+
{title}
diff --git a/src/features/TeamPage/index.tsx b/src/features/TeamPage/index.tsx
index 527949ec..cb75fa3d 100644
--- a/src/features/TeamPage/index.tsx
+++ b/src/features/TeamPage/index.tsx
@@ -6,6 +6,7 @@ import { Matches } from 'features/Matches'
import { UserFavorites } from 'features/UserFavorites'
import { MainWrapper } from 'features/MainWrapper'
import { MediaQuery } from 'features/MediaQuery'
+import { MatchPopup } from 'features/MatchPopup'
import { useTeamPage } from './hooks'
import { Content } from './styled'
@@ -19,6 +20,7 @@ export const TeamPage = () => {
return (
+
diff --git a/src/features/TournamentPage/index.tsx b/src/features/TournamentPage/index.tsx
index 15b37c34..01279294 100644
--- a/src/features/TournamentPage/index.tsx
+++ b/src/features/TournamentPage/index.tsx
@@ -6,6 +6,7 @@ import { Matches } from 'features/Matches'
import { MainWrapper } from 'features/MainWrapper'
import { UserFavorites } from 'features/UserFavorites'
import { MediaQuery } from 'features/MediaQuery'
+import { MatchPopup } from 'features/MatchPopup'
import { useTournamentPage } from './hooks'
import { Content } from './styled'
@@ -19,6 +20,7 @@ export const TournamentPage = () => {
return (
+
diff --git a/src/hooks/useStorage/index.tsx b/src/hooks/useStorage/index.tsx
index 7e67e8dd..d8e901e7 100644
--- a/src/hooks/useStorage/index.tsx
+++ b/src/hooks/useStorage/index.tsx
@@ -1,8 +1,4 @@
-import {
- useState,
- useCallback,
- useEffect,
-} from 'react'
+import { useState, useEffect } from 'react'
import { queryParamStorage } from 'features/QueryParamsStorage'
@@ -46,11 +42,6 @@ const createHook = (storage: Storage) => (
const [state, setState] = useState(getInitialState)
- const setStateAndSave = useCallback((value: T) => {
- storage.setItem(key, JSON.stringify(value, dateReplacer))
- setState(value)
- }, [key])
-
useEffect(() => {
const storeValue = readStorageInitialValue(storage, key)
const isValid = validator(storeValue)
@@ -59,7 +50,11 @@ const createHook = (storage: Storage) => (
}
}, [key, validator])
- return [state, setStateAndSave] as const
+ useEffect(() => {
+ storage.setItem(key, JSON.stringify(state, dateReplacer))
+ }, [key, state])
+
+ return [state, setState] as const
}
)
diff --git a/src/requests/getFullMatchDuration.tsx b/src/requests/getFullMatchDuration.tsx
new file mode 100644
index 00000000..dd9a1e90
--- /dev/null
+++ b/src/requests/getFullMatchDuration.tsx
@@ -0,0 +1,24 @@
+import pipe from 'lodash/fp/pipe'
+import orderBy from 'lodash/fp/orderBy'
+import sumBy from 'lodash/fp/sumBy'
+import uniqBy from 'lodash/fp/uniqBy'
+
+import type { Videos, Video } from './getVideos'
+import { getVideos } from './getVideos'
+
+const calculateDuration = (videos: Videos) => {
+ const durationMs = pipe(
+ orderBy(({ quality }: Video) => Number(quality), 'desc'),
+ uniqBy(({ period }: Video) => period),
+ sumBy(({ duration }: Video) => duration),
+ )(videos)
+ return durationMs / 1000
+}
+
+/**
+ * Временный способ получения длительности матча
+ */
+export const getFullMatchDuration = async (...args: Parameters) => {
+ const videos = await getVideos(...args)
+ return calculateDuration(videos)
+}
diff --git a/src/requests/getMatchPlaylists.tsx b/src/requests/getMatchPlaylists.tsx
new file mode 100644
index 00000000..1ac1477a
--- /dev/null
+++ b/src/requests/getMatchPlaylists.tsx
@@ -0,0 +1,85 @@
+import {
+ DATA_URL,
+ PROCEDURES,
+ SportTypes,
+} from 'config'
+import { callApi, getSportLexic } from 'helpers'
+
+import { getFullMatchDuration } from './getFullMatchDuration'
+
+const proc = PROCEDURES.ott_match_popup
+
+type Args = {
+ matchId: number,
+ selectedActions: Array,
+ sportType: SportTypes,
+}
+
+type PlaylistData = {
+ /** episode end */
+ e: number,
+
+ /** match half/time */
+ h: number,
+
+ /** episode start */
+ s: number,
+}
+
+type Playlist = {
+ data: Array,
+ dur: number,
+}
+
+type Player = {
+ id: number,
+ name_eng: string,
+ name_rus: string,
+ num: string,
+}
+
+export type Players = Array
+
+export type MatchPlaylists = {
+ ball_in_play: Playlist,
+ fullMatchDuration: number,
+ goals: Playlist,
+ highlights: Playlist,
+ players1: Players,
+ players2: Players,
+}
+
+type Response = {
+ data?: MatchPlaylists,
+}
+
+export const getMatchPlaylists = async ({
+ matchId,
+ selectedActions,
+ sportType,
+}: Args) => {
+ const config = {
+ body: {
+ params: {
+ _p_actions: selectedActions,
+ _p_match_id: matchId,
+ },
+ proc,
+ },
+ }
+
+ const playlistPromise: Promise = callApi({
+ config,
+ url: `${DATA_URL}/${getSportLexic(sportType)}`,
+ })
+
+ const matchDurationPromise = getFullMatchDuration(sportType, matchId)
+
+ const [playlist, fullMatchDuration] = await Promise.all(
+ [playlistPromise, matchDurationPromise],
+ )
+
+ return playlist.data
+ ? { ...playlist.data, fullMatchDuration }
+ : null
+}
diff --git a/src/requests/getSportActions.tsx b/src/requests/getSportActions.tsx
new file mode 100644
index 00000000..ade5ac3b
--- /dev/null
+++ b/src/requests/getSportActions.tsx
@@ -0,0 +1,35 @@
+import {
+ DATA_URL,
+ PROCEDURES,
+ SportTypes,
+} from 'config'
+import { callApi, getSportLexic } from 'helpers'
+
+const proc = PROCEDURES.ott_match_popup_actions
+
+type Action = {
+ id: number,
+ lexic: number,
+}
+
+export type Actions = Array
+
+type Response = {
+ data: Actions,
+}
+
+export const getSportActions = async (sportType: SportTypes) => {
+ const config = {
+ body: {
+ params: {},
+ proc,
+ },
+ }
+
+ const response: Response = await callApi({
+ config,
+ url: `${DATA_URL}/${getSportLexic(sportType)}`,
+ })
+
+ return response.data
+}
diff --git a/src/requests/getVideos.tsx b/src/requests/getVideos.tsx
index 5b38386d..14cb2bd7 100644
--- a/src/requests/getVideos.tsx
+++ b/src/requests/getVideos.tsx
@@ -1,7 +1,15 @@
+import isEmpty from 'lodash/isEmpty'
+import filter from 'lodash/filter'
+
import { API_ROOT, SportTypes } from 'config'
import { callApi } from 'helpers'
-type Video = {
+const filterByIds = (videos: Videos) => {
+ const zeroIdVideos = filter(videos, { abc: '0' })
+ return isEmpty(zeroIdVideos) ? videos : zeroIdVideos
+}
+
+export type Video = {
/** id дорожки */
abc: string,
duration: number,
@@ -27,5 +35,5 @@ export const getVideos = (
return callApi({
config,
url: `${API_ROOT}/videoapi`,
- })
+ }).then(filterByIds)
}
diff --git a/src/requests/index.tsx b/src/requests/index.tsx
index 9839374f..87f5f39a 100644
--- a/src/requests/index.tsx
+++ b/src/requests/index.tsx
@@ -21,3 +21,5 @@ export * from './getPlayerInfo'
export * from './getLiveVideos'
export * from './getMatchLastWatchSeconds'
export * from './getMatchesPreviewImages'
+export * from './getSportActions'
+export * from './getMatchPlaylists'