develop #128

Merged
andrey.dekterev merged 80 commits from develop into master 3 years ago
  1. 182
      .drone.yml
  2. 4
      .eslintrc
  3. 2
      .gitignore
  4. 17
      Makefile
  5. 88
      package-lock.json
  6. 4
      package.json
  7. 9
      public/apple-app-site-association
  8. BIN
      public/clients/tunisia/favicon/android-chrome-192x192.png
  9. BIN
      public/clients/tunisia/favicon/android-chrome-512x512.png
  10. BIN
      public/clients/tunisia/favicon/apple-touch-icon.png
  11. 12
      public/clients/tunisia/favicon/browserconfig.xml
  12. BIN
      public/clients/tunisia/favicon/favicon-16x16.png
  13. BIN
      public/clients/tunisia/favicon/favicon-32x32.png
  14. BIN
      public/clients/tunisia/favicon/favicon.ico
  15. 19
      public/clients/tunisia/favicon/manifest.json
  16. BIN
      public/clients/tunisia/favicon/mstile-144x144.png
  17. BIN
      public/clients/tunisia/favicon/mstile-150x150.png
  18. BIN
      public/clients/tunisia/favicon/mstile-310x310.png
  19. BIN
      public/clients/tunisia/favicon/mstile-70x70.png
  20. 18
      public/clients/tunisia/favicon/safari-pinned-tab.svg
  21. 1550
      public/clients/tunisia/privacy-policy-and-statement.html
  22. 4368
      public/clients/tunisia/terms-and-conditions.html
  23. BIN
      public/images/landing_ligue_1.png
  24. BIN
      public/images/landing_mobile_ligue_1.png
  25. 3
      public/images/matchTabs/bets.svg
  26. 3
      public/images/matchTabs/chat.svg
  27. 6
      public/images/matchTabs/players.svg
  28. 4
      public/images/matchTabs/plays.svg
  29. 12
      public/images/matchTabs/stats.svg
  30. 3
      public/images/matchTabs/watch.svg
  31. 22
      public/images/score-switch-tunisia-off.svg
  32. 29
      public/images/score-switch-tunisia-on.svg
  33. 3
      public/images/sortUp.svg
  34. 29
      public/images/tunis_auth_logo_mobile.svg
  35. 84
      public/images/tunis_clubs.svg
  36. BIN
      public/images/tunisia-logo-white.png
  37. 17
      public/index.html
  38. 15
      public/silent-refresh.html
  39. 128
      src/components/AccessTimer/index.tsx
  40. 124
      src/components/AccessTimer/styled.tsx
  41. 13
      src/components/ItemInfo/ItemInfo.tsx
  42. 5
      src/components/PictureInPicture/PiP.tsx
  43. 65
      src/components/SimplePopup/index.tsx
  44. 146
      src/components/SimplePopup/styled.tsx
  45. 33
      src/components/SportIcon/SportIcon.tsx
  46. 6
      src/config/clients/index.tsx
  47. 1
      src/config/clients/lff.tsx
  48. 57
      src/config/clients/tunis.tsx
  49. 58
      src/config/clients/tunisia.tsx
  50. 8
      src/config/clients/types.tsx
  51. 1
      src/config/currencies.tsx
  52. 2
      src/config/index.tsx
  53. 3
      src/config/keyboardKeys.tsx
  54. 13
      src/config/lexics/indexLexics.tsx
  55. 4
      src/config/lexics/joinMatch.tsx
  56. 3
      src/config/lexics/payment.tsx
  57. 1
      src/config/lexics/procedures.tsx
  58. 25
      src/config/matomo.tsx
  59. 3
      src/config/pages.tsx
  60. 22
      src/config/payments.tsx
  61. 1
      src/config/procedures.tsx
  62. 4
      src/config/queries.tsx
  63. 8
      src/config/routes.tsx
  64. 20
      src/config/sportTypes.tsx
  65. 13
      src/features/AddCardForm/components/Form/index.tsx
  66. 11
      src/features/AddCardForm/index.tsx
  67. 1
      src/features/AirPlay/index.tsx
  68. 8
      src/features/App/AuthenticatedApp.tsx
  69. 77
      src/features/App/index.tsx
  70. 4
      src/features/AuthServiceApp/components/ChangePassword/index.tsx
  71. 6
      src/features/AuthServiceApp/components/LanguageSelect/styled.tsx
  72. 5
      src/features/AuthServiceApp/components/Login/hooks.tsx
  73. 26
      src/features/AuthServiceApp/components/Login/index.tsx
  74. 5
      src/features/AuthServiceApp/components/Oauth/hooks.tsx
  75. 14
      src/features/AuthServiceApp/components/RecoveryPopup/index.tsx
  76. 17
      src/features/AuthServiceApp/components/RecoveryPopup/styled.tsx
  77. 4
      src/features/AuthServiceApp/components/RegisterPopup/index.tsx
  78. 18
      src/features/AuthServiceApp/components/RegisterPopup/styled.tsx
  79. 4
      src/features/AuthServiceApp/components/Registration/index.tsx
  80. 4
      src/features/AuthServiceApp/components/Registration/styled.tsx
  81. 1
      src/features/AuthServiceApp/config/clients/facr.tsx
  82. 4
      src/features/AuthServiceApp/config/clients/index.tsx
  83. 15
      src/features/AuthServiceApp/config/clients/tunisia.tsx
  84. 2
      src/features/AuthServiceApp/config/clients/types.tsx
  85. 8
      src/features/AuthServiceApp/hooks/useAuthFields.tsx
  86. 9
      src/features/AuthServiceApp/requests/register.tsx
  87. 10
      src/features/AuthServiceApp/styled.tsx
  88. 5
      src/features/AuthStore/config.tsx
  89. 21
      src/features/AuthStore/helpers.tsx
  90. 211
      src/features/AuthStore/hooks/useAuth.tsx
  91. 101
      src/features/BuyMatchPopup/components/BrazilPayment/hooks.tsx
  92. 6
      src/features/BuyMatchPopup/components/CardStep/index.tsx
  93. 4
      src/features/BuyMatchPopup/components/ErrorStep/index.tsx
  94. 173
      src/features/BuyMatchPopup/components/IframePayment/hooks.tsx
  95. 34
      src/features/BuyMatchPopup/components/IframePayment/index.tsx
  96. 8
      src/features/BuyMatchPopup/components/IframePayment/styled.tsx
  97. 77
      src/features/BuyMatchPopup/components/PackageSelectionStep/index.tsx
  98. 14
      src/features/BuyMatchPopup/components/PackagesList/index.tsx
  99. 2
      src/features/BuyMatchPopup/components/PackagesList/styled.tsx
  100. 21
      src/features/BuyMatchPopup/components/SelectSubscription/index.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -73,7 +73,7 @@ steps:
