Compare commits

..

35 Commits

Author SHA1 Message Date
Rakov 8896df9f1f fix(refresh): refresh token fix 2 years ago
Rakov 10bca448dc fix(refresh): resfresh token fix 2 years ago
Rakov 7a2423568a fix(refresh): save refresh lff facr 2 years ago
Rakov ccb265cb2e Revert "fix(#699): auth token" 2 years ago
Rakov 3152372c39 fix(inposrt.live): insport.live makefile build 2 years ago
Rakov 4e43b9aea8 fix(insport.live): insport.live deploy 2 years ago
Rakov 0c5a1aac4f fix(#772): fqtv button lexic 2 years ago
Rakov fc3a3c052c fix(#699): auth token 2 years ago
Rakov 888ac35156 fix(favicon): favicon facr lff fqtv 2 years ago
Rakov 2b9b93ef31 fix(favicon): favicon lff 2 years ago
Rakov 1335d34b07 fix(preview): added preview width 2 years ago
Rakov 42623654bb fix(#720): timeline tile size 2 years ago
Rakov c0d06f8ef9 fix(#720): update penalty score 2 years ago
Rakov 2dba409aa7 fix(#720): fix timeline scroll 2 years ago
Rakov 8dce04b35d fix(#720): add timeline timezone 2 years ago
Rakov 642e3809dc fix(#720): timeline media query fix 2 years ago
Rakov c41acef4ce fix(#720): timeline mode 2 years ago
Rakov af912dca10 fix(toc): tOC link fix 2 years ago
Ruslan Khayrullin d06425df94 feat(in-645): likes 2 years ago
Rakov 0e0f24454e fix(#747): new broadcast endpoint 2 years ago
Rakov 2a95f32aeb fix(#727): save match stats fix 2 years ago
Rakov bef03ff120 fix(#ios): ios player fix 2 years ago
Rakov bd218b8888 fix(#750): check device,toc for india 2 years ago
Margarita 2d321ff6a2 feat(date-filter): date filter fixes 2 years ago
Margarita d4c4bd6e81 feat(in-738): user password fix 2 years ago
Rakov 0b2c38871a fix(#hot-fix): disabled watching for virtual user 2 years ago
andreidekterev 9f0c779246 fix(#734): add watermark for india 2 years ago
Farber Denis 02c72a87f1 fix(#in730): facr header settings popup temp disable 2 years ago
Margarita f70fe23926 feat(in-695): mobile ads fixes 2 years ago
Margarita 5e0b262839 feat(in-717): ads fixes 2 years ago
Ruslan Khayrullin 590ce1bb90 fix(in-723): stats fixes 2 years ago
Rakov 806a2519b6 fix(#677): auto authorize 2 years ago
Margarita efce351342 feat(in-710): personal info language fixes 2 years ago
Rakov f4b96c4668 fix(india): fix india PP nad TaC 2 years ago
Rakov 77f71b852b fix(#phonepe): fix iframe payment for mobile 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. 15
      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. 46
      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. 145
      src/features/HeaderFilters/components/DateFilter/index.tsx
  57. 113
      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. 11
      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. 103
      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. 62
      src/features/MatchSidePlaylists/index.tsx
  100. 42
      src/features/MatchSidePlaylists/styled.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1007,3 +1007,56 @@ steps:
- aws cloudfront create-invalidation --distribution-id E15IFY23VM147K --paths "/*" - aws cloudfront create-invalidation --distribution-id E15IFY23VM147K --paths "/*"
depends_on: depends_on:
- make-rustat - 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,6 +225,15 @@ rustat-prod:
BUILD_PATH=build_rustat \ BUILD_PATH=build_rustat \
npm run build && cp -r .well-known 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 deploy-all: prod preprod facr-prod lff-prod diwansport-prod india-prod fqtv-prod rustat-prod
test: test:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

@ -0,0 +1,12 @@
<?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.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,19 @@
{
"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.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,18 @@
<?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>

After

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: 11 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 598 B

After

Width:  |  Height:  |  Size: 884 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,4 @@
<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>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -1,15 +1,28 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title></title> <title></title>
</head> </head>
<body> <body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client/1.11.5/oidc-client.min.js" 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> <script>
new Oidc.UserManager().signinSilentCallback() 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) => { .catch((err) => {
console.error('OIDC: silent refresh callback error', err); console.error('OIDC: silent refresh callback error', err);
}); });
</script> </script>
</body> </body>
</html> </html>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,191 +0,0 @@
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,3 +1,2 @@
export * from './components/DateFilter' export * from './components/DateFilter'
export * from './components/FacrDateFilter'
export * from './store' export * from './store'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,18 +1,7 @@
import { useEffect, useState } from 'react' import { useState } from 'react'
import { useQuery } from 'react-query'
import { T9n } from 'features/T9n' 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 { import {
Header, Header,
Footer, Footer,
@ -24,76 +13,20 @@ import {
FirstTitle, FirstTitle,
SecondTitle, SecondTitle,
RadioButtonsWrapper, RadioButtonsWrapper,
Input,
Label, Label,
} from './styled' } from './styled'
type Props = { type Props = {
closePopup: () => void, closePopup: () => void,
isModalOpen: boolean, 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 = ({ export const MatchDownloadPopup = ({
closePopup, closePopup,
isModalOpen, isModalOpen,
openDownloadNotification,
setPlaylistConfig,
}: Props) => { }: Props) => {
const [downloadConfig, setDownloadConfig] = useState<typeof initialRadioBtns>(initialRadioBtns) const [selected, setSelected] = useState(true)
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 ( return (
<Modal <Modal
@ -109,28 +42,28 @@ export const MatchDownloadPopup = ({
</HeaderTitle> </HeaderTitle>
</Header> </Header>
<RadioButtonsWrapper> <RadioButtonsWrapper>
{Object.entries(downloadConfig).map(([id, checked], index) => (
<Label> <Label>
<Radio <Input
name={id} defaultChecked={selected}
key={id} name='in_game_time_only'
checked={checked} onClick={() => setSelected(true)}
onChange={(e) => handleChange(e.target.name)}
/> />
<T9n t={id} /> <T9n t='in_game_time_only' />
</Label>
<Label>
<Input
name='entire_record'
defaultChecked={!selected}
onClick={() => setSelected(false)}
/>
<T9n t='entire_record' />
</Label> </Label>
))}
</RadioButtonsWrapper> </RadioButtonsWrapper>
<Footer> <Footer>
<ScApplyButton <ScApplyButton>
onClick={() => handleDownload(fileType.download_single_file)}
>
<T9n t='download_single_file' /> <T9n t='download_single_file' />
</ScApplyButton> </ScApplyButton>
<ScApplyButton <ScApplyButton disabled={!selected}>
disabled={!downloadConfig.in_game_time_only}
onClick={() => handleDownload(fileType.download_files_for_periods)}
>
<T9n t='download_files_for_periods' /> <T9n t='download_files_for_periods' />
</ScApplyButton> </ScApplyButton>
</Footer> </Footer>

@ -100,6 +100,29 @@ export const Label = styled.label`
font-size: 14px;` 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 Footer = styled.div``
export const ScApplyButton = styled(ApplyButton)` export const ScApplyButton = styled(ApplyButton)`

@ -0,0 +1,58 @@
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',
})

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

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

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

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

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

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

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

@ -1,62 +0,0 @@
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>
)
}

@ -1,48 +0,0 @@
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;
`

@ -0,0 +1,126 @@
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>
)
}

@ -0,0 +1,35 @@
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;
`

@ -0,0 +1,45 @@
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,31 +11,14 @@ import { MatchDownloadPopup } from 'features/MatchPage/components/MatchDownloadP
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks/usePageParams'
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' import { Item } from '../MatchPlaylists'
import { DownloadButton, Title } from '../../styled'
type TMatchDownloadButton = { export const MatchDownloadButton = () => {
close: () => void,
open: () => void,
}
const INTERVAL_CHECK_DOWNLOAD_STATUS = 60 * 1000
export const MatchDownloadButton = ({
close: closeDownloadNotification,
open: openDownloadNotification,
}:TMatchDownloadButton) => {
const { user, userInfo } = useAuthStore() const { user, userInfo } = useAuthStore()
const { profile } = useMatchPageStore() const { profile } = useMatchPageStore()
const { sportType } = usePageParams() const { sportType } = usePageParams()
const [playlistConfig, setPlaylistConfig] = useState<DownloadPlaylistProps | null>(null)
const [isDownloaded, setIsDownloaded] = useState(false)
const { const {
close, close,
isOpen, isOpen,
@ -64,41 +47,6 @@ export const MatchDownloadButton = ({
return null 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()) const isNeedDownloadButton = user && (isAvailableTournaments() || isAvailableTeams())
if (!isNeedDownloadButton) return null if (!isNeedDownloadButton) return null
@ -108,16 +56,8 @@ export const MatchDownloadButton = ({
<MatchDownloadPopup <MatchDownloadPopup
isModalOpen={isOpen} isModalOpen={isOpen}
closePopup={close} closePopup={close}
openDownloadNotification={openDownloadNotification}
setPlaylistConfig={setPlaylistConfig}
/> />
<DownloadButton onClick={ <DownloadButton onClick={open}>
() => {
open()
start()
}
}
>
<Title> <Title>
<T9n t='download_full_match' /> <T9n t='download_full_match' />
</Title> </Title>

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

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

@ -0,0 +1,235 @@
/* 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>
)
}

@ -0,0 +1,40 @@
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,4 +3,5 @@ export enum Tabs {
EVENTS, EVENTS,
STATS, STATS,
PLAYERS, PLAYERS,
LIKES,
} }

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

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

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

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

Loading…
Cancel
Save