diff --git a/packages/account/src/Assets/ic-poi-dob-example.svg b/packages/account/src/Assets/ic-poi-dob-example.svg deleted file mode 100644 index 7ad66a00fa5b..000000000000 --- a/packages/account/src/Assets/ic-poi-dob-example.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/account/src/Assets/ic-poi-name-example.svg b/packages/account/src/Assets/ic-poi-name-example.svg deleted file mode 100644 index eaaa9453759c..000000000000 --- a/packages/account/src/Assets/ic-poi-name-example.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/account/src/Components/form-body/form-body.tsx b/packages/account/src/Components/form-body/form-body.tsx index 1bff72577005..6806f15b33d8 100644 --- a/packages/account/src/Components/form-body/form-body.tsx +++ b/packages/account/src/Components/form-body/form-body.tsx @@ -1,26 +1,21 @@ import React from 'react'; import { ScrollbarsContainer } from '../scrollbars-container/scrollbars-container'; import { Div100vhContainer, DesktopWrapper, MobileWrapper } from '@deriv/components'; -import classNames from 'classnames'; type TFormBody = { scroll_offset?: string; - className?: string; }; -export const FormBody = ({ children, scroll_offset, className }: React.PropsWithChildren) => ( +export const FormBody = ({ children, scroll_offset }: React.PropsWithChildren) => ( - + {children} {children} diff --git a/packages/account/src/Components/forms/__tests__/idv-form.spec.tsx b/packages/account/src/Components/forms/__tests__/idv-form.spec.tsx deleted file mode 100644 index 82ba1e082c7d..000000000000 --- a/packages/account/src/Components/forms/__tests__/idv-form.spec.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import IDVForm from '../idv-form'; -import { Formik } from 'formik'; - -jest.mock('Helpers/utils', () => ({ - ...jest.requireActual('Helpers/utils'), - getDocumentData: jest.fn((country_code, key) => { - const data = { - tc: { - document_1: { - new_display_name: '', - example_format: '5436454364243', - sample_image: '', - }, - document_2: { - new_display_name: '', - example_format: 'A-52431', - sample_image: '', - }, - }, - }; - return data[country_code][key]; - }), -})); - -jest.mock('formik', () => ({ - ...jest.requireActual('formik'), - useFormikContext: jest.fn(() => ({ - values: { - document_type: {}, - document_number: '', - }, - errors: {}, - touched: {}, - setFieldValue: jest.fn(), - setFieldTouched: jest.fn(), - validateForm: jest.fn(), - validateField: jest.fn(), - handleBlur: jest.fn(), - handleChange: jest.fn(), - })), -})); - -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - isDesktop: jest.fn(() => true), - isMobile: jest.fn(() => false), -})); - -describe('', () => { - const mock_props = { - selected_country: { - value: 'tc', - identity: { - services: { - idv: { - documents_supported: { - document_1: { - display_name: 'Test document 1 name', - format: '5436454364243', - }, - document_2: { - display_name: 'Test document 2 name', - format: 'A54321', - }, - }, - has_visual_sample: 1, - }, - }, - }, - }, - }; - - const mock_values = { - document_type: { - display_name: 'Test document 1 name', - format: '5436454364243', - id: '1', - value: 'document_1', - }, - document_number: '5436454364243', - }; - - it('should render IDVForm component', () => { - render(, { - wrapper: ({ children }) => ( - - {() => children} - - ), - }); - - const document_type_input = screen.getByLabelText('Choose the document type'); - const document_number_input = screen.getByLabelText('Enter your document number'); - - expect(document_type_input).toBeInTheDocument(); - expect(document_number_input).toBeInTheDocument(); - }); - - it('Should change the document type value when document type is changed', async () => { - render(, { - wrapper: ({ children }) => ( - - {() => children} - - ), - }); - - const document_type_input = screen.getByLabelText('Choose the document type'); - - userEvent.click(document_type_input); - expect(await screen.findByText('Test document 1 name')).toBeInTheDocument(); - userEvent.tab(); - await waitFor(() => { - expect(screen.queryByText('Test document 1 name')).not.toBeInTheDocument(); - }); - }); -}); diff --git a/packages/account/src/Components/forms/idv-form.tsx b/packages/account/src/Components/forms/idv-form.tsx index 6c9566928cb6..f81b083fb5a0 100644 --- a/packages/account/src/Components/forms/idv-form.tsx +++ b/packages/account/src/Components/forms/idv-form.tsx @@ -1,39 +1,35 @@ import React from 'react'; import classNames from 'classnames'; -import { Field, FormikProps, FieldProps, useFormikContext } from 'formik'; -import { ResidenceList } from '@deriv/api-types'; -import { Autocomplete, DesktopWrapper, Input, MobileWrapper, SelectNative, Text } from '@deriv/components'; -import { formatInput, getIDVNotApplicableOption } from '@deriv/shared'; +import { Field, FieldProps } from 'formik'; import { localize } from '@deriv/translations'; +import { formatInput, getIDVNotApplicableOption } from '@deriv/shared'; +import { Autocomplete, DesktopWrapper, Input, MobileWrapper, SelectNative, Text } from '@deriv/components'; import { getDocumentData, preventEmptyClipboardPaste, generatePlaceholderText, getExampleFormat, } from '../../Helpers/utils'; -import { TDocument, TIDVFormValues } from '../../Types'; - -type TIDVFormProps = { - selected_country: ResidenceList[0]; - hide_hint?: boolean; - class_name?: string; - can_skip_document_verification?: boolean; -}; +import { TDocument, TIDVForm } from 'Types'; const IDVForm = ({ + errors, + touched, + values, + handleBlur, + handleChange, + setFieldValue, class_name, selected_country, hide_hint, can_skip_document_verification = false, -}: TIDVFormProps) => { - const [document_list, setDocumentList] = React.useState>([]); +}: TIDVForm) => { + const [document_list, setDocumentList] = React.useState([]); const [document_image, setDocumentImage] = React.useState(null); const [selected_doc, setSelectedDoc] = React.useState(''); const { documents_supported: document_data, has_visual_sample } = selected_country?.identity?.services?.idv ?? {}; - const { errors, touched, values, handleBlur, handleChange, setFieldValue }: FormikProps = - useFormikContext(); const default_document = { id: '', text: '', @@ -97,12 +93,10 @@ const IDVForm = ({ }; const onKeyUp = (e: { target: HTMLInputElement }, document_name: string) => { - const example_format = - document_name === 'document_number' - ? values?.document_type?.example_format - : values?.document_type?.additional?.example_format; + const { example_format } = + document_name === 'document_number' ? values.document_type : values.document_type.additional; let current_input: string | null = null; - current_input = example_format?.includes('-') + current_input = example_format.includes('-') ? formatInput(example_format, current_input ?? e.target.value, '-') : e.target.value; setFieldValue(document_name, current_input, true); @@ -141,32 +135,38 @@ const IDVForm = ({ {({ field }: FieldProps) => ( - { - handleBlur(e); - if (!getDocument(e.target.value)) { - resetDocumentItemSelected(); - } - }} - onChange={handleChange} - onItemSelection={(item: TDocument) => { - if (item.text === 'No results found' || !item.text) { - setSelectedDoc(''); - resetDocumentItemSelected(); - } else { - bindDocumentData(item); - } - }} - required - /> +
+ { + handleBlur(e); + if (!getDocument(e.target.value)) { + resetDocumentItemSelected(); + } + }} + onChange={handleChange} + onItemSelection={(item: TDocument) => { + if ( + item.text === 'No results found' || + !item.text + ) { + setSelectedDoc(''); + resetDocumentItemSelected(); + } else { + bindDocumentData(item); + } + }} + required + /> +
- {values?.document_type?.id !== IDV_NOT_APPLICABLE_OPTION.id && ( + {values.document_type.id !== IDV_NOT_APPLICABLE_OPTION.id && (
{ const { - inline_note_text, is_virtual, is_mf, is_svg, - is_rendered_for_idv, + is_qualified_for_idv, should_hide_helper_image, editable_fields = [], has_real_account, @@ -46,10 +47,10 @@ const PersonalDetailsForm = props => { setShouldCloseTooltip, class_name, states_list, - side_note, no_confirmation_needed, } = props; const autocomplete_value = 'none'; + const PoiNameDobExampleIcon = PoiNameDobExample; const [is_tax_residence_popover_open, setIsTaxResidencePopoverOpen] = React.useState(false); const [is_tin_popover_open, setIsTinPopoverOpen] = React.useState(false); @@ -71,7 +72,7 @@ const PersonalDetailsForm = props => { }, [no_confirmation_needed, setStatus, status]); const getNameAndDobLabels = () => { - const is_asterisk_needed = is_svg || is_mf || is_rendered_for_onfido || is_rendered_for_idv; + const is_asterisk_needed = is_svg || is_mf || is_rendered_for_onfido || is_qualified_for_idv; const first_name_label = is_asterisk_needed ? localize('First name*') : localize('First name'); const last_name_label = is_asterisk_needed ? localize('Last name*') : localize('Last name'); const dob_label = is_asterisk_needed ? localize('Date of birth*') : localize('Date of birth'); @@ -83,10 +84,8 @@ const PersonalDetailsForm = props => { }; }; - const is_rendered_for_idv_or_onfido = is_rendered_for_idv || is_rendered_for_onfido; - const getFieldHint = field_name => - is_rendered_for_idv_or_onfido ? ( + is_qualified_for_idv || is_rendered_for_onfido ? ( { } }; + const name_dob_clarification_message = ( + ]} + /> + ); + const poa_clarification_message = ( ); @@ -129,11 +135,14 @@ const PersonalDetailsForm = props => {
- {is_rendered_for_idv_or_onfido && !should_hide_helper_image && ( - + {(is_qualified_for_idv || is_rendered_for_onfido) && !should_hide_helper_image && ( + )} {is_qualified_for_poa && ( { /> )} } side_note_position='right' type='image' > @@ -172,7 +181,7 @@ const PersonalDetailsForm = props => {
)} - {!is_rendered_for_idv_or_onfido && !is_qualified_for_poa && ( + {!is_qualified_for_idv && !is_rendered_for_onfido && !is_qualified_for_poa && ( @@ -230,7 +239,7 @@ const PersonalDetailsForm = props => { data-testid='last_name' /> )} - {!is_rendered_for_idv_or_onfido && !is_qualified_for_poa && ( + {!is_qualified_for_idv && !is_rendered_for_onfido && !is_qualified_for_poa && ( )} {'date_of_birth' in values && ( @@ -537,7 +546,7 @@ const PersonalDetailsForm = props => { )}
- {!no_confirmation_needed && is_rendered_for_idv && ( + {!no_confirmation_needed && is_qualified_for_idv && ( jest.fn(() => 'PoiNameDobExampleImage')); @@ -29,13 +28,6 @@ jest.mock('../../real-account-signup/helpers/utils.ts', () => ({ })), })); -jest.mock('Helpers/utils', () => ({ - ...jest.requireActual('Helpers/utils'), - isDocumentTypeValid: jest.fn(), - shouldShowIdentityInformation: jest.fn(() => false), - isAdditionalDocumentValid: jest.fn(), -})); - const mock_warnings = {}; const mock_errors = { account_opening_reason: 'Account opening reason is required.', @@ -114,43 +106,6 @@ const runCommonFormfieldsTests = is_svg => { }; describe('', () => { - const idv_document_data = { - document_type: { - value: 'national_id', - text: 'National ID', - }, - document_number: '1234567890123', - }; - - const default_IDV_config = { - documents_supported: {}, - has_visual_sample: 0, - is_country_supported: 0, - }; - - const default_residence_details = [ - { - value: 'tc', - identity: { - services: { - idv: { - documents_supported: { - document_1: { - display_name: 'Test document 1 name', - format: '5436454364243', - }, - document_2: { - display_name: 'Test document 2 name', - format: 'A54321', - }, - }, - has_visual_sample: true, - }, - }, - }, - }, - ]; - const props = { is_svg: true, is_high_risk: false, @@ -184,7 +139,11 @@ describe('', () => { { identity: { services: { - idv: default_IDV_config, + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, onfido: { documents_supported: { passport: { @@ -202,7 +161,11 @@ describe('', () => { { identity: { services: { - idv: default_IDV_config, + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, onfido: { documents_supported: {}, is_country_supported: 0, @@ -273,10 +236,6 @@ describe('', () => { afterAll(() => ReactDOM.createPortal.mockClear()); - afterEach(() => { - jest.clearAllMocks(); - }); - const renderwithRouter = component => { const mock_store = mockStore({}); render( @@ -286,135 +245,6 @@ describe('', () => { ); }; - it('should have validation errors on form fields', async () => { - isMobile.mockReturnValue(false); - isDesktop.mockReturnValue(true); - - renderwithRouter(); - - const first_name = screen.getByTestId('first_name'); - const last_name = screen.getByTestId('last_name'); - const date_of_birth = await screen.getByTestId('date_of_birth'); - const place_of_birth = screen.getByTestId('place_of_birth'); - const citizenship = screen.getByTestId('citizenship'); - const phone = screen.getByTestId('phone'); - const tax_residence = screen.getByTestId('tax_residence'); - const tax_identification_number = screen.getByTestId('tax_identification_number'); - - fireEvent.blur(first_name); - fireEvent.blur(last_name); - fireEvent.blur(date_of_birth); - fireEvent.blur(place_of_birth); - fireEvent.blur(citizenship); - fireEvent.blur(phone); - fireEvent.blur(tax_residence); - fireEvent.blur(tax_identification_number); - - expect(await screen.findByText(/first name is required\./i)).toBeInTheDocument(); - expect(await screen.findByText(/last name is required\./i)).toBeInTheDocument(); - expect(await screen.findByText(/date of birth is required\./i)).toBeInTheDocument(); - expect(await screen.findByText(/place of birth is required\./i)).toBeInTheDocument(); - expect(await screen.findByText(/citizenship is required/i)).toBeInTheDocument(); - expect(await screen.findByText(/phone is required\./i)).toBeInTheDocument(); - expect(await screen.findByText(/tax residence is required\./i)).toBeInTheDocument(); - expect(await screen.findByText(/tax identification number is required\./i)).toBeInTheDocument(); - splitValidationResultTypes.mockReturnValue({ - ...mock_warnings, - errors: { - ...mock_errors.errors, - first_name: 'letters, spaces, periods, hyphens, apostrophes only', - last_name: 'last name should be between 2 and 50 characters.', - date_of_birth: 'You must be 18 years old and above.', - tax_identification_number: "Tax Identification Number can't be longer than 25 characters.", - }, - }); - fireEvent.change(first_name, { target: { value: '123' } }); - fireEvent.change(last_name, { target: { value: 'a' } }); - fireEvent.change(date_of_birth, { target: { value: '2021-04-13' } }); - fireEvent.change(tax_identification_number, { target: { value: '123456789012345678901234567890' } }); - - expect(await screen.findByText(/letters, spaces, periods, hyphens, apostrophes only/i)).toBeInTheDocument(); - expect(await screen.findByText(/last name should be between 2 and 50 characters/i)).toBeInTheDocument(); - expect(await screen.findByText(/you must be 18 years old and above\./i)).toBeInTheDocument(); - expect( - await screen.findByText(/tax Identification Number can't be longer than 25 characters\./i) - ).toBeInTheDocument(); - }); - - it('submit button should be enabled if TIN or tax_residence is optional in case of CR accounts', () => { - const new_props = { - ...props, - is_mf: false, - is_svg: true, - value: { - first_name: '', - last_name: '', - date_of_birth: '', - place_of_birth: '', - phone: '+34', - tax_residence: '', - tax_identification_number: '', - document_type: 'none', - }, - }; - renderwithRouter(); - - const first_name = screen.getByTestId('first_name'); - const last_name = screen.getByTestId('last_name'); - const date_of_birth = screen.getByTestId('date_of_birth'); - const phone = screen.getByTestId('phone'); - - userEvent.type(first_name, 'test firstname'); - userEvent.type(last_name, 'test lastname'); - userEvent.type(date_of_birth, '2000-12-12'); - userEvent.type(phone, '+49123456789012'); - expect(screen.getByRole('button', { name: /next/i })).toBeEnabled(); - }); - - it('should not display confirmation checkbox if opt-out of IDV', async () => { - splitValidationResultTypes.mockReturnValue({ warnings: {}, errors: {} }); - const new_props = { - ...props, - value: { - first_name: '', - last_name: '', - date_of_birth: '', - phone: '+93', - account_opening_reason: '', - place_of_birth: '', - document_type: 'none', - }, - }; - - renderwithRouter(); - - const first_name = screen.getByTestId('first_name'); - const last_name = screen.getByTestId('last_name'); - const date_of_birth = screen.getByTestId('date_of_birth'); - const phone = screen.getByTestId('phone'); - - userEvent.type(first_name, 'test firstname'); - userEvent.type(last_name, 'test lastname'); - userEvent.type(date_of_birth, '2000-12-12'); - userEvent.type(phone, '+49123456789012'); - - const previous_btn = screen.getByRole('button', { name: /previous/i }); - const next_btn = screen.getByRole('button', { name: /next/i }); - - const confirmation_checkbox = screen.queryByLabelText( - /i confirm that the name and date of birth above match my chosen identity document/i - ); - expect(confirmation_checkbox).not.toBeInTheDocument(); - - expect(previous_btn).toBeEnabled(); - expect(next_btn).toBeEnabled(); - userEvent.click(next_btn); - - await waitFor(() => { - expect(new_props.onSubmit).toBeCalled(); - }); - }); - it('should autopopulate tax_residence for MF clients', () => { const new_props = { ...props, @@ -607,6 +437,61 @@ describe('', () => { expect(getByText('Afghanistan')).toBeInTheDocument(); }); + it('should have validation errors on form fields', async () => { + isMobile.mockReturnValue(false); + isDesktop.mockReturnValue(true); + + renderwithRouter(); + + const first_name = screen.getByTestId('first_name'); + const last_name = screen.getByTestId('last_name'); + const date_of_birth = await screen.getByTestId('date_of_birth'); + const place_of_birth = screen.getByTestId('place_of_birth'); + const citizenship = screen.getByTestId('citizenship'); + const phone = screen.getByTestId('phone'); + const tax_residence = screen.getByTestId('tax_residence'); + const tax_identification_number = screen.getByTestId('tax_identification_number'); + + fireEvent.blur(first_name); + fireEvent.blur(last_name); + fireEvent.blur(date_of_birth); + fireEvent.blur(place_of_birth); + fireEvent.blur(citizenship); + fireEvent.blur(phone); + fireEvent.blur(tax_residence); + fireEvent.blur(tax_identification_number); + + expect(await screen.findByText(/first name is required\./i)).toBeInTheDocument(); + expect(await screen.findByText(/last name is required\./i)).toBeInTheDocument(); + expect(await screen.findByText(/date of birth is required\./i)).toBeInTheDocument(); + expect(await screen.findByText(/place of birth is required\./i)).toBeInTheDocument(); + expect(await screen.findByText(/citizenship is required/i)).toBeInTheDocument(); + expect(await screen.findByText(/phone is required\./i)).toBeInTheDocument(); + expect(await screen.findByText(/tax residence is required\./i)).toBeInTheDocument(); + expect(await screen.findByText(/tax identification number is required\./i)).toBeInTheDocument(); + splitValidationResultTypes.mockReturnValue({ + ...mock_warnings, + errors: { + ...mock_errors.errors, + first_name: 'letters, spaces, periods, hyphens, apostrophes only', + last_name: 'last name should be between 2 and 50 characters.', + date_of_birth: 'You must be 18 years old and above.', + tax_identification_number: "Tax Identification Number can't be longer than 25 characters.", + }, + }); + fireEvent.change(first_name, { target: { value: '123' } }); + fireEvent.change(last_name, { target: { value: 'a' } }); + fireEvent.change(date_of_birth, { target: { value: '2021-04-13' } }); + fireEvent.change(tax_identification_number, { target: { value: '123456789012345678901234567890' } }); + + expect(await screen.findByText(/letters, spaces, periods, hyphens, apostrophes only/i)).toBeInTheDocument(); + expect(await screen.findByText(/last name should be between 2 and 50 characters/i)).toBeInTheDocument(); + expect(await screen.findByText(/you must be 18 years old and above\./i)).toBeInTheDocument(); + expect( + await screen.findByText(/tax Identification Number can't be longer than 25 characters\./i) + ).toBeInTheDocument(); + }); + it('should show error for invalid TIN', async () => { const newvalidate = { errors: { @@ -656,8 +541,6 @@ describe('', () => { ); expect(checkbox).not.toBeInTheDocument(); - screen.debug(); - expect(previous_btn).toBeEnabled(); expect(next_btn).toBeEnabled(); fireEvent.click(next_btn); @@ -821,63 +704,92 @@ describe('', () => { expect(screen.queryByRole('link', { name: 'here' })).not.toBeInTheDocument(); }); - it('should validate idv values when a document type is selected', async () => { - shouldShowIdentityInformation.mockReturnValue(true); + it('should disable tax_residence field if it is immutable from BE', () => { + isMobile.mockReturnValue(false); + isDesktop.mockReturnValue(true); const new_props = { ...props, - is_mf: false, + is_mf: true, value: { ...props.value, - ...idv_document_data, + tax_residence: 'France', }, - residence_list: default_residence_details, + disabled_items: ['salutation', 'first_name', 'last_name', 'date_of_birth', 'tax_residence'], }; renderwithRouter(); - - await waitFor(() => { - expect(isDocumentTypeValid).toHaveBeenCalled(); - expect(isAdditionalDocumentValid).not.toHaveBeenCalled(); - }); + expect(screen.getByTestId('tax_residence')).toBeDisabled(); }); - it('should validate idv values along with additional document number when a document type is selected', async () => { - shouldShowIdentityInformation.mockReturnValue(true); - - const new_document_data = { - ...idv_document_data, - document_type: { ...idv_document_data.document_type, additional: '12345' }, - }; - + it('submit button should be enabled if TIN or tax_residence is optional in case of CR accounts', () => { const new_props = { ...props, is_mf: false, + is_svg: true, value: { - ...props.value, - ...new_document_data, + first_name: '', + last_name: '', + date_of_birth: '', + place_of_birth: '', + phone: '+34', + tax_residence: '', + tax_identification_number: '', }, - residence_list: default_residence_details, }; renderwithRouter(); - await waitFor(() => { - expect(isAdditionalDocumentValid).toHaveBeenCalled(); - }); + const first_name = screen.getByTestId('first_name'); + const last_name = screen.getByTestId('last_name'); + const date_of_birth = screen.getByTestId('date_of_birth'); + const phone = screen.getByTestId('phone'); + + userEvent.type(first_name, 'test firstname'); + userEvent.type(last_name, 'test lastname'); + userEvent.type(date_of_birth, '2000-12-12'); + userEvent.type(phone, '+49123456789012'); + expect(screen.getByRole('button', { name: /next/i })).toBeEnabled(); }); - it('should disable tax_residence field if it is immutable from BE', () => { - isMobile.mockReturnValue(false); - isDesktop.mockReturnValue(true); + it('should not display confirmation checkbox if opt-out of IDV', async () => { + splitValidationResultTypes.mockReturnValue({ warnings: {}, errors: {} }); const new_props = { ...props, - is_mf: true, value: { - ...props.value, - tax_residence: 'France', - document_type: idv_document_data, + first_name: '', + last_name: '', + date_of_birth: '', + phone: '+93', + account_opening_reason: '', + place_of_birth: '', + document_type: 'none', }, - disabled_items: ['salutation', 'first_name', 'last_name', 'date_of_birth', 'tax_residence'], }; + renderwithRouter(); - expect(screen.getByTestId('tax_residence')).toBeDisabled(); + + const first_name = screen.getByTestId('first_name'); + const last_name = screen.getByTestId('last_name'); + const date_of_birth = screen.getByTestId('date_of_birth'); + const phone = screen.getByTestId('phone'); + + userEvent.type(first_name, 'test firstname'); + userEvent.type(last_name, 'test lastname'); + userEvent.type(date_of_birth, '2000-12-12'); + userEvent.type(phone, '+49123456789012'); + + const previous_btn = screen.getByRole('button', { name: /previous/i }); + const next_btn = screen.getByRole('button', { name: /next/i }); + + const confirmation_checkbox = screen.queryByLabelText( + /i confirm that the name and date of birth above match my chosen identity document/i + ); + expect(confirmation_checkbox).not.toBeInTheDocument(); + + expect(previous_btn).toBeEnabled(); + expect(next_btn).toBeEnabled(); + userEvent.click(next_btn); + + await waitFor(() => { + expect(new_props.onSubmit).toBeCalled(); + }); }); }); diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 791b7440ae21..8bbfd36eb2ab 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -20,7 +20,6 @@ import { shouldHideHelperImage, shouldShowIdentityInformation, } from 'Helpers/utils'; -import PoiNameDobExample from '../../Assets/ic-poi-name-dob-example.svg'; import FormSubHeader from '../form-sub-header'; import IDVForm from '../forms/idv-form'; @@ -51,8 +50,6 @@ const PersonalDetails = ({ const { account_status, account_settings, residence, real_account_signup_target } = props; const [should_close_tooltip, setShouldCloseTooltip] = React.useState(false); - const PoiNameDobExampleIcon = PoiNameDobExample; - const isSubmitDisabled = errors => { return selected_step_ref?.current?.isSubmitting || Object.keys(errors).length > 0; }; @@ -63,8 +60,7 @@ const PersonalDetails = ({ onCancel(current_step, goToPreviousStep); }; - //is_rendered_for_idv is used for configuring the components when they are used in idv page - const is_rendered_for_idv = shouldShowIdentityInformation({ + const is_qualified_for_idv = shouldShowIdentityInformation({ account_status, account_settings, residence, @@ -92,7 +88,7 @@ const PersonalDetails = ({ const handleValidate = values => { let idv_error = {}; - if (is_rendered_for_idv) { + if (is_qualified_for_idv) { idv_error = validateIDV(values); } const { errors } = splitValidationResultTypes(validate(values)); @@ -116,7 +112,7 @@ const PersonalDetails = ({ if (IDV_NOT_APPLICABLE_OPTION.id === selected_document_type_id) return editable_fields; - if (is_confirmed && is_rendered_for_idv) { + if (is_confirmed && is_qualified_for_idv) { return editable_fields.filter(field => !['first_name', 'last_name', 'date_of_birth'].includes(field)); } @@ -130,12 +126,12 @@ const PersonalDetails = ({ validate={handleValidate} validateOnMount enableReinitialize - initialStatus={{ is_confirmed: !is_rendered_for_idv }} + initialStatus={{ is_confirmed: !is_qualified_for_idv }} onSubmit={(values, actions) => { onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); }} > - {({ handleSubmit, errors, values, status }) => ( + {({ handleSubmit, errors, setFieldValue, touched, values, handleChange, handleBlur, status }) => ( {({ setRef, height }) => (
- {!is_rendered_for_idv && ( + {!is_qualified_for_idv && ( - {is_rendered_for_idv && ( + {is_qualified_for_idv && ( @@ -178,14 +180,13 @@ const PersonalDetails = ({ } - is_rendered_for_idv={is_rendered_for_idv} + is_qualified_for_idv={is_qualified_for_idv} editable_fields={getEditableFields( status?.is_confirmed, values?.document_type?.id @@ -199,12 +200,6 @@ const PersonalDetails = ({ should_close_tooltip={should_close_tooltip} setShouldCloseTooltip={setShouldCloseTooltip} should_hide_helper_image={shouldHideHelperImage(values?.document_type?.id)} - inline_note_text={ - ]} - /> - } no_confirmation_needed={ values?.document_type?.id === IDV_NOT_APPLICABLE_OPTION.id } diff --git a/packages/account/src/Components/poa/continue-trading-button/continue-trading-button.tsx b/packages/account/src/Components/poa/continue-trading-button/continue-trading-button.tsx index 60e2c1ef75cb..3d7f70a4c12a 100644 --- a/packages/account/src/Components/poa/continue-trading-button/continue-trading-button.tsx +++ b/packages/account/src/Components/poa/continue-trading-button/continue-trading-button.tsx @@ -1,20 +1,11 @@ -import React from 'react'; -import classNames from 'classnames'; import { ButtonLink, Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; - -type TContinueTradingButtonProps = { className?: string }; +import { localize } from '@deriv/translations'; +import React from 'react'; -/** - * Renders a button that redirects to the trading platform - * @name ContinueTradingButton - * @param className - Styles to be applied to the button - * @returns React Element - */ -export const ContinueTradingButton = ({ className }: TContinueTradingButtonProps) => ( - +export const ContinueTradingButton = () => ( + - + {localize('Continue trading')} ); diff --git a/packages/account/src/Components/poa/poa-button/poa-button.tsx b/packages/account/src/Components/poa/poa-button/poa-button.tsx index 49d89e35f935..0a3780dc0f46 100644 --- a/packages/account/src/Components/poa/poa-button/poa-button.tsx +++ b/packages/account/src/Components/poa/poa-button/poa-button.tsx @@ -1,18 +1,14 @@ import React from 'react'; import { ButtonLink, Text } from '@deriv/components'; import { routes } from '@deriv/shared'; -import { Localize } from '@deriv/translations'; -import classNames from 'classnames'; +import { localize } from '@deriv/translations'; type TPoaButton = { - custom_text?: JSX.Element; - class_name?: string; + custom_text?: string; }; -const DEFAULT_BTN_TEXT = ; - -const PoaButton = ({ custom_text = DEFAULT_BTN_TEXT, class_name }: TPoaButton) => ( - +const PoaButton = ({ custom_text = localize('Submit proof of address') }: TPoaButton) => ( + {custom_text} diff --git a/packages/account/src/Components/poi/idv-document-submit/__tests__/idv-document-submit.spec.tsx b/packages/account/src/Components/poi/idv-document-submit/__tests__/idv-document-submit.spec.tsx index 207c0b95ae0b..7599e31bd4c7 100644 --- a/packages/account/src/Components/poi/idv-document-submit/__tests__/idv-document-submit.spec.tsx +++ b/packages/account/src/Components/poi/idv-document-submit/__tests__/idv-document-submit.spec.tsx @@ -1,10 +1,8 @@ import React from 'react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import { isDesktop, isMobile } from '@deriv/shared'; -import { StoreProvider, mockStore } from '@deriv/stores'; -import { isDocumentNumberValid } from 'Helpers/utils'; import IdvDocumentSubmit from '../idv-document-submit'; +import { StoreProvider, mockStore } from '@deriv/stores'; const mock_store = mockStore({ client: { @@ -12,6 +10,7 @@ const mock_store = mockStore({ }, }); +jest.mock('react-router'); jest.mock('Assets/ic-document-submit-icon.svg', () => jest.fn(() => 'DocumentSubmitLogo')); jest.mock('Helpers/utils', () => ({ ...jest.requireActual('Helpers/utils'), @@ -30,11 +29,9 @@ jest.mock('Helpers/utils', () => ({ }, }, }; - - const document = data[country_code as keyof typeof data]; - return document[key as keyof typeof document]; + return data[country_code][key]; }), - isDocumentNumberValid: jest.fn(), + getRegex: jest.fn(() => /5436454364243/i), })); jest.mock('@deriv/shared', () => ({ @@ -59,7 +56,7 @@ jest.mock('@deriv/shared', () => ({ })); describe('', () => { - const mock_props: React.ComponentProps = { + const mock_props = { handleBack: jest.fn(), handleViewComplete: jest.fn(), selected_country: { @@ -68,21 +65,14 @@ describe('', () => { services: { idv: { documents_supported: { - document_1: { - display_name: 'Test document 1 name', - format: '5436454364243', - }, - document_2: { - display_name: 'Test document 2 name', - format: 'A54321', - }, + document_1: { display_name: 'Test document 1 name', format: '5436454364243' }, + document_2: { display_name: 'Test document 2 name', format: 'A54321' }, }, has_visual_sample: 1, }, }, }, }, - getChangeableFields: jest.fn(() => []), is_from_external: false, }; @@ -112,7 +102,7 @@ describe('', () => { ); const backBtn = screen.getByRole('button', { name: /go back/i }); - userEvent.click(backBtn); + fireEvent.click(backBtn); expect(mock_props.handleBack).toHaveBeenCalledTimes(1); const document_type_input = screen.getByLabelText('Choose the document type'); @@ -121,12 +111,12 @@ describe('', () => { expect(screen.queryByText('Test document 1 name')).not.toBeInTheDocument(); expect(screen.queryByText('Test document 2 name')).not.toBeInTheDocument(); - userEvent.click(document_type_input); + fireEvent.click(document_type_input); expect(await screen.findByText('Test document 1 name')).toBeInTheDocument(); expect(await screen.findByText('Test document 2 name')).toBeInTheDocument(); expect(screen.queryByText('Please select a document type.')).not.toBeInTheDocument(); - userEvent.tab(); + fireEvent.blur(document_type_input); expect(await screen.findByText('Please select a document type.')).toBeInTheDocument(); await waitFor(() => { expect(screen.queryByText('Test document 1 name')).not.toBeInTheDocument(); @@ -155,20 +145,18 @@ describe('', () => { expect(document_number_input.name).toBe('document_number'); expect(document_number_input).toBeDisabled(); - userEvent.selectOptions(document_type_input, 'Test document 2 name'); - await waitFor(() => { - expect(document_number_input).toBeEnabled(); - }); + fireEvent.change(document_type_input, { target: { value: 'Test document 2 name' } }); + expect(document_number_input).toBeEnabled(); expect(screen.queryByText(/please enter the correct format/i)).not.toBeInTheDocument(); - (isDocumentNumberValid as jest.Mock).mockReturnValueOnce('please enter your document number'); + fireEvent.blur(document_number_input); expect(await screen.findByText(/please enter your document number/i)).toBeInTheDocument(); - (isDocumentNumberValid as jest.Mock).mockReturnValueOnce('please enter the correct format'); + fireEvent.keyUp(document_number_input); fireEvent.change(document_number_input, { target: { value: 'A-32523' } }); expect(await screen.findByText(/please enter the correct format/i)).toBeInTheDocument(); - userEvent.type(document_number_input, '5436454364234'); + fireEvent.change(document_number_input, { target: { value: 'A54321' } }); await waitFor(() => { expect(screen.queryByText(/please enter the correct format/i)).not.toBeInTheDocument(); expect(screen.queryByText(/please enter a valid ID number/i)).not.toBeInTheDocument(); @@ -177,7 +165,7 @@ describe('', () => { fireEvent.click(confirmation_checkbox); expect(verifyBtn).toBeEnabled(); - userEvent.click(verifyBtn); + fireEvent.click(verifyBtn); await waitFor(() => { expect(mock_props.handleViewComplete).toHaveBeenCalledTimes(1); }); diff --git a/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.tsx b/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.tsx index 584237b42360..461078949cd7 100644 --- a/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.tsx +++ b/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.tsx @@ -1,59 +1,48 @@ import React from 'react'; import classNames from 'classnames'; -import { Form, Formik, FormikErrors, FormikHelpers } from 'formik'; -import { ResidenceList, IdentityVerificationAddDocumentResponse } from '@deriv/api-types'; -import { Button, HintBox, Text } from '@deriv/components'; -import { Localize, localize } from '@deriv/translations'; +import { Button } from '@deriv/components'; +import { Formik } from 'formik'; +import { localize } from '@deriv/translations'; import { WS, + getIDVNotApplicableOption, toMoment, filterObjProperties, isDesktop, removeEmptyPropertiesFromObject, formatIDVFormValues, - isMobile, } from '@deriv/shared'; -import { observer, useStore } from '@deriv/stores'; -import BackButtonIcon from 'Assets/ic-poi-back-btn.svg'; -import PoiNameDobExample from 'Assets/ic-poi-name-dob-example.svg'; -import FormBody from 'Components/form-body'; -import FormFooter from 'Components/form-footer'; -import IDVForm from 'Components/forms/idv-form'; -import PersonalDetailsForm from 'Components/forms/personal-details-form'; -import FormSubHeader from 'Components/form-sub-header'; import { + documentAdditionalError, + isDocumentNumberValid, validate, makeSettingsRequest, validateName, - shouldHideHelperImage, - isDocumentTypeValid, - isAdditionalDocumentValid, - isDocumentNumberValid, + getExampleFormat, } from 'Helpers/utils'; -import { DUPLICATE_ACCOUNT_ERROR_MESSAGE, GENERIC_ERROR_MESSAGE } from 'Configs/poi-error-config'; -import { TIDVFormValues, TPersonalDetailsForm } from 'Types'; +import FormFooter from 'Components/form-footer'; +import BackButtonIcon from 'Assets/ic-poi-back-btn.svg'; +import IDVForm from 'Components/forms/idv-form'; +import PersonalDetailsForm from 'Components/forms/personal-details-form'; +import FormSubHeader from 'Components/form-sub-header'; +import { observer, useStore } from '@deriv/stores'; +import { ResidenceList, IdentityVerificationAddDocumentResponse } from '@deriv/api-types'; +import { TDocument, TInputFieldValues, TIDVFormValues } from 'Types'; type TIDVDocumentSubmitProps = { handleBack: React.MouseEventHandler; handleViewComplete: () => void; is_from_external: boolean; selected_country: ResidenceList[0]; - getChangeableFields: () => Array; }; -type TIdvDocumentSubmitForm = TIDVFormValues & TPersonalDetailsForm; - const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_country }: TIDVDocumentSubmitProps) => { const { client: { account_settings, getChangeableFields }, } = useStore(); const visible_settings = ['first_name', 'last_name', 'date_of_birth']; - const side_note_image = ; - - const form_initial_values = filterObjProperties(account_settings, visible_settings) as { - [Property in keyof TPersonalDetailsForm]: string; - }; + const form_initial_values = filterObjProperties(account_settings, visible_settings) || {}; if (form_initial_values.date_of_birth) { form_initial_values.date_of_birth = toMoment(form_initial_values.date_of_birth).format('YYYY-MM-DD'); @@ -61,7 +50,7 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c const changeable_fields = getChangeableFields(); - const initial_values: TIdvDocumentSubmitForm = { + const initial_values = { document_type: { id: '', text: '', @@ -73,8 +62,27 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c ...form_initial_values, }; - const validateFields = (values: TIdvDocumentSubmitForm) => { - const errors: FormikErrors> = {}; + const IDV_NOT_APPLICABLE_OPTION = React.useMemo(() => getIDVNotApplicableOption(), []); + + const shouldHideHelperImage = (document_id: string) => document_id === IDV_NOT_APPLICABLE_OPTION.id; + + const isDocumentTypeValid = (document_type: TDocument) => { + if (!document_type?.text) { + return localize('Please select a document type.'); + } + return undefined; + }; + + const isAdditionalDocumentValid = (document_type: TDocument, document_additional: string) => { + const error_message = documentAdditionalError(document_additional, document_type.additional?.format); + if (error_message) { + return localize(error_message) + getExampleFormat(document_type.additional?.example_format); + } + return undefined; + }; + + const validateFields = (values: TIDVFormValues) => { + const errors: Partial = {}; const { document_type, document_number, document_additional } = values; const needs_additional_document = !!document_type.additional; @@ -99,26 +107,21 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c return removeEmptyPropertiesFromObject(errors); }; - const submitHandler = async ( - values: TIdvDocumentSubmitForm, - { setSubmitting, setStatus }: FormikHelpers - ) => { + const submitHandler = async (values, { setSubmitting, setErrors }) => { setSubmitting(true); const request = makeSettingsRequest(values, changeable_fields); const data = await WS.setSettings(request); - if (data?.error) { - const response_error = - data.error?.code === 'DuplicateAccount' ? DUPLICATE_ACCOUNT_ERROR_MESSAGE : GENERIC_ERROR_MESSAGE; - setStatus({ error_message: response_error }); + if (data.error) { + setErrors({ error_message: data.error.message }); setSubmitting(false); return; } - const get_settings = await WS.authorized.storage.getSettings(); - if (get_settings?.error) { - setStatus({ error_message: get_settings?.error?.message ?? GENERIC_ERROR_MESSAGE }); + const get_settings = WS.authorized.storage.getSettings(); + if (get_settings.error) { + setErrors({ error_message: data.error.message }); setSubmitting(false); return; } @@ -132,7 +135,7 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c (response: IdentityVerificationAddDocumentResponse & { error: { message: string } }) => { setSubmitting(false); if (response.error) { - setStatus({ error_message: response?.error?.message ?? GENERIC_ERROR_MESSAGE }); + setErrors({ error_message: response.error.message }); return; } handleViewComplete(); @@ -149,25 +152,33 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c }} onSubmit={submitHandler} > - {({ dirty, isSubmitting, isValid, values, status }) => ( - - {status?.error_message && ( -
- - {status.error_message} - - } - is_danger - className='hint-box-layout' - /> -
- )} - + {({ + dirty, + errors, + handleBlur, + handleChange, + handleSubmit, + isSubmitting, + isValid, + setFieldValue, + touched, + values, + status, + }) => ( +
+
- + ]} - /> - } /> - +
{isDesktop() && (
)} ); diff --git a/packages/account/src/Components/poi/idv-status/idv-expired/__tests__/idv-expired.spec.tsx b/packages/account/src/Components/poi/idv-status/idv-expired/__tests__/idv-expired.spec.tsx new file mode 100644 index 000000000000..a0f151090751 --- /dev/null +++ b/packages/account/src/Components/poi/idv-status/idv-expired/__tests__/idv-expired.spec.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { screen, render, fireEvent } from '@testing-library/react'; +import { isDesktop, isMobile } from '@deriv/shared'; +import IdvExpired from '../idv-expired'; + +jest.mock('@deriv/components', () => { + const original_module = jest.requireActual('@deriv/components'); + return { + ...original_module, + Icon: jest.fn(() => mockedIcon), + }; +}); + +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + isDesktop: jest.fn(), + isMobile: jest.fn(), +})); + +beforeEach(() => { + (isDesktop as jest.Mock).mockReturnValue(true); + (isMobile as jest.Mock).mockReturnValue(false); + jest.clearAllMocks(); +}); + +describe('', () => { + const props = { + handleRequireSubmission: jest.fn(), + }; + + const testComponentRender = () => { + render(); + expect(screen.getByTestId('idv_expired_container')).toBeInTheDocument(); + expect(screen.getByText('mockedIcon')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /try again/i })).toBeInTheDocument(); + }; + + it('should render IdvExpired component on desktop', () => { + testComponentRender(); + }); + + it('should render IdvExpired component on mobile', () => { + (isDesktop as jest.Mock).mockReturnValue(false); + (isMobile as jest.Mock).mockReturnValue(true); + testComponentRender(); + }); + + it('should call handleRequireSubmission when try_again button is clicked', () => { + render(); + const try_again_btn = screen.getByRole('button', { name: /try again/i }); + fireEvent.click(try_again_btn); + expect(props.handleRequireSubmission).toBeCalledTimes(1); + }); +}); diff --git a/packages/account/src/Components/poi/idv-status/idv-expired/idv-expired.tsx b/packages/account/src/Components/poi/idv-status/idv-expired/idv-expired.tsx new file mode 100644 index 000000000000..bac7ec561865 --- /dev/null +++ b/packages/account/src/Components/poi/idv-status/idv-expired/idv-expired.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Button, Icon, Text } from '@deriv/components'; +import { isMobile } from '@deriv/shared'; +import { localize } from '@deriv/translations'; + +type TIdvExpired = { + handleRequireSubmission: () => void; +}; + +const IdvExpired = ({ handleRequireSubmission }: TIdvExpired) => { + return ( +
+ + + {isMobile() ? localize('ID verification failed') : localize('Verification of document number failed')} + + + {isMobile() + ? localize('The ID you submitted is expired.') + : localize( + 'It looks like your identity document has expired. Please try again with a valid document.' + )} + +
+ ); +}; + +export default IdvExpired; diff --git a/packages/account/src/Components/poi/idv-status/idv-expired/index.js b/packages/account/src/Components/poi/idv-status/idv-expired/index.js new file mode 100644 index 000000000000..6a966351c5b5 --- /dev/null +++ b/packages/account/src/Components/poi/idv-status/idv-expired/index.js @@ -0,0 +1,3 @@ +import IdvExpired from './idv-expired'; + +export default IdvExpired; diff --git a/packages/account/src/Components/poi/idv-status/idv-failed/__tests__/idv-failed.spec.tsx b/packages/account/src/Components/poi/idv-status/idv-failed/__tests__/idv-failed.spec.tsx deleted file mode 100644 index 30f58c16ebd3..000000000000 --- a/packages/account/src/Components/poi/idv-status/idv-failed/__tests__/idv-failed.spec.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import IdvFailed from '../idv-failed'; -import { idv_error_statuses } from '@deriv/shared'; - -jest.mock('@deriv/shared', () => ({ - ...jest.requireActual('@deriv/shared'), - WS: { - wait: jest.fn().mockResolvedValue(true), - }, - filterObjProperties: jest.fn(() => ({ - document_type: { - id: '', - text: '', - value: '', - example_format: '', - sample_image: '', - }, - document_number: '', - first_name: '', - last_name: '', - date_of_birth: '', - })), -})); - -jest.mock('Components/forms/personal-details-form', () => jest.fn(() =>
PersonalDetailsForm
)); -jest.mock('Components/forms/idv-form', () => jest.fn(() =>
IDVForm
)); -jest.mock('Assets/ic-poi-name-example.svg', () => jest.fn(() => 'PoiNameExample')); -jest.mock('Assets/ic-poi-dob-example.svg', () => jest.fn(() => 'PoiDobExample')); -jest.mock('Assets/ic-poi-name-dob-example.svg', () => jest.fn(() => 'PoiNameDobExample')); - -describe('', () => { - const mock_props: React.ComponentProps = { - getChangeableFields: jest.fn(() => []), - is_from_external: false, - residence_list: [], - handleSubmit: jest.fn(), - account_settings: { - citizen: 'gh', - }, - mismatch_status: idv_error_statuses.poi_name_mismatch, - latest_status: {}, - }; - - it('should render IDVfailed component with name mismatch message', async () => { - render(); - - await waitFor(() => { - expect(screen.getByTestId(idv_error_statuses.poi_name_mismatch)).toBeInTheDocument(); - expect(screen.getByText('PersonalDetailsForm')).toBeInTheDocument(); - }); - }); - - it('should render IDVfailed component with dob mismatch message', async () => { - const new_props = { ...mock_props, mismatch_status: idv_error_statuses.poi_dob_mismatch }; - render(); - - await waitFor(() => { - expect(screen.getByTestId(idv_error_statuses.poi_dob_mismatch)).toBeInTheDocument(); - expect(screen.queryByText('IDVForm')).not.toBeInTheDocument(); - }); - }); - - it('should render IDVfailed component with name & DOB mismatch message', async () => { - const new_props = { ...mock_props, mismatch_status: idv_error_statuses.poi_name_dob_mismatch }; - render(); - - await waitFor(() => { - expect(screen.getByTestId(idv_error_statuses.poi_name_dob_mismatch)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /Update profile/i })).toBeInTheDocument(); - }); - }); - - it('should render IDVfailed component with expired message', async () => { - const new_props = { ...mock_props, mismatch_status: idv_error_statuses.poi_expired }; - render(); - - await waitFor(() => { - expect(screen.getByTestId(idv_error_statuses.poi_expired)).toBeInTheDocument(); - expect(screen.getByText('IDVForm')).toBeInTheDocument(); - expect(screen.getByText('PersonalDetailsForm')).toBeInTheDocument(); - }); - }); - - it('should render IDVfailed component with verification failed message', async () => { - const new_props = { ...mock_props, mismatch_status: idv_error_statuses.poi_failed }; - render(); - - await waitFor(() => { - expect(screen.getByTestId(idv_error_statuses.poi_failed)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /Verify/i })).toBeInTheDocument(); - }); - }); -}); diff --git a/packages/account/src/Components/poi/idv-status/idv-failed/idv-failed.tsx b/packages/account/src/Components/poi/idv-status/idv-failed/idv-failed.tsx deleted file mode 100644 index 808c9a3546af..000000000000 --- a/packages/account/src/Components/poi/idv-status/idv-failed/idv-failed.tsx +++ /dev/null @@ -1,406 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import { Form, Formik, FormikHelpers, FormikValues } from 'formik'; -import { - GetAccountStatus, - GetSettings, - IdentityVerificationAddDocumentResponse, - ResidenceList, -} from '@deriv/api-types'; -import { Button, DesktopWrapper, HintBox, Loading, Text } from '@deriv/components'; -import { - filterObjProperties, - getIDVNotApplicableOption, - getPropertyValue, - idv_error_statuses, - isEmptyObject, - isMobile, - removeEmptyPropertiesFromObject, - TIDVErrorStatus, - toMoment, - WS, -} from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; -import PoiNameExample from '../../../../Assets/ic-poi-name-example.svg'; -import PoiDobExample from '../../../../Assets/ic-poi-dob-example.svg'; -import PoiNameDobExample from '../../../../Assets/ic-poi-name-dob-example.svg'; -import FormBody from '../../../form-body'; -import IDVForm from '../../../forms/idv-form'; -import FormFooter from '../../../form-footer'; -import FormSubHeader from '../../../form-sub-header'; -import PersonalDetailsForm from '../../../forms/personal-details-form.jsx'; -import { - getIDVDocumentType, - isAdditionalDocumentValid, - isDocumentNumberValid, - isDocumentTypeValid, - makeSettingsRequest, - shouldHideHelperImage, - validate, - validateName, -} from '../../../../Helpers/utils'; -import { GENERIC_ERROR_MESSAGE, DUPLICATE_ACCOUNT_ERROR_MESSAGE } from '../../../../Configs/poi-error-config'; -import { API_ERROR_CODES } from '../../../../Constants/api-error-codes'; -import { TIDVFormValues, TPersonalDetailsForm } from '../../../../Types'; -import LoadErrorMessage from '../../../load-error-message'; - -type TRestState = { - api_error: string; - errors?: boolean; - form_initial_values: TIdvFailedForm; - changeable_fields: string[]; -}; - -type TIdvFailed = { - account_settings: GetSettings; - getChangeableFields: () => string[]; - handleSubmit: () => void; - is_from_external: boolean; - mismatch_status: TIDVErrorStatus; - residence_list: ResidenceList; - latest_status: DeepRequired['authentication']['attempts']['latest']; - selected_country?: ResidenceList[0]; -}; - -type TIDVFailureConfig = { - required_fields: string[]; - side_note_image: JSX.Element; - failure_message: React.ReactNode; - inline_note_text: React.ReactNode; -}; - -type TIdvFailedForm = Partial & Partial; - -const IdvFailed = ({ - getChangeableFields, - is_from_external, - residence_list, - account_settings, - handleSubmit, - mismatch_status = idv_error_statuses.poi_failed, - latest_status, - selected_country, -}: TIdvFailed) => { - const [idv_failure, setIdvFailure] = React.useState({ - required_fields: [], - side_note_image: , - failure_message: null, - inline_note_text: null, - }); - const [is_loading, setIsLoading] = React.useState(true); - const [rest_state, setRestState] = React.useState({ - api_error: '', - errors: false, - form_initial_values: {}, - changeable_fields: [], - }); - - const is_document_upload_required = React.useMemo( - () => [idv_error_statuses.poi_expired, idv_error_statuses.poi_failed].includes(mismatch_status), - [mismatch_status] - ); - - /** - * If user needs to resubmit IDV document, the country should be the new selected country - * If user needs to update Personal info, the country should be the country of the latest status - */ - const chosen_country = React.useMemo( - () => - is_document_upload_required - ? selected_country ?? {} - : residence_list.find(residence_data => residence_data.value === latest_status?.country_code) ?? {}, - [selected_country, is_document_upload_required, latest_status?.country_code, residence_list] - ); - - const IDV_NOT_APPLICABLE_OPTION = React.useMemo(() => getIDVNotApplicableOption(), []); - - const generateIDVError = React.useCallback(() => { - const document_name = is_document_upload_required - ? getPropertyValue(chosen_country, ['identity', 'services', 'idv', 'documents_supported']) - : getIDVDocumentType(latest_status, chosen_country); - switch (mismatch_status) { - case idv_error_statuses.poi_name_dob_mismatch: - return { - required_fields: ['first_name', 'last_name', 'date_of_birth'], - side_note_image: , - inline_note_text: ( - ]} - values={{ document_name }} - /> - ), - failure_message: ( - ]} - /> - ), - }; - case idv_error_statuses.poi_name_mismatch: - return { - required_fields: ['first_name', 'last_name'], - side_note_image: , - inline_note_text: ( - ]} - values={{ document_name }} - /> - ), - failure_message: ( - ]} - /> - ), - }; - case idv_error_statuses.poi_dob_mismatch: - return { - required_fields: ['date_of_birth'], - side_note_image: , - inline_note_text: ( - ]} - values={{ document_name }} - /> - ), - failure_message: ( - ]} - /> - ), - }; - default: - return { - required_fields: ['first_name', 'last_name', 'date_of_birth'], - side_note_image: , - inline_note_text: ( - ]} - values={{ document_name }} - /> - ), - failure_message: ( - - ), - }; - } - }, [latest_status, mismatch_status, chosen_country]); - - React.useEffect(() => { - const initializeFormValues = async (required_fields: string[]) => { - await WS.wait('get_settings'); - const form_data = filterObjProperties(account_settings, required_fields); - if (form_data.date_of_birth) { - form_data.date_of_birth = toMoment(form_data.date_of_birth).format('YYYY-MM-DD'); - } - let initial_form_values = form_data; - if (is_document_upload_required) { - initial_form_values = { - document_type: { - id: '', - text: '', - value: '', - example_format: '', - sample_image: '', - }, - document_number: '', - ...initial_form_values, - }; - } - setRestState({ - form_initial_values: { ...initial_form_values }, - changeable_fields: [...getChangeableFields()], - api_error: '', - }); - setIsLoading(false); - }; - - const error_config = generateIDVError(); - setIdvFailure(error_config); - initializeFormValues(error_config?.required_fields ?? []).catch(e => { - setRestState({ - form_initial_values: {}, - changeable_fields: [], - api_error: e?.error?.message, - }); - }); - }, [mismatch_status, account_settings, is_document_upload_required, getChangeableFields, generateIDVError]); - - const onSubmit = async (values: TIdvFailedForm, { setStatus, setSubmitting }: FormikHelpers) => { - setSubmitting(true); - setStatus({ error_msg: null }); - const { document_number, document_type } = values; - const request = makeSettingsRequest( - values, - rest_state?.changeable_fields ? [...rest_state.changeable_fields] : [] - ); - const data = await WS.setSettings(request); - - if (data.error) { - const response_error = - data.error?.code === API_ERROR_CODES.DUPLICATE_ACCOUNT - ? DUPLICATE_ACCOUNT_ERROR_MESSAGE - : GENERIC_ERROR_MESSAGE; - setStatus({ error_msg: response_error }); - setSubmitting(false); - } else { - const response = await WS.authorized.storage.getSettings(); - if (response.error) { - setRestState(prev_state => ({ ...prev_state, api_error: response.error.message })); - setSubmitting(false); - return; - } - const submit_data = { - identity_verification_document_add: 1, - document_number, - document_type: document_type?.id, - issuing_country: chosen_country.value, - }; - - if (!submit_data.document_type || submit_data.document_type === IDV_NOT_APPLICABLE_OPTION.id) { - setSubmitting(false); - handleSubmit(); - return; - } - WS.send(submit_data).then((resp: IdentityVerificationAddDocumentResponse) => { - setSubmitting(false); - if (resp.error) { - return; - } - handleSubmit(); - }); - } - }; - - const validateFields = (values: TIdvFailedForm) => { - const errors: Record = {}; - if (is_document_upload_required) { - const { document_type, document_number, document_additional } = values; - const needs_additional_document = !!document_type?.additional; - errors.document_type = isDocumentTypeValid(document_type as FormikValues); - if (!shouldHideHelperImage(document_type?.id as string)) { - if (needs_additional_document) { - errors.document_additional = isAdditionalDocumentValid(document_type, document_additional); - } - errors.document_number = isDocumentNumberValid(document_number ?? '', document_type as FormikValues); - } - } - - const validateValues = validate(errors, values); - - validateValues(val => val, idv_failure?.required_fields ?? [], localize('This field is required')); - - if (values.first_name) { - errors.first_name = validateName(values.first_name); - } - if (values.last_name) { - errors.last_name = validateName(values.last_name); - } - - setRestState(prev_state => ({ - ...prev_state, - errors: !isEmptyObject(removeEmptyPropertiesFromObject(errors)), - })); - return removeEmptyPropertiesFromObject(errors); - }; - - if (rest_state?.api_error) return ; - - if (is_loading && Object.keys(rest_state?.form_initial_values ?? {}).length === 0) { - return ; - } - - return ( - - {({ isSubmitting, isValid, dirty, status }) => ( -
- - - - - {(status?.error_msg || idv_failure?.failure_message) && ( - - {status?.error_msg ?? idv_failure?.failure_message} - - } - is_danger - /> - )} - {is_document_upload_required && ( - - - - - - - - - )} - - - {!is_from_external && ( - - ); + if (api_error) { + return ; + } + /** + * Display loader while waiting for the account status and residence list to be populated + */ + if (is_status_loading || is_switching || isEmptyObject(account_status) || residence_list.length === 0) { + return ; + } else if (is_virtual) { + return ; + } - const should_show_mismatch_form = - idv.submissions_left > 0 && - [identity_status_codes.rejected, identity_status_codes.suspected, identity_status_codes.expired].includes( - idv.status - ); + const verification_status = populateVerificationStatus(account_status); + const { + idv, + allow_poi_resubmission, + identity_last_attempt, + identity_status, + is_age_verified, + is_idv_disallowed, + manual, + needs_poa, + onfido, + } = verification_status; + const should_ignore_idv = is_high_risk && is_withdrawal_lock; + + if (!should_allow_authentication && !is_age_verified) { + return ; + } - if ( - identity_status === identity_status_codes.none || - has_require_submission || - allow_poi_resubmission || - should_show_mismatch_form - ) { - return ( - - ); - } else if ( - !identity_last_attempt || - // Prioritise verified status from back office. How we know this is if there is mismatch between current statuses (Should be refactored) - (identity_status === identity_status_codes.verified && identity_status !== identity_last_attempt.status) - ) { - switch (identity_status) { - case identity_status_codes.pending: - return ( - - ); - case identity_status_codes.verified: - return ( - - ); - case identity_status_codes.expired: - return ( - - ); - case identity_status_codes.rejected: - case identity_status_codes.suspected: - return ; - default: - break; - } + const onClickRedirectButton = () => { + const platform = platforms[from_platform.ref]; + const { is_hard_redirect = false, url = '' } = platform ?? {}; + if (is_hard_redirect) { + window.location.href = url; + window.sessionStorage.removeItem('config.platform'); + } else { + routeBackTo(from_platform.route); } - - switch (identity_last_attempt.service) { - case service_code.idv: + }; + + const redirect_button = should_show_redirect_btn && ( + + ); + + if (identity_status === identity_status_codes.none || has_require_submission || allow_poi_resubmission) { + return ( + + ); + } else if ( + !identity_last_attempt || + // Prioritise verified status from back office. How we know this is if there is mismatch between current statuses (Should be refactored) + (identity_status === identity_status_codes.verified && identity_status !== identity_last_attempt.status) + ) { + switch (identity_status) { + case identity_status_codes.pending: return ( - ); - case service_code.onfido: + case identity_status_codes.verified: return ( - ); - case service_code.manual: + case identity_status_codes.expired: return ( - ); + case identity_status_codes.rejected: + case identity_status_codes.suspected: + return ; default: - return null; + break; } } -); + + switch (identity_last_attempt.service) { + case service_code.idv: + return ( + + ); + case service_code.onfido: + return ( + + ); + case service_code.manual: + return ( + + ); + default: + return null; + } +}); export default ProofOfIdentityContainer; diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx index f7993a0b0790..371aafc87833 100644 --- a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx @@ -1,28 +1,17 @@ import React from 'react'; import { AutoHeightWrapper } from '@deriv/components'; -import { WS, isVerificationServiceSupported, formatIDVFormValues, formatIDVError } from '@deriv/shared'; +import { WS, isVerificationServiceSupported, formatIDVFormValues } from '@deriv/shared'; import { useStore, observer } from '@deriv/stores'; import Unsupported from '../../../Components/poi/status/unsupported'; import OnfidoUpload from './onfido-sdk-view-container'; import { identity_status_codes, submission_status_code, service_code } from './proof-of-identity-utils'; -import IdvFailed from '../../../Components/poi/idv-status/idv-failed'; import { IdvDocSubmitOnSignup } from '../../../Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup'; import { makeSettingsRequest } from '../../../Helpers/utils'; const POISubmissionForMT5 = observer( - ({ - idv, - is_idv_disallowed, - onfido, - onStateChange, - citizen_data, - is_from_external, - residence_list, - identity_last_attempt, - }) => { + ({ idv, is_idv_disallowed, onfido, onStateChange, citizen_data, has_idv_error, residence_list }) => { const [submission_status, setSubmissionStatus] = React.useState(); // submitting const [submission_service, setSubmissionService] = React.useState(); - const [idv_mismatch_status, setIdvMismatchStatus] = React.useState(null); const { client, notifications, traders_hub } = useStore(); const { account_settings, getChangeableFields } = client; @@ -31,21 +20,12 @@ const POISubmissionForMT5 = observer( React.useEffect(() => { if (citizen_data) { - const { submissions_left: idv_submissions_left, last_rejected, status } = idv; + const { submissions_left: idv_submissions_left } = idv; const { submissions_left: onfido_submissions_left } = onfido; const is_idv_supported = isVerificationServiceSupported(residence_list, account_settings, 'idv'); const is_onfido_supported = isVerificationServiceSupported(residence_list, account_settings, 'onfido'); if (is_idv_supported && Number(idv_submissions_left) > 0 && !is_idv_disallowed && !is_eu_user) { setSubmissionService(service_code.idv); - if ( - [ - identity_status_codes.rejected, - identity_status_codes.suspected, - identity_status_codes.expired, - ].includes(status) - ) { - setIdvMismatchStatus(formatIDVError(last_rejected, status)); - } } else if (onfido_submissions_left && is_onfido_supported) { setSubmissionService(service_code.onfido); } else { @@ -98,23 +78,15 @@ const POISubmissionForMT5 = observer( handlePOIComplete(); }); }; + if (submission_status === submission_status_code.submitting) { switch (submission_service) { case service_code.idv: - return idv_mismatch_status ? ( - - ) : ( + return ( diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission.jsx index 6ebef32102d8..abf5f9c59326 100644 --- a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission.jsx +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission.jsx @@ -1,10 +1,9 @@ import React from 'react'; -import { formatIDVError, WS, idv_error_statuses } from '@deriv/shared'; +import { WS } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; -import CountrySelector from 'Components/poi/poi-country-selector'; +import PoiCountrySelector from 'Components/poi/poi-country-selector'; import IdvDocumentSubmit from 'Components/poi/idv-document-submit'; -import IdvFailed from 'Components/poi/idv-status/idv-failed'; -import IdvSubmitComplete from 'Components/poi/idv-status/idv-submit-complete'; +import IdvUploadComplete from 'Components/poi/idv-status/idv-submit-complete'; import Unsupported from 'Components/poi/status/unsupported'; import UploadComplete from 'Components/poi/status/upload-complete'; import OnfidoUpload from './onfido-sdk-view-container'; @@ -25,15 +24,15 @@ const POISubmission = observer( redirect_button, residence_list, setIsCfdPoiCompleted, - should_show_mismatch_form, }) => { const [submission_status, setSubmissionStatus] = React.useState(); // selecting, submitting, complete const [submission_service, setSubmissionService] = React.useState(); const [selected_country, setSelectedCountry] = React.useState({}); - const { client, notifications } = useStore(); + const { client, common, notifications } = useStore(); const { account_settings, getChangeableFields } = client; + const { current_language } = common; const { refreshNotifications } = notifications; const handleSelectionNext = () => { @@ -73,8 +72,6 @@ const POISubmission = observer( [residence_list] ); - const mismatch_status = formatIDVError(idv.last_rejected, idv.status); - React.useEffect(() => { if (submission_status !== submission_status_code.complete) { if ((has_require_submission || allow_poi_resubmission) && identity_last_attempt) { @@ -106,13 +103,6 @@ const POISubmission = observer( default: break; } - } else if ( - mismatch_status && - ![idv_error_statuses.poi_expired, idv_error_statuses.poi_failed].includes(mismatch_status) && - idv.submissions_left > 0 - ) { - setSubmissionService(service_code.idv); - setSubmissionStatus(submission_status_code.submitting); } else { setSubmissionStatus(submission_status_code.selecting); } @@ -129,31 +119,20 @@ const POISubmission = observer( switch (submission_status) { case submission_status_code.selecting: { return ( - ); } - case submission_status_code.submitting: { switch (submission_service) { case service_code.idv: - return should_show_mismatch_form ? ( - - ) : ( + return ( diff --git a/packages/account/src/Styles/account.scss b/packages/account/src/Styles/account.scss index 88b794eb97c2..09181ba539aa 100644 --- a/packages/account/src/Styles/account.scss +++ b/packages/account/src/Styles/account.scss @@ -200,6 +200,58 @@ $MIN_HEIGHT_FLOATING: calc( overflow: hidden; } + &__scrollbars_container { + height: 100%; + padding-top: 2.4rem; + padding-bottom: 6.4rem; + + &-wrapper { + overflow-x: hidden; + overflow-y: auto; + } + + &--grid-layout { + display: grid; + grid-gap: 4px; + + .dc-input { + margin-bottom: 0; + } + + .dc-dropdown-container { + .dc-dropdown__display-text, + .dc-list__item-text { + text-transform: unset; + } + } + + @include mobile { + padding: 0 1.6rem; + overflow-x: hidden; + overflow-y: auto; + grid-template-rows: auto; + + & .account-form__header { + &:first-child { + padding-top: 2.4rem; + margin-bottom: 3.2rem; + } + } + } + } + + @include desktop { + padding-left: 16px; + padding-right: 16px; + } + + @include mobile { + height: unset; + padding-top: unset; + padding-bottom: unset; + } + } + &-form { overflow: hidden; height: 100%; @@ -364,6 +416,144 @@ $MIN_HEIGHT_FLOATING: calc( margin-left: 0; } + &__footer { + height: 80px; + position: absolute; + bottom: 0; + left: 0; + display: flex; + width: 100%; + padding: 16px 24px; + align-items: center; + justify-content: flex-end; + z-index: 4; + border-radius: 0 $BORDER_RADIUS $BORDER_RADIUS 0; + border-top: 1px solid var(--general-section-1); + background-color: var(--general-main-1); + + &--reset { + padding: 2.4rem 0; + position: relative; + margin-top: 2rem; + } + + .back-btn { + margin-right: 8px; + + .back-btn-icon { + margin-right: 0.8rem; + } + + span { + display: inline-flex; + align-items: center; + } + } + + &-note { + color: var(--text-prominent); + font-size: var(--text-size-xxs); + line-height: 1.5; + text-align: right; + min-width: 27.6rem; + max-width: 36.6rem; + height: 3.6rem; + + @include mobile { + width: auto; + text-align: center; + align-self: center; + + &--dashboard { + margin: 0 1.6rem; + text-align: left; + } + } + } + + &-btn { + height: 4rem; + margin: 0 0 0 1.6rem; + + &-wrapper { + align-items: normal; + display: flex; + flex-direction: row; + + @include mobile { + flex-direction: column; + } + } + + &-dashboard { + .dc-btn__text { + color: var(--text-prominent); + } + } + + &-dashboard:hover { + background-color: unset !important; + } + + @include desktop { + margin-left: 1.6rem; + + &-wrapper--dashboard { + display: flex; + justify-content: space-between; + } + } + + @include mobile { + margin: 0 2.4rem; + + &--has-bottom-margin { + margin-bottom: 1.6rem; + } + } + } + + &--dashboard { + width: 68.5rem; + margin-left: 6rem; + + @include mobile { + width: 100vw; + margin-left: unset; + + & .account-form__footer-btn { + min-width: 35%; + + &-wrapper { + flex-direction: row; + } + } + + & > div { + display: flex; + justify-content: space-around; + } + } + } + + &-poa { + margin-left: unset; + } + + &-all-fields-required { + padding-bottom: 2.4rem; + padding-top: 0.8rem; + } + + @include mobile { + padding: 16px 0; + flex-direction: column; + height: auto; + align-items: initial; + border-top: 2px solid var(--general-section-1); + } + } + &__error-message { padding: 1rem 0; position: unset; @@ -1845,6 +2035,239 @@ $MIN_HEIGHT_FLOATING: calc( /* stylelint-enable */ +/** @define proof-of-identity */ +.proof-of-identity { + width: 100%; + height: 100%; + + &__main-container { + max-width: 68.2rem; + } + + .min-height { + min-height: 50vh; + + @include mobile { + min-height: unset; + } + } + + &__container { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + + @include mobile { + min-height: unset; + overflow-y: scroll; + justify-content: center; + } + + &--reset { + align-items: normal; + @include mobile { + overflow-y: unset; + padding: 1.6rem; + } + .proof-of-identity__submit-button { + @include mobile { + margin: unset; + width: 100%; + } + } + .account-form__footer { + @include mobile { + padding: 1.6rem 2.4rem; + } + } + .form-body { + @include desktop { + z-index: 5; + } + } + } + + .icon { + width: 128px; + height: 128px; + margin-top: 5.2rem; + margin-bottom: 2.6rem; + + @include mobile { + width: 72px; + height: 72px; + margin-top: 2.6rem; + } + } + + .external-dropdown { + .dc-dropdown-list { + z-index: 5; + } + } + } + + &__redirection { + .dc-btn { + margin-top: 3.2rem; + height: 4rem; + + @include mobile { + margin: 1.6rem 0; + padding: 1.6rem; + width: 100%; + } + } + } + + &__country-text { + margin-bottom: 1.6rem; + } + + &__text { + @include mobile { + width: 94%; + text-align: center; + } + } + + &__dropdown-container { + margin-top: 1.6rem; + } + + &__inner-container { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 100%; + + @include mobile { + flex-direction: column; + } + } + + &__header { + margin: 5.4rem 0 1.6rem; + + @include mobile { + margin: 2.4rem 0 0.8rem; + } + } + + &__footer.external-footer { + padding: unset; + position: unset; + top: unset; + bottom: unset; + border: none; + height: fit-content; + z-index: 4; + } + + &__footer { + @include mobile { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; + position: fixed; + bottom: 0; + } + } + + &__footer-alert { + margin-right: auto; + } + + &__submit-button { + @include mobile { + margin-right: 1.2rem; + } + } + + &__fieldset { + width: 39.5rem; + + @include mobile { + width: 94%; + margin: 0.4rem 0 3.2rem; + } + + .document-dropdown { + margin: 0.4rem 0 3.2rem; + } + + .country-dropdown { + min-height: 35.2rem; + } + } + + &__fieldset-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + @include mobile { + width: 100%; + } + } + + &__fieldset-input { + width: 39.5rem; + + @include mobile { + width: 94%; + margin-top: 0.4rem; + } + } + + &__sample-container { + margin-left: 1.6rem; + + @include mobile { + margin-left: unset; + width: 94%; + } + } + + &__sample-container-external { + margin-top: 2.8rem; + } + + &__image-container { + width: fit-content; + height: fit-content; + padding: 8px; + border-radius: 4px; + background-color: $color-grey-2; + } + + &__image { + max-width: 24.5rem; + border-radius: 4px; + object-fit: contain; + + @include mobile { + max-width: calc(95vw - 16px); + } + } + + &__redirect { + width: auto !important; + } + + .text { + display: block; + } + + .btm-spacer { + margin-bottom: 1.6rem; + } +} + /** @define closing-account; weak */ .closing-account { max-width: 67.2rem; diff --git a/packages/account/src/Types/common.type.ts b/packages/account/src/Types/common.type.ts index dcf351c133a4..03c62a89811f 100644 --- a/packages/account/src/Types/common.type.ts +++ b/packages/account/src/Types/common.type.ts @@ -1,7 +1,8 @@ /** Add types that are shared between components */ import React from 'react'; +import { FormikHandlers, FormikProps, FormikValues } from 'formik'; +import { Authorize, IdentityVerificationAddDocumentResponse, ResidenceList } from '@deriv/api-types'; import { Redirect } from 'react-router-dom'; -import { Authorize, IdentityVerificationAddDocumentResponse } from '@deriv/api-types'; import { Platforms } from '@deriv/shared'; export type TToken = { @@ -122,15 +123,33 @@ export type TPOIStatus = { }; export type TPersonalDetailsForm = { - first_name: string; - last_name: string; - date_of_birth: string; -}; + warning_items?: Record; + is_virtual?: boolean; + is_mf?: boolean; + is_svg?: boolean; + is_qualified_for_idv?: boolean; + should_hide_helper_image: boolean; + is_appstore?: boolean; + editable_fields: Array; + has_real_account?: boolean; + residence_list?: ResidenceList; + is_fully_authenticated?: boolean; + account_opening_reason_list?: Record[]; + closeRealAccountSignup: () => void; + salutation_list?: Record[]; + is_rendered_for_onfido?: boolean; + should_close_tooltip?: boolean; + setShouldCloseTooltip?: (should_close_tooltip: boolean) => void; +} & FormikProps; export type TInputFieldValues = Record; export type TIDVVerificationResponse = IdentityVerificationAddDocumentResponse & { error: { message: string } }; +export type TVerificationStatus = Readonly< + Record<'none' | 'pending' | 'rejected' | 'verified' | 'expired' | 'suspected', string> +>; + export type TDocument = { id: string; text: string; @@ -143,10 +162,6 @@ export type TDocument = { }; }; -export type TVerificationStatus = Readonly< - Record<'none' | 'pending' | 'rejected' | 'verified' | 'expired' | 'suspected', string> ->; - export type TIDVFormValues = { document_type: TDocument; document_number: string; @@ -154,6 +169,14 @@ export type TIDVFormValues = { error_message?: string; }; +export type TIDVForm = { + selected_country: ResidenceList[0]; + hide_hint?: boolean; + class_name?: string; + can_skip_document_verification: boolean; +} & Partial & + FormikProps; + export type TPlatforms = typeof Platforms[keyof typeof Platforms]; export type TServerError = { diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index 3f8915174632..9e8c6e8b8f16 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -879,20 +879,6 @@ position: relative; width: 100%; - .poi-form-on-signup__fields { - .dc-autocomplete { - margin-bottom: 0; - } - } - - .account-form__poi-confirm-example { - gap: 1.6rem; - - @include mobile { - gap: 0; - } - } - &__body { width: 100%; .poi-form-on-signup { @@ -949,18 +935,6 @@ padding-top: 1.6rem; } } - - .proof-of-identity__mismatch-container { - max-width: 70.2rem; - margin-inline: auto; - - .account__scrollbars_container { - padding-top: 2.4rem; - } - .dc-themed-scrollbars { - padding-inline: 1.6rem; - } - } } &__form { diff --git a/packages/components/src/components/hint-box/hint-box.scss b/packages/components/src/components/hint-box/hint-box.scss index 50d4813001e3..d3a330bd37be 100644 --- a/packages/components/src/components/hint-box/hint-box.scss +++ b/packages/components/src/components/hint-box/hint-box.scss @@ -38,9 +38,3 @@ color: var(--text-prominent); } } - -.hint-box-layout { - display: grid; - grid-template-columns: auto 1fr; - align-items: flex-start; -} diff --git a/packages/core/src/sass/app.scss b/packages/core/src/sass/app.scss index dc80c935a95c..32a0db6d08b8 100644 --- a/packages/core/src/sass/app.scss +++ b/packages/core/src/sass/app.scss @@ -47,7 +47,7 @@ @import 'app/_common/components/cookie-banner'; @import 'app/_common/components/notification-banner'; @import 'app/_common/components/notification-promo'; -@import 'app/_common/components/account-common'; +@import 'app/_common/components/onfido-container'; @import 'app/_common/components/cfd-poa'; // Modules @import 'app/modules/page-404'; diff --git a/packages/core/src/sass/app/_common/components/account-common.scss b/packages/core/src/sass/app/_common/components/account-common.scss deleted file mode 100644 index 64b558c93565..000000000000 --- a/packages/core/src/sass/app/_common/components/account-common.scss +++ /dev/null @@ -1,873 +0,0 @@ -.onfido-container { - display: flex; - flex-direction: column; - align-items: center; - gap: 1.6rem; - - &-view_wrapper { - position: relative; - min-width: 32.8rem; - min-height: 50rem; - margin: auto; - } - - .account-form__poi-confirm-example_container { - margin-bottom: 0; - } - - .onfido-sdk-ui-Modal-inner { - border-radius: $BORDER_RADIUS * 2; - } - - @include mobile { - padding: 1.6rem; - - .onfido-sdk-ui-PageTitle-titleSpan { - font-size: 2rem; - } - } - - .onfido-sdk-ui-Camera-webcamContainer { - left: auto; - width: -webkit-fill-available; - } - - &__disabled { - opacity: 0.5; - pointer-events: none; - } - - &__info-message { - position: absolute; - top: 2.4rem; - left: 25%; - z-index: 1; - @include mobile { - top: 1.6rem; - left: 3.1rem; - } - } - - &__status-message { - background-color: var(--transparent-correct-message); - justify-content: flex-start; - transition: transform 0.35s ease, opacity 0.35s linear; - transform-origin: top; - opacity: 1; - width: 98%; - - &--exit { - transform: scale(1, 0); - opacity: 0; - } - - &_container { - position: absolute; - top: 0.4rem; - min-height: 3.4rem; - text-align: center; - z-index: 1; - width: 100%; - } - } -} - -.account { - &-form { - &__poi-confirm-example { - display: flex; - flex-direction: column; - gap: 0.8rem; - @include mobile { - height: fit-content; - } - - .account-form__fieldset { - max-width: unset; - } - - &--status-message { - margin-top: 1.6rem; - text-align: center; - width: 100%; - } - &_container { - border: 1px solid var(--general-active); - border-radius: 0.8rem; - - @include desktop { - padding: 1.6rem; - } - - @include mobile { - padding: 1.6rem 1.6rem 0; - } - } - - &_wrapper { - transition: transform 0.35s ease, opacity 0.35s linear; - transform-origin: top; - opacity: 1; - padding: 1.6rem; - max-width: 67rem; - - @include mobile { - padding-inline: 0; - } - - &--exit { - transform: scale(1, 0); - opacity: 0; - } - } - - .da-inline-note-with-icon { - margin-top: 0; - padding: 0.8rem; - display: flex; - background: var(--status-alert-background); - border-radius: $BORDER_RADIUS; - gap: 0.8rem; - line-height: 1.4rem; - } - - .account { - &__scrollbars_container--grid-layout { - grid-gap: 0; - } - &-form { - &__section-side-note { - @include mobile { - margin-top: 1.6rem; - justify-content: center; - display: flex; - } - } - &__section-content { - .account-form__fieldset { - margin-bottom: 0; - - .dc-input { - margin-bottom: 1rem; - } - - .dc-input--error, - .dc-input.dc-input--hint { - margin-bottom: 3.8rem; - } - } - } - } - } - - &--button { - padding: 0; - margin: 0; - background-color: unset; - border: unset; - - &__disabled > * { - cursor: not-allowed; - } - } - } - - &__poi-additional-information { - margin-top: 2rem; - } - - &__section { - display: grid; - - @include desktop { - grid-template-areas: 'section-side-note section-content'; - grid-template-columns: auto 1fr; - grid-gap: 1rem; - } - - align-items: center; - - @include mobile { - grid-template-areas: 'section-side-note' 'section-content'; - } - - &-side-note { - grid-area: section-side-note; - - @include mobile { - margin-top: unset; - width: 100%; - margin-bottom: 0.8rem; - } - &__example { - @include mobile { - flex-direction: column; - } - - &-image { - align-self: center; - } - } - } - - &-content { - grid-area: section-content; - - @include mobile { - width: 100%; - } - } - @include desktop { - &--reversed { - grid-template-areas: 'section-content section-side-note'; - grid-template-columns: 1fr auto; - } - } - } - - &__footer { - height: 8.0rem; - position: absolute; - bottom: 0; - left: 0; - display: flex; - width: 100%; - padding: 1.6rem 2.4rem; - align-items: center; - justify-content: flex-end; - z-index: 4; - border-radius: 0 $BORDER_RADIUS $BORDER_RADIUS 0; - border-top: 1px solid var(--general-section-1); - background-color: var(--general-main-1); - - &--reset { - padding: 2.4rem 0; - position: relative; - margin-top: 2rem; - } - - .back-btn { - margin-right: 8px; - - .back-btn-icon { - margin-right: 0.8rem; - } - - span { - display: inline-flex; - align-items: center; - } - } - - &-note { - color: var(--text-prominent); - font-size: var(--text-size-xxs); - line-height: 1.5; - text-align: right; - min-width: 27.6rem; - max-width: 36.6rem; - height: 3.6rem; - - @include mobile { - width: auto; - text-align: center; - align-self: center; - - &--dashboard { - margin: 0 1.6rem; - text-align: left; - } - } - } - - &-btn { - height: 4rem; - margin: 0 0 0 1.6rem; - - &-wrapper { - align-items: normal; - display: flex; - flex-direction: row; - - @include mobile { - flex-direction: column; - } - } - - &-dashboard { - .dc-btn__text { - color: var(--text-prominent); - } - } - - &-dashboard:hover { - background-color: unset !important; - } - - @include desktop { - margin-left: 1.6rem; - - &-wrapper--dashboard { - display: flex; - justify-content: space-between; - } - } - - @include mobile { - margin: 0 2.4rem; - - &--has-bottom-margin { - margin-bottom: 1.6rem; - } - } - } - - &--dashboard { - width: 68.5rem; - margin-left: 6rem; - - @include mobile { - width: 100vw; - margin-left: unset; - - & .account-form__footer-btn { - min-width: 35%; - - &-wrapper { - flex-direction: row; - } - } - - & > div { - display: flex; - justify-content: space-around; - } - } - } - - &-poa { - margin-left: unset; - } - - &-all-fields-required { - padding-bottom: 2.4rem; - padding-top: 0.8rem; - } - - @include mobile { - flex-direction: column; - height: auto; - align-items: initial; - border-top: 2px solid var(--general-section-1); - } - } - } - &__scrollbars_container { - height: 100%; - padding-top: 2.4rem; - padding-bottom: 6.4rem; - - &-wrapper { - overflow-x: hidden; - overflow-y: auto; - } - - &--grid-layout { - display: grid; - grid-gap: 4px; - - .dc-dropdown-container { - .dc-dropdown__display-text, - .dc-list__item-text { - text-transform: unset; - } - } - - @include mobile { - padding: 0 1.6rem; - overflow-x: hidden; - overflow-y: auto; - grid-template-rows: auto auto 1fr; - - & .account-form__header { - &:first-child { - padding-top: 2.4rem; - margin-bottom: 3.2rem; - } - } - } - } - - @include desktop { - padding-left: 16px; - padding-right: 16px; - } - - @include mobile { - height: unset; - padding-top: unset; - padding-bottom: unset; - } - } -} - -.idv-layout { - width: 100%; - .poi-form-on-signup__fields { - .proof-of-identity { - &__container { - width: 100%; - padding: 0; - } - - &__fieldset { - margin-bottom: 2rem; - &-input { - @include desktop { - margin-bottom: unset; - } - } - } - } - } - .proof-of-identity__inner-container { - @include desktop { - &--incl-image { - display: grid; - align-items: center; - column-gap: 1.5rem; - grid-template-columns: auto 0.5fr; - } - } - } - - .additional-field { - margin-top: 1.6rem; - margin-bottom: 2rem; - - @include mobile { - margin-top: 1rem; - } - } -} - -.account-form_poa { - .account { - &-form { - &__section { - align-items: unset; - - &-side-note { - width: 26rem; - } - - &-content { - width: 40rem; - - @include mobile { - width: 100%; - } - } - } - - &__fieldset { - max-width: unset; - margin-top: 1.6rem; - display: flex; - flex-direction: column; - gap: 3.2rem; - } - } - - &__scrollbars_container { - padding-top: 0; - padding-left: 0; - padding-bottom: 0; - } - } - - .files-description { - &__title { - margin-bottom: 1.6rem; - } - - li { - list-style-type: initial; - margin-left: 1.6rem; - } - } - - &-submit-error { - justify-content: left; - } -} - -.file-uploader { - &__container { - @include desktop { - margin: 1.6rem 0; - padding: 1.6rem 2.4rem; - border-radius: $BORDER_RADIUS * 2; - border: 1px solid var(--border-normal); - } - } - - &__file { - &-dropzone-wrapper { - flex: 1; - height: 13.2rem; - position: relative; - - .dc-file-dropzone { - border-radius: $BORDER_RADIUS * 2; - - &__message { - max-width: unset; - - &-subtitle { - font-size: 1.4rem; - font-weight: bold; - display: flex; - flex-direction: column; - gap: 0.8rem; - margin-top: 1.6rem; - } - } - - @include mobile { - border: 1px dashed var(--icon-grey-background); - } - } - } - - &-title { - margin: 2.4rem 0 1.6rem; - } - - &-supported-formats { - display: flex; - justify-content: space-between; - margin: 1.6rem 0 2.4rem; - - @include mobile { - margin-bottom: 1.6rem; - } - - span { - @include mobile { - max-width: 14rem; - } - } - } - - @include mobile { - flex: unset; - margin-bottom: 2.4rem; - height: 15rem; - } - } - - &__remove-btn { - position: absolute; - width: 1.6rem; - height: 1.6rem; - top: 0.8rem; - right: 0.8rem; - cursor: pointer; - transition: transform 0.25s linear; - - &:hover { - transform: scale(1.25, 1.25); - } - - &--error { - circle { - fill: var(--status-danger); - } - } - - &-container { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - } -} - -.proof-of-identity { - width: 100%; - height: 100%; - - &__main-container { - max-width: 68.2rem; - } - - .min-height { - min-height: 50vh; - - @include mobile { - min-height: unset; - } - } - - &__container { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - - @include mobile { - min-height: unset; - overflow-y: scroll; - justify-content: center; - padding: 0 1.6rem; - } - - &--reset { - align-items: normal; - - @include mobile { - overflow-y: unset; - } - - .proof-of-identity__submit-button { - @include mobile { - margin: unset; - width: 100%; - } - } - - .form-body { - @include desktop { - z-index: 5; - } - } - - .account__scrollbars_container--grid-layout { - padding: 0; - } - } - - .icon { - width: 128px; - height: 128px; - margin-top: 5.2rem; - margin-bottom: 2.6rem; - - @include mobile { - width: 72px; - height: 72px; - margin-top: 2.6rem; - } - } - - .external-dropdown { - .dc-dropdown-list { - z-index: 5; - } - } - } - - .continue-trade { - margin-top: unset !important; - } - - &__redirection { - .dc-btn { - margin-top: 3.2rem; - height: 4rem; - - @include mobile { - margin: 1.6rem 0; - padding: 1.6rem; - width: 100%; - } - } - } - - &__country-text { - margin-bottom: 1.6rem; - } - - &__text { - @include mobile { - width: 94%; - text-align: center; - } - } - - &__dropdown-container { - margin-top: 1.6rem; - } - - &__inner-container { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - width: 100%; - - @include mobile { - flex-direction: column; - } - } - - &__header { - margin: 5.4rem 0 1.6rem; - - @include mobile { - margin: 2.4rem 0 0.8rem; - } - } - - &__footer.external-footer { - padding: unset; - position: unset; - top: unset; - bottom: unset; - border: none; - height: fit-content; - z-index: 4; - } - - &__footer { - @include mobile { - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-end; - position: fixed; - bottom: 0; - } - } - - &__footer-alert { - margin-right: auto; - } - - &__submit-button { - @include mobile { - margin-right: 1.2rem; - } - } - - &__fieldset { - width: 39.5rem; - - @include mobile { - width: 94%; - margin: 0.4rem 0 3.2rem; - } - - .country-dropdown { - min-height: 35.2rem; - } - } - - &__fieldset-container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - @include mobile { - width: 100%; - } - } - - &__fieldset-input { - width: 39.5rem; - - @include mobile { - width: 94%; - margin-top: 0.4rem; - } - } - - &__sample-container { - margin-left: 1.6rem; - - @include mobile { - margin-left: unset; - width: 94%; - } - } - - &__sample-container-external { - margin-top: 2.8rem; - } - - &__image-container { - width: fit-content; - height: fit-content; - padding: 8px; - border-radius: 4px; - background-color: $color-grey-2; - } - - &__image { - max-width: 24.5rem; - border-radius: 4px; - object-fit: contain; - - @include mobile { - max-width: calc(95vw - 16px); - } - } - - &__redirect { - width: auto !important; - } - - .text { - display: block; - } - - .btm-spacer { - margin-bottom: 1.6rem; - } - - &__failed { - &-message { - margin: 1.6rem auto 3.2rem; - border-radius: $BORDER_RADIUS * 2; - } - } - - &__mismatch-container { - .idv-form { - margin-bottom: 1.6rem; - } - - .proof-of-identity__submit-button { - @include desktop { - margin-top: 3.2rem; - margin-left: auto; - } - - @include mobile { - margin-inline: 2.4rem; - } - } - - .account__scrollbars_container { - @include desktop { - padding-top: 0; - } - } - - .account__scrollbars_container--grid-layout { - @include mobile { - margin-top: 2.4rem; - } - } - } -} - -@include mobile { - .formik__confirmation-checkbox { - margin-bottom: 1.6rem; - } -} diff --git a/packages/core/src/sass/app/_common/components/onfido-container.scss b/packages/core/src/sass/app/_common/components/onfido-container.scss new file mode 100644 index 000000000000..7c549ba2daf7 --- /dev/null +++ b/packages/core/src/sass/app/_common/components/onfido-container.scss @@ -0,0 +1,441 @@ +.onfido-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.6rem; + + &-view_wrapper { + position: relative; + min-width: 32.8rem; + min-height: 50rem; + margin: auto; + } + + .account-form__poi-confirm-example_container { + margin-bottom: 0; + } + + .onfido-sdk-ui-Modal-inner { + border-radius: 0.8rem; + } + + @include mobile() { + padding: 1.6rem; + + .onfido-sdk-ui-PageTitle-titleSpan { + font-size: 2rem; + } + } + + .onfido-sdk-ui-Camera-webcamContainer { + left: auto; + width: -webkit-fill-available; + } + + &__disabled { + opacity: 0.5; + pointer-events: none; + } + + &__info-message { + position: absolute; + top: 2.4rem; + left: 25%; + z-index: 1; + @include mobile { + top: 1.6rem; + left: 3.1rem; + } + } + + &__status-message { + background-color: var(--transparent-correct-message); + justify-content: flex-start; + transition: transform 0.35s ease, opacity 0.35s linear; + transform-origin: top; + opacity: 1; + width: 98%; + + &--exit { + transform: scale(1, 0); + opacity: 0; + } + + &_container { + position: absolute; + top: 0.4rem; + min-height: 3.4rem; + text-align: center; + z-index: 1; + width: 100%; + } + } +} + +.account-form { + &__poi-confirm-example { + display: flex; + flex-direction: column; + gap: 0.8rem; + + .account-form__fieldset { + max-width: unset; + } + + &--status-message { + margin-top: 1.6rem; + text-align: center; + width: 100%; + } + &_container { + border: 1px solid var(--general-active); + border-radius: 0.8rem; + + @include desktop() { + padding: 1.6rem; + } + + @include mobile() { + margin-bottom: 7rem; + padding: 1.6rem 1.6rem 0; + } + + .account__scrollbars_container { + padding: 0; + + &--grid-layout { + height: auto !important; + + @include mobile() { + padding: 0 1.6rem; + } + } + + .account-form__section { + align-items: center; + justify-content: space-between; + margin-top: 0.8rem; + + @include mobile() { + margin-bottom: 0; + } + } + } + } + + &_wrapper { + transition: transform 0.35s ease, opacity 0.35s linear; + transform-origin: top; + opacity: 1; + padding: 1.6rem; + max-width: 67rem; + + @include mobile() { + padding-inline: 0; + } + + &--exit { + transform: scale(1, 0); + opacity: 0; + } + } + + .da-inline-note-with-icon { + margin-top: 0; + padding: 0.8rem; + display: flex; + background: var(--status-alert-background); + border-radius: $BORDER_RADIUS; + gap: 0.8rem; + line-height: 1.4rem; + } + + .account__scrollbars_container--grid-layout { + grid-gap: 0; + } + + .account-form__section-side-note { + @include mobile { + margin-top: 1.6rem; + justify-content: center; + display: flex; + } + } + + .account-form__section-content { + .account-form__fieldset { + margin-bottom: 0; + + .dc-input { + margin-bottom: 1rem; + } + + .dc-input--error, + .dc-input.dc-input--hint { + margin-bottom: 3.8rem; + } + } + } + + &--button { + padding: 0; + margin: 0; + background-color: unset; + border: unset; + + &__disabled > * { + cursor: not-allowed; + } + } + } + + &__poi-additional-information { + margin-top: 2rem; + } + + &__section { + display: grid; + + @include desktop() { + grid-template-areas: 'section-side-note section-content'; + grid-template-columns: auto 1fr; + grid-gap: 1rem; + } + align-items: center; + @include mobile { + grid-template-areas: 'section-side-note' 'section-content'; + } + + &-side-note { + grid-area: section-side-note; + + @include mobile { + margin-top: unset; + width: 100%; + margin-bottom: 0.8rem; + } + + &__example { + @include mobile { + flex-direction: column; + } + + &-image { + align-self: center; + } + } + } + + &-content { + grid-area: section-content; + + @include mobile { + width: 100%; + } + } + + @include desktop { + &--reversed { + grid-template-areas: 'section-content section-side-note'; + grid-template-columns: 1fr auto; + } + } + } +} + +.idv-layout { + width: 100%; + .poi-form-on-signup__fields { + .proof-of-identity__container { + width: 100%; + } + .proof-of-identity__fieldset { + @include desktop { + margin-bottom: unset; + } + @include mobile { + margin-bottom: 2rem; + } + &-input { + @include desktop { + margin-bottom: unset; + } + } + } + } + .proof-of-identity__inner-container { + @include desktop { + &--incl-image { + display: grid; + align-items: center; + column-gap: 1.5rem; + grid-template-columns: auto 0.5fr; + } + } + } + + .additional-field { + margin-top: 1.6rem; + + @include mobile { + margin-top: 1rem; + } + } +} + +.account-form_poa { + .account { + &-form { + &__section { + align-items: unset; + + &-side-note { + width: 26rem; + } + + &-content { + width: 40rem; + + @include mobile { + width: 100%; + } + } + } + + &__fieldset { + max-width: unset; + margin-top: 1.6rem; + display: flex; + flex-direction: column; + gap: 3.2rem; + } + } + + &__scrollbars_container { + padding-top: 0; + padding-left: 0; + padding-bottom: 0; + + &--grid-layout { + @include mobile { + padding-top: 2.4rem; + } + } + } + } + + .files-description { + &__title { + margin-bottom: 1.6rem; + } + + li { + list-style-type: initial; + margin-left: 1.6rem; + } + } + + &-submit-error { + justify-content: left; + } +} + +.file-uploader { + &__container { + @include desktop { + margin: 1.6rem 0; + padding: 1.6rem 2.4rem; + border-radius: $BORDER_RADIUS * 2; + border: 1px solid var(--border-normal); + } + } + + &__file { + &-dropzone-wrapper { + flex: 1; + height: 13.2rem; + position: relative; + + .dc-file-dropzone { + border-radius: $BORDER_RADIUS * 2; + + &__message { + max-width: unset; + + &-subtitle { + font-size: 1.4rem; + font-weight: bold; + display: flex; + flex-direction: column; + gap: 0.8rem; + margin-top: 1.6rem; + } + } + + @include mobile { + border: 1px dashed var(--icon-grey-background); + } + } + } + + &-title { + margin: 2.4rem 0 1.6rem; + } + + &-supported-formats { + display: flex; + justify-content: space-between; + margin: 1.6rem 0 2.4rem; + + @include mobile { + margin-bottom: 1.6rem; + } + + span { + @include mobile { + max-width: 14rem; + } + } + } + + @include mobile { + flex: unset; + margin-bottom: 2.4rem; + height: 15rem; + } + } + + &__remove-btn { + position: absolute; + width: 1.6rem; + height: 1.6rem; + top: 0.8rem; + right: 0.8rem; + cursor: pointer; + transition: transform 0.25s linear; + + &:hover { + transform: scale(1.25, 1.25); + } + + &--error { + circle { + fill: var(--status-danger); + } + } + + &-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + } +} + +@include mobile { + .formik__confirmation-checkbox { + margin-bottom: 1.6rem; + } +} diff --git a/packages/core/src/sass/real-account-signup.scss b/packages/core/src/sass/real-account-signup.scss index 4c75c668ee88..7f2d972bf8ff 100644 --- a/packages/core/src/sass/real-account-signup.scss +++ b/packages/core/src/sass/real-account-signup.scss @@ -198,7 +198,6 @@ } .proof-of-identity { - &__footer { padding: 1.6rem 2.4rem; justify-content: end; @@ -206,7 +205,6 @@ border-top: 1px solid var(--general-section-1); @include mobile { width: 100%; - position:relative; } } @@ -218,8 +216,9 @@ } } .account-form__footer { + @include desktop { position: relative; - + } } } @@ -228,8 +227,7 @@ height: 100%; @include mobile { width: 100%; - display: grid; - grid-template-rows: 1fr auto; - padding: unset; + display: flex; + flex-direction: column; } } diff --git a/packages/shared/src/utils/constants/error.ts b/packages/shared/src/utils/constants/error.ts index 7058da23d4e0..53fd468f5770 100644 --- a/packages/shared/src/utils/constants/error.ts +++ b/packages/shared/src/utils/constants/error.ts @@ -5,12 +5,3 @@ export const getDefaultError = () => ({ description: localize('Our servers hit a bump. Let’s refresh to move on.'), cta_label: localize('Refresh'), }); - -export const STATUS_CODES = Object.freeze({ - NONE: 'none', - PENDING: 'pending', - REJECTED: 'rejected', - VERIFIED: 'verified', - EXPIRED: 'expired', - SUSPECTED: 'suspected', -}); diff --git a/packages/shared/src/utils/constants/idv-failure-codes.ts b/packages/shared/src/utils/constants/idv-failure-codes.ts deleted file mode 100644 index 5df21e24c9d5..000000000000 --- a/packages/shared/src/utils/constants/idv-failure-codes.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const idv_error_statuses = Object.freeze({ - poi_name_dob_mismatch: 'POI_NAME_DOB_MISMATCH', - poi_dob_mismatch: 'POI_DOB_MISMATCH', - poi_name_mismatch: 'POI_NAME_MISMATCH', - poi_expired: 'POI_EXPIRED', - poi_failed: 'POI_FAILED', -}); diff --git a/packages/shared/src/utils/constants/index.ts b/packages/shared/src/utils/constants/index.ts index 101af38867a5..d9817697f166 100644 --- a/packages/shared/src/utils/constants/index.ts +++ b/packages/shared/src/utils/constants/index.ts @@ -5,4 +5,3 @@ export * from './default-options'; export * from './jurisdictions-config'; export * from './signup_fields'; export * from './error'; -export * from './idv-failure-codes'; diff --git a/packages/shared/src/utils/helpers/format-response.ts b/packages/shared/src/utils/helpers/format-response.ts index 7149b8d706f5..9f46174f556e 100644 --- a/packages/shared/src/utils/helpers/format-response.ts +++ b/packages/shared/src/utils/helpers/format-response.ts @@ -1,9 +1,8 @@ import { GetSettings, ResidenceList } from '@deriv/api-types'; -import { STATUS_CODES, getUnsupportedContracts } from '../constants'; +import { getUnsupportedContracts } from '../constants'; import { getSymbolDisplayName, TActiveSymbols } from './active-symbols'; import { getMarketInformation } from './market-underlying'; import { TContractInfo } from '../contract'; -import { idv_error_statuses } from '../constants/idv-failure-codes'; type TIsUnSupportedContract = { contract_type?: string; @@ -43,37 +42,6 @@ export const formatPortfolioPosition = ( }; }; -export type TIDVErrorStatus = typeof idv_error_statuses[keyof typeof idv_error_statuses]; - -//formatIDVError is parsing errors messages from BE (strings) and returns error codes for using it on FE -export const formatIDVError = (errors: string[], status_code: string) => { - /** - * Check required incase of DIEL client - */ - if (errors.length === 0 && (status_code === STATUS_CODES.NONE || status_code === STATUS_CODES.VERIFIED)) - return null; - const error_keys: Record = { - name: 'POI_NAME_MISMATCH', - birth: 'POI_DOB_MISMATCH', - rejected: 'POI_FAILED', - }; - if (status_code === STATUS_CODES.EXPIRED) { - return 'POI_EXPIRED'; - } - const status: TIDVErrorStatus[] = []; - errors.forEach(error => { - const error_regex = RegExp(/(name|birth|rejected)/i).exec(error); - if (error_regex) { - status.push(error_keys[error_regex[0].toLowerCase()]); - } - }); - return status.includes(error_keys.name) && - status.includes(error_keys.birth) && - !status.includes(error_keys.rejected) - ? 'POI_NAME_DOB_MISMATCH' - : status[0] ?? 'POI_FAILED'; -}; - export const isVerificationServiceSupported = ( residence_list: ResidenceList, account_settings: GetSettings,