Ott 1633 add address (#541)

* feat: 🎸 ott-1633-add-address

address added, fix

* feat: 🎸 ott-1633-add-address

fields removed

* feat: 🎸 ott-1633-add-address

home removed

* feat: 🎸 ott-1633-add-address

fix

* feat: 🎸 ott-1633-address

fix pr

* feat: 🎸 ott-1633-add-address

fix pr
keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Zoia 4 years ago committed by Mirlan
parent c42818cc63
commit 90fc409557
  1. BIN
      public/images/home.png
  2. 9
      src/config/lexics/payment.tsx
  3. 1
      src/config/lexics/userAccount.tsx
  4. 5
      src/features/AddCardForm/components/ElementContainer/index.tsx
  5. 94
      src/features/AddCardForm/components/Form/hooks/index.tsx
  6. 46
      src/features/AddCardForm/components/Form/hooks/useCountries.tsx
  7. 20
      src/features/AddCardForm/components/Form/hooks/validateFields.tsx
  8. 60
      src/features/AddCardForm/components/Form/index.tsx
  9. 41
      src/features/AddCardForm/styled.tsx
  10. 12
      src/features/Combobox/index.tsx
  11. 1
      src/features/Combobox/styled.tsx
  12. 1
      src/features/Combobox/types.tsx
  13. 21
      src/features/UserAccount/components/Header/index.tsx
  14. 70
      src/features/UserAccount/components/PersonalInfoForm/hooks/useCountries.tsx
  15. 12
      src/features/UserAccount/components/PersonalInfoForm/hooks/useUserInfo.tsx
  16. 43
      src/features/UserAccount/components/PersonalInfoForm/hooks/useUserInfoForm.tsx
  17. 16
      src/features/UserAccount/components/PersonalInfoForm/index.tsx

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

@ -1,7 +1,16 @@
export const paymentLexics = { export const paymentLexics = {
add_card: 8313, add_card: 8313,
address: 15203,
billing_address: 15489,
card_holder_name: 2021, card_holder_name: 2021,
city: 15206,
country: 835,
error_address_latin_letters: 15758,
error_can_not_add_card: 14447, error_can_not_add_card: 14447,
error_city_latin_letters: 15759,
error_empty_address: 15755,
error_empty_city: 15754,
error_empty_country: 15753,
error_empty_name: 15290, error_empty_name: 15290,
error_payment_unsuccessful: 14446, error_payment_unsuccessful: 14446,
} }

@ -13,7 +13,6 @@ const navigations = {
export const userAccountLexics = { export const userAccountLexics = {
change: 12614, change: 12614,
change_password: 15054, change_password: 15054,
country: 835,
delete: 848, delete: 848,
delete_card: 8692, delete_card: 8692,
language: 15053, language: 15053,

@ -28,10 +28,7 @@ const Label = styled.label<LabelProps>`
line-height: 48px; line-height: 48px;
letter-spacing: -0.01em; letter-spacing: -0.01em;
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
margin-bottom: 10px;
:not(:first-child) {
margin-top: 10px;
}
${isMobileDevice ${isMobileDevice
? css` ? css`

@ -23,7 +23,17 @@ import { useObjectState } from 'hooks'
import { useCardsStore } from 'features/CardsStore' import { useCardsStore } from 'features/CardsStore'
import { useLexicsStore } from 'features/LexicsStore' import { useLexicsStore } from 'features/LexicsStore'
import { SelectedCountry, useCountries } from './useCountries'
import {
isValidAddress,
isValidName,
validateFields,
} from './validateFields'
export enum ElementTypes { export enum ElementTypes {
CardAddress = 'cardAddress',
CardCity = 'cardCity',
CardCountry = 'cardCountry',
CardCvc = 'cardCvc', CardCvc = 'cardCvc',
CardExpiry = 'cardExpiry', CardExpiry = 'cardExpiry',
CardHolder = 'cardHolder', CardHolder = 'cardHolder',
@ -44,6 +54,9 @@ const inputState = {
} }
const initialState = { const initialState = {
cardAddress: inputState,
cardCity: inputState,
cardCountry: inputState,
cardCvc: inputState, cardCvc: inputState,
cardExpiry: inputState, cardExpiry: inputState,
cardHolder: inputState, cardHolder: inputState,
@ -56,29 +69,69 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
const { translate } = useLexicsStore() const { translate } = useLexicsStore()
const { onAddCard, setError: setCardError } = useCardsStore() const { onAddCard, setError: setCardError } = useCardsStore()
const [name, setName] = useState('') const [name, setName] = useState('')
const [city, setCity] = useState('')
const [address, setAddress] = useState('')
const [inputStates, setInputStates] = useObjectState(initialState) const [inputStates, setInputStates] = useObjectState(initialState)
const [errorMessage, setErrorMessage] = useState('') const [errorMessage, setErrorMessage] = useState('')
const [loader, setLoader] = useState(false) const [loader, setLoader] = useState(false)
const {
countries,
selectedCountry,
setSelectedCountry,
} = useCountries()
const resetErrors = useCallback(() => { const resetErrors = useCallback(() => {
setErrorMessage('') setErrorMessage('')
setCardError('') setCardError('')
}, [setErrorMessage, setCardError]) }, [setErrorMessage, setCardError])
const setElementTypeState = useCallback((elementType: ElementTypes, value: string) => {
const elementState = inputStates[elementType]
setInputStates({
[elementType]: {
...elementState,
empty: !value,
},
})
}, [inputStates, setInputStates])
const onNameChange = (e: ChangeEvent<HTMLInputElement>) => { const onNameChange = (e: ChangeEvent<HTMLInputElement>) => {
const { value } = e.target const { value } = e.target
if (/^[A-Za-z .,'-]{0,500}$/.test(value)) { if (isValidName(value)) {
setName(toUpper(value)) setName(toUpper(value))
resetErrors() resetErrors()
setElementTypeState(ElementTypes.CardHolder, value)
}
}
const onCountryChange = useCallback((country: SelectedCountry) => {
resetErrors()
if (country?.id === selectedCountry?.id) return
setSelectedCountry(country)
setElementTypeState(ElementTypes.CardCountry, country?.name_eng || '')
}, [resetErrors, selectedCountry?.id, setElementTypeState, setSelectedCountry])
const cardHolderState = inputStates[ElementTypes.CardHolder] const onCityChange = (e: ChangeEvent<HTMLInputElement>) => {
setInputStates({ const { value } = e.target
[ElementTypes.CardHolder]: { if (!isValidAddress(value)) {
...cardHolderState, setErrorMessage(translate('error_city_latin_letters'))
empty: !value, return
},
})
} }
setCity(value)
resetErrors()
setElementTypeState(ElementTypes.CardCity, value)
}
const onAddressChange = (e: ChangeEvent<HTMLInputElement>) => {
const { value } = e.target
if (!isValidAddress(value)) {
setErrorMessage(translate('error_address_latin_letters'))
return
}
setAddress(value)
resetErrors()
setElementTypeState(ElementTypes.CardAddress, value)
} }
const onInputsChange = (e: StripeElementChangeEvent) => { const onInputsChange = (e: StripeElementChangeEvent) => {
@ -110,8 +163,15 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
return return
} }
if (!name) { const fieldError = validateFields({
setErrorMessage(translate('error_empty_name')) address,
city,
country: selectedCountry?.name || '',
name,
})
if (fieldError) {
setErrorMessage(translate(fieldError))
return return
} }
@ -120,7 +180,12 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
setLoader(true) setLoader(true)
const { error: tokenError, token } = await stripe.createToken( const { error: tokenError, token } = await stripe.createToken(
cardNumberElement, cardNumberElement,
{ name }, {
address_city: city,
address_country: selectedCountry?.name || '',
address_line1: address,
name,
},
) )
if (tokenError) { if (tokenError) {
@ -139,14 +204,21 @@ export const useFormSubmit = ({ onAddSuccess }: Props) => {
}, [setCardError]) }, [setCardError])
return { return {
address,
city,
countries,
errorMessage, errorMessage,
handleSubmit, handleSubmit,
isLabelVisible, isLabelVisible,
loader, loader,
name, name,
onAddressChange,
onCityChange,
onCountryChange,
onInputsBlur, onInputsBlur,
onInputsChange, onInputsChange,
onInputsFocus, onInputsFocus,
onNameChange, onNameChange,
selectedCountry,
} }
} }

@ -0,0 +1,46 @@
import {
useEffect,
useState,
useMemo,
} from 'react'
import orderBy from 'lodash/orderBy'
import map from 'lodash/map'
import type { Countries, Country } from 'requests'
import { getCountries } from 'requests'
export type SelectedCountry = (Country & {
name: string,
}) | null
const useCountriesList = () => {
const [countries, setCountries] = useState<Countries>([])
useEffect(() => {
getCountries().then(setCountries)
}, [])
return countries
}
export const useCountries = () => {
const countries = useCountriesList()
const [selectedCountry, setSelectedCountry] = useState<SelectedCountry>(null)
const transformedCountries = useMemo(
() => orderBy(
map(countries, (country) => ({
...country,
name: country.name_eng,
})),
({ name }) => name,
),
[countries],
)
return {
countries: transformedCountries,
selectedCountry,
setSelectedCountry,
}
}

@ -0,0 +1,20 @@
import size from 'lodash/size'
export const isValidName = (value: string) => (/^[A-Za-z .,'-]{0,500}$/.test(value))
export const isValidAddress = (value: string) => (/^[A-Za-z0-9 #!.,'-_]{0,500}$/.test(value))
type fieldsType = {
address: string,
city: string,
country: string,
name: string,
}
export const validateFields = (fields: fieldsType) => {
if (!fields.name) return 'error_empty_name'
if (!fields.country) return 'error_empty_country'
if (size(fields.city) < 3) return 'error_empty_city'
if (size(fields.address) < 10) return 'error_empty_address'
return false
}

@ -1,3 +1,4 @@
import { useRouteMatch } from 'react-router-dom'
import { import {
CardNumberElement, CardNumberElement,
CardExpiryElement, CardExpiryElement,
@ -5,6 +6,7 @@ import {
} from '@stripe/react-stripe-js' } from '@stripe/react-stripe-js'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
import { PAGES } from 'config/pages'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { useCardsStore } from 'features/CardsStore' import { useCardsStore } from 'features/CardsStore'
@ -18,6 +20,8 @@ import { useFormSubmit, ElementTypes } from './hooks'
import { import {
Form, Form,
Column, Column,
CountryWrapper,
CustomCombobox,
ButtonsBlock, ButtonsBlock,
Input, Input,
Errors, Errors,
@ -55,16 +59,26 @@ export const AddCardFormInner = (props: Props) => {
inputsBackground, inputsBackground,
} = props } = props
const { error: cardError } = useCardsStore() const { error: cardError } = useCardsStore()
const isUserAccountPage = useRouteMatch(PAGES.useraccount)?.path === '/useraccount'
const { const {
address,
city,
countries,
errorMessage, errorMessage,
handleSubmit, handleSubmit,
isLabelVisible, isLabelVisible,
loader, loader,
name, name,
onAddressChange,
onCityChange,
onCountryChange,
onInputsBlur, onInputsBlur,
onInputsChange, onInputsChange,
onInputsFocus, onInputsFocus,
onNameChange, onNameChange,
selectedCountry,
} = useFormSubmit(props) } = useFormSubmit(props)
return ( return (
@ -129,7 +143,51 @@ export const AddCardFormInner = (props: Props) => {
/> />
</ElementContainer> </ElementContainer>
</Column> </Column>
<ButtonsBlock> <SectionTitle>
<T9n t='billing_address' />
</SectionTitle>
<Column>
<CountryWrapper>
<CustomCombobox
value={selectedCountry?.name || ''}
labelLexic={selectedCountry?.name ? '' : 'country'}
onSelect={onCountryChange}
onBlur={onInputsBlur(ElementTypes.CardCountry)}
options={countries}
withError={false}
selected={Boolean(selectedCountry?.name)}
noSearch
/>
</CountryWrapper>
<ElementContainer
label={isLabelVisible(ElementTypes.CardCity) ? 'city' : ''}
width='275px'
backgroundColor={inputsBackground}
>
<Input
type='text'
autoComplete='address-level2'
value={city}
onChange={onCityChange}
onFocus={onInputsFocus(ElementTypes.CardCity)}
onBlur={onInputsBlur(ElementTypes.CardCity)}
/>
</ElementContainer>
<ElementContainer
label={isLabelVisible(ElementTypes.CardAddress) ? 'address' : ''}
backgroundColor={inputsBackground}
>
<Input
type='text'
autoComplete='street-address'
value={address}
onChange={onAddressChange}
onFocus={onInputsFocus(ElementTypes.CardAddress)}
onBlur={onInputsBlur(ElementTypes.CardAddress)}
/>
</ElementContainer>
</Column>
<ButtonsBlock isUserAccountPage={isUserAccountPage}>
{(errorMessage || cardError) && ( {(errorMessage || cardError) && (
<Errors> <Errors>
{errorMessage} {errorMessage}

@ -1,5 +1,16 @@
import styled, { css } from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
import { Combobox } from 'features/Combobox'
import { PopOver } from 'features/Combobox/styled'
import {
InputStyled,
InputWrapper,
LabelTitle,
} from 'features/Common/Input/styled'
type ButtonsBlockTypes = {
isUserAccountPage?: boolean,
}
export const Form = styled.form`` export const Form = styled.form``
@ -8,13 +19,14 @@ export const Column = styled.div`
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 8px;
` `
export const ButtonsBlock = styled.div` export const ButtonsBlock = styled.div<ButtonsBlockTypes>`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: start; align-items: ${({ isUserAccountPage }) => (isUserAccountPage ? 'start' : 'center')};
margin-top: 25px; margin-top: 15px;
` `
export const Input = styled.input` export const Input = styled.input`
@ -80,3 +92,26 @@ export const SectionTitle = styled.span`
: ''}; : ''};
` `
export const CountryWrapper = styled.div`
width: 275px;
`
export const CustomCombobox = styled(Combobox)`
${InputWrapper}{
height: 50px;
margin-top: 0;
}
${LabelTitle}{
line-height:45px;
font-size: 16px;
}
${InputStyled}{
height: 50px;
margin-left: 0;
}
${PopOver}{
top: 55px;
max-height: 300px;
}
` as typeof Combobox

@ -28,6 +28,7 @@ import { Arrow } from './components/Arrow'
export const Combobox = <T extends Option>(props: Props<T>) => { export const Combobox = <T extends Option>(props: Props<T>) => {
const { const {
className,
disabled, disabled,
error, error,
label, label,
@ -58,12 +59,15 @@ export const Combobox = <T extends Option>(props: Props<T>) => {
const isUserAccountPage = useRouteMatch(PAGES.useraccount)?.isExact || false const isUserAccountPage = useRouteMatch(PAGES.useraccount)?.isExact || false
return ( return (
<Column isUserAccountPage={isUserAccountPage}> <Column isUserAccountPage={isUserAccountPage} className={className}>
<InputWrapper <InputWrapper
error={error} error={error}
> >
<Label> <Label>
<LabelTitle labelWidth={labelWidth} isUserAccountPage={isUserAccountPage}> <LabelTitle
labelWidth={labelWidth}
isUserAccountPage={isUserAccountPage}
>
{labelLexic ? <T9n t={labelLexic} /> : label} {labelLexic ? <T9n t={labelLexic} /> : label}
</LabelTitle> </LabelTitle>
<InputStyled <InputStyled
@ -88,9 +92,7 @@ export const Combobox = <T extends Option>(props: Props<T>) => {
/> />
{isOpen && !isEmpty(options) && ( {isOpen && !isEmpty(options) && (
<OutsideClick onClick={onOutsideClick}> <OutsideClick onClick={onOutsideClick}>
<PopOver <PopOver ref={popoverRef}>
ref={popoverRef}
>
{map(options, (option, i) => ( {map(options, (option, i) => (
<ListOption <ListOption
onClick={(e) => onOptionSelect(option.name, e)} onClick={(e) => onOptionSelect(option.name, e)}

@ -10,7 +10,6 @@ export const Label = styled.label`
export const PopOver = styled.ul` export const PopOver = styled.ul`
position: absolute; position: absolute;
max-height: 400px;
width: 100%; width: 100%;
top: 55px; top: 55px;
left: -1px; left: -1px;

@ -16,6 +16,7 @@ export type Props<T> = Pick<InputHTMLAttributes<HTMLInputElement>, (
| 'placeholder' | 'placeholder'
| 'onBlur' | 'onBlur'
)> & { )> & {
className?: string,
customListStyles?: CustomStyles, customListStyles?: CustomStyles,
error?: string | null, error?: string | null,
label?: string, label?: string,

@ -11,7 +11,6 @@ import { Logo } from 'features/Logo'
const HeaderStyled = styled.header` const HeaderStyled = styled.header`
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 65px; margin-bottom: 65px;
@ -31,13 +30,29 @@ const HeaderStyled = styled.header`
` `
: ''}; : ''};
` `
const CustomHeaderGroup = styled(HeaderGroup)`
width: 100%;
justify-content: space-between;
`
const HomeIcon = styled.div`
display: block;
width: 30px;
height:30px;
background-size: contain;
background-repeat: no-repeat;
background-image: url(/images/home.png);
`
export const Header = () => ( export const Header = () => (
<HeaderStyled> <HeaderStyled>
<HeaderGroup> <CustomHeaderGroup>
<Link to={PAGES.home}> <Link to={PAGES.home}>
<Logo /> <Logo />
</Link> </Link>
</HeaderGroup> <Link to={PAGES.home}>
<HomeIcon />
</Link>
</CustomHeaderGroup>
</HeaderStyled> </HeaderStyled>
) )

@ -1,70 +0,0 @@
import {
useEffect,
useState,
useMemo,
} from 'react'
import orderBy from 'lodash/orderBy'
import find from 'lodash/find'
import map from 'lodash/map'
import type { Countries, Country } from 'requests'
import { getCountries } from 'requests'
import { formIds } from 'config/form'
import { useLexicsStore } from 'features/LexicsStore'
import { useForm } from 'features/FormStore'
export type SelectedCountry = (Country & {
name: string,
}) | null
type Names = 'name_eng' | 'name_rus'
const useCountriesList = () => {
const [countries, setCountries] = useState<Countries>([])
useEffect(() => {
getCountries().then(setCountries)
}, [])
return countries
}
export const useCountries = () => {
const { readFormValue, updateFormValue } = useForm()
const countries = useCountriesList()
const { suffix } = useLexicsStore()
const [selectedCountry, setSelectedCountry] = useState<SelectedCountry>(null)
const transformedCountries = useMemo(
() => orderBy(
map(countries, (country) => {
const nameField = `name_${suffix}` as Names
return {
...country,
name: country[nameField],
}
}),
({ name }) => name,
),
[countries, suffix],
)
const onCountryBlur = () => {
updateFormValue(formIds.country)('')
}
const countryId = readFormValue(formIds.initialCountryId)
useEffect(() => {
const initialCountry = find(transformedCountries, { id: Number(countryId) })
setSelectedCountry(initialCountry || null)
}, [transformedCountries, countryId])
return {
countries: transformedCountries,
onCountryBlur,
selectedCountry,
setSelectedCountry,
}
}

@ -26,13 +26,7 @@ export const useUserInfo = ({ loader, onSubmit }: Props) => {
updateFormValue, updateFormValue,
} = useForm() } = useForm()
const validateForm = useValidateForm() const validateForm = useValidateForm()
const { const { onPhoneBlur } = useUserInfoForm()
countries,
onCountryBlur,
onCountrySelect,
onPhoneBlur,
selectedCountry,
} = useUserInfoForm()
const readTrimmedValue = useCallback( const readTrimmedValue = useCallback(
(fieldName: string) => trim(readFormValue(fieldName)) || null, (fieldName: string) => trim(readFormValue(fieldName)) || null,
[readFormValue], [readFormValue],
@ -92,18 +86,14 @@ export const useUserInfo = ({ loader, onSubmit }: Props) => {
} }
return { return {
countries,
handleSubmit, handleSubmit,
hasChanges, hasChanges,
lang: selectedlangOption, lang: selectedlangOption,
loader, loader,
onCountryBlur,
onCountrySelect,
onLangSelect, onLangSelect,
onPhoneBlur, onPhoneBlur,
readFormError, readFormError,
readFormValue, readFormValue,
selectedCountry,
updateFormValue, updateFormValue,
} }
} }

@ -1,18 +1,12 @@
import type { ChangeEvent } from 'react' import type { ChangeEvent } from 'react'
import { useCallback } from 'react' import { useCallback } from 'react'
import trim from 'lodash/trim'
import { isValidPhone } from 'helpers/isValidPhone' import { isValidPhone } from 'helpers/isValidPhone'
import { formatPhoneCode } from 'helpers/formatPhoneCode'
import { formIds } from 'config/form' import { formIds } from 'config/form'
import { useForm } from 'features/FormStore' import { useForm } from 'features/FormStore'
import type { SelectedCountry } from './useCountries'
import { useCountries } from './useCountries'
export const useUserInfoForm = () => { export const useUserInfoForm = () => {
const { const {
readFormValue, readFormValue,
@ -20,13 +14,6 @@ export const useUserInfoForm = () => {
updateFormValue, updateFormValue,
} = useForm() } = useForm()
const {
countries,
onCountryBlur,
selectedCountry,
setSelectedCountry,
} = useCountries()
const onPhoneBlur = useCallback(({ target }: ChangeEvent<HTMLInputElement>) => { const onPhoneBlur = useCallback(({ target }: ChangeEvent<HTMLInputElement>) => {
const phone = target.value const phone = target.value
if (phone && !isValidPhone(phone)) { if (phone && !isValidPhone(phone)) {
@ -34,39 +21,9 @@ export const useUserInfoForm = () => {
} }
}, [updateFormError]) }, [updateFormError])
const phone = trim(readFormValue(formIds.phone))
const onCountrySelect = useCallback((country: SelectedCountry) => {
if (country?.id === selectedCountry?.id) return
setSelectedCountry(country)
updateFormValue(formIds.countryId)(country?.id ? String(country?.id) : '')
updateFormValue(formIds.region)('')
const selectedCountryCode = formatPhoneCode(selectedCountry?.phone_code || '')
const hasPhoneNumber = (
phone
&& selectedCountryCode
&& phone !== selectedCountryCode
)
if (!hasPhoneNumber) {
const code = formatPhoneCode(country?.phone_code || '')
updateFormValue(formIds.phone)(code)
}
}, [
phone,
selectedCountry,
setSelectedCountry,
updateFormValue,
])
return { return {
countries,
onCountryBlur,
onCountrySelect,
onPhoneBlur, onPhoneBlur,
readFormValue, readFormValue,
selectedCountry,
setSelectedCountry,
updateFormValue, updateFormValue,
} }
} }

@ -21,7 +21,6 @@ import {
const labelWidth = 76 const labelWidth = 76
const { const {
country,
email, email,
firstname, firstname,
formError, formError,
@ -31,18 +30,14 @@ const {
export const PersonalInfoForm = (props: Props) => { export const PersonalInfoForm = (props: Props) => {
const { const {
countries,
handleSubmit, handleSubmit,
hasChanges, hasChanges,
lang, lang,
loader, loader,
onCountryBlur,
onCountrySelect,
onLangSelect, onLangSelect,
onPhoneBlur, onPhoneBlur,
readFormError, readFormError,
readFormValue, readFormValue,
selectedCountry,
updateFormValue, updateFormValue,
} = useUserInfo(props) } = useUserInfo(props)
@ -91,17 +86,6 @@ export const PersonalInfoForm = (props: Props) => {
withError={false} withError={false}
disabled disabled
/> />
<Combobox
value={selectedCountry?.name ?? readFormValue(country)}
labelLexic='form_country'
labelWidth={labelWidth}
onChange={updateFormValue(country)}
onSelect={onCountrySelect}
onBlur={onCountryBlur}
options={countries}
withError={false}
selected={Boolean(selectedCountry)}
/>
<Combobox <Combobox
noSearch noSearch
selected selected

Loading…
Cancel
Save