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))
+}