feat(ott-191): player page header (#76)
parent
3e90223c5d
commit
22d93b845c
@ -0,0 +1,7 @@ |
|||||||
|
import { indexLexics } from './indexLexics' |
||||||
|
|
||||||
|
export const homeLexics = { |
||||||
|
...indexLexics, |
||||||
|
|
||||||
|
video_from_my_subscriptions: 13023, |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
import { indexLexics } from './indexLexics' |
||||||
|
|
||||||
|
export const playerLexics = { |
||||||
|
...indexLexics, |
||||||
|
|
||||||
|
add_to_favorites: 1701, |
||||||
|
added_to_favorites: 13048, |
||||||
|
cm: 817, |
||||||
|
kg: 652, |
||||||
|
} |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
import React from 'react' |
||||||
|
|
||||||
|
type StarProps = { |
||||||
|
fill: string, |
||||||
|
} |
||||||
|
|
||||||
|
export const StarIcon = ({ fill }: StarProps) => ( |
||||||
|
<svg width='20' height='19' viewBox='0 0 20 19' xmlns='http://www.w3.org/2000/svg'> |
||||||
|
<g filter='url(#filter0_d)'> |
||||||
|
<path d='M10 0L12.5814 5.44704L18.5595 6.21885L14.1767 10.3571L15.2901 16.2812L10 13.3917L4.70993 16.2812L5.82325 10.3571L1.44049 6.21885L7.41863 5.44704L10 0Z' fill={fill} /> |
||||||
|
</g> |
||||||
|
<defs> |
||||||
|
<filter id='filter0_d' x='0.44043' y='0' width='19.119' height='18.2812' filterUnits='userSpaceOnUse' colorInterpolationFilters='sRGB'> |
||||||
|
<feFlood floodOpacity='0' result='BackgroundImageFix' /> |
||||||
|
<feColorMatrix in='SourceAlpha' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0' /> |
||||||
|
<feOffset dy='1' /> |
||||||
|
<feGaussianBlur stdDeviation='0.5' /> |
||||||
|
<feColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0' /> |
||||||
|
<feBlend mode='normal' in2='BackgroundImageFix' result='effect1_dropShadow' /> |
||||||
|
<feBlend mode='normal' in='SourceGraphic' in2='effect1_dropShadow' result='shape' /> |
||||||
|
</filter> |
||||||
|
</defs> |
||||||
|
</svg> |
||||||
|
) |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
import { useEffect, useState } from 'react' |
||||||
|
|
||||||
|
import type { PlayerProfile } from 'requests/getPlayerInfo' |
||||||
|
import { getPlayerInfo } from 'requests/getPlayerInfo' |
||||||
|
import { useSportNameParam, usePageId } from 'hooks' |
||||||
|
import { useLexicsStore } from 'features/LexicsStore' |
||||||
|
|
||||||
|
type Firstname = 'firstname_eng' | 'firstname_rus' |
||||||
|
type Lastname = 'lastname_eng' | 'lastname_rus' |
||||||
|
type Name = 'name_eng' | 'name_rus' |
||||||
|
|
||||||
|
export const usePlayerPage = () => { |
||||||
|
const [playerProfile, setPlayerProfile] = useState<PlayerProfile>(null) |
||||||
|
const { sportType } = useSportNameParam() |
||||||
|
const pageId = usePageId() |
||||||
|
const { suffix, translate } = useLexicsStore() |
||||||
|
|
||||||
|
const { |
||||||
|
club_team, |
||||||
|
height, |
||||||
|
weight, |
||||||
|
[`firstname_${suffix}` as Firstname]: firstName = '', |
||||||
|
[`lastname_${suffix}` as Lastname]: lastName = '', |
||||||
|
} = playerProfile || {} |
||||||
|
|
||||||
|
const fullName = `${firstName} ${lastName}` |
||||||
|
const teamName = club_team?.[`name_${suffix}` as Name] || '' |
||||||
|
|
||||||
|
const infoItems = [ |
||||||
|
`${height ? `${height} ${translate('cm')}` : ''}${ |
||||||
|
weight ? `, ${weight} ${translate('kg')}` : ''}`,
|
||||||
|
teamName, |
||||||
|
] |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
getPlayerInfo(pageId, sportType).then(setPlayerProfile) |
||||||
|
}, [pageId, sportType]) |
||||||
|
|
||||||
|
return { |
||||||
|
infoItems, |
||||||
|
name: fullName, |
||||||
|
sportType, |
||||||
|
} |
||||||
|
} |
||||||
@ -1,12 +1,39 @@ |
|||||||
import React from 'react' |
import React from 'react' |
||||||
import styled from 'styled-components' |
|
||||||
|
|
||||||
const TempPageTitle = styled.span` |
import { ProfileTypes } from 'config' |
||||||
padding: 20px; |
import { playerLexics } from 'config/lexics/playerLexics' |
||||||
font-size: 20px; |
import { useLexicsConfig } from 'features/LexicsStore' |
||||||
color: white; |
import { Header } from 'features/Header' |
||||||
` |
import { MainWrapper } from 'features/MainWrapper' |
||||||
|
import { Search } from 'features/Search' |
||||||
|
import { UserFavorites } from 'features/UserFavorites' |
||||||
|
import { ProfileCard } from 'features/ProfileCard' |
||||||
|
import { UserFavoritesStore } from 'features/UserFavorites/store' |
||||||
|
|
||||||
export const PlayerPage = () => ( |
import { usePlayerPage } from './hooks' |
||||||
<TempPageTitle>PLAYER PAGE</TempPageTitle> |
|
||||||
) |
export const PlayerPage = () => { |
||||||
|
const { |
||||||
|
infoItems, |
||||||
|
name, |
||||||
|
sportType, |
||||||
|
} = usePlayerPage() |
||||||
|
useLexicsConfig(playerLexics) |
||||||
|
|
||||||
|
return ( |
||||||
|
<UserFavoritesStore> |
||||||
|
<MainWrapper> |
||||||
|
<UserFavorites /> |
||||||
|
<Header> |
||||||
|
<ProfileCard |
||||||
|
sportType={sportType} |
||||||
|
profileType={ProfileTypes.PLAYERS} |
||||||
|
name={name} |
||||||
|
infoItems={infoItems} |
||||||
|
/> |
||||||
|
<Search /> |
||||||
|
</Header> |
||||||
|
</MainWrapper> |
||||||
|
</UserFavoritesStore> |
||||||
|
) |
||||||
|
} |
||||||
|
|||||||
@ -0,0 +1,57 @@ |
|||||||
|
import type { BaseSyntheticEvent } from 'react' |
||||||
|
import { useCallback } from 'react' |
||||||
|
|
||||||
|
import findIndex from 'lodash/findIndex' |
||||||
|
|
||||||
|
import { handleImageError, getProfileLogo } from 'helpers' |
||||||
|
import { useUserFavoritesStore } from 'features/UserFavorites/store' |
||||||
|
import { usePageId } from 'hooks' |
||||||
|
|
||||||
|
import type { ProfileCardProps } from './types' |
||||||
|
|
||||||
|
export const useProfileCard = ({ |
||||||
|
profileType, |
||||||
|
sportType, |
||||||
|
}: ProfileCardProps) => { |
||||||
|
const { addRemoveFavorite, userFavorites } = useUserFavoritesStore() |
||||||
|
const profileId = usePageId() |
||||||
|
|
||||||
|
const isFavorite = findIndex(userFavorites, { |
||||||
|
id: profileId, |
||||||
|
sport: sportType, |
||||||
|
type: profileType, |
||||||
|
}) !== -1 |
||||||
|
|
||||||
|
const logo = getProfileLogo({ |
||||||
|
id: profileId, |
||||||
|
profileType, |
||||||
|
sportType, |
||||||
|
}) |
||||||
|
|
||||||
|
const addToFavorites = useCallback(() => { |
||||||
|
addRemoveFavorite({ |
||||||
|
action: 1, |
||||||
|
id: profileId, |
||||||
|
sport: sportType, |
||||||
|
type: profileType, |
||||||
|
}) |
||||||
|
}, [ |
||||||
|
addRemoveFavorite, |
||||||
|
profileId, |
||||||
|
profileType, |
||||||
|
sportType, |
||||||
|
]) |
||||||
|
|
||||||
|
const onError = useCallback((e: BaseSyntheticEvent) => handleImageError({ |
||||||
|
e, |
||||||
|
sport: sportType, |
||||||
|
type: profileType, |
||||||
|
}), [profileType, sportType]) |
||||||
|
|
||||||
|
return { |
||||||
|
addToFavorites, |
||||||
|
isFavorite, |
||||||
|
logo, |
||||||
|
onError, |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,63 @@ |
|||||||
|
import React from 'react' |
||||||
|
|
||||||
|
import map from 'lodash/map' |
||||||
|
|
||||||
|
import { T9n } from 'features/T9n' |
||||||
|
import { StarIcon } from 'features/Common' |
||||||
|
|
||||||
|
import type { ProfileCardProps } from './types' |
||||||
|
import { |
||||||
|
Wrapper, |
||||||
|
Details, |
||||||
|
AddToFavButton, |
||||||
|
Logo, |
||||||
|
InfoItem, |
||||||
|
InfoItems, |
||||||
|
Name, |
||||||
|
Bottom, |
||||||
|
InFavorites, |
||||||
|
} from './styled' |
||||||
|
import { useProfileCard } from './hooks' |
||||||
|
|
||||||
|
export const ProfileCard = (props: ProfileCardProps) => { |
||||||
|
const { |
||||||
|
infoItems, |
||||||
|
name, |
||||||
|
} = props |
||||||
|
|
||||||
|
const { |
||||||
|
addToFavorites, |
||||||
|
isFavorite, |
||||||
|
logo, |
||||||
|
onError, |
||||||
|
} = useProfileCard(props) |
||||||
|
|
||||||
|
return ( |
||||||
|
<Wrapper> |
||||||
|
<Logo |
||||||
|
src={logo} |
||||||
|
alt={name} |
||||||
|
onError={onError} |
||||||
|
/> |
||||||
|
<Details> |
||||||
|
<Name>{name}</Name> |
||||||
|
<Bottom> |
||||||
|
{isFavorite ? ( |
||||||
|
<InFavorites as='div'> |
||||||
|
<StarIcon fill='#eacb6f' /> |
||||||
|
<T9n t='added_to_favorites' /> |
||||||
|
</InFavorites> |
||||||
|
) : ( |
||||||
|
<AddToFavButton onClick={addToFavorites}> |
||||||
|
<StarIcon fill='#fff' /> |
||||||
|
<T9n t='add_to_favorites' /> |
||||||
|
</AddToFavButton> |
||||||
|
)} |
||||||
|
<InfoItems> |
||||||
|
{map(infoItems, (infoItem) => <InfoItem key={infoItem}>{infoItem}</InfoItem>)} |
||||||
|
</InfoItems> |
||||||
|
</Bottom> |
||||||
|
</Details> |
||||||
|
</Wrapper> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,75 @@ |
|||||||
|
import styled from 'styled-components/macro' |
||||||
|
|
||||||
|
export const Wrapper = styled.div` |
||||||
|
display: flex; |
||||||
|
max-width: 815px; |
||||||
|
margin-right: 100px; |
||||||
|
` |
||||||
|
|
||||||
|
export const Name = styled.h1` |
||||||
|
margin-bottom: 10px; |
||||||
|
line-height: 40px; |
||||||
|
font-size: 36px; |
||||||
|
font-weight: bold; |
||||||
|
letter-spacing: 0.02em; |
||||||
|
color: #fff; |
||||||
|
` |
||||||
|
|
||||||
|
export const Logo = styled.img` |
||||||
|
width: 88px; |
||||||
|
height: 88px; |
||||||
|
margin-right: 15px; |
||||||
|
border-radius: 50%; |
||||||
|
background-color: #000; |
||||||
|
` |
||||||
|
|
||||||
|
export const Details = styled.div`` |
||||||
|
|
||||||
|
export const Bottom = styled.div` |
||||||
|
display: flex; |
||||||
|
` |
||||||
|
|
||||||
|
export const AddToFavButton = styled.button` |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
height: 32px; |
||||||
|
padding: 0 16px; |
||||||
|
border-radius: 2px; |
||||||
|
font-size: 14px; |
||||||
|
color: #fff; |
||||||
|
background: |
||||||
|
linear-gradient( |
||||||
|
180deg, rgba(255, 255, 255, 0) 0%, |
||||||
|
rgba(255, 255, 255, 0.1) 0.01%,
|
||||||
|
rgba(0, 0, 0, 0.1) 99.99% |
||||||
|
), |
||||||
|
#0033CC; |
||||||
|
box-shadow: 0px 1px 0px rgba(0, 4, 41, 0.3); |
||||||
|
border-style: none; |
||||||
|
outline: none; |
||||||
|
cursor: pointer; |
||||||
|
|
||||||
|
svg { |
||||||
|
margin-right: 10px; |
||||||
|
width: 18px; |
||||||
|
height: 18px; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
export const InFavorites = styled(AddToFavButton)` |
||||||
|
color: #EACB6F; |
||||||
|
border: 1px solid currentColor; |
||||||
|
background-color: transparent; |
||||||
|
cursor: default; |
||||||
|
` |
||||||
|
|
||||||
|
export const InfoItems = styled.div` |
||||||
|
margin-left: 20px; |
||||||
|
` |
||||||
|
|
||||||
|
export const InfoItem = styled.div` |
||||||
|
line-height: 18px; |
||||||
|
font-size: 16px; |
||||||
|
letter-spacing: 0.02em; |
||||||
|
color: rgba(255, 255, 255, 0.5); |
||||||
|
` |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
import { ProfileTypes, SportTypes } from 'config' |
||||||
|
|
||||||
|
export type ProfileCardProps = { |
||||||
|
infoItems: Array<string>, |
||||||
|
name: string, |
||||||
|
profileType: ProfileTypes, |
||||||
|
sportType: SportTypes, |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
import type { ReactNode } from 'react' |
||||||
|
import React, { |
||||||
|
useContext, |
||||||
|
createContext, |
||||||
|
} from 'react' |
||||||
|
|
||||||
|
import { useUserFavorites } from '../hooks' |
||||||
|
|
||||||
|
export * from '../hooks' |
||||||
|
|
||||||
|
type UserFavoritesStore = ReturnType<typeof useUserFavorites> |
||||||
|
|
||||||
|
const UserFavoritesContext = createContext({} as UserFavoritesStore) |
||||||
|
|
||||||
|
type Props = { children: ReactNode } |
||||||
|
|
||||||
|
export const UserFavoritesStore = ({ children }: Props) => { |
||||||
|
const userFavoritesStore = useUserFavorites() |
||||||
|
|
||||||
|
return ( |
||||||
|
<UserFavoritesContext.Provider value={userFavoritesStore}> |
||||||
|
{children} |
||||||
|
</UserFavoritesContext.Provider> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export const useUserFavoritesStore = () => useContext(UserFavoritesContext) |
||||||
@ -1,2 +1,4 @@ |
|||||||
export * from './usePageId' |
export * from './usePageId' |
||||||
export * from './useToggle' |
export * from './useToggle' |
||||||
|
export * from './useRequest' |
||||||
|
export * from './useSportNameParam' |
||||||
|
|||||||
@ -0,0 +1,16 @@ |
|||||||
|
import { useParams } from 'react-router' |
||||||
|
|
||||||
|
import toUpper from 'lodash/toUpper' |
||||||
|
|
||||||
|
import { SportTypes } from 'config' |
||||||
|
|
||||||
|
type SportName = 'FOOTBALL' | 'HOCKEY' | 'BASKETBALL' |
||||||
|
|
||||||
|
export const useSportNameParam = () => { |
||||||
|
const { sportName } = useParams<{ sportName: string }>() |
||||||
|
|
||||||
|
return { |
||||||
|
sportName, |
||||||
|
sportType: SportTypes[toUpper(sportName) as SportName], |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
import { |
||||||
|
DATA_URL, |
||||||
|
PROCEDURES, |
||||||
|
SportTypes, |
||||||
|
} from 'config' |
||||||
|
import { callApi, getResponseData } from 'helpers' |
||||||
|
|
||||||
|
const proc = PROCEDURES.get_player_info |
||||||
|
|
||||||
|
export type PlayerProfile = { |
||||||
|
c_gender: 1 | 2 | null, |
||||||
|
club_team: { |
||||||
|
id: number, |
||||||
|
name_eng: string, |
||||||
|
name_rus: string, |
||||||
|
} | null, |
||||||
|
firstname_eng: string, |
||||||
|
firstname_rus: string, |
||||||
|
height: number | null, |
||||||
|
id: number, |
||||||
|
is_favorite: boolean, |
||||||
|
is_gk: boolean | null, |
||||||
|
lastname_eng: string, |
||||||
|
lastname_rus: string, |
||||||
|
nickname_eng: string | null, |
||||||
|
nickname_rus: string | null, |
||||||
|
weight: number | null, |
||||||
|
} | null |
||||||
|
|
||||||
|
export const getPlayerInfo = ( |
||||||
|
playerId: number, |
||||||
|
sportType: SportTypes, |
||||||
|
): Promise<PlayerProfile> => { |
||||||
|
const config = { |
||||||
|
body: { |
||||||
|
params: { |
||||||
|
_p_player_id: playerId, |
||||||
|
_p_sport: sportType, |
||||||
|
}, |
||||||
|
proc, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return callApi({ |
||||||
|
config, |
||||||
|
url: DATA_URL, |
||||||
|
}).then(getResponseData(proc)) |
||||||
|
} |
||||||
Loading…
Reference in new issue