From a12273cf2d26f8406489272faf08a435651527bf Mon Sep 17 00:00:00 2001
From: Likhith Kolayari <98398322+likhith-deriv@users.noreply.github.com>
Date: Wed, 10 May 2023 06:50:59 +0400
Subject: [PATCH] Likhith/kyc-34/ improve idv flow for poi (#11)
* feat: :art: incorporated new changes
* feat: :sparkles: incorporated idv for mt5
---
.../account/src/Components/forms/idv-form.tsx | 24 +-
.../forms/personal-details-form.jsx | 48 +-
.../personal-details/personal-details.jsx | 78 +--
.../idv-document-submit.jsx | 214 +++++--
...oi-confirm-with-example-form-container.tsx | 13 +-
.../idv-doc-submit-on-signup.tsx | 532 +++++-------------
.../src/Helpers/__tests__/utils.spec.ts | 13 +
packages/account/src/Helpers/utils.ts | 11 +
.../proof-of-identity-submission-for-mt5.jsx | 45 +-
.../proof-of-identity-submission.jsx | 2 +
packages/account/src/Styles/account.scss | 20 +-
.../src/components/input/input.scss | 2 +-
.../_common/components/onfido-container.scss | 15 +-
.../core/src/sass/real-account-signup.scss | 42 ++
14 files changed, 523 insertions(+), 536 deletions(-)
diff --git a/packages/account/src/Components/forms/idv-form.tsx b/packages/account/src/Components/forms/idv-form.tsx
index 25a2d3e2fae0..7386c4c85670 100644
--- a/packages/account/src/Components/forms/idv-form.tsx
+++ b/packages/account/src/Components/forms/idv-form.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import classNames from 'classnames';
import { Field, FormikValues, FormikProps, FormikHandlers } from 'formik';
import { ResidenceList } from '@deriv/api-types';
-import { localize, Localize } from '@deriv/translations';
+import { 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, generatePlaceholderText } from 'Helpers/utils';
@@ -24,7 +24,6 @@ type TFormProps = {
type TIDVForm = {
selected_country: ResidenceList[0];
- is_from_external: boolean;
hide_hint?: boolean;
class_name?: string;
} & FormikHandlers &
@@ -44,11 +43,9 @@ const IDVForm = ({
const [document_image, setDocumentImage] = React.useState(null);
const [selected_doc, setSelectedDoc] = React.useState('');
- const { selected_country, is_from_external } = props;
- // const citizen = account_settings?.citizen || residence;
+ const { selected_country } = props;
const { documents_supported: document_data, has_visual_sample } = selected_country?.identity?.services?.idv || {};
- // residence_list.find(residence_data => residence_data.value === citizen)?.identity.services?.idv || {};
React.useEffect(() => {
const document_types = Object.keys(document_data);
@@ -129,11 +126,7 @@ const IDVForm = ({
'proof-of-identity__container--idv': props.hide_hint,
})}
>
-
+
- {selected_doc && !props.hide_hint && (
-
-
-
- )}
);
};
diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx
index b96c714a9b85..ca39aa67a315 100644
--- a/packages/account/src/Components/forms/personal-details-form.jsx
+++ b/packages/account/src/Components/forms/personal-details-form.jsx
@@ -22,6 +22,7 @@ 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';
+import { isFieldImmutable } from 'Helpers/utils';
const DateOfBirthField = props => (
@@ -85,7 +86,7 @@ const PersonalDetailsForm = ({
is_qualified_for_idv,
should_hide_helper_image,
is_appstore,
- disabled_items,
+ editable_fields = [],
has_real_account,
residence_list,
is_fully_authenticated,
@@ -107,7 +108,7 @@ const PersonalDetailsForm = ({
handleToolTipStatus();
setShouldCloseTooltip(false);
}
- }, [should_close_tooltip]);
+ }, [should_close_tooltip, handleToolTipStatus, setShouldCloseTooltip]);
const getLastNameLabel = () => {
if (is_appstore) return localize('Family name*');
@@ -127,14 +128,14 @@ const PersonalDetailsForm = ({
);
};
- const handleToolTipStatus = () => {
+ const handleToolTipStatus = React.useCallback(() => {
if (is_tax_residence_popover_open) {
setIsTaxResidencePopoverOpen(false);
}
if (is_tin_popover_open) {
setIsTinPopoverOpen(false);
}
- };
+ }, [is_tax_residence_popover_open, is_tin_popover_open]);
const name_dob_clarification_message = (
))}
@@ -206,7 +207,10 @@ const PersonalDetailsForm = ({
required={is_svg || is_appstore}
label={is_svg || is_appstore || is_mf ? localize('First name*') : localize('First name')}
hint={getFieldHint(localize('first name'))}
- disabled={disabled_items.includes('first_name') || (values?.first_name && has_real_account)}
+ disabled={
+ isFieldImmutable('first_name', editable_fields) ||
+ (values?.first_name && has_real_account)
+ }
placeholder={localize('John')}
data-testid='first_name'
/>
@@ -217,7 +221,10 @@ const PersonalDetailsForm = ({
required={is_svg || is_appstore}
label={getLastNameLabel()}
hint={getFieldHint(localize('last name'))}
- disabled={disabled_items.includes('last_name') || (values?.last_name && has_real_account)}
+ disabled={
+ isFieldImmutable('last_name', editable_fields) ||
+ (values?.last_name && has_real_account)
+ }
placeholder={localize('Doe')}
data-testid='last_name'
/>
@@ -234,7 +241,8 @@ const PersonalDetailsForm = ({
}
hint={getFieldHint(localize('date of birth'))}
disabled={
- disabled_items.includes('date_of_birth') || (values?.date_of_birth && has_real_account)
+ isFieldImmutable('date_of_birth', editable_fields) ||
+ (values?.date_of_birth && has_real_account)
}
placeholder={localize('01-07-1999')}
portal_id={is_appstore ? '' : 'modal_root'}
@@ -249,7 +257,7 @@ const PersonalDetailsForm = ({
@@ -412,7 +420,7 @@ const PersonalDetailsForm = ({
{...field}
required
data_testid='tax_residence_mobile'
- disabled={disabled_items.includes('tax_residence')}
+ disabled={isFieldImmutable('tax_residence', editable_fields)}
/>
@@ -506,7 +514,7 @@ const PersonalDetailsForm = ({
onChange={handleChange}
handleBlur={handleBlur}
error={touched.employment_status && errors.employment_status}
- disabled={disabled_items.includes('employment_status')}
+ disabled={isFieldImmutable('employment_status', editable_fields)}
/>
@@ -523,7 +531,7 @@ const PersonalDetailsForm = ({
setFieldTouched('employment_status', true);
handleChange(e);
}}
- disabled={disabled_items.includes('employment_status')}
+ disabled={isFieldImmutable('employment_status', editable_fields)}
/>
@@ -572,7 +580,7 @@ const PersonalDetailsForm = ({
: localize('Account opening reason')
}
name={field.name}
- disabled={disabled_items.includes('account_opening_reason')}
+ disabled={isFieldImmutable('account_opening_reason', editable_fields)}
is_align_text_left
list={account_opening_reason_list}
value={values.account_opening_reason}
@@ -603,7 +611,7 @@ const PersonalDetailsForm = ({
{...field}
required
data_testid='account_opening_reason_mobile'
- disabled={disabled_items.includes('account_opening_reason')}
+ disabled={isFieldImmutable('account_opening_reason', editable_fields)}
/>
diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx
index 87087d105d78..6a7256c5cf43 100644
--- a/packages/account/src/Components/personal-details/personal-details.jsx
+++ b/packages/account/src/Components/personal-details/personal-details.jsx
@@ -43,7 +43,6 @@ const PersonalDetails = ({
const { is_appstore } = React.useContext(PlatformContext);
const [should_close_tooltip, setShouldCloseTooltip] = React.useState(false);
const [warning_items, setWarningItems] = React.useState({});
- const [should_hide_helper_image, setShouldHideHelperImage] = React.useState(false);
const is_submit_disabled_ref = React.useRef(true);
const isSubmitDisabled = errors => {
@@ -73,6 +72,8 @@ const PersonalDetails = ({
real_account_signup_target,
});
+ const shouldHideHelperImage = document_id => document_id === IDV_NOT_APPLICABLE_OPTION.id;
+
const validateIDV = values => {
const errors = {};
const { document_type, document_number, document_additional } = values;
@@ -82,23 +83,25 @@ const PersonalDetails = ({
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 (!shouldHideHelperImage(document_type?.id)) {
+ 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)) {
+ if (!document_number) {
errors.document_number =
- localize('Please enter the correct format. ') + getExampleFormat(document_type.example_format);
+ 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;
@@ -131,6 +134,8 @@ const PersonalDetails = ({
const citizen = account_settings?.citizen || residence;
const selected_country = residence_list.find(residence_data => residence_data.value === citizen) || {};
+ const editable_fields = Object.keys(props.value).filter(field => !disabled_items.includes(field)) || [];
+
return (
- {({ field, form }) => {
- setShouldHideHelperImage(
- form.values?.document_type?.id ===
- IDV_NOT_APPLICABLE_OPTION.id
- );
- return (
-
- );
- }}
+ {({ field }) => (
+
+ )}
@@ -213,7 +212,8 @@ const PersonalDetails = ({
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 a047e57f0cbf..75506eed7989 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
@@ -1,60 +1,143 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Button, Text } from '@deriv/components';
+import classNames from 'classnames';
+import { Button } from '@deriv/components';
import { Formik, Field } from 'formik';
import { localize } from '@deriv/translations';
-import { WS } from '@deriv/shared';
-import { documentAdditionalError, getRegex } from 'Helpers/utils';
+import {
+ WS,
+ IDV_NOT_APPLICABLE_OPTION,
+ toMoment,
+ validLength,
+ validName,
+ filterObjProperties,
+ isDesktop,
+} from '@deriv/shared';
+import { documentAdditionalError, getRegex, validate } from 'Helpers/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 IDVForm from 'Components/forms/idv-form';
+import PersonalDetailsForm from 'Components/forms/personal-details-form';
+import FormSubHeader from 'Components/form-sub-header';
-const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, is_from_external }) => {
- const initial_form_values = {
- document_type: '',
+const IdvDocumentSubmit = ({
+ handleBack,
+ handleViewComplete,
+ selected_country,
+ is_from_external,
+ account_settings,
+ getChangeableFields,
+}) => {
+ const visible_settings = ['first_name', 'last_name', 'date_of_birth'];
+ 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');
+ }
+
+ const changeable_fields = [...getChangeableFields()];
+
+ const initial_values = {
+ document_type: {
+ id: '',
+ text: '',
+ value: '',
+ example_format: '',
+ sample_image: '',
+ },
document_number: '',
+ ...form_initial_values,
};
const getExampleFormat = example_format => {
return example_format ? localize('Example: ') + example_format : '';
};
+ const shouldHideHelperImage = document_id => document_id === IDV_NOT_APPLICABLE_OPTION.id;
+
const validateFields = values => {
const errors = {};
const { document_type, document_number, document_additional } = values;
const needs_additional_document = !!document_type.additional;
const is_document_number_invalid = document_number === document_type.example_format;
- if (!document_type || !document_type.text || !document_type.value) {
+ 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)) {
+ if (!shouldHideHelperImage(document_type?.id)) {
+ 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 the correct format. ') + getExampleFormat(document_type.example_format);
+ 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);
+ }
}
}
+ const required_fields = ['first_name', 'last_name', 'date_of_birth'];
+ const validateValues = validate(errors, values);
+ validateValues(val => val, required_fields, localize('This field is required'));
+ const min_name = 2;
+ const max_name = 50;
+ const validateName = (name, field) => {
+ if (name) {
+ if (!validLength(name.trim(), { min: min_name, max: max_name })) {
+ errors[field] = localize('You should enter 2-50 characters.');
+ } else if (!validName(name)) {
+ errors[field] = localize('Letters, spaces, periods, hyphens, apostrophes only.');
+ }
+ }
+ };
+ validateName(values.first_name, 'first_name');
+ validateName(values.last_name, 'last_name');
return errors;
};
- const submitHandler = (values, { setSubmitting, setErrors }) => {
+ const makeSettingsRequest = settings => {
+ const request = filterObjProperties(settings, changeable_fields);
+
+ if (request.first_name) {
+ request.first_name = request.first_name.trim();
+ }
+ if (request.last_name) {
+ request.last_name = request.last_name.trim();
+ }
+ if (request.date_of_birth) {
+ request.date_of_birth = toMoment(request.date_of_birth).format('YYYY-MM-DD');
+ }
+
+ return request;
+ };
+
+ const submitHandler = async (values, { setSubmitting, setErrors }) => {
setSubmitting(true);
+
+ const request = makeSettingsRequest(values);
+
+ const data = await WS.setSettings(request);
+
+ if (data.error) {
+ setErrors({ error_message: data.error.message });
+ setSubmitting(false);
+ return;
+ }
+ const get_settings = WS.authorized.storage.getSettings();
+ if (get_settings.error) {
+ setErrors({ error_message: data.error.message });
+ setSubmitting(false);
+ return;
+ }
const submit_data = {
identity_verification_document_add: 1,
document_number: values.document_number,
@@ -63,6 +146,9 @@ const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, i
issuing_country: selected_country.value,
};
+ if (submit_data.document_type === IDV_NOT_APPLICABLE_OPTION.id) {
+ return;
+ }
WS.send(submit_data).then(response => {
setSubmitting(false);
if (response.error) {
@@ -74,7 +160,7 @@ const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, i
};
return (
-
+
{({
dirty,
errors,
@@ -84,39 +170,65 @@ const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, i
isSubmitting,
isValid,
setFieldValue,
+ setFieldTouched,
touched,
values,
}) => (
-
-
-
- {localize('Verify your identity')}
-
-
- {localize('Please select the document type and enter the ID number.')}
-
+
+
+
+ {({ field }) => {
+ return (
+
+ );
+ }}
+
+
{({ field }) => (
-
+
)}
-
+ {isDesktop() && (
+
+ )}