develop #222

Merged
andrey.dekterev merged 7 commits from develop into master 3 years ago
  1. 1
      Makefile
  2. 1
      package.json
  3. 54
      src/components/ErrorBoundary/index.tsx
  4. 2
      src/config/clients/lff.tsx
  5. 2
      src/config/lexics/matchDownload.tsx
  6. 2
      src/features/App/index.tsx
  7. 3
      src/features/AuthServiceApp/components/ConfirmPopup/index.tsx
  8. 12
      src/features/GlobalStyles/index.tsx
  9. 2
      src/features/MatchPage/store/hooks/index.tsx
  10. 8
      src/features/MatchPage/store/hooks/usePlayersStats.tsx
  11. 24
      src/features/MatchPage/store/hooks/useStatsTab.tsx
  12. 6
      src/features/MatchPage/store/hooks/useTeamsStats.tsx
  13. 68
      src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx
  14. 26
      src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx
  15. 6
      src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx
  16. 6
      src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx
  17. 16
      src/features/StreamPlayer/hooks/index.tsx
  18. 28
      src/features/UserAccount/components/PersonalInfoForm/index.tsx
  19. 9
      src/helpers/callApi/logoutIfUnauthorized.tsx
  20. 15
      src/index.tsx

@ -204,4 +204,3 @@ test:
generate-ssl-keys:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365

