From c9bcfc6c489ae3d4c6b946be2ba44f34c9fed771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Fri, 8 Sep 2023 11:32:08 +0300 Subject: [PATCH 01/28] feat: scroll to field with error component --- .../forms/scroll-to-field-with-error.tsx | 14 ++++++++++++++ packages/shared/src/utils/object/object.ts | 9 +++++++++ 2 files changed, 23 insertions(+) create mode 100644 packages/account/src/Components/forms/scroll-to-field-with-error.tsx diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx new file mode 100644 index 000000000000..5f57d19f8f31 --- /dev/null +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { useFormikContext } from 'formik'; + +export function ScrollToFieldWithError() { + const formik = useFormikContext(); + const submitting = formik?.isSubmitting; + + React.useEffect(() => { + const el = document.querySelector('.dc-input--error'); + (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + // (el?.parentElement ?? el)?.focus(); + }, [submitting]); + return null; +} diff --git a/packages/shared/src/utils/object/object.ts b/packages/shared/src/utils/object/object.ts index 9ff8296f1e4b..88b3e608d989 100644 --- a/packages/shared/src/utils/object/object.ts +++ b/packages/shared/src/utils/object/object.ts @@ -119,3 +119,12 @@ export const deepFreeze = (obj: any) => { }); return Object.freeze(obj); }; + +const firstPropertyObject = (obj: any) => { + const keys = Object.keys(obj); + if (!keys.length) return {}; + let first_key = keys[0]; + return { + [first_key]: obj[first_key], + }; +}; From 6f5a0ae647cc8ccb2a9a116b30fedbd5940abba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Tue, 12 Sep 2023 11:13:19 +0300 Subject: [PATCH 02/28] chore: account signup next behaviour improve --- .../Components/forms/scroll-to-field-with-error.tsx | 8 ++++---- .../Components/personal-details/personal-details.jsx | 10 +++++++--- packages/shared/src/utils/object/object.ts | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index 5f57d19f8f31..a6a466c1c7fc 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -4,11 +4,11 @@ import { useFormikContext } from 'formik'; export function ScrollToFieldWithError() { const formik = useFormikContext(); const submitting = formik?.isSubmitting; - + const error_field_name = Object.keys(formik.errors)[0]; React.useEffect(() => { - const el = document.querySelector('.dc-input--error'); - (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); - // (el?.parentElement ?? el)?.focus(); + const el = document.querySelector(`input[name="${error_field_name}"]`) as HTMLInputElement; + el?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + el?.focus(); }, [submitting]); return null; } diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 599139a3597b..805d3ace2cf8 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -10,6 +10,7 @@ import { Text, } from '@deriv/components'; import { + firstPropertyObject, isDesktop, isMobile, PlatformContext, @@ -28,6 +29,7 @@ import { splitValidationResultTypes } from '../real-account-signup/helpers/utils import IDVForm from '../forms/idv-form'; import PersonalDetailsForm from '../forms/personal-details-form'; import FormSubHeader from '../form-sub-header'; +import { ScrollToFieldWithError } from '../forms/scroll-to-field-with-error'; const PersonalDetails = ({ getCurrentStep, @@ -109,7 +111,8 @@ const PersonalDetails = ({ const { errors } = splitValidationResultTypes(validate(values)); const error_data = { ...idv_error, ...errors }; checkSubmitStatus(error_data); - return error_data; + // return error_data with only first error field name for highlighting only one field in a moment; + return firstPropertyObject(error_data); }; const closeToolTip = () => setShouldCloseTooltip(true); @@ -130,7 +133,6 @@ const PersonalDetails = ({ innerRef={selected_step_ref} initialValues={{ ...props.value }} validate={handleValidate} - validateOnMount onSubmit={(values, actions) => { onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); }} @@ -139,12 +141,14 @@ const PersonalDetails = ({ {({ setRef, height }) => (
+ {!is_qualified_for_idv && ( @@ -220,7 +224,7 @@ const PersonalDetails = ({ handleCancel(values)} diff --git a/packages/shared/src/utils/object/object.ts b/packages/shared/src/utils/object/object.ts index 88b3e608d989..aa664fef7494 100644 --- a/packages/shared/src/utils/object/object.ts +++ b/packages/shared/src/utils/object/object.ts @@ -120,7 +120,7 @@ export const deepFreeze = (obj: any) => { return Object.freeze(obj); }; -const firstPropertyObject = (obj: any) => { +export const firstPropertyObject = (obj: any) => { const keys = Object.keys(obj); if (!keys.length) return {}; let first_key = keys[0]; From 43ba2535efa407bdea25998bbed00b8a69bcc10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Tue, 12 Sep 2023 17:32:30 +0300 Subject: [PATCH 03/28] chore: personal details on signup field order chnage --- .../forms/scroll-to-field-with-error.tsx | 3 +- .../src/Configs/personal-details-config.ts | 77 ++++++++++--------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index a6a466c1c7fc..26cdc3c53949 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -3,12 +3,11 @@ import { useFormikContext } from 'formik'; export function ScrollToFieldWithError() { const formik = useFormikContext(); - const submitting = formik?.isSubmitting; const error_field_name = Object.keys(formik.errors)[0]; React.useEffect(() => { const el = document.querySelector(`input[name="${error_field_name}"]`) as HTMLInputElement; el?.scrollIntoView({ behavior: 'smooth', block: 'center' }); el?.focus(); - }, [submitting]); + }, [error_field_name]); return null; } diff --git a/packages/account/src/Configs/personal-details-config.ts b/packages/account/src/Configs/personal-details-config.ts index 050a903dea43..c38dc496dd50 100644 --- a/packages/account/src/Configs/personal-details-config.ts +++ b/packages/account/src/Configs/personal-details-config.ts @@ -42,16 +42,23 @@ export const personal_details_config = ({ const default_residence = real_account_signup_target === 'maltainvest' ? account_settings?.residence : ''; + //the order of fields should be the same as on the page for proper highlighting fields errors const config = { - account_opening_reason: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.account_opening_reason ?? '', - rules: [['req', localize('Account opening reason is required.')]], + document_type: { + default_value: account_settings.document_type ?? { + id: '', + text: '', + value: '', + example_format: '', + sample_image: '', + }, + supported_in: ['svg'], + rules: [], }, - salutation: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.salutation ?? '', - rules: [['req', localize('Salutation is required.')]], + document_number: { + default_value: account_settings.document_number ?? '', + supported_in: ['svg'], + rules: [], }, first_name: { supported_in: ['svg', 'iom', 'malta', 'maltainvest'], @@ -84,20 +91,6 @@ export const personal_details_config = ({ ], ], }, - place_of_birth: { - supported_in: ['maltainvest', 'iom', 'malta'], - default_value: account_settings.place_of_birth - ? residence_list.find(item => item.value === account_settings.place_of_birth)?.text - : '', - rules: [['req', localize('Place of birth is required.')]], - }, - citizen: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.citizen - ? residence_list.find(item => item.value === account_settings.citizen)?.text - : '', - rules: [['req', localize('Citizenship is required')]], - }, phone: { supported_in: ['svg', 'iom', 'malta', 'maltainvest'], default_value: account_settings.phone ?? '', @@ -117,6 +110,13 @@ export const personal_details_config = ({ ], ], }, + place_of_birth: { + supported_in: ['maltainvest', 'iom', 'malta'], + default_value: account_settings.place_of_birth + ? residence_list.find(item => item.value === account_settings.place_of_birth)?.text + : '', + rules: [['req', localize('Place of birth is required.')]], + }, tax_residence: { //if tax_residence is already set, we will use it as default value else for mf clients we will use residence as default value default_value: account_settings?.tax_residence @@ -159,6 +159,23 @@ export const personal_details_config = ({ ], ], }, + account_opening_reason: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.account_opening_reason ?? '', + rules: [['req', localize('Account opening reason is required.')]], + }, + salutation: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.salutation ?? '', + rules: [['req', localize('Salutation is required.')]], + }, + citizen: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.citizen + ? residence_list.find(item => item.value === account_settings.citizen)?.text + : '', + rules: [['req', localize('Citizenship is required')]], + }, employment_status: { default_value: account_settings.employment_status ?? '', supported_in: ['maltainvest'], @@ -169,22 +186,6 @@ export const personal_details_config = ({ 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 = () => { From f7796ada6a398fe889db36f75213aa8c44ae5d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Wed, 13 Sep 2023 12:31:10 +0300 Subject: [PATCH 04/28] chore: improving behavior for address details page in account signup flow --- .../address-details/address-details.tsx | 17 +++++++++++++---- .../forms/scroll-to-field-with-error.tsx | 7 ++++--- .../personal-details/personal-details.jsx | 1 + 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/account/src/Components/address-details/address-details.tsx b/packages/account/src/Components/address-details/address-details.tsx index 8b7320bb3a73..e4090e6794e8 100644 --- a/packages/account/src/Components/address-details/address-details.tsx +++ b/packages/account/src/Components/address-details/address-details.tsx @@ -16,9 +16,17 @@ import { Text, } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; -import { isDesktop, isMobile, getLocation, makeCancellablePromise, PlatformContext } from '@deriv/shared'; +import { + isDesktop, + isMobile, + getLocation, + makeCancellablePromise, + PlatformContext, + firstPropertyObject, +} from '@deriv/shared'; import { splitValidationResultTypes } from '../real-account-signup/helpers/utils'; import classNames from 'classnames'; +import { ScrollToFieldWithError } from 'Components/forms/scroll-to-field-with-error'; type TAddressDetails = { disabled_items: string[]; @@ -140,7 +148,7 @@ const AddressDetails = ({ const handleValidate = (values: FormikValues) => { const { errors } = splitValidationResultTypes(validate(values)); checkSubmitStatus(errors); - return errors; + return firstPropertyObject(errors); }; return ( @@ -161,12 +169,13 @@ const AddressDetails = ({ {({ handleSubmit, errors, values, setFieldValue, handleChange, setFieldTouched }: FormikValues) => ( {({ setRef, height }: { setRef: (instance: HTMLFormElement) => void; height: number | string }) => ( - + + {!is_appstore && ( { const formik = useFormikContext(); + const is_submitting = formik.isSubmitting; const error_field_name = Object.keys(formik.errors)[0]; React.useEffect(() => { const el = document.querySelector(`input[name="${error_field_name}"]`) as HTMLInputElement; el?.scrollIntoView({ behavior: 'smooth', block: 'center' }); el?.focus(); - }, [error_field_name]); + }, [is_submitting]); return null; -} +}; diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index a336c7d9260c..01cad373e500 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -131,6 +131,7 @@ const PersonalDetails = ({ innerRef={selected_step_ref} initialValues={{ ...props.value }} validate={handleValidate} + validateOnMount onSubmit={(values, actions) => { onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); }} From 9941a2fccd632f6c01edd27ce821470df3830edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Wed, 13 Sep 2023 14:05:10 +0300 Subject: [PATCH 05/28] chore: errors fields order --- .../forms/scroll-to-field-with-error.tsx | 4 +- .../src/Configs/personal-details-config.ts | 49 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index eff5152c27d0..d7c7a586f504 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -6,8 +6,8 @@ export const ScrollToFieldWithError = () => { const is_submitting = formik.isSubmitting; const error_field_name = Object.keys(formik.errors)[0]; React.useEffect(() => { - const el = document.querySelector(`input[name="${error_field_name}"]`) as HTMLInputElement; - el?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + const el = document.querySelector(`[name="${error_field_name}"]`) as HTMLInputElement; + (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); el?.focus(); }, [is_submitting]); return null; diff --git a/packages/account/src/Configs/personal-details-config.ts b/packages/account/src/Configs/personal-details-config.ts index c38dc496dd50..564ed78cfc6d 100644 --- a/packages/account/src/Configs/personal-details-config.ts +++ b/packages/account/src/Configs/personal-details-config.ts @@ -44,6 +44,11 @@ export const personal_details_config = ({ //the order of fields should be the same as on the page for proper highlighting fields errors const config = { + salutation: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.salutation ?? '', + rules: [['req', localize('Salutation is required.')]], + }, document_type: { default_value: account_settings.document_type ?? { id: '', @@ -91,6 +96,20 @@ export const personal_details_config = ({ ], ], }, + place_of_birth: { + supported_in: ['maltainvest', 'iom', 'malta'], + default_value: account_settings.place_of_birth + ? residence_list.find(item => item.value === account_settings.place_of_birth)?.text + : '', + rules: [['req', localize('Place of birth is required.')]], + }, + citizen: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.citizen + ? residence_list.find(item => item.value === account_settings.citizen)?.text + : '', + rules: [['req', localize('Citizenship is required')]], + }, phone: { supported_in: ['svg', 'iom', 'malta', 'maltainvest'], default_value: account_settings.phone ?? '', @@ -110,13 +129,7 @@ export const personal_details_config = ({ ], ], }, - place_of_birth: { - supported_in: ['maltainvest', 'iom', 'malta'], - default_value: account_settings.place_of_birth - ? residence_list.find(item => item.value === account_settings.place_of_birth)?.text - : '', - rules: [['req', localize('Place of birth is required.')]], - }, + tax_residence: { //if tax_residence is already set, we will use it as default value else for mf clients we will use residence as default value default_value: account_settings?.tax_residence @@ -159,23 +172,6 @@ export const personal_details_config = ({ ], ], }, - account_opening_reason: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.account_opening_reason ?? '', - rules: [['req', localize('Account opening reason is required.')]], - }, - salutation: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.salutation ?? '', - rules: [['req', localize('Salutation is required.')]], - }, - citizen: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.citizen - ? residence_list.find(item => item.value === account_settings.citizen)?.text - : '', - rules: [['req', localize('Citizenship is required')]], - }, employment_status: { default_value: account_settings.employment_status ?? '', supported_in: ['maltainvest'], @@ -186,6 +182,11 @@ export const personal_details_config = ({ supported_in: ['maltainvest'], rules: [['confirm', localize('Please confirm your tax information.')]], }, + account_opening_reason: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.account_opening_reason ?? '', + rules: [['req', localize('Account opening reason is required.')]], + }, }; const getConfig = () => { From 45208b5747793e7b28b2090e5faa67a5b27ded3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Fri, 15 Sep 2023 10:38:56 +0300 Subject: [PATCH 06/28] chore: should_scroll_to_error_field flag added --- .../address-details/address-details.tsx | 8 +++-- .../personal-details/personal-details.jsx | 7 +++-- .../src/Configs/personal-details-config.ts | 29 +++++++++---------- .../RealAccountSignup/account-wizard.jsx | 1 + 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/account/src/Components/address-details/address-details.tsx b/packages/account/src/Components/address-details/address-details.tsx index e4090e6794e8..67f2a759277f 100644 --- a/packages/account/src/Components/address-details/address-details.tsx +++ b/packages/account/src/Components/address-details/address-details.tsx @@ -51,6 +51,7 @@ type TAddressDetails = { selected_step_ref?: React.RefObject>; fetchStatesList: () => Promise; value: FormikValues; + should_scroll_to_error_field: boolean; }; type TInputField = { @@ -103,6 +104,7 @@ const AddressDetails = ({ selected_step_ref, disabled_items, has_real_account, + should_scroll_to_error_field = false, ...props }: TAddressDetails) => { const { is_appstore } = React.useContext(PlatformContext); @@ -148,7 +150,7 @@ const AddressDetails = ({ const handleValidate = (values: FormikValues) => { const { errors } = splitValidationResultTypes(validate(values)); checkSubmitStatus(errors); - return firstPropertyObject(errors); + return should_scroll_to_error_field ? firstPropertyObject(errors) : errors; }; return ( @@ -175,7 +177,7 @@ const AddressDetails = ({ height_offset={is_appstore ? '222px' : '90px'} is_disabled={isDesktop()} > - + {should_scroll_to_error_field && } {!is_appstore && ( { const { account_status, account_settings, residence, real_account_signup_target } = props; @@ -110,7 +111,7 @@ const PersonalDetails = ({ const error_data = { ...idv_error, ...errors }; checkSubmitStatus(error_data); // return error_data with only first error field name for highlighting only one field in a moment; - return firstPropertyObject(error_data); + return should_scroll_to_error_field ? firstPropertyObject(error_data) : error_data; }; const closeToolTip = () => setShouldCloseTooltip(true); @@ -147,7 +148,7 @@ const PersonalDetails = ({ onClick={closeToolTip} data-testid='personal_details_form' > - + {should_scroll_to_error_field && } {!is_qualified_for_idv && ( @@ -212,7 +213,7 @@ const PersonalDetails = ({ handleCancel(values)} diff --git a/packages/account/src/Configs/personal-details-config.ts b/packages/account/src/Configs/personal-details-config.ts index 564ed78cfc6d..a02f861755eb 100644 --- a/packages/account/src/Configs/personal-details-config.ts +++ b/packages/account/src/Configs/personal-details-config.ts @@ -96,20 +96,6 @@ export const personal_details_config = ({ ], ], }, - place_of_birth: { - supported_in: ['maltainvest', 'iom', 'malta'], - default_value: account_settings.place_of_birth - ? residence_list.find(item => item.value === account_settings.place_of_birth)?.text - : '', - rules: [['req', localize('Place of birth is required.')]], - }, - citizen: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.citizen - ? residence_list.find(item => item.value === account_settings.citizen)?.text - : '', - rules: [['req', localize('Citizenship is required')]], - }, phone: { supported_in: ['svg', 'iom', 'malta', 'maltainvest'], default_value: account_settings.phone ?? '', @@ -129,7 +115,20 @@ export const personal_details_config = ({ ], ], }, - + place_of_birth: { + supported_in: ['maltainvest', 'iom', 'malta'], + default_value: account_settings.place_of_birth + ? residence_list.find(item => item.value === account_settings.place_of_birth)?.text + : '', + rules: [['req', localize('Place of birth is required.')]], + }, + citizen: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.citizen + ? residence_list.find(item => item.value === account_settings.citizen)?.text + : '', + rules: [['req', localize('Citizenship is required')]], + }, tax_residence: { //if tax_residence is already set, we will use it as default value else for mf clients we will use residence as default value default_value: account_settings?.tax_residence diff --git a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx index c7797b582945..a045341644d4 100644 --- a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx +++ b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx @@ -351,6 +351,7 @@ const AccountWizard = props => { form_error={form_error} {...passthrough} key={step_index} + should_scroll_to_error_field /> ); }); From e2a89621cfa1834e6af96a5f24d4aa379925126b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Fri, 15 Sep 2023 14:52:08 +0300 Subject: [PATCH 07/28] chore: custom hook for getting error field and scroll to it --- .../personal-details/personal-details.jsx | 46 +++++++++++-- .../src/Configs/personal-details-config.ts | 69 +++++++++---------- 2 files changed, 75 insertions(+), 40 deletions(-) diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index af8c8e5172be..4d30086fa9ce 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -13,7 +13,7 @@ import { isDesktop, isMobile, getIDVNotApplicableOption, - firstPropertyObject, + // firstPropertyObject, removeEmptyPropertiesFromObject, } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; @@ -28,7 +28,40 @@ import { splitValidationResultTypes } from '../real-account-signup/helpers/utils import IDVForm from '../forms/idv-form'; import PersonalDetailsForm from '../forms/personal-details-form'; import FormSubHeader from '../form-sub-header'; -import { ScrollToFieldWithError } from '../forms/scroll-to-field-with-error'; +// import { ScrollToFieldWithError } from '../forms/scroll-to-field-with-error'; + +const useGetFirstInputError = () => { + const [all_page_inputs_names, setAllPageInputsNames] = React.useState([]); + const [current_error_field_name, setCurrentErrorFieldName] = React.useState(''); + + React.useEffect(() => { + const collectInputs = () => { + const inputs = [...document.querySelectorAll('input')]; + setAllPageInputsNames(inputs.map(input => input.name)); + }; + collectInputs(); + }, []); + + React.useEffect(() => { + const el = document.querySelector(`[name="${current_error_field_name}"]`); + (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + el?.focus(); + }, [current_error_field_name]); + + const getFirstError = error_data => { + let current_error = {}; + for (let i = 0; i <= all_page_inputs_names.length; i++) { + if (Object.hasOwn(error_data, all_page_inputs_names[i])) { + current_error = { [all_page_inputs_names[i]]: error_data[[all_page_inputs_names[i]]] }; + setCurrentErrorFieldName([all_page_inputs_names[i]]); + break; + } + } + return current_error; + }; + + return getFirstError; +}; const PersonalDetails = ({ getCurrentStep, @@ -57,6 +90,8 @@ const PersonalDetails = ({ const [should_close_tooltip, setShouldCloseTooltip] = React.useState(false); const is_submit_disabled_ref = React.useRef(true); + const getCurrentError = useGetFirstInputError(); + const isSubmitDisabled = errors => { return selected_step_ref?.current?.isSubmitting || Object.keys(errors).length > 0; }; @@ -110,8 +145,9 @@ const PersonalDetails = ({ const { errors } = splitValidationResultTypes(validate(values)); const error_data = { ...idv_error, ...errors }; checkSubmitStatus(error_data); - // return error_data with only first error field name for highlighting only one field in a moment; - return should_scroll_to_error_field ? firstPropertyObject(error_data) : error_data; + // if should_scroll_to_error_field - return error_data with only first error field name for highlighting only one field in a moment; + // return should_scroll_to_error_field ? firstPropertyObject(error_data) : error_data; + return should_scroll_to_error_field ? getCurrentError(error_data) : error_data; }; const closeToolTip = () => setShouldCloseTooltip(true); @@ -148,7 +184,7 @@ const PersonalDetails = ({ onClick={closeToolTip} data-testid='personal_details_form' > - {should_scroll_to_error_field && } + {/*{should_scroll_to_error_field && }*/} {!is_qualified_for_idv && ( diff --git a/packages/account/src/Configs/personal-details-config.ts b/packages/account/src/Configs/personal-details-config.ts index a02f861755eb..050a903dea43 100644 --- a/packages/account/src/Configs/personal-details-config.ts +++ b/packages/account/src/Configs/personal-details-config.ts @@ -42,29 +42,17 @@ export const personal_details_config = ({ const default_residence = real_account_signup_target === 'maltainvest' ? account_settings?.residence : ''; - //the order of fields should be the same as on the page for proper highlighting fields errors const config = { + account_opening_reason: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.account_opening_reason ?? '', + rules: [['req', localize('Account opening reason is required.')]], + }, salutation: { supported_in: ['iom', 'malta', 'maltainvest'], default_value: account_settings.salutation ?? '', rules: [['req', localize('Salutation is required.')]], }, - 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: [], - }, first_name: { supported_in: ['svg', 'iom', 'malta', 'maltainvest'], default_value: account_settings.first_name ?? '', @@ -96,6 +84,20 @@ export const personal_details_config = ({ ], ], }, + place_of_birth: { + supported_in: ['maltainvest', 'iom', 'malta'], + default_value: account_settings.place_of_birth + ? residence_list.find(item => item.value === account_settings.place_of_birth)?.text + : '', + rules: [['req', localize('Place of birth is required.')]], + }, + citizen: { + supported_in: ['iom', 'malta', 'maltainvest'], + default_value: account_settings.citizen + ? residence_list.find(item => item.value === account_settings.citizen)?.text + : '', + rules: [['req', localize('Citizenship is required')]], + }, phone: { supported_in: ['svg', 'iom', 'malta', 'maltainvest'], default_value: account_settings.phone ?? '', @@ -115,20 +117,6 @@ export const personal_details_config = ({ ], ], }, - place_of_birth: { - supported_in: ['maltainvest', 'iom', 'malta'], - default_value: account_settings.place_of_birth - ? residence_list.find(item => item.value === account_settings.place_of_birth)?.text - : '', - rules: [['req', localize('Place of birth is required.')]], - }, - citizen: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.citizen - ? residence_list.find(item => item.value === account_settings.citizen)?.text - : '', - rules: [['req', localize('Citizenship is required')]], - }, tax_residence: { //if tax_residence is already set, we will use it as default value else for mf clients we will use residence as default value default_value: account_settings?.tax_residence @@ -181,10 +169,21 @@ export const personal_details_config = ({ supported_in: ['maltainvest'], rules: [['confirm', localize('Please confirm your tax information.')]], }, - account_opening_reason: { - supported_in: ['iom', 'malta', 'maltainvest'], - default_value: account_settings.account_opening_reason ?? '', - rules: [['req', localize('Account opening reason is required.')]], + 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: [], }, }; From c3a6105347f12c126ed41ef76734a239775b7fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Mon, 18 Sep 2023 18:28:18 +0300 Subject: [PATCH 08/28] chore: remove one field error and hool --- .../address-details/address-details.tsx | 11 +--- .../forms/scroll-to-field-with-error.tsx | 21 +++++++- .../personal-details/personal-details.jsx | 51 ++----------------- packages/shared/src/utils/object/object.ts | 9 ---- 4 files changed, 25 insertions(+), 67 deletions(-) diff --git a/packages/account/src/Components/address-details/address-details.tsx b/packages/account/src/Components/address-details/address-details.tsx index 67f2a759277f..d85153ea47c6 100644 --- a/packages/account/src/Components/address-details/address-details.tsx +++ b/packages/account/src/Components/address-details/address-details.tsx @@ -16,14 +16,7 @@ import { Text, } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; -import { - isDesktop, - isMobile, - getLocation, - makeCancellablePromise, - PlatformContext, - firstPropertyObject, -} from '@deriv/shared'; +import { isDesktop, isMobile, getLocation, makeCancellablePromise, PlatformContext } from '@deriv/shared'; import { splitValidationResultTypes } from '../real-account-signup/helpers/utils'; import classNames from 'classnames'; import { ScrollToFieldWithError } from 'Components/forms/scroll-to-field-with-error'; @@ -150,7 +143,7 @@ const AddressDetails = ({ const handleValidate = (values: FormikValues) => { const { errors } = splitValidationResultTypes(validate(values)); checkSubmitStatus(errors); - return should_scroll_to_error_field ? firstPropertyObject(errors) : errors; + return errors; }; return ( diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index d7c7a586f504..35f79de9b714 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -2,13 +2,30 @@ import React from 'react'; import { useFormikContext } from 'formik'; export const ScrollToFieldWithError = () => { + const [all_page_inputs_names, setAllPageInputsNames] = React.useState([]); const formik = useFormikContext(); const is_submitting = formik.isSubmitting; - const error_field_name = Object.keys(formik.errors)[0]; + + React.useEffect(() => { + const inputs = [...document.querySelectorAll('input')]; + setAllPageInputsNames(inputs.map(input => input.name)); + }, []); + React.useEffect(() => { - const el = document.querySelector(`[name="${error_field_name}"]`) as HTMLInputElement; + if (!Object.keys(formik.errors).length) return; + let current_error_field_name = ''; + for (let i = 0; i <= all_page_inputs_names.length; i++) { + if (Object.hasOwn(formik.errors, all_page_inputs_names[i])) { + current_error_field_name = all_page_inputs_names[i]; + // setCurrentErrorFieldName(all_page_inputs_names[i]); + break; + } + } + + const el = document.querySelector(`[name="${current_error_field_name}"]`) as HTMLInputElement; (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); el?.focus(); }, [is_submitting]); + return null; }; diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 4d30086fa9ce..2e8a458aafa9 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -9,13 +9,7 @@ import { ThemedScrollbars, Text, } from '@deriv/components'; -import { - isDesktop, - isMobile, - getIDVNotApplicableOption, - // firstPropertyObject, - removeEmptyPropertiesFromObject, -} from '@deriv/shared'; +import { isDesktop, isMobile, getIDVNotApplicableOption, removeEmptyPropertiesFromObject } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { shouldShowIdentityInformation, @@ -28,40 +22,7 @@ import { splitValidationResultTypes } from '../real-account-signup/helpers/utils import IDVForm from '../forms/idv-form'; import PersonalDetailsForm from '../forms/personal-details-form'; import FormSubHeader from '../form-sub-header'; -// import { ScrollToFieldWithError } from '../forms/scroll-to-field-with-error'; - -const useGetFirstInputError = () => { - const [all_page_inputs_names, setAllPageInputsNames] = React.useState([]); - const [current_error_field_name, setCurrentErrorFieldName] = React.useState(''); - - React.useEffect(() => { - const collectInputs = () => { - const inputs = [...document.querySelectorAll('input')]; - setAllPageInputsNames(inputs.map(input => input.name)); - }; - collectInputs(); - }, []); - - React.useEffect(() => { - const el = document.querySelector(`[name="${current_error_field_name}"]`); - (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); - el?.focus(); - }, [current_error_field_name]); - - const getFirstError = error_data => { - let current_error = {}; - for (let i = 0; i <= all_page_inputs_names.length; i++) { - if (Object.hasOwn(error_data, all_page_inputs_names[i])) { - current_error = { [all_page_inputs_names[i]]: error_data[[all_page_inputs_names[i]]] }; - setCurrentErrorFieldName([all_page_inputs_names[i]]); - break; - } - } - return current_error; - }; - - return getFirstError; -}; +import { ScrollToFieldWithError } from '../forms/scroll-to-field-with-error'; const PersonalDetails = ({ getCurrentStep, @@ -90,8 +51,6 @@ const PersonalDetails = ({ const [should_close_tooltip, setShouldCloseTooltip] = React.useState(false); const is_submit_disabled_ref = React.useRef(true); - const getCurrentError = useGetFirstInputError(); - const isSubmitDisabled = errors => { return selected_step_ref?.current?.isSubmitting || Object.keys(errors).length > 0; }; @@ -145,9 +104,7 @@ const PersonalDetails = ({ const { errors } = splitValidationResultTypes(validate(values)); const error_data = { ...idv_error, ...errors }; checkSubmitStatus(error_data); - // if should_scroll_to_error_field - return error_data with only first error field name for highlighting only one field in a moment; - // return should_scroll_to_error_field ? firstPropertyObject(error_data) : error_data; - return should_scroll_to_error_field ? getCurrentError(error_data) : error_data; + return error_data; }; const closeToolTip = () => setShouldCloseTooltip(true); @@ -184,7 +141,7 @@ const PersonalDetails = ({ onClick={closeToolTip} data-testid='personal_details_form' > - {/*{should_scroll_to_error_field && }*/} + {should_scroll_to_error_field && } {!is_qualified_for_idv && ( diff --git a/packages/shared/src/utils/object/object.ts b/packages/shared/src/utils/object/object.ts index aa664fef7494..9ff8296f1e4b 100644 --- a/packages/shared/src/utils/object/object.ts +++ b/packages/shared/src/utils/object/object.ts @@ -119,12 +119,3 @@ export const deepFreeze = (obj: any) => { }); return Object.freeze(obj); }; - -export const firstPropertyObject = (obj: any) => { - const keys = Object.keys(obj); - if (!keys.length) return {}; - let first_key = keys[0]; - return { - [first_key]: obj[first_key], - }; -}; From 890d2dabcf7c875d55aabbdd1a2d190d0c7ec736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Tue, 19 Sep 2023 10:38:04 +0300 Subject: [PATCH 09/28] chore: review comments --- .../src/Components/address-details/address-details.tsx | 8 ++++---- .../src/Components/forms/scroll-to-field-with-error.tsx | 5 +++-- .../src/Components/personal-details/personal-details.jsx | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/account/src/Components/address-details/address-details.tsx b/packages/account/src/Components/address-details/address-details.tsx index d85153ea47c6..7318a12adc84 100644 --- a/packages/account/src/Components/address-details/address-details.tsx +++ b/packages/account/src/Components/address-details/address-details.tsx @@ -1,5 +1,6 @@ -import { Formik, Field, FormikProps, FormikValues } from 'formik'; import React from 'react'; +import classNames from 'classnames'; +import { Formik, Field, FormikProps, FormikValues } from 'formik'; import { StatesList } from '@deriv/api-types'; import { Modal, @@ -15,11 +16,10 @@ import { SelectNative, Text, } from '@deriv/components'; -import { localize, Localize } from '@deriv/translations'; import { isDesktop, isMobile, getLocation, makeCancellablePromise, PlatformContext } from '@deriv/shared'; +import { localize, Localize } from '@deriv/translations'; import { splitValidationResultTypes } from '../real-account-signup/helpers/utils'; -import classNames from 'classnames'; -import { ScrollToFieldWithError } from 'Components/forms/scroll-to-field-with-error'; +import ScrollToFieldWithError from 'Components/forms/scroll-to-field-with-error'; type TAddressDetails = { disabled_items: string[]; diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index 35f79de9b714..5916d1c16c22 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useFormikContext } from 'formik'; -export const ScrollToFieldWithError = () => { +const ScrollToFieldWithError = () => { const [all_page_inputs_names, setAllPageInputsNames] = React.useState([]); const formik = useFormikContext(); const is_submitting = formik.isSubmitting; @@ -17,7 +17,6 @@ export const ScrollToFieldWithError = () => { for (let i = 0; i <= all_page_inputs_names.length; i++) { if (Object.hasOwn(formik.errors, all_page_inputs_names[i])) { current_error_field_name = all_page_inputs_names[i]; - // setCurrentErrorFieldName(all_page_inputs_names[i]); break; } } @@ -29,3 +28,5 @@ export const ScrollToFieldWithError = () => { return null; }; + +export default ScrollToFieldWithError; diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 2e8a458aafa9..524982d0cdcd 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -22,7 +22,7 @@ import { splitValidationResultTypes } from '../real-account-signup/helpers/utils import IDVForm from '../forms/idv-form'; import PersonalDetailsForm from '../forms/personal-details-form'; import FormSubHeader from '../form-sub-header'; -import { ScrollToFieldWithError } from '../forms/scroll-to-field-with-error'; +import ScrollToFieldWithError from '../forms/scroll-to-field-with-error'; const PersonalDetails = ({ getCurrentStep, @@ -130,7 +130,7 @@ const PersonalDetails = ({ onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); }} > - {({ handleSubmit, errors, setFieldValue, touched, values, handleChange, handleBlur }) => ( + {({ handleSubmit, errors, isSubmitting, setFieldValue, touched, values, handleChange, handleBlur }) => ( {({ setRef, height }) => ( handleCancel(values)} From e88f22b45db4a8e4153332acb2460376433d8b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Wed, 4 Oct 2023 17:24:45 +0300 Subject: [PATCH 10/28] chore: remove shaking, added error message --- .../financial-details/financial-details.tsx | 22 ++++-- .../forms/personal-details-form.jsx | 4 + .../forms/scroll-to-field-with-error.tsx | 26 +++++-- .../personal-details/personal-details.jsx | 4 +- .../trading-assessment-dropdown.jsx | 17 ++--- .../trading-assessment-form.jsx | 11 +-- .../trading-assessment-radio-buttons.jsx | 7 ++ .../src/components/checkbox/checkbox.scss | 6 ++ .../src/components/checkbox/checkbox.tsx | 10 ++- .../components/radio-group/radio-group.scss | 6 ++ .../components/radio-group/radio-group.tsx | 3 + packages/core/src/sass/account-wizard.scss | 76 ++----------------- 12 files changed, 95 insertions(+), 97 deletions(-) diff --git a/packages/account/src/Components/financial-details/financial-details.tsx b/packages/account/src/Components/financial-details/financial-details.tsx index 9eed2b5c395f..5d7fad4a6390 100644 --- a/packages/account/src/Components/financial-details/financial-details.tsx +++ b/packages/account/src/Components/financial-details/financial-details.tsx @@ -11,6 +11,7 @@ import { } from '@deriv/components'; import { isDesktop, isMobile } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; +import ScrollToFieldWithError from 'Components/forms/scroll-to-field-with-error'; import { AccountTurnover, IncomeSource, @@ -106,7 +107,17 @@ const FinancialDetails = (props: TFinancialDetails & TFinancialInformationAndTra }} validateOnMount > - {({ handleSubmit, isSubmitting, errors, values, setFieldValue, handleChange, handleBlur, touched }) => { + {({ + handleSubmit, + isSubmitting, + isValid, + errors, + values, + setFieldValue, + handleChange, + handleBlur, + touched, + }) => { const shared_props = { values, handleChange, @@ -126,6 +137,10 @@ const FinancialDetails = (props: TFinancialDetails & TFinancialInformationAndTra height?: number | string; }) => ( + 0 - } + is_disabled={isSubmitting} is_absolute={isMobile()} label={localize('Next')} has_cancel diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx index a709120b8ea6..ea2c14b70e94 100644 --- a/packages/account/src/Components/forms/personal-details-form.jsx +++ b/packages/account/src/Components/forms/personal-details-form.jsx @@ -206,6 +206,7 @@ const PersonalDetailsForm = props => { disabled={ !!values.salutation && isFieldImmutable('salutation', editable_fields) } + has_error={touched.salutation && errors.salutation} /> ))} @@ -529,6 +530,9 @@ const PersonalDetailsForm = props => { )} withTabIndex={0} data-testid='tax_identification_confirm' + has_error={ + touched.tax_identification_confirm && errors.tax_identification_confirm + } /> )} diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index 5916d1c16c22..0690240e9084 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -1,19 +1,30 @@ import React from 'react'; import { useFormikContext } from 'formik'; -const ScrollToFieldWithError = () => { +type TScrollToFieldWithError = { + fields_to_scroll_top?: string[]; + fields_to_scroll_end?: string[]; +}; + +const ScrollToFieldWithError = ({ fields_to_scroll_top, fields_to_scroll_end }: TScrollToFieldWithError) => { const [all_page_inputs_names, setAllPageInputsNames] = React.useState([]); const formik = useFormikContext(); const is_submitting = formik.isSubmitting; + const scrollToElement = (element_name: string, block: ScrollLogicalPosition = 'center') => { + const el = document.querySelector(`[name="${element_name}"]`) as HTMLInputElement; + (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block }); + el?.focus(); + }; + React.useEffect(() => { const inputs = [...document.querySelectorAll('input')]; setAllPageInputsNames(inputs.map(input => input.name)); }, []); React.useEffect(() => { - if (!Object.keys(formik.errors).length) return; let current_error_field_name = ''; + for (let i = 0; i <= all_page_inputs_names.length; i++) { if (Object.hasOwn(formik.errors, all_page_inputs_names[i])) { current_error_field_name = all_page_inputs_names[i]; @@ -21,9 +32,14 @@ const ScrollToFieldWithError = () => { } } - const el = document.querySelector(`[name="${current_error_field_name}"]`) as HTMLInputElement; - (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); - el?.focus(); + if (fields_to_scroll_top?.includes(current_error_field_name)) { + scrollToElement(current_error_field_name, 'start'); + } else if (fields_to_scroll_end?.includes(current_error_field_name)) { + scrollToElement(current_error_field_name, 'end'); + } else { + scrollToElement(current_error_field_name); + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [is_submitting]); return null; diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 03f9932d73c7..1af552db6a29 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -165,7 +165,9 @@ const PersonalDetails = ({ onClick={closeToolTip} data-testid='personal_details_form' > - {should_scroll_to_error_field && } + {should_scroll_to_error_field && ( + + )} {!is_qualified_for_idv && ( diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx index c7c26e01a5fd..bc0d12121cee 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx @@ -11,6 +11,7 @@ const TradingAssessmentDropdown = ({ values, setFieldValue, setEnableNextSection, + has_error, }) => { React.useEffect(() => { checkIfAllFieldsFilled(); @@ -32,19 +33,16 @@ const TradingAssessmentDropdown = ({ {item_list.map(question => ( {() => { + const has_input_error = !values[question.form_control]; return ( onChange(e, question.form_control, setFieldValue)} value={values[question.form_control]} disabled={disabled_items.includes(question.form_control)} + error={has_error && has_input_error && localize('Please select an option')} /> diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx index 55fe6d1d54a8..2ed38c59bce3 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx @@ -34,9 +34,6 @@ const TradingAssessmentForm = ({ const verifyIfAllFieldsFilled = () => { shouldInformUser(!is_section_filled); - setTimeout(() => { - shouldInformUser(false); - }, 500); }; React.useEffect(() => { @@ -133,7 +130,6 @@ const TradingAssessmentForm = ({
@@ -145,13 +141,18 @@ const TradingAssessmentForm = ({ setFieldValue={setFieldValue} setEnableNextSection={setIsSectionFilled} disabled_items={disabled_items ?? []} + has_error={should_inform_user} /> ) : ( handleValueSelection(e, form_control, setFieldValue, values)} + onChange={e => { + handleValueSelection(e, form_control, setFieldValue, values); + shouldInformUser(false); + }} values={values} + has_error={should_inform_user} form_control={form_control} setEnableNextSection={setIsSectionFilled} disabled_items={disabled_items ?? []} diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx index 8d95b0829338..ccff259bbf28 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { Field } from 'formik'; import { Text, RadioGroup } from '@deriv/components'; +import { Localize } from '@deriv/translations'; const TradingAssessmentRadioButton = ({ disabled_items, @@ -10,6 +11,7 @@ const TradingAssessmentRadioButton = ({ values, form_control, setEnableNextSection, + has_error, }) => { React.useEffect(() => { setEnableNextSection(!!values[form_control]); @@ -20,6 +22,11 @@ const TradingAssessmentRadioButton = ({ {text} + {has_error && ( + + + + )} {() => ( , 'value' | 'label'> onChange?: (e: React.ChangeEvent | React.KeyboardEvent) => void; value?: boolean; withTabIndex?: number; + has_error?: boolean; }; const Checkbox = React.forwardRef( @@ -29,6 +30,7 @@ const Checkbox = React.forwardRef( value = false, withTabIndex = 0, greyDisabled = false, + has_error = false, ...otherProps }, ref @@ -84,7 +86,13 @@ const Checkbox = React.forwardRef( {!!checked && } - + {label} diff --git a/packages/components/src/components/radio-group/radio-group.scss b/packages/components/src/components/radio-group/radio-group.scss index f7f5b3628342..54ce0a77c187 100644 --- a/packages/components/src/components/radio-group/radio-group.scss +++ b/packages/components/src/components/radio-group/radio-group.scss @@ -33,10 +33,16 @@ border-width: 4px; border-color: var(--brand-red-coral); } + &--error { + border-color: var(--text-less-prominent); + } } &__label { &--disabled { color: var(--text-disabled); } + &--error { + color: var(--text-loss-danger); + } } } diff --git a/packages/components/src/components/radio-group/radio-group.tsx b/packages/components/src/components/radio-group/radio-group.tsx index 73b376417bcb..deba85081a1e 100644 --- a/packages/components/src/components/radio-group/radio-group.tsx +++ b/packages/components/src/components/radio-group/radio-group.tsx @@ -8,6 +8,7 @@ type TItem = React.HTMLAttributes & { label: string; disabled?: boolean; hidden?: boolean; + has_error?: boolean; }; type TItemWrapper = { should_wrap_items?: boolean; @@ -75,12 +76,14 @@ const RadioGroup = ({ className={classNames('dc-radio-group__circle', { 'dc-radio-group__circle--selected': selected_option === item.props.value, 'dc-radio-group__circle--disabled': item.props.disabled, + 'dc-radio-group__circle--error': item.props.has_error, })} /> {item.props.label} diff --git a/packages/core/src/sass/account-wizard.scss b/packages/core/src/sass/account-wizard.scss index f1953cd59a59..0bcd50ed037a 100644 --- a/packages/core/src/sass/account-wizard.scss +++ b/packages/core/src/sass/account-wizard.scss @@ -585,10 +585,6 @@ display: flex; padding: 0 10rem; - @include desktop() { - justify-content: center; - } - @include mobile() { padding: 0 1.6rem; } @@ -609,6 +605,11 @@ } &__wrapper__question { + .dc-field--error { + padding-left: 0; + margin: 1.6rem 0; + } + @include desktop() { padding: 0 2rem; } @@ -707,73 +708,6 @@ } } -.highlight { - animation: shake 150ms 2 linear; - -moz-animation: shake 150ms 2 linear; - -webkit-animation: shake 150ms 2 linear; - -o-animation: shake 150ms 2 linear; -} - -@keyframes shake { - 0% { - transform: translate(3px, 0); - } - 50% { - transform: translate(-3px, 0); - } - 100% { - transform: translate(0, 0); - } -} - -@-moz-keyframes shake { - 0% { - -moz-transform: translate(3px, 0); - } - 50% { - -moz-transform: translate(-3px, 0); - } - 100% { - -moz-transform: translate(0, 0); - } -} - -@-webkit-keyframes shake { - 0% { - -webkit-transform: translate(3px, 0); - } - 50% { - -webkit-transform: translate(-3px, 0); - } - 100% { - -webkit-transform: translate(0, 0); - } -} - -@-ms-keyframes shake { - 0% { - -ms-transform: translate(3px, 0); - } - 50% { - -ms-transform: translate(-3px, 0); - } - 100% { - -ms-transform: translate(0, 0); - } -} - -@-o-keyframes shake { - 0% { - -o-transform: translate(3px, 0); - } - 50% { - -o-transform: translate(-3px, 0); - } - 100% { - -o-transform: translate(0, 0); - } -} - .field-layout { .trading-assessment__wrapper__dropdown { div:last-child { From addaabcbbc1c3308cd717280977c8449f8e3722e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Fri, 6 Oct 2023 15:52:40 +0300 Subject: [PATCH 11/28] chore: KYC-586/ client cannot add personal details after switching from I want to do it later --- .../forms/confirmation-checkbox/confirmation-checkbox.tsx | 7 ++++--- .../account/src/Components/forms/personal-details-form.jsx | 6 +++++- .../src/Components/personal-details/personal-details.jsx | 6 +++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx index 24015e6bd133..066d4210e921 100644 --- a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx +++ b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Checkbox, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; -import { useFormikContext } from 'formik'; +import { FormikValues, useFormikContext } from 'formik'; /** * Props for the confirmation checkbox component. @@ -40,15 +40,16 @@ export const ConfirmationCheckbox = ({ * * This context provides information about the form's state and helps in managing form behavior. */ - const { setStatus, status } = useFormikContext(); - + const { setFieldValue, setStatus, status, values } = useFormikContext(); const handleChange = () => { // check if status is an object to avoid overwriting the status if it is a string if (typeof status === 'object') setStatus({ ...status, is_confirmed: !status?.is_confirmed }); + if ('confirmation_checkbox' in values) setFieldValue('confirmation_checkbox', !values.confirmation_checkbox); }; return ( {label}} diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx index 0d91f6471b58..c0ffeb64507d 100644 --- a/packages/account/src/Components/forms/personal-details-form.jsx +++ b/packages/account/src/Components/forms/personal-details-form.jsx @@ -66,10 +66,14 @@ const PersonalDetailsForm = props => { }, [should_close_tooltip, handleToolTipStatus, setShouldCloseTooltip]); React.useEffect(() => { + if (!no_confirmation_needed && typeof status === 'object' && !values.confirmation_checkbox) { + setStatus({ ...status, is_confirmed: false }); + } if (no_confirmation_needed && typeof status === 'object' && !status.is_confirmed) { setStatus({ ...status, is_confirmed: true }); } - }, [no_confirmation_needed, setStatus, status]); + //eslint-disable-next-line react-hooks/exhaustive-deps + }, [no_confirmation_needed]); const getNameAndDobLabels = () => { const is_asterisk_needed = is_svg || is_mf || is_rendered_for_onfido || is_qualified_for_idv; diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 8bbfd36eb2ab..35638375946d 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -83,6 +83,10 @@ const PersonalDetails = ({ } errors.document_number = isDocumentNumberValid(document_number, document_type); + + if (document_type.id !== IDV_NOT_APPLICABLE_OPTION.id && !values.confirmation_checkbox) { + errors.confirmation_checkbox = 'Hit the checkbox'; + } return removeEmptyPropertiesFromObject(errors); }; @@ -122,7 +126,7 @@ const PersonalDetails = ({ return ( Date: Fri, 6 Oct 2023 16:37:16 +0300 Subject: [PATCH 12/28] fix: checkbox handling field --- .../forms/confirmation-checkbox/confirmation-checkbox.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx index aa6fb1a2df32..25b04e969a29 100644 --- a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx +++ b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx @@ -40,15 +40,17 @@ export const ConfirmationCheckbox = ({ * * This context provides information about the form's state and helps in managing form behavior. */ - const { setStatus, status } = useFormikContext(); + const { setFieldValue, setStatus, status, values } = useFormikContext(); const handleChange = () => { // check if status is an object to avoid overwriting the status if it is a string if (typeof status === 'object') setStatus({ ...status, is_confirmed: !status?.is_confirmed }); + setFieldValue('confirmation-checkbox', !values.confirmation_checkbox); }; return ( {label}} From 16b178564a28026f17bb70c859dc3cbfe37acf37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Fri, 6 Oct 2023 16:40:53 +0300 Subject: [PATCH 13/28] chore: add chechbox to formik value --- .../confirmation-checkbox.tsx | 6 ++++-- .../src/Components/forms/personal-details-form.jsx | 6 +++++- .../personal-details/personal-details.jsx | 14 ++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx index c67256f7d1ea..ad1eb4b7565b 100644 --- a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx +++ b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useFormikContext } from 'formik'; +import { FormikValues, useFormikContext } from 'formik'; import { Checkbox, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; @@ -41,15 +41,17 @@ export const ConfirmationCheckbox = ({ * * This context provides information about the form's state and helps in managing form behavior. */ - const { setStatus, status } = useFormikContext(); + const { setFieldValue, setStatus, status, values } = useFormikContext(); const handleChange = () => { // check if status is an object to avoid overwriting the status if it is a string if (typeof status === 'object') setStatus({ ...status, is_confirmed: !status?.is_confirmed }); + setFieldValue('confirmation-checkbox', !values.confirmation_checkbox); }; return ( {label}} diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx index 9aeb9dbd0628..7a47931782db 100644 --- a/packages/account/src/Components/forms/personal-details-form.jsx +++ b/packages/account/src/Components/forms/personal-details-form.jsx @@ -66,10 +66,14 @@ const PersonalDetailsForm = props => { }, [should_close_tooltip, handleToolTipStatus, setShouldCloseTooltip]); React.useEffect(() => { + if (!no_confirmation_needed && typeof status === 'object' && !values.confirmation_checkbox) { + setStatus({ ...status, is_confirmed: false }); + } if (no_confirmation_needed && typeof status === 'object' && !status.is_confirmed) { setStatus({ ...status, is_confirmed: true }); } - }, [no_confirmation_needed, setStatus, status]); + //eslint-disable-next-line react-hooks/exhaustive-deps + }, [no_confirmation_needed]); const getNameAndDobLabels = () => { const is_asterisk_needed = is_svg || is_mf || is_rendered_for_onfido || is_qualified_for_idv; diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 60b811af39df..47f7442baccb 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -124,7 +124,7 @@ const PersonalDetails = ({ return ( - {({ handleSubmit, errors, isSubmitting, setFieldValue, touched, values, handleChange, handleBlur, status }) => ( + {({ + handleSubmit, + errors, + isSubmitting, + setFieldValue, + touched, + values, + handleChange, + handleBlur, + status, + }) => ( {({ setRef, height }) => ( Date: Sat, 7 Oct 2023 10:52:10 +0300 Subject: [PATCH 14/28] fix: checkbox field name --- .../forms/confirmation-checkbox/confirmation-checkbox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx index 25b04e969a29..98fa38335aca 100644 --- a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx +++ b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx @@ -45,12 +45,12 @@ export const ConfirmationCheckbox = ({ const handleChange = () => { // check if status is an object to avoid overwriting the status if it is a string if (typeof status === 'object') setStatus({ ...status, is_confirmed: !status?.is_confirmed }); - setFieldValue('confirmation-checkbox', !values.confirmation_checkbox); + setFieldValue('confirmation_checkbox', !values.confirmation_checkbox); }; return ( {label}} From f362df7676e1e65bdd31f0cdc5ba2da0776cfaaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Sun, 8 Oct 2023 19:03:28 +0300 Subject: [PATCH 15/28] chore: eu account confirmation fix --- .../account/src/Components/forms/personal-details-form.jsx | 7 ++++++- .../src/Components/personal-details/personal-details.jsx | 4 ++-- packages/account/src/Configs/personal-details-config.ts | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx index c0ffeb64507d..0f3f116bc9dc 100644 --- a/packages/account/src/Components/forms/personal-details-form.jsx +++ b/packages/account/src/Components/forms/personal-details-form.jsx @@ -66,7 +66,12 @@ const PersonalDetailsForm = props => { }, [should_close_tooltip, handleToolTipStatus, setShouldCloseTooltip]); React.useEffect(() => { - if (!no_confirmation_needed && typeof status === 'object' && !values.confirmation_checkbox) { + if ( + !no_confirmation_needed && + typeof status === 'object' && + !values.confirmation_checkbox && + is_qualified_for_idv + ) { setStatus({ ...status, is_confirmed: false }); } if (no_confirmation_needed && typeof status === 'object' && !status.is_confirmed) { diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 35638375946d..180f6e77fa04 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -85,7 +85,7 @@ const PersonalDetails = ({ errors.document_number = isDocumentNumberValid(document_number, document_type); if (document_type.id !== IDV_NOT_APPLICABLE_OPTION.id && !values.confirmation_checkbox) { - errors.confirmation_checkbox = 'Hit the checkbox'; + errors.confirmation_checkbox = 'error'; } return removeEmptyPropertiesFromObject(errors); }; @@ -126,7 +126,7 @@ const PersonalDetails = ({ return ( Date: Mon, 9 Oct 2023 11:31:40 +0300 Subject: [PATCH 16/28] chore: description for scrolling component --- .../forms/scroll-to-field-with-error.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index 004f41d951fb..87036ba5148e 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -2,11 +2,29 @@ import React from 'react'; import { useFormikContext } from 'formik'; type TScrollToFieldWithError = { + /** + * The fields_to_scroll_top is to scroll to top for exact fields. + */ fields_to_scroll_top?: string[]; + /** + * The fields_to_scroll_end is to scroll to end for exact fields. + */ fields_to_scroll_end?: string[]; + /** + * The should_recollect_inputs_names is to recollect all inputs on the page . + */ should_recollect_inputs_names?: boolean; }; +/** + * A ScrollToFieldWithError for scrolling to field with error. + * + * **Note**: This component is supposed to be used with Formik. + * To use scrolling to field with error, you have to use this component within Formik. + * + * @name ScrollToFieldWithError + * @returns {null} React component that renders nothing + */ const ScrollToFieldWithError = ({ fields_to_scroll_top, fields_to_scroll_end, From c4fb3e3734c4fa4cfed37990a191d77ff8f10815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Mon, 9 Oct 2023 16:54:40 +0300 Subject: [PATCH 17/28] chore: mobile scroll to errors --- .../financial-details/financial-details.tsx | 6 ++--- .../forms/scroll-to-field-with-error.tsx | 2 +- .../personal-details/personal-details.jsx | 3 ++- .../trading-assessment-dropdown.jsx | 5 ++-- .../trading-assessment-form.jsx | 24 +++++++++++++++++-- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/account/src/Components/financial-details/financial-details.tsx b/packages/account/src/Components/financial-details/financial-details.tsx index d6686610696c..cf07c5e610ad 100644 --- a/packages/account/src/Components/financial-details/financial-details.tsx +++ b/packages/account/src/Components/financial-details/financial-details.tsx @@ -79,10 +79,10 @@ const FinancialDetails = (props: TFinancialDetails) => { setRef: (instance: HTMLFormElement) => void; height?: number | string; }) => ( - + { - const inputs = [...document.querySelectorAll('input')]; + const inputs = [...document.querySelectorAll('input, select')] as HTMLInputElement[]; setAllPageInputsNames(inputs.map(input => input.name)); }, [should_recollect_inputs_names]); React.useEffect(() => { diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 8b20346b7e04..60b4a9bd3e40 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -160,7 +160,8 @@ const PersonalDetails = ({ > {should_scroll_to_error_field && ( onChange(e, question.form_control, setFieldValue)} @@ -60,7 +60,7 @@ const TradingAssessmentDropdown = ({ { onChange(e, question.form_control, setFieldValue); @@ -68,6 +68,7 @@ const TradingAssessmentDropdown = ({ value={values[question.form_control]} hide_top_placeholder disabled={disabled_items.includes(question.form_control)} + error={has_error && has_input_error && localize('Please select an option')} /> diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx index 2ed38c59bce3..0c078765aa8a 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx @@ -4,6 +4,7 @@ import { Formik, Form } from 'formik'; import { Button, Modal, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { localize, Localize, getLanguage } from '@deriv/translations'; +import ScrollToFieldWithError from '../forms/scroll-to-field-with-error'; import TradingAssessmentRadioButton from './trading-assessment-radio-buttons.jsx'; import TradingAssessmentDropdown from './trading-assessment-dropdown.jsx'; @@ -115,19 +116,39 @@ const TradingAssessmentForm = ({ } }; + const handleValidate = values => { + const errors = {}; + + if ('cfd_experience' in values && !values.cfd_experience) { + errors.cfd_experience = 'error'; + } + if ('cfd_frequency' in values && !values.cfd_frequency) { + errors.cfd_frequency = 'error'; + } + if ('trading_experience_financial_instruments' in values && !values.trading_experience_financial_instruments) { + errors.trading_experience_financial_instruments = 'error'; + } + if ('trading_frequency_financial_instruments' in values && !values.trading_frequency_financial_instruments) { + errors.trading_frequency_financial_instruments = 'error'; + } + + return errors; + }; + return (
- + {({ setFieldValue, values }) => { const { question_text, form_control, answer_options, questions } = current_question_details.current_question; return ( +
nextButtonHandler(values)} - type='button' text={localize('Next')} large primary From 096e4893f105b0d96c7a609b1b3e1a39260de155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Mon, 9 Oct 2023 17:38:13 +0300 Subject: [PATCH 18/28] chore: handling status for checkbox --- .../src/Components/forms/personal-details-form.jsx | 10 +++------- .../personal-details/personal-details.jsx | 14 +++++++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx index 8f69889a5265..2aeebd718084 100644 --- a/packages/account/src/Components/forms/personal-details-form.jsx +++ b/packages/account/src/Components/forms/personal-details-form.jsx @@ -67,16 +67,12 @@ const PersonalDetailsForm = props => { if ( !no_confirmation_needed && typeof status === 'object' && - !values.confirmation_checkbox && - is_qualified_for_idv + !status.is_confirmed && + values.confirmation_checkbox ) { - setStatus({ ...status, is_confirmed: false }); - } - if (no_confirmation_needed && typeof status === 'object' && !status.is_confirmed) { setStatus({ ...status, is_confirmed: true }); } - //eslint-disable-next-line react-hooks/exhaustive-deps - }, [no_confirmation_needed]); + }, [no_confirmation_needed, setStatus, status, values.confirmation_checkbox]); const getNameAndDobLabels = () => { const is_asterisk_needed = is_svg || is_mf || is_rendered_for_onfido || is_qualified_for_idv; diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 60b4a9bd3e40..4d7151e0d95e 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -51,6 +51,7 @@ const PersonalDetails = ({ }) => { const { account_status, account_settings, residence, real_account_signup_target } = props; const [should_close_tooltip, setShouldCloseTooltip] = React.useState(false); + const [no_confirmation_needed, setNoConfirmationNeeded] = React.useState(false); const isSubmitDisabled = errors => { return selected_step_ref?.current?.isSubmitting || Object.keys(errors).length > 0; @@ -93,6 +94,11 @@ const PersonalDetails = ({ }; const handleValidate = values => { + if (values?.document_type?.id === IDV_NOT_APPLICABLE_OPTION.id) { + setNoConfirmationNeeded(true); + } else { + setNoConfirmationNeeded(false); + } let idv_error = {}; if (is_qualified_for_idv) { idv_error = validateIDV(values); @@ -128,11 +134,11 @@ const PersonalDetails = ({ return ( { onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); }} @@ -226,9 +232,7 @@ const PersonalDetails = ({ should_close_tooltip={should_close_tooltip} setShouldCloseTooltip={setShouldCloseTooltip} should_hide_helper_image={shouldHideHelperImage(values?.document_type?.id)} - no_confirmation_needed={ - values?.document_type?.id === IDV_NOT_APPLICABLE_OPTION.id - } + no_confirmation_needed={no_confirmation_needed} />
From 9b61decd1d323941f114ae108098673ad8184093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Tue, 10 Oct 2023 17:31:27 +0300 Subject: [PATCH 19/28] chore: review comments --- .../forms/scroll-to-field-with-error.tsx | 19 ++--------- .../trading-assessment-dropdown.jsx | 14 ++++---- .../trading-assessment-form.jsx | 34 +++++++++++-------- .../trading-assessment-radio-buttons.jsx | 6 ++-- 4 files changed, 33 insertions(+), 40 deletions(-) diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index a06f76fb93b1..6d384078ca0b 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -2,29 +2,16 @@ import React from 'react'; import { useFormikContext } from 'formik'; type TScrollToFieldWithError = { - /** - * The fields_to_scroll_top is to scroll to top for exact fields. - */ fields_to_scroll_top?: string[]; - /** - * The fields_to_scroll_end is to scroll to end for exact fields. - */ fields_to_scroll_end?: string[]; - /** - * The should_recollect_inputs_names is to recollect all inputs on the page . - */ should_recollect_inputs_names?: boolean; }; /** - * A ScrollToFieldWithError for scrolling to field with error. - * - * **Note**: This component is supposed to be used with Formik. - * To use scrolling to field with error, you have to use this component within Formik. - * - * @name ScrollToFieldWithError - * @returns {null} React component that renders nothing + * A ScrollToFieldWithError is a helper component for scrolling to field with error. + * This component is supposed to be used with Formik and working validation as it uses formik errors object. */ + const ScrollToFieldWithError = ({ fields_to_scroll_top, fields_to_scroll_end, diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx index ef3db82dfdba..9cdc1628fb98 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx @@ -11,7 +11,7 @@ const TradingAssessmentDropdown = ({ values, setFieldValue, setEnableNextSection, - has_error, + error, }) => { React.useEffect(() => { checkIfAllFieldsFilled(); @@ -34,14 +34,16 @@ const TradingAssessmentDropdown = ({ {() => { const has_input_error = !values[question.form_control]; + const should_extend_trading_frequency_field = + question.form_control === 'trading_frequency_financial_instruments' && + question?.question_text.length > 90; + return ( onChange(e, question.form_control, setFieldValue)} value={values[question.form_control]} disabled={disabled_items.includes(question.form_control)} - error={has_error && has_input_error && localize('Please select an option')} + error={has_input_error && error} /> @@ -68,7 +70,7 @@ const TradingAssessmentDropdown = ({ value={values[question.form_control]} hide_top_placeholder disabled={disabled_items.includes(question.form_control)} - error={has_error && has_input_error && localize('Please select an option')} + error={has_input_error && error} /> diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx index 0c078765aa8a..37b09c96518a 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx @@ -119,18 +119,18 @@ const TradingAssessmentForm = ({ const handleValidate = values => { const errors = {}; - if ('cfd_experience' in values && !values.cfd_experience) { - errors.cfd_experience = 'error'; - } - if ('cfd_frequency' in values && !values.cfd_frequency) { - errors.cfd_frequency = 'error'; - } - if ('trading_experience_financial_instruments' in values && !values.trading_experience_financial_instruments) { - errors.trading_experience_financial_instruments = 'error'; - } - if ('trading_frequency_financial_instruments' in values && !values.trading_frequency_financial_instruments) { - errors.trading_frequency_financial_instruments = 'error'; - } + const trading_experience_required_fields = [ + 'cfd_experience', + 'cfd_frequency', + 'trading_experience_financial_instruments', + 'trading_frequency_financial_instruments', + ]; + + trading_experience_required_fields.forEach(field => { + if (field in values && !values[field]) { + errors[field] = 'error'; + } + }); return errors; }; @@ -145,13 +145,14 @@ const TradingAssessmentForm = ({ {({ setFieldValue, values }) => { const { question_text, form_control, answer_options, questions } = current_question_details.current_question; + const has_long_question = questions?.some(question => question.question_text.length > 90); return (
{questions?.length ? ( @@ -162,7 +163,7 @@ const TradingAssessmentForm = ({ setFieldValue={setFieldValue} setEnableNextSection={setIsSectionFilled} disabled_items={disabled_items ?? []} - has_error={should_inform_user} + error={should_inform_user && localize('Please select an option')} /> ) : ( + } form_control={form_control} setEnableNextSection={setIsSectionFilled} disabled_items={disabled_items ?? []} @@ -200,6 +203,7 @@ const TradingAssessmentForm = ({
@@ -309,9 +285,7 @@ const AddressDetails = observer( void, next_step: () => void ) => void; - selected_step_ref?: React.RefObject>; set_currency: boolean; validate: (values: TCurrencySelectorFormProps) => TCurrencySelectorFormProps; value: TCurrencySelectorFormProps; @@ -58,7 +57,6 @@ type TCurrencySelector = React.HTMLAttributes { @@ -114,10 +111,6 @@ const CurrencySelector = observer( item => item.landing_company_shortcode === real_account_signup_target ).length; - const isSubmitDisabled = (values: TCurrencySelectorFormProps) => { - return selected_step_ref?.current?.isSubmitting || !values.currency; - }; - const handleCancel = (values: TCurrencySelectorFormProps) => { const current_step = getCurrentStep() - 1; onSave(current_step, values); @@ -196,14 +189,13 @@ const CurrencySelector = observer( return ( { onSubmit(getCurrentStep ? getCurrentStep() - 1 : null, values, actions.setSubmitting, goToNextStep); }} validate={handleValidate} > - {({ handleSubmit, values }: FormikState & FormikHandlers) => ( + {({ handleSubmit, isSubmitting, values }: FormikState & FormikHandlers) => ( {({ setRef, @@ -288,7 +280,7 @@ const CurrencySelector = observer( ? 'currency-selector--set-currency' : 'currency-selector--deriv-account' } - is_disabled={isSubmitDisabled(values)} + is_disabled={isSubmitting} is_center={false} is_absolute={set_currency} label={getSubmitLabel()} diff --git a/packages/account/src/Components/forms/confirmation-checkbox/__tests__/confirmation-checkbox.spec.tsx b/packages/account/src/Components/forms/confirmation-checkbox/__tests__/confirmation-checkbox.spec.tsx deleted file mode 100644 index 4601c86759be..000000000000 --- a/packages/account/src/Components/forms/confirmation-checkbox/__tests__/confirmation-checkbox.spec.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { Form, Formik } from 'formik'; - -import { render, screen } from '@testing-library/react'; - -import { ConfirmationCheckbox } from '../confirmation-checkbox'; - -describe('ConfirmationCheckbox', () => { - const props: React.ComponentProps = { - label: 'I confirm my details are correct.', - }; - - test('renders checkbox with label', () => { - render( - - - - - - ); - - const checkbox = screen.getByLabelText('I confirm my details are correct.'); - expect(checkbox).toBeInTheDocument(); - }); -}); diff --git a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx b/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx deleted file mode 100644 index 96a5a17d7019..000000000000 --- a/packages/account/src/Components/forms/confirmation-checkbox/confirmation-checkbox.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { FormikValues, useFormikContext } from 'formik'; -import { Checkbox } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; - -/** - * Props for the confirmation checkbox component. - */ -type TConfirmationCheckboxProps = { - /** - * The label of the checkbox. - */ - label: string | React.ReactElement; - /** - * The size of the checkbox label. - */ - label_font_size?: 'xxxxs' | 'xxxs' | 'xxs' | 'xs' | 's' | 'sm' | 'm' | 'l' | 'xl' | 'xxl'; - disabled?: boolean; -}; - -/** - * A checkbox component for confirming an action with an optional description. - * - * This component renders a checkbox that can be used to confirm an action, such as agreeing to terms - * and conditions. It also allows displaying an optional description next to the checkbox. - * - * **Note**: This component is meant to be used with Formik forms. - * To use this component, you must set initialStatus in the Formik form to { is_confirmed: false }. - * - * @name ConfirmationCheckbox - * @returns {JSX.Element} React component that renders a checkbox with a label - */ -export const ConfirmationCheckbox = ({ label, label_font_size, disabled = false }: TConfirmationCheckboxProps) => { - /** - * The formik context for the current form. - * - * This context provides information about the form's state and helps in managing form behavior. - */ - const { setFieldValue, setStatus, status, values, touched, errors } = useFormikContext(); - - const handleChange = () => { - // check if status is an object to avoid overwriting the status if it is a string - if (typeof status === 'object') setStatus({ ...status, is_confirmed: !status?.is_confirmed }); - setFieldValue('confirmation_checkbox', !values.confirmation_checkbox); - }; - - return ( - - ); -}; diff --git a/packages/account/src/Components/forms/confirmation-checkbox/index.ts b/packages/account/src/Components/forms/confirmation-checkbox/index.ts deleted file mode 100644 index 2bac013d8e46..000000000000 --- a/packages/account/src/Components/forms/confirmation-checkbox/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ConfirmationCheckbox } from './confirmation-checkbox'; - -export default ConfirmationCheckbox; diff --git a/packages/account/src/Components/forms/personal-details-form.jsx b/packages/account/src/Components/forms/personal-details-form.jsx index 2aeebd718084..c98b05a0a17d 100644 --- a/packages/account/src/Components/forms/personal-details-form.jsx +++ b/packages/account/src/Components/forms/personal-details-form.jsx @@ -17,7 +17,6 @@ import { import { getLegalEntityName, isDesktop, isMobile, routes, validPhone } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; import PoiNameDobExample from '../../Assets/ic-poi-name-dob-example.svg'; -import ConfirmationCheckbox from './confirmation-checkbox'; import { isFieldImmutable } from '../../Helpers/utils'; import FormBodySection from '../form-body-section'; import { DateOfBirthField, FormInputField } from './form-fields'; @@ -53,8 +52,7 @@ const PersonalDetailsForm = props => { const [is_tax_residence_popover_open, setIsTaxResidencePopoverOpen] = React.useState(false); const [is_tin_popover_open, setIsTinPopoverOpen] = React.useState(false); - const { errors, touched, values, setFieldValue, handleChange, handleBlur, setFieldTouched, setStatus, status } = - useFormikContext(); + const { errors, touched, values, setFieldValue, handleChange, handleBlur, setFieldTouched } = useFormikContext(); React.useEffect(() => { if (should_close_tooltip) { @@ -63,17 +61,6 @@ const PersonalDetailsForm = props => { } }, [should_close_tooltip, handleToolTipStatus, setShouldCloseTooltip]); - React.useEffect(() => { - if ( - !no_confirmation_needed && - typeof status === 'object' && - !status.is_confirmed && - values.confirmation_checkbox - ) { - setStatus({ ...status, is_confirmed: true }); - } - }, [no_confirmation_needed, setStatus, status, values.confirmation_checkbox]); - const getNameAndDobLabels = () => { 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'); @@ -554,11 +541,17 @@ const PersonalDetailsForm = props => { {!no_confirmation_needed && is_qualified_for_idv && ( - } + label_font_size={isMobile() ? 'xxs' : 'xs'} + disabled={is_confirmation_checkbox_disabled} + onChange={handleChange} + has_error={!!(touched.confirmation_checkbox && errors.confirmation_checkbox)} /> )}
diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index 4d7151e0d95e..dfeff28f73a9 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -43,20 +43,14 @@ const PersonalDetails = ({ is_virtual, is_fully_authenticated, account_opening_reason_list, - selected_step_ref, closeRealAccountSignup, has_real_account, - should_scroll_to_error_field = false, ...props }) => { const { account_status, account_settings, residence, real_account_signup_target } = props; const [should_close_tooltip, setShouldCloseTooltip] = React.useState(false); const [no_confirmation_needed, setNoConfirmationNeeded] = React.useState(false); - const isSubmitDisabled = errors => { - return selected_step_ref?.current?.isSubmitting || Object.keys(errors).length > 0; - }; - const handleCancel = values => { const current_step = getCurrentStep() - 1; onSave(current_step, values); @@ -94,11 +88,8 @@ const PersonalDetails = ({ }; const handleValidate = values => { - if (values?.document_type?.id === IDV_NOT_APPLICABLE_OPTION.id) { - setNoConfirmationNeeded(true); - } else { - setNoConfirmationNeeded(false); - } + setNoConfirmationNeeded(values?.document_type?.id === IDV_NOT_APPLICABLE_OPTION.id); + let idv_error = {}; if (is_qualified_for_idv) { idv_error = validateIDV(values); @@ -133,27 +124,15 @@ const PersonalDetails = ({ return ( { onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); }} > - {({ - handleSubmit, - errors, - isSubmitting, - setFieldValue, - touched, - values, - handleChange, - handleBlur, - status, - }) => ( + {({ handleSubmit, errors, isSubmitting, setFieldValue, touched, values, handleChange, handleBlur }) => ( {({ setRef, height }) => (
- {should_scroll_to_error_field && ( - - )} + {!is_qualified_for_idv && ( @@ -220,7 +197,7 @@ const PersonalDetails = ({ is_mf={is_mf} is_qualified_for_idv={is_qualified_for_idv} editable_fields={getEditableFields( - status?.is_confirmed, + values.confirmation_checkbox, values?.document_type?.id )} residence_list={residence_list} @@ -241,11 +218,7 @@ const PersonalDetails = ({ handleCancel(values)} diff --git a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx index d897794c72b0..a7512e6ab496 100644 --- a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx +++ b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx @@ -359,7 +359,6 @@ const AccountWizard = props => { form_error={form_error} {...passthrough} key={step_index} - should_scroll_to_error_field /> ); }); From cdf5093288b8de053f14d42e5a5a556c1142a10f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Thu, 12 Oct 2023 16:41:37 +0300 Subject: [PATCH 21/28] chore: error message, scroll for checkboxes --- .../financial-details/financial-details.tsx | 11 +- .../forms/scroll-to-field-with-error.tsx | 8 +- .../trading-assessment-dropdown.jsx | 14 +- .../trading-assessment-form.jsx | 208 ++++++++++-------- .../trading-assessment-radio-buttons.jsx | 8 +- packages/core/src/sass/account-wizard.scss | 11 +- 6 files changed, 144 insertions(+), 116 deletions(-) diff --git a/packages/account/src/Components/financial-details/financial-details.tsx b/packages/account/src/Components/financial-details/financial-details.tsx index cf07c5e610ad..957a78f5fe7f 100644 --- a/packages/account/src/Components/financial-details/financial-details.tsx +++ b/packages/account/src/Components/financial-details/financial-details.tsx @@ -60,6 +60,11 @@ const FinancialDetails = (props: TFinancialDetails) => { return errors; }; + const fields_to_scroll_to_top = isMobile() + ? ['income_source', 'account_turnover', 'estimated_worth'] + : ['income_source']; + const fields_to_scroll_to_end = isMobile() ? [] : ['account_turnover', 'estimated_worth']; + return ( { }} validateOnMount > - {({ handleSubmit, isSubmitting, errors, values }) => { + {({ handleSubmit, isSubmitting, values }) => { return ( {({ @@ -81,8 +86,8 @@ const FinancialDetails = (props: TFinancialDetails) => { }) => ( { + if (!element_name) return; const el = document.querySelector(`[name="${element_name}"]`) as HTMLInputElement; (el?.parentElement ?? el)?.scrollIntoView({ behavior: 'smooth', block }); - el?.focus(); + if (el?.type !== 'radio') el?.focus(); }; React.useEffect(() => { diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx index 9cdc1628fb98..ce1fa3d196a3 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-dropdown.jsx @@ -1,8 +1,8 @@ import React from 'react'; +import classNames from 'classnames'; import { Field } from 'formik'; import { DesktopWrapper, Dropdown, MobileWrapper, Text, SelectNative } from '@deriv/components'; -import { localize, getLanguage } from '@deriv/translations'; -import classNames from 'classnames'; +import { localize } from '@deriv/translations'; const TradingAssessmentDropdown = ({ disabled_items, @@ -11,7 +11,6 @@ const TradingAssessmentDropdown = ({ values, setFieldValue, setEnableNextSection, - error, }) => { React.useEffect(() => { checkIfAllFieldsFilled(); @@ -32,8 +31,7 @@ const TradingAssessmentDropdown = ({
{item_list.map(question => ( - {() => { - const has_input_error = !values[question.form_control]; + {({ field, meta }) => { const should_extend_trading_frequency_field = question.form_control === 'trading_frequency_financial_instruments' && question?.question_text.length > 90; @@ -42,6 +40,7 @@ const TradingAssessmentDropdown = ({ onChange(e, question.form_control, setFieldValue)} value={values[question.form_control]} disabled={disabled_items.includes(question.form_control)} - error={has_input_error && error} + error={meta.touched && meta.error} /> @@ -60,6 +59,7 @@ const TradingAssessmentDropdown = ({ {question?.question_text} diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx index 37b09c96518a..ef4c40343890 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx @@ -2,8 +2,8 @@ import classNames from 'classnames'; import React from 'react'; import { Formik, Form } from 'formik'; import { Button, Modal, Text } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; -import { localize, Localize, getLanguage } from '@deriv/translations'; +import { isEmptyObject, isMobile } from '@deriv/shared'; +import { localize, Localize } from '@deriv/translations'; import ScrollToFieldWithError from '../forms/scroll-to-field-with-error'; import TradingAssessmentRadioButton from './trading-assessment-radio-buttons.jsx'; import TradingAssessmentDropdown from './trading-assessment-dropdown.jsx'; @@ -20,7 +20,6 @@ const TradingAssessmentForm = ({ is_independent_section, }) => { const [is_section_filled, setIsSectionFilled] = React.useState(false); - const [should_inform_user, shouldInformUser] = React.useState(false); const [current_question_details, setCurrentQuestionDetails] = React.useState({ current_question_index: 0, current_question: {}, @@ -33,10 +32,6 @@ const TradingAssessmentForm = ({ ? current_question_details.current_question_index !== 0 : true; - const verifyIfAllFieldsFilled = () => { - shouldInformUser(!is_section_filled); - }; - React.useEffect(() => { setCurrentQuestionDetails(prevState => { return { @@ -109,7 +104,6 @@ const TradingAssessmentForm = ({ const isAssessmentCompleted = answers => Object.values(answers).every(answer => Boolean(answer)); const nextButtonHandler = values => { - verifyIfAllFieldsFilled(); if (is_section_filled) { if (isAssessmentCompleted(values) && stored_items === last_question_index) onSubmit(values); else displayNextPage(); @@ -119,18 +113,41 @@ const TradingAssessmentForm = ({ const handleValidate = values => { const errors = {}; - const trading_experience_required_fields = [ - 'cfd_experience', - 'cfd_frequency', - 'trading_experience_financial_instruments', - 'trading_frequency_financial_instruments', - ]; - - trading_experience_required_fields.forEach(field => { - if (field in values && !values[field]) { - errors[field] = 'error'; - } - }); + if (!values.risk_tolerance && current_question_details.current_question.section === 'risk_tolerance') { + errors.risk_tolerance = 'error'; + } + if ( + !values.source_of_experience && + current_question_details.current_question.section === 'source_of_experience' + ) { + errors.source_of_experience = 'error'; + } + if (current_question_details.current_question.section === 'trading_experience') { + const trading_experience_required_fields = [ + 'cfd_experience', + 'cfd_frequency', + 'trading_experience_financial_instruments', + 'trading_frequency_financial_instruments', + ]; + trading_experience_required_fields.forEach(field => { + if (!values[field]) { + errors[field] = localize('Please select an option'); + } + }); + } + if (current_question_details.current_question.section === 'trading_knowledge') { + const trading_knowledge_required_fields = [ + 'cfd_trading_definition', + 'leverage_impact_trading', + 'leverage_trading_high_risk_stop_loss', + 'required_initial_margin', + ]; + trading_knowledge_required_fields.forEach(field => { + if (!values[field] && current_question_details.current_question.form_control === field) { + errors[field] = 'error'; + } + }); + } return errors; }; @@ -140,83 +157,90 @@ const TradingAssessmentForm = ({ -
- - {({ setFieldValue, values }) => { - const { question_text, form_control, answer_options, questions } = - current_question_details.current_question; - const has_long_question = questions?.some(question => question.question_text.length > 90); - - return ( - - -
- {questions?.length ? ( - - ) : ( - { - handleValueSelection(e, form_control, setFieldValue, values); - shouldInformUser(false); - }} - values={values} - error={ - should_inform_user && - } - form_control={form_control} - setEnableNextSection={setIsSectionFilled} - disabled_items={disabled_items ?? []} - /> - )} -
- - - {should_display_previous_button && ( + + {({ errors, setFieldValue, values }) => { + const { question_text, form_control, answer_options, questions } = + current_question_details.current_question; + const has_long_question = questions?.some(question => question.question_text.length > 90); + + return ( + + + + + * {!isEmptyObject(errors) && } + + +
+ + +
+ {questions?.length ? ( + + ) : ( + { + handleValueSelection(e, form_control, setFieldValue, values); + }} + values={values} + form_control={form_control} + setEnableNextSection={setIsSectionFilled} + disabled_items={disabled_items ?? []} + /> + )} +
+ + + {should_display_previous_button && ( +
+
+
+ +
+ + ); + }} +
); }; diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx index 0ec91d3430ed..5616508246cd 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-radio-buttons.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { Field } from 'formik'; import { Text, RadioGroup } from '@deriv/components'; -import { Localize } from '@deriv/translations'; const TradingAssessmentRadioButton = ({ disabled_items, @@ -11,7 +10,6 @@ const TradingAssessmentRadioButton = ({ values, form_control, setEnableNextSection, - error, }) => { React.useEffect(() => { setEnableNextSection(!!values[form_control]); @@ -22,16 +20,12 @@ const TradingAssessmentRadioButton = ({ {text} - {error && ( - - {error} - - )} {() => ( Date: Thu, 12 Oct 2023 18:03:34 +0300 Subject: [PATCH 22/28] trigger build From d4e63f07202a030f010486e56a2d686566c71687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Fri, 13 Oct 2023 11:47:09 +0300 Subject: [PATCH 23/28] chore: review comments --- .../financial-details/financial-details.tsx | 8 +++---- .../forms/personal-details-form.jsx | 2 +- .../forms/scroll-to-field-with-error.tsx | 21 +++++++------------ .../personal-details/personal-details.jsx | 2 +- .../trading-assessment-dropdown.jsx | 3 ++- .../trading-assessment-form.jsx | 7 +++++-- .../src/Constants/trading-assessment.ts | 1 + 7 files changed, 21 insertions(+), 23 deletions(-) create mode 100644 packages/account/src/Constants/trading-assessment.ts diff --git a/packages/account/src/Components/financial-details/financial-details.tsx b/packages/account/src/Components/financial-details/financial-details.tsx index 957a78f5fe7f..d3bfe2efcc8c 100644 --- a/packages/account/src/Components/financial-details/financial-details.tsx +++ b/packages/account/src/Components/financial-details/financial-details.tsx @@ -60,10 +60,10 @@ const FinancialDetails = (props: TFinancialDetails) => { return errors; }; - const fields_to_scroll_to_top = isMobile() + const fields_to_scroll_top = isMobile() ? ['income_source', 'account_turnover', 'estimated_worth'] : ['income_source']; - const fields_to_scroll_to_end = isMobile() ? [] : ['account_turnover', 'estimated_worth']; + const fields_to_scroll_bottom = isMobile() ? [] : ['account_turnover', 'estimated_worth']; return ( { }) => (
{ disabled={ !!values.salutation && isFieldImmutable('salutation', editable_fields) } - has_error={touched.salutation && errors.salutation} + has_error={!!(touched.salutation && errors.salutation)} /> ))} diff --git a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx index 91a9e3058724..a982e4777841 100644 --- a/packages/account/src/Components/forms/scroll-to-field-with-error.tsx +++ b/packages/account/src/Components/forms/scroll-to-field-with-error.tsx @@ -3,18 +3,17 @@ import { useFormikContext } from 'formik'; type TScrollToFieldWithError = { fields_to_scroll_top?: string[]; - fields_to_scroll_end?: string[]; + fields_to_scroll_bottom?: string[]; should_recollect_inputs_names?: boolean; }; const ScrollToFieldWithError = ({ fields_to_scroll_top, - fields_to_scroll_end, + fields_to_scroll_bottom, should_recollect_inputs_names = false, }: TScrollToFieldWithError) => { const [all_page_inputs_names, setAllPageInputsNames] = React.useState([]); - const formik = useFormikContext(); - const is_submitting = formik.isSubmitting; + const { errors, isSubmitting } = useFormikContext(); const scrollToElement = (element_name: string, block: ScrollLogicalPosition = 'center') => { if (!element_name) return; const el = document.querySelector(`[name="${element_name}"]`) as HTMLInputElement; @@ -27,24 +26,18 @@ const ScrollToFieldWithError = ({ setAllPageInputsNames(inputs.map(input => input.name)); }, [should_recollect_inputs_names]); React.useEffect(() => { - let current_error_field_name = ''; - - for (let i = 0; i <= all_page_inputs_names.length; i++) { - if (Object.hasOwn(formik.errors, all_page_inputs_names[i])) { - current_error_field_name = all_page_inputs_names[i]; - break; - } - } + const current_error_field_name = + all_page_inputs_names.find(input_name => Object.hasOwn(errors, input_name)) || ''; if (fields_to_scroll_top?.includes(current_error_field_name)) { scrollToElement(current_error_field_name, 'start'); - } else if (fields_to_scroll_end?.includes(current_error_field_name)) { + } else if (fields_to_scroll_bottom?.includes(current_error_field_name)) { scrollToElement(current_error_field_name, 'end'); } else { scrollToElement(current_error_field_name); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [is_submitting]); + }, [isSubmitting]); return null; }; diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index e5ed90032f24..47e657d283fb 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -141,7 +141,7 @@ const PersonalDetails = ({ data-testid='personal_details_form' > { const should_extend_trading_frequency_field = question.form_control === 'trading_frequency_financial_instruments' && - question?.question_text.length > 90; + question?.question_text.length > max_question_text_length; return ( diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx index ef4c40343890..a44c9df85dbb 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx @@ -4,6 +4,7 @@ import { Formik, Form } from 'formik'; import { Button, Modal, Text } from '@deriv/components'; import { isEmptyObject, isMobile } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; +import { max_question_text_length } from '../../Constants/trading-assessment'; import ScrollToFieldWithError from '../forms/scroll-to-field-with-error'; import TradingAssessmentRadioButton from './trading-assessment-radio-buttons.jsx'; import TradingAssessmentDropdown from './trading-assessment-dropdown.jsx'; @@ -161,13 +162,15 @@ const TradingAssessmentForm = ({ {({ errors, setFieldValue, values }) => { const { question_text, form_control, answer_options, questions } = current_question_details.current_question; - const has_long_question = questions?.some(question => question.question_text.length > 90); + const has_long_question = questions?.some( + question => question.question_text.length > max_question_text_length + ); return ( Date: Fri, 13 Oct 2023 14:18:52 +0300 Subject: [PATCH 24/28] fix: failing tests --- .../currency-selector/currency-selector.tsx | 4 +-- .../__tests__/idv-document-submit.spec.tsx | 4 ++- .../idv-document-submit.tsx | 19 ++++++-------- .../idv-doc-submit-on-signup.tsx | 25 ++++++------------- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/packages/account/src/Components/currency-selector/currency-selector.tsx b/packages/account/src/Components/currency-selector/currency-selector.tsx index 37c6ed926eed..f87bb044f5f2 100644 --- a/packages/account/src/Components/currency-selector/currency-selector.tsx +++ b/packages/account/src/Components/currency-selector/currency-selector.tsx @@ -195,7 +195,7 @@ const CurrencySelector = observer( }} validate={handleValidate} > - {({ handleSubmit, isSubmitting, values }: FormikState & FormikHandlers) => ( + {({ handleSubmit, values }: FormikState & FormikHandlers) => ( {({ setRef, @@ -280,7 +280,7 @@ const CurrencySelector = observer( ? 'currency-selector--set-currency' : 'currency-selector--deriv-account' } - is_disabled={isSubmitting} + is_disabled={!values.currency} is_center={false} is_absolute={set_currency} label={getSubmitLabel()} 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 a4c01889c3fa..f201bec27349 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 @@ -162,7 +162,9 @@ describe('', () => { }); fireEvent.click(confirmation_checkbox); - expect(verifyBtn).toBeEnabled(); + await waitFor(() => { + expect(verifyBtn).toBeEnabled(); + }); 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 026308a1b066..b1249555623c 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 @@ -58,6 +58,7 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c example_format: '', }, document_number: '', + confirmation_checkbox: false, ...form_initial_values, }; @@ -103,6 +104,10 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c errors.last_name = validateName(values.last_name); } + if (!values.confirmation_checkbox) { + errors.confirmation_checkbox = 'error'; + } + return removeEmptyPropertiesFromObject(errors); }; @@ -143,14 +148,7 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c }; return ( - + {({ dirty, errors, @@ -162,7 +160,6 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c setFieldValue, touched, values, - status, }) => (
@@ -188,7 +185,7 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c })} is_qualified_for_idv should_hide_helper_image={shouldHideHelperImage(values?.document_type?.id)} - editable_fields={status?.is_confirmed ? [] : changeable_fields} + editable_fields={values.confirmation_checkbox ? [] : changeable_fields} />
@@ -202,7 +199,7 @@ const IdvDocumentSubmit = observer(({ handleBack, handleViewComplete, selected_c type='submit' onClick={handleSubmit} has_effect - is_disabled={!dirty || isSubmitting || !isValid || !status?.is_confirmed} + is_disabled={!dirty || isSubmitting || !isValid} text={localize('Verify')} large primary diff --git a/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.tsx b/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.tsx index 4df0df924f3e..ea9f8a41a1f9 100644 --- a/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.tsx +++ b/packages/account/src/Components/poi/poi-form-on-signup/idv-doc-submit-on-signup/idv-doc-submit-on-signup.tsx @@ -59,6 +59,10 @@ export const IdvDocSubmitOnSignup = ({ errors.last_name = validateName(values.last_name); } + if (!values.confirmation_checkbox) { + errors.confirmation_checkbox = 'error'; + } + return removeEmptyPropertiesFromObject(errors); }; @@ -78,6 +82,7 @@ export const IdvDocSubmitOnSignup = ({ value: '', example_format: '', }, + confirmation_checkbox: false, document_number: '', ...form_initial_values, }; @@ -92,22 +97,8 @@ export const IdvDocSubmitOnSignup = ({ validateOnMount validateOnChange validateOnBlur - initialStatus={{ - is_confirmed: false, - }} > - {({ - errors, - handleBlur, - handleChange, - isSubmitting, - isValid, - setFieldValue, - touched, - dirty, - values, - status, - }) => ( + {({ errors, handleBlur, handleChange, isSubmitting, isValid, setFieldValue, touched, dirty, values }) => (
@@ -132,7 +123,7 @@ export const IdvDocSubmitOnSignup = ({ is_qualified_for_idv is_appstore should_hide_helper_image={shouldHideHelperImage(values?.document_type?.id)} - editable_fields={status?.is_confirmed ? [] : changeable_fields} + editable_fields={values.confirmation_checkbox ? [] : changeable_fields} residence_list={residence_list} />
@@ -141,7 +132,7 @@ export const IdvDocSubmitOnSignup = ({ className='proof-of-identity__submit-button' type='submit' has_effect - is_disabled={!dirty || isSubmitting || !isValid || !status?.is_confirmed} + is_disabled={!dirty || isSubmitting || !isValid} text={localize('Next')} large primary From 2751e53dc46029c8a9b096a508f54e250d79e85b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Thu, 19 Oct 2023 11:44:11 +0300 Subject: [PATCH 25/28] fix: unnecessary field removed from data to BE --- .../src/Components/address-details/address-details.tsx | 2 +- .../trading-assessment/trading-assessment-dropdown.jsx | 4 ++-- .../Components/trading-assessment/trading-assessment-form.jsx | 4 ++-- packages/account/src/Constants/trading-assessment.ts | 2 +- .../src/App/Containers/RealAccountSignup/account-wizard.jsx | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/account/src/Components/address-details/address-details.tsx b/packages/account/src/Components/address-details/address-details.tsx index df7ab55d12d1..de8aedd270d7 100644 --- a/packages/account/src/Components/address-details/address-details.tsx +++ b/packages/account/src/Components/address-details/address-details.tsx @@ -121,7 +121,6 @@ const AddressDetails = observer( {({ handleSubmit, - errors, isSubmitting, values, setFieldValue, @@ -136,6 +135,7 @@ const AddressDetails = observer( setRef: (instance: HTMLFormElement) => void; height: number | string; }) => ( + //noValidate here is for skipping default browser validation { const should_extend_trading_frequency_field = question.form_control === 'trading_frequency_financial_instruments' && - question?.question_text.length > max_question_text_length; + question?.question_text.length > MAX_QUESTION_TEXT_LENGTH; return ( diff --git a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx index a44c9df85dbb..3507a155ef79 100644 --- a/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx +++ b/packages/account/src/Components/trading-assessment/trading-assessment-form.jsx @@ -4,7 +4,7 @@ import { Formik, Form } from 'formik'; import { Button, Modal, Text } from '@deriv/components'; import { isEmptyObject, isMobile } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; -import { max_question_text_length } from '../../Constants/trading-assessment'; +import { MAX_QUESTION_TEXT_LENGTH } from '../../Constants/trading-assessment'; import ScrollToFieldWithError from '../forms/scroll-to-field-with-error'; import TradingAssessmentRadioButton from './trading-assessment-radio-buttons.jsx'; import TradingAssessmentDropdown from './trading-assessment-dropdown.jsx'; @@ -163,7 +163,7 @@ const TradingAssessmentForm = ({ const { question_text, form_control, answer_options, questions } = current_question_details.current_question; const has_long_question = questions?.some( - question => question.question_text.length > max_question_text_length + question => question.question_text.length > MAX_QUESTION_TEXT_LENGTH ); return ( diff --git a/packages/account/src/Constants/trading-assessment.ts b/packages/account/src/Constants/trading-assessment.ts index 23bc1df6bf56..764902ed9a0a 100644 --- a/packages/account/src/Constants/trading-assessment.ts +++ b/packages/account/src/Constants/trading-assessment.ts @@ -1 +1 @@ -export const max_question_text_length = 90; +export const MAX_QUESTION_TEXT_LENGTH = 90; diff --git a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx index a7512e6ab496..0aae229ec628 100644 --- a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx +++ b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx @@ -219,6 +219,7 @@ const AccountWizard = props => { delete clone?.tax_identification_confirm; delete clone?.agreed_tnc; delete clone?.agreed_tos; + delete clone?.confirmation_checkbox; // BE does not accept empty strings for TIN // so we remove it from the payload if it is empty in case of optional TIN field From b25768012890f821ffea7adb9fdcf78d1cc87b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Fri, 20 Oct 2023 15:12:45 +0300 Subject: [PATCH 26/28] fix: placeholders color with error --- packages/account/src/Components/forms/idv-form.tsx | 9 +++++++-- .../components/src/components/dropdown/dropdown.scss | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/account/src/Components/forms/idv-form.tsx b/packages/account/src/Components/forms/idv-form.tsx index 9febec547d50..36dfdc4069e6 100644 --- a/packages/account/src/Components/forms/idv-form.tsx +++ b/packages/account/src/Components/forms/idv-form.tsx @@ -204,7 +204,9 @@ const IDVForm = ({ } disabled={!values.document_type.id} error={ - (touched.document_number && errors.document_number) || + (values.document_type.id && + touched.document_number && + errors.document_number) || errors.error_message } autoComplete='off' @@ -218,7 +220,10 @@ const IDVForm = ({ } className='additional-field' required - label={generatePlaceholderText(selected_doc)} + label={ + values.document_type.id && + generatePlaceholderText(selected_doc) + } /> {values.document_type.additional?.display_name && ( Date: Mon, 23 Oct 2023 11:42:00 +0300 Subject: [PATCH 27/28] fix: red error placegolder for input --- packages/components/src/components/input/input.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/components/src/components/input/input.scss b/packages/components/src/components/input/input.scss index fe19c90bb935..bb5b2c0fcead 100644 --- a/packages/components/src/components/input/input.scss +++ b/packages/components/src/components/input/input.scss @@ -42,6 +42,11 @@ label { color: var(--brand-red-coral) !important; } + + & ::placeholder { + color: var(--text-loss-danger) !important; + opacity: 1 !important; + } } &__container { display: flex; From f3a9c72dd1936ca37dbab1306c04f204cb5ca649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cyauheni-kryzhyk-deriv=E2=80=9D?= <“yauheni@deriv.me”> Date: Mon, 23 Oct 2023 14:19:02 +0300 Subject: [PATCH 28/28] fix: failing tests --- .../__tests__/idv-document-submit.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f201bec27349..6837074c2440 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 @@ -104,7 +104,7 @@ describe('', () => { expect(mock_props.handleBack).toHaveBeenCalledTimes(1); const document_type_input = screen.getByLabelText('Choose the document type'); - const document_number_input = screen.getByLabelText('Enter your document number'); + const document_number_input = screen.getByPlaceholderText('Enter your document number'); expect(document_number_input).toBeDisabled(); expect(screen.queryByText('Test document 1 name')).not.toBeInTheDocument(); expect(screen.queryByText('Test document 2 name')).not.toBeInTheDocument(); @@ -139,7 +139,7 @@ describe('', () => { const document_type_input = screen.getByRole('combobox'); expect(document_type_input.name).toBe('document_type'); - const document_number_input = screen.getByLabelText('Enter your document number'); + const document_number_input = screen.getByPlaceholderText('Enter your document number'); expect(document_number_input.name).toBe('document_number'); expect(document_number_input).toBeDisabled();