Ott 1111 redesign (#435)
* refactor(1115): added some design tokens (#362) * style(1115): login page redesign (#363) * style(1115): password reset popup (#364) * Ott 1117 part 1 (#365) * style(1115): redesign * refactor(1115): auth refactor * refactor(1115): removed old registration step components (#366) * style(1114): user favorites (#367) * style(1114): user favorites * Update src/features/UserFavorites/TooltipBlock/styled.tsx Co-authored-by: Andrey Razdorskiy <quitesocial@yandex.ru> * Update src/features/UserFavorites/TooltipBlock/styled.tsx Co-authored-by: Andrey Razdorskiy <quitesocial@yandex.ru> Co-authored-by: Andrey Razdorskiy <quitesocial@yandex.ru> * Ott 1112 header (#372) * style(1112): removed old components * style(1112): header redesign (#369) * style(1112): wip (#370) * style(1112): date filter (#371) Co-authored-by: Ruslan Khayrullin <ruslfm08@gmail.com> * Ott 1133 profile header color (#374) * refactor(1133): removed unused AvailableMatches * feat(1133): dynamic header gradient * Ott 1113 home page (#375) * fix(1113): date filter day click fix * style(1113): match list * fix(1113): comment fix * fix(1113): date filter bug fix * style(#ott1167): responsive styles added (#377) Co-authored-by: Farber Denis <denis.farber@instatsport.com> * Ott 1186 match page (#381) * refactor: removed extended search page (#379) * Ott 1186 part 2 (#380) * style: match page redesign * style: restyled right playlists block * style: responsive date filter * fix(1197): display country flag in match card (#383) * style(1188): reduced match preview brightness (#384) * fix(1191): hover on team logos (#386) * Ott 1190 remove match popup (#385) * refactor(1190): redirecting to match profile * refactor(1190): removed finished match popup * fix(1190): player, two player stay active on last episode * style(1121): profile cards (#387) * style(1212): changed match preview opacity (#389) * Ott 1119 search (#388) * refactor(1119): removed SportType filter * refactor(1119): search redesign * feat(ott-273): scaling every element on the site (#391) * feat(ott-273): scaling every element on the site * refactor(ott-273): short fix for PR * fix(1111): fixed styled theme color reference (#392) * Ott 1211 finished match side block (#396) * refactor(1211): finished match right block redesign * feat(ott-1211): added lexics to match side playlists (#394) * refactor(ott-1211): added lexics to side playlists * refactor(ott-1211): refactor Co-authored-by: Mirlan <m.maksitaliev@gmail.com> * refactor(ott-1124): live match popup new design (#398) * Ott 1127 user account redesign (#402) * refactor(1127): removed subs select modal in user account (#397) * style(1127): user account redesign (#399) * Ott 1127 part 3 (#400) * refactor(1127): updated form fields * refactor(1127): added password inputs * fix(1127): fixed types * Ott 1127 part 4 (#401) * fix(1127): design fixes * fix(1127): language change * fix(1127): combobox fix * fix(1276): video endpoint change (#403) * Ott 1126 buy match popup (#406) * fix(ott-1280): return match overview (#407) * Ott 1249 auth mobile (#408) * feat(ott-1295): added some changes for preprod build (#409) * fix(ott-1295): small fix (#410) * Ott 1250 registration mobile (#411) * fix(ott-1341): fixed live match label position (#412) * fix(1345): close popup on live match click (#413) * fix(ott-1290): fix switch color (#415) Co-authored-by: Alex <alexnofoget@mail.ru> * fix(ott-1291): rm target blank on user favourites (#416) * fix(#1289): added adaptive(1370 px) for pay form (#417) * fix(#1289): added adaptive(1370 px) for pay form * refactor(#1289): added space * Ott 1282 buy match responsive (#414) * style(#1282): buy match page responcive styles added * style(#1282): buy match responsive styles added * style(#1282): cards styles fixes Co-authored-by: Farber Denis <denis.farber@instatsport.com> * feat(ott-1388): added env to configure api url (#418) * Ott 1387 main page mobile (#419) * style(#1387): main page mobile styles added part 1 * style(#1387): datepicker styles added part 2 * style(#1387): search bar styles fix * style(#1387): fixed match time icon * style(#1387): comments fix Co-authored-by: Farber Denis <denis.farber@instatsport.com> * fix(ott-1348): show team name abbrs (#420) * fix(ott-1409): add tournament link (#421) * feat(ott-1414): added make prod script (#422) * fix(ott-1413): hide fb google login (#424) * fix(1407): fixed switch colors (#423) * Ott 1371 hide pay tab (#425) * feat(ott-1371): hide unused pay tabs * feat(ott-1371): code review fix * feat(ott-1371): code review fix again * feat(ott-1371): add use memo and correct types * Ott 1285 video player mobile (#426) * style(#1285): video player mobile styles added * style(#1285): min styles fixes Co-authored-by: Farber Denis <denis.farber@instatsport.com> * Ott 1394 logging (#427) * feat(ott-1394): page change logging * feat(1394): player playlist change logging * style(#1422): min style fixes (#428) Co-authored-by: Farber Denis <denis.farber@instatsport.com> * style(#1423): video player play button fixed (#429) Co-authored-by: Farber Denis <denis.farber@instatsport.com> * fix(1396): player fix on ios (#430) * style(#1431): fixed live popup (#431) Co-authored-by: Farber Denis <denis.farber@instatsport.com> * style(fix): payment block style fix (#432) Co-authored-by: Farber Denis <denis.farber@instatsport.com> * fix(1434): play hls until has video true (#433) * fix(1373): show/hide card form labels (#434) Co-authored-by: Andrey Razdorskiy <quitesocial@yandex.ru> Co-authored-by: Ruslan Khayrullin <ruslfm08@gmail.com> Co-authored-by: Farber Denis <42491613+Bombamuerta@users.noreply.github.com> Co-authored-by: Farber Denis <denis.farber@instatsport.com> Co-authored-by: Sergiu <46888793+Serj10GR@users.noreply.github.com> Co-authored-by: Иван Пиминов <61900450+ivan-piminov@users.noreply.github.com> Co-authored-by: Alex <alexnofoget@mail.ru> Co-authored-by: Serg <936x936@gmail.com> Co-authored-by: KarelinDm <58763564+KarelinDm@users.noreply.github.com>keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
|
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 215 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 742 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 604 B |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 873 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 430 B |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@ -1,3 +1,5 @@ |
||||
export const isProduction = process.env.REACT_APP_PRODUCTION === 'true' |
||||
export const ENV = process.env.REACT_APP_ENV || 'staging' |
||||
|
||||
export const isProduction = ENV === 'production' || ENV === 'preproduction' |
||||
|
||||
export const STRIPE_PUBLIC_KEY = process.env.REACT_APP_STRIPE_PK || 'pk_test_fkEjSoWfJXuCwMgwHRpbOGPt' |
||||
|
||||
@ -1,2 +1,10 @@ |
||||
export const API_ROOT = 'https://api.instat.tv' |
||||
import { ENV } from './env' |
||||
|
||||
const APIS = { |
||||
preproduction: 'https://api-test.instat.tv', |
||||
production: 'https://api.instat.tv', |
||||
staging: 'https://api-staging.instat.tv', |
||||
} |
||||
|
||||
export const API_ROOT = APIS[ENV] |
||||
export const DATA_URL = `${API_ROOT}/data` |
||||
|
||||
@ -0,0 +1,5 @@ |
||||
import { includes } from 'lodash' |
||||
|
||||
export const isMobileDevice = includes(window.navigator.userAgent, 'Android') || includes(window.navigator.userAgent, 'iPhone') |
||||
// удалю когда закончу с адаптивом.
|
||||
// || includes(window.navigator.userAgent, 'Linux')
|
||||
@ -1,36 +1,38 @@ |
||||
import { T9n } from 'features/T9n' |
||||
import { |
||||
Header, |
||||
HeaderTitle, |
||||
HeaderActions, |
||||
CloseButton, |
||||
} from 'features/PopupComponents' |
||||
import { Header, HeaderTitle } from 'features/PopupComponents' |
||||
|
||||
import { useBuyMatchPopupStore } from '../../store' |
||||
import { |
||||
Wrapper, |
||||
Body, |
||||
Footer, |
||||
ResultText, |
||||
SmallButton, |
||||
} from '../../styled' |
||||
import { useBuyMatchPopupStore } from '../../store' |
||||
|
||||
export const ErrorStep = () => { |
||||
const { close, error: paymentError } = useBuyMatchPopupStore() |
||||
const { |
||||
close, |
||||
error: paymentError, |
||||
} = useBuyMatchPopupStore() |
||||
|
||||
return ( |
||||
<Wrapper width={517}> |
||||
<Header height={50}> |
||||
<Wrapper height={278} width={369}> |
||||
<Header height={24}> |
||||
<HeaderTitle> |
||||
<T9n t='payment' /> |
||||
</HeaderTitle> |
||||
<HeaderActions position='right'> |
||||
<CloseButton onClick={close} /> |
||||
</HeaderActions> |
||||
</Header> |
||||
<Body marginTop={30} marginBottom={35}> |
||||
<Body marginTop={30} marginBottom={40}> |
||||
<ResultText> |
||||
{paymentError || <T9n t='error_payment_unsuccessful' />} |
||||
</ResultText> |
||||
</Body> |
||||
<Footer> |
||||
<SmallButton onClick={close}> |
||||
Ок |
||||
</SmallButton> |
||||
</Footer> |
||||
</Wrapper> |
||||
) |
||||
} |
||||
|
||||
@ -0,0 +1,23 @@ |
||||
export const passLexics = { |
||||
all: 'pass_league', |
||||
team_away: 'pass_team', |
||||
team_home: 'pass_team', |
||||
team1: 'pass_team', |
||||
team2: 'pass_team', |
||||
} |
||||
|
||||
export const descriptionLexics = { |
||||
all: 'description_all_season_matches', |
||||
team_away: 'description_away_team_matches', |
||||
team_home: 'description_home_team_matches', |
||||
team1: 'description_all_team_matches', |
||||
team2: 'description_all_team_matches', |
||||
} |
||||
|
||||
export const passNameKeys = { |
||||
all: 'tournament', |
||||
team_away: 'team2', |
||||
team_home: 'team1', |
||||
team1: 'team1', |
||||
team2: 'team2', |
||||
} as const |
||||
@ -1,30 +1,98 @@ |
||||
import map from 'lodash/map' |
||||
import reduce from 'lodash/reduce' |
||||
|
||||
import { currencySymbols } from 'config' |
||||
|
||||
import type { MatchSubscriptionsResponse } from 'requests' |
||||
import type { |
||||
SubscriptionsByPeriods, |
||||
Subscriptions, |
||||
} from 'requests/getSubscriptions' |
||||
|
||||
import { SubscriptionType, MatchSubscriptions } from '../types' |
||||
import type { Match } from 'features/Matches' |
||||
import { getName } from 'features/Name' |
||||
|
||||
export const transformSubsciptions = ( |
||||
subscriptions: MatchSubscriptionsResponse, |
||||
) => ( |
||||
reduce( |
||||
subscriptions, |
||||
( |
||||
acc, |
||||
periodSubscriptions, |
||||
import type { MatchSubscriptions, MatchSubscription } from '../types' |
||||
import { SubscriptionType } from '../types' |
||||
import { |
||||
passLexics, |
||||
passNameKeys, |
||||
descriptionLexics, |
||||
} from './config' |
||||
|
||||
type SubscriptionArgs = { |
||||
match: Match, |
||||
subscriptions: SubscriptionsByPeriods, |
||||
suffix: string, |
||||
} |
||||
|
||||
const transformSubscription = ({ |
||||
match, |
||||
subscriptions, |
||||
suffix, |
||||
}: SubscriptionArgs) => ( |
||||
type: SubscriptionType.Month | SubscriptionType.Year, |
||||
): Array<MatchSubscription> => { |
||||
const { season } = subscriptions |
||||
return map(subscriptions[type], (subscription, rawKey) => { |
||||
const key = rawKey as keyof Subscriptions |
||||
const teamName = getName({ |
||||
nameObj: match[passNameKeys[key]], |
||||
suffix, |
||||
}) |
||||
return { |
||||
currency: currencySymbols[subscription.currency_iso], |
||||
description: { |
||||
lexic: descriptionLexics[key], |
||||
values: { |
||||
season: season.name, |
||||
team: teamName, |
||||
}, |
||||
}, |
||||
id: `${key} ${subscription.id}`, |
||||
name: teamName, |
||||
originalObject: subscription, |
||||
pass: passLexics[key], |
||||
price: subscription.price, |
||||
seasonName: season.name, |
||||
type, |
||||
) => { |
||||
const period = type as SubscriptionType |
||||
acc[period] = map(periodSubscriptions, (subscription) => ({ |
||||
currency: currencySymbols[subscription.currency_iso || 'RUB'], |
||||
type: period, |
||||
...subscription, |
||||
})) |
||||
return acc |
||||
}, |
||||
{} as MatchSubscriptions, |
||||
) |
||||
) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
type SubsciptionsArgs = { |
||||
match: Match, |
||||
subscriptions: SubscriptionsByPeriods, |
||||
suffix: string, |
||||
} |
||||
|
||||
export const transformSubsciptions = ({ |
||||
match, |
||||
subscriptions, |
||||
suffix, |
||||
}: SubsciptionsArgs): MatchSubscriptions => { |
||||
const { pay_per_view: payPerView } = subscriptions |
||||
const team1Name = getName({ nameObj: match.team1, suffix }) |
||||
const team2Name = getName({ nameObj: match.team2, suffix }) |
||||
const transformByType = transformSubscription({ |
||||
match, |
||||
subscriptions, |
||||
suffix, |
||||
}) |
||||
|
||||
return { |
||||
[SubscriptionType.Month]: transformByType(SubscriptionType.Month), |
||||
[SubscriptionType.Year]: transformByType(SubscriptionType.Year), |
||||
[SubscriptionType.PayPerView]: [{ |
||||
currency: currencySymbols[payPerView.currency_iso], |
||||
description: { |
||||
lexic: 'description_match_live_and_on_demand', |
||||
values: {}, |
||||
}, |
||||
id: '0', |
||||
name: `${team1Name} - ${team2Name}`, |
||||
originalObject: payPerView, |
||||
pass: 'pass_match_access', |
||||
price: payPerView.price, |
||||
type: SubscriptionType.PayPerView, |
||||
}], |
||||
} |
||||
} |
||||
|
||||
@ -1,25 +0,0 @@ |
||||
import { useCallback } from 'react' |
||||
|
||||
import isEmpty from 'lodash/isEmpty' |
||||
import flatMap from 'lodash/flatMap' |
||||
import map from 'lodash/map' |
||||
|
||||
import { useLexicsStore } from 'features/LexicsStore' |
||||
|
||||
import type { MatchSubscriptions } from '../../types' |
||||
|
||||
export const useLexicsFetcher = () => { |
||||
const { addLexicsConfig } = useLexicsStore() |
||||
const fetchLexics = useCallback((subscriptions: MatchSubscriptions) => { |
||||
const lexics = flatMap( |
||||
subscriptions, |
||||
(periodSubscriptions) => map(periodSubscriptions, ({ lexic }) => lexic), |
||||
) |
||||
if (!isEmpty(lexics)) { |
||||
addLexicsConfig(lexics) |
||||
} |
||||
return subscriptions |
||||
}, [addLexicsConfig]) |
||||
|
||||
return { fetchLexics } |
||||
} |
||||
@ -1,37 +0,0 @@ |
||||
import styled from 'styled-components/macro' |
||||
|
||||
const ArrowStyled = styled.button` |
||||
width: 40px; |
||||
height: 40px; |
||||
padding: 0; |
||||
border: 1px solid transparent; |
||||
border-radius: 50%; |
||||
background-color: transparent; |
||||
background-position: center; |
||||
outline: none; |
||||
z-index: 1; |
||||
position: absolute; |
||||
cursor: pointer; |
||||
|
||||
:active { |
||||
background-color: rgba(117, 117, 117, 1); |
||||
} |
||||
|
||||
:hover, :focus { |
||||
background-color: rgba(117, 117, 117, 0.5); |
||||
} |
||||
` |
||||
|
||||
export const ArrowLeft = styled(ArrowStyled)` |
||||
background-image: url(/images/arrowLeft.svg); |
||||
top: 50%; |
||||
left: 0px; |
||||
transform: translate(-10px, -50%); |
||||
` |
||||
|
||||
export const ArrowRight = styled(ArrowStyled)` |
||||
background-image: url(/images/arrowRight.svg); |
||||
top: 50%; |
||||
right: 0px; |
||||
transform: translate(10px, -50%); |
||||
` |
||||
@ -1,21 +0,0 @@ |
||||
import { ArrowLeft, ArrowRight } from 'features/Common' |
||||
|
||||
const Story = { |
||||
component: ArrowLeft, |
||||
title: 'Arrows', |
||||
} |
||||
|
||||
export default Story |
||||
|
||||
const backgroundStyles = { |
||||
backgroundColor: '#333', |
||||
height: '200px', |
||||
padding: '20px', |
||||
} |
||||
|
||||
export const Group = () => ( |
||||
<div style={backgroundStyles}> |
||||
<ArrowLeft /> |
||||
<ArrowRight /> |
||||
</div> |
||||
) |
||||
@ -0,0 +1,14 @@ |
||||
import styled, { css } from 'styled-components/macro' |
||||
import { isMobileDevice } from 'config/userAgent' |
||||
|
||||
export const InputGroup = styled.div` |
||||
width: 100%; |
||||
box-shadow: ${({ theme }) => theme.colors.shadow}; |
||||
border-radius: 14px; |
||||
overflow: hidden; |
||||
${isMobileDevice |
||||
? css` |
||||
border-radius: 8px; |
||||
` |
||||
: ''};
|
||||
` |
||||
@ -0,0 +1,55 @@ |
||||
import type { InputHTMLAttributes, ReactNode } from 'react' |
||||
|
||||
import { useLexicsStore } from 'features/LexicsStore' |
||||
|
||||
import { |
||||
Wrapper, |
||||
InputStyled, |
||||
Label, |
||||
} from './styled' |
||||
|
||||
type Props = Pick<InputHTMLAttributes<HTMLInputElement>, ( |
||||
'className' | |
||||
'onBlur' | |
||||
'onChange' | |
||||
'onFocus' | |
||||
'type' | |
||||
'placeholder' | |
||||
'value' |
||||
)> & { |
||||
leftContent?: ReactNode, |
||||
placeholderLexic?: string, |
||||
rightContent?: ReactNode, |
||||
} |
||||
|
||||
export const NewInput = ({ |
||||
className, |
||||
leftContent, |
||||
onBlur, |
||||
onChange, |
||||
onFocus, |
||||
placeholder, |
||||
placeholderLexic = '', |
||||
rightContent, |
||||
type, |
||||
value, |
||||
}: Props) => { |
||||
const { translate } = useLexicsStore() |
||||
|
||||
return ( |
||||
<Wrapper className={className}> |
||||
<Label> |
||||
{leftContent} |
||||
<InputStyled |
||||
onBlur={onBlur} |
||||
onChange={onChange} |
||||
onFocus={onFocus} |
||||
placeholder={placeholder ?? translate(placeholderLexic)} |
||||
type={type} |
||||
value={value} |
||||
/> |
||||
{rightContent} |
||||
</Label> |
||||
</Wrapper> |
||||
) |
||||
} |
||||
@ -0,0 +1,84 @@ |
||||
import styled, { css } from 'styled-components/macro' |
||||
|
||||
import { isMobileDevice } from 'config/userAgent' |
||||
|
||||
export const Wrapper = styled.div` |
||||
background-color: ${({ theme }) => theme.colors.inputs}; |
||||
width: 100%; |
||||
height: 2.123rem; |
||||
${isMobileDevice |
||||
? css` |
||||
height: 30px;
|
||||
min-height: 30px; |
||||
position: relative; |
||||
` |
||||
: ''}; |
||||
:not(:last-child) { |
||||
border-bottom: 0.5px solid ${({ theme }) => theme.colors.black40}; |
||||
} |
||||
` |
||||
|
||||
export const Label = styled.label` |
||||
display: flex; |
||||
align-items: center; |
||||
height: 100%; |
||||
color: rgba(255, 255, 255, 0.5); |
||||
font-weight: normal; |
||||
font-size: 16px; |
||||
line-height: 20px; |
||||
letter-spacing: -0.01em; |
||||
${isMobileDevice |
||||
? css` |
||||
font-size: 12px; |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
const resetStyles = css` |
||||
background-color: transparent; |
||||
border: none; |
||||
outline: none; |
||||
:-webkit-autofill, |
||||
:-webkit-autofill:hover, |
||||
:-webkit-autofill:focus, |
||||
:-webkit-autofill:active { |
||||
background-clip: text; |
||||
-webkit-background-clip: text; |
||||
-webkit-text-fill-color: #fff; |
||||
caret-color: #fff; |
||||
font-size: 16px; |
||||
} |
||||
` |
||||
|
||||
export const InputStyled = styled.input` |
||||
${resetStyles} |
||||
|
||||
padding: 0 0.755rem; |
||||
flex-grow: 1; |
||||
height: 100%; |
||||
color: ${({ theme }) => theme.colors.text100}; |
||||
|
||||
::placeholder { |
||||
color: rgba(255, 255, 255, 0.5); |
||||
font-weight: normal; |
||||
width: 100%; |
||||
color: ${({ theme }) => theme.colors.text100}; |
||||
font-style: normal; |
||||
font-weight: normal; |
||||
font-size: 0.755rem; |
||||
letter-spacing: 0.1px; |
||||
${isMobileDevice |
||||
? css` |
||||
font-size: 12.31px; |
||||
` |
||||
: ''} |
||||
} |
||||
${isMobileDevice |
||||
? css` |
||||
font-size: 12.31px; |
||||
padding: 0 30px 0 10px; |
||||
` |
||||
: ''}; |
||||
|
||||
font-weight: ${({ type }) => (type === 'password' ? 700 : 'normal')}; |
||||
` |
||||
@ -0,0 +1,58 @@ |
||||
import type { ComponentProps } from 'react' |
||||
|
||||
import styled, { css } from 'styled-components/macro' |
||||
|
||||
import { isMobileDevice } from 'config/userAgent' |
||||
|
||||
import { useToggle } from 'hooks' |
||||
|
||||
import { NewInput } from 'features/Common' |
||||
import { BaseButton } from 'features/PopupComponents' |
||||
|
||||
const VisibilityButton = styled(BaseButton)` |
||||
width: 1.14rem; |
||||
height: 1.14rem; |
||||
background-image: url(/images/visibility.svg); |
||||
opacity: 0.4; |
||||
background-color: transparent; |
||||
border-radius: 0; |
||||
margin-right: 0.5rem; |
||||
${isMobileDevice |
||||
? css` |
||||
min-width: 13.55px;
|
||||
height: 9.23px;
|
||||
right: 5px; |
||||
top: 50%; |
||||
transform: translateY(-50%); |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
type Props = ComponentProps<typeof NewInput> |
||||
|
||||
export const PasswordInput = ({ |
||||
className, |
||||
leftContent, |
||||
onBlur, |
||||
onChange, |
||||
onFocus, |
||||
placeholder, |
||||
placeholderLexic, |
||||
value, |
||||
}: Props) => { |
||||
const { isOpen: showPassword, toggle } = useToggle() |
||||
return ( |
||||
<NewInput |
||||
className={className} |
||||
onBlur={onBlur} |
||||
onChange={onChange} |
||||
onFocus={onFocus} |
||||
placeholder={placeholder} |
||||
placeholderLexic={placeholderLexic} |
||||
value={value} |
||||
type={showPassword ? 'text' : 'password'} |
||||
leftContent={leftContent} |
||||
rightContent={<VisibilityButton type='button' onClick={toggle} />} |
||||
/> |
||||
) |
||||
} |
||||
@ -1,93 +0,0 @@ |
||||
import styled, { css } from 'styled-components/macro' |
||||
|
||||
import { devices } from 'config/devices' |
||||
|
||||
export const RadioButtonGroup = styled.div.attrs({ |
||||
role: 'group', |
||||
})` |
||||
height: 100%; |
||||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); |
||||
border-radius: 2px; |
||||
overflow: hidden; |
||||
` |
||||
|
||||
type RadioButtonProps = { |
||||
buttonWidth?: number, |
||||
disabled?: boolean, |
||||
selected?: boolean, |
||||
upperCase?: boolean, |
||||
} |
||||
|
||||
export const RadioButton = styled.button.attrs(({ selected }: RadioButtonProps) => ({ |
||||
'aria-pressed': selected, |
||||
}))<RadioButtonProps>` |
||||
border: none; |
||||
outline: none; |
||||
height: 100%; |
||||
width: ${({ buttonWidth }) => (buttonWidth ? `${buttonWidth}px` : '')}; |
||||
font-weight: 600; |
||||
font-size: 18px; |
||||
cursor: pointer; |
||||
|
||||
${({ selected }) => ( |
||||
selected |
||||
? css` |
||||
background-color: #666666; |
||||
color: #ffffff; |
||||
|
||||
@media ${devices.tablet} { |
||||
:first-child { |
||||
background-color: #CC0000; |
||||
} |
||||
:nth-child(2){ |
||||
background-color: #EACB6F; |
||||
color: #000; |
||||
} |
||||
:last-child { |
||||
background: transparent; |
||||
color: #ffffff; |
||||
border: 1px solid #fff; |
||||
} |
||||
|
||||
} |
||||
` |
||||
: css` |
||||
background-color: #3F3F3F; |
||||
color: #999999; |
||||
|
||||
:hover { |
||||
background-color: #484848; |
||||
} |
||||
` |
||||
)} |
||||
|
||||
${({ upperCase }) => ( |
||||
upperCase ? 'text-transform: uppercase;' : '' |
||||
)} |
||||
|
||||
${({ disabled }) => ( |
||||
disabled |
||||
&& css` |
||||
pointer-events: none; |
||||
opacity: 0.7; |
||||
` |
||||
)} |
||||
|
||||
:not(:last-child) { |
||||
border-right: 1px solid #222222; |
||||
} |
||||
|
||||
@media ${devices.tablet} { |
||||
width: 105px; |
||||
height: 30px; |
||||
font-size: 13px; |
||||
color: #fff; |
||||
background-color: rgba(153, 153, 153, 0.5); |
||||
border-right: 0; |
||||
margin-right: 10px; |
||||
|
||||
:last-child { |
||||
margin-right: 0; |
||||
} |
||||
} |
||||
` |
||||
@ -1,22 +0,0 @@ |
||||
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,86 @@ |
||||
import styled, { css } from 'styled-components/macro' |
||||
|
||||
import { isMobileDevice } from 'config/userAgent' |
||||
|
||||
type TabProps = { |
||||
disabled?: boolean, |
||||
selected?: boolean, |
||||
upperCase?: boolean, |
||||
} |
||||
|
||||
export const Tab = styled.button.attrs(({ selected }: TabProps) => ({ |
||||
'aria-pressed': selected, |
||||
}))<TabProps>` |
||||
border: none; |
||||
outline: none; |
||||
height: 100%; |
||||
padding-top: 0.38rem; |
||||
padding-bottom: 0.27rem; |
||||
font-weight: 600; |
||||
font-size: 0.519rem; |
||||
line-height: 0.85rem; |
||||
letter-spacing: -0.1px; |
||||
text-transform: uppercase; |
||||
cursor: pointer; |
||||
|
||||
${({ selected, theme }) => ( |
||||
selected |
||||
? css` |
||||
background-color: #FFFFFF; |
||||
color: ${theme.colors.black}; |
||||
` |
||||
: css` |
||||
background-color: transparent; |
||||
color: #FFFFFF; |
||||
|
||||
:hover { |
||||
background-color: #484848; |
||||
} |
||||
` |
||||
)} |
||||
|
||||
${({ upperCase }) => ( |
||||
upperCase ? 'text-transform: uppercase;' : '' |
||||
)} |
||||
|
||||
${({ disabled }) => ( |
||||
disabled |
||||
&& css` |
||||
pointer-events: none; |
||||
opacity: 0.7; |
||||
` |
||||
)} |
||||
|
||||
:not(:last-child) { |
||||
border-right: 1px solid #FFFFFF; |
||||
} |
||||
${isMobileDevice |
||||
? css` |
||||
font-size: 10px; |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
type TabGroupProps = { |
||||
buttons: number, |
||||
} |
||||
|
||||
export const TabsGroup = styled.div.attrs({ |
||||
role: 'group', |
||||
})<TabGroupProps>` |
||||
width: 100%; |
||||
height: 100%; |
||||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); |
||||
overflow: hidden; |
||||
border: 1px solid #FFFFFF; |
||||
border-radius: 2px; |
||||
|
||||
${Tab} { |
||||
width: ${({ buttons }) => 100 / buttons}%; |
||||
} |
||||
${isMobileDevice |
||||
? css` |
||||
height: 28px; |
||||
` |
||||
: ''}; |
||||
` |
||||
@ -1,11 +1,12 @@ |
||||
export * from './Input' |
||||
export * from './NewInput' |
||||
export * from './PasswordInput' |
||||
export * from './InputGroup' |
||||
export * from './Button' |
||||
export * from './Radio' |
||||
export * from './Checkbox' |
||||
export * from './Arrows' |
||||
export * from './SportName' |
||||
export * from './StarIcon' |
||||
export * from './customScrollbar' |
||||
export * from './customStyles' |
||||
export * from './Image' |
||||
export * from './RadioButtons' |
||||
export * from './Tabs' |
||||
|
||||
@ -1,24 +0,0 @@ |
||||
import { Menu } from 'features/Menu' |
||||
import { LanguageSelect } from 'features/LanguageSelect' |
||||
import { |
||||
HeaderStyled, |
||||
HeaderGroup, |
||||
MenuWrapper, |
||||
} from 'features/ProfileHeader/styled' |
||||
|
||||
import { Filters } from '../Filters' |
||||
|
||||
export const DesktopHeader = () => ( |
||||
<HeaderStyled> |
||||
<HeaderGroup> |
||||
<Filters /> |
||||
</HeaderGroup> |
||||
|
||||
<HeaderGroup> |
||||
<MenuWrapper> |
||||
<Menu /> |
||||
</MenuWrapper> |
||||
<LanguageSelect /> |
||||
</HeaderGroup> |
||||
</HeaderStyled> |
||||
) |
||||
@ -1,29 +0,0 @@ |
||||
import { Fragment } from 'react' |
||||
|
||||
import { SportTypeFilter } from '../SportTypeFilter' |
||||
import { SearchInput } from '../SearchInput' |
||||
import { GenderFilter } from '../GenderFilter' |
||||
import { ProfileFilter } from '../ProfileFilter' |
||||
|
||||
import { |
||||
SportFilterWrapper, |
||||
FilterWrapper, |
||||
} from './styled' |
||||
|
||||
export const Filters = () => ( |
||||
<Fragment> |
||||
<FilterWrapper> |
||||
<SearchInput /> |
||||
</FilterWrapper> |
||||
|
||||
<SportFilterWrapper> |
||||
<SportTypeFilter /> |
||||
</SportFilterWrapper> |
||||
<FilterWrapper> |
||||
<GenderFilter /> |
||||
</FilterWrapper> |
||||
<FilterWrapper> |
||||
<ProfileFilter /> |
||||
</FilterWrapper> |
||||
</Fragment> |
||||
) |
||||
@ -1,43 +0,0 @@ |
||||
import styled from 'styled-components/macro' |
||||
|
||||
import { devices } from 'config' |
||||
|
||||
export const BaseWrapper = styled.div` |
||||
position: relative; |
||||
height: 48px; |
||||
margin-right: 16px; |
||||
display: flex; |
||||
` |
||||
|
||||
export const SportFilterWrapper = styled(BaseWrapper)` |
||||
width: 173px; |
||||
|
||||
@media ${devices.tablet} { |
||||
width: auto; |
||||
margin: 20px 14px 0 14px; |
||||
justify-content: center; |
||||
} |
||||
` |
||||
|
||||
export const FilterWrapper = styled(BaseWrapper)` |
||||
width: auto; |
||||
|
||||
@media ${devices.tablet} { |
||||
height: 30px; |
||||
margin: 20px 20vw 0 20vw; |
||||
justify-content: center; |
||||
} |
||||
` |
||||
|
||||
export const SearchInput = styled.input` |
||||
width: 100%; |
||||
padding-left: 20px; |
||||
padding-right: 20px; |
||||
font-weight: bold; |
||||
font-size: 18px; |
||||
background-color: #3F3F3F; |
||||
color: #fff; |
||||
border: transparent; |
||||
border-color: transparent; |
||||
outline: none; |
||||
` |
||||
@ -1,34 +0,0 @@ |
||||
import { Gender } from 'requests' |
||||
|
||||
import { T9n } from 'features/T9n' |
||||
import { |
||||
RadioButtonGroup, |
||||
RadioButton, |
||||
} from 'features/Common' |
||||
import { useExtendedSearchStore } from 'features/ExtendedSearchPage/store' |
||||
|
||||
export const GenderFilter = () => { |
||||
const { |
||||
onGenderChange, |
||||
selectedGender, |
||||
} = useExtendedSearchStore() |
||||
|
||||
return ( |
||||
<RadioButtonGroup> |
||||
<RadioButton |
||||
selected={selectedGender === Gender.MALE} |
||||
onClick={() => onGenderChange(Gender.MALE)} |
||||
buttonWidth={122} |
||||
> |
||||
<T9n t='gender_male_long' /> |
||||
</RadioButton> |
||||
<RadioButton |
||||
selected={selectedGender === Gender.FEMALE} |
||||
onClick={() => onGenderChange(Gender.FEMALE)} |
||||
buttonWidth={122} |
||||
> |
||||
<T9n t='gender_female_long' /> |
||||
</RadioButton> |
||||
</RadioButtonGroup> |
||||
) |
||||
} |
||||
@ -1,31 +0,0 @@ |
||||
import { Fragment } from 'react' |
||||
import { Link } from 'react-router-dom' |
||||
|
||||
import { PAGES } from 'config' |
||||
|
||||
import { Logo } from 'features/Logo' |
||||
import { Menu } from 'features/Menu' |
||||
|
||||
import { |
||||
HeaderMobileWrapper, |
||||
HeaderIconsWrapper, |
||||
IconFavWrapper, |
||||
} from 'features/HeaderMobile/styled' |
||||
|
||||
import { Filters } from '../Filters' |
||||
|
||||
export const MobileHeader = () => ( |
||||
<Fragment> |
||||
<HeaderMobileWrapper> |
||||
<Link to={PAGES.home}> |
||||
<Logo width={52} height={12} /> |
||||
</Link> |
||||
<HeaderIconsWrapper> |
||||
<IconFavWrapper /> |
||||
<Menu /> |
||||
</HeaderIconsWrapper> |
||||
</HeaderMobileWrapper> |
||||
|
||||
<Filters /> |
||||
</Fragment> |
||||
) |
||||
@ -1,42 +0,0 @@ |
||||
import { ProfileTypes } from 'config' |
||||
|
||||
import { T9n } from 'features/T9n' |
||||
import { |
||||
RadioButtonGroup, |
||||
RadioButton, |
||||
} from 'features/Common' |
||||
|
||||
import { useExtendedSearchStore } from '../../store' |
||||
|
||||
export const ProfileFilter = () => { |
||||
const { |
||||
onProfileChange, |
||||
selectedProfile, |
||||
} = useExtendedSearchStore() |
||||
|
||||
return ( |
||||
<RadioButtonGroup> |
||||
<RadioButton |
||||
selected={selectedProfile === ProfileTypes.TEAMS} |
||||
onClick={() => onProfileChange(ProfileTypes.TEAMS)} |
||||
buttonWidth={122} |
||||
> |
||||
<T9n t='team' /> |
||||
</RadioButton> |
||||
<RadioButton |
||||
selected={selectedProfile === ProfileTypes.PLAYERS} |
||||
onClick={() => onProfileChange(ProfileTypes.PLAYERS)} |
||||
buttonWidth={122} |
||||
> |
||||
<T9n t='player' /> |
||||
</RadioButton> |
||||
<RadioButton |
||||
selected={selectedProfile === ProfileTypes.TOURNAMENTS} |
||||
onClick={() => onProfileChange(ProfileTypes.TOURNAMENTS)} |
||||
buttonWidth={122} |
||||
> |
||||
<T9n t='tournament' /> |
||||
</RadioButton> |
||||
</RadioButtonGroup> |
||||
) |
||||
} |
||||
@ -1,44 +0,0 @@ |
||||
import { Fragment, memo } from 'react' |
||||
|
||||
import { isEmpty } from 'lodash' |
||||
|
||||
import { ProfileTypes } from 'config' |
||||
|
||||
import type { NormalizedSearchResults } from 'features/Search/helpers' |
||||
|
||||
import { |
||||
Title, |
||||
ResultsList, |
||||
} from '../../styled' |
||||
|
||||
type Props = { |
||||
results: NormalizedSearchResults, |
||||
} |
||||
|
||||
export const Results = memo(({ results }: Props) => { |
||||
const hasResults = ( |
||||
!isEmpty(results.players) |
||||
|| !isEmpty(results.teams) |
||||
|| !isEmpty(results.tournaments) |
||||
) |
||||
if (hasResults) { |
||||
return ( |
||||
<Fragment> |
||||
<Title t='search_results' /> |
||||
<ResultsList |
||||
list={results.teams} |
||||
profileType={ProfileTypes.TEAMS} |
||||
/> |
||||
<ResultsList |
||||
list={results.players} |
||||
profileType={ProfileTypes.PLAYERS} |
||||
/> |
||||
<ResultsList |
||||
list={results.tournaments} |
||||
profileType={ProfileTypes.TOURNAMENTS} |
||||
/> |
||||
</Fragment> |
||||
) |
||||
} |
||||
return null |
||||
}) |
||||
@ -1,54 +0,0 @@ |
||||
import { Fragment } from 'react' |
||||
|
||||
import styled from 'styled-components/macro' |
||||
|
||||
import { Loader } from 'features/Loader' |
||||
import { useExtendedSearchStore } from 'features/ExtendedSearchPage/store' |
||||
|
||||
export const LoaderWrapper = styled.div` |
||||
position: absolute; |
||||
top: 0; |
||||
background-color: rgba(129, 129, 129, 0.5); |
||||
width: 100%; |
||||
height: 100%; |
||||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); |
||||
border-radius: 2px; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
` |
||||
|
||||
const Input = styled.input` |
||||
width: 100%; |
||||
padding-left: 20px; |
||||
padding-right: 20px; |
||||
font-weight: bold; |
||||
font-size: 18px; |
||||
background-color: #3F3F3F; |
||||
color: #fff; |
||||
border: transparent; |
||||
border-color: transparent; |
||||
outline: none; |
||||
` |
||||
|
||||
export const SearchInput = () => { |
||||
const { |
||||
isFetching, |
||||
onQueryChange, |
||||
query, |
||||
} = useExtendedSearchStore() |
||||
return ( |
||||
<Fragment> |
||||
<Input |
||||
role='search' |
||||
value={query} |
||||
onChange={onQueryChange} |
||||
/> |
||||
{isFetching && ( |
||||
<LoaderWrapper> |
||||
<Loader color='#515151' /> |
||||
</LoaderWrapper> |
||||
)} |
||||
</Fragment> |
||||
) |
||||
} |
||||
@ -1,17 +0,0 @@ |
||||
import { SportTypeFilter as Filter } from 'features/SportTypeFilter' |
||||
|
||||
import { useExtendedSearchStore } from '../../store' |
||||
|
||||
export const SportTypeFilter = () => { |
||||
const { |
||||
onSportChange, |
||||
selectedSport, |
||||
} = useExtendedSearchStore() |
||||
return ( |
||||
<Filter |
||||
selectedSportId={selectedSport} |
||||
onSelect={onSportChange} |
||||
onReset={() => onSportChange(null)} |
||||
/> |
||||
) |
||||
} |
||||
@ -1,41 +0,0 @@ |
||||
import styled from 'styled-components/macro' |
||||
|
||||
import { devices } from 'config' |
||||
|
||||
import { T9n } from 'features/T9n' |
||||
import { ItemsList } from 'features/ItemsList' |
||||
import { GenderComponent, StyledLink } from 'features/ItemsList/styled' |
||||
|
||||
export const Main = styled.main` |
||||
margin-top: 75px; |
||||
margin-bottom: 30px; |
||||
|
||||
@media ${devices.tablet} { |
||||
margin-top: 30px; |
||||
} |
||||
` |
||||
|
||||
export const Title = styled(T9n)` |
||||
display: block; |
||||
font-weight: bold; |
||||
font-size: 36px; |
||||
line-height: 24px; |
||||
color: #fff; |
||||
margin-bottom: 30px; |
||||
padding: 0 14px; |
||||
` |
||||
|
||||
export const ResultsList = styled(ItemsList)` |
||||
${StyledLink} { |
||||
background-color: transparent; |
||||
|
||||
:focus-within, |
||||
:hover { |
||||
background-color: rgba(153, 153, 153, 0.4); |
||||
} |
||||
} |
||||
|
||||
${GenderComponent} { |
||||
display: none; |
||||
} |
||||
` |
||||
@ -1,194 +1,203 @@ |
||||
import styled from 'styled-components/macro' |
||||
import styled, { css } from 'styled-components/macro' |
||||
|
||||
import { devices } from 'config/devices' |
||||
import { isMobileDevice } from 'config/userAgent' |
||||
import { devices } from 'config' |
||||
|
||||
type Props = { |
||||
active: boolean, |
||||
} |
||||
export const BaseButton = styled.button` |
||||
border: none; |
||||
outline: none; |
||||
padding: 0; |
||||
background-color: transparent; |
||||
cursor: pointer; |
||||
` |
||||
|
||||
export const Wrapper = styled.div<Props>` |
||||
export const Wrapper = styled.div` |
||||
position: relative; |
||||
height: 100%; |
||||
width: 100%; |
||||
width: 32.8rem; |
||||
display: flex; |
||||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
color: #656565; |
||||
|
||||
@media ${devices.tablet} { |
||||
width: auto; |
||||
@media ${devices.mobile} { |
||||
width: 100%; |
||||
} |
||||
|
||||
${({ active }) => (active |
||||
? ` |
||||
& ${Button}, ${DateButton} { |
||||
color: #fff; |
||||
background-color: #666666; |
||||
} |
||||
${isMobileDevice |
||||
? css` |
||||
justify-content: flex-end; |
||||
padding-bottom: 6px; |
||||
width: auto; |
||||
` |
||||
: '' |
||||
)}; |
||||
: ''}; |
||||
` |
||||
|
||||
export const DateButton = styled.button` |
||||
border: none; |
||||
outline: none; |
||||
padding: 0; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
text-align: left; |
||||
width: 192px; |
||||
height: 100%; |
||||
border-left: 1px solid #222222; |
||||
border-right: 1px solid #222222; |
||||
cursor: pointer; |
||||
background-color: #3F3F3F; |
||||
color: #999999; |
||||
export const MonthWrapper = styled.div` |
||||
position: relative; |
||||
text-align: center; |
||||
` |
||||
|
||||
@media ${devices.tablet} { |
||||
background-color: transparent; |
||||
color: #fff; |
||||
} |
||||
export const MonthYear = styled.span` |
||||
display: inline-block; |
||||
font-weight: 600; |
||||
font-size: 1.04rem; |
||||
line-height: 1.8rem; |
||||
text-transform: uppercase; |
||||
${isMobileDevice |
||||
? css` |
||||
font-size: 13px; |
||||
line-height: 24px; |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
:hover { |
||||
background-color: #484848; |
||||
} |
||||
type DateButtonProps = { |
||||
isActive: boolean, |
||||
} |
||||
|
||||
export const DateButton = styled(BaseButton)<DateButtonProps>` |
||||
position: absolute; |
||||
top: 0.2rem; |
||||
right: 3.6rem; |
||||
width: 1.3rem; |
||||
height: 1.26rem; |
||||
color: #fff; |
||||
opacity: 0.5; |
||||
|
||||
:active { |
||||
color: #fff; |
||||
background-color: #666666; |
||||
:hover { |
||||
opacity: 0.7; |
||||
} |
||||
` |
||||
|
||||
export const Day = styled.span` |
||||
font-weight: bold; |
||||
font-size: 38px; |
||||
letter-spacing: -0.03em; |
||||
${({ isActive }) => ( |
||||
isActive |
||||
? 'opacity: 1;' |
||||
: '' |
||||
)} |
||||
|
||||
@media ${devices.tablet} { |
||||
font-weight: 600; |
||||
font-size: 14px; |
||||
} |
||||
${isMobileDevice |
||||
? css` |
||||
right: 14px; |
||||
width: 16px; |
||||
height: 16px; |
||||
@media screen and (orientation: landscape){ |
||||
right: 20px; |
||||
} |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
export const Month = styled.span` |
||||
font-weight: bold; |
||||
font-size: 14px; |
||||
|
||||
@media ${devices.tablet} { |
||||
font-weight: 600; |
||||
margin-right: 4px; |
||||
} |
||||
export const WeekDaysWrapper = styled.div` |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
margin-top: 0.567rem; |
||||
${isMobileDevice |
||||
? css` |
||||
padding: 0 20px; |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
export const MonthYearWrapper = styled.div` |
||||
height: 28px; |
||||
margin-left: 4px; |
||||
export const Week = styled.div` |
||||
margin: 0 0.95rem; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: space-between; |
||||
|
||||
@media ${devices.tablet} { |
||||
flex-direction: row; |
||||
align-items: center; |
||||
@media ${devices.mobile} { |
||||
width: 100%; |
||||
justify-content: space-between; |
||||
} |
||||
` |
||||
|
||||
export const Year = styled.span` |
||||
font-style: normal; |
||||
font-weight: 300; |
||||
font-size: 14px; |
||||
type WeekDayProps = { |
||||
selected?: boolean, |
||||
} |
||||
|
||||
export const WeekDay = styled(BaseButton)<WeekDayProps>` |
||||
width: 4rem; |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
color: #656565; |
||||
|
||||
@media ${devices.tablet} { |
||||
font-weight: 600; |
||||
:hover { |
||||
color: #FFFFFF; |
||||
} |
||||
` |
||||
|
||||
const Arrow = styled.div` |
||||
width: 16px; |
||||
height: 2px; |
||||
margin: auto; |
||||
background-color: #222222; |
||||
${({ selected }) => ( |
||||
selected |
||||
? 'color: #FFFFFF;' |
||||
: '' |
||||
)}; |
||||
|
||||
@media ${devices.tablet} { |
||||
height: 0; |
||||
@media ${devices.mobile} { |
||||
width: 45px; |
||||
} |
||||
` |
||||
|
||||
&::before { |
||||
content: ''; |
||||
position: absolute; |
||||
width: 10px; |
||||
height: 10px; |
||||
border-left: 2px solid #222222; |
||||
border-bottom: 2px solid #222222; |
||||
} |
||||
export const WeekName = styled.span` |
||||
font-weight: bold; |
||||
font-size: 1.04rem; |
||||
line-height: 1.5rem; |
||||
${isMobileDevice |
||||
? css` |
||||
font-size: 14px; |
||||
line-height: 13px; |
||||
padding-top: 6px; |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
export const ArrowLeft = styled(Arrow)` |
||||
&:before { |
||||
transform: rotate(45deg); |
||||
top: 19px; |
||||
left: 17px; |
||||
export const WeekNumber = styled.span` |
||||
font-weight: 600; |
||||
font-size: 1.3rem; |
||||
line-height: 1.8rem; |
||||
|
||||
@media ${devices.tablet} { |
||||
top: 5px; |
||||
left: 8px; |
||||
} |
||||
@media ${devices.mobile} { |
||||
font-size: 16px; |
||||
line-height: 24px; |
||||
} |
||||
` |
||||
|
||||
export const ArrowRight = styled(Arrow)` |
||||
&:before { |
||||
transform: rotate(225deg); |
||||
top: 19px; |
||||
right: 17px; |
||||
export const ArrowButton = styled(BaseButton)` |
||||
position: relative; |
||||
margin-top: 0.22rem; |
||||
width: 2.28rem; |
||||
height: 2.28rem; |
||||
${isMobileDevice |
||||
? css` |
||||
width: 15px; |
||||
height: 14px; |
||||
` |
||||
: ''}; |
||||
|
||||
@media ${devices.tablet} { |
||||
top: 5px; |
||||
right: 8px; |
||||
} |
||||
} |
||||
` |
||||
|
||||
type ButtonProps = { |
||||
borderLeftRadius?: number, |
||||
borderRightRadius?: number, |
||||
type ArrowProps = { |
||||
direction: 'left' | 'right', |
||||
} |
||||
|
||||
export const Button = styled.button<ButtonProps>` |
||||
position: relative; |
||||
outline: none; |
||||
border: none; |
||||
width: 48px; |
||||
height: 100%; |
||||
cursor: pointer; |
||||
background-color: #3F3F3F; |
||||
|
||||
@media ${devices.tablet} { |
||||
border-radius: 50%; |
||||
width: 23px; |
||||
} |
||||
${({ borderLeftRadius }) => ( |
||||
borderLeftRadius |
||||
? ` |
||||
border-top-left-radius: ${borderLeftRadius}px; |
||||
border-bottom-left-radius: ${borderLeftRadius}px; |
||||
` |
||||
: '' |
||||
)} |
||||
${({ borderRightRadius }) => ( |
||||
borderRightRadius |
||||
? ` |
||||
border-top-right-radius: ${borderRightRadius}px; |
||||
border-bottom-right-radius: ${borderRightRadius}px; |
||||
` |
||||
: '' |
||||
export const Arrow = styled.span<ArrowProps>` |
||||
width: 0.65rem; |
||||
height: 0.65rem; |
||||
position: absolute; |
||||
border-left: 0.15rem solid #fff; |
||||
border-bottom: 0.15rem solid #fff; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
|
||||
${({ direction }) => ( |
||||
direction === 'left' |
||||
? 'transform: translate(-50%, -50%) rotate(45deg);' |
||||
: 'transform: translate(-50%, -50%) rotate(225deg);' |
||||
)} |
||||
|
||||
:hover { |
||||
background-color: #484848; |
||||
} |
||||
|
||||
:active { |
||||
background-color: #666666; |
||||
} |
||||
${isMobileDevice |
||||
? css` |
||||
border-left: 1px solid #fff; |
||||
border-bottom: 1pxrem solid #fff; |
||||
width: 10px; |
||||
height: 10px; |
||||
` |
||||
: ''}; |
||||
` |
||||
|
||||
@ -1,45 +0,0 @@ |
||||
import { T9n } from 'features/T9n' |
||||
import { |
||||
RadioButtonGroup, |
||||
RadioButton, |
||||
} from 'features/Common' |
||||
|
||||
import { MatchStatuses, useHeaderFiltersStore } from '../../store' |
||||
|
||||
export const MatchStatusFilter = () => { |
||||
const { |
||||
isTodaySelected, |
||||
selectedMatchStatus, |
||||
setSelectedMatchStatus, |
||||
} = useHeaderFiltersStore() |
||||
|
||||
return ( |
||||
<RadioButtonGroup> |
||||
<RadioButton |
||||
upperCase |
||||
selected={selectedMatchStatus === MatchStatuses.Live} |
||||
onClick={() => setSelectedMatchStatus(MatchStatuses.Live)} |
||||
buttonWidth={80} |
||||
disabled={!isTodaySelected} |
||||
> |
||||
<T9n t='match_status_live' /> |
||||
</RadioButton> |
||||
<RadioButton |
||||
selected={selectedMatchStatus === MatchStatuses.Finished} |
||||
onClick={() => setSelectedMatchStatus(MatchStatuses.Finished)} |
||||
buttonWidth={121} |
||||
disabled={!isTodaySelected} |
||||
> |
||||
<T9n t='match_status_finished' /> |
||||
</RadioButton> |
||||
<RadioButton |
||||
selected={selectedMatchStatus === MatchStatuses.Soon} |
||||
onClick={() => setSelectedMatchStatus(MatchStatuses.Soon)} |
||||
buttonWidth={87} |
||||
disabled={!isTodaySelected} |
||||
> |
||||
<T9n t='match_status_soon' /> |
||||
</RadioButton> |
||||
</RadioButtonGroup> |
||||
) |
||||
} |
||||
@ -1,63 +0,0 @@ |
||||
import { css } from 'styled-components/macro' |
||||
|
||||
import { devices } from 'config' |
||||
|
||||
import { useHeaderFiltersStore } from 'features/HeaderFilters/store' |
||||
import { SportTypeFilter as Filter } from 'features/SportTypeFilter' |
||||
|
||||
import { DropdownButton } from '../TournamentFilter/styled' |
||||
|
||||
const wrapperStyles = css` |
||||
width: 50%; |
||||
border-right: 1px solid #222222; |
||||
|
||||
@media ${devices.tablet} { |
||||
border-right: 0; |
||||
} |
||||
|
||||
${DropdownButton} { |
||||
border-radius: 0; |
||||
border-top-left-radius: 2px; |
||||
border-bottom-left-radius: 2px; |
||||
} |
||||
` |
||||
|
||||
const useSportTypeFilter = () => { |
||||
const { |
||||
selectedSportTypeId, |
||||
setSelectedSportTypeId, |
||||
setSelectedTournamentId, |
||||
} = useHeaderFiltersStore() |
||||
|
||||
const onSelect = (id: number) => { |
||||
setSelectedSportTypeId(id) |
||||
setSelectedTournamentId(null) |
||||
} |
||||
|
||||
const onReset = () => { |
||||
setSelectedSportTypeId(null) |
||||
setSelectedTournamentId(null) |
||||
} |
||||
|
||||
return { |
||||
onReset, |
||||
onSelect, |
||||
selectedSportTypeId, |
||||
} |
||||
} |
||||
|
||||
export const SportTypeFilter = () => { |
||||
const { |
||||
onReset, |
||||
onSelect, |
||||
selectedSportTypeId, |
||||
} = useSportTypeFilter() |
||||
return ( |
||||
<Filter |
||||
selectedSportId={selectedSportTypeId} |
||||
onSelect={onSelect} |
||||
onReset={onReset} |
||||
wrapperStyles={wrapperStyles} |
||||
/> |
||||
) |
||||
} |
||||
@ -1,81 +0,0 @@ |
||||
import type { MouseEvent } from 'react' |
||||
import { useEffect, useState } from 'react' |
||||
|
||||
import find from 'lodash/find' |
||||
|
||||
import type { Tournaments } from 'requests' |
||||
import { getSportTournaments } from 'requests' |
||||
import { useRequest, useToggle } from 'hooks' |
||||
|
||||
import { useHeaderFiltersStore } from 'features/HeaderFilters' |
||||
|
||||
const findTournament = (tournaments: Tournaments, id: number) => { |
||||
const tournament = find(tournaments, { id }) |
||||
if (!tournament) return null |
||||
return { |
||||
...tournament, |
||||
name_eng: tournament.short_name_eng || tournament.name_eng, |
||||
name_rus: tournament.short_name_rus || tournament.name_rus, |
||||
} |
||||
} |
||||
|
||||
export const useTournamentFilter = () => { |
||||
const [tournaments, setTournaments] = useState<Tournaments>([]) |
||||
|
||||
const { |
||||
selectedSportTypeId, |
||||
selectedTournamentId, |
||||
setSelectedTournamentId, |
||||
} = useHeaderFiltersStore() |
||||
const { |
||||
close, |
||||
isOpen, |
||||
open, |
||||
} = useToggle() |
||||
const { |
||||
isFetching, |
||||
request: requestTournaments, |
||||
} = useRequest(getSportTournaments) |
||||
const [page, setPage] = useState(0) |
||||
|
||||
useEffect(() => { |
||||
setTournaments([]) |
||||
requestTournaments(selectedSportTypeId, 0).then((newTournaments) => { |
||||
setTournaments(newTournaments) |
||||
setPage(1) |
||||
}) |
||||
}, [selectedSportTypeId, requestTournaments]) |
||||
|
||||
const fetchMore = async () => { |
||||
if (isFetching) return |
||||
|
||||
const newTournaments = await requestTournaments(selectedSportTypeId, page) |
||||
setTournaments([...tournaments, ...newTournaments]) |
||||
setPage(page + 1) |
||||
} |
||||
|
||||
const selectedTournament = selectedTournamentId |
||||
? findTournament(tournaments, selectedTournamentId) |
||||
: null |
||||
|
||||
const onTournamentSelect = (tournamentId: number) => { |
||||
setSelectedTournamentId(tournamentId) |
||||
close() |
||||
} |
||||
|
||||
const onResetSelectedTournament = (e: MouseEvent<HTMLButtonElement>) => { |
||||
e.stopPropagation() |
||||
setSelectedTournamentId(null) |
||||
} |
||||
|
||||
return { |
||||
close, |
||||
fetchMore, |
||||
isOpen, |
||||
onResetSelectedTournament, |
||||
onTournamentSelect, |
||||
open, |
||||
selectedTournament, |
||||
tournaments, |
||||
} |
||||
} |
||||
@ -1,63 +0,0 @@ |
||||
import { T9n } from 'features/T9n' |
||||
import { Name } from 'features/Name' |
||||
import { OutsideClick } from 'features/OutsideClick' |
||||
|
||||
import { TournamentList } from '../TournamentList' |
||||
import { useTournamentFilter } from './hooks' |
||||
import { |
||||
Wrapper, |
||||
InfiniteScroll, |
||||
DropdownButton, |
||||
ButtonTitle, |
||||
Arrows, |
||||
ClearButton, |
||||
} from './styled' |
||||
|
||||
export const TournamentFilter = () => { |
||||
const { |
||||
close, |
||||
fetchMore, |
||||
isOpen, |
||||
onResetSelectedTournament, |
||||
onTournamentSelect, |
||||
open, |
||||
selectedTournament, |
||||
tournaments, |
||||
} = useTournamentFilter() |
||||
return ( |
||||
<Wrapper> |
||||
<DropdownButton |
||||
onClick={open} |
||||
active={isOpen} |
||||
aria-expanded={isOpen} |
||||
aria-controls='tournamentsList' |
||||
> |
||||
<ButtonTitle> |
||||
{ |
||||
selectedTournament |
||||
? <Name nameObj={selectedTournament} /> |
||||
: <T9n t='tournament' /> |
||||
} |
||||
</ButtonTitle> |
||||
{ |
||||
selectedTournament && ( |
||||
<ClearButton onClick={onResetSelectedTournament} /> |
||||
) |
||||
} |
||||
<Arrows active={isOpen} /> |
||||
</DropdownButton> |
||||
{ |
||||
isOpen && ( |
||||
<OutsideClick onClick={close}> |
||||
<InfiniteScroll onFetchMore={fetchMore}> |
||||
<TournamentList |
||||
tournaments={tournaments} |
||||
onSelect={onTournamentSelect} |
||||
/> |
||||
</InfiniteScroll> |
||||
</OutsideClick> |
||||
) |
||||
} |
||||
</Wrapper> |
||||
) |
||||
} |
||||
@ -1,134 +0,0 @@ |
||||
import styled from 'styled-components/macro' |
||||
|
||||
import { devices } from 'config/devices' |
||||
import { customScrollbar } from 'features/Common' |
||||
import { InfiniteScroll as InfiniteScrollBase } from 'features/InfiniteScroll' |
||||
|
||||
export const InfiniteScroll = styled(InfiniteScrollBase)` |
||||
position: absolute; |
||||
left: 0; |
||||
top: calc(100% + 8px); |
||||
width: 448px; |
||||
height: 456px; |
||||
overflow-y: scroll; |
||||
z-index: 2; |
||||
|
||||
@media ${devices.laptop} { |
||||
right: 0; |
||||
bottom: -1px; |
||||
left: auto; |
||||
} |
||||
|
||||
@media ${devices.tablet} { |
||||
width: 100%; |
||||
left: 0; |
||||
top: calc(-92vh + 36px); |
||||
height: calc(92vh - 36px); |
||||
} |
||||
|
||||
@media ${devices.mobile} { |
||||
width: 200%; |
||||
left: -100%; |
||||
} |
||||
|
||||
${customScrollbar} |
||||
` |
||||
|
||||
type Props = { |
||||
active?: boolean, |
||||
} |
||||
|
||||
export const DropdownButton = styled.button<Props>` |
||||
position: relative; |
||||
height: 100%; |
||||
width: 100%; |
||||
padding-right: 45px; |
||||
outline: none; |
||||
border: none; |
||||
border-radius: 2px; |
||||
display: flex; |
||||
align-items: center; |
||||
background-color: #3F3F3F; |
||||
font-weight: 600; |
||||
font-size: 18px; |
||||
cursor: pointer; |
||||
background-size: 12px 12px; |
||||
background-repeat: no-repeat; |
||||
background-position: 113px; |
||||
|
||||
@media ${devices.tablet} { |
||||
background: transparent; |
||||
font-weight: 500; |
||||
font-size: 14px; |
||||
} |
||||
|
||||
|
||||
${({ active }) => ( |
||||
active |
||||
? ` |
||||
background-color: #666666; |
||||
color: #fff; |
||||
` |
||||
: ` |
||||
color: #999999; |
||||
|
||||
:hover { |
||||
background-color: #484848; |
||||
} |
||||
` |
||||
)} |
||||
` |
||||
|
||||
export const ButtonTitle = styled.span` |
||||
display: inline-block; |
||||
flex-grow: 1; |
||||
white-space: nowrap; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
line-height: normal; |
||||
text-transform: capitalize; |
||||
` |
||||
|
||||
export const Arrows = styled.span<Props>` |
||||
position: absolute; |
||||
right: 19px; |
||||
display: inline-block; |
||||
width: 12px; |
||||
height: 12px; |
||||
background-repeat: no-repeat; |
||||
background-position: center; |
||||
|
||||
@media ${devices.tablet} { |
||||
display: none; |
||||
} |
||||
|
||||
${({ active }) => ( |
||||
active |
||||
? 'background-image: url(/images/arrowUp.svg);' |
||||
: 'background-image: url(/images/arrowDown.svg);' |
||||
)} |
||||
` |
||||
|
||||
export const Wrapper = styled.div` |
||||
height: 100%; |
||||
width: 50%; |
||||
position: relative; |
||||
|
||||
${DropdownButton} { |
||||
border-radius: 0; |
||||
border-top-right-radius: 2px; |
||||
border-bottom-right-radius: 2px; |
||||
} |
||||
` |
||||
|
||||
export const ClearButton = styled.span` |
||||
display: inline-block; |
||||
cursor: pointer; |
||||
min-width: 10px; |
||||
height: 10px; |
||||
margin-left: 10px; |
||||
background-color: transparent; |
||||
background-image: url(/images/clear.svg); |
||||
background-position: center; |
||||
background-repeat: no-repeat; |
||||
` |
||||