fix($#2579): add search to player name,autocomplete for team, fix audio for ios and fix some styles for mobile

keep-around/1461a3b0814b008b76b9697499c74c28b837bee1
Andrei Dekterev 3 years ago
parent d190b869cb
commit 1461a3b081
  1. 28
      Makefile
  2. 25
      src/components/AudioPlayer/hooks.tsx
  3. 6
      src/config/lexics/highlightsPageLexic.tsx
  4. 1
      src/config/lexics/payment.tsx
  5. 2
      src/config/routes.tsx
  6. 70
      src/features/AddCardForm/components/Form/hooks/index.tsx
  7. 2
      src/features/AddCardForm/styled.tsx
  8. 2
      src/features/AuthServiceApp/components/Login/hooks.tsx
  9. 31
      src/features/BuyMatchPopup/components/CardStep/index.tsx
  10. 3
      src/features/BuyMatchPopup/store/hooks/index.tsx
  11. 47
      src/features/BuyMatchPopup/styled.tsx
  12. 14
      src/features/CardsStore/hooks/index.tsx
  13. 9
      src/features/Combobox/index.tsx
  14. 14
      src/features/Combobox/styled.tsx
  15. 1
      src/features/Combobox/types.tsx
  16. 4
      src/features/Common/Input/index.tsx
  17. 35
      src/features/Common/Input/styled.tsx
  18. 6
      src/features/PopupComponents/BaseButton/index.tsx
  19. 2
      src/features/UserAccount/components/ChangeCardPopup/styled.tsx
  20. 1
      src/features/UserAccount/styled.tsx
  21. 1
      src/helpers/dateForIos/index.tsx
  22. 29
      src/hooks/useLocalStorage.tsx
  23. 79
      src/pages/HighlightsPage/components/FormHighlights/hooks.tsx
  24. 10
      src/pages/HighlightsPage/components/FormHighlights/index.tsx
  25. 21
      src/pages/HighlightsPage/components/FormHighlights/styled.tsx
  26. 4
      src/pages/HighlightsPage/components/MatchesHighlights/index.tsx
  27. 5
      src/pages/HighlightsPage/components/MatchesHighlights/styled.tsx
  28. 2
      src/pages/HighlightsPage/components/VideoHighlight/index.tsx
  29. 7
      src/pages/HighlightsPage/components/VideoHighlight/styled.tsx
  30. 51
      src/pages/HighlightsPage/index.tsx
  31. 6
      src/pages/HighlightsPage/storeHighlightsAtoms.tsx
  32. 32
      src/pages/HighlightsPage/styled.tsx
  33. 10
      src/requests/getSearchItems.tsx

@ -130,7 +130,7 @@ prod: clean
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@de.instat.tv:/usr/local/www/ott/wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@fr.instat.tv:/usr/local/www/ott/wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@10.0.3.8:/usr/local/www/ott/wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@137.74.33.74:/usr/local/www/ott/wwwroot/
preprod: clean
REACT_APP_TYPE=ott \
@ -139,7 +139,7 @@ preprod: clean
REACT_APP_STRIPE_PK=pk_live_ANI76cBhSo69DZUxPmyRVIZW \
npm run build
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-test@10.0.3.8:/usr/local/www/ott-test/wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-test@137.74.33.74:/usr/local/www/ott-test/wwwroot/
facr-prod: clean
REACT_APP_TYPE=ott \
@ -150,7 +150,7 @@ facr-prod: clean
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@de.instat.tv:/usr/local/www/ott/facr-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@fr.instat.tv:/usr/local/www/ott/facr-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@10.0.3.8:/usr/local/www/ott/facr-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@137.74.33.74:/usr/local/www/ott/facr-wwwroot/
lff-prod: clean
REACT_APP_TYPE=ott \
@ -161,39 +161,39 @@ lff-prod: clean
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@de.instat.tv:/usr/local/www/ott/lff-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@fr.instat.tv:/usr/local/www/ott/lff-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@10.0.3.8:/usr/local/www/ott/lff-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott@137.74.33.74:/usr/local/www/ott/lff-wwwroot/
deploy-all: prod preprod facr-prod lff-prod
stage: build-stage
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/wwwroot/
a-stage: build-a
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/a-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/a-wwwroot/
b-stage: build-b
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/b-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/b-wwwroot/
c-stage: build-c
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/c-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/c-wwwroot/
d-stage: build-d
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/d-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/d-wwwroot/
e-stage: build-e
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/e-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/e-wwwroot/
f-stage: build-f
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/f-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/f-wwwroot/
g-stage: build-g
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/g-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/g-wwwroot/
h-stage: build-h
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/h-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/h-wwwroot/
i-stage: build-i
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@10.0.3.8:/usr/local/www/ott-staging/i-wwwroot/
rsync -zavP --delete-before build/ -e 'ssh -p 666' ott-staging@137.74.33.74:/usr/local/www/ott-staging/i-wwwroot/
test:
npm test

@ -2,35 +2,36 @@ import {
SyntheticEvent,
useEffect,
useState,
useRef,
} from 'react'
import { readToken } from 'helpers'
export const useAudioPlayer = (asset: string) => {
const [audio, setAudio] = useState<HTMLAudioElement | null>(null)
const audio = useRef<HTMLAudioElement>(new Audio(''))
const [playing, setPlaying] = useState(false)
const toggle = (e: SyntheticEvent) => {
e.preventDefault()
e.stopPropagation()
setPlaying(!playing)
if (audio.current.paused) {
audio.current.play()
setPlaying(true)
} else {
audio.current.pause()
setPlaying(false)
}
}
useEffect(() => {
setPlaying(false)
audio?.pause()
asset && setAudio(new Audio(`${asset}?access_token=${readToken()}`))
// eslint-disable-next-line react-hooks/exhaustive-deps
audio.current?.pause()
audio.current = new Audio(`${asset}?access_token=${readToken()}`)
}, [asset])
useEffect(() => {
playing ? audio?.play() : audio?.pause()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playing])
useEffect(() => {
audio?.addEventListener('ended', () => setPlaying(false))
audio.current?.addEventListener('ended', () => audio.current.pause())
return () => {
audio?.removeEventListener('ended', () => setPlaying(false))
audio.current?.removeEventListener('ended', () => audio.current.pause())
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

@ -2,15 +2,19 @@ export const highlightsPageLexic = {
add_summary: 18360,
background_music: 18359,
choose_player: 18362,
describe_duration: 18358,
describe_music: 19527,
describe_statistic: 19528,
get_highlights: 18355,
highlight_will_be_generated: 18364,
matches_highlight: 419,
maximal_duration: 18358,
maximal_duration: 2543,
order_and_buy: 18361,
player_highlight: 630,
price: 18356,
purchase_auto_generated: 18363,
sport_highlight: 15115,
statistics_highlights: 1587,
team_highlight: 657,
watch_demo: 18357,
}

@ -8,6 +8,7 @@ export const paymentLexics = {
card_holder_name: 2021,
change_card: 14018,
city: 15206,
clear: 18313,
country: 835,
error_address_latin_letters: 15758,
error_can_not_add_card: 14447,

@ -4,7 +4,7 @@ import { ENV, isProduction } from './env'
export const APIS = {
preproduction: {
api: 'https://api-aws-stage.instat.tv:11630',
api: 'https://api.instat.tv', // 'https://api-aws-stage.instat.tv:11630',
auth: 'https://auth.instat.tv',
},
production: {

@ -12,6 +12,8 @@ import {
import type { StripeElementChangeEvent } from '@stripe/stripe-js'
import {
CardNumberElement,
CardCvcElement,
CardExpiryElement,
useStripe,
useElements,
} from '@stripe/react-stripe-js'
@ -49,10 +51,12 @@ export enum ElementTypes {
export type Props = {
children?: ReactNode,
clearInputs?: boolean,
initialformOpen?: boolean,
inputsBackground?: string,
loader?: boolean,
onAddSuccess?: () => void,
setClearInputs?: ((clear: boolean) => void) | undefined,
}
const inputState = {
@ -70,15 +74,21 @@ const initialState = {
cardNumber: inputState,
}
export const useFormSubmit = ({ onAddSuccess }: Props) => {
export const useFormSubmit = ({
clearInputs,
onAddSuccess,
setClearInputs,
}: Props) => {
const stripe = useStripe()
const elements = useElements()
const { translate } = useLexicsStore()
const {
defaultCard,
isHighlightsPage,
lastCard,
onAddCard,
setError: setCardError,
setLastCard,
} = useCardsStore()
const [name, setName] = useState('')
const [city, setCity] = useState('')
@ -91,6 +101,7 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
const {
onSuccessfulHighlights,
onUnsuccessfulSubscription,
setShowClearBtn,
} = useBuyMatchPopupStore()
const {
@ -104,8 +115,23 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
setCardError('')
}, [setErrorMessage, setCardError])
const resetInputs = () => {
resetErrors()
setName('')
setCity('')
setAddress('')
setSelectedCountry(null)
elements?.getElement(CardNumberElement)?.clear()
elements?.getElement(CardCvcElement)?.clear()
elements?.getElement(CardExpiryElement)?.clear()
setInputStates(initialState)
setShowClearBtn(false)
setClearInputs && setClearInputs(false)
}
const setElementTypeState = useCallback((elementType: ElementTypes, value: string) => {
const elementState = inputStates[elementType]
setInputStates({
[elementType]: {
...elementState,
@ -154,6 +180,7 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
const onInputsChange = (e: StripeElementChangeEvent) => {
const value = inputStates[e.elementType as ElementTypes]
setInputStates({ [e.elementType]: { ...value, empty: e.empty } })
resetErrors()
}
@ -172,6 +199,8 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
inputStates[elementType].empty && !inputStates[elementType].focused
)
const allInputEmpty = Object.values(inputStates).every((inputValue) => inputValue.empty)
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
resetErrors()
@ -181,8 +210,6 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
return
}
const allInputEmpty = Object.values(inputStates).every((inputValue) => inputValue.empty)
if (isHighlightsPage && defaultCard && allInputEmpty) {
setErrorMessage('')
setLoader(true)
@ -228,25 +255,38 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
setLoader(true)
onAddCard(token.id)
.then(onAddSuccess)
.then(() => defaultCard && isHighlightsPage && setInputStates(initialState))
.then(() => {
if (defaultCard && isHighlightsPage) {
onePayment({
cardId: defaultCard.id,
order: { ...dataHighlights?.data },
})
.then(onSuccessfulHighlights, onUnsuccessfulSubscription)
.catch(() => setErrorMessage(translate('error_payment_unsuccessful')))
}
})
.finally(() => setLoader(false))
.then(() => lastCard && isHighlightsPage && setInputStates(initialState))
.catch(() => setLoader(false))
}
}
useEffect(() => {
if (lastCard && isHighlightsPage) {
onePayment({
cardId: lastCard,
order: { ...dataHighlights?.data },
})
.then(onSuccessfulHighlights, onUnsuccessfulSubscription)
.catch(() => setErrorMessage(translate('error_payment_unsuccessful')))
.finally(() => setLoader(false))
}
setLastCard(null)
// eslint-disable-next-line
}, [lastCard])
useEffect(() => {
setCardError('')
}, [setCardError])
useEffect(() => {
if (!allInputEmpty) setShowClearBtn(true)
}, [allInputEmpty, setShowClearBtn])
useEffect(() => {
if (clearInputs) resetInputs()
// eslint-disable-next-line
}, [clearInputs])
return {
address,
city,

@ -83,7 +83,7 @@ export const SectionTitle = styled.span`
margin-bottom: 8px;
${isMobileDevice
? css`
margin-bottom: 25px;
margin-bottom: 5px;
font-size: 10px;
@media (orientation: landscape){
margin-bottom: 10px;

@ -73,8 +73,6 @@ export const useLoginForm = () => {
scope,
},
})
const numberVisits = localStorage.getItem('countVisits') ?? 0
localStorage.setItem('countVisits', (Number(numberVisits) + 1).toString())
submitForm()
} catch (err) {
handleError(String(err))

@ -1,3 +1,5 @@
import { useState } from 'react'
import isEmpty from 'lodash/isEmpty'
import { AddCardFormInner } from 'features/AddCardForm/components/Form'
@ -18,6 +20,8 @@ import {
Header,
HeaderTitle,
Button,
ButtonBlock,
ButtonClear,
ButtonPrevious,
} from '../../styled'
@ -32,8 +36,13 @@ export const CardStep = ({
closeHandle,
title,
}: CardStepType) => {
const [clearInputs, setClearInputs] = useState(false)
const { cards, isHighlightsPage } = useCardsStore()
const { close, goBack } = useBuyMatchPopupStore()
const {
close,
goBack,
showClearBtn,
} = useBuyMatchPopupStore()
const emptyCards = isEmpty(cards)
@ -56,10 +65,24 @@ export const CardStep = ({
onAddSuccess={goBack}
initialformOpen={emptyCards}
inputsBackground='rgba(255, 255, 255, 0.1)'
clearInputs={clearInputs}
setClearInputs={setClearInputs}
>
<Button type='submit'>
<T9n t={btnName ?? 'add'} />
</Button>
<ButtonBlock showClearBtn={showClearBtn}>
{isHighlightsPage
&& showClearBtn && (
<ButtonClear onClick={(e) => {
e.preventDefault()
setClearInputs(true)
}}
>
<T9n t='clear' />
</ButtonClear>
)}
<Button type='submit'>
<T9n t={btnName ?? 'add'} />
</Button>
</ButtonBlock>
</AddCardFormInner>
</Body>
</Wrapper>

@ -53,6 +53,7 @@ export const useBuyMatchPopup = () => {
const [error, setError] = useState('')
const [loader, setLoader] = useState(false)
const [disabledBuyBtn, setDisabledBuyBtn] = useState(false)
const [showClearBtn, setShowClearBtn] = useState(false)
const [lastSelectedPackage, setLastSelectedPackage] = useState('')
const setDataHighlights = useSetRecoilState(dataForPayHighlights)
@ -226,6 +227,8 @@ export const useBuyMatchPopup = () => {
selectedSubscription,
setDisabledBuyBtn,
setLastSelectedPackage,
setShowClearBtn,
showClearBtn,
subscriptions,
}
}

@ -2,7 +2,7 @@ import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { ButtonSolid } from 'features/Common'
import { ButtonOutline, ButtonSolid } from 'features/Common'
import { ModalWindow } from 'features/Modal/styled'
import { Modal as BaseModal } from 'features/Modal'
@ -48,8 +48,7 @@ export const HeaderTitle = styled.h2`
`
export const Button = styled(ButtonSolid)`
min-width: 270px;
width: auto;
width: 275px;
height: 50px;
padding: 0 20px;
background-color: ${({ theme: { colors } }) => colors.button};
@ -58,13 +57,15 @@ export const Button = styled(ButtonSolid)`
border-radius: 5px;
font-weight: 600;
font-size: 20px;
${isMobileDevice
? css`
height: 32px;
max-height: 38px;
min-width: 117px;
font-size: 12px;
max-width: 49%;
font-size: 16px;
@media screen and (orientation: landscape){
min-width: 178px;
/* min-width: 178px; */
}
`
@ -108,7 +109,7 @@ export const Wrapper = styled.div<WrapperProps>`
}
@media (max-width: 650px){
width: 100%;
padding: 40px 10px 20px;
padding: 20px 10px 20px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
@ -190,3 +191,35 @@ export const ButtonPrevious = styled.button`
left: 20px;
cursor: pointer;
`
export const ButtonClear = styled(ButtonOutline)`
border: 1px solid #FFFFFF;
border-radius: 4px;
font-weight: 600;
font-size: 20px;
line-height: 16px;
height: 50px;
width: 275px;
max-width: 49%;
${isMobileDevice
? css`
height: 100%;
font-size: 16px;
width: 100%;
padding: 0 20px;
max-width: 49%;
max-height: 38px;
`
: ''};
`
type ButtonsProps = {
showClearBtn: boolean,
}
export const ButtonBlock = styled.div<ButtonsProps>`
display: flex;
flex-direction: row;
height: 38px;
width: 100%;
justify-content: ${({ showClearBtn }) => (showClearBtn ? 'space-between' : 'center')};
`

@ -25,6 +25,7 @@ export const useBankCards = () => {
const [isHighlightsPage, setIsHighlightsPage] = useState(false)
const [error, setError] = useState('')
const [cards, setCards] = useState<Cards | null>(null)
const [lastCard, setLastCard] = useState<string | null>(null)
const defaultCard = useMemo(
() => find(cards, { default: true }),
[cards],
@ -38,6 +39,9 @@ export const useBankCards = () => {
setError('error_can_not_add_card')
return Promise.reject()
})
.then(({ card }) => {
isHighlightsPage && setLastCard(card.id)
})
.then(fetchCards)
)
@ -49,12 +53,8 @@ export const useBankCards = () => {
}
useEffect(() => {
if (window.location.pathname === PAGES.highlights) {
setIsHighlightsPage(true)
} else {
setIsHighlightsPage(false)
}
}, [])
setIsHighlightsPage(window.location.pathname === PAGES.highlights)
}, [cards])
return {
cards,
@ -64,10 +64,12 @@ export const useBankCards = () => {
infoModalOpen,
isFetching,
isHighlightsPage,
lastCard,
onAddCard,
onDeleteCard: handleCardDelete,
onSetDefaultCard,
setError,
setLastCard,
toggleInfoModal,
}
}

@ -22,6 +22,7 @@ import {
LabelTitle,
LabelAfter,
LabelBefore,
ScTooltip,
} from 'features/Common/Input/styled'
import { Props, Option } from './types'
@ -50,6 +51,7 @@ export const Combobox = <T extends Option>(props: Props<T>) => {
maxLength,
noSearch,
title,
toolTip,
withError,
wrapperHeight,
} = props
@ -82,8 +84,15 @@ export const Combobox = <T extends Option>(props: Props<T>) => {
<LabelTitle
labelWidth={labelWidth}
isUserAccountPage={isUserAccountPage}
onClick={(e) => {
if (toolTip) {
e.preventDefault()
e.stopPropagation()
}
}}
>
{labelLexic ? <T9n t={labelLexic} /> : label}
{toolTip && <ScTooltip className='Highlight__Tooltip'><T9n t={toolTip} /></ScTooltip>}
</LabelTitle>
{labelBefore && (
<LabelBefore>

@ -50,6 +50,14 @@ export const ListOption = styled.li.attrs(() => ({
background-color: #999;
color: #fff;
}
${isMobileDevice
? css`
font-size: 12px;
height: 36px;
padding-left: 14px;
`
: ''};
`
export const WrapperIcon = styled.span`
position: absolute;
@ -58,6 +66,12 @@ export const WrapperIcon = styled.span`
width: 15px;
height: 15px;
transform: translateY(-60%);
${isMobileDevice
? css`
right: 11px;
`
: ''};
`
export const ScAudioWrap = styled.div`

@ -35,6 +35,7 @@ export type Props<T> = Pick<InputHTMLAttributes<HTMLInputElement>, (
onSelect?: (option: T | null) => void,
options: Array<T>,
selected?: boolean,
toolTip?: string,
value: string,
withError?: boolean,
wrapperHeight?: number,

@ -16,6 +16,7 @@ import {
LabelTitle,
Error,
Column,
ScTooltip,
} from './styled'
type Props = {
@ -38,6 +39,7 @@ type Props = {
pattern?: string,
required?: boolean,
title?: string,
toolTip?: string,
type?: string,
value?: string,
withError?: boolean,
@ -65,6 +67,7 @@ export const Input = ({
pattern,
required,
title,
toolTip,
type,
value,
withError = true,
@ -85,6 +88,7 @@ export const Input = ({
<Label>
<LabelTitle labelWidth={labelWidth} isUserAccountPage={isUserAccountPage}>
{labelLexic ? <T9n t={labelLexic} /> : label}
{toolTip && <ScTooltip className='Highlight__Tooltip'><T9n t={toolTip} /></ScTooltip>}
</LabelTitle>
<InputStyled
autoComplete={autoComplete}

@ -28,6 +28,12 @@ export const wrapperStyles = css<WrapperProps>`
border-radius: 2px;
border: 1px solid ${(({ error }) => (isNil(error) ? 'transparent' : '#E64646'))};
border-width: 1px;
${isMobileDevice
? css`
padding-left: 0;
`
: ''};
`
type TitleProps = {
@ -52,6 +58,12 @@ export const LabelTitle = styled.p<TitleProps>`
width: ${({ labelWidth }) => (labelWidth ? `${labelWidth}px` : '')};
min-width: ${({ labelWidth }) => (labelWidth ? `${labelWidth}px` : '')};
&:hover{
.Highlight__Tooltip {
display: block;
}
}
@media ${devices.tablet} {
font-size: 1.6rem;
width: 9rem;
@ -219,3 +231,26 @@ export const LabelBefore = styled(LabelAfter)`
margin-left: -30px;
padding-top: 5px;
`
export const ScTooltip = styled.div`
display: none;
position: absolute;
font-weight: 600;
font-size: 0.8rem;
line-height: 18px;
color: #000000;
background: #FFFFFF;
border-radius: 6px;
padding: 3px 6px;
max-width: 292px;
overflow-wrap: break-word;
white-space: normal;
z-index: 10;
${isMobileDevice
? css`
font-size: 12px;
`
: ''};
`

@ -20,10 +20,10 @@ export const BaseButton = styled.button<Props>`
height: ${({ height = 1.6 }) => height}rem;
padding: ${({ padding = 0 }) => padding}px;
color: white;
/* background-color: rgba(255, 255, 255, 0.12);
background-color: rgba(255, 255, 255, 0.12);
background-position: center;
background-repeat: no-repeat;
border-radius: 50%; */
border-radius: 50%;
:hover {
background-color: rgba(255, 255, 255, 0.22);
@ -35,7 +35,7 @@ export const BaseButton = styled.button<Props>`
height: 18px;
padding: 4px;
position: absolute;
top: -10px;
top: -20px;
right: -30px;
@media screen and (orientation: landscape){
width: 24px;

@ -16,7 +16,7 @@ export const Modal = styled(BaseModal)`
width: calc(100vw - 60px);
@media screen and (orientation: landscape){
max-width: calc(100vw - 80px);
height: calc(100vh - 20px);
height: calc(100vh - 10px);
overflow: auto;
}
`

@ -264,6 +264,7 @@ export const ScButtonGetHighlight = styled(ButtonOutline)`
margin: 30px 0;
border: 1px solid #FFFFFF;
border-radius: 5px;
width: 95%;
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.3));
${isMobileDevice

@ -0,0 +1 @@
export const dateForIos = (date: string) => Date.parse(date?.replace(/ /, 'T'))

@ -0,0 +1,29 @@
import { useState } from 'react'
export const useLocalStorage = (keyName: string, defaultValue?: any) => {
const [storedValue, setStoredValue] = useState(() => {
try {
const value = localStorage.getItem(keyName)
if (value) {
return JSON.parse(value)
}
localStorage.setItem(keyName, JSON.stringify(defaultValue))
return defaultValue
} catch (err) {
return defaultValue
}
})
const setValue = (newValue: any) => {
try {
localStorage.setItem(keyName, JSON.stringify(newValue))
} catch (err) {
/* eslint-disable-next-line */
console.log(err)
}
setStoredValue(newValue)
}
return [storedValue, setValue]
}

@ -1,6 +1,5 @@
import {
ChangeEvent,
FormEvent,
useEffect,
useRef,
useState,
@ -28,6 +27,7 @@ import {
} from 'requests/getSportTeams'
import { getPlayerMatches } from 'requests/getMatches/getPlayerMatches'
import { getTeamPlayers, Player } from 'requests/getTeamPlayers'
import { getSearchItems, PlayerTypeFromSearch } from 'requests'
import {
checkedMatches,
@ -48,12 +48,29 @@ type SportTypeName = SportType & {
name: string,
}
type PlayerType = Player & {
type PlayerType = {
firstname_eng: string,
firstname_rus: string,
id: number,
lastname_eng: string,
lastname_rus: string,
name: string,
sport?: number,
sport_id?: number,
team?: {
id: number,
name_eng: string,
name_rus: string,
},
}
type TeamType = Team & {
type TeamType = {
id: number,
name: string,
name_eng: string,
name_rus: string,
short_name_eng?: string,
short_name_rus?: string,
}
type Sound = {
@ -73,7 +90,7 @@ type FormType = {
players: Array<string>,
selectedPlayer: PlayerType | null,
selectedSound: Sound | null,
selectedTeam: Team | null,
selectedTeam: TeamType | null,
sport: SportTypeName | null,
stats: StatsType,
teamValue: string,
@ -98,7 +115,7 @@ const defaultValues = {
selectedSound: null,
selectedTeam: null,
sport: null,
stats: summaryStats[1],
stats: summaryStats[0],
teamValue: '',
}
@ -141,6 +158,7 @@ export const useHighlightsForm = () => {
if (selectedTeam) {
setFormState((state) => ({
...state,
duration: '2',
selectedTeam,
}))
}
@ -148,10 +166,25 @@ export const useHighlightsForm = () => {
const onPlayerSelect = (selectedPlayer: PlayerType | null) => {
if (!selectedPlayer) return
setFormState((state) => ({
setFormState((state: FormType) => ({
...state,
duration: '2',
playerValue: '',
selectedPlayer,
teamValue: '',
}))
if (selectedPlayer.team && !formState.selectedTeam) {
setFormState((state: FormType) => ({
...state,
selectedTeam: {
id: selectedPlayer?.team?.id || 0,
name: selectedPlayer?.team?.name_eng || '',
name_eng: selectedPlayer?.team?.name_eng || '',
name_rus: selectedPlayer?.team?.name_rus || '',
},
}))
}
}
const onSoundSelect = (selectedSound: Sound | null) => {
@ -186,31 +219,49 @@ export const useHighlightsForm = () => {
setFormState((state: FormType) => ({
...state,
playerValue: '',
selectedPlayer: null,
selectedTeam: null,
teamValue: e?.target?.value,
}))
setPlayers([])
setPlayerMatches([])
}
const onChangePlayer = (e: ChangeEvent<HTMLInputElement>) => {
if (!/[A-Za-z\s]/g.test(e.target.value) && e.target.value.length) return
if (!formState.selectedTeam) {
e.target?.value?.length > 3
&& getSearchItems(formState.playerValue)
.then((state) => {
setPlayersData(state?.players?.map((player: PlayerTypeFromSearch) => ({
...player,
name: `${player?.firstname_eng} ${player?.lastname_eng}`,
})) || [])
setPlayers(state?.players?.map((player: PlayerTypeFromSearch) => ({
...player,
name: `${player?.firstname_eng} ${player?.lastname_eng}`,
})) || [])
})
}
setPlayerMatches([])
setFormState((state: FormType) => ({
...state,
playerValue: e?.target?.value,
selectedPlayer: null,
selectedTeam: state.selectedTeam ?? null,
}))
setPlayers(
playersData?.filter(
(player: PlayerType) => player
&& player.name
&& player?.name
?.toLowerCase()
.indexOf(e?.target?.value.toLowerCase()) >= 0,
),
)
}
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
}
useEffect(() => {
getSportList()
.then((res) => setSports(
@ -233,6 +284,7 @@ export const useHighlightsForm = () => {
])
setFormState((prev) => ({
...prev,
duration: '2',
selectedSound: {
asset: null,
id: 100,
@ -286,7 +338,7 @@ export const useHighlightsForm = () => {
)
useEffect(() => {
if (Number(formState?.duration) < checkedMatchesLength * 2) {
if (checkedMatchesLength * 2 >= 2) {
setFormState((prev) => ({
...prev,
duration: (checkedMatchesLength * 2).toString(),
@ -352,7 +404,7 @@ export const useHighlightsForm = () => {
p_match_completed: true,
playerId: formState?.selectedPlayer?.id,
sportType: formState?.sport?.id,
sub_only: false,
sub_only: true,
})
.then(({ broadcast }) => setPlayerMatches(
broadcast.map((match: Match) => ({ ...match, isChecked: false })),
@ -366,7 +418,6 @@ export const useHighlightsForm = () => {
checkedMatchesLength,
formRef,
formState,
handleSubmit,
isFetchingTeams,
onChangeMaxDuration,
onChangePlayer,

@ -103,11 +103,11 @@ export const FormHighlights = ({ price, setIsOpenPopupVideo }: PriceInfoType) =>
loading={isFetchingTeams}
/>
<Combobox
disabled={!sport || !selectedTeam}
disabled={!sport}
selected
labelLexic='player_highlight'
labelWidth={labelWidth}
value={selectedPlayer?.name || playerValue || ''}
value={playerValue || selectedPlayer?.name || ''}
onSelect={onPlayerSelect}
onChange={onChangePlayer}
options={players}
@ -127,7 +127,7 @@ export const FormHighlights = ({ price, setIsOpenPopupVideo }: PriceInfoType) =>
wrapperHeight={wrapperHeight}
labelAfter='min'
className='FormHighlights__input__duration'
required
toolTip='describe_duration'
/>
<Combobox
disabled={!sport}
@ -144,12 +144,13 @@ export const FormHighlights = ({ price, setIsOpenPopupVideo }: PriceInfoType) =>
labelBefore={selectedSound?.asset}
className={selectedSound?.asset
? 'FormHighlights__input__sound' : ''}
toolTip='describe_music'
/>
<Combobox
disabled
noSearch
selected
labelLexic='add_summary'
labelLexic='statistics_highlights'
labelWidth={labelWidth}
value={stats?.name || ''}
onSelect={onStatsSelect}
@ -157,6 +158,7 @@ export const FormHighlights = ({ price, setIsOpenPopupVideo }: PriceInfoType) =>
maxLength={500}
withError={false}
wrapperHeight={wrapperHeight}
toolTip='describe_statistic'
/>
</ScInputGroup>
</ScForm>

@ -1,6 +1,7 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { InputWrapper } from 'features/Common/Input/styled'
export const ScWrapper = styled.div`
max-width: 560px;
@ -45,7 +46,6 @@ export const ScTitle = styled.span`
`
: ''};
/* margin-bottom: 15px; */
`
export const ScText = styled.div`
@ -67,7 +67,19 @@ export const ScInfoWrap = styled.div`
`
export const ScInputGroup = styled.div`
${InputWrapper} {
${isMobileDevice
? css`
margin-top: 5px;
padding-left: 0;
@media screen and (orientation: landscape){
padding-left: 10px;
}
`
: ''};
}
.Search__input {
transform: translateY(50%);
}
@ -77,15 +89,12 @@ export const ScForm = styled.form`
max-width: 560px;
display: flex;
flex-direction: column;
${isMobileDevice
? css`
`
: ''};
& ul {
max-height: 200px;
}
& input {
font-size: 12px;
text-overflow: ellipsis;
}
@ -112,4 +121,6 @@ export const ScForm = styled.form`
max-width: 100%;
`
: ''};
`

@ -9,6 +9,8 @@ import { ArrowLoader } from 'features/ArrowLoader'
import { isMobileDevice } from 'config/userAgent'
import { dateForIos } from 'helpers/dateForIos'
import { MatchType, fetchingMatches } from '../../storeHighlightsAtoms'
import { useHighlighMatches } from './hooks'
@ -67,7 +69,7 @@ export const MatchesHighlights = () => {
label={(
<ScLabel>
<ScDate>
{format(new Date(date), 'd MMM yyyy H:mm')}
{format(new Date(dateForIos(date)), 'd MMM yyyy H:mm')}
</ScDate>&nbsp;
{team1.name_eng} - {team2.name_eng}
<ScTournament>

@ -46,11 +46,6 @@ export const ScMatchesList = styled.div`
margin-bottom: 15px;
}
}
${isMobileDevice
? css`
`
: ''};
`
export const ScLabel = styled.div`

@ -34,7 +34,7 @@ export const VideoHighlight = ({
<VideoPlayer
src={`${isProduction ? urls.production : urls.stage}?access_token=${readToken()}`}
ref={videoRef}
playing={Boolean(true)}
playing={Boolean(false)}
muted={false}
isFullscreen={false}
controls={Boolean(true)}

@ -49,4 +49,11 @@ export const ScCloseButton = styled.div`
display: flex;
justify-content: center;
align-items: center;
${isMobileDevice
? css`
right: 0;
top: 170px;
`
: ''};
`

@ -1,4 +1,4 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { isMobileDevice } from 'config/userAgent'
@ -20,10 +20,9 @@ import { ThanksPopup } from './components/ThanksPopup'
import { VideoHighlight } from './components/VideoHighlight'
import {
checkedMatches,
dataForPayHighlights,
MatchType,
openPopupChangeCard,
playerMatchesState,
} from './storeHighlightsAtoms'
import {
@ -37,7 +36,9 @@ import {
} from './styled'
const HighlightsPage = () => {
const playerMatches = useRecoilValue(playerMatchesState)
const { checkedMatchesLength } = useRecoilValue(checkedMatches)
const [startHeight] = useState(window.innerHeight)
const [showBtn, setShowBtn] = useState(true)
const [isOpenPopupChangeCard, setIsOpenPopupChangeCard] = useRecoilState(openPopupChangeCard)
const [isOpenPopupVideo, setIsOpenPopupVideo] = useState(false)
const { data } = useRecoilValue(dataForPayHighlights)
@ -49,10 +50,20 @@ const HighlightsPage = () => {
|| !isBoolean(data?.stats)
|| data?.matches.length > 10
|| data.background_music === undefined
|| !checkedMatchesLength
const price = playerMatches?.filter(
({ isChecked }: MatchType) => isChecked,
).length * 25
const price = checkedMatchesLength * 25
useEffect(() => {
window.addEventListener('resize', (e) => {
setShowBtn(startHeight === window.innerHeight)
})
return () => {
window.removeEventListener('resize', () => setShowBtn(true))
}
// eslint-disable-next-line
}, [])
return (
<ScWrapper>
@ -68,17 +79,21 @@ const HighlightsPage = () => {
<FormHighlights price={price} setIsOpenPopupVideo={setIsOpenPopupVideo} />
<MatchesHighlights />
</ScWrapperContent>
<ScButtonWrap
disabled={isNotEmpty}
onClick={() => !isNotEmpty && setIsOpenPopupChangeCard(true)}
>
<ScButton>
<T9n t='order_and_buy' />
<ScPrice>
&nbsp;${price}
</ScPrice>
</ScButton>
</ScButtonWrap>
{
showBtn && (
<ScButtonWrap
disabled={isNotEmpty}
onClick={() => !isNotEmpty && setIsOpenPopupChangeCard(true)}
>
<ScButton>
<T9n t='order_and_buy' />
<ScPrice>
&nbsp;${price}
</ScPrice>
</ScButton>
</ScButtonWrap>
)
}
<CompanyInfo clientName={client.name} textAlign='center' />
<ChangeCardPopup
btnName='buy_subscription'

@ -46,9 +46,9 @@ export const fetchingMatches = atom({
export const checkedMatches = selector({
get: ({ get }) => {
const matches = get(playerMatchesState)
const checkedPlayerMatches = matches.filter(({ isChecked }) => isChecked)
const idsCheckedMatches = checkedPlayerMatches.map(({ id }) => id)
const checkedMatchesLength = checkedPlayerMatches.length
const checkedPlayerMatches = matches?.filter(({ isChecked }) => isChecked)
const idsCheckedMatches = checkedPlayerMatches?.map(({ id }) => id)
const checkedMatchesLength = checkedPlayerMatches?.length
return {
checkedMatchesLength,

@ -7,20 +7,27 @@ import { isMobileDevice } from 'config/userAgent'
export const ScHeader = styled.div`
width: 100%;
max-height: 95px;
padding: 32px 36px;
background: linear-gradient(236.13deg, rgba(53, 96, 225, 0.56) -4.49%, rgba(0, 0, 0, 0) 98.29%), #000000;
${isMobileDevice
? css`
display: flex;
justify-content: center;
padding: 10px;
`
: ''};
`
export const ScHeaderLogo = styled(Logo)`
`
export const ScWrapper = styled.div`
width: 100%;
max-height: 100vh;
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
padding-bottom: 0;
`
export const ScWrapperContent = styled.div`
@ -53,7 +60,22 @@ export const ScButtonWrap = styled.div<{disabled: boolean}>`
justify-content: center;
margin-bottom: 100px;
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}
${isMobileDevice
? css`
position: fixed;
bottom: 10px;
left: 50%;
transform: translate(-62%, 0);
margin-bottom: 0px;
width: 270px;
@media (orientation: landscape) {
transform: translate(-50%, 0);
}
`
: ''};
opacity: ${({ disabled }) => (disabled ? 0.7 : 1)}
`
export const ScPrice = styled.span`

@ -12,13 +12,13 @@ export enum Gender {
FEMALE = 2,
}
type NamedObject = {
export type NamedObject = {
id: number,
name_eng: string,
name_rus: string,
}
type Player = {
export type PlayerTypeFromSearch = {
country?: NamedObject,
firstname_eng: string,
firstname_rus: string,
@ -30,6 +30,8 @@ type Player = {
team?: NamedObject,
}
export type PlayersType = Array<PlayerTypeFromSearch>
type Team = {
country?: NamedObject,
gender?: Gender,
@ -49,14 +51,14 @@ type Tournament = {
}
export type SearchItems = {
players?: Array<Player>,
players?: PlayersType,
teams?: Array<Team>,
tournaments?: Array<Tournament>,
}
export const getSearchItems = (
searchString: string,
abortSignal: AbortSignal,
abortSignal?: AbortSignal,
): Promise<SearchItems> => {
const config = {
body: {

Loading…
Cancel
Save