from_secret: AWS_DEFAULT_REGION
AWS_MAX_ATTEMPTS: 10
commands:
- aws s3 sync build s3://insports-auth --delete
- aws s3 sync build_auth s3://insports-auth --delete
- aws cloudfront create-invalidation --distribution-id EERIKX9X2SRPJ --paths "/*"
depends_on:
- make-auth
@ -88,6 +88,8 @@ steps:
- eval $(ssh-agent -s)
- echo -n "$SSH_KEY_AUTH" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan auth.insports.tv >> ~/.ssh/known_hosts
- rsync -v -r -C build_auth/ ubuntu@auth.insports.tv:/home/ubuntu/ott-auth/src/frontend/
depends_on:
- make-auth
@ -172,32 +174,34 @@ steps:
depends_on:
- make-lff
# - name: make-diwansport
# image: node:16-alpine
# environment:
# REACT_APP_STRIPE_PK:
# from_secret: REACT_APP_STRIPE_PK
# commands:
# - apk add --no-cache make
# - make diwansport-prod
# depends_on:
# - npm-install
#
# - name: deploy-diwansport
# 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_insports-diwansport s3://insports-diwansport --delete
# - aws cloudfront create-invalidation --distribution-id E3LKAH6TR4O2JL --paths "/*"
# depends_on:
# - make-diwansport
- name: make-diwansport
image: node:16-alpine
environment:
REACT_APP_STRIPE_PK:
from_secret: REACT_APP_STRIPE_PK
commands:
- apk add --no-cache make
- make diwansport-prod
depends_on:
- npm-install
- name: deploy-diwansport
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_tunisia s3://insports-diwansport --delete
- aws cloudfront create-invalidation --distribution-id E3LKAH6TR4O2JL --paths "/*" # # diwansport.net
- aws cloudfront create-invalidation --distribution-id E3NJ2G0QSB6MVI --paths "/*" # tunisia.insports.tv
depends_on:
- make-diwansport
---
kind: pipeline
@ -681,3 +685,127 @@ steps:
- rsync -v -r -C build_auth/clients/* ubuntu@auth.test.insports.tv:/home/ubuntu/ott-auth/src/frontend/templates
- aws s3 sync build_auth s3://auth-insports-test --delete
- aws cloudfront create-invalidation --distribution-id E10YI3RFOZZDLZ --paths "/*"
---
kind: pipeline
type: docker
name: deploy auth prod
concurrency:
limit: 1
platform:
os: linux
arch: amd64
trigger:
ref:
- refs/heads/auth
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-auth
image: node:16-alpine
environment:
REACT_APP_STRIPE_PK:
from_secret: REACT_APP_STRIPE_PK
commands:
- apk add --no-cache make
- make auth-production-build
depends_on:
- npm-install
- name: deploy-S3-auth
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_auth s3://insports-auth --delete
- aws cloudfront create-invalidation --distribution-id EERIKX9X2SRPJ --paths "/*"
depends_on:
- make-auth
- name: deploy-old-auth-server
image: node:16-alpine
environment:
SSH_KEY_AUTH:
from_secret: SSH_KEY_AUTH
commands:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo -n "$SSH_KEY_AUTH" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan auth.insports.tv >> ~/.ssh/known_hosts
- rsync -v -r -C build_auth/ ubuntu@auth.insports.tv:/home/ubuntu/ott-auth/src/frontend/
depends_on:
- make-auth
---
kind: pipeline
type: docker
name: deploy diwan.insports.tv
concurrency:
limit: 1
platform:
os: linux
arch: amd64
trigger:
ref:
- refs/heads/diwan.insports.tv
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-diwansport
image: node:16-alpine
environment:
REACT_APP_STRIPE_PK:
from_secret: REACT_APP_STRIPE_PK
commands:
- apk add --no-cache make
- make diwansport-prod
depends_on:
- npm-install
- name: deploy-diwansport
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_tunisia s3://insports-diwansport --delete
- aws cloudfront create-invalidation --distribution-id E3LKAH6TR4O2JL --paths "/*" # # diwansport.net
- aws cloudfront create-invalidation --distribution-id E3NJ2G0QSB6MVI --paths "/*" # tunisia.insports.tv
depends_on:
- make-diwansport

@ -11,6 +11,7 @@
"postro4no"
],
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/array-type": [
"warn",
{ "default" : "generic" }
@ -93,6 +94,7 @@
"react/prop-types": "off",
"react/react-in-jsx-scope": "off",
"react/require-default-props": "off",
"semi": "off"
"semi": "off",
"no-multiple-empty-lines":["error",{ "max":1 }]
}
}

2
.gitignore vendored

@ -7,6 +7,8 @@
# testing
/coverage
/cypress/videos
/cypress/screenshots
# production
/build

@ -130,6 +130,12 @@ india-build: clean
REACT_APP_CLIENT=india \
npm run build
tunisia-build: clean
REACT_APP_TYPE=ott \
REACT_APP_ENV=staging \
REACT_APP_CLIENT=tunisia \
npm run build
lff-build: clean
REACT_APP_TYPE=ott \
REACT_APP_ENV=staging \
@ -180,7 +186,16 @@ lff-prod:
BUILD_PATH=build_lff \
npm run build && cp -r .well-known build_lff
deploy-all: prod preprod facr-prod lff-prod
diwansport-prod:
rm -rf build_tunisia && \
REACT_APP_TYPE=ott \
REACT_APP_ENV=production \
REACT_APP_STRIPE_PK=pk_live_51J5TEYEDSxVnTgDW5XxhC6ntKZKddXgKHq5HOCDmJTdfSKluMYCdLHOcUA3Miuy8HesxG1eS4c0dQRQpMsEHRrQL00USpu5xIq \
REACT_APP_CLIENT=tunisia \
BUILD_PATH=build_tunisia \
npm run build && cp -r .well-known build_tunisia
deploy-all: prod preprod facr-prod lff-prod diwansport-prod india-prod
test:
npm test

88
package-lock.json generated

@ -2710,19 +2710,6 @@
}
}
},
"@jonkoops/matomo-tracker": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@jonkoops/matomo-tracker/-/matomo-tracker-0.7.0.tgz",
"integrity": "sha512-ppCXiDaVytTQOP6hNZIBwjUph5IrGgDoQw4IF5sBoA3PBpMAc5tWtPExbVWTR5pJWpTcp11dv2M83n9pm7LpeQ=="
},
"@jonkoops/matomo-tracker-react": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@jonkoops/matomo-tracker-react/-/matomo-tracker-react-0.7.0.tgz",
"integrity": "sha512-3iwG/QM1T6KokU/NZNCkhOccIkhaNnO1+0bTv2JsLbsS7u7hWxpio20gfBjCRd/9N1AMiGidvytG2FK9tu7WFw==",
"requires": {
"@jonkoops/matomo-tracker": "^0.7.0"
}
},
"@jridgewell/gen-mapping": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
@ -12034,9 +12021,7 @@
"big-integer": {
"version": "1.6.51",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
"dev": true,
"optional": true
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
},
"big.js": {
"version": "5.2.2",
@ -12241,6 +12226,21 @@
"fill-range": "^7.0.1"
}
},
"broadcast-channel": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
"integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
"requires": {
"@babel/runtime": "^7.7.2",
"detect-node": "^2.1.0",
"js-sha3": "0.8.0",
"microseconds": "0.2.0",
"nano-time": "1.0.0",
"oblivious-set": "1.0.0",
"rimraf": "3.0.2",
"unload": "2.2.0"
}
},
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
@ -19967,6 +19967,11 @@
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
"integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q=="
},
"js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
},
"js-string-escape": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
@ -20794,6 +20799,15 @@
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==",
"dev": true
},
"match-sorter": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
"integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
"requires": {
"@babel/runtime": "^7.12.5",
"remove-accents": "0.4.2"
}
},
"mathml-tag-names": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@ -21049,6 +21063,11 @@
"picomatch": "^2.3.1"
}
},
"microseconds": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
"integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
},
"miller-rabin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
@ -21416,6 +21435,14 @@
"dev": true,
"optional": true
},
"nano-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
"integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
"requires": {
"big-integer": "^1.6.16"
}
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
@ -21866,6 +21893,11 @@
"integrity": "sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==",
"dev": true
},
"oblivious-set": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
"integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
},
"obuf": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
@ -24062,6 +24094,16 @@
"warning": "^4.0.2"
}
},
"react-query": {
"version": "3.39.3",
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz",
"integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==",
"requires": {
"@babel/runtime": "^7.5.5",
"broadcast-channel": "^3.4.1",
"match-sorter": "^6.0.2"
}
},
"react-refresh": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@ -24752,6 +24794,11 @@
"mdast-util-to-markdown": "^0.6.0"
}
},
"remove-accents": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
"integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
},
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@ -27695,6 +27742,15 @@
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
},
"unload": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
"integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
"requires": {
"@babel/runtime": "^7.6.2",
"detect-node": "^2.0.4"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

@ -17,11 +17,10 @@
"facr": "REACT_APP_CLIENT=facr react-scripts start",
"lff": "REACT_APP_CLIENT=lff react-scripts start",
"india": "REACT_APP_CLIENT=india react-scripts start",
"tunis": "REACT_APP_CLIENT=tunis react-scripts start",
"tunisia": "REACT_APP_CLIENT=tunisia react-scripts start",
"insports": "REACT_APP_CLIENT=insports react-scripts start"
},
"dependencies": {
"@jonkoops/matomo-tracker-react": "^0.7.0",
"@stripe/react-stripe-js": "^1.4.0",
"@stripe/stripe-js": "^1.13.2",
"babel-polyfill": "^6.26.0",
@ -34,6 +33,7 @@
"react": "^17.0.2",
"react-datepicker": "^3.1.3",
"react-dom": "^17.0.2",
"react-query": "^3.39.3",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^5.0.1",

@ -0,0 +1,9 @@
{
"applinks": {
"apps": [],
"details": [{
"appID": "2X9NY5X2LZ.com.insports.ott",
"paths": ["*"]
}]
}
}

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.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,3 @@
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.75 6.5C20.9489 6.5 21.1397 6.42098 21.2803 6.28033C21.421 6.13968 21.5 5.94891 21.5 5.75V2C21.5 1.60218 21.342 1.22064 21.0607 0.93934C20.7794 0.658035 20.3978 0.5 20 0.5H2C1.60218 0.5 1.22064 0.658035 0.93934 0.93934C0.658035 1.22064 0.5 1.60218 0.5 2V5.75C0.5 5.94891 0.579018 6.13968 0.71967 6.28033C0.860322 6.42098 1.05109 6.5 1.25 6.5C1.64782 6.5 2.02936 6.65804 2.31066 6.93934C2.59196 7.22064 2.75 7.60218 2.75 8C2.75 8.39782 2.59196 8.77936 2.31066 9.06066C2.02936 9.34196 1.64782 9.5 1.25 9.5C1.05109 9.5 0.860322 9.57902 0.71967 9.71967C0.579018 9.86032 0.5 10.0511 0.5 10.25V14C0.5 14.3978 0.658035 14.7794 0.93934 15.0607C1.22064 15.342 1.60218 15.5 2 15.5H20C20.3978 15.5 20.7794 15.342 21.0607 15.0607C21.342 14.7794 21.5 14.3978 21.5 14V10.25C21.5 10.0511 21.421 9.86032 21.2803 9.71967C21.1397 9.57902 20.9489 9.5 20.75 9.5C20.3522 9.5 19.9706 9.34196 19.6893 9.06066C19.408 8.77936 19.25 8.39782 19.25 8C19.25 7.60218 19.408 7.22064 19.6893 6.93934C19.9706 6.65804 20.3522 6.5 20.75 6.5ZM20 10.9025V14H14.75V11.75H13.25V14H2V10.9025C2.642 10.7347 3.21025 10.3588 3.61582 9.8336C4.02139 9.3084 4.24139 8.66356 4.24139 8C4.24139 7.33644 4.02139 6.6916 3.61582 6.1664C3.21025 5.64121 2.642 5.2653 2 5.0975V2H13.25V4.25H14.75V2H20V5.0975C19.358 5.2653 18.7898 5.64121 18.3842 6.1664C17.9786 6.6916 17.7586 7.33644 17.7586 8C17.7586 8.66356 17.9786 9.3084 18.3842 9.8336C18.7898 10.3588 19.358 10.7347 20 10.9025Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 0H2C0.9 0 0 0.9 0 2V20L4 16H18C19.1 16 20 15.1 20 14V2C20 0.9 19.1 0 18 0ZM18 14H4L2 16V2H18V14Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 229 B

@ -0,0 +1,6 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.1862 14.29C15.1862 14.29 14.4125 13.347 13.7596 13.1778C11.7528 12.67 10.8824 12.0414 10.5681 11.6787C10.689 11.582 10.8099 11.4611 10.9308 11.3402C12.2606 9.84117 12.0672 7.32661 12.043 7.0123C11.8979 3.99001 9.6735 3.26466 8.36787 3.31301C7.06224 3.26466 4.83783 4.01418 4.69276 7.0123C4.66859 7.30244 4.47516 9.81698 5.80497 11.3402C5.92586 11.4853 6.07093 11.6304 6.216 11.7271C6.16764 11.7512 6.14346 11.7754 6.14346 11.7996C5.78079 12.1865 4.93455 12.7184 3.07282 13.2019C2.37164 13.3712 1.59794 14.5076 1.59794 14.5076C1.59794 14.5076 0.928283 15.9908 1.10911 16.1968C2.86924 18.199 5.44868 19.4641 8.31951 19.4641C11.3689 19.4641 14.0895 18.0368 15.8487 15.8148C15.6145 14.8567 15.1862 14.29 15.1862 14.29ZM3.41131 14.5801C2.90357 14.701 2.7585 15.3296 2.7585 15.8374C4.2092 17.1914 6.16764 18.0376 8.31951 18.0376C10.5681 18.0376 12.5991 17.1188 14.0981 15.6681C14.0503 14.7595 13.6004 14.6312 13.4268 14.5817L13.4211 14.5801C12.3331 14.3142 11.5111 13.9998 10.9066 13.6855C10.3021 14.4834 9.35918 14.967 8.34369 14.967C7.35238 14.967 6.4336 14.5076 5.82914 13.7339C5.22469 14.024 4.4268 14.3142 3.41131 14.5801ZM8.36787 10.9775C9.02068 10.9775 9.50425 10.7841 9.86692 10.3731C10.6648 9.47849 10.6648 7.73765 10.5923 7.13319V7.08483C10.5439 6.06934 10.1812 5.36817 9.52843 5.0055C9.02505 4.73093 8.50002 4.73784 8.38365 4.73938C8.37705 4.73946 8.37176 4.73953 8.36787 4.73953H8.27115C8.10191 4.73953 7.61834 4.73953 7.15895 5.0055C6.50614 5.36817 6.14346 6.06934 6.09511 7.08483V7.13319C6.02257 7.73765 6.04675 9.47849 6.84463 10.3731C7.20731 10.7599 7.69087 10.9775 8.34369 10.9775H8.36787ZM8.29533 12.4282C8.02937 12.4282 7.76341 12.4041 7.5458 12.3557C7.42491 12.525 7.25567 12.7426 7.03806 12.936C7.35238 13.3228 7.83594 13.5404 8.34369 13.5404C8.89979 13.5404 9.38336 13.2745 9.72185 12.8635C9.52843 12.67 9.38336 12.5008 9.28664 12.3315C9.02068 12.3799 8.75472 12.4282 8.4404 12.4282H8.29533Z" fill="white"/>
<rect x="13.3672" y="4.10608" width="6.14323" height="1.53581" rx="0.767904" fill="white"/>
<rect x="13.3672" y="6.40979" width="6.14323" height="1.53581" rx="0.767904" fill="white"/>
<rect x="13.3672" y="8.7135" width="6.14323" height="1.53581" rx="0.767904" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -0,0 +1,4 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.9915 0.666626C4.3915 0.666626 0.666504 4.39996 0.666504 8.99996C0.666504 13.6 4.3915 17.3333 8.9915 17.3333C13.5998 17.3333 17.3332 13.6 17.3332 8.99996C17.3332 4.39996 13.5998 0.666626 8.9915 0.666626ZM8.99984 15.6666C5.3165 15.6666 2.33317 12.6833 2.33317 8.99996C2.33317 5.31663 5.3165 2.33329 8.99984 2.33329C12.6832 2.33329 15.6665 5.31663 15.6665 8.99996C15.6665 12.6833 12.6832 15.6666 8.99984 15.6666Z" fill="white"/>
<path d="M9.4165 5.45829C9.4165 5.11311 9.13668 4.83329 8.7915 4.83329C8.44633 4.83329 8.1665 5.11311 8.1665 5.45829V9.2671C8.1665 9.61837 8.3508 9.94387 8.65201 10.1246L12.0317 12.1524C12.3139 12.3217 12.6797 12.2316 12.851 11.9507C13.0246 11.666 12.9321 11.2942 12.6453 11.1241L9.90623 9.49886C9.60263 9.31873 9.4165 8.99187 9.4165 8.63885V5.45829Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 908 B

@ -0,0 +1,12 @@
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.1 0H0.9C0.4 0 0 0.4 0 0.9V17.1C0 17.5 0.4 18 0.9 18H21.1C21.5 18 22 17.5 22 17.1V0.9C22 0.4 21.5 0 21.1 0ZM20 16H2V2H20V16Z" fill="white"/>
<path d="M13.5 4H9C8.44772 4 8 4.44772 8 5C8 5.55228 8.44772 6 9 6H13.5C14.0523 6 14.5 5.55228 14.5 5C14.5 4.44772 14.0523 4 13.5 4Z" fill="white"/>
<path d="M13.5 8H9C8.44772 8 8 8.44772 8 9C8 9.55228 8.44772 10 9 10H13.5C14.0523 10 14.5 9.55228 14.5 9C14.5 8.44772 14.0523 8 13.5 8Z" fill="white"/>
<path d="M13.5 12H9C8.44772 12 8 12.4477 8 13C8 13.5523 8.44772 14 9 14H13.5C14.0523 14 14.5 13.5523 14.5 13C14.5 12.4477 14.0523 12 13.5 12Z" fill="white"/>
<path d="M4 13C4 13.5523 4.44772 14 5 14C5.55228 14 6 13.5523 6 13C6 12.4477 5.55228 12 5 12C4.44772 12 4 12.4477 4 13Z" fill="white"/>
<path d="M4 9C4 9.55228 4.44772 10 5 10C5.55228 10 6 9.55228 6 9C6 8.44772 5.55228 8 5 8C4.44772 8 4 8.44772 4 9Z" fill="white"/>
<path d="M4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4C4.44772 4 4 4.44772 4 5Z" fill="white"/>
<path d="M16.5 13C16.5 13.5523 16.9477 14 17.5 14C18.0523 14 18.5 13.5523 18.5 13C18.5 12.4477 18.0523 12 17.5 12C16.9477 12 16.5 12.4477 16.5 13Z" fill="white"/>
<path d="M16.5 9C16.5 9.55228 16.9477 10 17.5 10C18.0523 10 18.5 9.55228 18.5 9C18.5 8.44772 18.0523 8 17.5 8C16.9477 8 16.5 8.44772 16.5 9Z" fill="white"/>
<path d="M16.5 5C16.5 5.55228 16.9477 6 17.5 6C18.0523 6 18.5 5.55228 18.5 5C18.5 4.44772 18.0523 4 17.5 4C16.9477 4 16.5 4.44772 16.5 5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 12.5C8 13.324 8.94076 13.7944 9.6 13.3L12.9333 10.8C13.4667 10.4 13.4667 9.6 12.9333 9.2L9.6 6.7C8.94076 6.20557 8 6.67595 8 7.5V12.5ZM10 0C4.48 0 0 4.48 0 10C0 15.52 4.48 20 10 20C15.52 20 20 15.52 20 10C20 4.48 15.52 0 10 0ZM10 18C5.59 18 2 14.41 2 10C2 5.59 5.59 2 10 2C14.41 2 18 5.59 18 10C18 14.41 14.41 18 10 18Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 451 B

@ -0,0 +1,22 @@
<svg width="38" height="23" viewBox="0 0 38 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31 18H11C7.1 18 4 14.9 4 11C4 7.1 7.1 4 11 4H31C34.9 4 38 7.1 38 11C38 14.9 34.9 18 31 18Z" fill="white" fill-opacity="0.4"/>
<g filter="url(#filter0_dd_1_1015)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 21C16.5228 21 21 16.5228 21 11C21 5.47715 16.5228 1 11 1C5.47715 1 1 5.47715 1 11C1 16.5228 5.47715 21 11 21Z" fill="#E1E1E1"/>
</g>
<defs>
<filter id="filter0_dd_1_1015" x="0" y="0" width="22" height="23" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.237602 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_1015"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1_1015" result="effect2_dropShadow_1_1015"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_1_1015" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,29 @@
<svg width="38" height="23" viewBox="0 0 38 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7 4L27 4C30.9 4 34 7.1 34 11C34 14.9 30.9 18 27 18L7 18C3.1 18 2.71011e-07 14.9 6.11959e-07 11C9.52908e-07 7.1 3.1 4 7 4Z"
fill="#0E8F84" />
<g filter="url(#filter0_dd_1_1076)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M27 0.999999C21.4772 0.999999 17 5.47715 17 11C17 16.5228 21.4772 21 27 21C32.5228 21 37 16.5228 37 11C37 5.47715 32.5228 1 27 0.999999Z"
fill="#E1E1E1" />
</g>
<defs>
<filter id="filter0_dd_1_1076" x="16" y="0" width="22" height="23" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix in="SourceAlpha" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feOffset dy="1" />
<feGaussianBlur stdDeviation="0.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.237602 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_1076" />
<feColorMatrix in="SourceAlpha" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feOffset />
<feGaussianBlur stdDeviation="0.5" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0" />
<feBlend mode="normal" in2="effect1_dropShadow_1_1076" result="effect2_dropShadow_1_1076" />
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_1_1076" result="shape" />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,3 @@
<svg width="7" height="6" viewBox="0 0 7 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.06699 0.75C3.25944 0.416667 3.74056 0.416667 3.93301 0.75L6.09808 4.5C6.29053 4.83333 6.04996 5.25 5.66506 5.25L1.33494 5.25C0.950036 5.25 0.709474 4.83333 0.901924 4.5L3.06699 0.75Z" fill="white" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 329 B

@ -0,0 +1,29 @@
<svg width="102" height="75" viewBox="0 0 102 75" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8_12573)">
<path d="M87.7871 0H14.2129C6.36333 0 0 6.37899 0 14.2479V47.1045C0 54.9734 6.36333 61.3524 14.2129 61.3524H87.7871C95.6367 61.3524 102 54.9734 102 47.1045V14.2479C102 6.37899 95.6367 0 87.7871 0Z" fill="#0B2E4D"/>
<path d="M88.6677 33.2715H13.3323C5.96907 33.2715 0 39.2553 0 46.6366V61.626C0 69.0074 5.96907 74.9911 13.3323 74.9911H88.6677C96.0309 74.9911 102 69.0074 102 61.626V46.6366C102 39.2553 96.0309 33.2715 88.6677 33.2715Z" fill="url(#paint0_linear_8_12573)"/>
<path d="M26.3565 21.1689C26.3565 26.5538 23.838 29.2551 18.801 29.2551C14.169 29.2551 11.853 26.8805 11.853 22.1223C11.853 20.8511 12.038 19.5182 12.4078 18.1146L17.1895 17.2583C16.7139 18.8031 16.4762 20.2773 16.4762 21.681C16.4762 24.3204 17.348 25.6358 19.1004 25.6358C20.0338 25.6358 20.6943 25.3444 21.0905 24.753C21.5044 24.1262 21.707 23.0404 21.707 21.4867V12.1559H26.3477V21.1689H26.3565ZM17.031 15.5545C16.5995 15.122 16.3881 14.6011 16.3881 13.992C16.3881 13.3829 16.5995 12.8621 17.031 12.4295C17.4536 12.0058 17.9644 11.7939 18.5808 11.7939C19.1972 11.7939 19.708 12.0058 20.1395 12.4295C20.571 12.8621 20.7823 13.3829 20.7823 13.992C20.7823 14.6011 20.571 15.122 20.1395 15.5545C19.708 15.9783 19.1884 16.1901 18.5808 16.1901C17.9732 16.1901 17.4536 15.9783 17.031 15.5545Z" fill="white"/>
<path d="M29.3945 6.42676H34.0177V23.6407H29.3945V6.42676Z" fill="white"/>
<path d="M36.7301 29.5023L36.9679 25.8035C38.0862 26.0154 39.3103 26.1213 40.64 26.1213C43.0088 26.1213 44.1976 25.3003 44.1976 23.6496V23.6231H43.0792C38.6322 23.6231 36.4043 21.7604 36.4043 18.0352C36.4043 16.2608 36.9327 14.8131 37.9982 13.6919C39.2134 12.4207 40.9746 11.7852 43.2818 11.7852C45.404 11.7852 47.2093 12.0676 48.6799 12.6326V20.0126H50.626V23.6231H48.6535C48.5302 25.5563 47.7817 27.0394 46.4167 28.0811C44.9902 29.1757 42.9472 29.723 40.2789 29.723C39.1077 29.723 37.9189 29.6436 36.7213 29.4847L36.7301 29.5023ZM44.1976 15.6958C43.8366 15.5016 43.3875 15.4133 42.8415 15.4133C42.2515 15.4133 41.7936 15.634 41.4501 16.0842C41.1419 16.4991 40.9834 17.0111 40.9834 17.6379C40.9834 19.2357 41.82 20.0302 43.4931 20.0302H44.1976V15.7046V15.6958Z" fill="white"/>
<path d="M50.2646 20.0303H55.3545V23.6408H50.2646V20.0303Z" fill="white"/>
<path d="M54.8613 20.0303H59.9512V23.6408H54.8613V20.0303Z" fill="white"/>
<path d="M59.4673 20.0303H64.5572V23.6408H59.4673V20.0303Z" fill="white"/>
<path d="M64.0728 20.0303H69.1626V23.6408H64.0728V20.0303Z" fill="white"/>
<path d="M68.6694 20.0305H72.8083V12.1562H77.4314V23.6322H68.6694V20.0217V20.0305ZM72.2359 28.8406C71.8308 29.2643 71.3288 29.4762 70.7388 29.4762C70.1488 29.4762 69.6645 29.2643 69.2682 28.8406C68.872 28.4168 68.6694 27.9048 68.6694 27.3046C68.6694 26.7043 68.8632 26.2099 69.2682 25.795C69.6733 25.3713 70.1665 25.1594 70.7388 25.1594C71.3112 25.1594 71.8308 25.3713 72.2359 25.795C72.6321 26.2187 72.8347 26.7219 72.8347 27.3046C72.8347 27.8872 72.6321 28.4168 72.2359 28.8406ZM76.9119 28.8406C76.5156 29.2643 76.0224 29.4762 75.4412 29.4762C74.8601 29.4762 74.3581 29.2643 73.953 28.8406C73.548 28.4168 73.3454 27.9048 73.3454 27.3046C73.3454 26.7043 73.548 26.2099 73.953 25.795C74.3581 25.3713 74.8512 25.1594 75.4412 25.1594C76.0313 25.1594 76.5156 25.3713 76.9119 25.795C77.3081 26.2187 77.5107 26.7219 77.5107 27.3046C77.5107 27.8872 77.3081 28.4168 76.9119 28.8406Z" fill="white"/>
<path d="M79.6855 20.0299H86.0435C85.9202 18.8028 85.4183 17.7788 84.5465 16.9402C83.6306 16.0574 82.3978 15.4925 80.8744 15.2364L81.9487 11.4141C87.699 12.5705 90.5698 15.9162 90.5698 21.4511V23.6404H79.6855V20.0299Z" fill="white"/>
<path d="M20.7294 63.4183C19.0651 64.8484 16.9604 65.5634 14.4067 65.5634C12.3373 65.5634 10.5761 65.378 9.10546 65.0161L9.61621 59.384C11.2189 60.0638 12.8568 60.408 14.53 60.408C15.9037 60.408 16.5906 59.9049 16.5906 58.8897C16.5906 58.2188 16.1767 57.6715 15.3401 57.2566C15.0583 57.1153 14.6356 56.9299 14.072 56.7092C13.35 56.4532 12.8568 56.2679 12.6014 56.1531C9.93322 55.0232 8.60352 53.0634 8.60352 50.265C8.60352 48.1111 9.38725 46.4073 10.9459 45.1538C12.5398 43.8915 14.7237 43.2559 17.5152 43.2559C18.9154 43.2559 20.2274 43.3971 21.4515 43.6708L20.9583 48.782C19.7519 48.3583 18.5279 48.1464 17.3039 48.1464C15.7452 48.1464 14.9615 48.6319 14.9615 49.5941C14.9615 50.1679 15.3929 50.6623 16.2471 51.1037C16.5289 51.2361 16.9604 51.4303 17.5592 51.6775L19.0739 52.2954C21.8213 53.5489 23.1951 55.4822 23.1951 58.104C23.1951 60.2403 22.3761 62.0059 20.7294 63.4183Z" fill="white"/>
<path d="M38.4733 56.3913C36.853 58.0068 34.581 58.8101 31.6574 58.8101C31.358 58.8101 31.0938 58.8013 30.8649 58.7748V65.1395H24.9648V43.6794C27.0783 43.5647 29.3767 43.5117 31.8423 43.5117C37.7512 43.5117 40.7012 45.8864 40.7012 50.6357C40.7012 52.9838 39.9615 54.8994 38.4733 56.3825V56.3913ZM31.6926 48.3669C31.4637 48.3669 31.1907 48.3758 30.8737 48.4023V53.8401C31.1731 53.9019 31.5253 53.9372 31.9304 53.9372C33.7268 53.9372 34.625 53.0015 34.625 51.1388C34.625 49.2762 33.6476 48.3758 31.7014 48.3758L31.6926 48.3669Z" fill="white"/>
<path d="M59.3964 62.4296C57.3446 64.5394 54.7732 65.5988 51.6999 65.5988C48.3977 65.5988 45.888 64.6454 44.1708 62.7298C42.5065 60.876 41.6787 58.2453 41.6787 54.8466C41.6787 51.448 42.7442 48.5878 44.8665 46.3986C46.9183 44.2976 49.4808 43.2471 52.5453 43.2471C55.8476 43.2471 58.3661 44.2005 60.0921 46.1161C61.7564 47.9611 62.5842 50.5829 62.5842 53.9904C62.5842 57.3978 61.5186 60.2668 59.3964 62.4385V62.4296ZM52.1755 48.6408C50.8722 48.6408 49.8507 49.1263 49.0934 50.0974C48.3361 51.0949 47.9662 52.4808 47.9662 54.2728C47.9662 58.1835 49.3399 60.1433 52.0874 60.1433C53.3907 60.1433 54.4122 59.6666 55.1519 58.7132C55.918 57.7333 56.2967 56.3474 56.2967 54.5553C56.2967 50.6094 54.9229 48.6408 52.1755 48.6408Z" fill="white"/>
<path d="M74.6221 65.1395L70.6594 57.3182H70.3776V65.1395H64.4775V43.6794C66.1419 43.5647 68.5811 43.5117 71.8041 43.5117C77.4752 43.5117 80.3107 45.7451 80.3107 50.2208C80.3107 51.5273 79.9673 52.719 79.2892 53.796C78.5848 54.8994 77.6513 55.6763 76.4713 56.1088C77.1054 57.0622 77.6161 57.892 78.0212 58.5894L81.799 65.1395H74.6309H74.6221ZM71.4959 48.3846C71.0116 48.3846 70.6417 48.4023 70.3776 48.4464V53.0544C70.677 53.0986 71.0028 53.1162 71.355 53.1162C73.1867 53.1162 74.0937 52.2953 74.0937 50.6621C74.0937 49.1438 73.2307 48.3758 71.4959 48.3758V48.3846Z" fill="white"/>
<path d="M91.6619 48.7644V65.1397H85.7618V48.7644H81.1299V43.6885H96.2938V48.7644H91.6619Z" fill="white"/>
</g>
<defs>
<linearGradient id="paint0_linear_8_12573" x1="0" y1="54.1401" x2="102" y2="54.1401" gradientUnits="userSpaceOnUse">
<stop stop-color="#005D6B"/>
<stop offset="1" stop-color="#1DB9AB"/>
</linearGradient>
<clipPath id="clip0_8_12573">
<rect width="102" height="75" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -42,14 +42,6 @@
name="msapplication-config"
content="%PUBLIC_URL%/clients/%REACT_APP_CLIENT%/favicon/browserconfig.xml" />
<% } %>
<!-- Matomo Tag Manager -->
<script>
var _mtm = window._mtm = window._mtm || [];
_mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'});
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src='https://matomo.insports.tv/js/container_KFCeSAkC.js'; s.parentNode.insertBefore(g,s);
</script>
<!-- End Matomo Tag Manager -->
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@ -67,5 +59,14 @@
<!-- Start of ChromeCast script -->
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
<!-- End of ChromeCast script -->
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LZXGB5GJG0"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LZXGB5GJG0');
</script>
</body>
</html>

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/oidc-client/1.11.5/oidc-client.min.js" integrity="sha512-pGtU1n/6GJ8fu6bjYVGIOT9Dphaw5IWPwVlqkpvVgqBxFkvdNbytUh0H8AP15NYF777P4D3XEeA/uDWFCpSQ1g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
new Oidc.UserManager().signinSilentCallback()
.catch((err) => {
console.error('OIDC: silent refresh callback error', err);
});
</script>
</body>
</html>

@ -0,0 +1,128 @@
import {
useEffect,
useState,
RefObject,
} from 'react'
import { T9n } from 'features/T9n'
import { Icon } from 'features/Icon'
import { useAuthStore } from 'features/AuthStore'
import {
AccessTimerContainer,
PreviewInfo,
SignInBtn,
SignText,
Timer,
TimerContainer,
} from './styled'
import { SimplePopup } from '../SimplePopup'
import { secondsToHms } from '../../helpers'
import { getViewMatchDuration } from '../../requests/getViewMatchDuration'
import { usePageParams } from '../../hooks'
import { getUserInfo } from '../../requests'
type AccessTimerType = {
access: boolean,
isFullscreen: boolean,
onFullscreenClick: () => void,
playing: boolean,
videoRef?: RefObject<HTMLVideoElement> | null,
}
const ACCESS_TIME = 60
export const AccessTimer = ({
access,
isFullscreen,
onFullscreenClick,
playing,
videoRef,
}: AccessTimerType) => {
const {
logout,
setUserInfo,
userInfo,
} = useAuthStore()
const { profileId, sportType } = usePageParams()
const [timeIsFinished, setTimeIsFinished] = useState(true)
const [time, setTime] = useState(ACCESS_TIME)
const isTimeExpired = time <= 0 || !access
useEffect(() => {
if (isTimeExpired) {
document.pictureInPictureEnabled && document.exitPictureInPicture()
setTimeIsFinished(true)
videoRef?.current?.pause()
setTime(0)
if (isFullscreen) onFullscreenClick()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [access, playing, profileId])
useEffect(() => {
let stopWatch: ReturnType<typeof setInterval> | null = null
if (playing) {
stopWatch = setInterval(() => {
setTime((prev) => prev - 1)
}, 1000)
} else {
stopWatch && clearInterval(stopWatch)
}
return () => {
stopWatch && clearInterval(stopWatch)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playing, profileId])
useEffect(() => {
(async () => {
setTime(ACCESS_TIME)
const updatedUserInfo = await getUserInfo()
setUserInfo(updatedUserInfo)
const timeViewed = await getViewMatchDuration({
matchId: profileId,
sportType,
userId: Number(updatedUserInfo?.email),
})
setTime(isTimeExpired ? 0 : (ACCESS_TIME - Number(timeViewed.duration)))
})()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [profileId])
return (
<>
{(!userInfo || Number(userInfo?.email) < 0) && access && time > 0
? (
<AccessTimerContainer isFullscreen={isFullscreen}>
<TimerContainer>
<PreviewInfo>
<Timer>{secondsToHms(time)}&nbsp;</Timer>
<T9n t='game_preview' className='gamePreview' />
</PreviewInfo>
<SignText>
<T9n t='sign_in_full_game' />
</SignText>
</TimerContainer>
<SignInBtn onClick={() => logout('saveToken')}>
<T9n t='sign_in' />
</SignInBtn>
</AccessTimerContainer>
)
: (
<SimplePopup
isModalOpen={timeIsFinished}
withCloseButton={false}
headerName='sec_60'
mainText='continue_watching'
buttonName='sign_in'
onHandle={() => logout('saveToken')}
icon={<Icon refIcon='ExclamationPoint' />}
/>
)}
</>
)
}

@ -0,0 +1,124 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config'
import { ButtonSolid } from 'features/Common'
export const AccessTimerContainer = styled.div<{isFullscreen?: boolean}>`
display: flex;
align-items: center;
position: absolute;
left: 50%;
bottom: 5.7rem;
transform: translateX(-50%);
background-color: #333333;
border-radius: 5px;
padding: 20px 50px;
gap: 40px;
${isMobileDevice
? css`
padding: 9px 7px 9px 15px;
bottom: 6.5rem;
@media screen and (orientation: landscape) {
max-width: 95%;
bottom: 4rem;
}
`
: ''};
${({ isFullscreen }) => isFullscreen && css`
${isMobileDevice
? css`
padding: 9px 7px 9px 15px;
bottom: 12rem;
@media screen and (orientation: landscape) {
max-width: 95%;
bottom: 6rem;
}
`
: ''};
`}
`
export const SignInBtn = styled(ButtonSolid)`
width: 246px;
border-radius: 5px;
height: 50px;
font-weight: 600;
font-size: 20px;
white-space: nowrap;
padding: 0 35px;
${isMobileDevice
? css`
font-size: 12px;
white-space: nowrap;
padding: 0px 16px;
width: 140px;
height: 30px;
@media screen and (orientation: landscape) {
font-size: 12px;
padding: 0px 8px;
}
`
: ''};
`
export const TimerContainer = styled.div`
color: white;
font-weight: 700;
font-size: 20px;
line-height: 24px;
white-space: nowrap;
max-width: 215px;
${isMobileDevice
? css`
font-size: 12px;
line-height: 28px;
`
: ''};
`
export const PreviewInfo = styled.div`
display: flex;
line-height: 24px;
${isMobileDevice
? css`
line-height: 14px;
`
: ''};
`
export const Timer = styled.div`
min-width: 67px;
${isMobileDevice
? css`
min-width: 40px;
`
: ''};
`
export const SignText = styled.span`
display: block;
font-size: 16px;
line-height: 20px;
font-weight: 400;
white-space: break-spaces;
${isMobileDevice
? css`
font-size: 9px;
line-height: 14px
`
: ''};
`

@ -1,4 +1,10 @@
import { ProfileTypes, PROFILE_NAMES } from 'config'
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
ProfileTypes,
PROFILE_NAMES,
SportTypes,
URL_AWS,
} from 'config'
import {
ScWrapper,
@ -11,18 +17,19 @@ type ItemInfoType = {
id: number,
name: string,
onClick: (val: any) => void,
sportId: SportTypes,
type: ProfileTypes,
}
export const ItemInfo = ({
active,
id,
name,
onClick,
sportId,
type,
}: ItemInfoType) => (
<ScWrapper onClick={onClick}>
<ScLogo src={`https://instatscout.com/images/${PROFILE_NAMES[type]}/180/${id}.png`} />
<ScLogo src={`${URL_AWS}/media/${PROFILE_NAMES[type]}/${sportId}/${id}/logo.jpg`} />
<ScName active={active}>{name}</ScName>
</ScWrapper>
)

@ -7,6 +7,7 @@ import {
import styled from 'styled-components/macro'
import { Icon } from 'features/Icon'
import { useAuthStore } from 'features/AuthStore'
const PipWrapper = styled.div`
cursor: pointer;
@ -21,6 +22,7 @@ type PipProps = {
}
export const PiP = memo(({ isPlaying, videoRef }: PipProps) => {
const { user } = useAuthStore()
const togglePip = async () => {
try {
if (
@ -43,11 +45,12 @@ export const PiP = memo(({ isPlaying, videoRef }: PipProps) => {
&& videoRef.current !== document.pictureInPictureElement
&& videoRef.current?.hidden === false
&& isPlaying
&& user
) {
await videoRef.current?.requestPictureInPicture()
}
})
}, [videoRef, isPlaying])
}, [videoRef, isPlaying, user])
return (
<PipWrapper>

@ -0,0 +1,65 @@
import type { ReactNode } from 'react'
import { T9n } from 'features/T9n'
import {
Header,
Footer,
ScBody,
Modal,
ScApplyButton,
ScHeaderTitle,
ScText,
Wrapper,
} from './styled'
type Props = {
buttonName?: string,
headerName?: string,
icon?: ReactNode,
isModalOpen: boolean,
mainText: string,
onHandle?: () => void,
withCloseButton: boolean,
}
export const SimplePopup = (props: Props) => {
const {
buttonName,
headerName,
icon,
isModalOpen,
mainText,
onHandle,
withCloseButton,
} = props
return (
<Modal
isOpen={isModalOpen}
withCloseButton={withCloseButton}
>
<Wrapper>
<Header>
{icon}
<ScHeaderTitle>
<T9n t={headerName ?? ''} />
</ScHeaderTitle>
</Header>
<ScBody>
<ScText>
<T9n t={mainText} />
</ScText>
</ScBody>
{buttonName
&& (
<Footer>
<ScApplyButton onClick={onHandle}>
<T9n t={buttonName} />
</ScApplyButton>
</Footer>
)}
</Wrapper>
</Modal>
)
}

@ -0,0 +1,146 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config'
import { ModalWindow } from 'features/Modal/styled'
import {
ApplyButton,
Body,
Modal as BaseModal,
HeaderTitle,
} from 'features/AuthServiceApp/components/RegisterPopup/styled'
import { Header as BaseHeader } from 'features/PopupComponents'
import { client } from 'features/AuthServiceApp/config/clients/index'
export const Modal = styled(BaseModal)`
${ModalWindow} {
width: 705px;
height: 472px;
padding: 60px 100px;
min-height: auto;
${isMobileDevice
? css`
max-width: 95vw;
padding: 50px 20px 80px 20px;
@media screen and (orientation: landscape) {
max-height: 80vh;
max-width: 95vw;
padding: 24px 100px 60px 100px;
}
`
: ''};
}
`
export const Wrapper = styled.div`
gap: 30px;
${isMobileDevice
? css`
max-height: 80vh;
gap: 10px;
`
: ''};
`
export const ScHeaderTitle = styled(HeaderTitle)`
text-align: center;
font-weight: 700;
font-size: 34px;
line-height: 34px;
${isMobileDevice
? css`
font-size: 20px;
line-height: 24px;
@media screen and (orientation: landscape) {
font-size: 17px;
}
`
: ''};
`
export const ScBody = styled(Body)`
padding: 16px 0 0 0;
${isMobileDevice
? css`
@media screen and (orientation: landscape) {
max-width: 491px;
}
`
: ''};
`
export const ScText = styled.span`
text-align: center;
${isMobileDevice
? css`
font-size: 14px;
line-height: 22px;
`
: ''};
`
export const ScApplyButton = styled(ApplyButton)`
width: 300px;
height: 60px;
margin: 0;
${isMobileDevice
? css`
font-size: 14px;
margin: 20px 0;
width: 293px;
height: 50px;
@media screen and (orientation: landscape) {
margin: 0;
width: 225px;
height: 44px;
}
`
: ''};
${client.styles.popupApplyButton}
`
export const Header = styled(BaseHeader)`
display: flex;
flex-direction: column;
align-items: center;
height: auto;
justify-content: center;
gap: 30px;
${isMobileDevice
? css`
@media screen and (orientation: landscape) {
gap: 10px;
svg {
width: 40px;
height: 40px;
}
}
`
: ''};
`
export const Footer = styled.div`
width: 100%;
display: flex;
justify-content: center;
padding: 33px 0 65px 0;
${isMobileDevice
? css`
padding-top: 30px;
`
: ''};
`

@ -1,3 +1,4 @@
/* eslint sort-keys: 0 */
import styled from 'styled-components/macro'
import { SportTypes } from 'config'
@ -44,6 +45,38 @@ const sportIcons = {
color: '#2D8B8A',
icon: 'Volleyball',
},
baseball: {
color: '#ffffff',
icon: 'Baseball',
},
tennis: {
color: '#ffffff',
icon: 'Tennis',
},
field_hockey: {
color: '#ffffff',
icon: 'FIELD_HOCKEY',
},
figure_skating: {
color: '#ffffff',
icon: 'FIGURE_SKATING',
},
american_football: {
color: '#ffffff',
icon: 'AMERICAN_FOOTBALL',
},
futsal: {
color: '#ffffff',
icon: 'FUTSAL',
},
floorball: {
color: '#ffffff',
icon: 'FLOORBALL',
},
cricket: {
color: '#ffffff',
icon: 'CRICKET',
},
}
export const SportIcon = ({

@ -5,7 +5,7 @@ import { instat } from './instat'
import { lff } from './lff'
import { insports } from './insports'
import { india } from './india'
import { tunis } from './tunis'
import { tunisia } from './tunisia'
export const currentClient = process.env.REACT_APP_CLIENT || 'insports'
@ -14,7 +14,7 @@ export const isInSportsClient = currentClient === 'insports'
export const isInstatClient = currentClient === 'instat'
export const isFacrClient = currentClient === 'facr'
export const isIndiaClient = currentClient === 'india'
export const isTunisClient = currentClient === 'tunis'
export const isTunisClient = currentClient === 'tunisia'
const clients = {
facr,
@ -22,7 +22,7 @@ const clients = {
insports,
instat,
lff,
tunis,
tunisia,
}
export const client: ClientConfig = clients[currentClient]

@ -11,6 +11,7 @@ export const lff: ClientConfig = {
},
defaultLanguage: 'lv',
description: 'Live sports streaming platform. Football, basketball, ice hockey and more. Access to various player playlists and game highlights. Multiple subscription options. Available across all devices.',
disabledHighlights: true,
disabledPreferences: true,
name: ClientNames.Lff,
privacyLink: '/clients/instat/terms-and-conditions.html',

@ -1,57 +0,0 @@
import { css } from 'styled-components/macro'
import {
ClientConfig,
ClientIds,
ClientNames,
} from './types'
const randomHash = () => (
(Math.random() ** Math.random()) * 9999999999999999
)
export const tunis: ClientConfig = {
auth: {
clientId: ClientIds.Tunis,
metaDataUrlParams: `?hash=${randomHash()}`,
},
defaultLanguage: 'fr',
description: 'Live sports streaming platform. All matches playing under the auspices of Czech Republic FA. Access to full matches, various player playlists, and highlights. Free access in the Czech Republic. Available across all devices',
disabledPreferences: false,
name: ClientNames.Tunis,
privacyLink: '/privacy-policy-and-statement',
showSearch: false,
styles: {
background: '',
homePageHeader: css`
background: radial-gradient(
160.34% 257.27% at -7.45% 162.22%,
#2AB7AA 3.27%,
#02505C 43.69%, #0B2E4D 100%);
`,
logo: 'tunis-logo.svg',
logoHeight: 6.3,
logoLeft: 1.1,
logoTop: 1.74,
logoWidth: 8.25,
matchLogoHeight: 3.4,
matchLogoTopMargin: 0.9,
matchLogoWidth: 4.5,
matchPageMobileHeaderLogo: css`
width: 35px;
height: 25px;
top: 2px;
`,
mobileHeaderLogo: css`
width: 48px;
height: 37px;
`,
userAccountLogo: css`
width: 4.56rem;
height: 3.488rem;
`,
},
termsLink: '/terms-and-conditions?client_id=facr-ott-web',
title: 'FACR.TV - The home of Czech football streaming',
userAccountLinksDisabled: true,
}

@ -0,0 +1,58 @@
import { css } from 'styled-components/macro'
import {
ClientConfig,
ClientIds,
ClientNames,
} from './types'
const randomHash = () => (
(Math.random() ** Math.random()) * 9999999999999999
)
export const tunisia: ClientConfig = {
auth: {
clientId: ClientIds.Tunisia,
metaDataUrlParams: `?hash=${randomHash()}`,
},
defaultLanguage: 'fr',
description: '',
disabledFilters: true,
disabledHighlights: true,
disabledPreferences: true,
name: ClientNames.Tunisia,
privacyLink: '/privacy-policy-and-statement?client_id=insports-ott-web',
showSearch: true,
styles: {
background: '',
homePageHeader: css`
background: radial-gradient(
160.34% 257.27% at -7.45% 162.22%,
#2AB7AA 3.27%,
#02505C 43.69%, #0B2E4D 100%);
`,
logo: 'tunis-logo.svg',
logoHeight: 1.922,
logoLeft: 1.1,
logoTop: 1.14,
logoWidth: 9,
matchLogoHeight: 1.922,
matchLogoTopMargin: 1,
matchLogoWidth: 9,
matchPageMobileHeaderLogo: css`
width: 100px;
height: 21px;
top: 5px;
`,
mobileHeaderLogo: css`
width: 100px;
height: 21px;
`,
userAccountLogo: css`
width: 9rem;
height: 1.922rem;
`,
},
termsLink: '/terms-and-conditions?client_id=insports-ott-web',
title: 'Diwan Sport - The home of Tunisian Ligue Professionnelle 1',
}

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { css } from 'styled-components/macro'
type ProcedureName = string
@ -10,16 +11,17 @@ export enum ClientIds {
Insports = 'insports-ott-web',
Instat = 'ott-web',
Lff = 'lff-ott-web',
Tunis = 'tunis-ott-web',
Tunisia = 'tunisia-ott-web',
}
export enum ClientNames {
Brasil = 'brasil',
Facr = 'facr',
India = 'india',
Insports = 'insports',
Instat = 'instat',
Lff = 'lff',
Tunis = 'tunis',
Tunisia = 'tunisia'
}
export type ClientConfig = {
@ -30,6 +32,8 @@ export type ClientConfig = {
},
defaultLanguage: string,
description: string,
disabledFilters?: boolean,
disabledHighlights?: boolean,
disabledPreferences?: boolean,
name: ClientNames,
privacyLink: string,

@ -12,5 +12,6 @@ export const currencySymbols = {
KZT: '₸',
MDL: 'L',
RUB: '₽',
TND: 'TND',
USD: '$',
} as const

@ -10,3 +10,5 @@ export * from './currencies'
export * from './dashes'
export * from './env'
export * from './userAgent'
export * from './queries'
export * from './keyboardKeys'

@ -0,0 +1,3 @@
export enum KEYBOARD_KEYS {
Enter = 'Enter',
}

@ -9,11 +9,17 @@ const matchPopupLexics = {
apply: 13491,
choose_fav_team: 19776,
commentators: 15424,
continue_watching: 20007,
current_stats: 19592,
display_all_stats: 19932,
display_stats_according_to_video: 19931,
episode_duration: 13410,
events: 1020,
final_stats: 19591,
from_end_match: 15396,
from_price: 3992,
from_start_match: 15395,
game_preview: 20005,
gk: 3515,
go_back_to_match: 13405,
group: 7850,
@ -22,15 +28,21 @@ const matchPopupLexics = {
match_interviews: 13031,
match_settings: 13490,
no_data: 15397,
other_games: 19997,
others: 19902,
players_episodes: 13398,
playlist_format: 13406,
playlist_format_all_actions: 13408,
playlist_format_all_match_time: 13407,
playlist_format_selected_acions: 13409,
sec_60: 20006,
sec_after: 13412,
sec_before: 13411,
selected_player_actions: 13413,
sign_in: 20003,
sign_in_full_game: 20004,
started_streaming_at: 16042,
stats: 18179,
streamed_live_on: 16043,
video: 1017,
views: 13440,
@ -158,6 +170,7 @@ export const indexLexics = {
no_match_access_body: 13419,
no_match_access_title: 13418,
player: 14975,
players: 164,
players_video: 13032,
privacy_policy_and_statement: 15404,
round_highilights: 13050,

@ -1,4 +1,8 @@
export const joinMatchLexics = {
diwan_desc: 20035,
diwan_join: 20037,
diwan_season: 19983,
diwan_title: 20036,
join_insports_tv: 15420,
join_now: 15422,
promo_text: 15421,

@ -19,13 +19,16 @@ export const paymentLexics = {
error_empty_name: 15290,
error_empty_state: 19821,
error_payment_unsuccessful: 14446,
failed_paymee: 20046,
if_you_cancel: 18189,
is_gpay: 20045,
next_payment: 18183,
notify_by_email: 18366,
order_received: 18365,
payment_date: 15603,
payment_method: 2010,
pb_instat: 18274,
processing: 722,
save_sub: 18190,
state: 12932,
still_cancel: 2646,

@ -1,6 +1,7 @@
export const proceduresLexics = {
3556: 3556,
6959: 6959,
9759: 9759,
9760: 9760,
9761: 9761,
12980: 12980,

@ -1,25 +0,0 @@
import { createInstance } from '@jonkoops/matomo-tracker-react'
import { InstanceParams } from '@jonkoops/matomo-tracker-react/lib/types'
import { PAGES } from 'config/pages'
const getMatomoInstance = () => {
let matomoInstance: InstanceParams = {
siteId: 999,
urlBase: 'link.to.domain',
}
switch (process.env.REACT_APP_CLIENT) {
case 'insports':
matomoInstance = {
...matomoInstance,
siteId: 1,
urlBase: PAGES.matomoInstatBaseUrl,
}
break
}
return createInstance(matomoInstance)
}
export const matomoInstance = getMatomoInstance()

@ -1,10 +1,11 @@
export const PAGES = {
about_the_project: 'https://instatsport.com/InStatTV/ott_platform',
failedPaymee: '/failed-paymee',
highlights: '/highlights',
home: '/',
landing: '/landing',
mailings: '/useraccount/mailings',
match: '/matches',
matomoInstatBaseUrl: 'https://matomo.insports.tv/',
player: '/players',
team: '/teams',
thanksForSubscribe: '/thanks-for-subscription',

@ -0,0 +1,22 @@
import { ClientNames } from './clients/types'
export enum PaymentSystem {
PagBrazil = 'pag_brasil',
Paymee = 'paymee',
Paytm = 'paytm',
Stripe = 'stripe'
}
type PaymentsType = {
[key in ClientNames]: PaymentSystem
}
export const payments: PaymentsType = {
[ClientNames.Tunisia]: PaymentSystem.Paymee,
brasil: PaymentSystem.PagBrazil,
[ClientNames.India]: PaymentSystem.Paytm,
[ClientNames.Insports]: PaymentSystem.Stripe,
[ClientNames.Instat]: PaymentSystem.Stripe,
[ClientNames.Facr]: PaymentSystem.Stripe,
[ClientNames.Lff]: PaymentSystem.Stripe,
}

@ -26,6 +26,7 @@ export const PROCEDURES = {
get_user_preferences: 'get_user_preferences',
get_user_subscribes: 'get_user_subscribes',
get_user_subscriptions: 'get_user_subscriptions',
get_view_user_match: 'get_view_user_match',
landing_get_match_info: 'landing_get_match_info',
lst_c_country: 'lst_c_country',
ott_match_events: 'ott_match_events',

@ -0,0 +1,4 @@
export const querieKeys = {
liveMatchScores: 'liveMatchScores',
matchScore: 'matchScore',
}

@ -23,9 +23,17 @@ const VIEWS_APIS = {
staging: 'https://views.test.insports.tv',
}
const STATS_APIS = {
preproduction: 'https://statistic.insports.tv',
production: 'https://statistic.insports.tv',
staging: 'https://statistic-stage.insports.tv',
}
const env = isProduction ? ENV : readSelectedApi() ?? ENV
export const VIEWS_API = VIEWS_APIS[env]
export const AUTH_SERVICE = APIS[env].auth
export const API_ROOT = APIS[env].api
export const DATA_URL = `${API_ROOT}/data`
export const URL_AWS = 'https://cf-aws.insports.tv'
export const STATS_API_URL = STATS_APIS[env]

@ -1,19 +1,35 @@
export enum SportTypes {
FOOTBALL = 1,
HANDBALL = 7,
HOCKEY = 2,
BASKETBALL = 3,
TENNIS = 4,
VOLLEYBALL = 6,
HANDBALL = 7,
STREETBALL = 9,
BOXING = 12
BOXING = 12,
FIELD_HOCKEY = 14,
FIGURE_SKATING = 15,
AMERICAN_FOOTBALL = 16,
FUTSAL = 17,
FLOORBALL = 18,
CRICKET = 19,
BASEBALL = 20
}
export const SPORT_NAMES = {
[SportTypes.BASKETBALL]: 'basketball',
[SportTypes.FOOTBALL]: 'football',
[SportTypes.TENNIS]: 'tennis',
[SportTypes.HANDBALL]: 'handball',
[SportTypes.HOCKEY]: 'hockey',
[SportTypes.VOLLEYBALL]: 'volleyball',
[SportTypes.STREETBALL]: 'streetball',
[SportTypes.BOXING]: 'boxing',
[SportTypes.BASEBALL]: 'baseball',
[SportTypes.FIELD_HOCKEY]: 'field_hockey',
[SportTypes.FIGURE_SKATING]: 'figure_skating',
[SportTypes.AMERICAN_FOOTBALL]: 'american_football',
[SportTypes.FUTSAL]: 'futsal',
[SportTypes.FLOORBALL]: 'floorball',
[SportTypes.CRICKET]: 'cricket',
} as const

@ -11,6 +11,7 @@ import { PAGES } from 'config/pages'
import { T9n } from 'features/T9n'
import { useCardsStore } from 'features/CardsStore'
import { ArrowLoader } from 'features/ArrowLoader'
import { SolidButton } from 'features/UserAccount/styled'
import { ElementContainer } from '../ElementContainer'
@ -54,10 +55,7 @@ const baseStyles = isMobileDevice
const options = { placeholder: '', style: { base: baseStyles } }
export const AddCardFormInner = (props: Props) => {
const {
children,
inputsBackground,
} = props
const { inputsBackground } = props
const { error: cardError } = useCardsStore()
const isUserAccountPage = useRouteMatch(PAGES.useraccount)?.path === PAGES.useraccount
@ -234,7 +232,12 @@ export const AddCardFormInner = (props: Props) => {
<T9n t={cardError} />
</Errors>
) }
{loader ? <ArrowLoader disabled width='204px' /> : children}
<SolidButton type='submit'>
{loader
? <ArrowLoader disabled /> : (
<T9n t='save' />
)}
</SolidButton>
</ButtonsBlock>
</Form>
)

@ -1,12 +1,13 @@
import type { MouseEvent } from 'react'
import { useTheme } from 'styled-components'
import { useToggle } from 'hooks'
import { T9n } from 'features/T9n'
import {
OutlineButton,
Icon,
SolidButton,
} from 'features/UserAccount/styled'
import type { Props } from './components/Form/hooks'
@ -17,6 +18,7 @@ export const AddCardForm = ({
onAddSuccess,
}: Props) => {
const { isOpen, toggle } = useToggle(initialformOpen)
const { colors } = useTheme()
const onAddClick = (e: MouseEvent) => {
e.stopPropagation()
@ -33,11 +35,8 @@ export const AddCardForm = ({
? (
<AddCardFormInner
onAddSuccess={onSuccess}
>
<SolidButton type='submit'>
<T9n t='save' />
</SolidButton>
</AddCardFormInner>
inputsBackground={colors.inputs}
/>
)
: (
<OutlineButton

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect } from 'react'
import includes from 'lodash/includes'

@ -36,10 +36,12 @@ const MatchPage = lazy(() => import('features/MatchPage'))
const PlayerPage = lazy(() => import('features/PlayerPage'))
const TournamentPage = lazy(() => import('features/TournamentPage'))
const SystemSettings = lazy(() => import('features/SystemSettings'))
const TournamentLanding = lazy(() => import('features/TournamentLanding'))
const HighlightsPage = lazy(() => import('pages/HighlightsPage'))
const ThanksPage = lazy(() => import('pages/ThanksPage'))
const Mailings = lazy(() => import('pages/Mailings'))
const FailedPaymeePage = lazy(() => import('pages/FailedPaymeePage'))
export const AuthenticatedApp = () => {
useLexicsConfig(indexLexics)
@ -66,6 +68,9 @@ export const AuthenticatedApp = () => {
<Route path={PAGES.mailings}>
<Mailings />
</Route>
<Route path={PAGES.failedPaymee}>
<FailedPaymeePage />
</Route>
<Route path={PAGES.useraccount}>
<UserAccount />
</Route>
@ -90,6 +95,9 @@ export const AuthenticatedApp = () => {
<Route path={`${PAGES.highlights}`}>
<HighlightsPage />
</Route>
<Route path={`${PAGES.landing}`}>
<TournamentLanding />
</Route>
<Redirect to={PAGES.home} />
</Switch>
{!isProduction && <SystemSettings />}

@ -1,47 +1,50 @@
import { Suspense } from 'react'
import {
Suspense,
useEffect,
useState,
} from 'react'
import { Router } from 'react-router-dom'
import { MatomoProvider } from '@jonkoops/matomo-tracker-react'
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import { history } from 'config/history'
import { client } from 'config/clients'
import { matomoInstance } from 'config/matomo'
import { isAvailable } from 'config/env'
import { readToken } from 'helpers'
import { isLocalhost } from 'serviceWorker'
import { setClientTitleAndDescription } from 'helpers/setClientHeads'
import { isMatchPage, isMatchPageRFEF } from 'helpers/isMatchPage'
import { GlobalStores } from 'features/GlobalStores'
import { useAuthStore } from 'features/AuthStore'
import { Background } from 'features/Background'
import { GlobalStyles } from 'features/GlobalStyles'
import { Theme } from 'features/Theme'
import { JoinMatchPage } from 'features/JoinMatchPage'
import { JoinMatchPageRFEF } from 'features/JoinMatchPageRFEF'
import { UnavailableText } from 'components/UnavailableText'
import { AuthenticatedApp } from './AuthenticatedApp'
import { checkPage } from '../../helpers/checkPage'
import { PAGES } from '../../config'
import { useAuthStore } from '../AuthStore'
setClientTitleAndDescription(client.title, client.description)
const Main = () => {
const { loadingUser, user } = useAuthStore()
if (!user && (isMatchPage() || checkPage(PAGES.tournament))) return <JoinMatchPage />
if (!user && isMatchPageRFEF()) return <JoinMatchPageRFEF />
const [isToken, setIsToken] = useState(false)
const { userInfo } = useAuthStore()
const queryClient = new QueryClient()
if (user && isMatchPageRFEF()) {
window.location.href = 'https://instat.tv/football/tournaments/131'
}
// юзер считывается из localstorage или
// access_token токен истек и запрашивается новый
if (loadingUser || user?.expired) return null
useEffect(() => {
if (userInfo) readToken() && setIsToken(true)
}, [userInfo])
// имеется действующий токен
return <AuthenticatedApp />
return isToken ? (
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={isLocalhost} />
<AuthenticatedApp />
</QueryClientProvider>
) : null
}
const date = new Date()
@ -49,23 +52,21 @@ const startDate = '2022-11-24T23:00:00'
const stopDate = '2022-11-25T05:00:00'
const OTTApp = () => (
<MatomoProvider value={matomoInstance}>
<Router history={history}>
<Theme>
<GlobalStyles />
<GlobalStores>
<Background>
<Suspense fallback={null}>
{isAvailable
&& (date.getTime() < new Date(startDate).getTime()
|| date.getTime() > new Date(stopDate).getTime())
? (<Main />) : (<UnavailableText />)}
</Suspense>
</Background>
</GlobalStores>
</Theme>
</Router>
</MatomoProvider>
<Router history={history}>
<Theme>
<GlobalStyles />
<GlobalStores>
<Background>
<Suspense fallback={null}>
{isAvailable
&& (date.getTime() < new Date(startDate).getTime()
|| date.getTime() > new Date(stopDate).getTime())
? (<Main />) : (<UnavailableText />)}
</Suspense>
</Background>
</GlobalStores>
</Theme>
</Router>
)
export default OTTApp

@ -1,5 +1,4 @@
import { T9n } from 'features/T9n'
import { ArrowLoader } from 'features/ArrowLoader'
import { PasswordInput } from '../PasswordInput'
import { Logo } from '../Logo'
@ -19,6 +18,7 @@ import {
ChangePasswordModalTitle,
ChangePasswordModalText,
ChangePasswordModalButton,
ScArrowLoader,
} from '../../styled'
const ChangePassword = () => {
@ -88,7 +88,7 @@ const ChangePassword = () => {
<ButtonSolid disabled={isSubmitDisabled}>
{
isFetching
? <ArrowLoader />
? <ScArrowLoader />
: <T9n t='change_password' />
}
</ButtonSolid>

@ -50,7 +50,7 @@ export const ListWrapper = styled.div`
top: calc(100% - 4px);
left: 0;
padding: 10px;
background-color: #333333;
background-color: ${({ theme }) => theme.colors.comboboxBackground};
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3);
border-radius: 2px;
`
@ -142,8 +142,8 @@ type FlagIconProps = {
export const FlagIcon = styled.span<FlagIconProps>`
display: inline-block;
width: 20px;
height: 14px;
width: 24px;
height: 16px;
background-image: url('/images/flags-sprite.png');
background-repeat: no-repeat;
background-size: 360px;

@ -16,6 +16,8 @@ import { loginCheck } from 'features/AuthServiceApp/requests/auth'
import { getApiUrl } from 'features/AuthServiceApp/config/routes'
import { useAuthFields } from 'features/AuthServiceApp/hooks/useAuthFields'
import { addAccessTokenToUrl } from 'helpers/languageUrlParam'
import { AuthProviders } from '../../config/authProviders'
import { getAuthUrl } from '../../helpers/getAuthUrl'
import { useParamsUrl } from '../../hooks/useParamsUrl'
@ -38,7 +40,6 @@ export const useLoginForm = () => {
const [isRecoveryPopupOpen, setIsRecoveryPopupOpen] = useState(false)
const formRef = useRef<HTMLFormElement>(null)
const {
email,
error: formError,
@ -134,6 +135,6 @@ export const useLoginForm = () => {
response_type,
scope,
setIsRecoveryPopupOpen,
url,
url: addAccessTokenToUrl(url),
}
}

@ -1,5 +1,4 @@
import { T9n } from 'features/T9n'
import { ArrowLoader } from 'features/ArrowLoader'
import { RecoveryPopup } from 'features/AuthServiceApp/components/RecoveryPopup'
import { client } from 'features/AuthServiceApp/config/clients'
@ -33,6 +32,7 @@ import {
LanguageSelectWrapper,
Wrapper,
ScLoaderWrapper,
ScArrowLoader,
} from '../../styled'
import { CompanyInfo } from '../../../CompanyInfo'
@ -109,18 +109,18 @@ const Login = () => {
<T9n t={formError} />
<T9n t={authError} />
</Error>
{
isFetching
? (
<ScLoaderWrapper>
<ArrowLoader />
</ScLoaderWrapper>
) : (
<ButtonSolid type='submit' disabled={isSubmitDisabled}>
<T9n t='login' />
</ButtonSolid>
)
}
<ButtonSolid type='submit' disabled={isSubmitDisabled}>
{
isFetching
? (
<ScLoaderWrapper>
<ScArrowLoader />
</ScLoaderWrapper>
) : (<T9n t='login' />)
}
</ButtonSolid>
<RegisterButton to={`${PAGES.registration}${window.location.search}`}>
<T9n t='register' />
</RegisterButton>

@ -9,8 +9,9 @@ import {
} from 'react'
import { isValidEmail } from 'features/AuthServiceApp/helpers/isValidEmail'
import { API_ROOT } from 'features/AuthServiceApp/config/routes'
import { API_ROOT } from '../../config/routes'
import { addAccessTokenToUrl } from 'helpers/languageUrlParam'
export const useOauth = () => {
const [email, setEmail] = useState('')
@ -24,7 +25,7 @@ export const useOauth = () => {
const authorize = useCallback(async () => {
if (!formRef.current) return
const url = `${API_ROOT}/oauth`
const url = addAccessTokenToUrl(`${API_ROOT}/oauth`)
const res = await fetch(url, {
body: new FormData(formRef.current),

@ -3,7 +3,6 @@ import { Fragment } from 'react'
import { T9n } from 'features/T9n'
import { ArrowLoader } from 'features/ArrowLoader'
import { client } from 'features/AuthServiceApp/config/clients'
import { useRecovery } from './hooks'
import {
@ -15,10 +14,11 @@ import {
ApplyButton,
Text,
Body,
ScInput,
ScInputGroup,
} from './styled'
import { Input } from '../../../../components/Input'
import { InputGroup, Error } from '../../styled'
import { Error } from '../../styled'
type Props = {
isModalOpen: boolean,
@ -57,8 +57,8 @@ export const RecoveryPopup = (props: Props) => {
</Text>
) : (
<Fragment>
<InputGroup>
<Input
<ScInputGroup>
<ScInput
autoFocus
type='email'
name='email'
@ -66,7 +66,7 @@ export const RecoveryPopup = (props: Props) => {
placeholderLexic='registration_email'
onChange={onEmailChange}
/>
</InputGroup>
</ScInputGroup>
<Error>
<T9n t={error} />
</Error>
@ -76,7 +76,7 @@ export const RecoveryPopup = (props: Props) => {
{!isSendMessage ? (
<ApplyButton onClick={handleSubmit} disabled={isSendBtnDisabled}>
{isFetching
? <ArrowLoader color={client.styles.popupLoader} />
? <ArrowLoader />
: <T9n t='send' />}
</ApplyButton>
) : (

@ -10,6 +10,10 @@ import { Header as BaseHeader } from 'features/PopupComponents'
import { ButtonSolid } from 'features/Common'
import { client } from 'features/AuthServiceApp/config/clients'
import { Input } from 'components/Input'
import { InputGroup } from '../../styled'
export const Modal = styled(BaseModal)`
background-color: rgba(0, 0, 0, 0.7);
padding: 0 60px;
@ -18,8 +22,7 @@ export const Modal = styled(BaseModal)`
width: 577px;
max-width: 577px;
max-height: 414px;
padding-top: 60px;
background-color: #333333;
padding-top: 40px;
border-radius: 5px;
@media (max-width: 1370px) {
@ -105,11 +108,19 @@ export const Body = styled.div`
: ''};
`
export const ScInput = styled(Input)`
background-color: ${({ theme }) => theme.colors.inputs};
`
export const ScInputGroup = styled(InputGroup)`
${client.styles.forgotPasswordInput}
`
export const Footer = styled.div`
width: 100%;
display: flex;
justify-content: center;
padding: 1.89rem;
padding: 10px 0 25px;
${isMobileDevice
? css`
@media ${devices.mobile}{

@ -8,8 +8,8 @@ import {
Body,
Footer,
ApplyButton,
// SendConfirmationButton,
Text,
ScEmail,
} from './styled'
type Props = {
@ -36,7 +36,7 @@ export const RegisterPopup = (props: Props) => {
<Body>
<Text>
<T9n t='to_email' />&nbsp;
{email}&nbsp;
<ScEmail>{email}</ScEmail>&nbsp;
<T9n t='send_confirm' />&nbsp;
</Text>
<Text>

@ -6,20 +6,18 @@ import { devices } from 'config/devices'
import { ModalWindow } from 'features/Modal/styled'
import { Modal as BaseModal } from 'features/Modal'
import { Header as BaseHeader } from 'features/PopupComponents'
import { client } from 'features/AuthServiceApp/config/clients'
import { ButtonSolid } from 'features/Common'
import { client } from '../../config/clients'
export const Modal = styled(BaseModal)`
background-color: rgba(0, 0, 0, 0.7);
padding: 0 60px;
${ModalWindow} {
max-width: 757px;
max-width: 949px;
min-height: 414px;
padding-top: 60px;
background-color: #333333;
border-radius: 5px;
@media (max-width: 1370px) {
@ -52,9 +50,12 @@ export const Wrapper = styled.div<WrapperProps>`
`
export const Header = styled(BaseHeader)`
display: flex;
flex-direction: column;
align-items: center;
height: auto;
padding-top: 60;
justify-content: center;
gap: 30px;
${isMobileDevice
? css`
@media ${devices.mobile}{
@ -108,7 +109,8 @@ export const Footer = styled.div`
width: 100%;
display: flex;
justify-content: center;
padding: 1.89rem;
padding: 10px 0 25px;
${isMobileDevice
? css`
@media ${devices.mobile}{
@ -147,3 +149,7 @@ export const ApplyButton = styled(ButtonSolid)`
export const Text = styled.span`
margin-bottom: 20px;
`
export const ScEmail = styled.span`
font-weight: 700;
`

@ -4,7 +4,6 @@ import { useHistory } from 'react-router'
import { T9n } from 'features/T9n'
import { Checkbox } from 'features/Common/Checkbox'
import { ArrowLoader } from 'features/ArrowLoader'
import { RegisterPopup } from 'features/AuthServiceApp/components/RegisterPopup'
import { client } from 'features/AuthServiceApp/config/clients'
import { CompanyInfo } from 'features/CompanyInfo'
@ -26,6 +25,7 @@ import {
Error,
LanguageSelectWrapper,
Wrapper,
ScArrowLoader,
} from '../../styled'
import {
Label,
@ -134,7 +134,7 @@ const Registration = () => {
<ButtonSolid disabled={isSubmitDisabled} type='submit'>
{
isFetching
? <ArrowLoader />
? <ScArrowLoader />
: <T9n t='sign_up' />
}
</ButtonSolid>

@ -47,4 +47,8 @@ export const Link = styled.a`
color: ${({ theme }) => theme.colors.white};
text-decoration: underline;
margin-left: 6px;
:hover {
font-weight: 700;
}
`

@ -56,7 +56,6 @@ export const facr: ClientConfig = {
background-color: #00257A;
color: ${({ theme }) => theme.colors.white};
`,
popupLoader: '#FFFFFF',
submitButton: css`
background-color: ${({ theme }) => theme.colors.white};
color: #00257A;

@ -5,7 +5,7 @@ import { insports } from './insports'
import { instat } from './instat'
import { lff } from './lff'
import { india } from './india'
import { tunis } from './tunis'
import { tunisia } from './tunisia'
const clients = {
[ClientIds.Facr]: facr,
@ -13,7 +13,7 @@ const clients = {
[ClientIds.Lff]: lff,
[ClientIds.Insports]: insports,
[ClientIds.India]: india,
[ClientIds.Tunis]: tunis,
[ClientIds.Tunisia]: tunisia,
}
const params = new URLSearchParams(window.location.search)

@ -1,6 +1,6 @@
import styled, { css } from 'styled-components/macro'
import { tunis as platformTunis } from 'config/clients/tunis'
import { tunisia as platformTunis } from 'config/clients/tunisia'
import { isMobileDevice } from 'config/userAgent'
import type { ClientConfig } from './types'
@ -16,7 +16,7 @@ const Background = styled.div`
radial-gradient(152.89% 271.81% at 0% 96.71%, #2AB7AA 3.27%, #02505C 43.69%, #0B2E4D 100%);
`
export const tunis: ClientConfig = {
export const tunisia: ClientConfig = {
...platformTunis,
background: Background,
styles: {
@ -30,6 +30,9 @@ export const tunis: ClientConfig = {
}
` : ''};
`,
forgotPasswordInput: css`
border: none;
`,
input: css`
background-color: transparent;
:not(:last-of-type) {
@ -49,16 +52,16 @@ export const tunis: ClientConfig = {
margin-bottom: 1.82rem;
${isMobileDevice ? css`
margin-bottom: 20px;
width: 130px;
height: 100px;
background-image: url(/images/tunis_auth_logo_mobile.svg);
margin-bottom: 20px;
width: 102px;
height: 75px;
` : ''}
`,
popupApplyButton: css`
background-color: #0E8F84;
color: ${({ theme }) => theme.colors.white};
`,
popupLoader: '#FFFFFF',
submitButton: css`
background-color: ${({ theme }) => theme.colors.white};
color: #0B2E4D;

@ -17,12 +17,12 @@ export type ClientConfig = {
privacyLink: string,
styles: {
centerBlock?: StyledCss,
forgotPasswordInput?: StyledCss,
input?: StyledCss,
inputGroup?: StyledCss,
loader?: StyledCss,
logo: StyledCss,
popupApplyButton?: StyledCss,
popupLoader?: string,
submitButton?: StyledCss,
},
termsLink: string,

@ -7,6 +7,8 @@ import { useState } from 'react'
import { isValidEmail } from 'features/AuthServiceApp/helpers/isValidEmail'
import { isValidPassword } from 'features/AuthServiceApp/helpers/isValidPassword'
import { PAGES } from 'features/AuthServiceApp/config/pages'
export const useAuthFields = (page: 'login'|'registration') => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
@ -20,12 +22,16 @@ export const useAuthFields = (page: 'login'|'registration') => {
const onEmailChange = ({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
setError('')
setEmail(value)
const isRegisterPage = page === PAGES.registration
if (password.length && !checkPassword(password) && isRegisterPage) {
setError('check_password')
}
}
const onPasswordChange = ({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
setError('')
setPassword(value)
const isRegisterPage = page === 'registration'
const isRegisterPage = page === PAGES.registration
if (!checkPassword(value) && isRegisterPage) {
setError('check_password')
}

@ -1,5 +1,7 @@
import type { ClientIds } from 'config/clients/types'
import { checkCookie } from 'helpers/cookie'
import { getApiUrl } from 'features/AuthServiceApp/config/routes'
const errorLexics = {
@ -45,8 +47,10 @@ export const registerCheck = async ({
password,
urlParams,
} : RegisterProps) => {
const url = getApiUrl('/registration')
const url = `
${getApiUrl('/registration')}${checkCookie('access_token')
? `&${checkCookie('access_token')}`
: ''}`
const init: RequestInit = {
body: new URLSearchParams({
email,
@ -56,7 +60,6 @@ export const registerCheck = async ({
method: 'POST',
}
const response = await fetch(url, init)
const body: SuccessResponse | FailedResponse = await response.json()
if (body.ok) return Promise.resolve()

@ -4,6 +4,7 @@ import { isMobileDevice } from 'config/userAgent'
import { ButtonSolid as BaseButtonSolid } from 'features/Common/Button'
import { T9n } from 'features/T9n'
import { ArrowLoader } from 'features/ArrowLoader'
import { client } from 'features/AuthServiceApp/config/clients'
@ -166,6 +167,7 @@ export const ChangePasswordModalWrapper = styled.div`
left: 0;
top: 0;
display: flex;
background: rgba(0, 0, 0, 0.7);
`
export const ChangePasswordModal = styled.div`
@ -173,7 +175,7 @@ export const ChangePasswordModal = styled.div`
justify-content: center;
flex-direction: column;
align-items: center;
background: #333333;
background: ${({ theme }) => theme.colors.modalBackground};
border-radius: 5px;
font-weight: 700;
height: 414px;
@ -222,4 +224,10 @@ export const ChangePasswordModalButton = styled(ButtonSolid)`
width: fit-content;
padding: 0 50px;
cursor: pointer;
background: ${({ theme }) => theme.colors.button};
color: ${({ theme }) => theme.colors.white};
`
export const ScArrowLoader = styled(ArrowLoader)`
color: ${({ theme }) => theme.colors.loaderAuth}
`

@ -0,0 +1,5 @@
import { UserManager } from 'oidc-client'
import { getClientSettings } from './helpers'
export const userManager = new UserManager(getClientSettings())

@ -22,11 +22,11 @@ export interface Settings extends UserManagerSettings {
export const getClientNameByRedirectUri = () => {
switch (client.name) {
case ClientNames.Lff:
return 'lff.instat'
return 'lff.insports'
case ClientNames.India:
return 'india.insports'
case ClientNames.Tunis:
return ClientNames.Tunis
case ClientNames.Tunisia:
return 'diwan.insports'
case ClientNames.Facr:
return ClientNames.Facr
case ClientNames.Instat:
@ -36,15 +36,20 @@ export const getClientNameByRedirectUri = () => {
}
}
const clientsForRedirect = {
facr: ClientNames.Facr,
india: ClientNames.India,
lff: ClientNames.Lff,
tunisia: ClientNames.Tunisia,
}
const redirectUrl = () => {
const clientName = getClientNameByRedirectUri()
switch (true) {
case (
process.env.NODE_ENV === 'development'
|| client.name === 'lff'
|| client.name === 'facr'
|| client.name === 'india'
|| client.name === 'tunis'
// @ts-ignore
|| Boolean(clientsForRedirect[client.name])
):
return `${window.origin}/redirect`
case (ENV === 'staging' || ENV === 'preproduction'):
@ -56,7 +61,6 @@ const redirectUrl = () => {
export const getClientSettings = (): Settings => ({
authority: AUTH_SERVICE,
automaticSilentRenew: true,
client_id: client.auth.clientId,
filterProtocolClaims: false,
loadUserInfo: false,
@ -65,7 +69,6 @@ export const getClientSettings = (): Settings => ({
response_mode: 'query',
response_type: 'id_token token',
scope: 'openid',
silent_redirect_uri: `${window.location.origin ?? window.origin}/silent-refresh.html`,
userStore: new WebStorageStateStore({ store: window.localStorage }),
})

@ -7,7 +7,6 @@ import {
import { useHistory } from 'react-router'
import type { User } from 'oidc-client'
import { UserManager } from 'oidc-client'
import isString from 'lodash/isString'
import isBoolean from 'lodash/isBoolean'
@ -16,21 +15,34 @@ import { PAGES } from 'config'
import {
addLanguageUrlParam,
} from 'helpers/languageUrlParam'
import { writeToken, removeToken } from 'helpers/token'
import { setCookie, removeCookie } from 'helpers/cookie'
import { isMatchPage, isMatchPageRFEF } from 'helpers/isMatchPage'
writeToken,
removeToken,
readToken,
setCookie,
removeCookie,
isMatchPage,
TOKEN_KEY,
} from 'helpers'
import { useLocalStore, useToggle } from 'hooks'
import {
useLocalStore,
useSessionStore,
useToggle,
useEventListener,
} from 'hooks'
import { useLexicsStore } from 'features/LexicsStore'
import { queryParamStorage } from 'features/QueryParamsStorage'
import { getUserInfo, UserInfo } from 'requests/getUserInfo'
import { checkDevice, FailedResponse } from 'requests/checkDevice'
import type { UserInfo, FailedResponse } from 'requests'
import {
getUserInfo,
checkDevice,
getTokenVirtualUser,
} from 'requests'
import { getClientSettings, needCheckNewDeviсe } from '../helpers'
import { checkPage } from '../../../helpers/checkPage'
import { userManager } from '../config'
import { needCheckNewDeviсe } from '../helpers'
export const useAuth = () => {
const { changeLang, lang } = useLexicsStore()
@ -42,21 +54,24 @@ export const useAuth = () => {
const [user, setUser] = useState<User>()
const [isNewDeviceLogin, setIsNewDeviceLogin] = useState(false)
const [userInfo, setUserInfo] = useState<UserInfo>()
const userManager = useMemo(() => new UserManager(getClientSettings()), [])
const login = useCallback(async () => (
const login = useCallback(async () => {
userManager.signinRedirect({ extraQueryParams: { lang } })
), [userManager, lang])
}, [lang])
const logout = useCallback(() => {
const logout = useCallback((key?: string) => {
setPage(history.location.pathname)
userManager.clearStaleState()
userManager.createSigninRequest().then(({ url }) => {
const urlWithLang = addLanguageUrlParam(lang, url)
userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang })
})
removeToken()
removeCookie('access_token')
}, [userManager, lang])
if (key !== 'saveToken') {
removeCookie('access_token')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lang])
const storeUser = useCallback((loadedUser: User) => {
setUser(loadedUser)
@ -70,13 +85,21 @@ export const useAuth = () => {
const checkUser = useCallback(async () => {
const loadedUser = await userManager.getUser()
if (!loadedUser) return Promise.reject()
if (!loadedUser) {
if (!readToken()) {
const token = await getTemporaryToken()
token && await fetchUserInfo()
return Promise.resolve()
}
return Promise.reject()
}
storeUser(loadedUser)
markUserLoaded()
return loadedUser
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
userManager,
storeUser,
markUserLoaded,
])
@ -95,58 +118,92 @@ export const useAuth = () => {
validator: isString,
})
const [isFromLanding, setIsFromLanding] = useSessionStore({
clearOnUnmount: true,
defaultValue: false,
key: 'isFromLanding',
validator: isBoolean,
})
useEffect(() => {
if (isMatchPage()) setPage(history.location.pathname)
if (history.location.pathname !== page) setIsFromLanding(false)
}, [
history.location.pathname,
page,
setIsFromLanding,
setPage,
])
const getTemporaryToken = async () => {
try {
const { access_token } = await getTokenVirtualUser()
writeToken(access_token)
setCookie({
exdays: 1,
name: 'access_token',
value: access_token,
})
return access_token
// eslint-disable-next-line no-empty
} catch {
return ''
}
}
const signinRedirectCallback = useCallback(() => {
userManager.signinRedirectCallback()
.then((loadedUser) => {
storeUser(loadedUser)
queryParamStorage.clear()
history.replace(PAGES.home)
markUserLoaded()
if (page) {
const route = `${page}${page === '/' ? search : ''}`
history.push(`${route}${(
page.includes('tournaments') || page.includes('matches'))
? '?from=landing'
: ''}`)
setPage('')
setSearch('')
if (page.includes(PAGES.useraccount)) {
history.push(PAGES.home)
} else {
const route = `${page}${search}`
history.push(route)
}
markUserLoaded()
setPage('')
setSearch('')
// }
}).catch(login)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
userManager,
login,
storeUser,
history,
markUserLoaded,
page,
search,
setPage,
setSearch,
])
useEventListener({
callback: useCallback(async (e: StorageEvent) => {
const loadedUser = await userManager.getUser()
if (
e.storageArea !== localStorage
|| e.key !== TOKEN_KEY
|| !e.newValue
|| !loadedUser
|| loadedUser.access_token === e.newValue
) return
userManager.storeUser({
...loadedUser,
access_token: e.newValue,
toStorageString: loadedUser.toStorageString,
})
}, []),
event: 'storage',
})
useEffect(() => {
const isRedirectedBackFromAuthProvider = history.location.pathname === '/redirect'
if (isRedirectedBackFromAuthProvider) {
signinRedirectCallback()
} else {
checkUser().catch(() => {
if (!isMatchPage() && !isMatchPageRFEF() && !checkPage(PAGES.tournament)) {
login()
}
if (history.location.pathname === '/') {
setSearch(history.location.search)
}
setPage(history.location.pathname)
})
}
isRedirectedBackFromAuthProvider ? signinRedirectCallback() : checkUser()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
checkUser,
signinRedirectCallback,
login,
setPage,
setSearch,
history,
])
const reChekNewDevice = useCallback(async () => {
@ -160,23 +217,26 @@ export const useAuth = () => {
setTimeout(logout, 10000)
}
})
}, [logout, userManager])
}, [logout])
const checkNewDevice = useCallback(async () => {
const loadedUser = await userManager.getUser()
if (!loadedUser) return
checkDevice(loadedUser.access_token).catch(() => {
setTimeout(reChekNewDevice, 2000)
setTimeout(reChekNewDevice, 5000)
})
}, [reChekNewDevice, userManager])
}, [reChekNewDevice])
useEffect(() => {
if (!needCheckNewDeviсe) return undefined
if (!needCheckNewDeviсe && !user) return undefined
const startCheckDevice = setInterval(checkNewDevice, 20000)
isNewDeviceLogin && clearInterval(startCheckDevice)
return () => clearInterval(startCheckDevice)
}, [checkNewDevice,
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
checkNewDevice,
isNewDeviceLogin,
setIsNewDeviceLogin,
])
@ -184,7 +244,16 @@ export const useAuth = () => {
useEffect(() => {
// попытаемся обновить токен используя refresh_token
const tryRenewToken = () => {
userManager.signinSilent().catch(logout)
const tokenLastUpdated = Number(localStorage.getItem('token_updated'))
// предотвращаем одновременное обновление токена в разных окнах/вкладках
const needRenewToken = Date.now() - tokenLastUpdated >= userManager.settings.clockSkew! * 1e3
if (!needRenewToken) return
localStorage.setItem('token_updated', String(Date.now()))
userManager.signinSilent()
.catch(() => user && logout())
}
// если запросы вернули 401 | 403
window.addEventListener('FORBIDDEN_REQUEST', tryRenewToken)
@ -195,14 +264,8 @@ export const useAuth = () => {
window.removeEventListener('FORBIDDEN_REQUEST', tryRenewToken)
userManager.events.removeAccessTokenExpired(tryRenewToken)
}
}, [userManager, logout])
useEffect(() => {
// событие срабатывает после получения токена(первый
// логин и обновление токена)
userManager.events.addUserLoaded(storeUser)
return () => userManager.events.removeUserLoaded(storeUser)
}, [userManager, storeUser])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [logout])
const fetchUserInfo = useCallback(async () => {
try {
@ -212,22 +275,26 @@ export const useAuth = () => {
userInfoFetched.language.iso && changeLang(userInfoFetched.language.iso)
// eslint-disable-next-line no-empty
// eslint-disable-next-line no-empty
} catch (error) {}
}, [changeLang])
useEffect(() => {
if (user) {
fetchUserInfo()
}
fetchUserInfo()
}, [fetchUserInfo, user])
const auth = useMemo(() => ({
fetchUserInfo,
isFromLanding,
isNewDeviceLogin,
loadingUser,
login,
logout,
page,
setIsFromLanding,
setPage,
setSearch,
setUserInfo,
user,
userInfo,
}), [
@ -238,6 +305,12 @@ export const useAuth = () => {
userInfo,
login,
loadingUser,
setSearch,
setPage,
setUserInfo,
page,
setIsFromLanding,
isFromLanding,
])
return auth

@ -1,101 +0,0 @@
import {
useEffect,
useState,
MouseEvent,
} from 'react'
import { useHistory } from 'react-router-dom'
import { ProfileTypes } from 'config'
import isNumber from 'lodash/isNumber'
import { useLexicsStore } from 'features/LexicsStore'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup/store'
import { getProfileUrl } from 'features/ProfileLink/helpers'
import { getMatchInfo } from 'requests/getMatchInfo'
import { getBrazilPaymentUrl } from 'requests/getBrazilPaymentUrl'
import type { Props } from './index'
type ResponsePayment = {
url: string,
}
type ResponsePaymentArray = ResponsePayment | null
export const useBrazilPayment = ({
match,
open,
selectedPackage,
setIsOpenBrasilian,
}: Props) => {
const history = useHistory()
const { close } = useBuyMatchPopupStore()
const [src, setSrc] = useState('')
const [error, setError] = useState('')
const { translate } = useLexicsStore()
const { id, sportType } = match
const {
name,
nameLexic,
originalObject,
pass,
} = selectedPackage
const teams = isNumber(nameLexic) ? translate(String(nameLexic)) : name
const pack = translate(String(pass))
const matchLink = getProfileUrl({
id,
profileType: ProfileTypes.MATCHES,
sportType,
})
const closePopup = async (e?: MouseEvent) => {
e?.stopPropagation()
setIsOpenBrasilian(false)
setError('')
const accessMatch = await getMatchInfo(sportType, id)
if (accessMatch?.access) {
close()
history.push(matchLink)
}
}
// eslint-disable-next-line
window.onmessage = function (event) {
if (event.data === 'close') {
closePopup()
}
}
useEffect(() => {
if (open) {
(async () => {
try {
const json: ResponsePaymentArray = await getBrazilPaymentUrl({
action: pass === 'pass_match_access' ? 'one_payment' : 'create_subscription',
item: originalObject,
product_name: `${pack} ${teams}`,
})
setSrc(json?.url || '')
} catch (err) {
setError('error_payment_unsuccessful')
}
})()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedPackage, open])
return {
closePopup,
error,
matchLink,
src,
}
}

@ -1,5 +1,7 @@
import { useState } from 'react'
import { useTheme } from 'styled-components'
import isEmpty from 'lodash/isEmpty'
import { AddCardFormInner } from 'features/AddCardForm/components/Form'
@ -43,7 +45,7 @@ export const CardStep = ({
goBack,
showClearBtn,
} = useBuyMatchPopupStore()
const { colors } = useTheme()
const emptyCards = isEmpty(cards)
return (
@ -64,7 +66,7 @@ export const CardStep = ({
<AddCardFormInner
onAddSuccess={goBack}
initialformOpen={emptyCards}
inputsBackground='rgba(255, 255, 255, 0.1)'
inputsBackground={colors.inputs}
clearInputs={clearInputs}
setClearInputs={setClearInputs}
>

@ -17,13 +17,13 @@ export const ErrorStep = () => {
} = useBuyMatchPopupStore()
return (
<Wrapper height={278} width={369}>
<Wrapper width={369}>
<Header height={24}>
<HeaderTitle>
<T9n t='payment' />
</HeaderTitle>
</Header>
<Body marginTop={30} marginBottom={40}>
<Body marginTop={30}>
<ResultText>
{paymentError || <T9n t='error_payment_unsuccessful' />}
</ResultText>

@ -0,0 +1,173 @@
import {
MouseEvent,
useCallback,
useEffect,
useState,
} from 'react'
import { PAGES, ProfileTypes } from 'config'
import { ClientNames } from 'config/clients/types'
import { payments, PaymentSystem } from 'config/payments'
import isNumber from 'lodash/isNumber'
import { useLexicsStore } from 'features/LexicsStore'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup/store'
import { getProfileUrl } from 'features/ProfileLink/helpers'
import { getMatchInfo } from 'requests/getMatchInfo'
import { getPaymentUrl } from 'requests/getPaymentUrl'
import { redirectToUrl } from 'helpers'
import type { Props } from './index'
type ResponsePayment = {
url: string,
}
type ResponsePaymentArray = ResponsePayment | null
export const useIframePayment = ({
match,
open,
paymentSystem,
selectedPackage,
setIsOpenIframe,
}: Props) => {
const { close } = useBuyMatchPopupStore()
const [src, setSrc] = useState('')
const [error, setError] = useState('')
const [isPaymentProcessing, setIsPaymentProcessing] = useState(false)
const { translate } = useLexicsStore()
const { id, sportType } = match
const {
name,
nameLexic,
originalObject,
pass,
} = selectedPackage
const teams = isNumber(nameLexic) ? translate(String(nameLexic)) : name
const pack = translate(String(pass))
const matchLink = getProfileUrl({
id,
profileType: ProfileTypes.MATCHES,
sportType,
})
const closePopup = useCallback(async (e?: MouseEvent) => {
e?.stopPropagation()
if (error) {
setIsOpenIframe(false)
setError('')
}
const accessMatch = await getMatchInfo(sportType, id)
if (accessMatch?.access) {
setIsPaymentProcessing(false)
setIsOpenIframe(false)
setError('')
close()
redirectToUrl(matchLink)
}
}, [close, error, id, matchLink, setIsOpenIframe, sportType])
const paymentRequest = async () => {
let url_cancel
let url_return
let action: Parameters<typeof getPaymentUrl>[0]['action']
switch (paymentSystem) {
case PaymentSystem.Paymee:
url_cancel = `${window.origin}/failed-paymee`
url_return = null
// paymee не умеет работать с подписками
action = 'one_payment'
break
default:
url_return = `${window.location.origin}${PAGES.thanksForSubscribe}`
action = pass === 'pass_match_access' ? 'one_payment' : 'create_subscription'
break
}
const payment: ResponsePaymentArray = await getPaymentUrl({
action,
item: originalObject,
product_name: `${pack} ${teams}`,
service: paymentSystem,
url_cancel,
url_return,
})
setSrc(payment?.url || '')
}
if (paymentSystem === payments[ClientNames.Brasil]) {
// eslint-disable-next-line
window.onmessage = function (event) {
if (event.data === 'close') {
closePopup()
}
}
}
useEffect(() => {
let interval: ReturnType<typeof setInterval>
let timeout: ReturnType<typeof setTimeout>
const paymentCallback = (event: MessageEvent<{ event_id: string }>) => {
if (event.data.event_id === 'paymee.complete') {
setIsPaymentProcessing(true)
interval = setInterval(() => closePopup(), 2000)
timeout = setTimeout(() => {
clearInterval(interval)
setIsPaymentProcessing(false)
setError('failed_paymee')
setSrc('')
}, 60000)
}
}
if (paymentSystem === payments[ClientNames.Tunisia]) {
window.addEventListener(
'message',
paymentCallback,
false,
)
}
return () => {
window.removeEventListener(
'message',
paymentCallback,
false,
)
clearInterval(interval)
clearTimeout(timeout)
}
}, [closePopup, paymentSystem])
useEffect(() => {
if (open) {
(async () => {
try {
await paymentRequest()
} catch (err) {
setError('error_payment_unsuccessful')
}
})()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedPackage, open])
return {
closePopup,
error,
isPaymentProcessing,
matchLink,
src,
}
}

@ -2,7 +2,10 @@ import { Loader } from 'features/Loader'
import { MatchPackage, Match } from 'features/BuyMatchPopup/types'
import { T9n } from 'features/T9n'
import { useBrazilPayment } from './hooks'
import { PaymentSystem } from 'config/payments'
import { isMobileDevice } from 'config'
import { useIframePayment } from './hooks'
import {
LoaderWrapper,
@ -12,35 +15,38 @@ import {
export type Props = {
match: Match,
open: boolean,
paymentSystem: PaymentSystem,
selectedPackage: MatchPackage,
setIsOpenBrasilian: (open: boolean) => void,
setIsOpenIframe: (open: boolean) => void,
}
export const BrazilPayment = ({
export const IframePayment = ({
match,
open,
paymentSystem,
selectedPackage,
setIsOpenBrasilian,
setIsOpenIframe,
}: Props) => {
const {
closePopup,
error,
isPaymentProcessing,
src,
} = useBrazilPayment({
} = useIframePayment({
match,
open,
paymentSystem,
selectedPackage,
setIsOpenBrasilian,
setIsOpenIframe,
})
return (
<ScModal isOpen={open} withCloseButton close={closePopup}>
<ScModal isOpen={open} withCloseButton close={() => setIsOpenIframe(false)}>
{src && open ? (
<iframe
title='BrazilPayment'
title='Payment'
frameBorder='0'
src={src}
height={600}
height={isMobileDevice ? 450 : 600}
width='100%'
/>
) : (
@ -54,6 +60,14 @@ export const BrazilPayment = ({
)}
</>
)}
{
isPaymentProcessing && (
<LoaderWrapper>
<Loader color='#515151' />
<T9n t='processing' />
</LoaderWrapper>
)
}
</ScModal>
)
}

@ -7,16 +7,18 @@ import { Modal as BaseModal } from 'features/Modal'
export const ScModal = styled(BaseModal)`
background-color: rgba(0, 0, 0, 0.7);
z-index: 53;
${ModalWindow} {
width: 800px;
padding: 50px;
background-color: #333333;
border-radius: 5px;
justify-content: center;
${isMobileDevice
? css`
text-align: center;
font-size: 16px;
padding: 50px 0px;
width: calc(100vw - 30px);
@media screen and (orientation: landscape){
@ -30,7 +32,7 @@ export const ScModal = styled(BaseModal)`
`
export const LoaderWrapper = styled.div`
position: absolute;
position: fixed;
top: 0;
left: 0;
width: 100%;
@ -38,4 +40,6 @@ export const LoaderWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
z-index: 20;
`

@ -3,11 +3,14 @@ import {
useEffect,
useState,
MouseEvent,
useMemo,
} from 'react'
import isNull from 'lodash/isNull'
import { MDASH } from 'config'
import { payments } from 'config/payments'
import { client } from 'config/clients'
import { CountryCodeType, getCountryCode } from 'requests/getCountryCode'
@ -17,8 +20,9 @@ import { Name } from 'features/Name'
import { useCardsStore } from 'features/CardsStore'
import { ArrowLoader } from 'features/ArrowLoader'
import { Arrow } from 'features/HeaderFilters/components/DateFilter/styled'
import { useAuthStore } from 'features/AuthStore'
import { BrazilPayment } from '../BrazilPayment'
import { IframePayment } from '../IframePayment'
import { useBuyMatchPopupStore } from '../../store'
import { SelectedCard } from '../SelectedCard'
@ -38,8 +42,13 @@ export const PackageSelectionStep = () => {
cards,
fetchCards,
} = useCardsStore()
const [isOpenBrasilian, setIsOpenBrasilian] = useState(false)
const [isOpenIframe, setIsOpenIframe] = useState(false)
const [countryCode, setCountryCode] = useState<CountryCodeType | null>(null)
const {
logout,
setSearch,
user,
} = useAuthStore()
const {
close,
@ -63,9 +72,28 @@ export const PackageSelectionStep = () => {
}
}, [cards, fetchCards])
if (!match) return null
const paymentSystem = useMemo(() => {
switch (countryCode?.country_code) {
case 'BR':
return payments.brasil
case 'TN':
return payments.tunisia
default:
return payments[client.name]
}
}, [countryCode])
const isIframePayment = useMemo(() => {
switch (countryCode?.country_code) {
case 'BR':
case 'TN':
return true
default:
return false
}
}, [countryCode])
const isBrasil = countryCode?.country_code === 'BR'
if (!match) return null
const getUserCountry = () => {
getCountryCode().then(setCountryCode)
@ -75,8 +103,8 @@ export const PackageSelectionStep = () => {
cards?.length
&& lastSelectedPackage === selectedPackage?.id
&& setDisabledBuyBtn(true)
if (isBrasil) {
setIsOpenBrasilian(true)
if (isIframePayment) {
setIsOpenIframe(true)
} else {
onBuyClick(e)
}
@ -85,6 +113,7 @@ export const PackageSelectionStep = () => {
return (
<Wrapper>
<Header>
{hasPreviousStep && (
<HeaderActions position='left'>
@ -110,26 +139,34 @@ export const PackageSelectionStep = () => {
</Header>
<Body marginTop={20}>
<Packages />
{isBrasil ? null : <SelectedCard />}
{!isIframePayment && <SelectedCard />}
</Body>
<Footer>
{loader ? (
<ArrowLoader width='204px' disabled />
) : (
<Button
disabled={!selectedPackage || disabledBuyBtn}
onClick={onHandleClick}
>
<Button
disabled={!selectedPackage || disabledBuyBtn}
onClick={(e) => {
if (user) {
onHandleClick(e)
} else {
setSearch(window.location.search)
logout('saveToken')
}
}}
>
{loader ? (
<ArrowLoader disabled />
) : (
<T9n t='buy_subscription' />
</Button>
)}
)}
</Button>
</Footer>
{selectedPackage && isOpenBrasilian && (
<BrazilPayment
{selectedPackage && isIframePayment && (
<IframePayment
match={match}
open={isOpenBrasilian}
open={isOpenIframe}
paymentSystem={paymentSystem}
selectedPackage={selectedPackage}
setIsOpenBrasilian={setIsOpenBrasilian}
setIsOpenIframe={setIsOpenIframe}
/>
)}
</Wrapper>

@ -67,13 +67,13 @@ export const PackagesList = ({
/>
{
subPackage.type === SubscriptionType.Month
&& (
<ScAutoRenewal>
<T9n
t='auto_renewal'
/>
</ScAutoRenewal>
)
&& (
<ScAutoRenewal>
<T9n
t='auto_renewal'
/>
</ScAutoRenewal>
)
}
</ScPriceContainer>
</Item>

@ -52,7 +52,7 @@ export const Item = styled.li.attrs(() => ({
width: 100%;
min-height: 140px;
padding: 20px 30px 20px 20px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 100%), #3F3F3F;
background: ${({ theme }) => theme.colors.packageBackground};
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3);
border-radius: 2px;

@ -4,6 +4,8 @@ import map from 'lodash/map'
import { MDASH } from 'config'
import { isSubscribePopup } from 'helpers'
import { Name as Names } from 'features/Name'
import { T9n } from 'features/T9n'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup/store'
@ -55,14 +57,17 @@ export const SelectSubscriptionStep = () => {
return (
<Wrapper>
<Header>
<HeaderTitle>
<Names nameObj={match.team1} />
{` ${MDASH} `}
<Names nameObj={match.team2} />
<ChooseSub>
<T9n t='choose_subscription' />
</ChooseSub>
</HeaderTitle>
{!isSubscribePopup()
&& (
<HeaderTitle>
<Names nameObj={match.team1} />
{` ${MDASH} `}
<Names nameObj={match.team2} />
<ChooseSub>
<T9n t='choose_subscription' />
</ChooseSub>
</HeaderTitle>
)}
<HeaderActions position='right'>
<CloseButton onClick={close} />
</HeaderActions>

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

Loading…
Cancel
Save