From 92599ec976ca3202d56c7202dbfc90407f43a026 Mon Sep 17 00:00:00 2001 From: Mirlan Date: Tue, 21 Sep 2021 18:23:11 +0600 Subject: [PATCH] Preprod (#495) * 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> --- package.json | 2 + src/config/languages.tsx | 35 +++++ src/config/lexics/indexLexics.tsx | 9 ++ src/config/procedures.tsx | 2 + src/features/App/AuthenticatedApp.tsx | 60 +++++---- src/features/ArrowLoader/index.tsx | 2 + src/features/ArrowLoader/types.tsx | 1 + src/features/Common/Checkbox/Icon.tsx | 8 -- src/features/Common/Checkbox/index.tsx | 4 +- .../isValidMatchStatus/__tests__/index.tsx | 15 --- .../helpers/isValidMatchStatus/index.tsx | 9 -- .../isValidSportType/__tests__/index.tsx | 15 --- .../store/helpers/isValidSportType/index.tsx | 9 -- .../HeaderFilters/store/hooks/index.tsx | 72 +--------- src/features/HomePage/hooks.tsx | 73 +---------- src/features/ItemsList/styled.tsx | 1 + .../isSupportedLang/__tests__/index.tsx | 13 +- .../MatchCard/CardFrontside/index.tsx | 3 - src/features/MatchSwitches/hooks.tsx | 10 -- src/features/Matches/hooks.tsx | 5 +- src/features/Menu/index.tsx | 5 +- src/features/PlayerPage/hooks.tsx | 5 - .../components/Search/index.tsx | 74 +++++++++++ .../components/SportList/index.tsx | 39 ++++++ .../components/SportList/styled.tsx | 39 ++++++ .../components/TournamentInfo/index.tsx | 34 +++++ .../components/TournamentListItem/index.tsx | 59 +++++++++ .../components/TournamentsBlock/index.tsx | 53 ++++++++ .../components/TournamentsList/index.tsx | 68 ++++++++++ src/features/PreferencesPopup/index.tsx | 61 +++++++++ .../PreferencesPopup/store/helpers.tsx | 50 +++++++ .../PreferencesPopup/store/hooks/index.tsx | 124 ++++++++++++++++++ .../store/hooks/usePreferencesLoader.tsx | 68 ++++++++++ .../store/hooks/useSearch.tsx | 62 +++++++++ .../store/hooks/useSports.tsx | 35 +++++ .../store/hooks/useTournaments.tsx | 77 +++++++++++ .../store/hooks/useTournamentsBySports.tsx | 37 ++++++ src/features/PreferencesPopup/store/index.tsx | 20 +++ src/features/PreferencesPopup/styled.tsx | 106 +++++++++++++++ src/features/StripeElements/index.tsx | 1 + src/features/TeamPage/hooks.tsx | 5 - src/features/TournamentPage/hooks.tsx | 4 - src/requests/getMatches/getHomeMatches.tsx | 16 +-- src/requests/getMatches/getPlayerMatches.tsx | 3 - src/requests/getMatches/getTeamMatches.tsx | 3 - .../getMatches/getTournamentMatches.tsx | 3 - src/requests/getSportTournaments.tsx | 28 ++-- src/requests/getUserPreferences.tsx | 29 ++++ src/requests/saveUserPreferences.tsx | 25 ++++ 49 files changed, 1192 insertions(+), 289 deletions(-) delete mode 100644 src/features/HeaderFilters/store/helpers/isValidMatchStatus/__tests__/index.tsx delete mode 100644 src/features/HeaderFilters/store/helpers/isValidMatchStatus/index.tsx delete mode 100644 src/features/HeaderFilters/store/helpers/isValidSportType/__tests__/index.tsx delete mode 100644 src/features/HeaderFilters/store/helpers/isValidSportType/index.tsx create mode 100644 src/features/PreferencesPopup/components/Search/index.tsx create mode 100644 src/features/PreferencesPopup/components/SportList/index.tsx create mode 100644 src/features/PreferencesPopup/components/SportList/styled.tsx create mode 100644 src/features/PreferencesPopup/components/TournamentInfo/index.tsx create mode 100644 src/features/PreferencesPopup/components/TournamentListItem/index.tsx create mode 100644 src/features/PreferencesPopup/components/TournamentsBlock/index.tsx create mode 100644 src/features/PreferencesPopup/components/TournamentsList/index.tsx create mode 100644 src/features/PreferencesPopup/index.tsx create mode 100644 src/features/PreferencesPopup/store/helpers.tsx create mode 100644 src/features/PreferencesPopup/store/hooks/index.tsx create mode 100644 src/features/PreferencesPopup/store/hooks/usePreferencesLoader.tsx create mode 100644 src/features/PreferencesPopup/store/hooks/useSearch.tsx create mode 100644 src/features/PreferencesPopup/store/hooks/useSports.tsx create mode 100644 src/features/PreferencesPopup/store/hooks/useTournaments.tsx create mode 100644 src/features/PreferencesPopup/store/hooks/useTournamentsBySports.tsx create mode 100644 src/features/PreferencesPopup/store/index.tsx create mode 100644 src/features/PreferencesPopup/styled.tsx create mode 100644 src/requests/getUserPreferences.tsx create mode 100644 src/requests/saveUserPreferences.tsx diff --git a/package.json b/package.json index 2be163a4..40b612b3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-scripts": "^4.0.1", + "react-window": "^1.8.6", "screenfull": "^5.0.2", "styled-components": "^5.1.1" }, @@ -55,6 +56,7 @@ "@types/react-responsive": "^8.0.2", "@types/react-router": "^5.1.8", "@types/react-router-dom": "^5.1.6", + "@types/react-window": "^1.8.5", "@types/styled-components": "^5.1.0", "commitizen": "^4.1.2", "eslint": "^7.14.0", diff --git a/src/config/languages.tsx b/src/config/languages.tsx index 8cbb20f1..d5c8f7f4 100644 --- a/src/config/languages.tsx +++ b/src/config/languages.tsx @@ -14,6 +14,41 @@ export const langsList = [ locale: 'cs', 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 export type Languages = typeof langsList[number]['locale'] diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx index 5d68abe2..45a3b65f 100644 --- a/src/config/lexics/indexLexics.tsx +++ b/src/config/lexics/indexLexics.tsx @@ -59,6 +59,14 @@ const buyMatchPopupLexics = { success_subscription: 14097, } +const preferencesPopupLexics = { + all: 15449, + apply: 15117, + my_preferences: 15087, + search: 15127, + sport_types: 15088, +} + export const indexLexics = { add_to_favorites: 14967, add_to_favorites_error: 12943, @@ -107,6 +115,7 @@ export const indexLexics = { watch_from_last_pause: 13022, watch_now: 13020, + ...preferencesPopupLexics, ...proceduresLexics, ...matchPopupLexics, ...buyMatchPopupLexics, diff --git a/src/config/procedures.tsx b/src/config/procedures.tsx index f6e2c165..40563ec1 100644 --- a/src/config/procedures.tsx +++ b/src/config/procedures.tsx @@ -16,6 +16,7 @@ export const PROCEDURES = { get_user_favorites: 'get_user_favorites', get_user_info: 'get_user_info', get_user_match_second: 'get_user_match_second', + get_user_preferences: 'get_user_preferences', get_user_subscriptions: 'get_user_subscriptions', landing_get_match_info: 'landing_get_match_info', lst_c_country: 'lst_c_country', @@ -29,5 +30,6 @@ export const PROCEDURES = { save_user_info: 'save_user_info', save_user_match_second: 'save_user_match_second', save_user_page: 'save_user_page', + save_user_preferences: 'save_user_preferences', save_user_subscription: 'save_user_subscription', } diff --git a/src/features/App/AuthenticatedApp.tsx b/src/features/App/AuthenticatedApp.tsx index c228c7ae..e861c774 100644 --- a/src/features/App/AuthenticatedApp.tsx +++ b/src/features/App/AuthenticatedApp.tsx @@ -18,6 +18,7 @@ import { MatchSwitchesStore } from 'features/MatchSwitches' import { UserFavoritesStore } from 'features/UserFavorites/store' import { MatchPopup, MatchPopupStore } from 'features/MatchPopup' import { BuyMatchPopup, BuyMatchPopupStore } from 'features/BuyMatchPopup' +import { PreferencesPopup, PreferencesPopupStore } from 'features/PreferencesPopup' import { CardsStore } from 'features/CardsStore' const HomePage = lazy(() => import('features/HomePage')) @@ -36,35 +37,38 @@ export const AuthenticatedApp = () => { - - - - + + + + + + - {/* в Switch как прямой children можно рендерить только Route или Redirect */} - - - - - - - - - - - - - - - - - - - - - - - + {/* в Switch как прямой children можно рендерить только Route или Redirect */} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/features/ArrowLoader/index.tsx b/src/features/ArrowLoader/index.tsx index 8e432a21..f3e4ed31 100644 --- a/src/features/ArrowLoader/index.tsx +++ b/src/features/ArrowLoader/index.tsx @@ -4,10 +4,12 @@ import { Wrapper, Arrows } from './styled' export const ArrowLoader = ({ backgroundColor, backgroundSize, + className, disabled, width, }: ArrowLoaderProps) => ( ` fill: ${({ checked }) => (checked ? '#ffffff' : '#B8C1CC')}; - - @media ${devices.mobile} { - fill: ${({ checked }) => (checked ? '#294FC4' : '#B8C1CC')} - } ` export const CheckboxSvg = styled.svg` - min-width: 24px; - min-height: 24px; margin-right: 22px; align-self: flex-start; diff --git a/src/features/Common/Checkbox/index.tsx b/src/features/Common/Checkbox/index.tsx index 9b41bd03..644c9b30 100644 --- a/src/features/Common/Checkbox/index.tsx +++ b/src/features/Common/Checkbox/index.tsx @@ -1,4 +1,4 @@ -import { InputHTMLAttributes } from 'react' +import type { ReactNode, InputHTMLAttributes } from 'react' import type { LexicsId } from 'features/LexicsStore/types' import { T9n } from 'features/T9n' @@ -18,7 +18,7 @@ type Props = Pick, ( | 'onClick' | 'onChange' )> & { - label?: string, + label?: ReactNode, labelLexic?: LexicsId, } diff --git a/src/features/HeaderFilters/store/helpers/isValidMatchStatus/__tests__/index.tsx b/src/features/HeaderFilters/store/helpers/isValidMatchStatus/__tests__/index.tsx deleted file mode 100644 index de53ba35..00000000 --- a/src/features/HeaderFilters/store/helpers/isValidMatchStatus/__tests__/index.tsx +++ /dev/null @@ -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) -}) diff --git a/src/features/HeaderFilters/store/helpers/isValidMatchStatus/index.tsx b/src/features/HeaderFilters/store/helpers/isValidMatchStatus/index.tsx deleted file mode 100644 index 3a11cb65..00000000 --- a/src/features/HeaderFilters/store/helpers/isValidMatchStatus/index.tsx +++ /dev/null @@ -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) -) diff --git a/src/features/HeaderFilters/store/helpers/isValidSportType/__tests__/index.tsx b/src/features/HeaderFilters/store/helpers/isValidSportType/__tests__/index.tsx deleted file mode 100644 index e1e99439..00000000 --- a/src/features/HeaderFilters/store/helpers/isValidSportType/__tests__/index.tsx +++ /dev/null @@ -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) -}) diff --git a/src/features/HeaderFilters/store/helpers/isValidSportType/index.tsx b/src/features/HeaderFilters/store/helpers/isValidSportType/index.tsx deleted file mode 100644 index 876cd9e9..00000000 --- a/src/features/HeaderFilters/store/helpers/isValidSportType/index.tsx +++ /dev/null @@ -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) -) diff --git a/src/features/HeaderFilters/store/hooks/index.tsx b/src/features/HeaderFilters/store/hooks/index.tsx index 15b6ec71..4139afd5 100644 --- a/src/features/HeaderFilters/store/hooks/index.tsx +++ b/src/features/HeaderFilters/store/hooks/index.tsx @@ -1,28 +1,17 @@ import { useMemo, - useState, useEffect, useCallback, } from 'react' import { useLocation } from 'react-router-dom' import { isToday } from 'date-fns' -import { SportTypes } from 'config' - import { useQueryParamStore } from 'hooks' import { filterKeys } from '../config' import { getMoscowDate } from '../helpers/getMoscowDate' import { serializeDate } from '../helpers/dateSerializers' 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 = () => { const { search } = useLocation() @@ -36,88 +25,29 @@ export const useFilters = () => { const isTodaySelected = isToday(selectedDate) - const [ - selectedMatchStatus, - setMatchStatus, - ] = useQueryParamStore({ - 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({ - clearOnUnmount: true, - defaultValue: null, - key: filterKeys.SPORT_TYPE, - validator: isValidSportType, - }) - - const [ - selectedTournamentId, - setSelectedTournamentId, - ] = useState(null) - const resetFilters = useCallback(() => { setSelectedDate(new Date()) - setMatchStatus(null) - setSelectedSportTypeId(null) - setSelectedTournamentId(null) - }, [ - setSelectedDate, - setMatchStatus, - setSelectedSportTypeId, - ]) + }, [setSelectedDate]) useEffect(() => { if (!search) { resetFilters() } - if (!isTodaySelected) { - setMatchStatus(null) - } }, [ isTodaySelected, resetFilters, search, - setMatchStatus, ]) const store = useMemo(() => ({ isTodaySelected, selectedDate, selectedDateFormatted: getMoscowDate(selectedDate), - selectedMatchStatus, - selectedSportTypeId, - selectedTournamentId, setSelectedDate, - setSelectedMatchStatus, - setSelectedSportTypeId, - setSelectedTournamentId, }), [ isTodaySelected, selectedDate, - selectedMatchStatus, - selectedSportTypeId, - selectedTournamentId, setSelectedDate, - setSelectedMatchStatus, - setSelectedSportTypeId, - setSelectedTournamentId, ]) return store diff --git a/src/features/HomePage/hooks.tsx b/src/features/HomePage/hooks.tsx index 751d4325..861dce73 100644 --- a/src/features/HomePage/hooks.tsx +++ b/src/features/HomePage/hooks.tsx @@ -1,82 +1,19 @@ import { useCallback } from 'react' -import filter from 'lodash/filter' -import isPast from 'date-fns/isPast' -import differenceInMinutes from 'date-fns/differenceInMinutes' +import { getHomeMatches } from 'requests' -import { - 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), - } -} +import { useHeaderFiltersStore } from 'features/HeaderFilters' export const useHomePage = () => { - const { - selectedDateFormatted, - selectedMatchStatus, - selectedSportTypeId, - selectedTournamentId, - } = useHeaderFiltersStore() - const { availableMatchesOnly } = useMatchSwitchesStore() + const { selectedDateFormatted } = useHeaderFiltersStore() const fetchMatches = useCallback( (limit: number, offset: number) => getHomeMatches({ - availableMatchesOnly, date: selectedDateFormatted, limit, - matchStatus: null, offset, - sportType: selectedSportTypeId, - tournamentId: selectedTournamentId, - }) - .then((matches) => setMatches(matches, selectedMatchStatus)), - [ - selectedDateFormatted, - selectedMatchStatus, - selectedSportTypeId, - selectedTournamentId, - availableMatchesOnly, - ], + }), + [selectedDateFormatted], ) return { fetchMatches } } diff --git a/src/features/ItemsList/styled.tsx b/src/features/ItemsList/styled.tsx index 9a114234..581c181f 100644 --- a/src/features/ItemsList/styled.tsx +++ b/src/features/ItemsList/styled.tsx @@ -87,6 +87,7 @@ export const Flag = styled.img` ` export const TeamOrCountry = styled(NameBase)` + font-weight: normal; font-size: 0.49rem; line-height: 0.75rem; color: rgba(255, 255, 255, 0.7); diff --git a/src/features/LexicsStore/helpers/isSupportedLang/__tests__/index.tsx b/src/features/LexicsStore/helpers/isSupportedLang/__tests__/index.tsx index 9ccb22df..2e915260 100644 --- a/src/features/LexicsStore/helpers/isSupportedLang/__tests__/index.tsx +++ b/src/features/LexicsStore/helpers/isSupportedLang/__tests__/index.tsx @@ -3,11 +3,18 @@ import { isSupportedLang } from '..' it('returns true for supported languages', () => { expect(isSupportedLang('ru')).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', () => { - expect(isSupportedLang('es')).toBe(false) expect(isSupportedLang('ja')).toBe(false) - expect(isSupportedLang('fr')).toBe(false) - expect(isSupportedLang('de')).toBe(false) + expect(isSupportedLang('ro')).toBe(false) + expect(isSupportedLang('tr')).toBe(false) }) diff --git a/src/features/MatchCard/CardFrontside/index.tsx b/src/features/MatchCard/CardFrontside/index.tsx index 63b700e5..3da95a48 100644 --- a/src/features/MatchCard/CardFrontside/index.tsx +++ b/src/features/MatchCard/CardFrontside/index.tsx @@ -38,7 +38,6 @@ import { NameSignWrapper, HoverFrame, } from '../styled' -import { MatchStatuses, useHeaderFiltersStore } from '../../HeaderFilters' type Props = { match: Match, @@ -70,12 +69,10 @@ export const CardFrontside = ({ const tournamentName = useName(tournament) const { isInFavorites } = useUserFavoritesStore() const { isScoreHidden } = useMatchSwitchesStore() - const { selectedMatchStatus } = useHeaderFiltersStore() const isInFuture = getUnixTime(date) > getUnixTime(new Date()) const showScore = !( !calc || isInFuture - || selectedMatchStatus === MatchStatuses.Soon || isScoreHidden ) || (live && !isScoreHidden) const tournamentInFavorites = isInFavorites(ProfileTypes.TOURNAMENTS, tournament.id) diff --git a/src/features/MatchSwitches/hooks.tsx b/src/features/MatchSwitches/hooks.tsx index e2f20b98..7cba65e1 100644 --- a/src/features/MatchSwitches/hooks.tsx +++ b/src/features/MatchSwitches/hooks.tsx @@ -3,7 +3,6 @@ import isBoolean from 'lodash/isBoolean' import { useLocalStore } from 'hooks' const SCORE_KEY = 'score_hidden' -const MATCH_AVAILABILITY_KEY = 'available_matches_only' export const useMatchSwitches = () => { const [isScoreHidden, setScoreHidden] = useLocalStore({ @@ -13,17 +12,8 @@ export const useMatchSwitches = () => { }) const toggleScore = () => setScoreHidden(!isScoreHidden) - const [availableMatchesOnly, setAvailableMatchesOnly] = useLocalStore({ - defaultValue: false, - key: MATCH_AVAILABILITY_KEY, - validator: isBoolean, - }) - const toggleMatchAvailablity = () => setAvailableMatchesOnly(!availableMatchesOnly) - return { - availableMatchesOnly, isScoreHidden, - toggleMatchAvailablity, toggleScore, } } diff --git a/src/features/Matches/hooks.tsx b/src/features/Matches/hooks.tsx index 5dcc14e0..c5c8f376 100644 --- a/src/features/Matches/hooks.tsx +++ b/src/features/Matches/hooks.tsx @@ -10,6 +10,8 @@ import type { MatchesBySection } from 'requests' import { useRequest } from 'hooks' +import { usePreferencesStore } from 'features/PreferencesPopup' + import { prepareMatches } from './helpers/prepareMatches' export type Match = ReturnType[number] @@ -29,6 +31,7 @@ const initialState = { } export const useMatches = ({ fetch }: Props) => { + const { userPreferences } = usePreferencesStore() const { isFetching, request: requestMatches, @@ -65,7 +68,7 @@ export const useMatches = ({ fetch }: Props) => { useEffect(() => { fetchMatches(0).then(setMatches) pageRef.current = 1 - }, [fetchMatches]) + }, [fetchMatches, userPreferences]) const preparedMatches = useMemo(() => ({ broadcast: prepareMatches(matches.broadcast), diff --git a/src/features/Menu/index.tsx b/src/features/Menu/index.tsx index 068e61f6..babb40ac 100644 --- a/src/features/Menu/index.tsx +++ b/src/features/Menu/index.tsx @@ -3,6 +3,8 @@ import { Link, useRouteMatch } from 'react-router-dom' import { PAGES } from 'config' import { isMobileDevice } from 'config/userAgent' +import { usePreferencesStore } from 'features/PreferencesPopup' + import { FavoritesMobilePopup } from '../FavoritesMobilePopup' import { MenuList, @@ -11,6 +13,7 @@ import { } from './styled' export const Menu = () => { + const { openPopup } = usePreferencesStore() const isHomePage = useRouteMatch(PAGES.home)?.isExact return ( @@ -18,7 +21,7 @@ export const Menu = () => { {isMobileDevice && } { isHomePage && ( - + ) diff --git a/src/features/PlayerPage/hooks.tsx b/src/features/PlayerPage/hooks.tsx index b32be1b3..aa6957ef 100644 --- a/src/features/PlayerPage/hooks.tsx +++ b/src/features/PlayerPage/hooks.tsx @@ -9,13 +9,10 @@ import { useSportNameParam, usePageId } from 'hooks' import type { PlayerProfile } from 'requests/getPlayerInfo' import { getPlayerInfo, getPlayerMatches } from 'requests' -import { useMatchSwitchesStore } from 'features/MatchSwitches' - export const usePlayerPage = () => { const [playerProfile, setPlayerProfile] = useState(null) const { sportType } = useSportNameParam() const playerId = usePageId() - const { availableMatchesOnly } = useMatchSwitchesStore() const { firstname_eng = '', @@ -39,7 +36,6 @@ export const usePlayerPage = () => { const fetchMatches = useCallback( (limit: number, offset: number) => getPlayerMatches({ - availableMatchesOnly, limit, offset, playerId, @@ -48,7 +44,6 @@ export const usePlayerPage = () => { [ playerId, sportType, - availableMatchesOnly, ], ) diff --git a/src/features/PreferencesPopup/components/Search/index.tsx b/src/features/PreferencesPopup/components/Search/index.tsx new file mode 100644 index 00000000..d6563e5f --- /dev/null +++ b/src/features/PreferencesPopup/components/Search/index.tsx @@ -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 ( + + + {query && } + + ) +} diff --git a/src/features/PreferencesPopup/components/SportList/index.tsx b/src/features/PreferencesPopup/components/SportList/index.tsx new file mode 100644 index 00000000..27c97e59 --- /dev/null +++ b/src/features/PreferencesPopup/components/SportList/index.tsx @@ -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 ( + + + + + + {map(sports, (sport) => ( + + onSportSelect(sport.id)} + /> + + ))} + + + ) +} diff --git a/src/features/PreferencesPopup/components/SportList/styled.tsx b/src/features/PreferencesPopup/components/SportList/styled.tsx new file mode 100644 index 00000000..e34071a4 --- /dev/null +++ b/src/features/PreferencesPopup/components/SportList/styled.tsx @@ -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; + } +` diff --git a/src/features/PreferencesPopup/components/TournamentInfo/index.tsx b/src/features/PreferencesPopup/components/TournamentInfo/index.tsx new file mode 100644 index 00000000..a57b7092 --- /dev/null +++ b/src/features/PreferencesPopup/components/TournamentInfo/index.tsx @@ -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) => ( + + + + + + + + +) diff --git a/src/features/PreferencesPopup/components/TournamentListItem/index.tsx b/src/features/PreferencesPopup/components/TournamentListItem/index.tsx new file mode 100644 index 00000000..f610c2a3 --- /dev/null +++ b/src/features/PreferencesPopup/components/TournamentListItem/index.tsx @@ -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) => { + const { + isTournamentSelected, + onTournamentSelect, + tournaments, + } = data + const tournament = tournaments[index] + if (!tournament) return null + + return ( + + } + onChange={() => onTournamentSelect(tournament)} + /> + + ) +} diff --git a/src/features/PreferencesPopup/components/TournamentsBlock/index.tsx b/src/features/PreferencesPopup/components/TournamentsBlock/index.tsx new file mode 100644 index 00000000..a4c2a676 --- /dev/null +++ b/src/features/PreferencesPopup/components/TournamentsBlock/index.tsx @@ -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 ( + + + + + + + + + + + ) +} diff --git a/src/features/PreferencesPopup/components/TournamentsList/index.tsx b/src/features/PreferencesPopup/components/TournamentsList/index.tsx new file mode 100644 index 00000000..1cde6218 --- /dev/null +++ b/src/features/PreferencesPopup/components/TournamentsList/index.tsx @@ -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 ( + + {TournamentListItem} + + ) +}) + +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); + } +` diff --git a/src/features/PreferencesPopup/index.tsx b/src/features/PreferencesPopup/index.tsx new file mode 100644 index 00000000..695a3dea --- /dev/null +++ b/src/features/PreferencesPopup/index.tsx @@ -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 ( + + {isFetching && } + +
+ + + + + + +
+ + + + +
+ + + +
+
+
+ ) +} diff --git a/src/features/PreferencesPopup/store/helpers.tsx b/src/features/PreferencesPopup/store/helpers.tsx new file mode 100644 index 00000000..b6bedd39 --- /dev/null +++ b/src/features/PreferencesPopup/store/helpers.tsx @@ -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, +) => ( + 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, + 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), + ), + ) +} diff --git a/src/features/PreferencesPopup/store/hooks/index.tsx b/src/features/PreferencesPopup/store/hooks/index.tsx new file mode 100644 index 00000000..ce24b77a --- /dev/null +++ b/src/features/PreferencesPopup/store/hooks/index.tsx @@ -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, + } +} diff --git a/src/features/PreferencesPopup/store/hooks/usePreferencesLoader.tsx b/src/features/PreferencesPopup/store/hooks/usePreferencesLoader.tsx new file mode 100644 index 00000000..857ae77a --- /dev/null +++ b/src/features/PreferencesPopup/store/hooks/usePreferencesLoader.tsx @@ -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(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, + } +} diff --git a/src/features/PreferencesPopup/store/hooks/useSearch.tsx b/src/features/PreferencesPopup/store/hooks/useSearch.tsx new file mode 100644 index 00000000..0c41742c --- /dev/null +++ b/src/features/PreferencesPopup/store/hooks/useSearch.tsx @@ -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[0] + +export const useSearch = ( + tournamentsBySports: TournamentsBySports, + selectedSports: Array, +) => { + const { suffix } = useLexicsStore() + + const [query, setQuery] = useState('') + const [tournaments, setTournaments] = useState([]) + + const onQueryChange = (e: ChangeEvent) => { + 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, + } +} diff --git a/src/features/PreferencesPopup/store/hooks/useSports.tsx b/src/features/PreferencesPopup/store/hooks/useSports.tsx new file mode 100644 index 00000000..274975be --- /dev/null +++ b/src/features/PreferencesPopup/store/hooks/useSports.tsx @@ -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([]) + const [selectedSports, setSelectedSports] = useState>([]) + + 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, + } +} diff --git a/src/features/PreferencesPopup/store/hooks/useTournaments.tsx b/src/features/PreferencesPopup/store/hooks/useTournaments.tsx new file mode 100644 index 00000000..a36dd668 --- /dev/null +++ b/src/features/PreferencesPopup/store/hooks/useTournaments.tsx @@ -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, + tournaments: Tournaments, +) => { + const [selectedTournaments, setSelectedTournaments] = useState([]) + + 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, + } +} diff --git a/src/features/PreferencesPopup/store/hooks/useTournamentsBySports.tsx b/src/features/PreferencesPopup/store/hooks/useTournamentsBySports.tsx new file mode 100644 index 00000000..85d2e765 --- /dev/null +++ b/src/features/PreferencesPopup/store/hooks/useTournamentsBySports.tsx @@ -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) => { + const [tournamentsBySports, setTournamentsBySports] = useState({}) + + 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, + } +} diff --git a/src/features/PreferencesPopup/store/index.tsx b/src/features/PreferencesPopup/store/index.tsx new file mode 100644 index 00000000..15bd29b1 --- /dev/null +++ b/src/features/PreferencesPopup/store/index.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react' +import { createContext, useContext } from 'react' + +import { usePreferences } from './hooks' + +type Context = ReturnType +type Props = { children: ReactNode } + +const PreferencesPopupContext = createContext({} as Context) + +export const PreferencesPopupStore = ({ children }: Props) => { + const value = usePreferences() + return ( + + {children} + + ) +} + +export const usePreferencesStore = () => useContext(PreferencesPopupContext) diff --git a/src/features/PreferencesPopup/styled.tsx b/src/features/PreferencesPopup/styled.tsx new file mode 100644 index 00000000..22cfcb72 --- /dev/null +++ b/src/features/PreferencesPopup/styled.tsx @@ -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` + ${({ 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; +` diff --git a/src/features/StripeElements/index.tsx b/src/features/StripeElements/index.tsx index e9036394..0e80eafb 100644 --- a/src/features/StripeElements/index.tsx +++ b/src/features/StripeElements/index.tsx @@ -20,6 +20,7 @@ export const StripeElements = ({ children }: Props) => { {children} diff --git a/src/features/TeamPage/hooks.tsx b/src/features/TeamPage/hooks.tsx index 5472a2f0..5eddfebf 100644 --- a/src/features/TeamPage/hooks.tsx +++ b/src/features/TeamPage/hooks.tsx @@ -9,13 +9,10 @@ import { getTeamInfo, getTeamMatches } from 'requests' import { useSportNameParam, usePageId } from 'hooks' -import { useMatchSwitchesStore } from 'features/MatchSwitches' - export const useTeamPage = () => { const [teamProfile, setTeamProfile] = useState(null) const { sportType } = useSportNameParam() const teamId = usePageId() - const { availableMatchesOnly } = useMatchSwitchesStore() useEffect(() => { getTeamInfo(sportType, teamId) @@ -28,7 +25,6 @@ export const useTeamPage = () => { const fetchMatches = useCallback( (limit: number, offset: number) => getTeamMatches({ - availableMatchesOnly, limit, offset, sportType, @@ -37,7 +33,6 @@ export const useTeamPage = () => { [ teamId, sportType, - availableMatchesOnly, ], ) diff --git a/src/features/TournamentPage/hooks.tsx b/src/features/TournamentPage/hooks.tsx index 2103f9e5..f49158f1 100644 --- a/src/features/TournamentPage/hooks.tsx +++ b/src/features/TournamentPage/hooks.tsx @@ -10,14 +10,12 @@ import { getTournamentInfo, getTournamentMatches } from 'requests' import { useSportNameParam, usePageId } from 'hooks' import { useName } from 'features/Name' -import { useMatchSwitchesStore } from 'features/MatchSwitches' export const useTournamentPage = () => { const [tournamentProfile, setTournamentProfile] = useState(null) const { sportType } = useSportNameParam() const tournamentId = usePageId() const country = useName(tournamentProfile?.country || {}) - const { availableMatchesOnly } = useMatchSwitchesStore() useEffect(() => { getTournamentInfo(sportType, tournamentId) @@ -30,7 +28,6 @@ export const useTournamentPage = () => { const fetchMatches = useCallback( (limit: number, offset: number) => getTournamentMatches({ - availableMatchesOnly, limit, offset, sportType, @@ -39,7 +36,6 @@ export const useTournamentPage = () => { [ tournamentId, sportType, - availableMatchesOnly, ], ) diff --git a/src/requests/getMatches/getHomeMatches.tsx b/src/requests/getMatches/getHomeMatches.tsx index 3b55dabd..df7af011 100644 --- a/src/requests/getMatches/getHomeMatches.tsx +++ b/src/requests/getMatches/getHomeMatches.tsx @@ -1,9 +1,7 @@ -import { PROCEDURES, SportTypes } from 'config' +import { PROCEDURES } from 'config' import { client } from 'config/clients' -import type { MatchStatuses } from 'features/HeaderFilters' - import type { MatchesBySection } from './types' import { requestMatches } from './request' import { getMatchesPreviews } from './getPreviews' @@ -11,23 +9,15 @@ import { getMatchesPreviews } from './getPreviews' const proc = PROCEDURES.get_matches type Args = { - availableMatchesOnly: boolean, date: string, limit: number, - matchStatus: MatchStatuses | null, offset: number, - sportType: SportTypes | null, - tournamentId: number | null, } export const getHomeMatches = async ({ - availableMatchesOnly, date, limit, - matchStatus, offset, - sportType, - tournamentId, }: Args): Promise => { const config = { body: { @@ -35,10 +25,6 @@ export const getHomeMatches = async ({ _p_date: date, _p_limit: limit, _p_offset: offset, - _p_sport: sportType, - _p_stream_status: matchStatus, - _p_sub_only: availableMatchesOnly, - _p_tournament_id: tournamentId, ...client.requests?.[proc], }, proc, diff --git a/src/requests/getMatches/getPlayerMatches.tsx b/src/requests/getMatches/getPlayerMatches.tsx index a4755baf..6f256a35 100644 --- a/src/requests/getMatches/getPlayerMatches.tsx +++ b/src/requests/getMatches/getPlayerMatches.tsx @@ -11,7 +11,6 @@ import { getMatchesPreviews } from './getPreviews' const proc = PROCEDURES.get_player_matches type Args = { - availableMatchesOnly: boolean, limit: number, offset: number, playerId: number, @@ -19,7 +18,6 @@ type Args = { } export const getPlayerMatches = async ({ - availableMatchesOnly, limit, offset, playerId, @@ -32,7 +30,6 @@ export const getPlayerMatches = async ({ _p_offset: offset, _p_player_id: playerId, _p_sport: sportType, - _p_sub_only: availableMatchesOnly, ...client.requests?.[proc], }, proc, diff --git a/src/requests/getMatches/getTeamMatches.tsx b/src/requests/getMatches/getTeamMatches.tsx index 23463f59..67b6e9cb 100644 --- a/src/requests/getMatches/getTeamMatches.tsx +++ b/src/requests/getMatches/getTeamMatches.tsx @@ -11,7 +11,6 @@ import { requestMatches } from './request' const proc = PROCEDURES.get_team_matches type Args = { - availableMatchesOnly: boolean, limit: number, offset: number, sportType: SportTypes, @@ -19,7 +18,6 @@ type Args = { } export const getTeamMatches = async ({ - availableMatchesOnly, limit, offset, sportType, @@ -31,7 +29,6 @@ export const getTeamMatches = async ({ _p_limit: limit, _p_offset: offset, _p_sport: sportType, - _p_sub_only: availableMatchesOnly, _p_team_id: teamId, ...client.requests?.[proc], }, diff --git a/src/requests/getMatches/getTournamentMatches.tsx b/src/requests/getMatches/getTournamentMatches.tsx index 73cae9cc..789870eb 100644 --- a/src/requests/getMatches/getTournamentMatches.tsx +++ b/src/requests/getMatches/getTournamentMatches.tsx @@ -11,7 +11,6 @@ import { requestMatches } from './request' const proc = PROCEDURES.get_tournament_matches type Args = { - availableMatchesOnly: boolean, limit: number, offset: number, sportType: SportTypes, @@ -19,7 +18,6 @@ type Args = { } export const getTournamentMatches = async ({ - availableMatchesOnly, limit, offset, sportType, @@ -31,7 +29,6 @@ export const getTournamentMatches = async ({ _p_limit: limit, _p_offset: offset, _p_sport: sportType, - _p_sub_only: availableMatchesOnly, _p_tournament_id: tournamentId, ...client.requests?.[proc], }, diff --git a/src/requests/getSportTournaments.tsx b/src/requests/getSportTournaments.tsx index fbf6e2e1..73c40882 100644 --- a/src/requests/getSportTournaments.tsx +++ b/src/requests/getSportTournaments.tsx @@ -1,3 +1,6 @@ +import reduce from 'lodash/reduce' +import map from 'lodash/map' + import { DATA_URL, PROCEDURES, @@ -27,17 +30,10 @@ export type Tournament = { export type Tournaments = Array -const LIMIT = 20 - -export const getSportTournaments = ( - sportId: SportTypes | null, - page: number = 0, -): Promise => { +const getSportTournaments = (sportId: SportTypes): Promise => { const config = { body: { params: { - _p_limit: LIMIT, - _p_offset: page * LIMIT, _p_sport: sportId, }, proc, @@ -49,3 +45,19 @@ export const getSportTournaments = ( url: DATA_URL, }) } + +export type TournamentsBySports = Partial> + +export const getTournamentsBySports = async (sportIds: Array) => { + 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 +} diff --git a/src/requests/getUserPreferences.tsx b/src/requests/getUserPreferences.tsx new file mode 100644 index 00000000..d4940e78 --- /dev/null +++ b/src/requests/getUserPreferences.tsx @@ -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 || [] +} diff --git a/src/requests/saveUserPreferences.tsx b/src/requests/saveUserPreferences.tsx new file mode 100644 index 00000000..4457e1a1 --- /dev/null +++ b/src/requests/saveUserPreferences.tsx @@ -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, + }) +}