diff --git a/public/images/sportFavStar.png b/public/images/sportFavStar.png new file mode 100644 index 00000000..62581020 Binary files /dev/null and b/public/images/sportFavStar.png differ diff --git a/public/images/xIcon.png b/public/images/xIcon.png new file mode 100644 index 00000000..b7dab3d2 Binary files /dev/null and b/public/images/xIcon.png differ diff --git a/src/config/procedures.tsx b/src/config/procedures.tsx index 21badd69..37b91cb4 100644 --- a/src/config/procedures.tsx +++ b/src/config/procedures.tsx @@ -4,7 +4,9 @@ export const PROCEDURES = { get_cities: 'get_cities', get_matches: 'get_matches', get_players_teams_tournaments: 'get_players_teams_tournaments', + get_user_favorites: 'get_user_favorites', logout_user: 'logout_user', lst_c_country: 'lst_c_country', param_lexical: 'param_lexical', + save_user_favorite: 'save_user_favorite', } diff --git a/src/config/routes.tsx b/src/config/routes.tsx index f6ddfa14..260dbfba 100644 --- a/src/config/routes.tsx +++ b/src/config/routes.tsx @@ -19,4 +19,44 @@ export const LOGOS_FALLBACKS = { teams: 'https://hockey.instatscout.com/images/team-no-photo.png', tournaments: 'https://hockey.instatscout.com/images/tournaments/180/no-photo.png', }, -} +} as const + +export const LOGOS_URLS = { + basketball: { + players: 'https://basketball.instatscout.com/images/players/180', + teams: 'https://basketball.instatscout.com/images/teams/180', + tournaments: 'https://basketball.instatscout.com/images/tournaments/180', + }, + + football: { + players: 'https://instatscout.com/images/players/180', + teams: 'https://instatscout.com/images/teams/180', + tournaments: 'https://instatscout.com/images/tournaments/180', + }, + + hockey: { + players: 'https://hockey.instatscout.com/images/players/180', + teams: 'https://hockey.instatscout.com/images/teams/180', + tournaments: 'https://hockey.instatscout.com/images/tournaments/180', + }, +} as const + +export const FAV_SPORT_URLS = { + basketball: { + players: '/basketball/players', + teams: '/basketball/teams', + tournaments: '/basketball/tournaments', + }, + + football: { + players: '/football/players', + teams: '/football/teams', + tournaments: '/football/tournaments', + }, + + hockey: { + players: '/hockey/players', + teams: '/hockey/teams', + tournaments: '/hockey/tournaments', + }, +} as const diff --git a/src/features/HomePage/index.tsx b/src/features/HomePage/index.tsx index cd018fef..35685a73 100644 --- a/src/features/HomePage/index.tsx +++ b/src/features/HomePage/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { Fragment } from 'react' import { Header } from 'features/Header' import { MainWrapper } from 'features/MainWrapper' @@ -7,30 +7,34 @@ import { DateFilter } from 'features/DateFilter' import { MatchStatusFilter } from 'features/MatchStatusFilter' import { SportTypeFilter } from 'features/SportTypeFilter' import { TournamentFilter } from 'features/TournamentFilter' +import { UserSportFav } from 'features/UserSportFav' import { FilterWrapper, } from './styled' export const HomePage = () => ( - -
- - - + + + +
+ + + - - - + + + - - - + + + - - - - -
-
+ + + + +
+
+ ) diff --git a/src/features/MainWrapper/index.tsx b/src/features/MainWrapper/index.tsx index 40b8e074..10d22364 100644 --- a/src/features/MainWrapper/index.tsx +++ b/src/features/MainWrapper/index.tsx @@ -2,5 +2,5 @@ import styled from 'styled-components/macro' export const MainWrapper = styled.div` width: 100%; - padding-left: 80px; + display: flex; ` diff --git a/src/features/UserSportFav/TooltipBlock/hooks/index.tsx b/src/features/UserSportFav/TooltipBlock/hooks/index.tsx new file mode 100644 index 00000000..9968062f --- /dev/null +++ b/src/features/UserSportFav/TooltipBlock/hooks/index.tsx @@ -0,0 +1,27 @@ +import { SPORT_COLORS } from 'config' + +export const getSportName = (sport: number, suffix: string): string => { + switch (sport) { + case 1: + return suffix === 'eng' ? 'Football' : 'Футбол' + case 2: + return suffix === 'eng' ? 'Hockey' : 'Хоккей' + case 3: + return suffix === 'eng' ? 'Basketball' : 'Баскетбол' + default: + return '' + } +} + +export const getSportColor = (sport: number): string => { + switch (sport) { + case 1: + return SPORT_COLORS.football + case 2: + return SPORT_COLORS.hockey + case 3: + return SPORT_COLORS.basketball + default: + return '' + } +} diff --git a/src/features/UserSportFav/TooltipBlock/index.tsx b/src/features/UserSportFav/TooltipBlock/index.tsx new file mode 100644 index 00000000..5c031eb6 --- /dev/null +++ b/src/features/UserSportFav/TooltipBlock/index.tsx @@ -0,0 +1,48 @@ +import React from 'react' + +import { + getSportName, + getSportColor, +} from './hooks' +import { + TooltipBlockWrapper, + TooltipBlockItem, + TooltipBlockItemThinUpperCase, + TooltipBlockItemThin, +} from './styled' + +type TTooltipBlock = { + countryName?: string, + date?: string, + playerFirstName?: string, + playerLastName?: string, + playerTeamName?: string, + sport: number, + suffix: string, + teamName?: string, +} + +export const TooltipBlock = ({ + countryName, + playerFirstName, + playerLastName, + playerTeamName, + sport, + suffix, + teamName, +}: TTooltipBlock) => ( + + + {playerFirstName && playerLastName && `${playerFirstName} ${playerLastName}`} + + + {teamName} + + + + {getSportName(sport, suffix)} + {' '} + {playerTeamName || countryName} + + +) diff --git a/src/features/UserSportFav/TooltipBlock/styled.tsx b/src/features/UserSportFav/TooltipBlock/styled.tsx new file mode 100644 index 00000000..79a97bba --- /dev/null +++ b/src/features/UserSportFav/TooltipBlock/styled.tsx @@ -0,0 +1,47 @@ +import styled, { css } from 'styled-components/macro' + +export const TooltipBlockWrapper = styled.div` + background-color: #fff; + border-radius: 10px; + padding: 12px; + white-space: nowrap; + &::before { + position: absolute; + top: -8px; + content: ''; + border-bottom: 8px solid #fff; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + } +` + +export type TTooltipProps = { + color?: string, +} + +export const TooltipStyles = css` + display: block; + font-family: Montserrat, Tahoma, sans-serif; + font-size: 14px; + line-height: 18px; + color: ${({ color }) => (color ? `${color}` : '#2c2d2e')}; + font-weight: 600; + + &:hover { + background-color: rgba(255, 255, 255, 0.7); + } +` + +export const TooltipBlockItem = styled.span` + ${TooltipStyles} +` + +export const TooltipBlockItemThin = styled(TooltipBlockItem)` + font-weight: 400; +` + +export const TooltipBlockItemThinUpperCase = styled(TooltipBlockItem)` + display: inline; + text-transform: uppercase; + font-weight: 400; +` diff --git a/src/features/UserSportFav/hooks/index.tsx b/src/features/UserSportFav/hooks/index.tsx new file mode 100644 index 00000000..67225785 --- /dev/null +++ b/src/features/UserSportFav/hooks/index.tsx @@ -0,0 +1,132 @@ +import { + useEffect, + useState, + useMemo, +} from 'react' + +import map from 'lodash/map' + +import { + LOGOS_URLS, + FAV_SPORT_URLS, +} from 'config' +import { + SPORT_TYPES, + DATA_TYPES, +} from 'helpers' +import { + modifyUserSportFavs, + ModifyUserSportFavsArgs, + getUserSportFavs, + UserSportFavItem, +} from 'requests' +import { useLexicsStore } from 'features/LexicsStore' + +type TNames = 'name_eng' | 'name_rus' +type TShortNames = 'short_name_eng' | 'short_name_rus' +type TFirstNames = 'firstname_eng' | 'firstname_rus' +type TLastNames = 'lastname_eng' | 'firstname_rus' +type TNickNames = 'nickname_eng' | 'nickname_rus' + +export type TMemoizedUserSportFavItem = { + countryName?: string, + firstname?: string, + id: number, + lastname?: string, + name?: string, + nickname?: string, + shortName?: string, + sport: number, + teamName?: string, + type: number, +} + +type TMakeUrlArgs = { + id: number, + sport: number, + type: number, +} + +export const useUserSportFavs = () => { + const { suffix } = useLexicsStore() + const [userSportFavItems, setUserSportFavItems] = useState>([]) + + const nameField = `name_${suffix}` as TNames + const shortNameField = `short_name_${suffix}` as TShortNames + const firtsNameField = `firstname_${suffix}` as TFirstNames + const lastNameField = `lastname_${suffix}` as TLastNames + const nickNameField = `nickname_${suffix}` as TNickNames + + useEffect(() => { + getUserSportFavs().then(setUserSportFavItems) + }, []) + + const addRemoveSportFav = ({ + action, + id, + sport, + type, + }: ModifyUserSportFavsArgs) => { + // при добавлении дубликата userSportFavItem back возвращает {} + modifyUserSportFavs({ + action, + id, + sport, + type, + }).then((userSportFavs) => Array.isArray(userSportFavs) && setUserSportFavItems(userSportFavs)) + } + + const memoizedUserSportFavItems = useMemo(() => map(userSportFavItems, (item) => ({ + countryName: item.info.country?.[nameField], + firstname: item.info[firtsNameField], + id: item.id, + lastname: item.info[lastNameField], + name: item.info[nameField], + nickname: item.info[nickNameField], + shortName: item.info[shortNameField], + sport: item.sport, + teamName: item.info.team?.[nameField], + type: item.type, + })), + [ + firtsNameField, + lastNameField, + userSportFavItems, + nameField, + nickNameField, + shortNameField, + ]) + + return { + addRemoveSportFav, + userSportFavItems: memoizedUserSportFavItems, + } +} + +export const makePicUrl = (arg: TMakeUrlArgs) => ( + // @ts-expect-error + `${LOGOS_URLS[SPORT_TYPES[arg.sport]][DATA_TYPES[arg.type]]}/${arg.id}.png` +) + +export const makeProfileUrl = (arg: TMakeUrlArgs) => ( + // @ts-expect-error + `${FAV_SPORT_URLS[SPORT_TYPES[arg.sport]][DATA_TYPES[arg.type]]}/${arg.id}` +) + +export const userSportFavs = ( + userSportFavItems: Array, +) => userSportFavItems?.length > 0 && map( + userSportFavItems, (fav) => ({ + ...fav, + pic_url: makePicUrl({ + id: fav.id, + sport: fav.sport, + type: fav.type, + }), + profile_url: makeProfileUrl({ + id: fav.id, + sport: fav.sport, + type: fav.type, + }), + }), +) diff --git a/src/features/UserSportFav/index.tsx b/src/features/UserSportFav/index.tsx new file mode 100644 index 00000000..61c82331 --- /dev/null +++ b/src/features/UserSportFav/index.tsx @@ -0,0 +1,68 @@ +import React from 'react' + +import map from 'lodash/map' + +import { useLexicsStore } from 'features/LexicsStore' +import { handleImageError } from 'helpers' + +import { + useUserSportFavs, + userSportFavs, +} from './hooks' +import { TooltipBlock } from './TooltipBlock' +import { + StyledLink, + UserSportFavItemLogoWrapper, + UserSportFavXWrapper, + UserSportFavImgWrapper, + UserSportFavStar, + UserSportFavLogoWrapper, + UserSportFavWrapper, +} from './styled' + +export const UserSportFav = () => { + const { addRemoveSportFav, userSportFavItems } = useUserSportFavs() + + const { suffix } = useLexicsStore() + + const userSportFavList = userSportFavs(userSportFavItems) + + return ( + + + + {userSportFavList && map(userSportFavList, (item) => ( + + addRemoveSportFav({ + action: 2, + id: item.id, + sport: item.sport, + type: item.type, + })} + /> + + + handleImageError({ + e, + sport: item.sport, + type: item.type, + })} + /> + + + ))} + + ) +} diff --git a/src/features/UserSportFav/styled.tsx b/src/features/UserSportFav/styled.tsx new file mode 100644 index 00000000..09af0011 --- /dev/null +++ b/src/features/UserSportFav/styled.tsx @@ -0,0 +1,80 @@ +import { Link } from 'react-router-dom' + +import styled from 'styled-components/macro' + +import { Logo } from 'features/Logo' +import { TooltipBlockWrapper } from './TooltipBlock/styled' + +export const StyledLink = styled(Link)`` + +export const UserSportFavWrapper = styled.div` + width: 80px; + display: flex; + flex: 0 0 auto; + flex-direction:column; + align-items: center; + background: rgba(255, 255, 255, 0.1); +` + +export const UserSportFavLogoWrapper = styled(Logo)` + margin-top: 35px; + margin-bottom: 120px; +` + +export const UserSportFavXWrapper = styled.span` + display: block; + position: absolute; + top: 0; + right: 0; + background: transparent url('/images/xIcon.png') no-repeat center; + height: 11px; + width: 11px; + border: none; +` + +export const UserSportFavItemLogoWrapper = styled.div` + position: relative; + width: 48px; + height: 48px; + border-radius: 50%; + padding: 5px; + background-color: #fff; + margin-bottom: 16px; + + ${UserSportFavXWrapper} { + display: none; + } + + ${TooltipBlockWrapper} { + display: none; + position: absolute; + left: 40%; + top: 120%; + z-index: 2; + } + + &:hover { + background-color: rgba(255, 255, 255, 0.7); + cursor: pointer; + + ${UserSportFavXWrapper} { + display: block; + } + + ${TooltipBlockWrapper} { + display: block; + } + } +` + +export const UserSportFavImgWrapper = styled.img` + width: 100%; +` + +export const UserSportFavStar = styled.div` + width: 48px; + height: 48px; + border-radius: 50%; + background: #3f3f3f url('/images/sportFavStar.png') no-repeat center; + margin-bottom: 16px; +` diff --git a/src/helpers/handleImg/index.tsx b/src/helpers/handleImg/index.tsx new file mode 100644 index 00000000..5981a11f --- /dev/null +++ b/src/helpers/handleImg/index.tsx @@ -0,0 +1,27 @@ +import { BaseSyntheticEvent } from 'react' + +import { LOGOS_FALLBACKS } from 'config' + +export type imageErrorArgs = { + e: BaseSyntheticEvent, + sport: number, + type: number, +} + +export const SPORT_TYPES = { + 1: 'football', + 2: 'hockey', + 3: 'basketball', +} as const + +export const DATA_TYPES = { + 1: 'tournaments', + 2: 'teams', + 3: 'players', +} as const + +export const handleImageError = (arg: imageErrorArgs): void => { + arg.e.target.onError = '' + // @ts-expect-error + arg.e.target.src = LOGOS_FALLBACKS[SPORT_TYPES[arg.sport]][DATA_TYPES[arg.type]] +} diff --git a/src/helpers/index.tsx b/src/helpers/index.tsx index f955ad23..da4863fb 100644 --- a/src/helpers/index.tsx +++ b/src/helpers/index.tsx @@ -2,3 +2,5 @@ export * from './callApi' export * from './callApi/getResponseData' export * from './token' export * from './getLogo' +export * from './handleImg' + diff --git a/src/requests/getUserSportFavs.tsx b/src/requests/getUserSportFavs.tsx new file mode 100644 index 00000000..0c57a55f --- /dev/null +++ b/src/requests/getUserSportFavs.tsx @@ -0,0 +1,53 @@ +import { DATA_URL, PROCEDURES } from 'config' +import { callApi, getResponseData } from 'helpers' + +const proc = PROCEDURES.get_user_favorites + +export type UserSportFavItem = { + id: number, + info: { + country?: { + name_eng: string, + name_rus: string, + }, + date?: string, + firstname_eng?: string, + firstname_rus?: string, + lastname_eng?: string, + lastname_rus?: string, + name_eng?: string, + name_rus?: string, + nickname_eng?: string, + nickname_rus?: string, + short_name_eng?: string, + short_name_rus?: string, + team?: { + name_eng: string, + name_rus: string, + }, + team1?: { + name_eng: string, + name_rus: string, + }, + team2?: { + name_eng: string, + name_rus: string, + }, + }, + sport: number, + type: number, +} + +export const getUserSportFavs = (): Promise> => { + const config = { + body: { + params: {}, + proc, + }, + } + + return callApi({ + config, + url: DATA_URL, + }).then(getResponseData(proc)) +} diff --git a/src/requests/index.tsx b/src/requests/index.tsx index 762d8112..7e65e875 100644 --- a/src/requests/index.tsx +++ b/src/requests/index.tsx @@ -6,3 +6,5 @@ export * from './getCountryCities' export * from './getLexics' export * from './getSearchItems' export * from './getMatches' +export * from './getUserSportFavs' +export * from './modifyUserSportFavs' diff --git a/src/requests/modifyUserSportFavs.tsx b/src/requests/modifyUserSportFavs.tsx new file mode 100644 index 00000000..c2eb3470 --- /dev/null +++ b/src/requests/modifyUserSportFavs.tsx @@ -0,0 +1,37 @@ +import { DATA_URL, PROCEDURES } from 'config' +import { callApi, getResponseData } from 'helpers' +import { UserSportFavItem } from './getUserSportFavs' + +const proc = PROCEDURES.save_user_favorite + +export type ModifyUserSportFavsArgs = { + action: number, + id: number, + sport: number, + type: number, +} + +// при добавлении дубликата userSportFavItem back возвращает {} +export const modifyUserSportFavs = ({ + action, + id, + sport, + type, +}: ModifyUserSportFavsArgs): Promise | {}> => { + const config = { + body: { + params: { + _p_action: action, + _p_id: id, + _p_sport: sport, + _p_type: type, + }, + proc, + }, + } + + return callApi({ + config, + url: DATA_URL, + }).then(getResponseData(proc)) +}