-
Notifications
You must be signed in to change notification settings - Fork 354
Arshad/ COJ-481 / Address Fields #13074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
likhith-deriv
merged 16 commits into
deriv-com:master
from
arshad-rao-deriv:Arshad/COJ-481/AddressDetailsFields
Jan 30, 2024
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
a19445c
feat: added formInputfield to accounts v2
arshad-rao-deriv cc9703b
feat: Added formdropdownfield to accounts v2
arshad-rao-deriv f45c183
feat: added address field validations
arshad-rao-deriv 9aa50b4
feat: Added address fields in account v2 package
arshad-rao-deriv f0a3e1f
fix: Make the options parameter optional in useStatesList
arshad-rao-deriv 2fd8524
refactor: Append tests, refactor constants
arshad-rao-deriv 7f4708d
fix: fix regex character duplication
arshad-rao-deriv 86edb3d
chore: update deriv-com/ui package version
arshad-rao-deriv 28a9a0f
chore: change deriv ui package version
arshad-rao-deriv 2896b6d
Merge branch 'master' into arshad/COJ-481/AddressDetailsFields
arshad-rao-deriv 7917fd6
Merge branch 'master' into arshad/COJ-481/AddressDetailsFields
arshad-rao-deriv 531bdbf
fix: fix regex for postcode
arshad-rao-deriv d96bb80
Merge branch 'master' into arshad/COJ-481/AddressDetailsFields
arshad-rao-deriv b21b2b7
Merge branch 'master' into arshad/COJ-481/AddressDetailsFields
arshad-rao-deriv b1d36a2
Merge branch 'master' into Arshad/COJ-481/AddressDetailsFields
arshad-rao-deriv 692abd7
refactor: export modules and useKycAuthStatus hook
arshad-rao-deriv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
51 changes: 51 additions & 0 deletions
51
packages/account-v2/src/components/FormFields/FormDropDownField.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React, { ComponentProps } from 'react'; | ||
import { Field, FieldProps } from 'formik'; | ||
import * as Yup from 'yup'; | ||
import { useBreakpoint } from '@deriv/quill-design'; | ||
import { WalletDropdown as DropDown } from '../base/WalletDropdown'; | ||
|
||
type FormDropDownFieldProps = Omit< | ||
ComponentProps<typeof DropDown>, | ||
'errorMessage' | 'isRequired' | 'onSelect' | 'variant' | ||
> & { | ||
name: string; | ||
validationSchema?: Yup.AnySchema; | ||
}; | ||
|
||
/** | ||
* FormDropDownField is a wrapper around Dropdown that can be used with Formik. | ||
* @name FormDropDownField | ||
* @param name - Name of the field | ||
* @param [props] - Other props to pass to Input | ||
* @returns ReactNode | ||
*/ | ||
const FormDropDownField = ({ name, validationSchema, ...rest }: FormDropDownFieldProps) => { | ||
const { isMobile } = useBreakpoint(); | ||
|
||
const validateField = (value: unknown) => { | ||
try { | ||
if (validationSchema) { | ||
validationSchema.validateSync(value); | ||
} | ||
} catch (err: unknown) { | ||
return (err as Yup.ValidationError).message; | ||
} | ||
}; | ||
|
||
return ( | ||
<Field name={name} validate={validateField}> | ||
{({ field, form, meta: { error, touched } }: FieldProps<string>) => ( | ||
<DropDown | ||
{...field} | ||
{...rest} | ||
errorMessage={error} | ||
isRequired={touched && !!error} | ||
onSelect={(value: string) => form.setFieldValue(name, value)} | ||
variant={isMobile ? 'prompt' : 'comboBox'} | ||
/> | ||
)} | ||
</Field> | ||
); | ||
}; | ||
|
||
export default FormDropDownField; |
47 changes: 47 additions & 0 deletions
47
packages/account-v2/src/components/FormFields/FormInputField.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import React, { ComponentProps } from 'react'; | ||
import { Field, FieldProps } from 'formik'; | ||
import * as Yup from 'yup'; | ||
import { WalletTextField as TextField } from '../base/WalletTextField'; | ||
|
||
type FormInputFieldProps = Omit<ComponentProps<typeof TextField>, 'errorMessage' | 'isInvalid' | 'showMessage'> & { | ||
name: string; | ||
validationSchema?: Yup.AnySchema; | ||
}; | ||
|
||
/** | ||
* FormInputField is a wrapper around Input that can be used with Formik. | ||
* @name FormInputField | ||
* @param name - Name of the field | ||
* @param [validationSchema] - Yup validation schema to use for the field | ||
* @param [props] - Other props to pass to Input | ||
* @returns ReactNode | ||
*/ | ||
const FormInputField = ({ name, validationSchema, ...rest }: FormInputFieldProps) => { | ||
const validateField = (value: unknown) => { | ||
try { | ||
if (validationSchema) { | ||
validationSchema.validateSync(value); | ||
} | ||
} catch (err: unknown) { | ||
return (err as Yup.ValidationError).message; | ||
} | ||
}; | ||
|
||
return ( | ||
<Field name={name} validate={validateField}> | ||
{({ field, meta: { error, touched } }: FieldProps<string>) => ( | ||
<TextField | ||
{...field} | ||
{...rest} | ||
autoComplete='off' | ||
errorMessage={touched && error} | ||
isInvalid={touched && !!error} | ||
showMessage | ||
type='text' | ||
/> | ||
)} | ||
</Field> | ||
); | ||
}; | ||
|
||
export default FormInputField; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export const LANDING_COMPANY = { | ||
BVI: 'bvi', | ||
LABUAN: 'labuan', | ||
MALTAINVEST: 'maltainvest', | ||
SVG: 'svg', | ||
VANUATU: 'vanuatu', | ||
} as const; |
72 changes: 72 additions & 0 deletions
72
packages/account-v2/src/modules/AddressFields/AddressFields.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import React from 'react'; | ||
import { useAuthorize, useSettings, useStatesList } from '@deriv/api'; | ||
import FormDropDownField from '../../components/FormFields/FormDropDownField'; | ||
import FormInputField from '../../components/FormFields/FormInputField'; | ||
import { LANDING_COMPANY } from '../../constants/constants'; | ||
import { addressDetailValidations } from './validations'; | ||
|
||
export const AddressFields = () => { | ||
const { data: activeAccount } = useAuthorize(); | ||
const { data: settings } = useSettings(); | ||
|
||
const { landing_company_name: landingCompanyName, upgradeable_landing_companies: upgradableLandingCompanies } = | ||
activeAccount; | ||
|
||
const isSvg = | ||
landingCompanyName === LANDING_COMPANY.SVG || !!upgradableLandingCompanies?.includes(LANDING_COMPANY.SVG); | ||
const { data: statesList, isFetched: statesListFetched } = useStatesList(settings.country_code || '', { | ||
enabled: !!settings.country_code, | ||
}); | ||
|
||
const { | ||
addressCity: addressCitySchema, | ||
addressLine1: addressLine1Schema, | ||
addressLine2: addressLine2Schema, | ||
addressPostcode: addressPostcodeSchema, | ||
addressState: addressStateSchema, | ||
} = addressDetailValidations(settings.country_code ?? '', isSvg); | ||
|
||
return ( | ||
arshad-rao-deriv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<div className='space-y-600'> | ||
<FormInputField | ||
label='First line of address*' | ||
name='addressLine1' | ||
placeholder='First line of address' | ||
validationSchema={addressLine1Schema} | ||
/> | ||
<FormInputField | ||
label='Second line of address' | ||
name='addressLine2' | ||
placeholder='Second line of address' | ||
validationSchema={addressLine2Schema} | ||
/> | ||
<FormInputField | ||
label='Town/City*' | ||
name='addressCity' | ||
placeholder='Town/City' | ||
validationSchema={addressCitySchema} | ||
/> | ||
{statesListFetched && statesList.length ? ( | ||
<FormDropDownField | ||
label='State/Province' | ||
list={statesList} | ||
name='addressState' | ||
validationSchema={addressStateSchema} | ||
/> | ||
) : ( | ||
<FormInputField | ||
label='State/Province' | ||
name='addressState' | ||
placeholder='State/Province' | ||
validationSchema={addressStateSchema} | ||
/> | ||
)} | ||
<FormInputField | ||
label='Postal/ZIP Code' | ||
name='addressPostcode' | ||
placeholder='Postal/ZIP Code' | ||
validationSchema={addressPostcodeSchema} | ||
/> | ||
</div> | ||
); | ||
}; |
88 changes: 88 additions & 0 deletions
88
packages/account-v2/src/modules/AddressFields/__tests__/validations.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { addressDetailValidations as validations, addressPermittedSpecialCharactersMessage } from '../validations'; | ||
|
||
describe('validations', () => { | ||
const maxCharsMessage = 'Only 70 characters, please.'; | ||
it('validates addressLine1 correctly without svg flag', async () => { | ||
const { addressLine1 } = validations('id', false); | ||
|
||
await expect(addressLine1.validate('123 Main St')).resolves.toBe('123 Main St'); | ||
await expect(addressLine1.validate('')).rejects.toThrow('First line of address is required'); | ||
await expect(addressLine1.validate('a$^&')).rejects.toThrow( | ||
`Use only the following special characters: ${addressPermittedSpecialCharactersMessage}` | ||
); | ||
await expect(addressLine1.validate('a'.repeat(71))).rejects.toThrow(maxCharsMessage); | ||
await expect(addressLine1.validate('P.O. Box 121')).resolves.toBe('P.O. Box 121'); | ||
}); | ||
|
||
it('validates addressLine1 correctly with svg flag', async () => { | ||
const { addressLine1 } = validations('id', true); | ||
|
||
await expect(addressLine1.validate('123 Main St1')).resolves.toBe('123 Main St1'); | ||
await expect(addressLine1.validate('')).rejects.toThrow('First line of address is required'); | ||
await expect(addressLine1.validate('a'.repeat(71))).rejects.toThrow(maxCharsMessage); | ||
await expect(addressLine1.validate('P.O. Box 123')).rejects.toThrow('P.O. Box is not accepted in address'); | ||
}); | ||
|
||
it('validates addressLine2 correctly', async () => { | ||
const { addressLine2 } = validations('id', false); | ||
|
||
await expect(addressLine2.validate('Apt 4B')).resolves.toBe('Apt 4B'); | ||
await expect(addressLine2.validate('a$^&')).rejects.toThrow( | ||
`Use only the following special characters: ${addressPermittedSpecialCharactersMessage}` | ||
); | ||
await expect(addressLine2.validate('a'.repeat(71))).rejects.toThrow(maxCharsMessage); | ||
await expect(addressLine2.validate('P.O. Box 120')).resolves.toBe('P.O. Box 120'); | ||
}); | ||
|
||
it('validates addressLine2 correctly with svg flag', async () => { | ||
const { addressLine2 } = validations('id', true); | ||
|
||
await expect(addressLine2.validate('Apt 4B')).resolves.toBe('Apt 4B'); | ||
await expect(addressLine2.validate('a'.repeat(71))).rejects.toThrow(maxCharsMessage); | ||
await expect(addressLine2.validate('P.O. Box 122')).rejects.toThrow('P.O. Box is not accepted in address'); | ||
}); | ||
|
||
it('validates addressPostcode correctly with country gb', async () => { | ||
const { addressPostcode } = validations('gb', false); | ||
|
||
await expect(addressPostcode.validate('12345')).resolves.toBe('12345'); | ||
await expect(addressPostcode.validate('$%&')).rejects.toThrow('Letters, numbers, spaces, hyphens only'); | ||
await expect(addressPostcode.validate('a'.repeat(21))).rejects.toThrow( | ||
'Please enter a postal/ZIP code under 20 characters.' | ||
); | ||
await expect(addressPostcode.validate('JE1 1AA')).rejects.toThrow( | ||
'Our accounts and services are unavailable for the Jersey postal code.' | ||
); | ||
}); | ||
|
||
it('validates addressPostcode correctly with country id', async () => { | ||
const { addressPostcode } = validations('id', false); | ||
|
||
await expect(addressPostcode.validate('12345')).resolves.toBe('12345'); | ||
await expect(addressPostcode.validate('$%&')).rejects.toThrow('Letters, numbers, spaces, hyphens only'); | ||
await expect(addressPostcode.validate('a'.repeat(21))).rejects.toThrow( | ||
'Please enter a postal/ZIP code under 20 characters.' | ||
); | ||
await expect(addressPostcode.validate('JE1 1AA')).resolves.toBe('JE1 1AA'); | ||
}); | ||
|
||
it('validates addressState correctly', async () => { | ||
const { addressState } = validations('id', false); | ||
|
||
await expect(addressState.validate('NY')).resolves.toBe('NY'); | ||
await expect(addressState.validate('')).rejects.toThrow('State is required'); | ||
await expect(addressState.validate('a'.repeat(100))).rejects.toThrow('State is not in a proper format'); | ||
await expect(addressState.validate('%_ASD')).rejects.toThrow('State is not in a proper format'); | ||
}); | ||
|
||
it('validates addressCity correctly', async () => { | ||
const { addressCity } = validations('id', false); | ||
|
||
await expect(addressCity.validate('New York')).resolves.toBe('New York'); | ||
await expect(addressCity.validate('')).rejects.toThrow('City is required'); | ||
await expect(addressCity.validate('a'.repeat(100))).rejects.toThrow('Only 99 characters, please.'); | ||
await expect(addressCity.validate('%_ASD')).rejects.toThrow( | ||
'Only letters, periods, hyphens, apostrophes, and spaces, please.' | ||
); | ||
}); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { AddressFields } from './AddressFields'; |
66 changes: 66 additions & 0 deletions
66
packages/account-v2/src/modules/AddressFields/validations.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import * as Yup from 'yup'; | ||
|
||
const regexChecks = { | ||
address_details: { | ||
address_city: /^\p{L}[\p{L}\s'.-]{0,99}$/u, | ||
address_line_1: /^[\p{L}\p{Nd}\s'.,:;()\u00b0@#/-]{1,70}$/u, | ||
address_line_2: /^[\p{L}\p{Nd}\s'.,:;()\u00b0@#/-]{0,70}$/u, | ||
address_postcode: /^[a-zA-Z0-9\s-]{0,20}$/, | ||
address_state: /^[\w\s'.;,-]{0,99}$/, | ||
non_jersey_postcode: /^(?!\s*je.*)[a-z0-9\s-]*/i, | ||
}, | ||
}; | ||
|
||
export const addressPermittedSpecialCharactersMessage = ". , ' : ; ( ) ° @ # / -"; | ||
|
||
export const addressDetailValidations = (countryCode: string, isSvg: boolean) => ({ | ||
arshad-rao-deriv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
addressCity: Yup.string() | ||
.required('City is required') | ||
.max(99, 'Only 99 characters, please.') | ||
.matches( | ||
regexChecks.address_details.address_city, | ||
'Only letters, periods, hyphens, apostrophes, and spaces, please.' | ||
arshad-rao-deriv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
), | ||
addressLine1: Yup.string() | ||
.required('First line of address is required') | ||
.max(70, 'Only 70 characters, please.') | ||
.matches( | ||
regexChecks.address_details.address_line_1, | ||
`Use only the following special characters: ${addressPermittedSpecialCharactersMessage}` | ||
) | ||
.when({ | ||
is: () => isSvg, | ||
then: Yup.string().test( | ||
'po_box', | ||
'P.O. Box is not accepted in address', | ||
value => !/p[.\s]+o[.\s]+box/i.test(value ?? '') | ||
), | ||
}), | ||
addressLine2: Yup.string() | ||
.max(70, 'Only 70 characters, please.') | ||
.matches( | ||
regexChecks.address_details.address_line_2, | ||
`Use only the following special characters: ${addressPermittedSpecialCharactersMessage}` | ||
) | ||
.when({ | ||
is: () => isSvg, | ||
then: Yup.string().test( | ||
'po_box', | ||
'P.O. Box is not accepted in address', | ||
value => !/p[.\s]+o[.\s]+box/i.test(value ?? '') | ||
), | ||
}), | ||
addressPostcode: Yup.string() | ||
.max(20, 'Please enter a postal/ZIP code under 20 characters.') | ||
.matches(regexChecks.address_details.address_postcode, 'Letters, numbers, spaces, hyphens only') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we dont have localize in v2 packages for now @suisin-deriv |
||
.when({ | ||
is: () => countryCode === 'gb', | ||
then: Yup.string().matches( | ||
regexChecks.address_details.non_jersey_postcode, | ||
'Our accounts and services are unavailable for the Jersey postal code.' | ||
), | ||
}), | ||
addressState: Yup.string() | ||
.required('State is required') | ||
.matches(regexChecks.address_details.address_state, 'State is not in a proper format'), | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { AddressFields } from './AddressFields'; | ||
export { IDVForm } from './IDVForm'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import useQuery from '../../useQuery'; | ||
import useStatesList from '../useStatesList'; | ||
|
||
jest.mock('../../useQuery'); | ||
|
||
const mockUseQuery = useQuery as jest.MockedFunction<typeof useQuery<'states_list'>>; | ||
|
||
describe('useStatesList', () => { | ||
it('should return an empty array when the store is not ready', () => { | ||
// @ts-expect-error need to come up with a way to mock the return type of useFetch | ||
mockUseQuery.mockReturnValue({ | ||
data: { | ||
states_list: [], | ||
}, | ||
}); | ||
const { result } = renderHook(() => useStatesList('in')); | ||
|
||
expect(result.current.data).toHaveLength(0); | ||
}); | ||
|
||
it('should return data fetched along with correct status', () => { | ||
// @ts-expect-error need to come up with a way to mock the return type of useFetch | ||
mockUseQuery.mockReturnValue({ | ||
data: { | ||
states_list: [ | ||
{ text: 'state 1', value: 's1' }, | ||
{ text: 'state 2', value: 's2' }, | ||
], | ||
}, | ||
isFetched: true, | ||
}); | ||
const { result } = renderHook(() => useStatesList('in')); | ||
expect(result.current.isFetched).toBeTruthy(); | ||
}); | ||
|
||
it('should call the useQuery with options if passed', () => { | ||
renderHook(() => useStatesList('in', { enabled: false })); | ||
expect(mockUseQuery).toHaveBeenCalledWith('states_list', { | ||
payload: { states_list: 'in' }, | ||
options: { enabled: false }, | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.