diff --git a/packages/account/jest.config.js b/packages/account/jest.config.js index 1bb16f90d1ff..11898a71490e 100644 --- a/packages/account/jest.config.js +++ b/packages/account/jest.config.js @@ -9,6 +9,7 @@ module.exports = { '^Components/(.*)$': '/src/Components/$1', '^Constants/(.*)$': '/src/Constants/$1', '^Configs/(.*)$': '/src/Configs/$1', + '^Containers/(.*)$': '/src/Containers/$1', '^Duplicated/(.*)$': '/src/Duplicated/$1', '^Helpers/(.*)$': '/src/Helpers/$1', '^Services/(.*)$': '/src/Services/$1', diff --git a/packages/account/src/Components/forms/idv-form.tsx b/packages/account/src/Components/forms/idv-form.tsx new file mode 100644 index 000000000000..bff0be253e6b --- /dev/null +++ b/packages/account/src/Components/forms/idv-form.tsx @@ -0,0 +1,292 @@ +import React from 'react'; +import { Form, Field, FormikValues, FormikProps, FormikHandlers } from 'formik'; +import { localize, Localize } from '@deriv/translations'; +import { formatInput, IDV_NOT_APPLICABLE_OPTION } from '@deriv/shared'; +import { Autocomplete, DesktopWrapper, Input, MobileWrapper, SelectNative, Text } from '@deriv/components'; +import { getDocumentData, preventEmptyClipboardPaste } from 'Helpers/utils'; +import classNames from 'classnames'; +import { GetSettings, ResidenceList } from '@deriv/api-types'; + +type TDocumentList = Array<{ + id: string; + text: string; + value?: string; + sample_image?: string; + example_format?: string; + additional?: any; +}>; + +type TFormProps = { + document_list: TDocumentList; + document_number: string; + document_additional: string; +}; + +type TIDVForm = { + account_settings: GetSettings; + residence: string; + residence_list: ResidenceList; + hide_hint?: boolean; +} & FormikHandlers & + FormikProps; + +const IDVForm = ({ errors, touched, values, handleBlur, handleChange, setFieldValue, ...props }: TIDVForm) => { + const [document_list, setDocumentList] = React.useState([]); + const [document_image, setDocumentImage] = React.useState(null); + const [is_doc_selected, setDocSelected] = React.useState(false); + + const { account_settings, residence, residence_list } = props; + const citizen = account_settings?.citizen || residence; + + const { documents_supported: document_data, has_visual_sample }: any = + residence_list.find(residence_data => residence_data.value === citizen)?.identity.services?.idv || {}; + + React.useEffect(() => { + const document_types = Object.keys(document_data); + if (document_types.length === 0) return; + const filtered_documents = ['gh', 'ng'].includes(citizen) + ? document_types.filter(d => d !== 'voter_id') + : document_types; + + setDocumentList([ + ...filtered_documents.map(key => { + const { display_name, format } = document_data[key]; + const { new_display_name, example_format, sample_image } = getDocumentData(citizen, key); + const needs_additional_document = !!document_data[key].additional; + + if (needs_additional_document) { + return { + id: key, + text: new_display_name || display_name, + additional: { + display_name: document_data[key].additional?.display_name, + format: document_data[key].additional?.format, + }, + value: format, + sample_image, + example_format, + }; + } + return { + id: key, + text: new_display_name || display_name, + value: format, + sample_image, + example_format, + }; + }), + IDV_NOT_APPLICABLE_OPTION, + ]); + }, [document_data]); + + const resetDocumentItemSelected = () => { + setFieldValue( + 'document_type', + { + id: '', + text: '', + value: '', + example_format: '', + sample_image: '', + }, + true + ); + }; + + const getExampleFormat = (example_format: string) => { + return example_format ? localize('Example: ') + example_format : ''; + }; + const getDocument = (text: string) => { + return document_list.find(d => d.text === text); + }; + + const onKeyUp = (e: { target: HTMLInputElement }, document_name: string) => { + 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('-') + ? formatInput(example_format, current_input || e.target.value, '-') + : e.target.value; + setFieldValue(document_name, current_input, true); + }; + + return ( +
+
+
+
+
+
+
+ + {({ field }: FormikValues) => ( + + +
+ { + handleBlur(e); + if (!getDocument(e.target.value)) { + resetDocumentItemSelected(); + } + }} + onChange={handleChange} + onItemSelection={item => { + if (item.text === 'No results found' || !item.text) { + setDocSelected(false); + resetDocumentItemSelected(); + } else { + setFieldValue('document_type', item, true); + setDocSelected(true); + if (has_visual_sample) { + setDocumentImage(item.sample_image || ''); + } + } + }} + required + /> +
+
+ + { + handleChange(e); + const selected_document = getDocument(e.target.value); + if (selected_document) { + setDocSelected(true); + setFieldValue('document_type', selected_document, true); + if (has_visual_sample) { + setDocumentImage( + // eslint-disable-next-line max-len + selected_document.sample_image + ); + } + } + }} + use_text={true} + required + /> + +
+ )} +
+
+
+ + {({ field }: FormikValues) => ( + + + onKeyUp(e, 'document_number') + } + required + /> + {values.document_type.additional?.display_name && ( + + onKeyUp(e, 'document_additional') + } + required + /> + )} + + )} + +
+
+ {document_image && ( +
+ + {localize('Sample:')} + +
+ document sample image +
+
+ )} + {is_doc_selected && !props.hide_hint && ( + + + + )} +
+
+
+
+
+ ); +}; + +export default IDVForm; diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx new file mode 100644 index 000000000000..6641eaad9cee --- /dev/null +++ b/packages/account/src/Components/forms/personal-details-form.jsx @@ -0,0 +1,593 @@ +import React from 'react'; +import { Field } from 'formik'; +import classNames from 'classnames'; +import { + Autocomplete, + Checkbox, + Dropdown, + DesktopWrapper, + MobileWrapper, + DateOfBirthPicker, + Input, + Popover, + RadioGroup, + SelectNative, + Text, +} from '@deriv/components'; +import { getLegalEntityName, isDesktop, isMobile, routes, toMoment, validPhone } from '@deriv/shared'; +import { Localize, localize } from '@deriv/translations'; +import FormSubHeader from 'Components/form-sub-header'; +import PoiNameDobExample from 'Assets/ic-poi-name-dob-example.svg'; +import InlineNoteWithIcon from 'Components/inline-note-with-icon'; +import FormBodySection from 'Components/form-body-section'; +import { Link } from 'react-router-dom'; +import { getEmploymentStatusList } from 'Sections/Assessment/FinancialAssessment/financial-information-list'; + +const DateOfBirthField = props => ( + + {({ field: { value }, form: { setFieldValue, errors, touched, setTouched } }) => ( + + setTouched({ + ...touched, + date_of_birth: true, + }) + } + onChange={({ target }) => + setFieldValue( + 'date_of_birth', + target?.value ? toMoment(target.value).format('YYYY-MM-DD') : '', + true + ) + } + value={value} + portal_id={props.portal_id} + {...props} + /> + )} + +); + +const FormInputField = ({ name, optional = false, warn, ...props }) => ( + + {({ field, form: { errors, touched } }) => ( + + )} + +); + +const PersonalDetailsForm = ({ + errors, + touched, + values, + setFieldValue, + handleChange, + handleBlur, + warning_items, + setFieldTouched, + ...props +}) => { + const { + is_virtual, + is_mf, + is_svg, + is_qualified_for_idv, + is_appstore, + disabled_items, + has_real_account, + residence_list, + is_fully_authenticated, + account_opening_reason_list, + closeRealAccountSignup, + salutation_list, + } = 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); + + const getLastNameLabel = () => { + if (is_appstore) return localize('Family name*'); + return is_svg || is_mf ? localize('Last name*') : localize('Last name'); + }; + + const getFieldHint = field_name => { + return ( + + ); + }; + + const name_dob_clarification_message = ( + ]} + /> + ); + + return ( +
+ + }> +
+ {'salutation' in values && ( +
+ + {is_virtual ? ( + localize( + 'Please remember that it is your responsibility to keep your answers accurate and up to date. You can update your personal details at any time in your account settings.' + ) + ) : ( + , + ]} + /> + )} + +
+ )} + {!is_qualified_for_idv && !is_appstore && ( + + )} + {'salutation' in values && ( + { + e.persist(); + setFieldValue('salutation', e.target.value); + }} + required + > + {salutation_list.map(item => ( + + ))} + + )} + {'first_name' in values && ( + + )} + {'last_name' in values && ( + + )} + {!is_appstore && !is_qualified_for_idv && } + {'date_of_birth' in values && ( + + )} + {'place_of_birth' in values && ( + + {({ field }) => ( + + + + setFieldValue('place_of_birth', value ? text : '', true) + } + required + data-testid='place_of_birth' + /> + + + { + handleChange(e); + setFieldValue('place_of_birth', e.target.value, true); + }} + {...field} + list_portal_id='modal_root' + required + should_hide_disabled_options={false} + data_testid='place_of_birth_mobile' + /> + + + )} + + )} + {'citizen' in values && ( + + {({ field }) => ( + + + + setFieldValue('citizen', value ? text : '', true) + } + list_portal_id='modal_root' + required + data-testid='citizenship' + /> + + + { + handleChange(e); + setFieldValue('citizen', e.target.value, true); + }} + {...field} + required + should_hide_disabled_options={false} + data_testid='citizenship_mobile' + /> + + + )} + + )} + {'phone' in values && ( + = 9 && + values?.phone?.length <= 35) + } + maxLength={50} + data-testid='phone' + /> + )} + {('tax_residence' in values || 'tax_identification_number' in values) && ( + + + {'tax_residence' in values && ( + + {({ field }) => ( +
+ + + setFieldValue('tax_residence', value ? text : '', true) + } + list_portal_id='modal_root' + data-testid='tax_residence' + disabled={disabled_items.includes('tax_residence')} + /> + + + { + handleChange(e); + setFieldValue('tax_residence', e.target.value, true); + }} + {...field} + required + data_testid='tax_residence_mobile' + disabled={disabled_items.includes('tax_residence')} + /> + +
{ + setIsTaxResidencePopoverOpen(true); + setIsTinPopoverOpen(false); + e.stopPropagation(); + }} + > + +
+
+ )} +
+ )} + {'tax_identification_number' in values && ( +
+ +
{ + setIsTaxResidencePopoverOpen(false); + setIsTinPopoverOpen(true); + e.stopPropagation(); + }} + > + here to learn more." + } + components={[ + , + ]} + /> + } + zIndex={9998} + disable_message_icon + /> +
+
+ )} + {warning_items?.tax_identification_number && ( +
+ )} + {'employment_status' in values && ( +
+ + + + + { + setFieldTouched('employment_status', true); + handleChange(e); + }} + disabled={disabled_items.includes('employment_status')} + /> + +
+ )} + {'tax_identification_confirm' in values && ( + + setFieldValue( + 'tax_identification_confirm', + !values.tax_identification_confirm, + true + ) + } + value={values.tax_identification_confirm} + label={localize( + 'I hereby confirm that the tax information I provided is true and complete. I will also inform {{legal_entity_name}} about any changes to this information.', + { + legal_entity_name: getLegalEntityName('maltainvest'), + } + )} + renderlabel={title => ( + + {title} + + )} + withTabIndex={0} + data-testid='tax_identification_confirm' + /> + )} + + )} + {'account_opening_reason' in values && ( + + + + {({ field }) => ( + + + + + + { + handleChange(e); + setFieldValue('account_opening_reason', e.target.value, true); + }} + {...field} + required + data_testid='account_opening_reason_mobile' + disabled={disabled_items.includes('account_opening_reason')} + /> + + + )} + + + )} +
+
+
+ ); +}; + +export default PersonalDetailsForm; diff --git a/packages/account/src/Components/personal-details/__tests__/personal-details.spec.js b/packages/account/src/Components/personal-details/__tests__/personal-details.spec.js index e14d980ccf9a..85f672a5b70b 100644 --- a/packages/account/src/Components/personal-details/__tests__/personal-details.spec.js +++ b/packages/account/src/Components/personal-details/__tests__/personal-details.spec.js @@ -229,6 +229,7 @@ describe('', () => { getCurrentStep: jest.fn(() => 1), onSave: jest.fn(), onCancel: jest.fn(), + account_settings: {}, }; beforeAll(() => (ReactDOM.createPortal = jest.fn(component => component))); @@ -253,6 +254,7 @@ describe('', () => { expect(screen.getByText(fake_alert_messaget)).toBeInTheDocument(); }); + it('should not show fake_alert_message when is_appstore is false ', () => { renderwithRouter( @@ -298,6 +300,7 @@ describe('', () => { }) ).toBeInTheDocument(); }); + it('should show Name label when salutation is not passed', () => { const newprops = { ...props, value: {} }; renderwithRouter(); diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 3a13c4b9e5dc..217bbd5c6f14 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -1,74 +1,21 @@ -import { Formik, Field } from 'formik'; +import { Formik, Field, Form } from 'formik'; import React from 'react'; import { Modal, - Autocomplete, AutoHeightWrapper, - Checkbox, - Dropdown, - DesktopWrapper, - MobileWrapper, - DateOfBirthPicker, Div100vhContainer, FormSubmitButton, - Input, - Popover, - RadioGroup, - SelectNative, ThemedScrollbars, Text, } from '@deriv/components'; -import { Link } from 'react-router-dom'; import { localize, Localize } from '@deriv/translations'; -import { getLegalEntityName, isDesktop, isMobile, routes, toMoment, PlatformContext, validPhone } from '@deriv/shared'; -import { getEmploymentStatusList } from 'Sections/Assessment/FinancialAssessment/financial-information-list'; +import { isDesktop, isMobile, PlatformContext, IDV_NOT_APPLICABLE_OPTION } from '@deriv/shared'; import { splitValidationResultTypes } from '../real-account-signup/helpers/utils'; import FormSubHeader from '../form-sub-header'; import classNames from 'classnames'; - -const DateOfBirthField = props => ( - - {({ field: { value }, form: { setFieldValue, errors, touched, setTouched } }) => ( - - setTouched({ - ...touched, - date_of_birth: true, - }) - } - onChange={({ target }) => - setFieldValue( - 'date_of_birth', - target?.value ? toMoment(target.value).format('YYYY-MM-DD') : '', - true - ) - } - value={value} - portal_id={props.portal_id} - {...props} - /> - )} - -); - -const FormInputField = ({ name, optional = false, warn, ...props }) => ( - - {({ field, form: { errors, touched } }) => ( - - )} - -); +import { shouldShowIdentityInformation, documentAdditionalError, getRegex } from 'Helpers/utils'; +import IDVForm from '../forms/idv-form'; +import PersonalDetailsForm from '../forms/personal-details-form'; const PersonalDetails = ({ getCurrentStep, @@ -92,6 +39,7 @@ const PersonalDetails = ({ has_real_account, ...props }) => { + const { account_status, account_settings, residence, real_account_signup_target } = props; const { is_appstore } = React.useContext(PlatformContext); const [is_tax_residence_popover_open, setIsTaxResidencePopoverOpen] = React.useState(false); const [is_tin_popover_open, setIsTinPopoverOpen] = React.useState(false); @@ -117,11 +65,57 @@ const PersonalDetails = ({ onCancel(current_step, goToPreviousStep); }; + const is_qualified_for_idv = shouldShowIdentityInformation({ + account_status, + account_settings, + residence, + residence_list, + real_account_signup_target, + }); + + // console.log('is_qualified_for_idv: ', is_qualified_for_idv); + + const validateIDV = values => { + const errors = {}; + const { document_type, document_number, document_additional } = values; + if (document_type.id === IDV_NOT_APPLICABLE_OPTION.id) return errors; + const needs_additional_document = !!document_type.additional; + const is_document_number_invalid = document_number === document_type.example_format; + if (!document_type || !document_type.text) { + errors.document_type = localize('Please select a document type.'); + } + if (needs_additional_document) { + const error_message = documentAdditionalError(document_additional, document_type.additional?.format); + if (error_message) + errors.document_additional = + localize(error_message) + getExampleFormat(document_type.additional?.example_format); + } + + if (!document_number) { + errors.document_number = + localize('Please enter your document number. ') + getExampleFormat(document_type.example_format); + } else if (is_document_number_invalid) { + errors.document_number = localize('Please enter a valid ID number.'); + } else { + const format_regex = getRegex(document_type.value); + if (!format_regex.test(document_number)) { + errors.document_number = + localize('Please enter the correct format. ') + getExampleFormat(document_type.example_format); + } + } + return errors; + }; + const handleValidate = values => { + let idv_error = {}; + if (is_qualified_for_idv) { + idv_error = validateIDV(values); + } const { errors, warnings } = splitValidationResultTypes(validate(values)); + const error_data = { ...idv_error, ...errors }; setWarningItems(warnings); - checkSubmitStatus(errors); - return errors; + checkSubmitStatus(error_data); + return error_data; }; const closeTooltipOnScroll = () => { @@ -141,26 +135,15 @@ const PersonalDetails = ({ } }; - const getLastNameLabel = () => { - if (is_appstore) return localize('Family name*'); - return is_svg || is_mf ? localize('Last name*') : localize('Last name'); - }; - - const getFieldHint = field_name => { - return ( - - ); - }; - /* In most modern browsers, setting autocomplete to "off" will not prevent a password manager from asking the user if they would like to save username and password information, or from automatically filling in those values in a site's login form. check this link https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion#the_autocomplete_attribute_and_login_fields */ // for dropdowns use 'none' - const autocomplete_value = 'none'; + + const getExampleFormat = example_format => { + return example_format ? localize('Example: ') + example_format : ''; + }; return ( ( {({ setRef, height }) => ( -
- - - + {!is_qualified_for_idv && ( + + + + )} - {is_appstore && ( + {!is_qualified_for_idv && is_appstore && (
{localize( @@ -206,564 +191,68 @@ const PersonalDetails = ({ )}
- {'salutation' in props.value && ( -
- - {is_virtual ? ( - localize( - 'Please remember that it is your responsibility to keep your answers accurate and up to date. You can update your personal details at any time in your account settings.' - ) - ) : ( - , - ]} + {is_qualified_for_idv && ( + + + + {({ field }) => ( + )} - -
- )} - {!is_appstore && ( - - )} - {'salutation' in props.value && ( // TODO: [deriv-eu] Remove salutation once api is optional - { - e.persist(); - setFieldValue('salutation', e.target.value); - }} - required - > - {salutation_list.map(item => ( - - ))} - - )} - {'first_name' in props.value && ( - - )} - {'last_name' in props.value && ( - - )} - {!is_appstore && } - {'date_of_birth' in props.value && ( - - )} - {'place_of_birth' in props.value && ( - - {({ field }) => ( - - - - setFieldValue( - 'place_of_birth', - value ? text : '', - true - ) - } - required - data-testid='place_of_birth' - /> - - - { - handleChange(e); - setFieldValue( - 'place_of_birth', - e.target.value, - true - ); - }} - {...field} - list_portal_id='modal_root' - required - should_hide_disabled_options={false} - data_testid='place_of_birth_mobile' - /> - - - )} - + + + )} - {'citizen' in props.value && ( - + + {({ field }) => ( - - - - setFieldValue('citizen', value ? text : '', true) - } - list_portal_id='modal_root' - required - data-testid='citizenship' - /> - - - { - handleChange(e); - setFieldValue('citizen', e.target.value, true); - }} - {...field} - required - should_hide_disabled_options={false} - data_testid='citizenship_mobile' - /> - - - )} - - )} - {'phone' in props.value && ( - = 9 && - props.value?.phone?.length <= 35) - } - maxLength={50} - data-testid='phone' - /> - )} - {('tax_residence' in props.value || - 'tax_identification_number' in props.value) && ( - - - {'tax_residence' in props.value && ( - - {({ field }) => ( -
- - - setFieldValue( - 'tax_residence', - value ? text : '', - true - ) - } - list_portal_id='modal_root' - data-testid='tax_residence' - disabled={disabled_items.includes( - 'tax_residence' - )} - /> - - - { - handleChange(e); - setFieldValue( - 'tax_residence', - e.target.value, - true - ); - }} - {...field} - required - data_testid='tax_residence_mobile' - disabled={disabled_items.includes( - 'tax_residence' - )} - /> - -
{ - setIsTaxResidencePopoverOpen(true); - setIsTinPopoverOpen(false); - e.stopPropagation(); - }} - > - -
-
- )} -
- )} - {'tax_identification_number' in props.value && ( -
)} - {warning_items?.tax_identification_number && ( -
- )} - {'employment_status' in props.value && ( -
- - - - - { - setFieldTouched('employment_status', true); - handleChange(e); - }} - disabled={disabled_items.includes('employment_status')} - /> - -
- )} - {'tax_identification_confirm' in props.value && ( - - setFieldValue( - 'tax_identification_confirm', - !values.tax_identification_confirm, - true - ) - } - value={values.tax_identification_confirm} - label={localize( - 'I hereby confirm that the tax information I provided is true and complete. I will also inform {{legal_entity_name}} about any changes to this information.', - { - legal_entity_name: getLegalEntityName('maltainvest'), - } - )} - renderlabel={title => ( - - {title} - - )} - withTabIndex={0} - data-testid='tax_identification_confirm' - /> - )} - - )} - {'account_opening_reason' in props.value && ( // TODO: [deriv-eu] Remove account opening reason once api is optional - - - - {({ field }) => ( - - - - - - { - handleChange(e); - setFieldValue( - 'account_opening_reason', - e.target.value, - true - ); - }} - {...field} - required - data_testid='account_opening_reason_mobile' - disabled={disabled_items.includes( - 'account_opening_reason' - )} - /> - - - )} - - - )} + +
@@ -777,7 +266,7 @@ const PersonalDetails = ({ onCancel={() => handleCancel(values)} /> - + )} )} diff --git a/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.jsx b/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.jsx index cda0b51d312f..d27f75a928ad 100644 --- a/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.jsx +++ b/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.jsx @@ -5,11 +5,11 @@ import { Autocomplete, Button, DesktopWrapper, Input, MobileWrapper, Text, Selec import { Formik, Field } from 'formik'; import { localize, Localize } from '@deriv/translations'; import { formatInput, WS } from '@deriv/shared'; -import { generatePlaceholderText } from 'Helpers/utils'; -import { documentAdditionalError, getDocumentData, getRegex, preventEmptyClipboardPaste } from './utils'; +import { documentAdditionalError, getRegex, preventEmptyClipboardPaste } from './utils'; import FormFooter from 'Components/form-footer'; import BackButtonIcon from 'Assets/ic-poi-back-btn.svg'; import DocumentSubmitLogo from 'Assets/ic-document-submit-icon.svg'; +import { getDocumentData, generatePlaceholderText } from 'Helpers/utils'; const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, is_from_external }) => { const [document_list, setDocumentList] = React.useState([]); diff --git a/packages/account/src/Components/poi/idv-document-submit/utils.js b/packages/account/src/Components/poi/idv-document-submit/utils.js index 765cf262e0e0..b9f6ee0feea6 100644 --- a/packages/account/src/Components/poi/idv-document-submit/utils.js +++ b/packages/account/src/Components/poi/idv-document-submit/utils.js @@ -1,3 +1,5 @@ +// [TODO]: Remove duplicated functions from this file and use the ones in Helpers/utils + import { getUrlBase } from '@deriv/shared'; import { localize } from '@deriv/translations'; diff --git a/packages/account/src/Components/poi/poi-confirm-with-example-form-container/poi-confirm-with-example-form-container.tsx b/packages/account/src/Components/poi/poi-confirm-with-example-form-container/poi-confirm-with-example-form-container.tsx index 2e64b241c3d9..109bfd3d47f9 100644 --- a/packages/account/src/Components/poi/poi-confirm-with-example-form-container/poi-confirm-with-example-form-container.tsx +++ b/packages/account/src/Components/poi/poi-confirm-with-example-form-container/poi-confirm-with-example-form-container.tsx @@ -85,7 +85,6 @@ const PoiConfirmWithExampleFormContainer = ({ setRestState({ ...rest_state, api_error: response.error.message }); return; } - updateAccountStatus(); setRestState({ ...rest_state, ...response.get_settings }); setChecked(true); setIsLoading(false); diff --git a/packages/account/src/Components/poi/poi-confirm-with-example-form/poi-confirm-with-example-form.tsx b/packages/account/src/Components/poi/poi-confirm-with-example-form/poi-confirm-with-example-form.tsx index f7c284326fd2..e2773f5534b8 100644 --- a/packages/account/src/Components/poi/poi-confirm-with-example-form/poi-confirm-with-example-form.tsx +++ b/packages/account/src/Components/poi/poi-confirm-with-example-form/poi-confirm-with-example-form.tsx @@ -31,7 +31,7 @@ const PoiConfirmWithExampleForm = ({ ); return ( -
+
}> diff --git a/packages/account/src/Configs/personal-details-config.js b/packages/account/src/Configs/personal-details-config.js index 2eeac703c48e..a678579cdc85 100644 --- a/packages/account/src/Configs/personal-details-config.js +++ b/packages/account/src/Configs/personal-details-config.js @@ -1,6 +1,6 @@ import { generateValidationFunction, getDefaultFields, getErrorMessages, toMoment, validLength } from '@deriv/shared'; - import { localize } from '@deriv/translations'; +import { shouldShowIdentityInformation } from 'Helpers/utils'; const personal_details_config = ({ residence_list, account_settings, is_appstore, real_account_signup_target }) => { if (!residence_list || !account_settings) { @@ -142,6 +142,22 @@ const personal_details_config = ({ residence_list, account_settings, is_appstore supported_in: ['maltainvest'], rules: [['confirm', localize('Please confirm your tax information.')]], }, + document_type: { + default_value: account_settings.document_type ?? { + id: '', + text: '', + value: '', + example_format: '', + sample_image: '', + }, + supported_in: ['svg'], + rules: [], + }, + document_number: { + default_value: account_settings.document_number ?? '', + supported_in: ['svg'], + rules: [], + }, }; const getConfig = () => { @@ -161,7 +177,7 @@ const personal_details_config = ({ residence_list, account_settings, is_appstore }; const personalDetailsConfig = ( - { upgrade_info, real_account_signup_target, residence_list, account_settings }, + { upgrade_info, real_account_signup_target, residence_list, account_settings, account_status, residence }, PersonalDetails, is_appstore = false ) => { @@ -182,7 +198,13 @@ const personalDetailsConfig = ( props: { validate: generateValidationFunction( real_account_signup_target, - transformConfig(config, { real_account_signup_target }) + transformConfig(config, { + real_account_signup_target, + residence_list, + account_settings, + account_status, + residence, + }) ), is_svg: upgrade_info?.can_upgrade_to === 'svg', is_mf: real_account_signup_target === 'maltainvest', @@ -212,17 +234,36 @@ const personalDetailsConfig = ( }, ], disabled_items, + account_status, + residence, + account_settings, }, passthrough: ['residence_list', 'is_fully_authenticated', 'has_real_account'], icon: 'IcDashboardPersonalDetails', }; }; -const transformConfig = (config, { real_account_signup_target }) => { +const transformConfig = ( + config, + { real_account_signup_target, residence_list, account_settings, account_status, residence } +) => { // Remove required rule for malta and iom if (['malta', 'iom'].includes(real_account_signup_target) && config.tax_residence) { config.tax_residence.rules.shift(); } + // Remove IDV for non supporting SVG countries + if ( + !shouldShowIdentityInformation({ + account_status, + account_settings, + residence, + residence_list, + real_account_signup_target, + }) + ) { + delete config.document_type; + delete config.document_number; + } return config; }; diff --git a/packages/account/src/Helpers/utils.js b/packages/account/src/Helpers/utils.js deleted file mode 100644 index 1cb567d90c7c..000000000000 --- a/packages/account/src/Helpers/utils.js +++ /dev/null @@ -1,15 +0,0 @@ -import { localize } from '@deriv/translations'; -/** - * @param {string} selected_doc - Could be one of the following: 'drivers_license', 'ssnit', 'id_card', 'passport' - * @returns {string} - Returns the placeholder text for the document number input - */ -export const generatePlaceholderText = selected_doc => { - switch (selected_doc) { - case 'drivers_license': - return localize('Enter Driver License Reference number'); - case 'ssnit': - return localize('Enter your SSNIT number'); - default: - return localize('Enter your document number'); - } -}; diff --git a/packages/account/src/Helpers/utils.ts b/packages/account/src/Helpers/utils.ts new file mode 100644 index 000000000000..7f76ab3711d4 --- /dev/null +++ b/packages/account/src/Helpers/utils.ts @@ -0,0 +1,217 @@ +import { getUrlBase } from '@deriv/shared'; + +import { localize } from '@deriv/translations'; +import { ResidenceList, GetSettings, GetAccountStatus } from '@deriv/api-types'; + +const getImageLocation = (image_name: string) => getUrlBase(`/public/images/common/${image_name}`); + +export const documentAdditionalError = (document_additional: string, document_additional_format: string) => { + let error_message = null; + if (!document_additional) { + error_message = 'Please enter your document number. '; + } else { + const format_regex = getRegex(document_additional_format); + if (!format_regex.test(document_additional)) { + error_message = 'Please enter the correct format. '; + } + } + + return error_message; +}; + +// Unsupported Regex List +const regex = [ + { + regex_string: '^(?i)G[a-zA-Z0-9]{7,9}$', + value: '^G[a-zA-Z0-9]{7,9}$', + flags: 'i', + }, +]; + +// Note: Ensure that the object keys matches BE API's keys. This is simply a mapping for FE templates +const idv_document_data = Object.freeze({ + ke: { + alien_card: { + new_display_name: '', + example_format: '123456', + sample_image: getImageLocation('ke_alien_card.png'), + }, + national_id: { + new_display_name: '', + example_format: '12345678', + sample_image: getImageLocation('ke_national_identity_card.png'), + }, + passport: { + new_display_name: '', + example_format: 'A12345678', + sample_image: getImageLocation('ke_passport.png'), + }, + }, + za: { + national_id: { + new_display_name: 'National ID', + example_format: '1234567890123', + sample_image: getImageLocation('za_national_identity_card.png'), + }, + national_id_no_photo: { + new_display_name: 'National ID (No Photo)', + example_format: '1234567890123', + sample_image: '', + }, + }, + ng: { + bvn: { + new_display_name: 'Bank Verification Number', + example_format: '12345678901', + sample_image: '', + }, + cac: { + new_display_name: 'Corporate Affairs Commission', + example_format: '12345678', + sample_image: '', + }, + drivers_license: { + new_display_name: '', + example_format: 'ABC123456789', + sample_image: getImageLocation('ng_drivers_license.png'), + }, + nin: { + new_display_name: 'National Identity Number', + example_format: '12345678901', + sample_image: '', + }, + nin_slip: { + new_display_name: 'National Identity Number Slip', + example_format: '12345678901', + sample_image: getImageLocation('ng_nin_slip.png'), + }, + tin: { + new_display_name: 'Taxpayer identification number', + example_format: '12345678-1234', + sample_image: '', + }, + voter_id: { + new_display_name: 'Voter ID', + example_format: '1234567890123456789', + sample_image: getImageLocation('ng_voter_id.png'), + }, + }, + gh: { + drivers_license: { + new_display_name: '', + example_format: 'B1234567', + sample_image: '', + }, + national_id: { + new_display_name: 'National ID', + example_format: 'GHA-123456789-1', + sample_image: '', + }, + passport: { + new_display_name: 'Passport', + example_format: 'G1234567', + sample_image: '', + }, + ssnit: { + new_display_name: 'Social Security and National Insurance Trust', + example_format: 'C123456789012', + sample_image: '', + }, + voter_id: { + new_display_name: 'Voter ID', + example_format: '01234567890', + sample_image: '', + }, + }, + br: { + cpf: { + new_display_name: 'CPF', + example_format: '123.456.789-12', + sample_image: '', + }, + }, + ug: { + national_id: { + new_display_name: 'National ID', + example_format: 'CM12345678PE1D', + sample_image: getImageLocation('ug_national_identity_card.png'), + }, + national_id_no_photo: { + new_display_name: 'National ID (No Photo)', + example_format: 'CM12345678PE1D', + sample_image: '', + }, + }, + zw: { + national_id: { + new_display_name: 'National ID', + example_format: '081234567F53', + sample_image: getImageLocation('zw_national_identity_card.png'), + }, + }, +}); + +type TIDVSupportCheck = { + residence_list: ResidenceList; + account_settings: GetSettings; + account_status: GetAccountStatus; + real_account_signup_target: string; + residence: string; +}; + +export const shouldShowIdentityInformation = ({ + account_status, + account_settings, + residence, + residence_list, + real_account_signup_target, +}: TIDVSupportCheck) => { + const citizen = account_settings.citizen || residence; + const country = residence_list.find(item => item.value === citizen); + const maltainvest = real_account_signup_target === 'maltainvest'; + const should_skip_idv = account_status?.status?.some((status: string) => status === 'skip_idv'); //status added by BE when idv should be skipped for the user + return Boolean( + !maltainvest && citizen && country?.identity?.services?.idv?.is_country_supported && !should_skip_idv + ); +}; + +export const getDocumentData = (country_code: string, document_type: string) => { + return ( + (Object.keys(idv_document_data).includes(country_code) && + (idv_document_data as any)[country_code][document_type]) || { + new_display_name: '', + example_format: '', + sample_image: '', + } + ); +}; + +export const preventEmptyClipboardPaste = (e: ClipboardEvent) => { + const clipboardData = (e.clipboardData || window.clipboardData).getData('text'); + if (clipboardData.length === 0) { + e.preventDefault(); + } +}; + +export const getRegex = (target_regex: string) => { + const output_regex = regex.find(r => r.regex_string === target_regex); + if (output_regex) { + return new RegExp(output_regex.value, output_regex.flags); + } + return new RegExp(target_regex); +}; + +/** + * @param {string} selected_doc - Could be one of the following: 'drivers_license', 'ssnit', 'id_card', 'passport' + * @returns {string} - Returns the placeholder text for the document number input + */ +export const generatePlaceholderText = (selected_doc: string) => { + switch (selected_doc) { + case 'drivers_license': + return localize('Enter Driver License Reference number'); + case 'ssnit': + return localize('Enter your SSNIT number'); + default: + return localize('Enter your document number'); + } +}; diff --git a/packages/account/src/Sections/Security/Passwords/deriv-password.jsx b/packages/account/src/Sections/Security/Passwords/deriv-password.jsx index eb70b8ab67d4..02ee7fcf5610 100644 --- a/packages/account/src/Sections/Security/Passwords/deriv-password.jsx +++ b/packages/account/src/Sections/Security/Passwords/deriv-password.jsx @@ -59,24 +59,24 @@ const DerivPassword = ({ email, is_social_signup, social_identity_provider }) =>
- + diff --git a/packages/account/src/Sections/Security/Passwords/passwords-platform.jsx b/packages/account/src/Sections/Security/Passwords/passwords-platform.jsx index a022f798b87e..f4092bf78314 100644 --- a/packages/account/src/Sections/Security/Passwords/passwords-platform.jsx +++ b/packages/account/src/Sections/Security/Passwords/passwords-platform.jsx @@ -54,7 +54,7 @@ const PasswordsPlatform = ({ email, has_dxtrade_accounts, has_mt5_accounts }) =>
- +