Compare commits

..

3 Commits

Author SHA1 Message Date
Rakov 80f2f4386c fix(#720): media query fqtv 2 years ago
Rakov fb4acfbebd fix(#720): fix media query 2 years ago
Rakov 71eff354a9 fix(#720): timeline mode 2 years ago
  1. 53
      .drone.yml
  2. 11
      Makefile
  3. BIN
      public/clients/facr/favicon/android-chrome-192x192.png
  4. BIN
      public/clients/facr/favicon/android-chrome-512x512.png
  5. BIN
      public/clients/facr/favicon/apple-touch-icon.png
  6. BIN
      public/clients/facr/favicon/favicon-16x16.png
  7. BIN
      public/clients/facr/favicon/favicon-32x32.png
  8. BIN
      public/clients/facr/favicon/favicon.ico
  9. BIN
      public/clients/fqtv/favicon/android-chrome-192x192.png
  10. BIN
      public/clients/fqtv/favicon/android-chrome-512x512.png
  11. BIN
      public/clients/fqtv/favicon/apple-touch-icon.png
  12. BIN
      public/clients/fqtv/favicon/favicon-16x16.png
  13. BIN
      public/clients/fqtv/favicon/favicon-32x32.png
  14. BIN
      public/clients/fqtv/favicon/favicon.ico
  15. BIN
      public/clients/lff/favicon/android-chrome-192x192.png
  16. BIN
      public/clients/lff/favicon/android-chrome-512x512.png
  17. BIN
      public/clients/lff/favicon/apple-touch-icon.png
  18. BIN
      public/clients/lff/favicon/favicon-16x16.png
  19. BIN
      public/clients/lff/favicon/favicon-32x32.png
  20. BIN
      public/clients/lff/favicon/favicon.ico
  21. 21
      public/silent-refresh.html
  22. 6
      src/features/AuthServiceApp/components/ConfirmPopup/index.tsx
  23. 2
      src/features/AuthServiceApp/config/lexics.tsx
  24. 30
      src/features/AuthStore/hooks/useAuth.tsx
  25. 1
      src/features/Common/Image/index.tsx
  26. 2
      src/features/HeaderFilters/store/hooks/index.tsx
  27. 17
      src/features/MatchCard/CardFrontside/hooks.tsx
  28. 33
      src/features/MatchesSlider/hooks.tsx
  29. 60
      src/features/MatchesSlider/index.tsx
  30. 38
      src/features/MatchesSlider/styled.tsx
  31. 71
      src/features/MatchesTimeline/hooks.tsx
  32. 62
      src/features/MatchesTimeline/index.tsx
  33. 34
      src/features/MatchesTimeline/styled.tsx
  34. 5
      src/features/SystemSettings/hooks.tsx
  35. 13
      src/helpers/token/index.tsx
  36. 9
      src/requests/getMatches/getTimelineMatches.tsx

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

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 884 B

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 102 KiB

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

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

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

@ -24,10 +24,6 @@ import {
setCookie, setCookie,
removeCookie, removeCookie,
isMatchPage, isMatchPage,
REFRESH_TOKEN_KEY,
removeRefreshToken,
writeRefreshToken,
readRefreshToken,
} from 'helpers' } from 'helpers'
import { import {
@ -81,7 +77,6 @@ export const useAuth = () => {
userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang }) userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang })
}) })
removeToken() removeToken()
removeRefreshToken()
if (key !== 'saveToken') { if (key !== 'saveToken') {
removeCookie('access_token') removeCookie('access_token')
} }
@ -163,15 +158,12 @@ export const useAuth = () => {
} }
} }
const signinRedirectCallback = useCallback((refreshToken: string | null) => { const signinRedirectCallback = useCallback(() => {
setPage(history.location.pathname) setPage(history.location.pathname)
userManager.signinRedirectCallback() userManager.signinRedirectCallback()
.then((loadedUser) => { .then((loadedUser) => {
storeUser(loadedUser) storeUser(loadedUser)
if (refreshToken) writeRefreshToken(refreshToken)
queryParamStorage.clear() queryParamStorage.clear()
if (page.includes(PAGES.useraccount)) { if (page.includes(PAGES.useraccount)) {
history.push(PAGES.home) history.push(PAGES.home)
@ -183,7 +175,7 @@ export const useAuth = () => {
setPage('') setPage('')
setSearch('') setSearch('')
}).catch(login) }).catch(login)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
login, login,
storeUser, storeUser,
@ -195,14 +187,11 @@ export const useAuth = () => {
const searchToken = urlSearch.get('access_token') const searchToken = urlSearch.get('access_token')
const searchRefToken = urlSearch.get('id_token') const searchRefToken = urlSearch.get('id_token')
const searchExp = urlSearch.get('expires_in') const searchExp = urlSearch.get('expires_in')
const refreshToken = urlSearch.get(REFRESH_TOKEN_KEY)
const isRedirectedBackFromAuthProvider = Boolean(searchToken && searchRefToken && searchExp) const isRedirectedBackFromAuthProvider = Boolean(searchToken && searchRefToken && searchExp)
isRedirectedBackFromAuthProvider isRedirectedBackFromAuthProvider ? signinRedirectCallback() : checkUser()
? signinRedirectCallback(refreshToken) // eslint-disable-next-line react-hooks/exhaustive-deps
: checkUser()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
checkUser, checkUser,
signinRedirectCallback, signinRedirectCallback,
@ -232,7 +221,7 @@ export const useAuth = () => {
}, [reChekNewDevice]) }, [reChekNewDevice])
useEffect(() => { useEffect(() => {
if (!needCheckNewDeviсe || !user) return undefined if (!needCheckNewDeviсe && !user) return undefined
const startCheckDevice = setInterval(checkNewDevice, 20000) const startCheckDevice = setInterval(checkNewDevice, 20000)
isNewDeviceLogin && clearInterval(startCheckDevice) isNewDeviceLogin && clearInterval(startCheckDevice)
return () => clearInterval(startCheckDevice) return () => clearInterval(startCheckDevice)
@ -242,7 +231,6 @@ export const useAuth = () => {
checkNewDevice, checkNewDevice,
isNewDeviceLogin, isNewDeviceLogin,
setIsNewDeviceLogin, setIsNewDeviceLogin,
user,
]) ])
duel.channel('active_page') // поле в LS, определяющее активность вкладки duel.channel('active_page') // поле в LS, определяющее активность вкладки
@ -252,13 +240,7 @@ export const useAuth = () => {
// библиотека oidc-client не поддерживает обновление токена только на 1 вкладке // библиотека oidc-client не поддерживает обновление токена только на 1 вкладке
// @ts-ignore // @ts-ignore
if (window.isMaster()) { if (window.isMaster()) {
// safari ограничивает доступ к куке через крос доменные запросы userManager.signinSilent().catch(logout)
// передаем рефреш токен через квери параметры
userManager.signinSilent({
extraQueryParams: {
refresh_token: readRefreshToken(),
},
}).catch(logout)
} }
} }
// если запросы вернули 401 | 403 // если запросы вернули 401 | 403

@ -37,6 +37,7 @@ export const Image = ({
onError={onError} onError={onError}
onLoad={onLoad} onLoad={onLoad}
title={title} title={title}
loading='lazy'
/> />
) )
} }

@ -31,7 +31,7 @@ export const useFilters = () => {
}) })
const sportList = getLocalStorageItem(querieKeys.sportsList) const sportList = getLocalStorageItem(querieKeys.sportsList)
const [selectedMode, setSelectedMode] = useState<Tabs>(Tabs.WEEK) const [selectedMode, setSelectedMode] = useState<Tabs>(isMobileDevice ? Tabs.WEEK : Tabs.TIMELINE)
const [selectedMonthModeDate, setSelectedMonthModeDate] = useState(startOfMonth(new Date())) const [selectedMonthModeDate, setSelectedMonthModeDate] = useState(startOfMonth(new Date()))
const isMonthMode = selectedMode === Tabs.MONTH const isMonthMode = selectedMode === Tabs.MONTH
const isTimelineMode = selectedMode === Tabs.TIMELINE && !isMobileDevice const isTimelineMode = selectedMode === Tabs.TIMELINE && !isMobileDevice

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

