Compare commits

..

4 Commits

Author SHA1 Message Date
andreidekterev 9397ba64b4 fix(#168): add isDownloaded state 2 years ago
andreidekterev b1f82cd1b5 fix(#168): fix download video for completed status 2 years ago
andreidekterev d1eb9868e9 fix(#168): fix refetch status download 2 years ago
andreidekterev 9271894154 feat(#168): add video download process 2 years ago
  1. 53
      .drone.yml
  2. 9
      Makefile
  3. BIN
      public/clients/facr/favicon/android-chrome-192x192.png
  4. BIN
      public/clients/facr/favicon/android-chrome-512x512.png
  5. BIN
      public/clients/facr/favicon/apple-touch-icon.png
  6. BIN
      public/clients/facr/favicon/favicon-16x16.png
  7. BIN
      public/clients/facr/favicon/favicon-32x32.png
  8. BIN
      public/clients/facr/favicon/favicon.ico
  9. BIN
      public/clients/fqtv/favicon/android-chrome-192x192.png
  10. BIN
      public/clients/fqtv/favicon/android-chrome-512x512.png
  11. BIN
      public/clients/fqtv/favicon/apple-touch-icon.png
  12. BIN
      public/clients/fqtv/favicon/favicon-16x16.png
  13. BIN
      public/clients/fqtv/favicon/favicon-32x32.png
  14. BIN
      public/clients/fqtv/favicon/favicon.ico
  15. BIN
      public/clients/india/favicon/android-chrome-192x192.png
  16. BIN
      public/clients/india/favicon/android-chrome-512x512.png
  17. BIN
      public/clients/india/favicon/apple-touch-icon.png
  18. 12
      public/clients/india/favicon/browserconfig.xml
  19. BIN
      public/clients/india/favicon/favicon-16x16.png
  20. BIN
      public/clients/india/favicon/favicon-32x32.png
  21. BIN
      public/clients/india/favicon/favicon.ico
  22. 19
      public/clients/india/favicon/manifest.json
  23. BIN
      public/clients/india/favicon/mstile-144x144.png
  24. BIN
      public/clients/india/favicon/mstile-150x150.png
  25. BIN
      public/clients/india/favicon/mstile-310x310.png
  26. BIN
      public/clients/india/favicon/mstile-70x70.png
  27. 18
      public/clients/india/favicon/safari-pinned-tab.svg
  28. 1903
      public/clients/india/privacy-policy-and-statement.html
  29. 4385
      public/clients/india/terms-and-conditions.html
  30. BIN
      public/clients/lff/favicon/android-chrome-192x192.png
  31. BIN
      public/clients/lff/favicon/android-chrome-512x512.png
  32. BIN
      public/clients/lff/favicon/apple-touch-icon.png
  33. BIN
      public/clients/lff/favicon/favicon-16x16.png
  34. BIN
      public/clients/lff/favicon/favicon-32x32.png
  35. BIN
      public/clients/lff/favicon/favicon.ico
  36. 4
      public/images/matchTabs/likes.svg
  37. 23
      public/silent-refresh.html
  38. 19
      src/components/Ads/components/AdComponent/hooks.tsx
  39. 6
      src/components/Ads/components/MobileAd/styled.tsx
  40. 2
      src/config/clients/india.tsx
  41. 18
      src/config/lexics/indexLexics.tsx
  42. 4
      src/config/lexics/matchDownload.tsx
  43. 1
      src/config/queries.tsx
  44. 11
      src/config/routes.tsx
  45. 6
      src/features/AirPlay/index.tsx
  46. 16
      src/features/App/AuthenticatedApp.tsx
  47. 6
      src/features/AuthServiceApp/components/ConfirmPopup/index.tsx
  48. 2
      src/features/AuthServiceApp/config/lexics.tsx
  49. 10
      src/features/AuthStore/helpers.tsx
  50. 50
      src/features/AuthStore/hooks/useAuth.tsx
  51. 9
      src/features/BuyMatchPopup/components/PackageSelectionStep/index.tsx
  52. 2
      src/features/BuyMatchPopup/types.tsx
  53. 16
      src/features/ChromeCast/index.tsx
  54. 14
      src/features/HeaderFilters/components/DateFilter/helpers.tsx
  55. 25
      src/features/HeaderFilters/components/DateFilter/hooks/index.tsx
  56. 199
      src/features/HeaderFilters/components/DateFilter/index.tsx
  57. 117
      src/features/HeaderFilters/components/DateFilter/styled.tsx
  58. 191
      src/features/HeaderFilters/components/FacrDateFilter/index.tsx
  59. 1
      src/features/HeaderFilters/index.tsx
  60. 1
      src/features/HeaderFilters/store/config.tsx
  61. 22
      src/features/HeaderFilters/store/hooks/index.tsx
  62. 5
      src/features/HeaderMobile/index.tsx
  63. 1
      src/features/HeaderMobile/styled.tsx
  64. 5
      src/features/HomePage/components/Header/index.tsx
  65. 3
      src/features/HomePage/components/HeaderFilters/index.tsx
  66. 3
      src/features/HomePage/components/HeaderFilters/styled.tsx
  67. 8
      src/features/HomePage/index.tsx
  68. 3
      src/features/Icon/index.tsx
  69. 2
      src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx
  70. 17
      src/features/MatchCard/CardFrontside/hooks.tsx
  71. 8
      src/features/MatchCard/CardFrontside/index.tsx
  72. 4
      src/features/MatchCard/config.tsx
  73. 2
      src/features/MatchCard/hooks.tsx
  74. 2
      src/features/MatchCard/index.tsx
  75. 2
      src/features/MatchPage/components/LiveMatch/hooks/index.tsx
  76. 109
      src/features/MatchPage/components/MatchDownloadPopup/index.tsx
  77. 23
      src/features/MatchPage/components/MatchDownloadPopup/styled.tsx
  78. 58
      src/features/MatchPage/store/atoms.tsx
  79. 75
      src/features/MatchPage/store/hooks/useLikes.tsx
  80. 4
      src/features/MatchPage/store/hooks/usePlayersStats.tsx
  81. 5
      src/features/MatchPage/store/hooks/useStatsTab.tsx
  82. 2
      src/features/MatchPage/store/hooks/useTeamsStats.tsx
  83. 4
      src/features/MatchPage/store/hooks/useTournamentData.tsx
  84. 2
      src/features/MatchPage/store/index.tsx
  85. 3
      src/features/MatchPage/types.tsx
  86. 2
      src/features/MatchPopup/types.tsx
  87. 62
      src/features/MatchSidePlaylists/components/DownloadNotification/DownloadNotification.tsx
  88. 48
      src/features/MatchSidePlaylists/components/DownloadNotification/styled.tsx
  89. 126
      src/features/MatchSidePlaylists/components/LikeEvent/index.tsx
  90. 35
      src/features/MatchSidePlaylists/components/LikeEvent/styled.tsx
  91. 45
      src/features/MatchSidePlaylists/components/LikesList/index.tsx
  92. 68
      src/features/MatchSidePlaylists/components/MatchDownloadButton/index.tsx
  93. 35
      src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx
  94. 2
      src/features/MatchSidePlaylists/components/TabEvents/styled.tsx
  95. 235
      src/features/MatchSidePlaylists/components/TabLikes/index.tsx
  96. 40
      src/features/MatchSidePlaylists/components/TabLikes/styled.tsx
  97. 1
      src/features/MatchSidePlaylists/config.tsx
  98. 4
      src/features/MatchSidePlaylists/hooks.tsx
  99. 142
      src/features/MatchSidePlaylists/index.tsx
  100. 44
      src/features/MatchSidePlaylists/styled.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1007,56 +1007,3 @@ steps:
- aws cloudfront create-invalidation --distribution-id E15IFY23VM147K --paths "/*"
depends_on:
- make-rustat
---
kind: pipeline
type: docker
name: deploy insport.live
concurrency:
limit: 1
platform:
os: linux
arch: amd64
trigger:
ref:
- refs/heads/insport.live
steps:
- name: npm-install
image: node:16-alpine
environment:
REACT_APP_STRIPE_PK:
from_secret: REACT_APP_STRIPE_PK
commands:
- apk add --no-cache make
- npm install --legacy-peer-deps
- name: make-insport-live
image: node:16-alpine
environment:
REACT_APP_STRIPE_PK:
from_secret: REACT_APP_STRIPE_PK
commands:
- apk add --no-cache make
- make insport-live-prod
depends_on:
- npm-install
- name: deploy-insport-live
image: amazon/aws-cli:latest
environment:
AWS_ACCESS_KEY_ID:
from_secret: AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
from_secret: AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION:
from_secret: AWS_DEFAULT_REGION
AWS_MAX_ATTEMPTS: 10
commands:
- aws s3 sync build_insport_live s3://insports-live --delete
- aws cloudfront create-invalidation --distribution-id E1LBC88VYP6XVB --paths "/*"
depends_on:
- make-insport-live

@ -225,15 +225,6 @@ rustat-prod:
BUILD_PATH=build_rustat \
npm run build && cp -r .well-known build_rustat
insport-live-prod:
rm -rf build_insport_live && \
REACT_APP_TYPE=ott \
REACT_APP_ENV=staging \
REACT_APP_CLIENT=lff \
REACT_APP_STRIPE_PK=pk_live_51J5TEYEDSxVnTgDW5XxhC6ntKZKddXgKHq5HOCDmJTdfSKluMYCdLHOcUA3Miuy8HesxG1eS4c0dQRQpMsEHRrQL00USpu5xIq \
BUILD_PATH=build_insport_live \
npm run build && cp -r .well-known build_insport_live
deploy-all: prod preprod facr-prod lff-prod diwansport-prod india-prod fqtv-prod rustat-prod
test:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/mstile-70x70.png"/>
<square144x144logo src="/mstile-144x144.png"/>
<square150x150logo src="/mstile-150x150.png"/>
<square310x310logo src="/mstile-310x310.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

@ -1,19 +0,0 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

@ -1,18 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000" preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none">
<path d="M1499 6996 c-2 -2 -59 -6 -125 -9 -247 -12 -462 -57 -614 -129 -64
-30 -206 -123 -265 -173 -105 -89 -173 -164 -247 -270 -153 -219 -210 -411
-235 -790 -11 -161 -10 -4130 0 -4270 12 -158 20 -222 43 -331 60 -297 246
-573 515 -766 182 -130 345 -193 584 -225 22 -2 51 -6 65 -8 124 -17 518 -20
2260 -20 1773 0 2220 4 2295 19 11 2 45 7 75 10 75 9 120 17 190 36 121 32
186 61 300 129 87 53 77 46 155 109 140 114 277 283 353 437 83 166 119 330
139 630 11 160 10 4107 0 4260 -29 420 -108 642 -311 880 -175 205 -390 346
-626 412 -47 13 -107 26 -135 29 -27 4 -52 8 -55 9 -3 2 -44 6 -90 10 -47 4
-96 9 -110 13 -26 6 -4154 14 -4161 8z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 B

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 102 KiB

@ -1,4 +0,0 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9261 5.8212C12.9307 5.87963 12.9358 5.93231 12.9406 5.97754C12.9332 5.99732 12.9251 6.019 12.9163 6.04251C12.8651 6.17851 12.7898 6.37523 12.6939 6.61585C12.502 7.09769 12.229 7.75251 11.9041 8.44659C11.5783 9.14247 11.2053 9.86744 10.8143 10.4938C10.4154 11.1329 10.0302 11.616 9.69372 11.8772C9.66718 11.8978 9.64228 11.9205 9.61923 11.9449C9.18111 12.41 8.91275 12.9173 8.74763 13.3195L8.7475 13.3194L8.74278 13.3316C8.71208 13.4109 8.6845 13.4866 8.66007 13.556C8.58397 13.7007 8.41439 13.9457 8.03684 14.1757C7.79824 14.321 7.67921 14.6026 7.74121 14.875L9.45857 22.4214C9.51892 22.6866 9.738 22.8862 10.0077 22.9216L10.0924 22.2772C10.0077 22.9216 10.0077 22.9216 10.0078 22.9216L10.008 22.9217L10.0085 22.9217L10.0104 22.922L10.0169 22.9228L10.0413 22.9259L10.1335 22.9373C10.2137 22.9471 10.3304 22.9608 10.4785 22.977C10.7745 23.0094 11.1963 23.052 11.7023 23.0927C12.704 23.1733 14.0447 23.2476 15.3961 23.2207C16.4734 23.3062 17.6651 23.3176 18.6492 23.1041C20.351 22.7378 21.1491 21.9031 21.424 21.0082C21.5524 20.5903 21.5506 20.2065 21.5195 19.9324C21.5184 19.923 21.5173 19.9137 21.5162 19.9045C22.2578 19.2016 22.4576 18.4095 22.4234 17.7496C22.407 17.4337 22.3384 17.1612 22.268 16.9564C22.532 16.603 22.6944 16.243 22.7698 15.8841C22.869 15.4117 22.8081 14.9852 22.6891 14.6367C22.5848 14.3315 22.4352 14.0826 22.3041 13.9021C22.3596 13.7543 22.414 13.5732 22.4473 13.3692C22.5426 12.7857 22.4608 11.9957 21.7482 11.3383C21.301 10.9249 20.7077 10.7201 20.1465 10.6173C19.5762 10.5129 18.9701 10.5014 18.4303 10.5271C17.8871 10.553 17.3897 10.6177 17.0293 10.6755C16.8485 10.7044 16.7006 10.7319 16.5969 10.7524C16.545 10.7626 16.504 10.7711 16.4753 10.7772L16.4465 10.7835C16.2767 10.8127 16.1004 10.8472 15.9166 10.8879C15.9176 10.8109 15.9236 10.7124 15.9386 10.5884C15.9877 10.1833 16.1238 9.57481 16.4163 8.67814C17.0456 6.75441 16.9047 5.43702 16.2643 4.58987C15.6381 3.76143 14.6875 3.5895 14.1627 3.5895C13.6555 3.5895 13.3235 3.89075 13.149 4.21465C12.9929 4.50432 12.9404 4.83899 12.9208 5.09751C12.9001 5.36964 12.9113 5.63254 12.9261 5.8212Z" stroke="white" stroke-width="1.3" stroke-linejoin="round"/>
<path d="M7.34175 23.2783C7.70504 23.2809 8.04815 23.132 8.27682 22.8488C8.50519 22.566 8.57817 22.2007 8.50503 21.8497C8.50503 21.8496 8.50503 21.8496 8.50503 21.8496L7.204 15.603L7.204 15.6029C7.06791 14.9497 6.44949 14.4357 5.77589 14.4357H3.29278C2.93392 14.4357 2.64296 14.7265 2.64278 15.0853L2.63906 22.6279C2.63898 22.8004 2.70742 22.9658 2.82933 23.0878C2.95124 23.2097 3.11662 23.2783 3.28906 23.2783H7.34175ZM7.34175 23.2783C7.34045 23.2783 7.33916 23.2782 7.33786 23.2782L7.34456 22.6283V23.2783H7.34175Z" stroke="white" stroke-width="1.3" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

@ -1,28 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client/1.11.5/oidc-client.min.js"
integrity="sha512-pGtU1n/6GJ8fu6bjYVGIOT9Dphaw5IWPwVlqkpvVgqBxFkvdNbytUh0H8AP15NYF777P4D3XEeA/uDWFCpSQ1g=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client/1.11.5/oidc-client.min.js" integrity="sha512-pGtU1n/6GJ8fu6bjYVGIOT9Dphaw5IWPwVlqkpvVgqBxFkvdNbytUh0H8AP15NYF777P4D3XEeA/uDWFCpSQ1g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
new Oidc.UserManager().signinSilentCallback()
// обновляем рефреш токен в локалсторадже
// так как safari не дает доступ к кукам
.then(() => {
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken) {
localStorage.setItem('refresh_token', new URLSearchParams(document.location.search).get('refresh_token'));
}
})
.catch((err) => {
console.error('OIDC: silent refresh callback error', err);
});
.catch((err) => {
console.error('OIDC: silent refresh callback error', err);
});
</script>
</body>
</html>
</html>

@ -27,10 +27,8 @@ import {
const countryCode = getLocalStorageItem(COUNTRY)
export const useAd = ({ ad }: AdComponentType) => {
const isMatch = isMatchPage()
const [isOpenAd, setIsOpenAd] = useState(isMatch)
const [isNeedToShow, setIsNeedToShow] = useState(isMatch)
const [isOpenAd, setIsOpenAd] = useState(true)
const [isNeedToShow, setIsNeedToShow] = useState(true)
const [shownTime, setShownTime] = useState(0)
const { isFullscreen } = useMatchPageStore()
@ -74,7 +72,7 @@ export const useAd = ({ ad }: AdComponentType) => {
const handleClose = () => {
setIsOpenAd(false)
isMatch && setIsNeedToShow(false)
isMatchPage() && setIsNeedToShow(false)
sendBannerClickEvent(EventGA.CLOSE)
}
@ -91,7 +89,7 @@ export const useAd = ({ ad }: AdComponentType) => {
useEffect(() => {
setShownTime(0)
if (isMatch) {
if (isMatchPage()) {
const interval = setInterval(() => {
setIsNeedToShow(true)
setIsOpenAd(true)
@ -107,7 +105,6 @@ export const useAd = ({ ad }: AdComponentType) => {
isNeedToShow,
views?.HOME,
isNeedBanner,
isMatch,
])
useEffect(() => {
@ -139,18 +136,14 @@ export const useAd = ({ ad }: AdComponentType) => {
])
useEffect(() => {
if (!isNeedToShow || (!isMatch && !isNeedBanner)) return
if (!isNeedToShow || (!isMatchPage() && !isNeedBanner)) return
(async () => {
await updateAdsView({ adv_id: id })
})()
sendBannerClickEvent(EventGA.DISPLAY)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
id,
isNeedToShow,
isMatch,
])
}, [id, isNeedToShow])
return {
handleClose,

@ -9,7 +9,6 @@ import {
PLAYER_MOBILE_FULL_SCREEN,
PLAYER_MOBILE_ADS,
MATCH_PAGE_MOBILE_ADS,
VIEW_ADS,
} from '../../types'
type Props = {
@ -41,9 +40,6 @@ const chooseStyle = (type: number) => {
padding: 5px;
background-color: rgba(0, 0, 0, 0.7);
`
case VIEW_ADS.MOBILE_IN_COLLAPSE_FOOTER:
case VIEW_ADS.MOBILE_IN_COLLAPSE_HEADER:
return 'margin-top: 6px'
default:
return ''
}
@ -52,7 +48,7 @@ const chooseStyle = (type: number) => {
export const MobileAdWrapper = styled.div<Props>`
position: relative;
width: 100%;
z-index: ${({ position }) => (includes(PLAYER_MOBILE_FULL_SCREEN, position) ? '101' : '4')};
z-index: ${({ position }) => (includes(PLAYER_MOBILE_FULL_SCREEN, position) ? '101' : '100')};
${({ position }) => chooseStyle(position)};
`

@ -19,7 +19,5 @@ export const india: ClientConfig = {
disabledHighlights: true,
host: 'india.insports.tv',
name: ClientNames.India,
privacyLink: '/privacy-policy-and-statement?client_id=india-ott-web',
termsLink: '/terms-and-conditions?client_id=india-ott-web',
userAccountCardsHidden: true,
}

@ -155,11 +155,6 @@ const newDevicePopup = {
}
export const indexLexics = {
'1st_half': 1031,
'2nd_half': 1032,
'3rd_half': 20262,
'4th_half': 20263,
'5th_half': 20264,
add_to_favorites: 14967,
add_to_favorites_error: 12943,
all_competitions: 17926,
@ -175,9 +170,7 @@ export const indexLexics = {
cm: 817,
features: 13051,
football: 6958,
ft: 20261,
full_game: 13028,
full_time: 20258,
futsal: 17670,
game: 9680,
game_finished: 13026,
@ -193,11 +186,9 @@ export const indexLexics = {
hide_score: 12982,
highlights: 13033,
hockey: 6959,
ht: 20260,
interview: 13031,
kg: 652,
kickoff_in: 13027,
likes: 16628,
live: 13024,
loading: 3527,
logout: 4306,
@ -208,14 +199,11 @@ export const indexLexics = {
match_status_soon: 12986,
match_video: 13025,
month_title: 2202,
my_likes: 20254,
no_match_access_body: 13419,
no_match_access_title: 13418,
player: 14975,
players: 164,
players_video: 13032,
pm: 20259,
pre_match: 20257,
privacy_policy_and_statement: 15404,
round_highilights: 13050,
save: 828,
@ -224,21 +212,15 @@ export const indexLexics = {
sport: 12993,
team: 14973,
terms_and_conditions: 15738,
timeline_title: 20266,
to_home: 13376,
total_likes: 20253,
tournament: 14974,
upcoming: 17925,
user_account: 12928,
user_liked_this: 20267,
users_liked_this: 20255,
volleyball: 9761,
watch_from_beginning: 13021,
watch_from_last_pause: 13022,
watch_now: 13020,
week_title: 6584,
you_and: 20256,
you_liked_this: 20265,
...filterPopup,
...confirmPopup,

@ -1,8 +1,12 @@
export const matchDownload = {
can_close: 20238,
choose_what_to_download: 20193,
download_files_for_periods: 20197,
download_full_match: 20198,
download_single_file: 20196,
entire_record: 20195,
error_message: 20240,
in_game_time_only: 20194,
processed: 20200,
will_notified: 20239,
}

@ -1,5 +1,6 @@
export const querieKeys = {
ads: 'ads',
downloadPlaylist: 'downloadPlaylist',
liveMatchScores: 'liveMatchScores',
matchScore: 'matchScore',
sportsList: 'sportsList',

@ -6,17 +6,14 @@ export const APIS = {
preproduction: {
api: 'https://api.insports.tv',
auth: 'https://api.auth.insports.tv',
auth_old: 'https://auth.insports.tv',
},
production: {
api: 'https://api.insports.tv',
auth: 'https://api.auth.insports.tv',
auth_old: 'https://auth.insports.tv',
},
staging: {
api: 'https://api.test.insports.tv',
auth: 'https://api.auth.test.insports.tv',
auth_old: 'https://auth.test.insports.tv',
},
}
@ -44,21 +41,13 @@ const PAYMENT_APIS = {
staging: 'https://pay.test.insports.tv',
}
const LIKES_API = {
preproduction: 'wss://ws.insports.tv/v1/events',
production: 'wss://ws.insports.tv/v1/events',
staging: 'wss://ws-test.insports.tv/v1/events',
}
const env = isProduction ? ENV : readSelectedApi() ?? ENV
export const VIEWS_API = VIEWS_APIS[env]
export const AUTH_SERVICE = APIS[env].auth
export const AUTH_SERVICE_OLD = APIS[env].auth_old
export const API_ROOT = APIS[env].api
export const DATA_URL = `${API_ROOT}/data`
export const URL_AWS = 'https://cf-aws.insports.tv'
export const STATS_API_URL = STATS_APIS[env]
export const ADS_API_URL = ADS_APIS[env]
export const PAYMENT_API_URL = PAYMENT_APIS[env]
export const LIKES_API_URL = LIKES_API[env]

@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect } from 'react'
import includes from 'lodash/includes'
import { usePageParams } from 'hooks/usePageParams'
import { API_ROOT } from 'config'
@ -17,7 +19,9 @@ export const AirPlay = ({ videoRef }: Props) => {
const { profileId: matchId, sportType } = usePageParams()
useEffect(() => {
const baseUrl = `${API_ROOT}/v1/broadcasts/${sportType}/${matchId}/master.m3u8?access_token=${readToken()}`
const baseUrl = includes(videoRef?.current?.src, '.m3u8')
? `${API_ROOT}/video/chromecast/stream/${sportType}/${matchId}.m3u8?access_token=${readToken()}`
: videoRef?.current?.src!
const video = videoRef.current!
const airPlayBtn = document.getElementById('airPlay')!

@ -10,7 +10,7 @@ import { RecoilRoot } from 'recoil'
import { indexLexics } from 'config/lexics/indexLexics'
import {
// client,
client,
PAGES,
isProduction,
isMobileDevice,
@ -23,11 +23,8 @@ import { MatchSwitchesStore } from 'features/MatchSwitches'
import { UserFavoritesStore } from 'features/UserFavorites/store'
import { MatchPopup, MatchPopupStore } from 'features/MatchPopup'
import { BuyMatchPopupStore } from 'features/BuyMatchPopup'
import {
// PreferencesPopup,
PreferencesPopupStore,
} from 'features/PreferencesPopup'
// import { TournamentsPopup } from 'features/TournamentsPopup'
import { PreferencesPopup, PreferencesPopupStore } from 'features/PreferencesPopup'
import { TournamentsPopup } from 'features/TournamentsPopup'
import { TournamentPopupStore } from 'features/TournamentsPopup/store'
import { CardsStore } from 'features/CardsStore'
import { NoNetworkPopup, NoNetworkPopupStore } from 'features/NoNetworkPopup'
@ -66,12 +63,7 @@ export const AuthenticatedApp = () => {
<BuyMatchPopupStore>
<NoNetworkPopupStore>
<MatchPopup />
{/* временно отключено по задаче IN-730 */}
{/* {
client.name === 'facr'
? <TournamentsPopup />
: <PreferencesPopup />
} */}
{ client.name === 'facr' ? <TournamentsPopup /> : <PreferencesPopup /> }
<NoNetworkPopup />
{/* в Switch как прямой children
можно рендерить только Route или Redirect */}

@ -1,7 +1,7 @@
import { T9n } from 'features/T9n'
import { client } from 'config/clients'
import { AUTH_SERVICE_OLD } from 'config/routes'
import { AUTH_SERVICE } from 'config/routes'
import {
ScBody,
@ -43,11 +43,11 @@ export const ConfirmPopup = (props: Props) => {
</ScText>
<ScText>
<T9n t='by_clicking' />
<ScLink href={`${AUTH_SERVICE_OLD}${client.termsLink}`} target='_blank' id='personal_t_k'>
<ScLink href={`${AUTH_SERVICE}${client.termsLink}`} target='_blank' id='personal_t_k'>
<T9n t='terms_and_conditions' />
</ScLink>&nbsp;
<T9n t='and' />
<ScLink href={`${AUTH_SERVICE_OLD}${client.privacyLink}`} target='_blank' id='personal_policy'>
<ScLink href={`${AUTH_SERVICE}${client.privacyLink}`} target='_blank' id='personal_policy'>
<T9n t='privacy_policy_and_statement' />
</ScLink>
</ScText>

@ -34,7 +34,7 @@ export const lexics = {
go_back: 1907,
i_accept: 15737,
i_agree: 15430,
login: 20304,
login: 13404,
ok: 724,
or_continue_with: 15118,
password_changed_success: 17824,

@ -54,11 +54,11 @@ const redirectUrl = () => {
// @ts-ignore
|| Boolean(clientsForRedirect[client.name])
):
return `${window.origin}`
return `${window.origin}/redirect`
case (ENV === 'staging' || ENV === 'preproduction'):
return `https://${stageENV}.insports.tv`
return `https://${stageENV}.insports.tv/redirect`
default:
return `https://${clientName}.tv`
return `https://${clientName}.tv/redirect`
}
}
@ -76,6 +76,4 @@ export const getClientSettings = (): Settings => ({
userStore: new WebStorageStateStore({ store: window.localStorage }),
})
export const needCheckNewDeviсe = client.name === ClientNames.Instat
|| client.name === ClientNames.Insports
|| client.name === ClientNames.India
export const needCheckNewDeviсe = client.name === 'instat' || client.name === 'insports'

@ -23,11 +23,6 @@ import {
readToken,
setCookie,
removeCookie,
isMatchPage,
REFRESH_TOKEN_KEY,
removeRefreshToken,
writeRefreshToken,
readRefreshToken,
} from 'helpers'
import {
@ -81,7 +76,6 @@ export const useAuth = () => {
userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang })
})
removeToken()
removeRefreshToken()
if (key !== 'saveToken') {
removeCookie('access_token')
}
@ -163,15 +157,10 @@ export const useAuth = () => {
}
}
const signinRedirectCallback = useCallback((refreshToken: string | null) => {
setPage(history.location.pathname)
const signinRedirectCallback = useCallback(() => {
userManager.signinRedirectCallback()
.then((loadedUser) => {
storeUser(loadedUser)
if (refreshToken) writeRefreshToken(refreshToken)
queryParamStorage.clear()
if (page.includes(PAGES.useraccount)) {
history.push(PAGES.home)
@ -182,8 +171,9 @@ export const useAuth = () => {
markUserLoaded()
setPage('')
setSearch('')
// }
}).catch(login)
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
login,
storeUser,
@ -191,18 +181,9 @@ export const useAuth = () => {
])
useEffect(() => {
const urlSearch = new URLSearchParams(history.location.search)
const searchToken = urlSearch.get('access_token')
const searchRefToken = urlSearch.get('id_token')
const searchExp = urlSearch.get('expires_in')
const refreshToken = urlSearch.get(REFRESH_TOKEN_KEY)
const isRedirectedBackFromAuthProvider = Boolean(searchToken && searchRefToken && searchExp)
isRedirectedBackFromAuthProvider
? signinRedirectCallback(refreshToken)
: checkUser()
// eslint-disable-next-line react-hooks/exhaustive-deps
const isRedirectedBackFromAuthProvider = history.location.pathname === '/redirect'
isRedirectedBackFromAuthProvider ? signinRedirectCallback() : checkUser()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
checkUser,
signinRedirectCallback,
@ -232,7 +213,7 @@ export const useAuth = () => {
}, [reChekNewDevice])
useEffect(() => {
if (!needCheckNewDeviсe || !user) return undefined
if (!needCheckNewDeviсe && !user) return undefined
const startCheckDevice = setInterval(checkNewDevice, 20000)
isNewDeviceLogin && clearInterval(startCheckDevice)
return () => clearInterval(startCheckDevice)
@ -242,7 +223,6 @@ export const useAuth = () => {
checkNewDevice,
isNewDeviceLogin,
setIsNewDeviceLogin,
user,
])
duel.channel('active_page') // поле в LS, определяющее активность вкладки
@ -252,13 +232,7 @@ export const useAuth = () => {
// библиотека oidc-client не поддерживает обновление токена только на 1 вкладке
// @ts-ignore
if (window.isMaster()) {
// safari ограничивает доступ к куке через крос доменные запросы
// передаем рефреш токен через квери параметры
userManager.signinSilent({
extraQueryParams: {
refresh_token: readRefreshToken(),
},
}).catch(logout)
userManager.signinSilent().catch(logout)
}
}
// если запросы вернули 401 | 403
@ -301,14 +275,6 @@ export const useAuth = () => {
readToken() && fetchUserInfo()
}, [fetchUserInfo, user])
// временно отрубили возможность смотреть матчи вирт. юзерам
// в связи с переездом бэка
useEffect(() => {
if (userInfo && isMatchPage() && +userInfo.email < 0) {
logout()
}
}, [logout, userInfo])
const auth = useMemo(() => ({
fetchUserInfo,
isFromLanding,

@ -161,15 +161,6 @@ export const PackageSelectionStep = () => {
buttonLexic={buttonLexic}
/>
</Body>
{selectedPackage && isIframePayment && (
<IframePayment
match={match}
open={isOpenIframe}
paymentSystem={paymentSystem}
selectedPackage={selectedPackage}
setIsOpenIframe={setIsOpenIframe}
/>
)}
</Wrapper>
)
}

@ -1,7 +1,7 @@
import type { SubscriptionResponse, Subscription } from 'requests/getSubscriptions'
import type { LexicsId, Values } from 'features/LexicsStore/types'
import type { Match as MatchBase } from 'helpers/prepareMatches'
import type { Match as MatchBase } from 'features/Matches/hooks'
import { MatchAccess } from 'features/Matches/helpers/getMatchClickAction'
export enum Steps {

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
Fragment,
memo,
useEffect,
useRef,
useState,
@ -15,9 +16,13 @@ import { Container } from './styled'
import { CastPlayer } from './CastVideos'
import { useMatchPageStore } from '../MatchPage/store'
type Props = {
src?: string,
}
const NO_DEVICES_AVAILABLE = 'NO_DEVICES_AVAILABLE'
export const ChromeCast = () => {
export const ChromeCast = memo(({ src } : Props) => {
const [isCastAvailable, setIsCastAvailable] = useState(false)
const { profile } = useMatchPageStore()
@ -27,7 +32,8 @@ export const ChromeCast = () => {
const GoogleCastLauncher = (document as any).createElement('google-cast-launcher')
GoogleCastLauncher.setAttribute('id', 'castbutton')
const baseUrl = `${API_ROOT}/v1/broadcasts/${sportType}/${matchId}/master.m3u8?access_token=${readToken()}`
const baseUrl = src ?? `${API_ROOT}/video/chromecast/stream/${sportType}/${matchId}.m3u8`
const urlWithToken = (/\d.m3u8/.test(baseUrl)) ? `${baseUrl}?access_token=${readToken()}` : baseUrl
const teamsInfo = `${profile?.team1.name_eng} - ${profile?.team2.name_eng}`
@ -49,7 +55,7 @@ export const ChromeCast = () => {
document.body.appendChild(script)
const castPlayer = new CastPlayer(baseUrl, teamsInfo);
const castPlayer = new CastPlayer(urlWithToken, teamsInfo);
(window as any).__onGCastApiAvailable = (isAvailable: boolean) => {
if (isAvailable) {
castPlayer.initializeCastPlayer()
@ -65,11 +71,11 @@ export const ChromeCast = () => {
document.body.removeChild(script)
setIsCastAvailable(false)
}
}, [teamsInfo, baseUrl])
}, [teamsInfo, urlWithToken])
return (
<Fragment>
{isCastAvailable && <Container ref={containerRef} />}
</Fragment>
)
}
})

@ -3,8 +3,6 @@ import addDays from 'date-fns/addDays'
import addMonths from 'date-fns/addMonths'
import startOfYear from 'date-fns/startOfYear'
import { isMobileDevice } from 'config'
type Args = {
date: Date,
lang: string,
@ -22,7 +20,7 @@ const getMonthName = ({
export const getDisplayDate = ({
date,
lang,
monthType = isMobileDevice ? 'long' : 'short',
monthType = 'long',
}: Args) => ({
day: date.getDate(),
month: getMonthName({
@ -50,8 +48,6 @@ type Week = {
name: string,
}
type Month = Week
export const getWeeks = (date: Date, locale: string) => {
const weekStart = startOfWeek(date, { weekStartsOn: 1 })
const getWeekDay = createDayGetter(weekStart, locale)
@ -68,17 +64,17 @@ export const getWeeks = (date: Date, locale: string) => {
}
const createMonthGetter = (yearStart: Date, locale: string) => (month: number) => {
const monthDate = addMonths(yearStart, month)
const dayDate = addMonths(yearStart, month)
return {
date: monthDate,
name: new Intl.DateTimeFormat(locale || 'en', { month: 'short' }).format(monthDate),
date: dayDate,
name: new Intl.DateTimeFormat(locale || 'en', { month: 'short' }).format(dayDate),
}
}
export const getMonths = (locale: string, date: Date) => {
const yearStart = startOfYear(date)
const getMonth = createMonthGetter(yearStart, locale)
const months: Array<Month> = [
const months: Array<Week> = [
getMonth(0),
getMonth(1),
getMonth(2),

@ -29,8 +29,6 @@ import {
export const useDateFilter = () => {
const {
isMonthMode,
isTimelineMode,
isWeekMode,
selectedDate,
selectedMode,
selectedMonthModeDate,
@ -64,11 +62,8 @@ export const useDateFilter = () => {
lang,
})
const filters = localStorage.getItem('filters')
const dateMode = localStorage.getItem('dateMode')
const parseFilters = filters && JSON.parse(filters)
const parseMode = dateMode && JSON.parse(dateMode)
const lastDate = parseFilters?.selectedDate
const lastMonthDate = new Date(parseMode?.selectedMonthModeDate)
const weekName = getWeekName(selectedDate, 'en')
const validator = (value: unknown) => Boolean(value) && isObject(value)
@ -79,16 +74,8 @@ export const useDateFilter = () => {
validator,
})
const addAdsViews = () => {
setAdsViews({
...adsViews,
HOME: (adsViews.HOME ?? 0) + 1,
})
}
useEffect(() => {
if (lastDate === selectedDate.getDate()
&& lastMonthDate === selectedMonthModeDate
&& parseFilters
&& parseFilters.selectedLeague[0] !== 'all_competitions') {
setIsShowTournament(false)
@ -98,18 +85,20 @@ export const useDateFilter = () => {
setIsShowTournament(true)
setSelectedFilters([])
setSelectedLeague(['all_competitions'])
setAdsViews({
...adsViews,
HOME: (adsViews.HOME ?? 0) + 1,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedDate, selectedMonthModeDate])
}, [selectedDate])
const onPreviousClick = () => {
addAdsViews()
const numberAddDays = weekName === 'Mon' ? -1 : -7
setSelectedDate(addDays(selectedDate, numberAddDays))
}
const onNextClick = () => {
addAdsViews()
const numberAddDays = weekName === 'Sun' ? 1 : 7
setSelectedDate(addDays(selectedDate, numberAddDays))
}
@ -119,7 +108,6 @@ export const useDateFilter = () => {
const onDateChange = (newDate: Date | null) => {
if (newDate) {
addAdsViews()
setSelectedDate(newDate)
close()
}
@ -131,13 +119,10 @@ export const useDateFilter = () => {
}
return {
addAdsViews,
close,
date,
isMonthMode,
isOpen,
isTimelineMode,
isWeekMode,
months,
onDateChange,
onNextClick,

@ -2,62 +2,39 @@ import { Fragment } from 'react'
import map from 'lodash/map'
import { T9n } from 'features/T9n'
import { Icon } from 'features/Icon'
import { OutsideClick } from 'features/OutsideClick'
import { BodyBackdrop } from 'features/PageLayout'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { Icon } from 'features/Icon'
import { isInSportsClient, isMobileDevice } from 'config'
import { useDateFilter } from './hooks'
import { DatePicker } from '../DatePicker'
import { Tabs } from '../../store/config'
import {
TabsList,
Tab,
TabTitle,
YearWrapper,
Wrapper,
MonthWrapper,
WeekDaysWrapper,
ArrowButton,
Arrow,
MonthModeYear,
MonthModeWrapper,
Month,
MonthName,
CalendarWrapper,
DateWrapper,
MonthArrow,
WeekDaysWrapper,
DateButton,
MonthYear,
Week,
WeekDay,
WeekName,
WeekNumber,
WeekModeButton,
WeekModeYear,
Week,
MonthButtonWrapper,
} from './styled'
import { useHeaderFiltersStore } from '../../store'
export const DateFilter = () => {
const {
addAdsViews,
close,
date,
isMonthMode,
isOpen,
isWeekMode,
months,
onDateChange,
onNextClick,
onNextYearClick,
onPreviousClick,
onPrevYearClick,
onWeekDayClick,
openDatePicker,
selectedDate,
selectedMode,
selectedMonthModeDate,
setSelectedMode,
setSelectedMonthModeDate,
week,
} = useDateFilter()
@ -66,134 +43,54 @@ export const DateFilter = () => {
} = useHeaderFiltersStore()
return (
<CalendarWrapper isMonthMode={isMonthMode}>
<DateWrapper isMonthMode={isMonthMode}>
{isWeekMode && (
<MonthButtonWrapper>
<WeekModeYear onClick={openDatePicker}>
{date.month} {' '} {date.year}
</WeekModeYear>
<WeekModeButton isActive={isOpen} onClick={openDatePicker}>
<Icon refIcon='Calendar' color='#fff' />
</WeekModeButton>
</MonthButtonWrapper>
)}
{isMonthMode && (
<YearWrapper>
<MonthArrow
aria-label='Previous year'
onClick={onPrevYearClick}
>
<Arrow direction='left' />
</MonthArrow>
<MonthModeYear>
{selectedMonthModeDate.getFullYear()}
</MonthModeYear>
<MonthArrow
aria-label='Next year'
onClick={onNextYearClick}
>
<Arrow direction='right' />
</MonthArrow>
</YearWrapper>
)}
<Wrapper>
<MonthWrapper>
<MonthYear onClick={openDatePicker}>
{date.month} {' '} {date.year}
</MonthYear>
<DateButton
isActive={isOpen}
onClick={openDatePicker}
id='main_calendar'
>
<Icon refIcon='Calendar' color='#fff' />
</DateButton>
</MonthWrapper>
<TabsList>
{!isInSportsClient && (
<Tab
aria-pressed={selectedMode === Tabs.MONTH}
onClick={() => setSelectedMode(Tabs.MONTH)}
>
<TabTitle>
<T9n t='month_title' />
</TabTitle>
</Tab>
)}
{!isMobileDevice && (
<Tab
aria-pressed={selectedMode === Tabs.TIMELINE}
onClick={() => setSelectedMode(Tabs.TIMELINE)}
>
<TabTitle>
<T9n t='timeline_title' />
</TabTitle>
</Tab>
)}
{(!isMobileDevice || !isInSportsClient) && (
<Tab
aria-pressed={selectedMode === Tabs.WEEK}
onClick={() => setSelectedMode(Tabs.WEEK)}
>
<TabTitle>
<T9n t='week_title' />
</TabTitle>
</Tab>
)}
</TabsList>
</DateWrapper>
{isMonthMode && (
<MonthModeWrapper>
<WeekDaysWrapper>
<ArrowButton
aria-label='Previous week'
onClick={onPreviousClick}
>
<Arrow direction='left' />
</ArrowButton>
<Week>
{
map(months, (month) => (
<Month
key={month.name}
selected={month.date.getMonth() === selectedMonthModeDate.getMonth()}
map(week, (day) => (
<WeekDay
key={day.name}
selected={day.date.getDate() === selectedDate.getDate()}
onClick={() => {
if (month.date.getMonth() !== selectedMonthModeDate.getMonth()) {
setSelectedMonthModeDate(month.date)
if (day.date.getDate() !== selectedDate.getDate()) {
onWeekDayClick(day.date)
} else {
resetFilters()
}
}}
>
<MonthName>{month.name}</MonthName>
</Month>
<WeekName>{day.name.slice(0, 3)}</WeekName>
<WeekNumber>{day.date.getDate()}</WeekNumber>
</WeekDay>
))
}
</MonthModeWrapper>
)}
{isWeekMode && (
<WeekDaysWrapper>
<ArrowButton
aria-label='Previous week'
onClick={onPreviousClick}
>
<Arrow direction='left' />
</ArrowButton>
<Week>
{
map(week, (day) => (
<WeekDay
key={day.name}
selected={day.date.getDate() === selectedDate.getDate()}
onClick={() => {
if (day.date.getDate() !== selectedDate.getDate()) {
addAdsViews()
onWeekDayClick(day.date)
} else {
resetFilters()
}
}}
>
<WeekName>{day.name.slice(0, 3)}</WeekName>
<WeekNumber>{day.date.getDate()}</WeekNumber>
</WeekDay>
))
}
</Week>
<ArrowButton
aria-label='Next week'
onClick={onNextClick}
>
<Arrow direction='right' />
</ArrowButton>
</WeekDaysWrapper>
)}
</Week>
<ArrowButton
aria-label='Next week'
onClick={onNextClick}
>
<Arrow direction='right' />
</ArrowButton>
</WeekDaysWrapper>
{
isOpen && (
<Fragment>
@ -208,6 +105,6 @@ export const DateFilter = () => {
</Fragment>
)
}
</CalendarWrapper>
</Wrapper>
)
}

@ -2,7 +2,6 @@ import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { devices } from 'config/devices'
import { isInSportsClient } from 'config'
type Props = {
isMonthMode: boolean,
@ -18,6 +17,7 @@ export const BaseButton = styled.button`
export const Wrapper = styled.div`
position: relative;
/* width: 32.8rem; */
display: flex;
flex-direction: column;
justify-content: center;
@ -29,6 +29,8 @@ export const Wrapper = styled.div`
${isMobileDevice
? css`
/* padding-top: 4px; */
/* min-height: 84px; */
justify-content: space-between;
@media (max-width: 450px){
@ -106,6 +108,7 @@ export const WeekDaysWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
margin-top: 0.567rem;
${isMobileDevice
? css`
@ -116,6 +119,7 @@ export const WeekDaysWrapper = styled.div`
`
export const Week = styled.div`
margin: 0 0.95rem;
display: flex;
@media (max-width: 600px) {
@ -221,8 +225,11 @@ export const Arrow = styled.span<ArrowProps>`
: ''};
`
export const MonthModeWrapper = styled.div`
export const MonthModeWrapper = styled(WeekDaysWrapper)<Props>`
display: flex;
justify-content: center;
align-items: center;
margin-top: ${({ isMonthMode }) => (isMonthMode ? '0' : '0.2rem')};;
::-webkit-scrollbar {
display: none;
@ -234,64 +241,47 @@ export const MonthModeWrapper = styled.div`
margin-top: 0;
overflow-y: auto;
height: 100%;
& > :not(:last-child) {
margin-right: 25px;
}
`
: ''};
`
export const CalendarWrapper = styled(Wrapper)<Props>`
display: flex;
flex-direction: column;
height: fit-content;
padding-top: 2rem;
position: relative;
export const FacrWrapper = styled(Wrapper)<Props>`
justify-content: space-between;
height: ${(isMobileDevice ? '100%' : 'fit-content')};
width: ${({ isMonthMode }) => (isMonthMode ? '49.5%' : '26.3rem')};
@media (max-width: 450px){
width: 100%;
justify-content: flex-start;
padding-top: 0;
};
`
export const WeekModeButton = styled(DateButton)`
right: 4.5rem;
top: -5px;
export const FacrDateButton = styled(DateButton)`
left: 24rem;
top: 0;
`
export const FacrMonthWrapper = styled(MonthWrapper)`
position: static;
width: auto;
align-self: auto;
${isMobileDevice
? css`
position: static;
position: absolute;
left: 50%;
transform: translateX(-50%);
`
: ''};
`
export const MonthButtonWrapper = styled.div`
${() => {
if (isMobileDevice) {
if (isInSportsClient) {
return css`
position: static;
display: flex;
align-items: baseline;
justify-content: center;
`
}
return css`
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
`
}
return ''
}}
`
export const DateWrapper = styled.div<Props>`
position: relative;
margin-bottom: 0.7rem;
display: flex;
align-items: flex-end;
justify-content: space-between;
width: ${({ isMonthMode }) => (isMonthMode ? '25rem' : '16.9rem')};
margin: ${({ isMonthMode }) => (isMonthMode ? '3.7rem 0 0.7rem' : '3rem 0 0')};
${isMobileDevice
? css`
@ -310,18 +300,24 @@ export const MonthArrow = styled(ArrowButton)`
height: auto;
`
export const Months = styled(Week)``
export const Months = styled(Week)`
height: 100%;
width: 100%;
margin: 0;
justify-content: space-between;
`
export const FacrWeek = styled(Week)`
margin: 0 1.8rem;
`
export const Month = styled(WeekDay)`
width: 3.5rem;
width: auto;
${isMobileDevice
? css`
margin-top: 10px;
margin-right: 25px;
min-width: fit-content;
`
: ''};
@ -339,36 +335,13 @@ export const MonthModeYear = styled(MonthYear)`
: ''};
`
export const WeekModeYear = styled(MonthYear)`
line-height: normal;
font-size: 1rem;
position: absolute;
left: 2.5rem;
cursor: pointer;
display: flex;
align-items: center;
height: 100%;
${isMobileDevice
? css`
position: static;
font-size: 10px;
`
: ''};
`
export const TabsList = styled.div`
display: flex;
justify-content: center;
margin: 0 auto 0;
& > :not(:last-child) {
margin-right: 2rem;
}
justify-content: space-between;
height: fit-content;
${isMobileDevice
? css`
justify-content: flex-start;
top: 3px;
padding: 0;
`
@ -376,9 +349,6 @@ export const TabsList = styled.div`
`
export const YearWrapper = styled.div`
position: absolute;
right: 0;
top: -5px;
display: flex;
align-items: center;
justify-content: center;
@ -390,7 +360,6 @@ export const YearWrapper = styled.div`
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -1px;
`
: ''};
`
@ -418,13 +387,17 @@ export const Tab = styled.button`
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.85rem;
font-size: .75rem;
font-weight: 600;
cursor: pointer;
border: none;
background: none;
padding: 0;
:first-child {
margin-right: 2rem;
}
${isMobileDevice
? css`
font-size: 10px;

@ -0,0 +1,191 @@
import map from 'lodash/map'
import { T9n } from 'features/T9n'
import { Icon } from 'features/Icon'
import { OutsideClick } from 'features/OutsideClick'
import { BodyBackdrop } from 'features/PageLayout'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { Fragment } from 'react'
import { useDateFilter } from '../DateFilter/hooks'
import { DatePicker } from '../DatePicker'
import { Tabs } from '../../store/config'
import {
TabsList,
Tab,
TabTitle,
MonthsMode,
YearWrapper,
ArrowButton,
Arrow,
MonthModeYear,
MonthModeWrapper,
Months,
Month,
MonthName,
FacrWrapper,
DateWrapper,
MonthArrow,
WeekDaysWrapper,
FacrMonthWrapper,
WeekDay,
WeekName,
WeekNumber,
FacrDateButton,
FacrWeek,
} from '../DateFilter/styled'
export const FacrDateFilter = () => {
const {
close,
date,
isMonthMode,
isOpen,
months,
onDateChange,
onNextClick,
onNextYearClick,
onPreviousClick,
onPrevYearClick,
onWeekDayClick,
openDatePicker,
selectedDate,
selectedMode,
selectedMonthModeDate,
setSelectedMode,
setSelectedMonthModeDate,
week,
} = useDateFilter()
const {
resetFilters,
} = useHeaderFiltersStore()
return (
<FacrWrapper isMonthMode={isMonthMode}>
<DateWrapper isMonthMode={isMonthMode}>
<TabsList>
<Tab
aria-pressed={selectedMode === Tabs.MONTH}
onClick={() => setSelectedMode(Tabs.MONTH)}
>
<TabTitle>
<T9n t='month_title' />
</TabTitle>
</Tab>
<Tab
aria-pressed={selectedMode === Tabs.WEEK}
onClick={() => setSelectedMode(Tabs.WEEK)}
>
<TabTitle>
<T9n t='week_title' />
</TabTitle>
</Tab>
</TabsList>
{isMonthMode
? (
<YearWrapper>
<MonthArrow
aria-label='Previous year'
onClick={onPrevYearClick}
>
<Arrow direction='left' />
</MonthArrow>
<MonthModeYear>
{selectedMonthModeDate.getFullYear()}
</MonthModeYear>
<MonthArrow
aria-label='Next year'
onClick={onNextYearClick}
>
<Arrow direction='right' />
</MonthArrow>
</YearWrapper>
)
: (
<FacrMonthWrapper>
<MonthModeYear onClick={openDatePicker}>
{date.month} {' '} {date.year}
</MonthModeYear>
<FacrDateButton isActive={isOpen} onClick={openDatePicker}>
<Icon refIcon='Calendar' color='#fff' />
</FacrDateButton>
</FacrMonthWrapper>
)}
</DateWrapper>
{isMonthMode
? (
<MonthsMode>
<MonthModeWrapper isMonthMode={isMonthMode}>
<Months>
{
map(months, (day) => (
<Month
key={day.name}
selected={day.date.getMonth() === selectedMonthModeDate.getMonth()}
onClick={() => setSelectedMonthModeDate(day.date)}
>
<MonthName>{day.name}</MonthName>
</Month>
))
}
</Months>
</MonthModeWrapper>
</MonthsMode>
)
: (
<WeekDaysWrapper>
<ArrowButton
aria-label='Previous week'
onClick={onPreviousClick}
>
<Arrow direction='left' />
</ArrowButton>
<FacrWeek>
{
map(week, (day) => (
<WeekDay
key={day.name}
selected={day.date.getDate() === selectedDate.getDate()}
onClick={() => {
if (day.date.getDate() !== selectedDate.getDate()) {
onWeekDayClick(day.date)
} else {
resetFilters()
}
}}
>
<WeekName>{day.name.slice(0, 3)}</WeekName>
<WeekNumber>{day.date.getDate()}</WeekNumber>
</WeekDay>
))
}
</FacrWeek>
<ArrowButton
aria-label='Next week'
onClick={onNextClick}
>
<Arrow direction='right' />
</ArrowButton>
</WeekDaysWrapper>
)}
{
isOpen && (
<Fragment>
<OutsideClick onClick={close}>
<DatePicker
open
selected={selectedDate}
onChange={onDateChange}
/>
</OutsideClick>
<BodyBackdrop />
</Fragment>
)
}
</FacrWrapper>
)
}

@ -1,2 +1,3 @@
export * from './components/DateFilter'
export * from './components/FacrDateFilter'
export * from './store'

@ -8,5 +8,4 @@ export const filterKeys = {
export enum Tabs {
WEEK,
MONTH,
TIMELINE
}

@ -14,12 +14,13 @@ import { useQueryParamStore } from 'hooks'
import { getSportLexic } from 'helpers'
import { isMobileDevice, querieKeys } from 'config'
import { isFacrClient } from 'config/clients'
import { querieKeys } from 'config'
import { getLocalStorageItem } from 'helpers/getLocalStorage'
import type { Match } from 'helpers'
import { filterKeys, Tabs } from '../config'
import { isValidDate } from '../helpers/isValidDate'
import type { Match } from '../../../Matches'
export const useFilters = () => {
const { search } = useLocation()
@ -31,11 +32,9 @@ export const useFilters = () => {
})
const sportList = getLocalStorageItem(querieKeys.sportsList)
const [selectedMode, setSelectedMode] = useState<Tabs>(Tabs.WEEK)
const [selectedMode, setSelectedMode] = useState(0)
const [selectedMonthModeDate, setSelectedMonthModeDate] = useState(startOfMonth(new Date()))
const isMonthMode = selectedMode === Tabs.MONTH
const isTimelineMode = selectedMode === Tabs.TIMELINE && !isMobileDevice
const isWeekMode = selectedMode === Tabs.WEEK
const [selectedSport, setSelectedSport] = useState(['all_sports'])
const [selectedLeague, setSelectedLeague] = useState<Array<string | number>>(['all_competitions'])
@ -46,14 +45,15 @@ export const useFilters = () => {
const isTodaySelected = isToday(selectedDate)
useEffect(() => {
if (!isFacrClient) return
const dateMode = localStorage.getItem('dateMode')
if (!dateMode) return
const parseDateMode = JSON.parse(dateMode)
const parseDateMode = dateMode && JSON.parse(dateMode)
setSelectedMode(parseDateMode.selectedMode)
setSelectedMonthModeDate(new Date(parseDateMode.selectedMonthModeDate))
}, [])
useEffect(() => {
if (!isFacrClient) return
localStorage.setItem(
'dateMode',
JSON.stringify({
@ -63,11 +63,11 @@ export const useFilters = () => {
)
}, [selectedMode, selectedMonthModeDate])
const compareSport = useCallback((sportType: number, sportNames: Array<string>) => {
const compareSport = useCallback((match: Match, sportNames: Array<string>) => {
if (sportNames[0] === 'all_sports') {
return true
}
const sport = getSportLexic(sportType)
const sport = getSportLexic(match.sportType)
return (sportNames.indexOf(sport) >= 0 || sportNames.indexOf(`${sport}_popup`) >= 0)
}, [])
@ -119,9 +119,7 @@ export const useFilters = () => {
compareSport,
isMonthMode,
isShowTournament,
isTimelineMode,
isTodaySelected,
isWeekMode,
resetFilters,
selectTournament,
selectedDate,
@ -167,8 +165,6 @@ export const useFilters = () => {
setSelectedMonthModeDate,
selectedMonthModeDate,
isMonthMode,
isTimelineMode,
isWeekMode,
])
return store

@ -3,11 +3,12 @@ import { useRecoilValue } from 'recoil'
import { isAndroid, isIOS } from 'config/userAgent'
import {
client,
isFqtvClient,
isLffClient,
} from 'config/clients'
import { HeaderMenu } from 'features/HeaderMenu'
import { DateFilter } from 'features/HeaderFilters'
import { DateFilter, FacrDateFilter } from 'features/HeaderFilters'
import { ScoreSwitch } from 'features/MatchSwitches'
import { SportsFilter } from 'features/SportsFilter'
import { isSportFilterShownAtom } from 'features/HomePage/Atoms/HomePageAtoms'
@ -44,7 +45,7 @@ export const HeaderMobile = ({
}
<HeaderStyled>
<HeaderMenu />
<DateFilter />
{isFqtvClient ? <FacrDateFilter /> : <DateFilter />}
<ScSportsWrapper>
{!isLffClient && isSportFilterShown ? <SportsFilter /> : null}
<ScoreSwitchWrapper>

@ -74,7 +74,6 @@ export const HeaderStyled = styled.header<HeaderProps>`
? css`
padding: 8px;
margin-bottom: 50px;
justify-content: flex-start;
`
: ''}
`

@ -1,7 +1,7 @@
import { Link } from 'react-router-dom'
import { PAGES } from 'config/pages'
import { client } from 'config/clients'
import { client, isFqtvClient } from 'config/clients'
import { Menu } from 'features/Menu'
import { ScoreSwitch } from 'features/MatchSwitches'
@ -9,6 +9,7 @@ import { Search } from 'features/Search'
import {
DateFilter,
useHeaderFiltersStore,
FacrDateFilter,
} from 'features/HeaderFilters'
import {
@ -44,7 +45,7 @@ export const Header = () => {
<Search />
</HeaderGroup>
</Position>
<DateFilter />
{isFqtvClient ? <FacrDateFilter /> : <DateFilter />}
<Position right={0.71}>
<HeaderGroup>
<ScoreSwitch />

@ -22,7 +22,6 @@ import { isSportFilterShownAtom } from '../../Atoms/HomePageAtoms'
export const HeaderFilters = () => {
const {
isShowTournament,
isTimelineMode,
selectedFilters,
selectTournament,
setIsShowTournament,
@ -72,7 +71,7 @@ export const HeaderFilters = () => {
)}
{!isLffClient && isShowTournament && isSportFilterShown && <SportsFilter />}
{isShowTournament && !isTimelineMode && (
{isShowTournament && (
<ScFilterItemsWrap>
<ScFilterItem
className={isActiveFilter('live') ? 'activeLive' : ''}

@ -6,6 +6,7 @@ export const ScHeaderFilters = styled.div`
display: flex;
flex-direction: row;
margin-bottom: 23px;
align-items: center;
.activeLive {
color: #ffffff;
@ -33,7 +34,7 @@ type Props = {
export const ScFilterItem = styled.div<Props>`
text-transform: uppercase;
font-weight: 700;
font-size: 0.75rem;
font-size: 18px;
color: rgba(255, 255, 255, 0.5);
margin: 0 10px;
cursor: pointer;

@ -6,7 +6,6 @@ import { ConfirmPopup } from 'features/AuthServiceApp/components/ConfirmPopup'
import { Matches } from 'features/Matches'
import {
HeaderFiltersStore,
useHeaderFiltersStore,
} from 'features/HeaderFilters'
import {
PageWrapper,
@ -15,7 +14,6 @@ import {
} from 'features/PageLayout'
import { UserFavorites } from 'features/UserFavorites'
import { BuyMatchPopup } from 'features/BuyMatchPopup'
import { MatchesTimeline } from 'features/MatchesTimeline'
import { HEADER_MOBILE_ADS } from 'components/Ads/types'
import { HeaderAds } from 'components/Ads'
@ -37,8 +35,6 @@ const Home = () => {
userInfo,
} = useHomePage()
const { isTimelineMode } = useHeaderFiltersStore()
return (
<PageWrapper>
{isMobileDevice ? (
@ -64,9 +60,7 @@ const Home = () => {
}
/>
)}
{isTimelineMode
? <MatchesTimeline />
: <Matches fetch={fetchMatches} />}
<Matches fetch={fetchMatches} />
<ConfirmPopup
isModalOpen={isShowConfirmPopup}
handleModalClose={handleCloseConfirmPopup}

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { CSSProperties } from 'react'
import * as icons from '../../libs/index'
@ -6,7 +7,7 @@ export type IconProps = {
color?: string,
direction?: number,
onClick?: () => void,
refIcon: keyof typeof icons | string,
refIcon: any,
size?: number | string,
styles?: CSSProperties,
}

@ -9,7 +9,7 @@ import {
client,
} from 'config'
import type { Match } from 'helpers'
import type { Match } from 'features/Matches'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { useName } from 'features/Name'
import { T9n } from 'features/T9n'

@ -4,20 +4,20 @@ import {
useState,
} from 'react'
import { readToken } from 'helpers'
export type TUseCardFrontside = {
preview?: string,
previewURL?: string,
}
const PREVIEW_WIDTH = 400 // макс. 1920
export const useCardPreview = ({
preview,
previewURL,
}: TUseCardFrontside) => {
const [previewImage, setPreviewImage] = useState('')
const currentPreviewURL = useMemo(() => (
previewURL ? `${previewURL}?width=${PREVIEW_WIDTH}` : preview
previewURL ? `${previewURL}?access_token=${readToken()}` : preview
), [preview, previewURL])
useEffect(() => {
@ -25,11 +25,12 @@ export const useCardPreview = ({
if (!currentPreviewURL) return
try {
const image = await fetch(String(currentPreviewURL))
.then(async (result) => ({
blob: await result.blob(),
status: result.status,
}))
const image = await fetch(String(currentPreviewURL), {
headers: { Authorization: `Bearer ${readToken()}` },
}).then(async (result) => ({
blob: await result.blob(),
status: result.status,
}))
if (image.status === 200) {
setPreviewImage(URL.createObjectURL(image.blob))

@ -8,7 +8,7 @@ import { client } from 'config/clients'
import type { LiveScore } from 'requests'
import type { Match } from 'helpers'
import type { Match } from 'features/Matches'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { useName } from 'features/Name'
import { T9n } from 'features/T9n'
@ -82,7 +82,7 @@ export const CardFrontside = ({
const tournamentName = useName(tournament)
const { isInFavorites } = useUserFavoritesStore()
const { isScoreHidden } = useMatchSwitchesStore()
const { isMonthMode, isTimelineMode } = useHeaderFiltersStore()
const { isMonthMode } = useHeaderFiltersStore()
const { color } = tournament
const isInFuture = getUnixTime(date) > getUnixTime(new Date())
const showScore = !(
@ -179,9 +179,9 @@ export const CardFrontside = ({
<MatchDate
isHomePage={isHomePage}
isMatchPage={isMatchPage}
isMonthMode={isMonthMode || isTimelineMode}
isMonthMode={isMonthMode}
>
{(isHomePage && !isMonthMode && !isTimelineMode) || isMatchPage ? null : prepareDate}
{(isHomePage && !isMonthMode) || isMatchPage ? null : prepareDate}
<Time>{prepareTime}</Time>
</MatchDate>
{live && (

@ -1,3 +1,3 @@
export const MATCH_CARD_WIDTH = 22
export const MATCH_CARD_WIDTH = 12
export const MATCH_CARD_GAP = 40
export const MATCH_CARD_GAP = 20

@ -10,7 +10,7 @@ import {
ProfileTypes,
} from 'config'
import type { Match } from 'helpers'
import type { Match } from 'features/Matches'
import { useMatchPopupStore } from 'features/MatchPopup'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup'
import { useAuthStore } from 'features/AuthStore'

@ -1,4 +1,4 @@
import type { Match } from 'helpers'
import type { Match } from 'features/Matches'
import { isMobileDevice } from 'config/userAgent'

@ -39,7 +39,7 @@ export const useLiveMatch = () => {
const { chapters } = useChapters({
profile,
selectedPlaylist,
url: `${API_ROOT}/v1/broadcasts/${sportType}/${matchId}/master.m3u8?access_token=${readToken()}`,
url: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8?access_token=${readToken()}`,
})
const {

@ -1,7 +1,18 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { useQuery } from 'react-query'
import { T9n } from 'features/T9n'
import { usePageParams } from 'hooks'
import { querieKeys } from 'config'
import {
downloadPlaylist,
type DownloadPlaylistProps,
} from 'requests/downloadPlaylist'
import { Radio } from 'features/Common'
import {
Header,
Footer,
@ -13,20 +24,76 @@ import {
FirstTitle,
SecondTitle,
RadioButtonsWrapper,
Input,
Label,
} from './styled'
type Props = {
closePopup: () => void,
isModalOpen: boolean,
openDownloadNotification: () => void,
setPlaylistConfig: (config: DownloadPlaylistProps) => void,
}
const fileType: Record<'download_single_file'| 'download_files_for_periods', string> = {
download_files_for_periods: 'download_files_for_periods',
download_single_file: 'download_single_file',
}
const initialRadioBtns: Record<string, boolean> = {
entire_record: true,
in_game_time_only: false,
}
export const MatchDownloadPopup = ({
closePopup,
isModalOpen,
openDownloadNotification,
setPlaylistConfig,
}: Props) => {
const [selected, setSelected] = useState(true)
const [downloadConfig, setDownloadConfig] = useState<typeof initialRadioBtns>(initialRadioBtns)
const { profileId: match_id, sportType: sport_id } = usePageParams()
const config: DownloadPlaylistProps = {
ball_in_play: downloadConfig.in_game_time_only,
match_id,
sport_id,
}
const { refetch } = useQuery(
querieKeys.downloadPlaylist,
() => downloadPlaylist(config),
{
enabled: false, // disable this query from automatically running
refetchOnWindowFocus: false,
staleTime: 0,
},
)
const handleDownload = (id = fileType.download_single_file) => {
if (downloadConfig.in_game_time_only) {
config.concat = fileType.download_single_file === id
}
setPlaylistConfig(config)
refetch()
closePopup()
openDownloadNotification()
}
useEffect(() => {
setPlaylistConfig(config)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
downloadConfig,
])
const handleChange = (id: string) => {
setDownloadConfig((prev) => Object.keys(prev)
.reduce((result: typeof initialRadioBtns, key: keyof typeof initialRadioBtns) => ({
...result,
[key]: id === key,
}), {} as typeof initialRadioBtns))
}
return (
<Modal
@ -42,28 +109,28 @@ export const MatchDownloadPopup = ({
</HeaderTitle>
</Header>
<RadioButtonsWrapper>
<Label>
<Input
defaultChecked={selected}
name='in_game_time_only'
onClick={() => setSelected(true)}
/>
<T9n t='in_game_time_only' />
</Label>
<Label>
<Input
name='entire_record'
defaultChecked={!selected}
onClick={() => setSelected(false)}
/>
<T9n t='entire_record' />
</Label>
{Object.entries(downloadConfig).map(([id, checked], index) => (
<Label>
<Radio
name={id}
key={id}
checked={checked}
onChange={(e) => handleChange(e.target.name)}
/>
<T9n t={id} />
</Label>
))}
</RadioButtonsWrapper>
<Footer>
<ScApplyButton>
<ScApplyButton
onClick={() => handleDownload(fileType.download_single_file)}
>
<T9n t='download_single_file' />
</ScApplyButton>
<ScApplyButton disabled={!selected}>
<ScApplyButton
disabled={!downloadConfig.in_game_time_only}
onClick={() => handleDownload(fileType.download_files_for_periods)}
>
<T9n t='download_files_for_periods' />
</ScApplyButton>
</Footer>

@ -100,29 +100,6 @@ export const Label = styled.label`
font-size: 14px;`
: ''};
`
export const Input = styled.input.attrs(() => ({
type: 'radio',
}))`
margin: 0 25px 0 0;
appearance: none;
width: 25px;
height: 25px;
cursor: pointer;
background-image: url(/images/${({ defaultChecked }) => (
defaultChecked
? 'checkedRadiobutton.png'
: 'radiobutton.png'
)});
${isMobileDevice
? css`
margin-right: 10px;`
: ''};
`
export const Footer = styled.div``
export const ScApplyButton = styled(ApplyButton)`

@ -1,58 +0,0 @@
import { atom, selector } from 'recoil'
export interface RawLikeEvent {
e: number,
episode: number,
likes: number,
s: number,
}
export interface LikeEvent extends RawLikeEvent {
iLiked: boolean,
}
export const myLikesState = atom<Array<RawLikeEvent>>({
default: [],
key: 'myLikesState',
})
export const totalLikesState = atom<Array<RawLikeEvent>>({
default: [],
key: 'totalLikesState',
})
const transformedLikesState = selector({
get: ({ get }) => {
const totalLikes = get(totalLikesState)
const myLikes = get(myLikesState)
const likes: Array<LikeEvent> = totalLikes.map((like) => ({
...like,
iLiked: myLikes.findIndex(({ episode }) => episode === like.episode) !== -1,
}))
return likes
},
key: 'transformedLikesState',
})
export const sortTypeState = atom<'asc' | 'desc'>({
default: 'asc',
key: 'sortTypeState',
})
export const filterTypeState = atom<'myLikes' | 'totalLikes'>({
default: 'totalLikes',
key: 'filterTypeState',
})
export const filteredLikesState = selector<Array<LikeEvent>>({
get: ({ get }) => {
const likes = get(transformedLikesState)
const filterType = get(filterTypeState)
return filterType === 'totalLikes' ? likes : likes.filter(({ iLiked }) => iLiked)
},
key: 'filteredLikesState',
})

@ -1,75 +0,0 @@
import { useEffect } from 'react'
import { useSetRecoilState } from 'recoil'
import { LIKES_API_URL } from 'config'
import { useAuthStore } from 'features/AuthStore'
import { FULL_MATCH_BOUNDARY } from 'features/MatchPage/components/LiveMatch/helpers'
import {
usePageParams,
useVideoBounds,
useWebSocket,
} from 'hooks'
import {
myLikesState,
totalLikesState,
type RawLikeEvent,
} from '../atoms'
import { useMatchPageStore } from '..'
interface Data {
data: Array<RawLikeEvent>,
type: 'user_likes' | 'total_likes',
}
export const useLikes = () => {
const setTotalLikes = useSetRecoilState(totalLikesState)
const setMyLikes = useSetRecoilState(myLikesState)
const { playingProgress } = useMatchPageStore()
const videoBounds = useVideoBounds()
const { profileId, sportType } = usePageParams()
const { user } = useAuthStore()
useEffect(() => {
setTotalLikes([])
setMyLikes([])
}, [setMyLikes, setTotalLikes, profileId, sportType])
const url = `${LIKES_API_URL}?sport_id=${sportType}&match_id=${profileId}&access_token=${user?.access_token}`
const { sendMessage, webSocketState } = useWebSocket<Data>({
allowConnection: Boolean(user),
autoReconnect: true,
handlers: {
onMessage: ({ data = [], type }) => {
type === 'total_likes' && setTotalLikes(data)
type === 'user_likes' && setMyLikes(data)
},
},
maxReconnectAttempts: 10,
url,
})
const likeClick = () => {
const startSecond = Number(videoBounds?.find(({ h }) => h === FULL_MATCH_BOUNDARY)?.s || 0)
const message = {
data: {
second: playingProgress + startSecond,
},
event: 'like',
}
sendMessage(message)
}
return {
canLike: user && webSocketState === WebSocket.OPEN,
likeClick,
}
}

@ -147,6 +147,9 @@ export const usePlayersStats = ({
|| !includes([StatsTabs.TEAM1, StatsTabs.TEAM2], selectedStatsTable)
|| !matchProfile?.team1.id
|| !matchProfile?.team2.id
|| (!matchProfile?.live && !isCurrentStats && !isEmpty(playersStats[isTeam1Selected
? matchProfile.team1.id
: matchProfile.team2.id]))
) return
const [res1, res2] = await Promise.all([
@ -198,6 +201,7 @@ export const usePlayersStats = ({
fakeData,
selectedTab,
selectedStatsTable,
isCurrentStats,
])
const beforeCloseTourCallback = () => {

@ -63,9 +63,8 @@ export const useStatsTab = ({
const newStatsType = isFinalStatsType ? StatsType.CURRENT_STATS : StatsType.FINAL_STATS
setStatsType(newStatsType)
selectedStatsTable === StatsTabs.TEAMS
? setIsTeamsStatsFetching(true)
: setIsPlayersStatsFetching(true)
setIsTeamsStatsFetching(true)
setIsPlayersStatsFetching(true)
}
const getEpisodesToPlay = (episodes: Episodes) => map(episodes, (episode, i) => ({

@ -100,6 +100,7 @@ export const useTeamsStats = ({
|| !videoBounds
|| selectedTab !== Tabs.STATS
|| selectedStatsTable !== StatsTab.TEAMS
|| (!matchProfile?.live && !isCurrentStats && !isEmpty(teamsStats))
) return
try {
@ -130,6 +131,7 @@ export const useTeamsStats = ({
getFirstClickableParam,
selectedTab,
selectedStatsTable,
isCurrentStats,
])
const beforeCloseTourCallback = () => {

@ -9,8 +9,8 @@ import sortedUniq from 'lodash/sortedUniq'
import isNull from 'lodash/isNull'
import sortBy from 'lodash/sortBy'
import type { Match } from 'helpers'
import { prepareMatches } from 'helpers'
import type { Match } from 'features/Matches'
import { prepareMatches } from 'features/Matches/helpers/prepareMatches'
import { useAuthStore } from 'features/AuthStore'
import type { MatchInfo } from 'requests'

@ -6,8 +6,6 @@ import {
import { useMatchPage } from './hooks'
export * from './atoms'
type Context = ReturnType<typeof useMatchPage>
type Props = { children: ReactNode }

@ -1,7 +1,6 @@
import type { Lexics, Episodes } from 'requests'
import type { Match } from 'helpers'
import type { Match } from 'features/Matches'
import { Tabs } from 'features/MatchSidePlaylists/config'
import type { MatchPlaylistIds } from './helpers/buildPlaylists'

@ -1,5 +1,5 @@
import type { Match } from 'helpers'
import type { Match } from 'features/Matches/hooks'
export type MatchData = Pick<Match, (
'calc'

@ -0,0 +1,62 @@
import { useQueryClient } from 'react-query'
import { T9n } from 'features/T9n'
import { querieKeys } from 'config'
import { DownloadResponse } from 'requests/downloadPlaylist'
import {
Container,
Description,
Title,
CloseBtn,
Header,
MainContainer,
Error,
} from './styled'
type TDownloadNotification = {
close: () => void,
}
export const DownloadNotification = ({ close }:TDownloadNotification) => {
const client = useQueryClient()
const data = client.getQueryData<DownloadResponse>(querieKeys.downloadPlaylist)
if (!data) {
return null
}
return (
<Container>
<Header>
<CloseBtn
onClick={close}
/>
</Header>
<MainContainer>
<Title>
<T9n t='processed' />
</Title>
<Description>
{data?.status === 'ERROR'
? (
<Error>
<T9n t='error_message' />
</Error>
) : (
<>
<T9n t='can_close' />&nbsp;
<T9n t='will_notified' />&nbsp;
{/* <LinkToDownload href='/useraccount/downloads'> */}
{/* My Videos */}
{/* </LinkToDownload> */}
</>
)}
</Description>
</MainContainer>
</Container>
)
}

@ -0,0 +1,48 @@
import styled from 'styled-components/macro'
import { CloseButton } from 'features/PopupComponents'
export const Container = styled.section`
width: 100%;
border-radius: 0.125rem;
background: #333;
padding: 0.5rem 0.5rem ;
color: #FFF;
font-weight: 400;
line-height: normal;
font-size: 0.75rem;
margin-bottom: 0.45rem;
`
export const Title = styled.h3`
font-size: 0.875rem;
font-style: normal;
font-weight: 700;
margin-bottom: 0.56rem;
`
export const Description = styled.span`
`
export const CloseBtn = styled(CloseButton)`
padding: 4px;
`
export const Header = styled.header`
display: flex;
justify-content: end;
`
export const MainContainer = styled.main`
padding: 0.4rem 1.1rem 2rem 1.3rem;
`
export const LinkToDownload = styled.a`
text-decoration: underline;
cursor: pointer;
color: white;
font-weight: 600;
`
export const Error = styled.span`
color: red;
`

@ -1,126 +0,0 @@
import { Fragment } from 'react'
import { useVideoBounds } from 'hooks'
import type { LikeEvent as LikeEventType } from 'features/MatchPage/store/atoms'
import { T9n } from 'features/T9n'
import { useMatchPageStore } from 'features/MatchPage/store'
import { Tabs } from 'features/MatchSidePlaylists/config'
import { FULL_MATCH_BOUNDARY } from 'features/MatchPage/components/LiveMatch/helpers'
import { PlaylistTypes } from 'features/MatchPage/types'
import {
Title,
LikeIcon,
Time,
Button,
LikesCount,
} from './styled'
type Props = {
groupTitle: string,
likeEvent: LikeEventType,
}
const groupTitlesMap: Record<string, string> = {
full_time: 'ft',
half_time: 'ht',
pre_match: 'pm',
}
export const LikeEvent = ({
groupTitle,
likeEvent,
}: Props) => {
const videoBounds = useVideoBounds()
const { handlePlaylistClick, selectedPlaylist } = useMatchPageStore()
const firstBound = videoBounds?.find(({ h }) => h === FULL_MATCH_BOUNDARY)
const half = groupTitle.match(/\d/)
const startSecond = half
? likeEvent.s - Number(videoBounds?.find(({ h }) => h === half[0])?.s || 0)
: 0
const startMinute = Math.ceil(startSecond / 60)
const active = selectedPlaylist.id === likeEvent.episode && selectedPlaylist.tab === Tabs.LIKES
const handleClick = () => {
handlePlaylistClick({
playlist: {
episodes: [{
c: Math.ceil((likeEvent.e - likeEvent.s) / 12),
e: likeEvent.e - Number(firstBound?.s || 0),
h: 0,
s: likeEvent.s - Number(firstBound?.s || 0),
}],
id: likeEvent.episode,
type: PlaylistTypes.EVENT,
},
tab: Tabs.LIKES,
})
}
const getTitle = () => {
switch (true) {
case likeEvent.iLiked && likeEvent.likes === 1:
return (
<T9n t='you_liked_this' />
)
case likeEvent.iLiked && likeEvent.likes === 2:
return (
<Fragment>
<T9n t='you_and' /> <LikesCount>1</LikesCount> <T9n t='user_liked_this' />
</Fragment>
)
case likeEvent.iLiked && likeEvent.likes > 2:
return (
<Fragment>
<T9n t='you_and' /> <LikesCount>{likeEvent.likes - 1}</LikesCount> <T9n t='users_liked_this' />
</Fragment>
)
case !likeEvent.iLiked && likeEvent.likes === 1:
return (
<Fragment>
<LikesCount>1</LikesCount> <T9n t='user_liked_this' />
</Fragment>
)
case !likeEvent.iLiked && likeEvent.likes > 1:
return (
<Fragment>
<LikesCount>{likeEvent.likes}</LikesCount> <T9n t='users_liked_this' />
</Fragment>
)
default: return null
}
}
return (
<Button
onClick={handleClick}
active={active}
groupTitle={groupTitle}
>
{likeEvent.iLiked && (
<LikeIcon
alt='Like'
src='/images/like-active-icon.svg'
/>
)}
{groupTitle && (
<Fragment>
{groupTitle in groupTitlesMap
? <Time><T9n t={groupTitlesMap[groupTitle]} /></Time>
: <Time>{startMinute}&apos;</Time>}
</Fragment>
)}
<Title>{getTitle()}</Title>
</Button>
)
}

@ -1,35 +0,0 @@
import styled from 'styled-components/macro'
import { Button as ButtonBase } from 'features/MatchSidePlaylists/styled'
type ButtonProps = {
groupTitle: string,
}
export const Button = styled(ButtonBase)<ButtonProps>`
justify-content: initial;
padding-left: ${({ groupTitle }) => (groupTitle ? 77 : 42)}px;
`
export const Title = styled.div`
font-size: 12px;
color: ${({ theme }) => theme.colors.white};
`
export const LikeIcon = styled.img`
position: absolute;
left: 20px;
width: 11px;
height: 11px;
`
export const Time = styled.span`
position: absolute;
right: calc(100% - 67px);
font-size: 12px;
color: ${({ theme }) => theme.colors.white};
`
export const LikesCount = styled.span`
font-weight: 600;
`

@ -1,45 +0,0 @@
import { T9n } from 'features/T9n'
import type { LikeEvent as LikeEventType } from 'features/MatchPage/store'
import {
BlockTitle,
Event,
TextEvent,
List,
} from '../TabEvents/styled'
import { LikeEvent } from '../LikeEvent'
type Props = {
groupTitle: string,
likeEvents: Array<LikeEventType>,
}
export const LikesList = ({
groupTitle,
likeEvents,
}: Props) => {
if (!likeEvents.length) return null
const title = groupTitle.startsWith('half') ? 'half_time' : groupTitle
return (
<List>
{title && (
<TextEvent>
<BlockTitle>
<T9n t={title} />
</BlockTitle>
</TextEvent>
)}
{likeEvents.map((likeEvent) => (
<Event key={likeEvent.episode}>
<LikeEvent
likeEvent={likeEvent}
groupTitle={title}
/>
</Event>
))}
</List>
)
}

@ -11,14 +11,31 @@ import { MatchDownloadPopup } from 'features/MatchPage/components/MatchDownloadP
import { usePageParams } from 'hooks/usePageParams'
import { Item } from '../MatchPlaylists'
import { useQuery } from 'react-query'
import { downloadPlaylist, type DownloadPlaylistProps } from 'requests/downloadPlaylist'
import { querieKeys } from 'config'
import { useInterval } from 'hooks'
import { useEffect, useState } from 'react'
import { downloadFile } from 'helpers'
import { DownloadButton, Title } from '../../styled'
import { Item } from '../MatchPlaylists'
type TMatchDownloadButton = {
close: () => void,
open: () => void,
}
export const MatchDownloadButton = () => {
const INTERVAL_CHECK_DOWNLOAD_STATUS = 60 * 1000
export const MatchDownloadButton = ({
close: closeDownloadNotification,
open: openDownloadNotification,
}:TMatchDownloadButton) => {
const { user, userInfo } = useAuthStore()
const { profile } = useMatchPageStore()
const { sportType } = usePageParams()
const [playlistConfig, setPlaylistConfig] = useState<DownloadPlaylistProps | null>(null)
const [isDownloaded, setIsDownloaded] = useState(false)
const {
close,
isOpen,
@ -47,6 +64,41 @@ export const MatchDownloadButton = () => {
return null
}
const { data, refetch } = useQuery(
querieKeys.downloadPlaylist,
() => playlistConfig && downloadPlaylist(playlistConfig),
{
enabled: false,
refetchOnWindowFocus: false,
staleTime: 0,
},
)
const { start, stop } = useInterval({
callback: refetch,
intervalDuration: INTERVAL_CHECK_DOWNLOAD_STATUS,
startImmediate: true,
})
useEffect(() => {
if (!data) return undefined
if (
data?.status === 'COMPLETED'
&& data.urls
&& !isDownloaded) {
downloadFile(data.urls)
setIsDownloaded(true)
closeDownloadNotification()
stop()
} else if (data?.status === 'ERROR') {
stop()
}
return () => stop()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data?.status, refetch, playlistConfig])
const isNeedDownloadButton = user && (isAvailableTournaments() || isAvailableTeams())
if (!isNeedDownloadButton) return null
@ -56,8 +108,16 @@ export const MatchDownloadButton = () => {
<MatchDownloadPopup
isModalOpen={isOpen}
closePopup={close}
openDownloadNotification={openDownloadNotification}
setPlaylistConfig={setPlaylistConfig}
/>
<DownloadButton onClick={open}>
<DownloadButton onClick={
() => {
open()
start()
}
}
>
<Title>
<T9n t='download_full_match' />
</Title>

@ -1,5 +1,6 @@
import type { ForwardedRef } from 'react'
import { forwardRef } from 'react'
import { forwardRef, useEffect } from 'react'
import { useQueryClient } from 'react-query'
import styled, { css } from 'styled-components/macro'
@ -7,7 +8,9 @@ import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import some from 'lodash/some'
import { isMobileDevice } from 'config'
import { isMobileDevice, querieKeys } from 'config'
import { DownloadResponse } from 'requests/downloadPlaylist'
import type {
MatchPlaylistOption,
@ -22,6 +25,9 @@ import { useLexicsStore } from 'features/LexicsStore'
import { MATCH_ADS } from 'components/Ads/types'
import { usePageParams, useToggle } from 'hooks'
import { DownloadNotification } from '../DownloadNotification/DownloadNotification'
import { PlayButton } from '../PlayButton'
import { MatchDownloadButton } from '../MatchDownloadButton'
@ -71,6 +77,13 @@ export const MatchPlaylists = forwardRef(
) => {
const { ads, setEpisodeInfo } = useMatchPageStore()
const { translate } = useLexicsStore()
const {
close,
isOpen,
open,
} = useToggle()
const { profileId } = usePageParams()
const handleButtonClick = (playlist: MatchPlaylistOption) => {
onSelect?.(playlist)
@ -82,13 +95,22 @@ export const MatchPlaylists = forwardRef(
})
}
const client = useQueryClient()
const data = client.getQueryData<DownloadResponse>(querieKeys.downloadPlaylist)
useEffect(() => {
close()
}, [profileId, close])
return (
<List
ref={ref}
isAdsExist={some(ads, ({ position }) => position.id === MATCH_ADS.WATCH_TOP)}
>
{
map(playlists, (playlist) => (
{isOpen && data
? (<DownloadNotification close={close} />)
: map(playlists, (playlist) => (
<Item
key={playlist.id}
id={`match_watch_${playlist.id}${live ? '_live' : ''}`}
@ -103,9 +125,8 @@ export const MatchPlaylists = forwardRef(
<T9n t={playlist.lexic} />
</PlayButton>
</Item>
))
}
<MatchDownloadButton />
))}
<MatchDownloadButton open={open} close={close} />
</List>
)
},

@ -157,7 +157,7 @@ export const Tabs = styled(TabsBase)`
color: #ffff;
flex: 1 1 auto;
${isMobileDevice && 'padding-left: 33px;'}
${isMobileDevice ? 'padding-left: 33px;' : 'padding-left: 20px;'}
`
type TTab = {

@ -1,235 +0,0 @@
/* eslint-disable sort-keys */
import { useEffect } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import findKey from 'lodash/findKey'
import orderBy from 'lodash/orderBy'
import isNumber from 'lodash/isNumber'
import { useVideoBounds } from 'hooks'
import type { MatchInfo } from 'requests'
import { T9n } from 'features/T9n'
import {
filterTypeState,
filteredLikesState,
sortTypeState,
type LikeEvent,
useMatchPageStore,
} from 'features/MatchPage/store'
import { Tabs } from 'features/MatchSidePlaylists/config'
import { LikesList } from '../LikesList'
import {
Wrapper,
Tabs as SortTabs,
Tab as SortTab,
ButtonsBlock,
HalfEvents,
HalfList,
} from '../TabEvents/styled'
import {
Tab,
TabList,
TabTitle,
} from './styled'
type Props = {
profile?: MatchInfo,
}
const groupTitles = [
'pre_match',
'1st_half',
'half1-2',
'half1-3',
'half1-4',
'half1-5',
'2nd_half',
'half2-3',
'half2-4',
'half2-5',
'3rd_half',
'half3-4',
'half3-5',
'4th_half',
'half4-5',
'5th_half',
'full_time',
]
export const TabLikes = ({ profile: matchProfile }:Props) => {
const [filterType, setFilterType] = useRecoilState(filterTypeState)
const [sortType, setSortType] = useRecoilState(sortTypeState)
const likeEvents = useRecoilValue(filteredLikesState)
const { selectedPlaylist, setEpisodeInfo } = useMatchPageStore()
const videoBounds = useVideoBounds()
const needUseGroups = videoBounds && videoBounds.findIndex(({ h }) => h === '1') !== -1
const getHalfInterval = (period: number) => {
const bound = videoBounds?.find(({ h }) => h === String(period))
return [bound?.s && Number(bound.s), bound?.e && Number(bound.e)]
}
const getTimeoutInterval = (firstPeriod: number, secondPeriod: number) => {
const firstBound = videoBounds?.find(({ h }) => h === String(firstPeriod))
const secondBound = videoBounds?.find(({ h }) => h === String(secondPeriod))
return [firstBound?.e && Number(firstBound.e) + 1, secondBound?.s && Number(secondBound.s) - 1]
}
const getGroupedLikes = () => {
if (!needUseGroups) return {}
const firstHalf = videoBounds?.find(({ h }) => h === '1')
const lastHalf = videoBounds?.[videoBounds.length - 1]
const groupedLikes: Record<string, Array<LikeEvent>> = {
pre_match: [],
'1st_half': [],
'half1-2': [],
'2nd_half': [],
'half2-3': [],
'3rd_half': [],
'half3-4': [],
'4th_half': [],
'half4-5': [],
'5th_half': [],
full_time: [],
'half1-3': [],
'half1-4': [],
'half1-5': [],
'half2-4': [],
'half2-5': [],
'half3-5': [],
}
const intervals: Record<string, Array<number | undefined | string>> = {
pre_match: [0, Number(firstHalf?.s || 0) - 1],
'1st_half': getHalfInterval(1),
'half1-2': getTimeoutInterval(1, 2),
'2nd_half': getHalfInterval(2),
'half2-3': getTimeoutInterval(2, 3),
'3rd_half': getHalfInterval(3),
'half3-4': getTimeoutInterval(3, 4),
'4th_half': getHalfInterval(4),
'half4-5': getTimeoutInterval(4, 5),
'5th_half': getHalfInterval(5),
full_time: [Number(lastHalf?.e || 0) + 1, 1e5],
'half3-5': getTimeoutInterval(3, 5),
'half2-4': getTimeoutInterval(2, 4),
'half2-5': getTimeoutInterval(2, 5),
'half1-3': getTimeoutInterval(1, 3),
'half1-4': getTimeoutInterval(1, 4),
'half1-5': getTimeoutInterval(1, 5),
}
likeEvents.forEach((likeEvent) => {
const half = findKey(intervals, (interval) => {
if (isNumber(interval[0]) && isNumber(interval[1])) {
return likeEvent.s >= interval[0] && likeEvent.s <= interval[1]
}
if (isNumber(interval[0])
&& !interval[1]
&& likeEvent.s >= interval[0]
&& matchProfile?.live
) return true
return false
})
half && groupedLikes[half].push(likeEvent)
})
Object.keys(groupedLikes).forEach((key) => {
groupedLikes[key] = orderBy(
groupedLikes[key],
's',
sortType,
)
})
return groupedLikes
}
const groupedLikes = getGroupedLikes()
const likesEntries = needUseGroups
? orderBy(
Object.entries(groupedLikes),
([title]) => groupTitles.findIndex((key) => key === title),
sortType,
)
: []
const sortedLikes = needUseGroups ? [] : orderBy(
likeEvents,
's',
sortType,
)
useEffect(() => {
selectedPlaylist.tab === Tabs.LIKES && setEpisodeInfo({})
}, [setEpisodeInfo, selectedPlaylist.tab])
return (
<Wrapper>
<ButtonsBlock>
<SortTabs>
<T9n t={sortType === 'asc' ? 'from_start_match' : 'from_end_match'} />
<SortTab
active={sortType === 'asc'}
onClick={() => setSortType('asc')}
id='match_likes_sort_start'
/>
<SortTab
active={sortType === 'desc'}
onClick={() => setSortType('desc')}
id='match_likes_sort_final'
/>
</SortTabs>
<TabList>
<Tab
aria-pressed={filterType === 'totalLikes'}
onClick={() => setFilterType('totalLikes')}
>
<TabTitle t='total_likes' />
</Tab>
<Tab
aria-pressed={filterType === 'myLikes'}
onClick={() => setFilterType('myLikes')}
>
<TabTitle t='my_likes' />
</Tab>
</TabList>
</ButtonsBlock>
{needUseGroups ? (
<HalfList>
{likesEntries.map(([groupTitle, likesInGroup]) => (
<HalfEvents key={groupTitle}>
<LikesList
groupTitle={groupTitle}
likeEvents={likesInGroup}
/>
</HalfEvents>
))}
</HalfList>
)
: (
<HalfList>
<HalfEvents>
<LikesList
groupTitle=''
likeEvents={sortedLikes}
/>
</HalfEvents>
</HalfList>
)}
</Wrapper>
)
}

@ -1,40 +0,0 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config'
import { T9n } from 'features/T9n'
export const TabList = styled.div.attrs({ role: 'tablist' })`
display: flex;
gap: 12px;
${isMobileDevice && css`
padding-right: 33px;
`}
`
export const TabTitle = styled(T9n)`
position: relative;
color: rgba(255, 255, 255, 0.5);
`
export const Tab = styled.button.attrs({ role: 'tab' })`
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 0 0 5px;
font-size: 12px;
cursor: pointer;
border: none;
background: none;
border-bottom: 2px solid transparent;
&[aria-pressed="true"] {
border-color: ${({ theme }) => theme.colors.white};
${TabTitle} {
color: ${({ theme }) => theme.colors.white};
}
}
`

@ -3,5 +3,4 @@ export enum Tabs {
EVENTS,
STATS,
PLAYERS,
LIKES,
}

@ -27,7 +27,7 @@ export const useMatchSidePlaylists = () => {
const videoBounds = matchScore?.video_bounds || matchProfile?.video_bounds
const matchStatus = matchScore?.c_match_calc_status || matchProfile?.c_match_calc_status
const isMatchParsed = Number(matchStatus) > MatchStatuses.Upcoming
const showTabs = Number(matchStatus) > MatchStatuses.Upcoming
&& findIndex(videoBounds, ({ h, s }) => h === '1' && !isNil(s)) !== -1
useEffect(() => {
@ -35,8 +35,8 @@ export const useMatchSidePlaylists = () => {
}, [selectedTab, closePopup])
return {
isMatchParsed,
onTabClick: setSelectedTab,
selectedTab,
showTabs,
}
}

@ -4,12 +4,11 @@ import {
useState,
} from 'react'
import { createPortal } from 'react-dom'
import { useRecoilValue } from 'recoil'
import { useTour } from '@reactour/tour'
import type { PlaylistOption } from 'features/MatchPage/types'
import { totalLikesState, useMatchPageStore } from 'features/MatchPage/store'
import { useMatchPageStore } from 'features/MatchPage/store'
import {
Spotlight,
Steps,
@ -31,7 +30,6 @@ import { TabEvents } from './components/TabEvents'
import { TabWatch } from './components/TabWatch'
import { TabPlayers } from './components/TabPlayers'
import { TabStats } from './components/TabStats'
import { TabLikes } from './components/TabLikes'
import { useMatchSidePlaylists } from './hooks'
import {
@ -42,6 +40,7 @@ import {
TabIcon,
TabTitle,
Container,
TabButton,
EventsAdsWrapper,
} from './styled'
import { HeaderAds } from '../../components/Ads'
@ -51,7 +50,6 @@ const tabPanes = {
[Tabs.EVENTS]: TabEvents,
[Tabs.STATS]: TabStats,
[Tabs.PLAYERS]: TabPlayers,
[Tabs.LIKES]: TabLikes,
}
type Props = {
@ -63,8 +61,6 @@ export const MatchSidePlaylists = ({
onSelect,
selectedPlaylist,
}: Props) => {
const likeEvents = useRecoilValue(totalLikesState)
const {
ads,
hideProfileCard,
@ -77,8 +73,8 @@ export const MatchSidePlaylists = ({
} = useMatchPageStore()
const {
isMatchParsed,
onTabClick,
showTabs,
} = useMatchSidePlaylists()
const {
@ -93,7 +89,6 @@ export const MatchSidePlaylists = ({
const containerRef = useRef<HTMLDivElement | null>(null)
const tabPaneContainerRef = useRef<HTMLDivElement | null>(null)
const tabsGroupRef = useRef<HTMLDivElement | null>(null)
const [hasTabPaneScroll, setTabPaneScroll] = useState(false)
@ -118,14 +113,14 @@ export const MatchSidePlaylists = ({
if (
getLocalStorageItem(TOUR_COMPLETED_STORAGE_KEY) === 'true'
|| isOpen
|| !isMatchParsed
|| !showTabs
|| Number(profile?.c_match_calc_status) < 2
) return undefined
const timer = setTimeout(() => setIsOpen(true), 1500)
return () => clearTimeout(timer)
}, [isMatchParsed, setIsOpen, profile?.c_match_calc_status, isOpen])
}, [showTabs, setIsOpen, profile?.c_match_calc_status, isOpen])
useEventListener({
callback: () => {
@ -142,19 +137,6 @@ export const MatchSidePlaylists = ({
target: containerRef,
})
const getTabsSpace = () => {
const tabsGroup = tabsGroupRef.current
const tabsWidth = isMobileDevice && containerRef.current
? containerRef.current.clientWidth - 20
: 306
return tabsGroup?.children.length && tabsGroup.children.length > 1
? (tabsWidth - (Array.from(tabsGroup.children) as Array<HTMLButtonElement>)
.reduce((acc, elem) => acc + elem.clientWidth, 0)) / tabsGroup.children.length - 1
: 0
}
return (
<Wrapper
ref={containerRef}
@ -163,74 +145,64 @@ export const MatchSidePlaylists = ({
isTourOpen={Boolean(isOpen)}
isHidden={!profileCardShown}
>
<TabsWrapper>
{selectedTab === Tabs.EVENTS
{showTabs
&& (
<TabsWrapper>
{selectedTab === Tabs.EVENTS
&& ads
&& (
<EventsAdsWrapper hasScroll={hasTabPaneScroll}>
<HeaderAds ads={ads.filter(({ position }) => position.id === adsPositionId)} />
</EventsAdsWrapper>
)}
<TabsGroup
ref={tabsGroupRef}
space={getTabsSpace()}
>
{(isMatchParsed || likeEvents.length > 0) && (
<Tab
aria-pressed={selectedTab === Tabs.WATCH}
onClick={() => onTabClick(Tabs.WATCH)}
id='match_watch'
>
<TabIcon icon='watch' />
<TabTitle t='watch' />
</Tab>
)}
{isMatchParsed && (
<Tab
aria-pressed={selectedTab === Tabs.EVENTS}
onClick={() => onTabClick(Tabs.EVENTS)}
id='match_plays'
>
<TabIcon icon='plays' />
<TabTitle t='actions' />
</Tab>
)}
{likeEvents.length > 0 && (
<Tab
aria-pressed={selectedTab === Tabs.LIKES}
onClick={() => onTabClick(Tabs.LIKES)}
id='match_likes'
>
<TabIcon icon='likes' />
<TabTitle t='likes' />
</Tab>
)}
{isMatchParsed && (
<Tab
aria-pressed={selectedTab === Tabs.PLAYERS}
onClick={() => onTabClick(Tabs.PLAYERS)}
id='match_players'
>
<TabIcon icon='players' />
<TabTitle t='players' />
</Tab>
)}
{isMatchParsed && (
<Tab
aria-pressed={selectedTab === Tabs.STATS}
onClick={() => onTabClick(Tabs.STATS)}
data-step={Steps.Start}
id='match_stats'
>
{Boolean(currentStep === Steps.Start && isOpen) && (
<Spotlight />
)}
<TabIcon icon='stats' />
<TabTitle t='stats' />
</Tab>
)}
</TabsGroup>
</TabsWrapper>
<TabsGroup>
<Tab
aria-pressed={selectedTab === Tabs.WATCH}
onClick={() => onTabClick(Tabs.WATCH)}
id='match_watch'
>
<TabButton>
<TabIcon icon='watch' />
<TabTitle t='watch' />
</TabButton>
</Tab>
<Tab
aria-pressed={selectedTab === Tabs.EVENTS}
onClick={() => onTabClick(Tabs.EVENTS)}
id='match_plays'
>
<TabButton>
<TabIcon icon='plays' />
<TabTitle t='actions' />
</TabButton>
</Tab>
<Tab
aria-pressed={selectedTab === Tabs.PLAYERS}
onClick={() => onTabClick(Tabs.PLAYERS)}
id='match_players'
>
<TabButton>
<TabIcon icon='players' />
<TabTitle t='players' />
</TabButton>
</Tab>
<Tab
aria-pressed={selectedTab === Tabs.STATS}
onClick={() => onTabClick(Tabs.STATS)}
data-step={Steps.Start}
id='match_stats'
>
{Boolean(currentStep === Steps.Start && isOpen) && (
<Spotlight />
)}
<TabButton>
<TabIcon icon='stats' />
<TabTitle t='stats' />
</TabButton>
</Tab>
</TabsGroup>
</TabsWrapper>
)}
<Container
hasScroll={hasTabPaneScroll}
ref={tabPaneContainerRef}

@ -6,7 +6,7 @@ import {
devices,
} from 'config'
import { customScrollbar } from 'features/Common'
import { ButtonOutline, customScrollbar } from 'features/Common'
import { T9n } from 'features/T9n'
type WrapperProps = {
@ -53,14 +53,10 @@ export const Wrapper = styled.div<WrapperProps>`
export const TabsWrapper = styled.div``
type TabsGroupProps = {
space: number,
}
export const TabsGroup = styled.div.attrs({ role: 'tablist' })<TabsGroupProps>`
export const TabsGroup = styled.div.attrs({ role: 'tablist' })`
display: flex;
justify-content: center;
gap: min(${({ space }) => space}px, ${isMobileDevice ? 19 : 16}px);
gap: ${isMobileDevice ? 30 : 20}px;
padding-top: 10px;
`
@ -71,8 +67,7 @@ export const TabTitle = styled(T9n)`
color: ${({ theme }) => theme.colors.white};
`
export const Tab = styled.button.attrs({ role: 'tab' })`
position: relative;
export const TabButton = styled.button`
display: flex;
flex-direction: column;
justify-content: space-between;
@ -83,9 +78,19 @@ export const Tab = styled.button.attrs({ role: 'tab' })`
cursor: pointer;
border: none;
background: none;
`
export const Tab = styled.div.attrs({ role: 'tab' })`
position: relative;
&[aria-pressed="true"], :hover {
opacity: 1;
${TabButton} {
opacity: 1;
${TabTitle} {
font-weight: 600;
}
}
}
:only-child {
@ -94,7 +99,7 @@ export const Tab = styled.button.attrs({ role: 'tab' })`
`
type TabIconProps = {
icon: 'watch' | 'plays' | 'players' | 'stats' | 'likes',
icon: 'watch' | 'plays' | 'players' | 'stats',
}
export const TabIcon = styled.div<TabIconProps>`
@ -107,7 +112,7 @@ export const TabIcon = styled.div<TabIconProps>`
background-position: center;
background-size: contain;
${({ icon }) => (['likes', 'players'].includes(icon)
${({ icon }) => (icon === 'players'
? css`
background-size: 25px;
`
@ -214,19 +219,16 @@ export const Button = styled.button<ButtonProps>`
}}
`
export const DownloadButton = styled(Button)`
export const DownloadButton = styled(ButtonOutline)`
width: 100%;
height: 2.25rem;
font-size: 0.875rem;
font-weight: 600;
line-height: 1rem;
:hover {
background-color: ${({ theme }) => theme.colors.buttonHover};
}
${({ active }) => (active
? css`
border: 1px solid #FFFFFF;
border-radius: 2px;
background-color: black;
`
: '')}
`
export const Title = styled.span`

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save