diff --git a/Makefile b/Makefile index e663d6c5..15d8ae0f 100644 --- a/Makefile +++ b/Makefile @@ -204,4 +204,3 @@ test: generate-ssl-keys: openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 - \ No newline at end of file diff --git a/package.json b/package.json index c023ddac..8e59a5b2 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/ErrorBoundary/index.tsx b/src/components/ErrorBoundary/index.tsx index 4f3ad182..b3a27e45 100644 --- a/src/components/ErrorBoundary/index.tsx +++ b/src/components/ErrorBoundary/index.tsx @@ -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) => ( - - {children} - - - )} - > - { children } - -) +interface State { + hasError: boolean, +} + +class ErrorBoundary extends Component { + // 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 && } + {children} + + ) + } +} +export default ErrorBoundary diff --git a/src/config/clients/lff.tsx b/src/config/clients/lff.tsx index 2a00328c..02dedb6b 100644 --- a/src/config/clients/lff.tsx +++ b/src/config/clients/lff.tsx @@ -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);', diff --git a/src/config/lexics/matchDownload.tsx b/src/config/lexics/matchDownload.tsx index 43f7291a..d8c12bf3 100644 --- a/src/config/lexics/matchDownload.tsx +++ b/src/config/lexics/matchDownload.tsx @@ -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, diff --git a/src/features/App/index.tsx b/src/features/App/index.tsx index d65ffd0a..af66da71 100644 --- a/src/features/App/index.tsx +++ b/src/features/App/index.tsx @@ -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' diff --git a/src/features/AuthServiceApp/components/ConfirmPopup/index.tsx b/src/features/AuthServiceApp/components/ConfirmPopup/index.tsx index 4aae7ef3..1eb79dfb 100644 --- a/src/features/AuthServiceApp/components/ConfirmPopup/index.tsx +++ b/src/features/AuthServiceApp/components/ConfirmPopup/index.tsx @@ -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 { diff --git a/src/features/GlobalStyles/index.tsx b/src/features/GlobalStyles/index.tsx index 33028585..528352bb 100644 --- a/src/features/GlobalStyles/index.tsx +++ b/src/features/GlobalStyles/index.tsx @@ -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 { diff --git a/src/features/MatchPage/store/hooks/index.tsx b/src/features/MatchPage/store/hooks/index.tsx index 1d7ee57a..30bf93db 100644 --- a/src/features/MatchPage/store/hooks/index.tsx +++ b/src/features/MatchPage/store/hooks/index.tsx @@ -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, diff --git a/src/features/MatchPage/store/hooks/usePlayersStats.tsx b/src/features/MatchPage/store/hooks/usePlayersStats.tsx index f46f1c69..d608a597 100644 --- a/src/features/MatchPage/store/hooks/usePlayersStats.tsx +++ b/src/features/MatchPage/store/hooks/usePlayersStats.tsx @@ -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>, @@ -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, diff --git a/src/features/MatchPage/store/hooks/useStatsTab.tsx b/src/features/MatchPage/store/hooks/useStatsTab.tsx index b2658dbd..2f1666f1 100644 --- a/src/features/MatchPage/store/hooks/useStatsTab.tsx +++ b/src/features/MatchPage/store/hooks/useStatsTab.tsx @@ -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 diff --git a/src/features/MatchPage/store/hooks/useTeamsStats.tsx b/src/features/MatchPage/store/hooks/useTeamsStats.tsx index b2c0c862..2e92672d 100644 --- a/src/features/MatchPage/store/hooks/useTeamsStats.tsx +++ b/src/features/MatchPage/store/hooks/useTeamsStats.tsx @@ -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>, @@ -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, diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx index 9b29e6fd..112456ac 100644 --- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx @@ -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(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 ? ( - + 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 ? ( - + / 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 = ({ )} + {isTooltipShown && modalRoot.current && createPortal( + + {tooltipText} + , + modalRoot.current, + )} ) } diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx index c3f1bf7d..ff5bd97e 100644 --- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx @@ -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>( + 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 diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx index c1c05cde..dc5f59f6 100644 --- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx @@ -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 = () => { {map(teamsStats[profile.team1.id], (team1StatItem) => { const team2StatItem = getStatItemById(team1StatItem.param1.id) - const statItemTitle = team1StatItem[`name_${shortSuffix}`] return ( @@ -80,7 +76,7 @@ export const TeamsStatsTable = () => { /> - {statItemTitle} + ({ : '')} ` -export const StatItemTitle = styled.span` +export const StatItemTitle = styled(T9n)` color: ${({ theme }) => theme.colors.white}; letter-spacing: -0.078px; text-transform: uppercase; diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx index 485528dc..787875a7 100644 --- a/src/features/StreamPlayer/hooks/index.tsx +++ b/src/features/StreamPlayer/hooks/index.tsx @@ -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) diff --git a/src/features/UserAccount/components/PersonalInfoForm/index.tsx b/src/features/UserAccount/components/PersonalInfoForm/index.tsx index cede3ac4..d3dd78ee 100644 --- a/src/features/UserAccount/components/PersonalInfoForm/index.tsx +++ b/src/features/UserAccount/components/PersonalInfoForm/index.tsx @@ -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 (
{ > - {isPrivacyPolicyShown && ( - - - - )} + + + ) diff --git a/src/helpers/callApi/logoutIfUnauthorized.tsx b/src/helpers/callApi/logoutIfUnauthorized.tsx index d9958362..18e71881 100644 --- a/src/helpers/callApi/logoutIfUnauthorized.tsx +++ b/src/helpers/callApi/logoutIfUnauthorized.tsx @@ -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) } diff --git a/src/index.tsx b/src/index.tsx index 8622573d..81041121 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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'))