@ -9,9 +9,11 @@ import {
import throttle from 'lodash/throttle' import throttle from 'lodash/throttle'
import { MATCH_CARD_WIDTH, MATCH_CARD_GAP } from 'features/MatchCard/config'
import type { Match } from 'helpers' import type { Match } from 'helpers'
const MATCHES_TO_SCROLL = 3 const MATCHES_TO_SCROLL = 12
const SCROLLING_DELAY = 350 const SCROLLING_DELAY = 350
@ -22,8 +24,6 @@ export const useMatchesSlider = (matches: Array<Match>) => {
const [showRightArrow, setShowRigthArrow] = useState(false) const [showRightArrow, setShowRigthArrow] = useState(false)
const [isLeftArrowDisabled, setIsLeftArrowDisabled] = useState(false) const [isLeftArrowDisabled, setIsLeftArrowDisabled] = useState(false)
const [isRightArrowDisabled, setIsRightArrowDisabled] = useState(false) const [isRightArrowDisabled, setIsRightArrowDisabled] = useState(false)
const [sliderLiWidth, setSliderLiWidth] = useState(0)
const [sliderLiGap, setSliderLiGap] = useState(0)
useEffect(() => { useEffect(() => {
const { const {
@ -70,32 +70,13 @@ export const useMatchesSlider = (matches: Array<Match>) => {
setShowRigthArrow(false) setShowRigthArrow(false)
} }
useEffect(() => {
const liFirst = slidesRef.current?.querySelectorAll('li')[0]?.getBoundingClientRect()
const liWidth = liFirst?.width
const liFirstLeft = liFirst?.left
const liSecondLeft = slidesRef.current?.querySelectorAll('li')[1]?.getBoundingClientRect().left
if (liWidth && liFirstLeft && liSecondLeft) {
setSliderLiWidth(liWidth)
setSliderLiGap(liSecondLeft - liFirstLeft - liWidth)
}
}, [])
const slideLeft = useMemo(() => throttle(() => { const slideLeft = useMemo(() => throttle(() => {
slidesRef.current!.scrollBy({ slidesRef.current!.scrollBy(-((MATCH_CARD_WIDTH + MATCH_CARD_GAP) * MATCHES_TO_SCROLL), 0)
behavior: 'smooth', }, SCROLLING_DELAY), [])
left: -((sliderLiWidth + sliderLiGap) * MATCHES_TO_SCROLL),
top: 0,
})
}, SCROLLING_DELAY), [sliderLiGap, sliderLiWidth])
const slideRight = useMemo(() => throttle(() => { const slideRight = useMemo(() => throttle(() => {
slidesRef.current!.scrollBy({ slidesRef.current!.scrollBy((MATCH_CARD_WIDTH + MATCH_CARD_GAP) * MATCHES_TO_SCROLL, 0)
behavior: 'smooth', }, SCROLLING_DELAY), [])
left: ((sliderLiWidth + sliderLiGap) * MATCHES_TO_SCROLL),
top: 0,
})
}, SCROLLING_DELAY), [sliderLiGap, sliderLiWidth])
return { return {
isLeftArrowDisabled, isLeftArrowDisabled,

@ -1,11 +1,18 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { useQuery } from 'react-query'
import map from 'lodash/map' import map from 'lodash/map'
import isEmpty from 'lodash/isEmpty' import isEmpty from 'lodash/isEmpty'
import type { Match } from 'helpers' import type { Match } from 'helpers'
import { querieKeys } from 'config'
import { MatchCard } from 'features/MatchCard' import { MatchCard } from 'features/MatchCard'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { LiveScore, getLiveScores } from 'requests'
import { useMatchesSlider } from './hooks' import { useMatchesSlider } from './hooks'
import { import {
@ -16,13 +23,10 @@ import {
} from './styled' } from './styled'
type MatchesSliderProps = { type MatchesSliderProps = {
cardSize?: number,
matches: Array<Match>, matches: Array<Match>,
} }
const PADDING_PARENT = 10 export const MatchesSlider = ({ matches }: MatchesSliderProps) => {
export const MatchesSlider = ({ cardSize, matches }: MatchesSliderProps) => {
const { const {
isLeftArrowDisabled, isLeftArrowDisabled,
isRightArrowDisabled, isRightArrowDisabled,
@ -36,19 +40,27 @@ export const MatchesSlider = ({ cardSize, matches }: MatchesSliderProps) => {
slidesRef, slidesRef,
} = useMatchesSlider(matches) } = useMatchesSlider(matches)
const { isScoreHidden } = useMatchSwitchesStore()
const { data: liveMatchScores } = useQuery({
queryFn: async () => {
if (!isScoreHidden && matches.filter(({ live }) => live)?.length > 0) {
const scores = await getLiveScores()
return scores
}
return []
},
queryKey: querieKeys.liveMatchScores,
refetchInterval: 5000,
})
const scrollToMatchByIndex = (index: number) => { const scrollToMatchByIndex = (index: number) => {
const match = slidesRef.current!.querySelectorAll('li')[index] const offsetLeftCount = slidesRef.current!.querySelectorAll('li')[index].offsetLeft
const offsetLeftCount = match.offsetLeft const PADDING_PARENT = 10
const offsetLeft = offsetLeftCount - PADDING_PARENT const offsetLeft = offsetLeftCount - PADDING_PARENT
setTimeout(() => { slidesRef.current!.scrollBy(offsetLeft, 0)
slidesRef.current!.scrollBy({
// @ts-ignore
behavior: 'instant',
left: offsetLeft,
})
}, 0)
} }
// скролл к лайв матчам или сегодняшней дате // скролл к лайв матчам или сегодняшней дате
@ -67,16 +79,8 @@ export const MatchesSlider = ({ cardSize, matches }: MatchesSliderProps) => {
const slidesRefClientWidth = slidesRef.current!.clientWidth const slidesRefClientWidth = slidesRef.current!.clientWidth
const slidesRefScrollWidth = slidesRef.current!.scrollWidth const slidesRefScrollWidth = slidesRef.current!.scrollWidth
slidesRef.current!.scrollBy(slidesRefScrollWidth - slidesRefClientWidth, 0)
setTimeout(() => { // eslint-disable-next-line react-hooks/exhaustive-deps
slidesRef.current!.scrollBy({
// @ts-ignore
behavior: 'instant',
left: slidesRefScrollWidth - slidesRefClientWidth + PADDING_PARENT,
})
}, 0)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
if (isEmpty(matches)) return null if (isEmpty(matches)) return null
@ -98,15 +102,15 @@ export const MatchesSlider = ({ cardSize, matches }: MatchesSliderProps) => {
/> />
</ArrowButton> </ArrowButton>
)} )}
<Slides <Slides ref={slidesRef} onScroll={onScroll}>
ref={slidesRef}
onScroll={onScroll}
size={cardSize}
>
{map(matches, (match) => ( {map(matches, (match) => (
<MatchCard <MatchCard
match={match} match={match}
key={match.id} key={match.id}
score={liveMatchScores?.find(
({ match_id, sport_id }: LiveScore) => match_id === match.id
&& sport_id === match.sportType,
)}
/> />
))} ))}
</Slides> </Slides>

@ -8,9 +8,7 @@ export const Wrapper = styled.div`
padding-right: 5px; padding-right: 5px;
` `
export const Slides = styled.ul<{ export const Slides = styled.ul`
size?: number,
}>`
display: flex; display: flex;
scroll-behavior: smooth; scroll-behavior: smooth;
overflow-x: auto; overflow-x: auto;
@ -28,13 +26,37 @@ export const Slides = styled.ul<{
${CardWrapper} { ${CardWrapper} {
position: relative; position: relative;
height: ${({ size }) => (size ? `${size}px` : '12.9rem')}; height: 12.9rem;
width: ${({ size }) => (size ? `${size}px` : '12.9rem')}; width: 12.9rem;
transition: scale 0.2s;
&:hover { &:hover {
scale: 1.04; scale: 1.04;
} }
@media screen and (min-width: 1920px) {
height: 13.8rem;
width: 13.8rem;
}
@media screen and (max-width: 1920px) {
height: 13.4rem;
width: 13.4rem;
}
@media screen and (max-width: 1600px) {
height: 13rem;
width: 13rem;
}
@media screen and (max-width: 1440px) {
height: 12.8rem;
width: 12.8rem;
}
@media screen and (max-width: 1280px) {
height: 12.6rem;
width: 12.6rem;
}
} }
` `
@ -77,13 +99,13 @@ export const ArrowButton = styled.button<ArrowProps>`
z-index: 3; z-index: 3;
top: 50%; top: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
left: ${({ direction }) => (direction === 'left' ? '1.6rem' : 'calc(100% - 2.3rem)')}; left: ${({ direction }) => (direction === 'left' ? '1.6rem' : 'calc(100% - 1.6rem)')};
&:hover { &:hover {
${({ direction, disabled }) => (!disabled ? css` ${({ direction, disabled }) => (!disabled ? css`
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
left: ${direction === 'left' ? '2rem' : 'calc(100% - 2.8rem)'}; left: ${direction === 'left' ? '2rem' : 'calc(100% - 2rem)'};
${Arrow} { ${Arrow} {
width: 1.5rem; width: 1.5rem;

@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import { import {
useCallback, useCallback,
useEffect, useEffect,
@ -6,16 +5,11 @@ import {
useState, useState,
} from 'react' } from 'react'
import { useQuery } from 'react-query'
import { querieKeys, PAGES } from 'config'
import { useRouteMatch } from 'react-router-dom' import { useRouteMatch } from 'react-router-dom'
import { import {
MatchDto, MatchDto,
MatchesTimeline,
TimelineTournamentDto, TimelineTournamentDto,
getLiveScores,
getTimelineMatches, getTimelineMatches,
} from 'requests' } from 'requests'
@ -23,7 +17,8 @@ import { Match, prepareMatches } from 'helpers'
import { useAuthStore } from 'features/AuthStore' import { useAuthStore } from 'features/AuthStore'
import { useHeaderFiltersStore } from 'features/HeaderFilters' import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
import { PAGES } from 'config'
export type TimelineTournamentList = Array<Omit<TimelineTournamentDto, 'matches'> & { export type TimelineTournamentList = Array<Omit<TimelineTournamentDto, 'matches'> & {
matches: Array<Match>, matches: Array<Match>,
@ -40,12 +35,10 @@ export const useTimeline = () => {
selectedSport, selectedSport,
setSportIds, setSportIds,
} = useHeaderFiltersStore() } = useHeaderFiltersStore()
const { isScoreHidden } = useMatchSwitchesStore()
const [isTimelineFetching, setIsTimelineFetching] = useState(true) const [isTimelineFetching, setIsTimelineFetching] = useState(true)
const [onlineUpcomingMatches, setOnlineUpcomingMatches] = useState<Array<Match>>([]) const [onlineUpcomingMatches, setOnlineUpcomingMatches] = useState<Array<Match>>([])
const [tournamentList, setTournamentList] = useState<TimelineTournamentList>([]) const [tournamentList, setTournamentList] = useState<TimelineTournamentList>([])
const [timeline, setTimeline] = useState<MatchesTimeline>()
const prepareMatchesDto = useCallback((matches: Array<MatchDto>) => prepareMatches( const prepareMatchesDto = useCallback((matches: Array<MatchDto>) => prepareMatches(
matches, matches,
@ -57,22 +50,21 @@ export const useTimeline = () => {
(async () => { (async () => {
setIsTimelineFetching(true) setIsTimelineFetching(true)
try { try {
const timelineFetched = await getTimelineMatches() const timeline = await getTimelineMatches()
setTimeline(timelineFetched) const convertedMatches = timeline.online_upcoming[0].matches
const convertedMatches = timelineFetched.online_upcoming[0].matches
const preparedMatches = prepareMatchesDto(convertedMatches) const preparedMatches = prepareMatchesDto(convertedMatches)
setOnlineUpcomingMatches(preparedMatches) setOnlineUpcomingMatches(preparedMatches)
setTournamentList([ setTournamentList([
...timelineFetched.favorite.map((item) => ({ ...timeline.favorite.map((item) => ({
...item, ...item,
matches: prepareMatchesDto(item.matches), matches: prepareMatchesDto(item.matches),
})), })),
...timelineFetched.promo.map((item) => ({ ...timeline.promo.map((item) => ({
...item, ...item,
matches: prepareMatchesDto(item.matches), matches: prepareMatchesDto(item.matches),
})), })),
...timelineFetched.others.map((item) => ({ ...timeline.others.map((item) => ({
...item, ...item,
matches: prepareMatchesDto(item.matches), matches: prepareMatchesDto(item.matches),
})), })),
@ -84,55 +76,6 @@ export const useTimeline = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
useQuery({
queryFn: async () => {
let isLiveMatch = false
if (timeline) {
for (const [, value] of Object.entries(timeline)) {
if (isLiveMatch) break
// eslint-disable-next-line no-loop-func
value.forEach((t) => {
const liveMatch = t.matches.find((match) => match.live)
if (liveMatch) {
isLiveMatch = true
}
})
}
}
if (!isScoreHidden && isLiveMatch) {
const scores = await getLiveScores()
tournamentList.forEach((tournament) => {
tournament.matches.forEach((match) => {
const score = scores.find((s) => (
s.match_id === match.id && s.sport_id === match.sportType
))
if (score) {
match.team1.score = score?.team1.score ?? match.team1.score
match.team1.penalty_score = score.team1.penalty_score ?? match.team1.penalty_score
match.team2.score = score?.team2.score ?? match.team2.score
match.team2.penalty_score = score.team2.penalty_score ?? match.team2.penalty_score
}
})
})
onlineUpcomingMatches.forEach((upcomingMatch) => {
const score = scores.find((s) => (
s.match_id === upcomingMatch.id && s.sport_id === upcomingMatch.sportType
))
if (score) {
upcomingMatch.team1.score = score?.team1.score ?? upcomingMatch.team1.score
upcomingMatch.team1.penalty_score = score.team1.penalty_score
?? upcomingMatch.team1.penalty_score
upcomingMatch.team2.score = score?.team2.score ?? upcomingMatch.team2.score
upcomingMatch.team2.penalty_score = score.team2.penalty_score
?? upcomingMatch.team2.penalty_score
}
})
}
},
queryKey: querieKeys.liveMatchScores,
refetchInterval: 30000,
})
const filteredTournamentsBySport = useMemo(() => { const filteredTournamentsBySport = useMemo(() => {
if (isHomePage && selectedSport) { if (isHomePage && selectedSport) {
return tournamentList.filter((t) => compareSport(t.sport_id, selectedSport)) return tournamentList.filter((t) => compareSport(t.sport_id, selectedSport))

@ -1,13 +1,10 @@
import { import {
useCallback, useCallback,
useEffect, useEffect,
useLayoutEffect,
useRef, useRef,
useState, useState,
} from 'react' } from 'react'
import isEmpty from 'lodash/isEmpty'
import { MatchesSlider } from 'features/MatchesSlider' import { MatchesSlider } from 'features/MatchesSlider'
import { TimelineTournamentList, useTimeline } from 'features/MatchesTimeline/hooks' import { TimelineTournamentList, useTimeline } from 'features/MatchesTimeline/hooks'
import { InfiniteScroll } from 'features/InfiniteScroll' import { InfiniteScroll } from 'features/InfiniteScroll'
@ -26,11 +23,7 @@ import {
TournamentSubtitleWrapper, TournamentSubtitleWrapper,
} from './styled' } from './styled'
const TOURNAMENT_LIMIT = 10 const TOURNAMENT_LIMIT = 6
const MATCH_COUNT = 6
const PADDING = 30
const GAP_COUNT = 5
const SLIDER_ITEM_GAP = 0.9 // rem
export const MatchesTimeline = () => { export const MatchesTimeline = () => {
const { const {
@ -43,25 +36,6 @@ export const MatchesTimeline = () => {
const pageRef = useRef(0) const pageRef = useRef(0)
const isLastPageRef = useRef(false) const isLastPageRef = useRef(false)
const wrapperRef = useRef<HTMLDivElement>(null)
const [cardSize, setCardSize] = useState(0)
// вычисляем размеры плитки
useLayoutEffect(() => {
const offsetWidth = wrapperRef.current?.offsetWidth
const ulItemGapFromRem = SLIDER_ITEM_GAP * parseFloat(
getComputedStyle(document.documentElement).fontSize,
)
if (offsetWidth) {
const size = Math.round(
((offsetWidth - (ulItemGapFromRem * GAP_COUNT) - PADDING) / MATCH_COUNT),
)
setCardSize(size)
}
}, [])
const getTournaments = useCallback(() => tournamentList.slice( const getTournaments = useCallback(() => tournamentList.slice(
pageRef.current * TOURNAMENT_LIMIT, pageRef.current * TOURNAMENT_LIMIT,
@ -86,29 +60,25 @@ export const MatchesTimeline = () => {
useEffect(() => { useEffect(() => {
if (tournamentList.length) { if (tournamentList.length) {
pageRef.current = 0 pageRef.current = 0
isLastPageRef.current = false tournamentList.length && setTournaments(getTournaments())
tournamentList.length > 0 && setTournaments(getTournaments())
pageRef.current = 1 pageRef.current = 1
} }
}, [getTournaments, tournamentList]) }, [getTournaments, tournamentList])
if (isTimelineFetching) {
return <Loading><T9n t='loading' />...</Loading>
}
return ( return (
<InfiniteScroll fullPageScroll onFetchMore={getMoreTournaments}> <InfiniteScroll fullPageScroll onFetchMore={getMoreTournaments}>
<Wrapper ref={wrapperRef}> <Wrapper>
{isTimelineFetching && <Loading><T9n t='loading' />...</Loading>} {onlineUpcomingMatches.length > 0 && (
{(!isEmpty(onlineUpcomingMatches)) && (
<RowWrapper> <RowWrapper>
<Content> <Content>
<Tournament <Tournament isOnlineUpcoming>
size={cardSize}
isOnlineUpcoming
>
LIVE & UPCOMING LIVE & UPCOMING
</Tournament> </Tournament>
<MatchesSlider <MatchesSlider matches={onlineUpcomingMatches} />
cardSize={cardSize}
matches={onlineUpcomingMatches}
/>
</Content> </Content>
</RowWrapper> </RowWrapper>
)} )}
@ -118,7 +88,7 @@ export const MatchesTimeline = () => {
tournament, tournament,
tournament_id, tournament_id,
}) => ( }) => (
<RowWrapper key={`${tournament_id}_${sport_id}`}> <RowWrapper key={tournament_id}>
<TournamentSubtitleWrapper> <TournamentSubtitleWrapper>
<TournamentSubtitle <TournamentSubtitle
sportInfo={matches[0].sportInfo} sportInfo={matches[0].sportInfo}
@ -128,20 +98,14 @@ export const MatchesTimeline = () => {
/> />
</TournamentSubtitleWrapper> </TournamentSubtitleWrapper>
<Content> <Content>
<Tournament <Tournament gradientColor={tournament.color}>
size={cardSize}
gradientColor={tournament.color}
>
<TournamentLogo <TournamentLogo
id={tournament_id} id={tournament_id}
profileType={ProfileTypes.TOURNAMENTS} profileType={ProfileTypes.TOURNAMENTS}
sportType={sport_id} sportType={sport_id}
/> />
</Tournament> </Tournament>
<MatchesSlider <MatchesSlider matches={matches} />
cardSize={cardSize}
matches={matches}
/>
</Content> </Content>
</RowWrapper> </RowWrapper>
))} ))}

@ -12,12 +12,12 @@ export const RowWrapper = styled.div``
export const Content = styled.div` export const Content = styled.div`
display: flex; display: flex;
` gap: 10px;
`
export const Tournament = styled.div<{ export const Tournament = styled.div<{
gradientColor?: string, gradientColor?: string,
isOnlineUpcoming?: boolean, isOnlineUpcoming?: boolean,
size?: number,
}>` }>`
${({ gradientColor }) => (gradientColor ${({ gradientColor }) => (gradientColor
? css`background: linear-gradient(187deg, ${gradientColor} -4.49%, #000000 68.29%), #000000;` ? css`background: linear-gradient(187deg, ${gradientColor} -4.49%, #000000 68.29%), #000000;`
@ -29,10 +29,10 @@ export const Tournament = styled.div<{
`} `}
position: relative; position: relative;
height: ${({ size }) => (size ? `${size}px` : '12.9rem')}; height: 12.9rem;
width: ${({ size }) => (size ? `${size}px` : '12.9rem')}; width: 12.9rem;
flex-shrink: 0; flex-shrink: 0;
margin: 10px 0.93rem 10px 0px; margin: 10px 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -42,14 +42,32 @@ export const Tournament = styled.div<{
color: #FFFFFF; color: #FFFFFF;
text-align: start; text-align: start;
padding: 10px; padding: 10px;
@media screen and (min-width: 1920px) {
height: 13.8rem;
width: 13.8rem;
}
@media screen and (max-width: 1920px) {
height: 13.4rem;
width: 13.4rem;
}
@media screen and (max-width: 1440px) {
height: 12.8rem;
width: 12.8rem;
}
@media screen and (max-width: 1280px) {
height: 12.6rem;
width: 12.6rem;
}
` `
export const TournamentLogo = styled(ProfileLogo)` export const TournamentLogo = styled(ProfileLogo)`
position: absolute; position: absolute;
max-height: 100%;
padding: 20px; padding: 20px;
width: 100%;
height: 100%;
object-fit: scale-down;
` `
export const Loading = styled.div` export const Loading = styled.div`

@ -9,8 +9,8 @@ import { SELECTED_API_KEY } from 'helpers/selectedApi'
import { useToggle } from 'hooks/useToggle' import { useToggle } from 'hooks/useToggle'
import { useLocalStore } from 'hooks/useStorage' import { useLocalStore } from 'hooks/useStorage'
import { removeRefreshToken, removeToken } from 'helpers' import { removeToken } from '../../helpers'
import { removeCookie } from 'helpers/cookie' import { removeCookie } from '../../helpers/cookie'
type FormElement = HTMLFormElement & { type FormElement = HTMLFormElement & {
api: HTMLInputElement & { api: HTMLInputElement & {
@ -37,7 +37,6 @@ export const useSystemSettings = () => {
const { api } = e.currentTarget const { api } = e.currentTarget
setSelectedApi(api.value) setSelectedApi(api.value)
removeToken() removeToken()
removeRefreshToken()
removeCookie('access_token') removeCookie('access_token')
window.location.reload() window.location.reload()
} }

@ -1,5 +1,4 @@
export const TOKEN_KEY = 'token' export const TOKEN_KEY = 'token'
export const REFRESH_TOKEN_KEY = 'refresh_token'
export const readToken = () => ( export const readToken = () => (
localStorage.getItem(TOKEN_KEY) localStorage.getItem(TOKEN_KEY)
@ -12,15 +11,3 @@ export const writeToken = (token: string) => (
export const removeToken = () => ( export const removeToken = () => (
localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(TOKEN_KEY)
) )
export const removeRefreshToken = () => {
localStorage.removeItem(REFRESH_TOKEN_KEY)
}
export const writeRefreshToken = (token: string) => (
localStorage.setItem(REFRESH_TOKEN_KEY, token)
)
export const readRefreshToken = () => (
localStorage.getItem(REFRESH_TOKEN_KEY)
)

@ -18,14 +18,7 @@ export type MatchesTimeline = {
} }
export const getTimelineMatches = (sportId?: number): Promise<MatchesTimeline> => { export const getTimelineMatches = (sportId?: number): Promise<MatchesTimeline> => {
const getTimezoneOffset = () => { const url = new URL(`${API_ROOT}/v1/matches/timeline`)
const offset = new Date().getTimezoneOffset()
if (offset === 0) return offset
return -(offset)
}
const timeZoneOffset = getTimezoneOffset()
const url = new URL(`${API_ROOT}/v1/broadcasts/timeline/${timeZoneOffset}`)
if (sportId) { if (sportId) {
url.searchParams.append('sport_id', `${sportId}`) url.searchParams.append('sport_id', `${sportId}`)

Loading…
Cancel
Save