Ott 415 change user account data (#159)

* fix(ott-394): deleted search box target blank

* fix(ott-394): minor change

* fix(ott-415): not finished yet

* fix(ott-415): not finished yet

* fix(ott-415): not finished yet

* fix(ott-415): not finished yet

* fix(ott-415): not finished yet

* fix(ott-415): added configs

* fix(ott-415): partly fixed validation

* fix(ott-415): almost finished

* fix(ott-415): almost finished

* fix(ott-415): 90% finished

* fix(ott-415): deleted offset from infinite scroll

* fix(ott-415): minor bug

* fix(ott-415): deleted console.logs

* fix(ott-415): deleted delete test component

* fix(ott-415): deleted mutation

* fix(ott-415): merged dropdown with combobox

* fix(ott-415): deleted dropdown

* fix(ott-415): changed any to string

* fix(ott-415): minor bug fix

* fix(ott-415): fixed some bugs

* fix(ott-415): added types

* fix(ott-415): deleted filter options

* fix(ott-415): user account consistency

* fix(ott-415): added onClickOutside wrapper

* fix(ott-415): finish
keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Armen 5 years ago committed by GitHub
parent 904be6332a
commit 2994726834
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/config/lexics/userAccount.tsx
  2. 2
      src/config/procedures.tsx
  3. 10
      src/features/App/AuthenticatedApp.tsx
  4. 3
      src/features/App/UnauthenticatedApp.tsx
  5. 17
      src/features/Combobox/components/Arrow/index.tsx
  6. 114
      src/features/Combobox/hooks/index.tsx
  7. 110
      src/features/Combobox/index.tsx
  8. 117
      src/features/Combobox/styled.tsx
  9. 7
      src/features/Combobox/types.tsx
  10. 1
      src/features/Common/Checkbox/index.tsx
  11. 48
      src/features/Common/Input/index.tsx
  12. 4
      src/features/Common/Input/stories.tsx
  13. 15
      src/features/Common/Input/styled.tsx
  14. 1
      src/features/Common/Radio/index.tsx
  15. 5
      src/features/FormStore/hooks/useFormState.tsx
  16. 2
      src/features/HeaderFilters/components/TournamentFilter/hooks.tsx
  17. 2
      src/features/Login/index.tsx
  18. 17
      src/features/Modal/index.tsx
  19. 10
      src/features/OutsideClick/hooks/index.tsx
  20. 11
      src/features/OutsideClick/index.tsx
  21. 3
      src/features/Register/components/CardStep/index.tsx
  22. 6
      src/features/Register/components/RegistrationStep/hooks/useForm.tsx
  23. 2
      src/features/Register/components/RegistrationStep/hooks/useSubmitHandler.tsx
  24. 13
      src/features/Register/components/RegistrationStep/index.tsx
  25. 1
      src/features/Search/index.tsx
  26. 28
      src/features/UserAccount/config.tsx
  27. 117
      src/features/UserAccount/hooks.tsx
  28. 97
      src/features/UserAccount/index.tsx
  29. 7
      src/features/UserAccount/styled.tsx
  30. 57
      src/features/UserAccount/useValidateForm.tsx
  31. 0
      src/hooks/useCountries.tsx
  32. 19
      src/requests/getUserInfo.tsx
  33. 2
      src/requests/index.tsx
  34. 74
      src/requests/saveUserInfo.tsx

@ -1,3 +1,5 @@
import { publicLexics } from 'config/lexics/public'
export const userAccountLexics = {
add_card: 8313,
change: 12614,
@ -15,4 +17,5 @@ export const userAccountLexics = {
select_subscription: 12583,
subscriptions: 13016,
user_account: 12928,
...publicLexics,
}

@ -11,9 +11,11 @@ export const PROCEDURES = {
get_tournament_info: 'get_tournament_info',
get_tournament_list: 'get_tournament_list',
get_user_favorites: 'get_user_favorites',
get_user_info: 'get_user_info',
logout_user: 'logout_user',
lst_c_country: 'lst_c_country',
param_lexical: 'param_lexical',
save_user_favorite: 'save_user_favorite',
save_user_info: 'save_user_info',
save_user_match_second: 'save_user_match_second',
}

@ -30,6 +30,7 @@ import { UserFavorites } from 'features/UserFavorites'
import { UserFavoritesStore } from 'features/UserFavorites/store'
import { useMediaQuery } from 'features/MediaQuery'
import { HeaderMobile } from 'features/HeaderMobile'
import { FormStore } from 'features/FormStore'
export const AuthenticatedApp = () => {
useLexicsConfig(indexLexics)
@ -49,9 +50,8 @@ export const AuthenticatedApp = () => {
}
<Switch>
<Route path={PAGES.useraccount}>
<UserAccount />
<UserAccountForm />
</Route>
<HeaderFiltersStore>
<UserFavoritesStore>
<MainWrapper>
@ -100,3 +100,9 @@ export const AuthenticatedApp = () => {
</ScoreStore>
)
}
export const UserAccountForm = () => (
<FormStore>
<UserAccount />
</FormStore>
)

@ -8,13 +8,14 @@ import {
import { PAGES } from 'config'
import { publicLexics } from 'config/lexics/public'
import { useLexicsConfig } from 'features/LexicsStore'
import { Login } from 'features/Login'
import { Register } from 'features/Register'
import { LanguageSelect } from 'features/LanguageSelect'
import { useLexicsConfig } from 'features/LexicsStore'
export const UnauthenticatedApp = () => {
useLexicsConfig(publicLexics)
return (
<Fragment>
<LanguageSelect />

@ -2,7 +2,10 @@ import React from 'react'
import styled from 'styled-components/macro'
import { useComboboxContext } from '@reach/combobox'
type Props = {
isExpanded: boolean,
toggle: () => void,
}
const Wrapper = styled.div<{ isExpanded: boolean }>`
position: absolute;
@ -19,14 +22,14 @@ const Wrapper = styled.div<{ isExpanded: boolean }>`
)});
background-position: center;
background-repeat: no-repeat;
:hover {
cursor: pointer;
}
`
export const Arrow = () => {
const { isExpanded } = useComboboxContext()
return <Wrapper isExpanded={isExpanded} />
}
export const Arrow = ({ isExpanded, toggle }: Props) => (
<Wrapper
onClick={toggle}
isExpanded={isExpanded}
/>
)

@ -1,19 +1,22 @@
import type { ChangeEvent, FocusEvent } from 'react'
import { useState, useCallback } from 'react'
import type { ChangeEvent } from 'react'
import {
useState,
useCallback,
useRef,
useEffect,
} from 'react'
import isUndefined from 'lodash/isUndefined'
import toLower from 'lodash/toLower'
import find from 'lodash/find'
import trim from 'lodash/trim'
import size from 'lodash/size'
import { useToggle, useEventListener } from 'hooks'
import type { Props, Option } from '../types'
import { matchSort } from '../helpers'
import { useKeyboardScroll } from './useKeyboardScroll'
const isOptionClicked = (target: HTMLElement) => (
target?.getAttribute('role') === 'option'
)
const useQuery = <T extends Option>({ onChange, value }: Props<T>) => {
const [query, setQuery] = useState('')
@ -22,59 +25,124 @@ const useQuery = <T extends Option>({ onChange, value }: Props<T>) => {
}, [])
return {
onQueryChange: !isUndefined(onChange) ? onChange : onQueryChange,
query: !isUndefined(value) ? value : query,
onQueryChange: onChange ?? onQueryChange,
query: value ?? query,
setQuery,
}
}
export const useCombobox = <T extends Option>(props: Props<T>) => {
const {
filterOptions = true,
onSelect,
options,
} = props
const inputFieldRef = useRef<HTMLInputElement>(null)
const [index, setIndex] = useState(0)
const { onKeyDown, popoverRef } = useKeyboardScroll()
const {
onQueryChange,
query,
setQuery,
} = useQuery(props)
const {
close,
isOpen,
open,
toggle,
} = useToggle()
const results = matchSort(
options,
'name',
query,
)
const findOptionByName = (optionName: string) => (
const findOptionByName = useCallback((optionName: string) => (
find(
options,
({ name }) => toLower(name) === toLower(trim(optionName)),
) || null
)
), [options])
const onOptionSelect = (option: string) => {
const onOptionSelect = useCallback((option: string) => {
const selectedOption = findOptionByName(option)
setQuery(selectedOption?.name || '')
onSelect?.(selectedOption)
close()
}, [
close,
findOptionByName,
onSelect,
setQuery,
])
const onOutsideClick = (event: MouseEvent) => {
if (event.target !== inputFieldRef.current as HTMLInputElement) {
close()
}
}
const onInputBlur = (event: FocusEvent<HTMLInputElement>) => {
const target = event.relatedTarget as HTMLElement | null
// клик по элементу списка тоже вызывает onBlur
// если кликали элемент списка то событие обрабатывает onOptionSelect
if (target && isOptionClicked(target)) return
const optionSelectListener = useCallback((event: KeyboardEvent) => {
if (event.key === 'ArrowUp') {
setIndex(index - 1)
} else if (event.key === 'ArrowDown') {
setIndex(index + 1)
} else if (event.key === 'Enter') {
onSelect?.(results[index])
close()
inputFieldRef.current?.blur()
}
}, [
close,
index,
results,
onSelect,
])
onOptionSelect(query)
}
useEffect(() => {
const lastElementIndex = size(results) - 1
if (index < 0) setIndex(0)
if (index >= lastElementIndex) setIndex(lastElementIndex)
}, [
index,
options,
results,
])
useEventListener({
callback: optionSelectListener,
event: 'keydown',
})
const escPressListener = useCallback((event: KeyboardEvent) => {
if (event.key === 'Escape') {
close()
inputFieldRef.current?.blur()
}
}, [close])
useEventListener({
callback: escPressListener,
event: 'keydown',
})
return {
...useKeyboardScroll(),
onInputBlur,
close,
index,
inputFieldRef,
isOpen,
onKeyDown,
onOptionSelect,
onOutsideClick,
onQueryChange,
options: filterOptions ? results : options,
open,
options: results,
popoverRef,
query,
toggle,
}
}

@ -2,104 +2,98 @@ import React from 'react'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import '@reach/combobox/styles.css'
import { T9n } from 'features/T9n'
import { useLexicsStore } from 'features/LexicsStore'
import { OutsideClick } from 'features/OutsideClick'
import {
Label,
Column,
Error,
InputWrapper,
InputStyled,
Label,
LabelTitle,
} from 'features/Common/Input/styled'
import { Props, Option } from './types'
import { useCombobox } from './hooks'
import {
ComboboxStyled,
ComboboxInputStyled,
ComboboxPopoverStyled,
ComboboxListStyled,
ComboboxOptionStyled,
PopOver,
ListOption,
} from './styled'
import { Arrow } from './components/Arrow'
export const Combobox = <T extends Option>(props: Props<T>) => {
const {
customListStyles,
disabled,
error,
id,
label,
labelLexic,
labelWidth,
openOnFocus,
pattern,
readOnly,
required,
title,
withArrow,
} = props
const {
onInputBlur,
index,
inputFieldRef,
isOpen,
onKeyDown,
onOptionSelect,
onOutsideClick,
onQueryChange,
open,
options,
popoverRef,
query,
toggle,
} = useCombobox(props)
const { translate } = useLexicsStore()
return (
<Column>
<ComboboxStyled
<InputWrapper
error={error}
openOnFocus={openOnFocus}
onSelect={onOptionSelect}
>
<Label
labelWidth={labelWidth}
htmlFor={id}
>
{
labelLexic
? <T9n t={labelLexic} />
: label
}
{withArrow && <Arrow />}
<Label>
<LabelTitle labelWidth={labelWidth}>
{labelLexic ? <T9n t={labelLexic} /> : label}
</LabelTitle>
<InputStyled
onFocus={open}
ref={inputFieldRef}
autoComplete='off'
title={title}
value={query}
disabled={disabled}
onChange={onQueryChange}
onKeyDown={onKeyDown}
/>
</Label>
<ComboboxInputStyled
id={id}
required={required}
disabled={disabled}
value={query}
title={title}
pattern={pattern}
onChange={onQueryChange}
onBlur={onInputBlur}
onKeyDown={onKeyDown}
placeholder={translate(labelLexic || '')}
readOnly={readOnly}
/>
{!isEmpty(options) && (
<ComboboxPopoverStyled>
<ComboboxListStyled ref={popoverRef} customstyles={customListStyles}>
{map(options, ({
children,
id: optionId,
name,
}) => (
<ComboboxOptionStyled
key={optionId}
value={name}
{withArrow && (
<Arrow
isExpanded={isOpen}
toggle={toggle}
/>
)}
{isOpen && !isEmpty(options) && (
<OutsideClick fullWidth onClick={onOutsideClick}>
<PopOver
ref={popoverRef}
>
{map(options, (option, i) => (
<ListOption
onClick={() => onOptionSelect(option.name)}
aria-selected={index === i}
isHighlighted={index === i}
key={option.id}
>
{children}
</ComboboxOptionStyled>
{option.name}
</ListOption>
))}
</ComboboxListStyled>
</ComboboxPopoverStyled>
</PopOver>
</OutsideClick>
)}
</ComboboxStyled>
</InputWrapper>
<Error t={error || ''} />
</Column>
)

@ -1,100 +1,27 @@
import styled from 'styled-components/macro'
import { devices } from 'config/devices'
import {
Combobox,
ComboboxInput,
ComboboxList,
ComboboxOption,
ComboboxPopover,
} from '@reach/combobox'
import {
wrapperStyles,
inputStyles,
Label,
} from 'features/Common/Input/styled'
import { customScrollbar, customStylesMixin } from 'features/Common'
export const ComboboxStyled = styled(Combobox)`
${wrapperStyles}
position: relative;
@media ${devices.laptop} {
margin-top: 12px;
${Label} {
display: block;
span {
display: none;
}
}
}
@media ${devices.tablet} {
margin-top: -8px;
}
`
export const ComboboxInputStyled = styled(ComboboxInput)`
${inputStyles}
padding-right: 24px;
::placeholder {
opacity: 0;
}
@media ${devices.laptop} {
margin-left: -105px;
padding-right: 4px;
::placeholder {
opacity: 1;
width: 90%;
font-style: normal;
font-weight: normal;
white-space: nowrap;
font-size: 16px;
line-height: 24px;
letter-spacing: -0.01em;
text-align: center;
}
}
@media ${devices.mobile} {
text-align: center;
font-size: 14px;
}
`
export const ComboboxPopoverStyled = styled(ComboboxPopover)`
border: none;
export const Label = styled.label`
letter-spacing: -0.01em;
color: #999999;
font-size: 16px;
`
export const ComboboxListStyled = styled(ComboboxList)`
background: #666;
min-width: 544px;
max-height: 336px;
height: auto;
border-radius: 2px;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3);
export const PopOver = styled.ul`
position: absolute;
top: 9px;
left: -164px;
min-height: 70px;
max-height: 400px;
width: 100%;
top: 55px;
left: 0px;
overflow: auto;
z-index: 2;
${customScrollbar}
${customStylesMixin}
@media ${devices.laptop} {
left: -33px;
min-width: calc(100% + 58px);
}
`
export const ComboboxOptionStyled = styled(ComboboxOption)`
export const ListOption = styled.li<{isHighlighted?: boolean}>`
width: 100%;
height: 48px;
font-size: 16px;
@ -102,16 +29,20 @@ export const ComboboxOptionStyled = styled(ComboboxOption)`
display: flex;
align-items: center;
padding-left: 24px;
color: #ccc;
background: transparent;
&[aria-selected="true"] {
background: #999;
color: #fff;
}
color: ${({ isHighlighted }) => (
isHighlighted
? '#fff'
: '#ccc'
)};
background-color: ${({ isHighlighted }) => (
isHighlighted
? '#999'
: '#3F3F3F'
)};
cursor: pointer;
&:hover {
background: #999;
background-color: #999;
color: #fff;
}
`

@ -13,24 +13,17 @@ export type Option = {
}
export type Props<T> = Pick<InputHTMLAttributes<HTMLInputElement>, (
| 'id'
| 'onChange'
| 'required'
| 'disabled'
| 'pattern'
| 'title'
| 'placeholder'
| 'readOnly'
)> & {
customListStyles?: CustomStyles,
error?: string | null,
filterOptions?: boolean,
label?: string,
labelLexic?: string,
labelWidth?: number,
onChange?: (event: ChangeEvent<HTMLInputElement>) => void,
onSelect?: (option: T | null) => void,
openOnFocus?: boolean,
options: Array<T>,
value?: string,
withArrow?: boolean,

@ -26,7 +26,6 @@ export const Checkbox = ({
}: Props) => (
<Wrapper>
<Input
id={id}
type='checkbox'
name={name}
value={value}

@ -9,6 +9,7 @@ import {
InputWrapper,
InputStyled,
Label,
LabelTitle,
Error,
Column,
} from './styled'
@ -16,7 +17,6 @@ import {
type Props = {
autoComplete?: string,
defaultValue?: string,
id: string,
inputWidth?: number,
label?: string,
labelLexic?: string,
@ -36,7 +36,6 @@ export const Input = ({
autoComplete = '',
defaultValue,
error,
id,
inputWidth,
label,
labelLexic,
@ -54,6 +53,7 @@ export const Input = ({
wrapperWidth,
}: Props) => {
const { translate } = useLexicsStore()
return (
<Column>
<InputWrapper
@ -61,32 +61,26 @@ export const Input = ({
paddingX={paddingX}
error={error}
>
<Label
htmlFor={id}
labelWidth={labelWidth}
>
{
labelLexic
? <T9n t={labelLexic} />
: label
}
<Label>
<LabelTitle labelWidth={labelWidth}>
{labelLexic ? <T9n t={labelLexic} /> : label}
</LabelTitle>
<InputStyled
autoComplete={autoComplete}
type={type}
required={required}
value={value}
defaultValue={defaultValue}
onChange={onChange}
onBlur={onBlur}
onFocus={onFocus}
maxLength={maxLength}
inputWidth={inputWidth}
pattern={pattern}
title={title}
placeholder={translate(labelLexic || '')}
/>
</Label>
<InputStyled
autoComplete={autoComplete}
id={id}
type={type}
required={required}
value={value}
defaultValue={defaultValue}
onChange={onChange}
onBlur={onBlur}
onFocus={onFocus}
maxLength={maxLength}
inputWidth={inputWidth}
pattern={pattern}
title={title}
placeholder={translate(labelLexic || '')}
/>
</InputWrapper>
<Error t={error || ''} />
</Column>

@ -9,7 +9,6 @@ export default {
export const Empty = () => (
<Input
id='email'
type='email'
label='Email'
/>
@ -18,14 +17,12 @@ export const Empty = () => (
export const EmailPassword = () => (
<Fragment>
<Input
id='email'
type='email'
label='Email'
labelWidth={72}
defaultValue='someveeeeeeeeerylooooooooong@email.com'
/>
<Input
id='password'
type='password'
label='Password'
labelWidth={72}
@ -36,7 +33,6 @@ export const EmailPassword = () => (
export const WithError = () => (
<Input
id='email'
type='email'
label='Email'
error='Error message'

@ -28,11 +28,15 @@ export const wrapperStyles = css<WrapperProps>`
border-width: 1px;
`
type LabelProps = {
type TitleProps = {
labelWidth?: number,
}
export const Label = styled.label<LabelProps>`
export const Label = styled.label`
width: 100%;
display: flex;
`
export const LabelTitle = styled.p<TitleProps>`
font-style: normal;
font-weight: normal;
white-space: nowrap;
@ -42,8 +46,8 @@ export const Label = styled.label<LabelProps>`
padding-top: 2px;
color: ${({ theme: { colors } }) => colors.secondary};
width: ${({ labelWidth }) => (labelWidth ? `${labelWidth}px` : '')};
@media ${devices.laptop} {
@media ${devices.laptop} {
display: none;
}
`
@ -117,8 +121,8 @@ export const InputStyled = styled.input<InputProps>`
}
}
`
export const InputWrapper = styled.div<WrapperProps>`
position: relative;
${wrapperStyles}
@ -150,6 +154,7 @@ type ErrorProps = {
}
export const Error = styled(T9n)<ErrorProps>`
display: block;
min-height: 16px;
margin-top: 5px;
font-style: normal;

@ -26,7 +26,6 @@ export const Radio = ({
}: Props) => (
<Wrapper>
<Input
id={id}
type='radio'
name={name}
value={value}

@ -1,7 +1,7 @@
import type { ChangeEvent } from 'react'
import { useState, useCallback } from 'react'
import isString from 'lodash/isString'
import isObject from 'lodash/isObject'
type FieldState = {
error: string | null,
@ -24,7 +24,7 @@ export const useFormState = () => {
const updateFormValue = useCallback(
(fieldName: string) => (
(event: ChangeEvent<HTMLInputElement> | string) => {
const value = isString(event) ? event : event.target.value
const value = isObject(event) ? event.target.value : event
setFormState(((state) => {
const newState = {
...state,
@ -55,6 +55,7 @@ export const useFormState = () => {
},
[],
)
return {
readFormError,
readFormValue,

@ -32,7 +32,6 @@ export const useTournamentFilter = () => {
getSportTournaments(selectedSportTypeId).then(setList)
}, [selectedSportTypeId])
const [offset, setOffset] = useState(0)
const [requestSent, setRequestSent] = useState(false)
const handleScroll = (e: BaseSyntheticEvent) => {
@ -41,7 +40,6 @@ export const useTournamentFilter = () => {
if (scrollPositionBottom && !requestSent) {
setRequestSent(true)
setOffset(offset + 1)
getSportTournaments(selectedSportTypeId).then((res) => {
setList([...list, ...res])
setRequestSent(false)

@ -40,7 +40,6 @@ const LoginForm = () => {
</BlockTitle>
<Input
id={formIds.email}
type='email'
value={readFormValue(formIds.email)}
error={readFormError(formIds.email)}
@ -50,7 +49,6 @@ const LoginForm = () => {
labelWidth={labelWidth}
/>
<Input
id={formIds.password}
type='password'
value={readFormValue(formIds.password)}
error={readFormError(formIds.password)}

@ -1,10 +1,9 @@
import type { ReactNode } from 'react'
import React, {
useRef,
} from 'react'
import React, { useRef } from 'react'
import ReactDOM from 'react-dom'
import { OutsideClick } from 'features/OutsideClick'
import {
ModalContainer,
ModalWindow,
@ -27,10 +26,12 @@ export const Modal = ({
return isOpen
? ReactDOM.createPortal(
<ModalContainer>
<ModalWindow>
<ModalCloseButton onClick={close} />
{children}
</ModalWindow>
<OutsideClick onClick={close}>
<ModalWindow>
<ModalCloseButton onClick={close} />
{children}
</ModalWindow>
</OutsideClick>
</ModalContainer>,
modalRoot.current as Element,
)

@ -3,21 +3,21 @@ import { useRef } from 'react'
import { useEventListener } from 'hooks'
type Args = {
onClick: Function,
onClick: (event: MouseEvent) => void,
}
export const useOutsideClickEffect = ({
onClick,
}: Args) => {
const wrapperRef = useRef<HTMLDivElement>(null)
const handleOutsideClick = ({ target }: MouseEvent) => {
const targetNode = target instanceof Node
? target
const handleOutsideClick = (e: MouseEvent) => {
const targetNode = e.target instanceof Node
? e.target
: null
/** деструктуризация wrapperRef не сработает, ссылка на реф теряется */
if (!wrapperRef.current?.contains(targetNode)) {
onClick()
onClick(e)
}
}

@ -8,20 +8,23 @@ import { useOutsideClickEffect } from './hooks'
type Props = {
/** элемент, которому необходим функционал `OutsideClick` */
children: ReactNode,
fullWidth?: boolean,
/** функция-коллбек, отрабатывающая по клику вне области элемента */
onClick: () => void,
onClick: (event: MouseEvent) => void,
}
const OutsideClickWrapper = styled.div``
const OutsideClickWrapper = styled.div<{fullWidth: boolean}>`
width: ${({ fullWidth }) => (fullWidth ? '100%' : '')};
`
export const OutsideClick = ({
children,
fullWidth = false,
onClick,
}: Props) => {
const wrapperRef = useOutsideClickEffect({ onClick })
return (
<OutsideClickWrapper ref={wrapperRef}>
<OutsideClickWrapper fullWidth={fullWidth} ref={wrapperRef}>
{children}
</OutsideClickWrapper>
)

@ -22,7 +22,6 @@ export const CardStep = () => {
<Card>
<Input
id='cardNumber'
labelLexic='form_card_number'
labelWidth={122}
paddingX={21}
@ -30,12 +29,10 @@ export const CardStep = () => {
/>
<Row>
<Input
id='expiration'
labelLexic='form_card_expiration'
title={defaultMessage}
/>
<Input
id='code'
labelLexic='form_card_code'
maxLength={3}
title={defaultMessage}

@ -8,9 +8,9 @@ import { isValidEmail } from 'features/Register/helpers/isValidEmail'
import { isValidPhone } from 'features/Register/helpers/isValidPhone'
import { formatPhoneCode } from 'features/Register/helpers/formatPhoneCode'
import type { SelectedCountry } from 'hooks/useCountries'
import { useCountries } from 'hooks/useCountries'
import { formIds } from '../config'
import type { SelectedCountry } from './useCountries'
import { useCountries } from './useCountries'
import { useCities } from './useCities'
import { useSubmitHandler } from './useSubmitHandler'
@ -21,11 +21,13 @@ export const useRegistrationForm = () => {
updateFormError,
updateFormValue,
} = useForm()
const {
countries,
selectedCountry,
setSelectedCountry,
} = useCountries()
const {
cities,
getCities,

@ -7,8 +7,8 @@ import type { City } from 'requests'
import { useAuthStore } from 'features/AuthStore'
import { useForm } from 'features/FormStore'
import type { SelectedCountry } from 'hooks/useCountries'
import { formIds } from '../config'
import type { SelectedCountry } from './useCountries'
import { useValidateForm } from './useValidateForm'
type Args = {

@ -36,9 +36,7 @@ const Registration = () => {
<BlockTitle>
<T9n t='step_title_registration' />
</BlockTitle>
<Input
id={formIds.firstname}
value={readFormValue(formIds.firstname)}
error={readFormError(formIds.firstname)}
labelLexic='form_firstname'
@ -46,7 +44,6 @@ const Registration = () => {
onChange={updateFormValue(formIds.firstname)}
/>
<Input
id={formIds.lastname}
value={readFormValue(formIds.lastname)}
error={readFormError(formIds.lastname)}
labelLexic='form_lastname'
@ -54,7 +51,6 @@ const Registration = () => {
onChange={updateFormValue(formIds.lastname)}
/>
<Input
id={formIds.email}
value={readFormValue(formIds.email)}
error={readFormError(formIds.email)}
type='email'
@ -64,7 +60,6 @@ const Registration = () => {
onBlur={onEmailBlur}
/>
<Input
id={formIds.password}
value={readFormValue(formIds.password)}
error={readFormError(formIds.password)}
type='password'
@ -73,9 +68,7 @@ const Registration = () => {
onChange={updateFormValue(formIds.password)}
/>
<Combobox
openOnFocus
withArrow
id={formIds.country}
value={readFormValue(formIds.country)}
error={readFormError(formIds.country)}
labelLexic='form_country'
@ -85,7 +78,6 @@ const Registration = () => {
onSelect={onCountrySelect}
/>
<Input
id={formIds.region}
value={readFormValue(formIds.region)}
error={readFormError(formIds.region)}
labelLexic='form_region'
@ -93,7 +85,6 @@ const Registration = () => {
onChange={onRegionOrCityChange(formIds.region)}
/>
<Combobox
id={formIds.city}
value={readFormValue(formIds.city)}
error={readFormError(formIds.city)}
labelLexic='form_city'
@ -103,7 +94,6 @@ const Registration = () => {
onSelect={onCitySelect}
/>
<Input
id={formIds.postalCode}
value={readFormValue(formIds.postalCode)}
error={readFormError(formIds.postalCode)}
labelLexic='form_postal_code'
@ -111,7 +101,6 @@ const Registration = () => {
onChange={updateFormValue(formIds.postalCode)}
/>
<Input
id={formIds.address1}
value={readFormValue(formIds.address1)}
error={readFormError(formIds.address1)}
labelLexic='form_address1'
@ -119,7 +108,6 @@ const Registration = () => {
onChange={updateFormValue(formIds.address1)}
/>
<Input
id={formIds.address2}
value={readFormValue(formIds.address2)}
error={readFormError(formIds.address2)}
labelLexic='form_address2'
@ -127,7 +115,6 @@ const Registration = () => {
onChange={updateFormValue(formIds.address2)}
/>
<Input
id={formIds.phone}
value={readFormValue(formIds.phone)}
error={readFormError(formIds.phone)}
type='tel'

@ -39,7 +39,6 @@ export const Search = () => {
<Form role='search' onSubmit={onSubmit} isMatch={isMatch}>
<Input
autoComplete='off'
id='searchInput'
type='search'
onChange={onChange}
onFocus={onFocus}

@ -0,0 +1,28 @@
export const formIds = {
address_line1: 'address_line1',
address_line2: 'address_line2',
city: 'city',
city_id: 'city_id',
country: 'country',
countryId: 'countryId',
firstname: 'firstname',
formError: 'formError',
lastname: 'lastname',
password: 'password',
phone: 'phone',
postalCode: 'postalCode',
region: 'region',
}
export const requiredFields = [
formIds.country,
formIds.firstname,
formIds.lastname,
formIds.phone,
]
export const simpleValidationFields = [
formIds.country,
formIds.firstname,
formIds.lastname,
]

@ -0,0 +1,117 @@
import {
useEffect,
useCallback,
useRef,
} from 'react'
import forEach from 'lodash/forEach'
import trim from 'lodash/trim'
import isString from 'lodash/isString'
import type { SelectedCountry } from 'hooks/useCountries'
import { useCountries } from 'hooks/useCountries'
import { getUserInfo } from 'requests/getUserInfo'
import { saveUserInfo } from 'requests/saveUserInfo'
import type { UserInfo } from 'requests/saveUserInfo'
import { useForm } from 'features/FormStore'
import { useLexicsStore } from 'features/LexicsStore'
import { useValidateForm } from './useValidateForm'
import { formIds } from './config'
type Name = 'name_rus' | 'name_eng'
export const useUserInfo = () => {
const {
readFormError,
readFormValue,
updateFormError,
updateFormValue,
} = useForm()
const { suffix } = useLexicsStore()
const validateForm = useValidateForm()
const saveButton = useRef<HTMLButtonElement>(null)
const {
countries,
setSelectedCountry,
} = useCountries()
const readTrimmedValue = useCallback(
(fieldName: string) => trim(readFormValue(fieldName)),
[readFormValue],
)
const handleSubmit = useCallback(() => {
if (validateForm()) {
const firstname = readTrimmedValue(formIds.firstname)
const lastname = readTrimmedValue(formIds.lastname)
const phone = readTrimmedValue(formIds.phone)
const password = readTrimmedValue(formIds.password)
const postalCode = Number(readTrimmedValue(formIds.postalCode))
const region = readTrimmedValue(formIds.region)
const address_line1 = readTrimmedValue(formIds.address_line1)
const address_line2 = readTrimmedValue(formIds.address_line2)
const city = readTrimmedValue(formIds.city)
const city_id = Number(readTrimmedValue(formIds.city_id))
const countryId = Number(readTrimmedValue(formIds.countryId))
saveUserInfo({
address_line1,
address_line2,
city,
city_id,
countryId,
firstname,
lastname,
password,
phone,
postalCode,
region,
}).then(() => saveButton?.current?.blur())
}
}, [
readTrimmedValue,
validateForm,
])
const onCountrySelect = useCallback((country: SelectedCountry) => {
setSelectedCountry(country)
const countryName = country ? country[`name_${suffix}` as Name] : ''
updateFormValue(formIds.country)(countryName)
// TO DO пока не решили сбрасываем ли город и адрес
// updateFormValue(formIds.region)('')
updateFormValue(formIds.countryId)(`${country?.id}`)
}, [
setSelectedCountry,
updateFormValue,
suffix,
])
useEffect(() => {
getUserInfo().then((res: UserInfo) => {
forEach(res, (value: string | number | SelectedCountry, key: string) => {
if (value && typeof value === 'object') {
onCountrySelect(value)
} else if (isString(value)) {
updateFormValue(key)(value)
}
})
})
}, [updateFormValue, onCountrySelect])
return {
countries,
handleSubmit,
onCountrySelect,
readFormError,
readFormValue,
saveButton,
updateFormError,
updateFormValue,
}
}

@ -1,19 +1,21 @@
import React from 'react'
import { userAccountLexics } from 'config/lexics/userAccount'
import { Background } from 'features/Background'
import { Combobox } from 'features/Combobox'
import { Input } from 'features/Common'
import { Form } from 'features/Login/styled'
import { T9n } from 'features/T9n'
import { Error } from 'features/Common/Input/styled'
import { useLexicsStore, useLexicsConfig } from 'features/LexicsStore'
import { userAccountLexics } from 'config/lexics/userAccount'
import { CardNumber } from './CardNumber'
import { UserAccountButton } from './UserAccountButton'
import { PageTitle } from './PageTitle'
import { UserAccountSubscription } from './UserAccountSubscription'
import { formIds } from './config'
import { TextNoBorder } from './TextNoBorder'
import { useUserInfo } from './hooks'
import {
FormWrapper,
@ -22,14 +24,26 @@ import {
UserAccountFormWrapper,
UserAccountBlockTitle,
UserAccountComponentWrapper,
ButtonWrapper,
} from './styled'
const labelWidth = 78
export const UserAccount = () => {
useLexicsConfig(userAccountLexics)
const { translate } = useLexicsStore()
const {
countries,
handleSubmit,
onCountrySelect,
readFormError,
readFormValue,
saveButton,
updateFormValue,
} = useUserInfo()
return (
<UserAccountComponentWrapper>
<Background>
@ -42,39 +56,46 @@ export const UserAccount = () => {
<T9n t='main' />
</UserAccountBlockTitle>
<Input
id='firstname'
label={translate('name')}
labelWidth={labelWidth}
/>
<Input
id='lastname'
label={translate('lastname')}
value={readFormValue(formIds.firstname)}
labelLexic='name'
labelWidth={labelWidth}
onChange={updateFormValue(formIds.firstname)}
error={readFormError(formIds.firstname)}
/>
<Input
id='phone'
label={translate('phone')}
value={readFormValue(formIds.lastname)}
labelLexic='lastname'
labelWidth={labelWidth}
onChange={updateFormValue(formIds.lastname)}
error={readFormError(formIds.lastname)}
/>
<Input
id='email'
type='email'
label={translate('mail')}
value={readFormValue(formIds.phone)}
labelLexic='phone'
labelWidth={labelWidth}
onChange={updateFormValue(formIds.phone)}
error={readFormError(formIds.phone)}
/>
<Combobox
withArrow
options={[
{ id: 1, name: 'Армения' },
{ id: 2, name: 'Латвия' },
{ id: 3, name: 'Россия' },
]}
label={translate('country')}
labelLexic='form_country'
options={countries}
labelWidth={labelWidth}
withArrow
onChange={updateFormValue(formIds.country)}
value={readFormValue(formIds.country)}
error={readFormError(formIds.country)}
onSelect={onCountrySelect}
/>
<OutlinedButton>
<T9n t='save_changes' />
</OutlinedButton>
<ButtonWrapper>
<OutlinedButton
type='button'
onClick={handleSubmit}
ref={saveButton}
>
<T9n t='save_changes' />
</OutlinedButton>
<Error t={readFormError(formIds.formError) || ''} />
</ButtonWrapper>
</Form>
</FormWrapper>
<FormWrapper>
@ -138,35 +159,31 @@ export const UserAccount = () => {
<T9n t='main' />
</UserAccountBlockTitle>
<Input
id='firstname'
label={translate('name')}
labelLexic='name'
labelWidth={labelWidth}
/>
<Input
id='lastname'
label={translate('lastname')}
labelLexic='lastname'
labelWidth={labelWidth}
/>
<Input
id='phone'
label={translate('phone')}
labelLexic='phone'
labelWidth={labelWidth}
/>
<Input
id='email'
type='email'
label={translate('mail')}
labelLexic='mail'
labelWidth={labelWidth}
/>
<Combobox
withArrow
options={[
{ id: 1, name: 'Армения' },
{ id: 2, name: 'Латвия' },
{ id: 3, name: 'Россия' },
]}
label={translate('country')}
labelLexic='form_country'
options={countries}
labelWidth={labelWidth}
withArrow
onChange={updateFormValue(formIds.country)}
value={readFormValue(formIds.country)}
error={readFormError(formIds.country)}
onSelect={onCountrySelect}
/>
<OutlinedButton>
<T9n t='save_changes' />

@ -22,6 +22,13 @@ export const UserAccountFormWrapper = styled.div`
flex-wrap: wrap;
`
export const ButtonWrapper = styled.div`
display: flex;
align-self: flex-start;
flex-direction: column;
align-items: center;
`
export const UserAccountWrapper = styled.div`
width: 1776px;
margin-top: 140px;

@ -0,0 +1,57 @@
import trim from 'lodash/trim'
import reduce from 'lodash/reduce'
import { useForm } from 'features/FormStore'
import { isValidPhone } from 'features/Register/helpers/isValidPhone'
import {
formIds,
requiredFields,
simpleValidationFields,
} from './config'
export const useValidateForm = () => {
const {
allFieldsEmpty,
isFieldEmpty,
readFormValue,
updateFormError,
} = useForm()
const readTrimmedValue = (fieldName: string) => trim(readFormValue(fieldName))
const setErrorOnEmptyFields = (fieldNames: Array<string>, message: string) => (
reduce(
fieldNames,
(acc, fieldName) => {
if (isFieldEmpty(fieldName)) {
updateFormError(fieldName, message)
return true
}
return acc
},
false,
)
)
const validateForm = () => {
let hasError = false
const phone = readTrimmedValue(formIds.phone)
if (allFieldsEmpty(requiredFields)) {
updateFormError(formIds.formError, 'error_fill_out_required_fields')
setErrorOnEmptyFields(requiredFields, '')
return false
}
hasError = setErrorOnEmptyFields(simpleValidationFields, 'error_fill_out_this_field')
if (!isValidPhone(phone)) {
updateFormError(formIds.phone, 'error_invalid_phone_format')
hasError = true
}
return !hasError
}
return validateForm
}

@ -0,0 +1,19 @@
import {
DATA_URL,
PROCEDURES,
} from 'config'
import { callApi, getResponseData } from 'helpers'
import type { UserInfo } from 'requests/saveUserInfo'
const proc = PROCEDURES.get_user_info
export const getUserInfo = (): Promise<UserInfo> => {
const config = {
body: { params: {}, proc },
}
return callApi({
config,
url: DATA_URL,
}).then(getResponseData(proc))
}

@ -11,6 +11,8 @@ export * from './modifyUserSportFavs'
export * from './getSportTournaments'
export * from './getTournamentInfo'
export * from './getTeamInfo'
export * from './getUserInfo'
export * from './getMatchInfo'
export * from './reportPlayerProgress'
export * from './getVideos'
export * from './saveUserInfo'

@ -0,0 +1,74 @@
import {
DATA_URL,
PROCEDURES,
} from 'config'
import { callApi } from 'helpers'
const proc = PROCEDURES.save_user_info
export type UserInfo = {
address_line1: string,
address_line2: string,
city: string,
city_id: number,
countryId: number,
firstname: string,
lastname: string,
password: string,
phone: string,
postalCode: number,
region: string,
}
type Response = {
_p_error: string | null,
_p_status: 1 | 2,
}
const responseStatus = {
FAILURE: 2,
SUCCESS: 1,
}
export const saveUserInfo = async ({
address_line1,
address_line2,
city,
city_id,
countryId,
firstname,
lastname,
password,
phone,
postalCode,
region,
}: UserInfo) => {
const config = {
body: {
params: {
_p_address_line1: address_line1,
_p_address_line2: address_line2,
_p_city: city,
_p_city_id: city_id,
_p_country_id: countryId,
_p_firstname: firstname,
_p_lastname: lastname,
_p_password: password,
_p_phone: phone,
_p_postal_code: postalCode,
_p_region: region,
},
proc,
},
}
const response: Response = await callApi({
config,
url: DATA_URL,
})
if (response._p_status === responseStatus.SUCCESS) {
return Promise.resolve(response)
}
return Promise.reject(response._p_error)
}
Loading…
Cancel
Save