@ -23,7 +23,6 @@
},
"dependencies": {
"@reactour/tour": "^3.3.0",
"@sentry/react": "^7.53.1",
"@stripe/react-stripe-js": "^1.4.0",
"@stripe/stripe-js": "^1.13.2",
"babel-polyfill": "^6.26.0",

@ -1,8 +1,8 @@
import React from 'react'
// eslint-disable react/destructuring-assignment
import type{ ReactNode } from 'react'
import { Component } from 'react'
import * as Sentry from '@sentry/react'
import type{ ErrorInfo, ReactNode } from 'react'
import { Error } from '../Error'
@ -10,15 +10,41 @@ interface Props {
children?: ReactNode,
}
export const ErrorBoundary = ({ children }: Props) => (
<Sentry.ErrorBoundary fallback={(
<>
{children}
<Error />
</>
)}
>
{ children }
</Sentry.ErrorBoundary>
)
interface State {
hasError: boolean,
}
class ErrorBoundary extends Component<Props, State> {
// eslint-disable-next-line react/state-in-constructor
public state: State = {
hasError: false,
}
public static getDerivedStateFromError(_: Error): State {
// Update state so the next render will show the fallback UI.
return { hasError: true }
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// eslint-disable-next-line no-console
console.error(
'Uncaught error:',
error,
errorInfo,
)
}
public render() {
const { hasError } = this.state
const { children } = this.props
return (
<>
{hasError && <Error />}
{children}
</>
)
}
}
export default ErrorBoundary

@ -19,7 +19,7 @@ export const lff: ClientConfig = {
disabledHighlights: true,
disabledPreferences: true,
name: ClientNames.Lff,
privacyLink: '/clients/instat/terms-and-conditions.html',
privacyLink: '/privacy-policy-and-statement',
showSearch: true,
styles: {
background: 'background-image: url(/images/Checker.png);',

@ -1,7 +1,7 @@
export const matchDownload = {
choose_what_to_download: 20193,
download_files_for_periods: 20197,
download_full_match: 9435,
download_full_match: 20198,
download_single_file: 20196,
entire_record: 20195,
in_game_time_only: 20194,

@ -23,7 +23,7 @@ import { GlobalStyles } from 'features/GlobalStyles'
import { Theme } from 'features/Theme'
import { UnavailableText } from 'components/UnavailableText'
import { ErrorBoundary } from 'components/ErrorBoundary'
import ErrorBoundary from 'components/ErrorBoundary'
import { AuthenticatedApp } from './AuthenticatedApp'
import { useAuthStore } from '../AuthStore'

@ -1,7 +1,6 @@
import { T9n } from 'features/T9n'
import { client } from 'features/AuthServiceApp/config/clients'
import { client } from 'config/clients'
import { AUTH_SERVICE } from 'config/routes'
import {

@ -1,6 +1,4 @@
import { createGlobalStyle, css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { createGlobalStyle } from 'styled-components/macro'
export const GlobalStyles = createGlobalStyle`
*, *:before, *:after {
@ -9,13 +7,7 @@ export const GlobalStyles = createGlobalStyle`
html {
font-size: calc(2px + 1vw);
overflow-y: hidden;
${isMobileDevice
? css`
overflow-y: auto;
`
: ''};
overflow-y: auto;
}
body {

@ -296,7 +296,6 @@ export const useMatchPage = () => {
} = useTeamsStats({
matchProfile,
playingProgress,
selectedPlaylist,
selectedStatsTable,
selectedTab,
setIsTeamsStatsFetching,
@ -311,7 +310,6 @@ export const useMatchPage = () => {
} = usePlayersStats({
matchProfile,
playingProgress,
selectedPlaylist,
selectedStatsTable,
selectedTab,
setIsPlayersStatsFetching,

@ -36,9 +36,7 @@ import { getLocalStorageItem } from 'helpers/getLocalStorage'
import { useObjectState, usePageParams } from 'hooks'
import type{ PlaylistOption } from 'features/MatchPage/types'
import { StatsType, Tabs as StatsTabs } from 'features/MatchSidePlaylists/components/TabStats/config'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import { TOUR_COMPLETED_STORAGE_KEY } from 'features/MatchTour'
import { Tabs } from 'features/MatchSidePlaylists/config'
@ -52,7 +50,6 @@ const STATS_POLL_INTERVAL = 30000
type UsePlayersStatsArgs = {
matchProfile: MatchInfo,
playingProgress: number,
selectedPlaylist?: PlaylistOption,
selectedStatsTable: StatsTabs,
selectedTab: Tabs,
setIsPlayersStatsFetching: Dispatch<SetStateAction<boolean>>,
@ -67,7 +64,6 @@ type PlayersData = {
export const usePlayersStats = ({
matchProfile,
playingProgress,
selectedPlaylist,
selectedStatsTable,
selectedTab,
setIsPlayersStatsFetching,
@ -147,8 +143,7 @@ export const usePlayersStats = ({
const isTeam1Selected = selectedStatsTable === StatsTabs.TEAM1
if (
selectedPlaylist?.id !== FULL_GAME_KEY
|| selectedTab !== Tabs.STATS
selectedTab !== Tabs.STATS
|| !includes([StatsTabs.TEAM1, StatsTabs.TEAM2], selectedStatsTable)
|| !matchProfile?.team1.id
|| !matchProfile?.team2.id
@ -195,7 +190,6 @@ export const usePlayersStats = ({
setIsPlayersStatsFetching(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, REQUEST_DELAY), [
selectedPlaylist?.id,
fetchPlayers,
fetchPlayersStats,
setPlayersStats,

@ -1,10 +1,8 @@
import { useState, useEffect } from 'react'
import map from 'lodash/map'
import isEqual from 'lodash/isEqual'
import type {
Episode,
Episodes,
Events,
MatchInfo,
@ -36,18 +34,6 @@ type PlayNextEpisodeArgs = {
order?: number,
}
const EPISODE_TIMESTAMP_OFFSET = 0.001
const addOffset = ({
e,
h,
s,
}: Episode) => ({
e: e + EPISODE_TIMESTAMP_OFFSET,
h,
s: s + EPISODE_TIMESTAMP_OFFSET,
})
export const useStatsTab = ({
disablePlayingEpisodes,
handlePlaylistClick,
@ -82,15 +68,7 @@ export const useStatsTab = ({
}
const getEpisodesToPlay = (episodes: Episodes) => map(episodes, (episode, i) => ({
episodes: [
/** При проигрывании нового эпизода с такими же e и s, как у текущего
воспроизведение начинается не с начала, чтобы пофиксить это добавляем
небольшой оффсет
*/
isEqual(episode, selectedPlaylist?.episodes[0])
? addOffset(episode)
: episode,
],
episodes: [episode],
id: i,
type: PlaylistTypes.EVENT,
})) as Array<EventPlaylistOption>

@ -25,9 +25,7 @@ import { usePageParams } from 'hooks'
import { getLocalStorageItem } from 'helpers/getLocalStorage'
import type { PlaylistOption } from 'features/MatchPage/types'
import { StatsType, Tabs as StatsTab } from 'features/MatchSidePlaylists/components/TabStats/config'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import { TOUR_COMPLETED_STORAGE_KEY } from 'features/MatchTour'
import { Tabs } from 'features/MatchSidePlaylists/config'
@ -40,7 +38,6 @@ const STATS_POLL_INTERVAL = 30000
type UseTeamsStatsArgs = {
matchProfile: MatchInfo,
playingProgress: number,
selectedPlaylist?: PlaylistOption,
selectedStatsTable: StatsTab,
selectedTab: Tabs,
setIsTeamsStatsFetching: Dispatch<SetStateAction<boolean>>,
@ -54,7 +51,6 @@ type TeamsStats = {
export const useTeamsStats = ({
matchProfile,
playingProgress,
selectedPlaylist,
selectedStatsTable,
selectedTab,
setIsTeamsStatsFetching,
@ -101,7 +97,6 @@ export const useTeamsStats = ({
if (
!sportName
|| selectedPlaylist?.id !== FULL_GAME_KEY
|| !videoBounds
|| selectedTab !== Tabs.STATS
|| selectedStatsTable !== StatsTab.TEAMS
@ -129,7 +124,6 @@ export const useTeamsStats = ({
matchProfile?.video_bounds,
matchProfile?.live,
matchScore?.video_bounds,
selectedPlaylist?.id,
matchId,
setIsTeamsStatsFetching,
sportName,

@ -1,11 +1,16 @@
import { Fragment, useRef } from 'react'
import { createPortal } from 'react-dom'
import { useQueryClient } from 'react-query'
import { useTour } from '@reactour/tour'
import isNumber from 'lodash/isNumber'
import { KEYBOARD_KEYS, querieKeys } from 'config'
import {
isMobileDevice,
KEYBOARD_KEYS,
querieKeys,
} from 'config'
import type {
Param,
@ -14,7 +19,12 @@ import type {
} from 'requests'
import { getStatsEvents } from 'requests'
import { usePageParams, useEventListener } from 'hooks'
import {
usePageParams,
useEventListener,
useTooltip,
useModalRoot,
} from 'hooks'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import { useMatchPageStore } from 'features/MatchPage/store'
@ -24,6 +34,7 @@ import { Spotlight, Steps } from 'features/MatchTour'
import { StatsType } from '../TabStats/config'
import { CircleAnimationBar } from '../CircleAnimationBar'
import { Tooltip } from '../TabStats/styled'
import {
CellContainer,
ParamValueContainer,
@ -59,14 +70,28 @@ export const Cell = ({
watchAllEpisodesTimer,
} = useMatchPageStore()
const { shortSuffix, suffix } = useLexicsStore()
const { suffix } = useLexicsStore()
const { currentStep, isOpen } = useTour()
const client = useQueryClient()
const {
isTooltipShown,
onMouseLeave,
onMouseOver,
tooltipStyle,
tooltipText,
} = useTooltip()
const modalRoot = useModalRoot()
const { translate } = useLexicsStore()
const matchScore = client.getQueryData<MatchScore>(querieKeys.matchScore)
const isTeam1 = teamId === profile?.team1.id
const getDisplayedValue = (val: number | null) => (
isNumber(val) ? String(val) : '-'
)
@ -104,7 +129,7 @@ export const Cell = ({
setEpisodeInfo({
episodesCount: param.val!,
paramName,
playerOrTeamName: teamId === profile?.team1.id
playerOrTeamName: isTeam1
? profile.team1[`name_${suffix}`]
: profile?.team2[`name_${suffix}`] || '',
})
@ -122,7 +147,7 @@ export const Cell = ({
? teamStatItem.param1
: teamStatItem.param2)
param && onParamClick(param, teamStatItem[`name_${shortSuffix}`])
param && onParamClick(param, translate(teamStatItem.lexic))
},
event: 'keydown',
target: paramValueContainerRef,
@ -137,7 +162,7 @@ export const Cell = ({
&& playingData.team.paramId === teamStatItem.param1.id
&& playingData.team.id === teamId
? (
<ParamValue>
<ParamValue onMouseLeave={onMouseLeave}>
<CircleAnimationBar
text={getDisplayedValue(teamStatItem.param1.val)}
size={22}
@ -147,13 +172,21 @@ export const Cell = ({
: (
<ParamValue
clickable={isClickable(teamStatItem.param1)}
onClick={() => onParamClick(teamStatItem.param1, teamStatItem[`name_${shortSuffix}`])}
onClick={() => onParamClick(teamStatItem.param1, translate(teamStatItem.lexic))}
data-param-id={teamStatItem.param1.id}
hasValue={Boolean(teamStatItem.param1.val)}
// eslint-disable-next-line react/jsx-props-no-spreading
{...firstClickableParam === teamStatItem.param1 && {
'data-step': Steps.ClickToWatchPlaylist,
}}
// eslint-disable-next-line react/jsx-props-no-spreading
{...!isMobileDevice && isNumber(teamStatItem.param1.val) && teamStatItem.param2 && {
onMouseLeave,
onMouseOver: onMouseOver({
horizontalPosition: isTeam1 ? 'left' : 'right',
tooltipText: translate(teamStatItem.param1.lexic),
}),
}}
>
{getDisplayedValue(teamStatItem.param1.val)}
{firstClickableParam === teamStatItem.param1
@ -168,7 +201,7 @@ export const Cell = ({
&& playingData.team.paramId === teamStatItem.param2.id
&& playingData.team.id === teamId
? (
<ParamValue>
<ParamValue onMouseLeave={onMouseLeave}>
<CircleAnimationBar
text={getDisplayedValue(teamStatItem.param2.val)}
size={22}
@ -180,13 +213,24 @@ export const Cell = ({
<Divider>/</Divider>
<ParamValue
clickable={isClickable(teamStatItem.param2)}
onClick={() => onParamClick(teamStatItem.param2!, teamStatItem[`name_${shortSuffix}`])}
onClick={() => onParamClick(
teamStatItem.param2!,
translate(teamStatItem.lexic),
)}
data-param-id={teamStatItem.param2.id}
hasValue={Boolean(teamStatItem.param2.val)}
// eslint-disable-next-line react/jsx-props-no-spreading
{...firstClickableParam === teamStatItem.param2 && {
'data-step': Steps.ClickToWatchPlaylist,
}}
// eslint-disable-next-line react/jsx-props-no-spreading
{...!isMobileDevice && isNumber(teamStatItem.param2.val) && {
onMouseLeave,
onMouseOver: onMouseOver({
horizontalPosition: isTeam1 ? 'left' : 'right',
tooltipText: translate(teamStatItem.param2.lexic),
}),
}}
>
{getDisplayedValue(teamStatItem.param2.val)}
{firstClickableParam === teamStatItem.param2
@ -198,6 +242,12 @@ export const Cell = ({
</Fragment>
)}
</ParamValueContainer>
{isTooltipShown && modalRoot.current && createPortal(
<Tooltip style={tooltipStyle}>
{tooltipText}
</Tooltip>,
modalRoot.current,
)}
</CellContainer>
)
}

@ -1,8 +1,12 @@
import { useEffect } from 'react'
import { useEffect, useMemo } from 'react'
import find from 'lodash/find'
import reduce from 'lodash/reduce'
import type { TeamStatItem } from 'requests'
import { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsConfig } from 'features/LexicsStore'
export const useTeamsStatsTable = () => {
const {
@ -13,6 +17,26 @@ export const useTeamsStatsTable = () => {
teamsStats,
} = useMatchPageStore()
const lexicsIds = useMemo(
() => (
profile
? reduce<TeamStatItem, Array<number>>(
teamsStats[profile.team1.id],
(acc, curr) => {
!acc.includes(curr.lexic) && acc.push(curr.lexic)
!acc.includes(curr.param1.lexic) && acc.push(curr.param1.lexic)
curr.param2 && !acc.includes(curr.param2.lexic) && acc.push(curr.param2.lexic)
return acc
},
[],
)
: []),
[profile, teamsStats],
)
useLexicsConfig(lexicsIds)
const getStatItemById = (paramId: number) => {
if (!profile) return null

@ -3,7 +3,6 @@ import { useTour } from '@reactour/tour'
import map from 'lodash/map'
import { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsStore } from 'features/LexicsStore'
import { Loader } from 'features/Loader'
import { defaultTheme } from 'features/Theme/config'
@ -32,8 +31,6 @@ export const TeamsStatsTable = () => {
getStatItemById,
} = useTeamsStatsTable()
const { shortSuffix } = useLexicsStore()
const { isOpen } = useTour()
if (!profile) return null
@ -69,7 +66,6 @@ export const TeamsStatsTable = () => {
<tbody>
{map(teamsStats[profile.team1.id], (team1StatItem) => {
const team2StatItem = getStatItemById(team1StatItem.param1.id)
const statItemTitle = team1StatItem[`name_${shortSuffix}`]
return (
<Row key={team1StatItem.param1.id}>
@ -80,7 +76,7 @@ export const TeamsStatsTable = () => {
/>
<CellContainer>
<StatItemTitle>{statItemTitle}</StatItemTitle>
<StatItemTitle t={team1StatItem.lexic} />
</CellContainer>
<Cell

@ -4,6 +4,7 @@ import { isMobileDevice } from 'config'
import { Name } from 'features/Name'
import { customScrollbar } from 'features/Common'
import { T9n } from 'features/T9n'
export const Container = styled.div``
@ -59,11 +60,12 @@ export const CellContainer = styled.td`
}
:first-child, :last-child {
width: 32px;
width: 50px;
}
:first-child {
padding-left: 12px;
text-align: left;
}
:last-child {
@ -122,7 +124,7 @@ export const ParamValue = styled.span.attrs(({ clickable }: TParamValue) => ({
: '')}
`
export const StatItemTitle = styled.span`
export const StatItemTitle = styled(T9n)`
color: ${({ theme }) => theme.colors.white};
letter-spacing: -0.078px;
text-transform: uppercase;

@ -339,7 +339,7 @@ export const useVideoPlayer = ({
setPlayerState({ playedProgress: value })
timeForStatistics.current = (value + chapter.startMs) / 1000
setPlayingProgress(Math.floor(value / 1000))
chapter.isFullMatchChapter && setPlayingProgress(Math.floor(value / 1000))
progressChangeCallback(value / 1000)
}
@ -530,6 +530,20 @@ export const useVideoPlayer = ({
selectedPlaylist,
])
/**
* Для воcпроизведения нового эпизода, аналогичного текущему, с начала
*/
useEffect(() => {
if (chaptersProps[0].isFullMatchChapter) return
setPlayerState({
...initialState,
chapters: chaptersProps,
playing: true,
seek: chaptersProps[0].startOffsetMs / 1000,
})
}, [chaptersProps, setPlayerState])
useEffect(() => {
if ((
chapters[0]?.isFullMatchChapter)

@ -1,9 +1,6 @@
import { useMemo } from 'react'
import { client } from 'config/clients'
import { formIds } from 'config/form'
import { AUTH_SERVICE } from 'config/routes'
import { ClientNames } from 'config/clients/types'
import { Combobox } from 'features/Combobox'
import { Input } from 'features/Common'
@ -47,15 +44,6 @@ export const PersonalInfoForm = (props: Props) => {
updateFormValue,
} = useUserInfo(props)
const isPrivacyPolicyShown = useMemo(() => {
switch (client.name) {
case ClientNames.Facr:
return false
default:
return true
}
}, [])
return (
<Form>
<Input
@ -136,15 +124,13 @@ export const PersonalInfoForm = (props: Props) => {
>
<T9n t='terms_and_conditions' />
</PrivacyPolicyLink>
{isPrivacyPolicyShown && (
<PrivacyPolicyLink
target='_blank'
href={`${AUTH_SERVICE}${client.privacyLink}`}
id='personal_policy'
>
<T9n t='privacy_policy_and_statement' />
</PrivacyPolicyLink>
)}
<PrivacyPolicyLink
target='_blank'
href={`${AUTH_SERVICE}${client.privacyLink}`}
id='personal_policy'
>
<T9n t='privacy_policy_and_statement' />
</PrivacyPolicyLink>
</PrivacyWrapper>
</Form>
)

@ -1,13 +1,5 @@
import * as Sentry from '@sentry/react'
export const logoutIfUnauthorized = async (response: Response) => {
/* отключили из-за доступа без авторизации */
const body = await response.json()
if (response.status === 400) {
Sentry.captureException(body)
}
if (response.status === 401 || response.status === 403) {
window.dispatchEvent(new Event('FORBIDDEN_REQUEST'))
}
@ -16,5 +8,6 @@ export const logoutIfUnauthorized = async (response: Response) => {
// eslint-disable-next-line no-console
console.error(error)
const body = await response.json()
return Promise.reject(body)
}

@ -5,24 +5,11 @@ import {
} from 'react'
import ReactDOM from 'react-dom'
import * as Sentry from '@sentry/react'
import { BrowserTracing } from '@sentry/react'
import { isIOS, ENV } from 'config'
import { isIOS } from 'config/userAgent'
// import { makeServer } from 'utilits/mirage/Mirage'
import * as serviceWorker from './serviceWorker'
if (process.env.NODE_ENV !== 'development') {
Sentry.init({
dsn: 'https://bbe0cdfb954644ebaf3be16bb472cc3d@sentry.insports.tv/21',
environment: ENV,
integrations: [new BrowserTracing()],
tracesSampleRate: 1.0,
})
}
export const App = process.env.REACT_APP_TYPE === 'auth-service'
? lazy(() => import('features/AuthServiceApp'))
: lazy(() => import('features/App'))

Loading…
Cancel
Save