* feat(ott-1637): change in match popup and match events requests (#488)

* Ott 1118 user match preferences (#492)

* Ott 1118 part 1 (#469)

* fix(1118): wip

* feat(1118): tournaments block

* feat(1118): resolved comments

* feat(1118): show and search tournaments (#472)

feat(1118): show loader on fetch

* Ott 1118 part 3 (#478)

* feat(1118): added lexics

* feat(1118): wip, load and save prefs

* feat(1118): virtualization using list

* Ott 1118 part 4 (#487)

* fix(1118): reverted useRequest hook change

* fix(1118): px -> rem

* fix(1118): request matches after apply

* fix(1118): wip

* refactor(1118): removed old filters from HeaderFiltersStore (#491)

* fix(1118): disabled preferences (#493)

* fix(ott-1662): add new langs (#494)

Co-authored-by: PolyakovaM <55061222+PolyakovaM@users.noreply.github.com>
Co-authored-by: Serg <936x936@gmail.com>
keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Mirlan 4 years ago committed by GitHub
parent e7f20c0ccb
commit 92599ec976
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      package.json
  2. 35
      src/config/languages.tsx
  3. 9
      src/config/lexics/indexLexics.tsx
  4. 2
      src/config/procedures.tsx
  5. 60
      src/features/App/AuthenticatedApp.tsx
  6. 2
      src/features/ArrowLoader/index.tsx
  7. 1
      src/features/ArrowLoader/types.tsx
  8. 8
      src/features/Common/Checkbox/Icon.tsx
  9. 4
      src/features/Common/Checkbox/index.tsx
  10. 15
      src/features/HeaderFilters/store/helpers/isValidMatchStatus/__tests__/index.tsx
  11. 9
      src/features/HeaderFilters/store/helpers/isValidMatchStatus/index.tsx
  12. 15
      src/features/HeaderFilters/store/helpers/isValidSportType/__tests__/index.tsx
  13. 9
      src/features/HeaderFilters/store/helpers/isValidSportType/index.tsx
  14. 72
      src/features/HeaderFilters/store/hooks/index.tsx
  15. 73
      src/features/HomePage/hooks.tsx
  16. 1
      src/features/ItemsList/styled.tsx
  17. 13
      src/features/LexicsStore/helpers/isSupportedLang/__tests__/index.tsx
  18. 3
      src/features/MatchCard/CardFrontside/index.tsx
  19. 10
      src/features/MatchSwitches/hooks.tsx
  20. 5
      src/features/Matches/hooks.tsx
  21. 5
      src/features/Menu/index.tsx
  22. 5
      src/features/PlayerPage/hooks.tsx
  23. 74
      src/features/PreferencesPopup/components/Search/index.tsx
  24. 39
      src/features/PreferencesPopup/components/SportList/index.tsx
  25. 39
      src/features/PreferencesPopup/components/SportList/styled.tsx
  26. 34
      src/features/PreferencesPopup/components/TournamentInfo/index.tsx
  27. 59
      src/features/PreferencesPopup/components/TournamentListItem/index.tsx
  28. 53
      src/features/PreferencesPopup/components/TournamentsBlock/index.tsx
  29. 68
      src/features/PreferencesPopup/components/TournamentsList/index.tsx
  30. 61
      src/features/PreferencesPopup/index.tsx
  31. 50
      src/features/PreferencesPopup/store/helpers.tsx
  32. 124
      src/features/PreferencesPopup/store/hooks/index.tsx
  33. 68
      src/features/PreferencesPopup/store/hooks/usePreferencesLoader.tsx
  34. 62
      src/features/PreferencesPopup/store/hooks/useSearch.tsx
  35. 35
      src/features/PreferencesPopup/store/hooks/useSports.tsx
  36. 77
      src/features/PreferencesPopup/store/hooks/useTournaments.tsx
  37. 37
      src/features/PreferencesPopup/store/hooks/useTournamentsBySports.tsx
  38. 20
      src/features/PreferencesPopup/store/index.tsx
  39. 106
      src/features/PreferencesPopup/styled.tsx
  40. 1
      src/features/StripeElements/index.tsx
  41. 5
      src/features/TeamPage/hooks.tsx
  42. 4
      src/features/TournamentPage/hooks.tsx
  43. 16
      src/requests/getMatches/getHomeMatches.tsx
  44. 3
      src/requests/getMatches/getPlayerMatches.tsx
  45. 3
      src/requests/getMatches/getTeamMatches.tsx
  46. 3
      src/requests/getMatches/getTournamentMatches.tsx
  47. 28
      src/requests/getSportTournaments.tsx
  48. 29
      src/requests/getUserPreferences.tsx
  49. 25
      src/requests/saveUserPreferences.tsx

@ -28,6 +28,7 @@
"react-router": "^5.2.0", "react-router": "^5.2.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "^4.0.1", "react-scripts": "^4.0.1",
"react-window": "^1.8.6",
"screenfull": "^5.0.2", "screenfull": "^5.0.2",
"styled-components": "^5.1.1" "styled-components": "^5.1.1"
}, },
@ -55,6 +56,7 @@
"@types/react-responsive": "^8.0.2", "@types/react-responsive": "^8.0.2",
"@types/react-router": "^5.1.8", "@types/react-router": "^5.1.8",
"@types/react-router-dom": "^5.1.6", "@types/react-router-dom": "^5.1.6",
"@types/react-window": "^1.8.5",
"@types/styled-components": "^5.1.0", "@types/styled-components": "^5.1.0",
"commitizen": "^4.1.2", "commitizen": "^4.1.2",
"eslint": "^7.14.0", "eslint": "^7.14.0",

@ -14,6 +14,41 @@ export const langsList = [
locale: 'cs', locale: 'cs',
title: 'Čeština', title: 'Čeština',
}, },
{
className: 'ua',
locale: 'uk',
title: 'Українська',
},
{
className: 'fr',
locale: 'fr',
title: 'Français',
},
{
className: 'de',
locale: 'de',
title: 'Deutsch',
},
{
className: 'es',
locale: 'es',
title: 'Español',
},
{
className: 'pt',
locale: 'pt',
title: 'Português',
},
{
className: 'it',
locale: 'it',
title: 'Italiano',
},
{
className: 'pl',
locale: 'pl',
title: 'Polska',
},
] as const ] as const
export type Languages = typeof langsList[number]['locale'] export type Languages = typeof langsList[number]['locale']

@ -59,6 +59,14 @@ const buyMatchPopupLexics = {
success_subscription: 14097, success_subscription: 14097,
} }
const preferencesPopupLexics = {
all: 15449,
apply: 15117,
my_preferences: 15087,
search: 15127,
sport_types: 15088,
}
export const indexLexics = { export const indexLexics = {
add_to_favorites: 14967, add_to_favorites: 14967,
add_to_favorites_error: 12943, add_to_favorites_error: 12943,
@ -107,6 +115,7 @@ export const indexLexics = {
watch_from_last_pause: 13022, watch_from_last_pause: 13022,
watch_now: 13020, watch_now: 13020,
...preferencesPopupLexics,
...proceduresLexics, ...proceduresLexics,
...matchPopupLexics, ...matchPopupLexics,
...buyMatchPopupLexics, ...buyMatchPopupLexics,

@ -16,6 +16,7 @@ export const PROCEDURES = {
get_user_favorites: 'get_user_favorites', get_user_favorites: 'get_user_favorites',
get_user_info: 'get_user_info', get_user_info: 'get_user_info',
get_user_match_second: 'get_user_match_second', get_user_match_second: 'get_user_match_second',
get_user_preferences: 'get_user_preferences',
get_user_subscriptions: 'get_user_subscriptions', get_user_subscriptions: 'get_user_subscriptions',
landing_get_match_info: 'landing_get_match_info', landing_get_match_info: 'landing_get_match_info',
lst_c_country: 'lst_c_country', lst_c_country: 'lst_c_country',
@ -29,5 +30,6 @@ export const PROCEDURES = {
save_user_info: 'save_user_info', save_user_info: 'save_user_info',
save_user_match_second: 'save_user_match_second', save_user_match_second: 'save_user_match_second',
save_user_page: 'save_user_page', save_user_page: 'save_user_page',
save_user_preferences: 'save_user_preferences',
save_user_subscription: 'save_user_subscription', save_user_subscription: 'save_user_subscription',
} }

@ -18,6 +18,7 @@ import { MatchSwitchesStore } from 'features/MatchSwitches'
import { UserFavoritesStore } from 'features/UserFavorites/store' import { UserFavoritesStore } from 'features/UserFavorites/store'
import { MatchPopup, MatchPopupStore } from 'features/MatchPopup' import { MatchPopup, MatchPopupStore } from 'features/MatchPopup'
import { BuyMatchPopup, BuyMatchPopupStore } from 'features/BuyMatchPopup' import { BuyMatchPopup, BuyMatchPopupStore } from 'features/BuyMatchPopup'
import { PreferencesPopup, PreferencesPopupStore } from 'features/PreferencesPopup'
import { CardsStore } from 'features/CardsStore' import { CardsStore } from 'features/CardsStore'
const HomePage = lazy(() => import('features/HomePage')) const HomePage = lazy(() => import('features/HomePage'))
@ -36,35 +37,38 @@ export const AuthenticatedApp = () => {
<MatchSwitchesStore> <MatchSwitchesStore>
<UserFavoritesStore> <UserFavoritesStore>
<ExtendedSearchStore> <ExtendedSearchStore>
<MatchPopupStore> <PreferencesPopupStore>
<BuyMatchPopupStore> <MatchPopupStore>
<MatchPopup /> <BuyMatchPopupStore>
<BuyMatchPopup /> <MatchPopup />
<BuyMatchPopup />
<PreferencesPopup />
{/* в Switch как прямой children можно рендерить только Route или Redirect */} {/* в Switch как прямой children можно рендерить только Route или Redirect */}
<Switch> <Switch>
<Route path={PAGES.useraccount}> <Route path={PAGES.useraccount}>
<UserAccount /> <UserAccount />
</Route> </Route>
<Route exact path={PAGES.home}> <Route exact path={PAGES.home}>
<HomePage /> <HomePage />
</Route> </Route>
<Route path={`/:sportName${PAGES.tournament}/:pageId`}> <Route path={`/:sportName${PAGES.tournament}/:pageId`}>
<TournamentPage /> <TournamentPage />
</Route> </Route>
<Route path={`/:sportName${PAGES.team}/:pageId`}> <Route path={`/:sportName${PAGES.team}/:pageId`}>
<TeamPage /> <TeamPage />
</Route> </Route>
<Route path={`/:sportName${PAGES.player}/:pageId`}> <Route path={`/:sportName${PAGES.player}/:pageId`}>
<PlayerPage /> <PlayerPage />
</Route> </Route>
<Route path={`/:sportName${PAGES.match}/:pageId`}> <Route path={`/:sportName${PAGES.match}/:pageId`}>
<MatchPage /> <MatchPage />
</Route> </Route>
<Redirect to={PAGES.home} /> <Redirect to={PAGES.home} />
</Switch> </Switch>
</BuyMatchPopupStore> </BuyMatchPopupStore>
</MatchPopupStore> </MatchPopupStore>
</PreferencesPopupStore>
</ExtendedSearchStore> </ExtendedSearchStore>
</UserFavoritesStore> </UserFavoritesStore>
</MatchSwitchesStore> </MatchSwitchesStore>

@ -4,10 +4,12 @@ import { Wrapper, Arrows } from './styled'
export const ArrowLoader = ({ export const ArrowLoader = ({
backgroundColor, backgroundColor,
backgroundSize, backgroundSize,
className,
disabled, disabled,
width, width,
}: ArrowLoaderProps) => ( }: ArrowLoaderProps) => (
<Wrapper <Wrapper
className={className}
width={width} width={width}
backgroundColor={backgroundColor} backgroundColor={backgroundColor}
disabled={disabled} disabled={disabled}

@ -1,6 +1,7 @@
export type ArrowLoaderProps = { export type ArrowLoaderProps = {
backgroundColor?: string, backgroundColor?: string,
backgroundSize?: string, backgroundSize?: string,
className?: string,
disabled?: boolean, disabled?: boolean,
width?:string, width?:string,
} }

@ -1,22 +1,14 @@
import styled, { css } from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { devices } from 'config'
type SvgColorStylesProps = { type SvgColorStylesProps = {
checked?: boolean, checked?: boolean,
} }
export const svgColorStyles = css<SvgColorStylesProps>` export const svgColorStyles = css<SvgColorStylesProps>`
fill: ${({ checked }) => (checked ? '#ffffff' : '#B8C1CC')}; fill: ${({ checked }) => (checked ? '#ffffff' : '#B8C1CC')};
@media ${devices.mobile} {
fill: ${({ checked }) => (checked ? '#294FC4' : '#B8C1CC')}
}
` `
export const CheckboxSvg = styled.svg` export const CheckboxSvg = styled.svg`
min-width: 24px;
min-height: 24px;
margin-right: 22px; margin-right: 22px;
align-self: flex-start; align-self: flex-start;

@ -1,4 +1,4 @@
import { InputHTMLAttributes } from 'react' import type { ReactNode, InputHTMLAttributes } from 'react'
import type { LexicsId } from 'features/LexicsStore/types' import type { LexicsId } from 'features/LexicsStore/types'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
@ -18,7 +18,7 @@ type Props = Pick<InputHTMLAttributes<HTMLInputElement>, (
| 'onClick' | 'onClick'
| 'onChange' | 'onChange'
)> & { )> & {
label?: string, label?: ReactNode,
labelLexic?: LexicsId, labelLexic?: LexicsId,
} }

@ -1,15 +0,0 @@
import { MatchStatuses } from 'features/HeaderFilters/store/hooks'
import { isValidMatchStatus } from '..'
it('returns true for valid match statuses', () => {
expect(isValidMatchStatus(MatchStatuses.Finished)).toBe(true)
expect(isValidMatchStatus(MatchStatuses.Live)).toBe(true)
expect(isValidMatchStatus(MatchStatuses.Soon)).toBe(true)
})
it('returns false for invalid match statuses', () => {
expect(isValidMatchStatus(-1)).toBe(false)
expect(isValidMatchStatus(0)).toBe(false)
expect(isValidMatchStatus(4)).toBe(false)
})

@ -1,9 +0,0 @@
import isNumber from 'lodash/isNumber'
import includes from 'lodash/includes'
import values from 'lodash/values'
import { MatchStatuses } from '../../hooks'
export const isValidMatchStatus = (value: number | null) => (
isNumber(value) && includes(values(MatchStatuses), value)
)

@ -1,15 +0,0 @@
import { SportTypes } from 'config'
import { isValidSportType } from '..'
it('returns true for valid sport types', () => {
expect(isValidSportType(SportTypes.BASKETBALL)).toBe(true)
expect(isValidSportType(SportTypes.FOOTBALL)).toBe(true)
expect(isValidSportType(SportTypes.HOCKEY)).toBe(true)
})
it('returns false for invalid sport types', () => {
expect(isValidSportType(-1)).toBe(false)
expect(isValidSportType(0)).toBe(false)
expect(isValidSportType(4)).toBe(false)
})

@ -1,9 +0,0 @@
import isNumber from 'lodash/isNumber'
import includes from 'lodash/includes'
import values from 'lodash/values'
import { SportTypes } from 'config'
export const isValidSportType = (value: number | null) => (
isNumber(value) && includes(values(SportTypes), value)
)

@ -1,28 +1,17 @@
import { import {
useMemo, useMemo,
useState,
useEffect, useEffect,
useCallback, useCallback,
} from 'react' } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { isToday } from 'date-fns' import { isToday } from 'date-fns'
import { SportTypes } from 'config'
import { useQueryParamStore } from 'hooks' import { useQueryParamStore } from 'hooks'
import { filterKeys } from '../config' import { filterKeys } from '../config'
import { getMoscowDate } from '../helpers/getMoscowDate' import { getMoscowDate } from '../helpers/getMoscowDate'
import { serializeDate } from '../helpers/dateSerializers' import { serializeDate } from '../helpers/dateSerializers'
import { isValidDate } from '../helpers/isValidDate' import { isValidDate } from '../helpers/isValidDate'
import { isValidSportType } from '../helpers/isValidSportType'
import { isValidMatchStatus } from '../helpers/isValidMatchStatus'
export enum MatchStatuses {
Live = 2,
Finished = 3,
Soon = 1,
}
export const useFilters = () => { export const useFilters = () => {
const { search } = useLocation() const { search } = useLocation()
@ -36,88 +25,29 @@ export const useFilters = () => {
const isTodaySelected = isToday(selectedDate) const isTodaySelected = isToday(selectedDate)
const [
selectedMatchStatus,
setMatchStatus,
] = useQueryParamStore<MatchStatuses | null>({
clearOnUnmount: true,
defaultValue: null,
key: filterKeys.MATCH_STATUS,
validator: isValidMatchStatus,
})
const setSelectedMatchStatus = useCallback((status: MatchStatuses) => {
if (status === selectedMatchStatus) {
setMatchStatus(null)
} else {
setMatchStatus(status)
}
}, [
selectedMatchStatus,
setMatchStatus,
])
const [
selectedSportTypeId,
setSelectedSportTypeId,
] = useQueryParamStore<SportTypes | null>({
clearOnUnmount: true,
defaultValue: null,
key: filterKeys.SPORT_TYPE,
validator: isValidSportType,
})
const [
selectedTournamentId,
setSelectedTournamentId,
] = useState<number | null>(null)
const resetFilters = useCallback(() => { const resetFilters = useCallback(() => {
setSelectedDate(new Date()) setSelectedDate(new Date())
setMatchStatus(null) }, [setSelectedDate])
setSelectedSportTypeId(null)
setSelectedTournamentId(null)
}, [
setSelectedDate,
setMatchStatus,
setSelectedSportTypeId,
])
useEffect(() => { useEffect(() => {
if (!search) { if (!search) {
resetFilters() resetFilters()
} }
if (!isTodaySelected) {
setMatchStatus(null)
}
}, [ }, [
isTodaySelected, isTodaySelected,
resetFilters, resetFilters,
search, search,
setMatchStatus,
]) ])
const store = useMemo(() => ({ const store = useMemo(() => ({
isTodaySelected, isTodaySelected,
selectedDate, selectedDate,
selectedDateFormatted: getMoscowDate(selectedDate), selectedDateFormatted: getMoscowDate(selectedDate),
selectedMatchStatus,
selectedSportTypeId,
selectedTournamentId,
setSelectedDate, setSelectedDate,
setSelectedMatchStatus,
setSelectedSportTypeId,
setSelectedTournamentId,
}), [ }), [
isTodaySelected, isTodaySelected,
selectedDate, selectedDate,
selectedMatchStatus,
selectedSportTypeId,
selectedTournamentId,
setSelectedDate, setSelectedDate,
setSelectedMatchStatus,
setSelectedSportTypeId,
setSelectedTournamentId,
]) ])
return store return store

@ -1,82 +1,19 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import filter from 'lodash/filter' import { getHomeMatches } from 'requests'
import isPast from 'date-fns/isPast'
import differenceInMinutes from 'date-fns/differenceInMinutes'
import { import { useHeaderFiltersStore } from 'features/HeaderFilters'
getHomeMatches,
Matches,
MatchesBySection,
} from 'requests'
import { MatchStatuses, useHeaderFiltersStore } from 'features/HeaderFilters'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
const matchesFilteredByStatus = (matches : Matches, status : MatchStatuses | null) => {
if (!status) return matches
const filteredMatches = filter(matches, (match) => {
const matchDate = new Date(match.date)
const matchIsStarted = isPast(matchDate)
const difTime = differenceInMinutes(new Date(), matchDate)
switch (status) {
case MatchStatuses.Soon:
return !matchIsStarted && (difTime > -60)
case MatchStatuses.Live:
return match.live && matchIsStarted
case MatchStatuses.Finished:
return matchIsStarted && (match.storage || match.has_video)
default: return false
}
})
return filteredMatches
}
const setMatches = (
matches : MatchesBySection,
status : MatchStatuses | null,
): MatchesBySection => {
if (matches.isVideoSections) {
return {
...matches,
broadcast: matchesFilteredByStatus(matches.broadcast, status),
features: matchesFilteredByStatus(matches.features, status),
highlights: matchesFilteredByStatus(matches.highlights, status),
}
} return {
...matches,
broadcast: matchesFilteredByStatus(matches.broadcast, status),
}
}
export const useHomePage = () => { export const useHomePage = () => {
const { const { selectedDateFormatted } = useHeaderFiltersStore()
selectedDateFormatted,
selectedMatchStatus,
selectedSportTypeId,
selectedTournamentId,
} = useHeaderFiltersStore()
const { availableMatchesOnly } = useMatchSwitchesStore()
const fetchMatches = useCallback( const fetchMatches = useCallback(
(limit: number, offset: number) => getHomeMatches({ (limit: number, offset: number) => getHomeMatches({
availableMatchesOnly,
date: selectedDateFormatted, date: selectedDateFormatted,
limit, limit,
matchStatus: null,
offset, offset,
sportType: selectedSportTypeId, }),
tournamentId: selectedTournamentId, [selectedDateFormatted],
})
.then((matches) => setMatches(matches, selectedMatchStatus)),
[
selectedDateFormatted,
selectedMatchStatus,
selectedSportTypeId,
selectedTournamentId,
availableMatchesOnly,
],
) )
return { fetchMatches } return { fetchMatches }
} }

@ -87,6 +87,7 @@ export const Flag = styled.img`
` `
export const TeamOrCountry = styled(NameBase)` export const TeamOrCountry = styled(NameBase)`
font-weight: normal;
font-size: 0.49rem; font-size: 0.49rem;
line-height: 0.75rem; line-height: 0.75rem;
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);

@ -3,11 +3,18 @@ import { isSupportedLang } from '..'
it('returns true for supported languages', () => { it('returns true for supported languages', () => {
expect(isSupportedLang('ru')).toBe(true) expect(isSupportedLang('ru')).toBe(true)
expect(isSupportedLang('en')).toBe(true) expect(isSupportedLang('en')).toBe(true)
expect(isSupportedLang('cs')).toBe(true)
expect(isSupportedLang('uk')).toBe(true)
expect(isSupportedLang('fr')).toBe(true)
expect(isSupportedLang('de')).toBe(true)
expect(isSupportedLang('es')).toBe(true)
expect(isSupportedLang('pt')).toBe(true)
expect(isSupportedLang('it')).toBe(true)
expect(isSupportedLang('pl')).toBe(true)
}) })
it('returns false for not supported languages', () => { it('returns false for not supported languages', () => {
expect(isSupportedLang('es')).toBe(false)
expect(isSupportedLang('ja')).toBe(false) expect(isSupportedLang('ja')).toBe(false)
expect(isSupportedLang('fr')).toBe(false) expect(isSupportedLang('ro')).toBe(false)
expect(isSupportedLang('de')).toBe(false) expect(isSupportedLang('tr')).toBe(false)
}) })

@ -38,7 +38,6 @@ import {
NameSignWrapper, NameSignWrapper,
HoverFrame, HoverFrame,
} from '../styled' } from '../styled'
import { MatchStatuses, useHeaderFiltersStore } from '../../HeaderFilters'
type Props = { type Props = {
match: Match, match: Match,
@ -70,12 +69,10 @@ export const CardFrontside = ({
const tournamentName = useName(tournament) const tournamentName = useName(tournament)
const { isInFavorites } = useUserFavoritesStore() const { isInFavorites } = useUserFavoritesStore()
const { isScoreHidden } = useMatchSwitchesStore() const { isScoreHidden } = useMatchSwitchesStore()
const { selectedMatchStatus } = useHeaderFiltersStore()
const isInFuture = getUnixTime(date) > getUnixTime(new Date()) const isInFuture = getUnixTime(date) > getUnixTime(new Date())
const showScore = !( const showScore = !(
!calc !calc
|| isInFuture || isInFuture
|| selectedMatchStatus === MatchStatuses.Soon
|| isScoreHidden || isScoreHidden
) || (live && !isScoreHidden) ) || (live && !isScoreHidden)
const tournamentInFavorites = isInFavorites(ProfileTypes.TOURNAMENTS, tournament.id) const tournamentInFavorites = isInFavorites(ProfileTypes.TOURNAMENTS, tournament.id)

@ -3,7 +3,6 @@ import isBoolean from 'lodash/isBoolean'
import { useLocalStore } from 'hooks' import { useLocalStore } from 'hooks'
const SCORE_KEY = 'score_hidden' const SCORE_KEY = 'score_hidden'
const MATCH_AVAILABILITY_KEY = 'available_matches_only'
export const useMatchSwitches = () => { export const useMatchSwitches = () => {
const [isScoreHidden, setScoreHidden] = useLocalStore({ const [isScoreHidden, setScoreHidden] = useLocalStore({
@ -13,17 +12,8 @@ export const useMatchSwitches = () => {
}) })
const toggleScore = () => setScoreHidden(!isScoreHidden) const toggleScore = () => setScoreHidden(!isScoreHidden)
const [availableMatchesOnly, setAvailableMatchesOnly] = useLocalStore({
defaultValue: false,
key: MATCH_AVAILABILITY_KEY,
validator: isBoolean,
})
const toggleMatchAvailablity = () => setAvailableMatchesOnly(!availableMatchesOnly)
return { return {
availableMatchesOnly,
isScoreHidden, isScoreHidden,
toggleMatchAvailablity,
toggleScore, toggleScore,
} }
} }

@ -10,6 +10,8 @@ import type { MatchesBySection } from 'requests'
import { useRequest } from 'hooks' import { useRequest } from 'hooks'
import { usePreferencesStore } from 'features/PreferencesPopup'
import { prepareMatches } from './helpers/prepareMatches' import { prepareMatches } from './helpers/prepareMatches'
export type Match = ReturnType<typeof prepareMatches>[number] export type Match = ReturnType<typeof prepareMatches>[number]
@ -29,6 +31,7 @@ const initialState = {
} }
export const useMatches = ({ fetch }: Props) => { export const useMatches = ({ fetch }: Props) => {
const { userPreferences } = usePreferencesStore()
const { const {
isFetching, isFetching,
request: requestMatches, request: requestMatches,
@ -65,7 +68,7 @@ export const useMatches = ({ fetch }: Props) => {
useEffect(() => { useEffect(() => {
fetchMatches(0).then(setMatches) fetchMatches(0).then(setMatches)
pageRef.current = 1 pageRef.current = 1
}, [fetchMatches]) }, [fetchMatches, userPreferences])
const preparedMatches = useMemo(() => ({ const preparedMatches = useMemo(() => ({
broadcast: prepareMatches(matches.broadcast), broadcast: prepareMatches(matches.broadcast),

@ -3,6 +3,8 @@ import { Link, useRouteMatch } from 'react-router-dom'
import { PAGES } from 'config' import { PAGES } from 'config'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
import { usePreferencesStore } from 'features/PreferencesPopup'
import { FavoritesMobilePopup } from '../FavoritesMobilePopup' import { FavoritesMobilePopup } from '../FavoritesMobilePopup'
import { import {
MenuList, MenuList,
@ -11,6 +13,7 @@ import {
} from './styled' } from './styled'
export const Menu = () => { export const Menu = () => {
const { openPopup } = usePreferencesStore()
const isHomePage = useRouteMatch(PAGES.home)?.isExact const isHomePage = useRouteMatch(PAGES.home)?.isExact
return ( return (
@ -18,7 +21,7 @@ export const Menu = () => {
{isMobileDevice && <FavoritesMobilePopup />} {isMobileDevice && <FavoritesMobilePopup />}
{ {
isHomePage && ( isHomePage && (
<MenuItem> <MenuItem onClick={openPopup}>
<Icon src='header-settings' size='1.4rem' /> <Icon src='header-settings' size='1.4rem' />
</MenuItem> </MenuItem>
) )

@ -9,13 +9,10 @@ import { useSportNameParam, usePageId } from 'hooks'
import type { PlayerProfile } from 'requests/getPlayerInfo' import type { PlayerProfile } from 'requests/getPlayerInfo'
import { getPlayerInfo, getPlayerMatches } from 'requests' import { getPlayerInfo, getPlayerMatches } from 'requests'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
export const usePlayerPage = () => { export const usePlayerPage = () => {
const [playerProfile, setPlayerProfile] = useState<PlayerProfile>(null) const [playerProfile, setPlayerProfile] = useState<PlayerProfile>(null)
const { sportType } = useSportNameParam() const { sportType } = useSportNameParam()
const playerId = usePageId() const playerId = usePageId()
const { availableMatchesOnly } = useMatchSwitchesStore()
const { const {
firstname_eng = '', firstname_eng = '',
@ -39,7 +36,6 @@ export const usePlayerPage = () => {
const fetchMatches = useCallback( const fetchMatches = useCallback(
(limit: number, offset: number) => getPlayerMatches({ (limit: number, offset: number) => getPlayerMatches({
availableMatchesOnly,
limit, limit,
offset, offset,
playerId, playerId,
@ -48,7 +44,6 @@ export const usePlayerPage = () => {
[ [
playerId, playerId,
sportType, sportType,
availableMatchesOnly,
], ],
) )

@ -0,0 +1,74 @@
import styled from 'styled-components/macro'
import { ClearButton } from 'features/Search/styled'
import { usePreferencesStore } from 'features/PreferencesPopup/store'
import { useLexicsStore } from 'features/LexicsStore'
const Wrapper = styled.div`
position: relative;
width: 100%;
height: 36px;
margin-right: 5px;
background: #292929;
border-radius: 10px;
display: flex;
align-items: center;
::before {
content: '';
display: block;
position: absolute;
top: 50%;
margin-left: 7px;
transform: translateY(-50%);
width: 20px;
height: 20px;
background-image: url(/images/search.svg);
background-size: 14px;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
`
const Input = styled.input`
border: none;
background: transparent;
outline: none;
position: relative;
width: 100%;
height: 100%;
display: block;
margin-bottom: 2px;
padding-left: 34px;
color: ${({ theme }) => theme.colors.text100};
caret-color: rgba(255, 255, 255, 0.5);
font-weight: normal;
font-size: 0.66rem;
line-height: 1.04rem;
letter-spacing: -0.4px;
::placeholder {
color: rgba(255, 255, 255, 0.5);
}
`
export const Search = () => {
const { translate } = useLexicsStore()
const {
clearQuery,
onQueryChange,
query,
} = usePreferencesStore()
return (
<Wrapper>
<Input
placeholder={translate('search')}
value={query}
onChange={onQueryChange}
/>
{query && <ClearButton onClick={clearQuery} />}
</Wrapper>
)
}

@ -0,0 +1,39 @@
import map from 'lodash/map'
import includes from 'lodash/includes'
import { usePreferencesStore } from 'features/PreferencesPopup/store'
import { T9n } from 'features/T9n'
import { BlockTitle } from '../../styled'
import {
Wrapper,
List,
Item,
Checkbox,
} from './styled'
export const SportsList = () => {
const {
onSportSelect,
selectedSports,
sports,
} = usePreferencesStore()
return (
<Wrapper>
<BlockTitle>
<T9n t='sport_types' />
</BlockTitle>
<List>
{map(sports, (sport) => (
<Item key={sport.id}>
<Checkbox
checked={includes(selectedSports, sport.id)}
labelLexic={sport.lexic}
onChange={() => onSportSelect(sport.id)}
/>
</Item>
))}
</List>
</Wrapper>
)
}

@ -0,0 +1,39 @@
import styled from 'styled-components/macro'
import { devices } from 'config'
import { Checkbox as BaseCheckbox } from 'features/Common/Checkbox'
import { Label } from 'features/Common/Checkbox/styled'
import { CheckboxSvg } from 'features/Common/Checkbox/Icon'
export const Wrapper = styled.div`
min-width: 264px;
display: flex;
flex-direction: column;
@media ${devices.tablet} {
min-width: 13rem;
}
`
export const List = styled.ul`
width: 100%;
margin-top: 6px;
`
export const Item = styled.li`
width: 100%;
display: flex;
padding: 6px 0;
`
export const Checkbox = styled(BaseCheckbox)`
${Label} {
font-weight: normal;
font-size: 0.66rem;
line-height: 1rem;
}
${CheckboxSvg} {
margin-right: 12px;
}
`

@ -0,0 +1,34 @@
import styled from 'styled-components/macro'
import type { Tournament } from 'requests/getSportTournaments'
import {
ItemInfo,
Name,
Flag,
TeamOrCountry,
} from 'features/ItemsList/styled'
import { SportIcon } from 'features/SportIcon'
const Wrapper = styled.div`
width: 100%;
max-width: 300px;
display: flex;
flex-direction: column;
justify-content: center;
`
type Props = {
tournament: Tournament,
}
export const TournamentInfo = ({ tournament }: Props) => (
<Wrapper>
<Name nameObj={tournament} />
<ItemInfo>
<SportIcon sport={tournament.sport} />
<Flag src={`https://instatscout.com/images/flags/48/${tournament.country.id}.png`} />
<TeamOrCountry nameObj={tournament.country} />
</ItemInfo>
</Wrapper>
)

@ -0,0 +1,59 @@
import type { CSSProperties } from 'react'
import type { ListChildComponentProps } from 'react-window'
import styled from 'styled-components/macro'
import floor from 'lodash/floor'
import type { Tournament, Tournaments } from 'requests'
import { TournamentInfo } from '../TournamentInfo'
import { Checkbox } from '../../styled'
const Item = styled.li`
width: 48.5%;
padding: 6.5px 0;
`
const getTwoColumnListStyles = (index: number) => {
const isEven = index % 2 === 0
const rowIndex = floor(index / 2)
const style: CSSProperties = {
position: 'absolute',
top: 48 * rowIndex,
}
if (isEven) {
style.left = 0
} else {
style.right = 0
}
return style
}
export type ItemData = {
isTournamentSelected: (tournament: Tournament) => boolean,
onTournamentSelect: (tournament: Tournament) => void,
tournaments: Tournaments,
}
export const TournamentListItem = ({
data,
index,
}: ListChildComponentProps<ItemData>) => {
const {
isTournamentSelected,
onTournamentSelect,
tournaments,
} = data
const tournament = tournaments[index]
if (!tournament) return null
return (
<Item style={getTwoColumnListStyles(index)}>
<Checkbox
checked={isTournamentSelected(tournament)}
label={<TournamentInfo tournament={tournament} />}
onChange={() => onTournamentSelect(tournament)}
/>
</Item>
)
}

@ -0,0 +1,53 @@
import styled from 'styled-components/macro'
import { T9n } from 'features/T9n'
import { usePreferencesStore } from '../../store'
import { Search } from '../Search'
import { TournamentsList } from '../TournamentsList'
import { BlockTitle, Checkbox } from '../../styled'
const Wrapper = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
padding-left: 14px;
flex-grow: 1;
`
const CheckboxWrapper = styled.div`
margin: 20px 0 22px 2px;
margin-top: 20px;
margin-bottom: 22px;
`
export const TournamentsBlock = () => {
const {
allTournamentsSelected,
isTournamentSelected,
onSelectAllTournaments,
onTournamentSelect,
tournaments,
} = usePreferencesStore()
return (
<Wrapper>
<BlockTitle>
<T9n t='tournament' />
</BlockTitle>
<CheckboxWrapper>
<Checkbox
labelLexic='all'
checked={allTournamentsSelected}
onChange={onSelectAllTournaments}
/>
</CheckboxWrapper>
<Search />
<TournamentsList
isTournamentSelected={isTournamentSelected}
onTournamentSelect={onTournamentSelect}
tournaments={tournaments}
/>
</Wrapper>
)
}

@ -0,0 +1,68 @@
import { memo } from 'react'
import type { ListItemKeySelector } from 'react-window'
import { FixedSizeList } from 'react-window'
import styled from 'styled-components/macro'
import size from 'lodash/size'
import { customScrollbar } from 'features/Common'
import type { ItemData } from '../TournamentListItem'
import { TournamentListItem } from '../TournamentListItem'
const getItemKey: ListItemKeySelector = (index, data) => {
const tournament = data[index]
return tournament
? `${tournament.sport}_${tournament.id}`
: index
}
type Props = ItemData & {
className?: string,
}
const List = memo((props: Props) => {
const { className, tournaments } = props
return (
<FixedSizeList
className={className}
innerElementType='ul'
width='100%'
height={432}
itemSize={24}
itemCount={size(tournaments)}
itemKey={getItemKey}
itemData={props}
>
{TournamentListItem}
</FixedSizeList>
)
})
export const TournamentsList = styled(List)`
width: 100%;
height: 432px;
margin-top: 14px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: flex-start;
overflow-y: auto;
@media (max-width: 1370px) {
height: 20rem !important;
}
${customScrollbar}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
}
`

@ -0,0 +1,61 @@
import {
HeaderActions,
CloseButton,
} from 'features/PopupComponents'
import { T9n } from 'features/T9n'
import { SportsList } from './components/SportList'
import { TournamentsBlock } from './components/TournamentsBlock'
import { usePreferencesStore } from './store'
import {
Modal,
Wrapper,
Header,
HeaderTitle,
Body,
Footer,
ApplyButton,
Loader,
} from './styled'
export * from './store'
export const PreferencesPopup = () => {
const {
closePopup,
isApplyButtonDisabled,
isFetching,
isOpen,
onApplyClick,
} = usePreferencesStore()
return (
<Modal
isOpen={isOpen}
close={closePopup}
withCloseButton={false}
>
{isFetching && <Loader />}
<Wrapper isFetching={isFetching}>
<Header>
<HeaderTitle>
<T9n t='my_preferences' />
</HeaderTitle>
<HeaderActions position='right'>
<CloseButton onClick={closePopup} />
</HeaderActions>
</Header>
<Body>
<SportsList />
<TournamentsBlock />
</Body>
<Footer>
<ApplyButton onClick={onApplyClick} disabled={isApplyButtonDisabled}>
<T9n t='apply' />
</ApplyButton>
</Footer>
</Wrapper>
</Modal>
)
}

@ -0,0 +1,50 @@
import includes from 'lodash/includes'
import toLower from 'lodash/toLower'
import reduce from 'lodash/reduce'
import filter from 'lodash/filter'
import type { SportTypes } from 'config'
import type { TournamentsBySports, Tournaments } from 'requests/getSportTournaments'
const getFlatTournaments = (
tournamentsBySports: TournamentsBySports,
selectedSports: Array<SportTypes>,
) => (
reduce(
selectedSports,
(acc, sport) => {
const sportTournaments = tournamentsBySports[sport]
if (sportTournaments) {
return [...acc, ...sportTournaments]
}
return acc
},
[] as Tournaments,
)
)
type NameKey = 'name_eng' | 'name_rus'
type SearchProps = {
query: string,
selectedSports: Array<SportTypes>,
suffix: string,
tournamentsBySports: TournamentsBySports,
}
export const search = async ({
query,
selectedSports,
suffix,
tournamentsBySports,
}: SearchProps) => {
const tournaments = getFlatTournaments(tournamentsBySports, selectedSports)
const nameKey = `name_${suffix}` as NameKey
return filter(
tournaments,
(tournament) => includes(
toLower(tournament[nameKey]),
toLower(query),
),
)
}

@ -0,0 +1,124 @@
import { useEffect, useCallback } from 'react'
import isBoolean from 'lodash/isBoolean'
import uniq from 'lodash/uniq'
import map from 'lodash/map'
import { useLocalStore, useToggle } from 'hooks'
import { useTournamentsBySports } from './useTournamentsBySports'
import { usePreferencesLoader } from './usePreferencesLoader'
import { useSelectedTournaments } from './useTournaments'
import { useSports } from './useSports'
import { useSearch } from './useSearch'
export const usePreferences = () => {
const [firstLoad, setFirstLoad] = useLocalStore({
// defaultValue: true,
defaultValue: false,
key: 'preferences_first_load',
validator: isBoolean,
})
const {
close,
isOpen,
// open,
} = useToggle()
const {
// fetchInitialSports,
onSportSelect,
selectedSports,
setInitialSelectedSports,
sports,
} = useSports()
const {
isFetching: isFetchingTournaments,
tournamentsBySports,
} = useTournamentsBySports(selectedSports)
const {
clearQuery,
onQueryChange,
query,
tournaments,
} = useSearch(tournamentsBySports, selectedSports)
const {
allTournamentsSelected,
isTournamentSelected,
onSelectAllTournaments,
onTournamentSelect,
selectedTournaments,
setInitialSelectedTournaments,
} = useSelectedTournaments(selectedSports, tournaments)
const {
// fetchInitialPreferences,
isApplyButtonDisabled,
isFetching: isFetchingPrefernces,
isSaving,
onApplyClick,
userPreferences,
} = usePreferencesLoader(selectedTournaments, close)
const reset = useCallback(() => {
if (!userPreferences) return
const initialSelectedSports = uniq(map(
userPreferences,
({ sport }) => sport,
))
setInitialSelectedTournaments(userPreferences)
setInitialSelectedSports(initialSelectedSports)
}, [
userPreferences,
setInitialSelectedTournaments,
setInitialSelectedSports,
])
const openPopup = useCallback(() => {
// fetchInitialPreferences()
// fetchInitialSports()
// open()
}, [/* fetchInitialPreferences, fetchInitialSports, open */])
const closePopup = () => {
reset()
close()
}
useEffect(reset, [reset])
useEffect(() => {
if (firstLoad) {
openPopup()
setFirstLoad(false)
}
}, [
firstLoad,
openPopup,
setFirstLoad,
])
return {
allTournamentsSelected,
clearQuery,
closePopup,
isApplyButtonDisabled,
isFetching: isFetchingPrefernces || isSaving || isFetchingTournaments,
isOpen,
isTournamentSelected,
onApplyClick,
onQueryChange,
onSelectAllTournaments,
onSportSelect,
onTournamentSelect,
openPopup,
query,
selectedSports,
sports,
tournaments,
userPreferences,
}
}

@ -0,0 +1,68 @@
import {
useCallback,
useState,
useMemo,
} from 'react'
import isEmpty from 'lodash/isEmpty'
import isNull from 'lodash/isNull'
import size from 'lodash/size'
import xorWith from 'lodash/xorWith'
import isEqual from 'lodash/isEqual'
import type{ UserPreferences } from 'requests/getUserPreferences'
import { getUserPreferences as getUserPreferencesRequest } from 'requests/getUserPreferences'
import { saveUserPreferences as saveUserPreferencesRequest } from 'requests/saveUserPreferences'
import { useRequest } from 'hooks'
export const usePreferencesLoader = (
selectedTournaments: UserPreferences,
onApply: () => void,
) => {
const [userPreferences, setUserPreferences] = useState<UserPreferences | null>(null)
const {
isFetching,
request: fetchUserPreferences,
} = useRequest(getUserPreferencesRequest)
const {
isFetching: isSaving,
request: saveUserPreferences,
} = useRequest(saveUserPreferencesRequest)
const fetchInitialPreferences = useCallback(() => {
if (isNull(userPreferences)) {
fetchUserPreferences().then(setUserPreferences)
}
}, [fetchUserPreferences, userPreferences])
const onApplyClick = () => {
const data = isEmpty(selectedTournaments) ? null : selectedTournaments
saveUserPreferences(data)
.then(fetchUserPreferences)
.then(setUserPreferences)
.then(onApply)
}
const isApplyButtonDisabled = useMemo(() => {
if (!userPreferences) return true
if (size(userPreferences) !== size(selectedTournaments)) return false
return isEmpty(xorWith(
userPreferences,
selectedTournaments,
isEqual,
))
}, [selectedTournaments, userPreferences])
return {
fetchInitialPreferences,
isApplyButtonDisabled,
isFetching,
isSaving,
onApplyClick,
userPreferences,
}
}

@ -0,0 +1,62 @@
import type { ChangeEvent, MouseEvent } from 'react'
import {
useState,
useEffect,
useMemo,
} from 'react'
import debounce from 'lodash/debounce'
import type { SportTypes } from 'config'
import type { Tournaments, TournamentsBySports } from 'requests/getSportTournaments'
import { useLexicsStore } from 'features/LexicsStore'
import { search } from '../helpers'
type SearchArgs = Parameters<typeof search>[0]
export const useSearch = (
tournamentsBySports: TournamentsBySports,
selectedSports: Array<SportTypes>,
) => {
const { suffix } = useLexicsStore()
const [query, setQuery] = useState('')
const [tournaments, setTournaments] = useState<Tournaments>([])
const onQueryChange = (e: ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value)
}
const clearQuery = (e: MouseEvent) => {
e.stopPropagation()
setQuery('')
}
const debouncedSearch = useMemo(() => debounce((args: SearchArgs) => {
search(args).then(setTournaments)
}, 300), [])
useEffect(() => {
debouncedSearch({
query,
selectedSports,
suffix,
tournamentsBySports,
})
}, [
selectedSports,
query,
tournamentsBySports,
suffix,
debouncedSearch,
])
return {
clearQuery,
onQueryChange,
query,
tournaments,
}
}

@ -0,0 +1,35 @@
import { useCallback, useState } from 'react'
import includes from 'lodash/includes'
import without from 'lodash/without'
import isEmpty from 'lodash/isEmpty'
import type { SportTypes } from 'config'
import type { SportList } from 'requests/getSportList'
import { getSportList } from 'requests/getSportList'
export const useSports = () => {
const [sports, setSports] = useState<SportList>([])
const [selectedSports, setSelectedSports] = useState<Array<SportTypes>>([])
const fetchInitialSports = useCallback(() => {
if (isEmpty(sports)) {
getSportList().then(setSports)
}
}, [sports])
const onSportSelect = (sport: SportTypes) => {
const newSports = includes(selectedSports, sport)
? without(selectedSports, sport)
: [sport, ...selectedSports]
setSelectedSports(newSports)
}
return {
fetchInitialSports,
onSportSelect,
selectedSports,
setInitialSelectedSports: setSelectedSports,
sports,
}
}

@ -0,0 +1,77 @@
import {
useMemo,
useState,
useCallback,
useEffect,
} from 'react'
import includes from 'lodash/includes'
import isEmpty from 'lodash/isEmpty'
import filter from 'lodash/filter'
import remove from 'lodash/remove'
import every from 'lodash/every'
import find from 'lodash/find'
import map from 'lodash/map'
import type { SportTypes } from 'config'
import type { Tournament, Tournaments } from 'requests/getSportTournaments'
import type { UserPreferences } from 'requests/getUserPreferences'
export const useSelectedTournaments = (
selectedSports: Array<SportTypes>,
tournaments: Tournaments,
) => {
const [selectedTournaments, setSelectedTournaments] = useState<UserPreferences>([])
const isTournamentSelected = useCallback(({ id, sport }: Tournament) => (
Boolean(find(
selectedTournaments,
{ sport, tournament_id: id },
))
), [selectedTournaments])
const onTournamentSelect = useCallback((tournament: Tournament) => {
const preference = { sport: tournament.sport, tournament_id: tournament.id }
if (isTournamentSelected(tournament)) {
remove(selectedTournaments, preference)
setSelectedTournaments([...selectedTournaments])
} else {
setSelectedTournaments([...selectedTournaments, preference])
}
}, [isTournamentSelected, selectedTournaments])
const allTournamentsSelected = useMemo(
() => {
if (isEmpty(tournaments)) return false
return every(tournaments, isTournamentSelected)
},
[isTournamentSelected, tournaments],
)
const onSelectAllTournaments = () => {
const newSelectedTournaments = allTournamentsSelected
? []
: map(
tournaments,
({ id, sport }) => ({ sport, tournament_id: id }),
)
setSelectedTournaments(newSelectedTournaments)
}
useEffect(() => {
setSelectedTournaments((oldTournaments) => (
filter(oldTournaments, ({ sport }) => includes(selectedSports, sport))
))
}, [selectedSports])
return {
allTournamentsSelected,
isTournamentSelected,
onSelectAllTournaments,
onTournamentSelect,
selectedTournaments,
setInitialSelectedTournaments: setSelectedTournaments,
}
}

@ -0,0 +1,37 @@
import { useState, useEffect } from 'react'
import isEmpty from 'lodash/isEmpty'
import filter from 'lodash/filter'
import type { SportTypes } from 'config'
import type { TournamentsBySports } from 'requests/getSportTournaments'
import { getTournamentsBySports } from 'requests/getSportTournaments'
import { useRequest } from 'hooks'
export const useTournamentsBySports = (selectedSports: Array<SportTypes>) => {
const [tournamentsBySports, setTournamentsBySports] = useState<TournamentsBySports>({})
const {
isFetching,
request: fetchTournaments,
} = useRequest(getTournamentsBySports)
useEffect(() => {
const missingTournaments = filter(selectedSports, (sport) => !tournamentsBySports[sport])
if (!isEmpty(missingTournaments)) {
fetchTournaments(missingTournaments).then((newTournaments) => {
setTournamentsBySports({
...tournamentsBySports,
...newTournaments,
})
})
}
}, [selectedSports, tournamentsBySports, fetchTournaments])
return {
isFetching,
tournamentsBySports,
}
}

@ -0,0 +1,20 @@
import type { ReactNode } from 'react'
import { createContext, useContext } from 'react'
import { usePreferences } from './hooks'
type Context = ReturnType<typeof usePreferences>
type Props = { children: ReactNode }
const PreferencesPopupContext = createContext({} as Context)
export const PreferencesPopupStore = ({ children }: Props) => {
const value = usePreferences()
return (
<PreferencesPopupContext.Provider value={value}>
{children}
</PreferencesPopupContext.Provider>
)
}
export const usePreferencesStore = () => useContext(PreferencesPopupContext)

@ -0,0 +1,106 @@
import styled, { css } from 'styled-components/macro'
import { ModalWindow } from 'features/Modal/styled'
import { Modal as BaseModal } from 'features/Modal'
import { Header as BaseHeader } from 'features/PopupComponents'
import { ButtonSolid } from 'features/Common'
import { Checkbox as BaseCheckbox } from 'features/Common/Checkbox'
import { Label } from 'features/Common/Checkbox/styled'
import { CheckboxSvg } from 'features/Common/Checkbox/Icon'
import { ArrowLoader } from 'features/ArrowLoader'
export const Modal = styled(BaseModal)`
background-color: rgba(0, 0, 0, 0.7);
padding: 0 60px;
${ModalWindow} {
width: 1066px;
height: 781px;
padding: 0;
background-color: #333333;
border-radius: 5px;
@media (max-width: 1370px) {
width: 70rem;
height: auto;
}
}
`
type WrapperProps = {
isFetching?: boolean,
}
export const Wrapper = styled.div<WrapperProps>`
${({ isFetching }) => (
isFetching
? css`pointer-events: none;`
: ''
)}
`
export const Header = styled(BaseHeader)`
height: auto;
padding-top: 40px;
justify-content: center;
`
export const HeaderTitle = styled.span`
font-weight: 600;
font-size: 1.13rem;
line-height: 1.13rem;
color: #FFFFFF;
`
export const BlockTitle = styled.span`
font-weight: 600;
font-size: 0.56rem;
line-height: 0.85rem;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.5);
`
export const Body = styled.div`
padding: 25px 40px 0 42px;
display: flex;
`
export const Footer = styled.div`
width: 100%;
display: flex;
justify-content: center;
padding: 1.89rem;
`
export const Checkbox = styled(BaseCheckbox)`
${Label} {
font-weight: 600;
font-size: 0.66rem;
line-height: 0.94rem;
letter-spacing: 0.1px;
}
${CheckboxSvg} {
align-self: auto;
margin-right: 18px;
max-width: 20px;
max-height: 20px;
}
`
export const ApplyButton = styled(ButtonSolid)`
width: 270px;
border-radius: 5px;
`
export const Loader = styled(ArrowLoader)`
position: absolute;
z-index: 10;
background-color: #3f3f3f60;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
`

@ -20,6 +20,7 @@ export const StripeElements = ({ children }: Props) => {
<Elements <Elements
key={lang} key={lang}
stripe={stripe} stripe={stripe}
// @ts-expect-error
options={{ locale: lang }} options={{ locale: lang }}
> >
{children} {children}

@ -9,13 +9,10 @@ import { getTeamInfo, getTeamMatches } from 'requests'
import { useSportNameParam, usePageId } from 'hooks' import { useSportNameParam, usePageId } from 'hooks'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
export const useTeamPage = () => { export const useTeamPage = () => {
const [teamProfile, setTeamProfile] = useState<TeamInfo>(null) const [teamProfile, setTeamProfile] = useState<TeamInfo>(null)
const { sportType } = useSportNameParam() const { sportType } = useSportNameParam()
const teamId = usePageId() const teamId = usePageId()
const { availableMatchesOnly } = useMatchSwitchesStore()
useEffect(() => { useEffect(() => {
getTeamInfo(sportType, teamId) getTeamInfo(sportType, teamId)
@ -28,7 +25,6 @@ export const useTeamPage = () => {
const fetchMatches = useCallback( const fetchMatches = useCallback(
(limit: number, offset: number) => getTeamMatches({ (limit: number, offset: number) => getTeamMatches({
availableMatchesOnly,
limit, limit,
offset, offset,
sportType, sportType,
@ -37,7 +33,6 @@ export const useTeamPage = () => {
[ [
teamId, teamId,
sportType, sportType,
availableMatchesOnly,
], ],
) )

@ -10,14 +10,12 @@ import { getTournamentInfo, getTournamentMatches } from 'requests'
import { useSportNameParam, usePageId } from 'hooks' import { useSportNameParam, usePageId } from 'hooks'
import { useName } from 'features/Name' import { useName } from 'features/Name'
import { useMatchSwitchesStore } from 'features/MatchSwitches'
export const useTournamentPage = () => { export const useTournamentPage = () => {
const [tournamentProfile, setTournamentProfile] = useState<TournamentInfo>(null) const [tournamentProfile, setTournamentProfile] = useState<TournamentInfo>(null)
const { sportType } = useSportNameParam() const { sportType } = useSportNameParam()
const tournamentId = usePageId() const tournamentId = usePageId()
const country = useName(tournamentProfile?.country || {}) const country = useName(tournamentProfile?.country || {})
const { availableMatchesOnly } = useMatchSwitchesStore()
useEffect(() => { useEffect(() => {
getTournamentInfo(sportType, tournamentId) getTournamentInfo(sportType, tournamentId)
@ -30,7 +28,6 @@ export const useTournamentPage = () => {
const fetchMatches = useCallback( const fetchMatches = useCallback(
(limit: number, offset: number) => getTournamentMatches({ (limit: number, offset: number) => getTournamentMatches({
availableMatchesOnly,
limit, limit,
offset, offset,
sportType, sportType,
@ -39,7 +36,6 @@ export const useTournamentPage = () => {
[ [
tournamentId, tournamentId,
sportType, sportType,
availableMatchesOnly,
], ],
) )

@ -1,9 +1,7 @@
import { PROCEDURES, SportTypes } from 'config' import { PROCEDURES } from 'config'
import { client } from 'config/clients' import { client } from 'config/clients'
import type { MatchStatuses } from 'features/HeaderFilters'
import type { MatchesBySection } from './types' import type { MatchesBySection } from './types'
import { requestMatches } from './request' import { requestMatches } from './request'
import { getMatchesPreviews } from './getPreviews' import { getMatchesPreviews } from './getPreviews'
@ -11,23 +9,15 @@ import { getMatchesPreviews } from './getPreviews'
const proc = PROCEDURES.get_matches const proc = PROCEDURES.get_matches
type Args = { type Args = {
availableMatchesOnly: boolean,
date: string, date: string,
limit: number, limit: number,
matchStatus: MatchStatuses | null,
offset: number, offset: number,
sportType: SportTypes | null,
tournamentId: number | null,
} }
export const getHomeMatches = async ({ export const getHomeMatches = async ({
availableMatchesOnly,
date, date,
limit, limit,
matchStatus,
offset, offset,
sportType,
tournamentId,
}: Args): Promise<MatchesBySection> => { }: Args): Promise<MatchesBySection> => {
const config = { const config = {
body: { body: {
@ -35,10 +25,6 @@ export const getHomeMatches = async ({
_p_date: date, _p_date: date,
_p_limit: limit, _p_limit: limit,
_p_offset: offset, _p_offset: offset,
_p_sport: sportType,
_p_stream_status: matchStatus,
_p_sub_only: availableMatchesOnly,
_p_tournament_id: tournamentId,
...client.requests?.[proc], ...client.requests?.[proc],
}, },
proc, proc,

@ -11,7 +11,6 @@ import { getMatchesPreviews } from './getPreviews'
const proc = PROCEDURES.get_player_matches const proc = PROCEDURES.get_player_matches
type Args = { type Args = {
availableMatchesOnly: boolean,
limit: number, limit: number,
offset: number, offset: number,
playerId: number, playerId: number,
@ -19,7 +18,6 @@ type Args = {
} }
export const getPlayerMatches = async ({ export const getPlayerMatches = async ({
availableMatchesOnly,
limit, limit,
offset, offset,
playerId, playerId,
@ -32,7 +30,6 @@ export const getPlayerMatches = async ({
_p_offset: offset, _p_offset: offset,
_p_player_id: playerId, _p_player_id: playerId,
_p_sport: sportType, _p_sport: sportType,
_p_sub_only: availableMatchesOnly,
...client.requests?.[proc], ...client.requests?.[proc],
}, },
proc, proc,

@ -11,7 +11,6 @@ import { requestMatches } from './request'
const proc = PROCEDURES.get_team_matches const proc = PROCEDURES.get_team_matches
type Args = { type Args = {
availableMatchesOnly: boolean,
limit: number, limit: number,
offset: number, offset: number,
sportType: SportTypes, sportType: SportTypes,
@ -19,7 +18,6 @@ type Args = {
} }
export const getTeamMatches = async ({ export const getTeamMatches = async ({
availableMatchesOnly,
limit, limit,
offset, offset,
sportType, sportType,
@ -31,7 +29,6 @@ export const getTeamMatches = async ({
_p_limit: limit, _p_limit: limit,
_p_offset: offset, _p_offset: offset,
_p_sport: sportType, _p_sport: sportType,
_p_sub_only: availableMatchesOnly,
_p_team_id: teamId, _p_team_id: teamId,
...client.requests?.[proc], ...client.requests?.[proc],
}, },

@ -11,7 +11,6 @@ import { requestMatches } from './request'
const proc = PROCEDURES.get_tournament_matches const proc = PROCEDURES.get_tournament_matches
type Args = { type Args = {
availableMatchesOnly: boolean,
limit: number, limit: number,
offset: number, offset: number,
sportType: SportTypes, sportType: SportTypes,
@ -19,7 +18,6 @@ type Args = {
} }
export const getTournamentMatches = async ({ export const getTournamentMatches = async ({
availableMatchesOnly,
limit, limit,
offset, offset,
sportType, sportType,
@ -31,7 +29,6 @@ export const getTournamentMatches = async ({
_p_limit: limit, _p_limit: limit,
_p_offset: offset, _p_offset: offset,
_p_sport: sportType, _p_sport: sportType,
_p_sub_only: availableMatchesOnly,
_p_tournament_id: tournamentId, _p_tournament_id: tournamentId,
...client.requests?.[proc], ...client.requests?.[proc],
}, },

@ -1,3 +1,6 @@
import reduce from 'lodash/reduce'
import map from 'lodash/map'
import { import {
DATA_URL, DATA_URL,
PROCEDURES, PROCEDURES,
@ -27,17 +30,10 @@ export type Tournament = {
export type Tournaments = Array<Tournament> export type Tournaments = Array<Tournament>
const LIMIT = 20 const getSportTournaments = (sportId: SportTypes): Promise<Tournaments> => {
export const getSportTournaments = (
sportId: SportTypes | null,
page: number = 0,
): Promise<Tournaments> => {
const config = { const config = {
body: { body: {
params: { params: {
_p_limit: LIMIT,
_p_offset: page * LIMIT,
_p_sport: sportId, _p_sport: sportId,
}, },
proc, proc,
@ -49,3 +45,19 @@ export const getSportTournaments = (
url: DATA_URL, url: DATA_URL,
}) })
} }
export type TournamentsBySports = Partial<Record<SportTypes, Tournaments>>
export const getTournamentsBySports = async (sportIds: Array<SportTypes>) => {
const responses = await Promise.all(map(sportIds, getSportTournaments))
const tournamentsBySports = reduce(
responses,
(acc, curr) => {
const { sport } = curr[0]
acc[sport] = curr
return acc
},
{} as TournamentsBySports,
)
return tournamentsBySports
}

@ -0,0 +1,29 @@
import {
DATA_URL,
PROCEDURES,
SportTypes,
} from 'config'
import { callApi } from 'helpers'
const proc = PROCEDURES.get_user_preferences
export type UserPreferences = Array<{
sport: SportTypes,
tournament_id: number,
}>
export const getUserPreferences = async () => {
const config = {
body: {
params: {},
proc,
},
}
const response: UserPreferences | null = await callApi({
config,
url: DATA_URL,
})
return response || []
}

@ -0,0 +1,25 @@
import {
DATA_URL,
PROCEDURES,
} from 'config'
import { callApi } from 'helpers'
import type { UserPreferences } from './getUserPreferences'
const proc = PROCEDURES.save_user_preferences
export const saveUserPreferences = (data: UserPreferences | null) => {
const config = {
body: {
params: {
_p_data: data,
},
proc,
},
}
return callApi({
config,
url: DATA_URL,
})
}
Loading…
Cancel
Save