From f80071ba0fce18819fe5c91130c5ef940bf23537 Mon Sep 17 00:00:00 2001 From: amina-deriv <84661147+amina-deriv@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:29:32 +0400 Subject: [PATCH] [COJ]/Amina/COJ-445/feat: add confirmation checkbox for spanish residents (#13114) * fix: checkbox for spanish residents * feat: add testcase for checkox * feat: add testcase for checkox * fix: style for checkbox * fix: delete form value * fix: form value * fix: stylelint fix * fix: test case * fix: spain * fix: hooks * fix: test * refactor: refactors hook for account_opening_signup_declaration_required * fix: removes api package usage * docs: adds temporary type * fix: fixes minor issue * test: updates test * fix: change flag based on BE * fix: text size --------- Co-authored-by: Shaheer Co-authored-by: Shaheer <122449658+shaheer-deriv@users.noreply.github.com> --- .../__tests__/terms-of-use.spec.tsx | 78 ++++++ .../Components/terms-of-use/terms-of-use.scss | 12 +- .../Components/terms-of-use/terms-of-use.tsx | 244 ++++++++++-------- .../src/Configs/terms-of-use-config.ts | 8 +- .../src/components/checkbox/checkbox.tsx | 4 +- .../RealAccountSignup/account-wizard-form.js | 2 +- .../RealAccountSignup/account-wizard.jsx | 7 +- .../useResidenceSelfDeclaration.spec.tsx | 44 ++++ packages/hooks/src/index.ts | 1 + .../hooks/src/useResidenceSelfDeclaration.ts | 14 + packages/stores/types.ts | 41 ++- 11 files changed, 333 insertions(+), 122 deletions(-) create mode 100644 packages/hooks/src/__tests__/useResidenceSelfDeclaration.spec.tsx create mode 100644 packages/hooks/src/useResidenceSelfDeclaration.ts diff --git a/packages/account/src/Components/terms-of-use/__tests__/terms-of-use.spec.tsx b/packages/account/src/Components/terms-of-use/__tests__/terms-of-use.spec.tsx index 8f7eae701080..5db704aeb1d9 100644 --- a/packages/account/src/Components/terms-of-use/__tests__/terms-of-use.spec.tsx +++ b/packages/account/src/Components/terms-of-use/__tests__/terms-of-use.spec.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { isDesktop, isMobile } from '@deriv/shared'; +import { useResidenceSelfDeclaration } from '@deriv/hooks'; + import TermsOfUse from '../terms-of-use'; jest.mock('@deriv/shared', () => ({ @@ -10,6 +12,15 @@ jest.mock('@deriv/shared', () => ({ isMobile: jest.fn(() => false), })); +jest.mock('@deriv/hooks', () => { + return { + ...jest.requireActual('@deriv/hooks'), + useResidenceSelfDeclaration: jest.fn(() => ({ + is_residence_self_declaration_required: true, + })), + }; +}); + describe('', () => { const agree_check = /i agree to the/i; const law_title = 'Jurisdiction and choice of law'; @@ -30,8 +41,10 @@ describe('', () => { goToPreviousStep: jest.fn(), onCancel: jest.fn(), onSubmit: jest.fn(), + onSave: jest.fn(), real_account_signup_target: 'svg', value: { agreed_tos: false, agreed_tnc: false }, + residence: 'id', }; const commonFieldsCheck = () => { @@ -109,4 +122,69 @@ describe('', () => { expect(el_fatca_accept).toBeInTheDocument(); expect(el_fatca_reject).toBeInTheDocument(); }); + + it('should render TermsOfUse component with spain residence confirmation checkbox if residence is spain', () => { + (isMobile as jest.Mock).mockReturnValue(true); + (isDesktop as jest.Mock).mockReturnValue(false); + + mock_props.real_account_signup_target = 'maltainvest'; + mock_props.residence = 'es'; + + render(); + + commonFieldsCheck(); + expect( + screen.getByText( + 'I hereby confirm that my request for opening an account with Deriv Investments (Europe) Ltd is made on my own initiative.' + ) + ).toBeInTheDocument(); + }); + + it('should enable add account button only if spain residence confirmation checkbox is checked for spain clients', () => { + mock_props.residence = 'es'; + mock_props.value = { ...mock_props.value, fatca_declaration: '1' }; + + render(); + const pep_checkbox = screen.getByRole('checkbox', { + name: 'I am not a PEP, and I have not been a PEP in the last 12 months.', + }); + const terms_and_condition_checkbox = screen.getByRole('checkbox', { + name: 'I agree to the terms and conditions .', + }); + const spain_checkbox = screen.getByRole('checkbox', { + name: 'I hereby confirm that my request for opening an account with Deriv Investments (Europe) Ltd is made on my own initiative.', + }); + const add_btn = screen.getByRole('button', { name: /add account/i }); + + commonFieldsCheck(); + expect(add_btn).toBeDisabled(); + + userEvent.click(pep_checkbox); + userEvent.click(terms_and_condition_checkbox); + + expect( + screen.getByText( + 'I hereby confirm that my request for opening an account with Deriv Investments (Europe) Ltd is made on my own initiative.' + ) + ).toBeInTheDocument(); + expect(spain_checkbox).toBeInTheDocument(); + userEvent.click(spain_checkbox); + expect(spain_checkbox).toBeChecked(); + expect(add_btn).toBeEnabled(); + }); + + it('should not display spain residence confirmation checkbox if residence is indonesia', () => { + (isMobile as jest.Mock).mockReturnValue(true); + (isDesktop as jest.Mock).mockReturnValue(false); + + (useResidenceSelfDeclaration as jest.Mock).mockReturnValue(false); + render(); + + commonFieldsCheck(); + expect( + screen.queryByText( + 'I hereby confirm that my request for opening an account with Deriv Investments (Europe) Ltd is made on my own initiative.' + ) + ).not.toBeInTheDocument(); + }); }); diff --git a/packages/account/src/Components/terms-of-use/terms-of-use.scss b/packages/account/src/Components/terms-of-use/terms-of-use.scss index 188ca6dfe2e5..59ce19b82a76 100644 --- a/packages/account/src/Components/terms-of-use/terms-of-use.scss +++ b/packages/account/src/Components/terms-of-use/terms-of-use.scss @@ -13,7 +13,9 @@ &__checkbox { margin-top: 1.6rem; - + .dc-checkbox { + align-items: unset; + } &:last-of-type { margin-bottom: 5rem; } @@ -48,12 +50,12 @@ @include mobile { & .dc-checkbox__box { - width: 2.4rem; - height: 2.4rem; + width: 1.6rem; + height: 1.6rem; .dc-icon { - width: 2.4rem; - height: 2.4rem; + width: 1.6rem; + height: 1.6rem; } } } diff --git a/packages/account/src/Components/terms-of-use/terms-of-use.tsx b/packages/account/src/Components/terms-of-use/terms-of-use.tsx index f3e80f5ee1db..3fe46af69c29 100644 --- a/packages/account/src/Components/terms-of-use/terms-of-use.tsx +++ b/packages/account/src/Components/terms-of-use/terms-of-use.tsx @@ -9,7 +9,9 @@ import { AutoHeightWrapper, StaticUrl, } from '@deriv/components'; -import { isDesktop, isMobile, PlatformContext, TBrokerCodes } from '@deriv/shared'; +import { useResidenceSelfDeclaration } from '@deriv/hooks'; +import { observer } from '@deriv/stores'; +import { isDesktop, isMobile, TBrokerCodes } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import CheckboxField from './checkbox-field'; import { SharedMessage, BrokerSpecificMessage, Hr } from './terms-of-use-messages'; @@ -20,6 +22,7 @@ type TTermsOfUseFormProps = { agreed_tos: boolean; agreed_tnc: boolean; fatca_declaration: '0' | '1'; + resident_self_declaration?: boolean; }; type TTermsOfUseProps = { @@ -38,6 +41,7 @@ type TTermsOfUseProps = { real_account_signup_target: TBrokerCodes; form_error?: string; is_multi_account: boolean; + residence: string; }; /** @@ -55,120 +59,138 @@ type TTermsOfUseProps = { * @param props - other props * @returns React node */ -const TermsOfUse = ({ - getCurrentStep, - onCancel, - onSave, - goToPreviousStep, - goToNextStep, - onSubmit, - value, - real_account_signup_target, - ...props -}: TTermsOfUseProps) => { - const { is_appstore } = React.useContext(PlatformContext); +const TermsOfUse = observer( + ({ + getCurrentStep, + onCancel, + onSave, + goToPreviousStep, + goToNextStep, + onSubmit, + value, + real_account_signup_target, + ...props + }: TTermsOfUseProps) => { + const { is_residence_self_declaration_required } = useResidenceSelfDeclaration(); - const handleCancel = () => { - const current_step = getCurrentStep() - 1; - onCancel(current_step, goToPreviousStep); - }; + const handleCancel = () => { + const current_step = getCurrentStep() - 1; + onCancel(current_step, goToPreviousStep); + }; - const getSubmitButtonLabel = () => { - if (is_appstore) { - return localize('Finish'); - } - return localize('Add account'); - }; + const getSubmitButtonLabel = () => { + return localize('Add account'); + }; - const onValuesChange = (values: TTermsOfUseFormProps) => { - const current_step = (getCurrentStep?.() || 1) - 1; - onSave(current_step, values); - }; + const onValuesChange = (values: TTermsOfUseFormProps) => { + const current_step = (getCurrentStep?.() || 1) - 1; + onSave(current_step, values); + }; - return ( - { - onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); - }} - validate={onValuesChange} - > - {({ handleSubmit, values, isSubmitting }) => ( - - {({ setRef }) => ( -
- - -
- -
- -
- -
- { + onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); + }} + validate={onValuesChange} + > + {({ handleSubmit, values, isSubmitting }) => ( + + {({ setRef }) => ( + + + +
+ +
+ +
+ +
+ +
+ , + ]} + /> + } + /> + {is_residence_self_declaration_required && ( + +
+ + } + label_line_height='s' + /> +
)} - label_font_size={isDesktop() ? 'xs' : 'xxs'} - /> -
- , - ]} - /> - } - /> -
-
-
- - handleCancel()} - cancel_label={localize('Previous')} - form_error={props.form_error} - /> - - - )} -
- )} - - ); -}; +
+
+
+ + handleCancel()} + cancel_label={localize('Previous')} + form_error={props.form_error} + /> + + + )} +
+ )} +
+ ); + } +); export default TermsOfUse; diff --git a/packages/account/src/Configs/terms-of-use-config.ts b/packages/account/src/Configs/terms-of-use-config.ts index 7c0101cdf334..bc67ab6e2c0d 100644 --- a/packages/account/src/Configs/terms-of-use-config.ts +++ b/packages/account/src/Configs/terms-of-use-config.ts @@ -14,6 +14,10 @@ const getTermsOfUseConfig = (account_settings: Partial) supported_in: ['svg', 'maltainvest'], default_value: false, }, + resident_self_declaration: { + supported_in: ['maltainvest'], + default_value: false, + }, fatca_declaration: { supported_in: ['svg', 'maltainvest'], default_value: String(account_settings?.fatca_declaration ?? ''), @@ -24,7 +28,8 @@ const termsOfUseConfig = ( { real_account_signup_target, account_settings, - }: { real_account_signup_target: string; account_settings: TTermsOfConfigSettings }, + residence, + }: { real_account_signup_target: string; account_settings: TTermsOfConfigSettings; residence: string }, TermsOfUse: React.Component ) => { const active_title = localize('Terms of use'); @@ -39,6 +44,7 @@ const termsOfUseConfig = ( props: { real_account_signup_target, is_multi_account: Boolean(String(account_settings?.fatca_declaration ?? '')), + residence, }, icon: 'IcDashboardTermsOfUse', }; diff --git a/packages/components/src/components/checkbox/checkbox.tsx b/packages/components/src/components/checkbox/checkbox.tsx index 1cd831016b86..85f1ff1ee18e 100644 --- a/packages/components/src/components/checkbox/checkbox.tsx +++ b/packages/components/src/components/checkbox/checkbox.tsx @@ -16,6 +16,7 @@ type TCheckBoxProps = Omit, 'value' | 'label'> value?: boolean; withTabIndex?: number; has_error?: boolean; + label_line_height?: string; }; const Checkbox = React.forwardRef( @@ -27,6 +28,7 @@ const Checkbox = React.forwardRef( id, label, label_font_size = 'xs', + label_line_height = 'unset', defaultChecked, onChange, // This needs to be here so it's not included in `otherProps` value = false, @@ -89,7 +91,7 @@ const Checkbox = React.forwardRef( real_account_signup_target === 'maltainvest'; diff --git a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx index 49d7f08a764b..ae1883ecd1da 100644 --- a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx +++ b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx @@ -10,7 +10,7 @@ import { observer, useStore } from '@deriv/stores'; import AcceptRiskForm from './accept-risk-form.jsx'; import LoadingModal from './real-account-signup-loader.jsx'; import { getItems } from './account-wizard-form'; -import { useIsClientHighRiskForMT5 } from '@deriv/hooks'; +import { useIsClientHighRiskForMT5, useResidenceSelfDeclaration } from '@deriv/hooks'; import 'Sass/details-form.scss'; import { Analytics } from '@deriv-com/analytics'; @@ -100,6 +100,7 @@ const AccountWizard = observer(props => { const [state_items, setStateItems] = React.useState(real_account_signup_form_data ?? []); const [should_accept_financial_risk, setShouldAcceptFinancialRisk] = React.useState(false); const is_high_risk_client_for_mt5 = useIsClientHighRiskForMT5(); + const { is_residence_self_declaration_required } = useResidenceSelfDeclaration(); const trackEvent = React.useCallback( payload => { @@ -296,6 +297,10 @@ const AccountWizard = observer(props => { delete clone?.confirmation_checkbox; delete clone?.crs_confirmation; + if (is_residence_self_declaration_required && clone?.resident_self_declaration) + clone.resident_self_declaration = 1; + else delete clone.resident_self_declaration; + // 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 // as the value will be available from the form_values diff --git a/packages/hooks/src/__tests__/useResidenceSelfDeclaration.spec.tsx b/packages/hooks/src/__tests__/useResidenceSelfDeclaration.spec.tsx new file mode 100644 index 000000000000..7ca64f69833e --- /dev/null +++ b/packages/hooks/src/__tests__/useResidenceSelfDeclaration.spec.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import useResidenceSelfDeclaration from '../useResidenceSelfDeclaration'; + +describe('useResidenceSelfDeclaration', () => { + test("should return true if client's residence require self-declaration", async () => { + const mock = mockStore({ + client: { + residence: 'es', + residence_list: [ + { + value: 'es', + account_opening_self_declaration_required: true, + }, + { + value: 'id', + account_opening_self_declaration_required: false, + }, + ], + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useResidenceSelfDeclaration(), { wrapper }); + expect(result.current.is_residence_self_declaration_required).toBe(true); + }); + + test("should return false if client's residence does not require self-declaration", async () => { + const mock = mockStore({ + client: { + residence: 'id', + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + const { result } = renderHook(() => useResidenceSelfDeclaration(), { wrapper }); + expect(result.current.is_residence_self_declaration_required).toBe(false); + }); +}); diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 8bb02b6544ed..61b419887215 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -77,3 +77,4 @@ export { default as useWalletTransactions } from './useWalletTransactions'; export { default as useWalletTransfer } from './useWalletTransfer'; export { default as useWalletsList } from './useWalletsList'; export { default as useGrowthbookFeatureFlag } from './useGrowthbookFeatureFlag'; +export { default as useResidenceSelfDeclaration } from './useResidenceSelfDeclaration'; diff --git a/packages/hooks/src/useResidenceSelfDeclaration.ts b/packages/hooks/src/useResidenceSelfDeclaration.ts new file mode 100644 index 000000000000..71b72603726a --- /dev/null +++ b/packages/hooks/src/useResidenceSelfDeclaration.ts @@ -0,0 +1,14 @@ +import { useStore } from '@deriv/stores'; + +/** A custom hook used for enabling residenceSelfDeclaration checkbox in terms of use section in real account signup */ +const useResidenceSelfDeclaration = () => { + const { client } = useStore(); + const { residence, residence_list } = client; + const is_residence_self_declaration_required = !!residence_list?.find( + residence_item => residence_item?.value === residence + )?.account_opening_self_declaration_required; + + return { is_residence_self_declaration_required }; +}; + +export default useResidenceSelfDeclaration; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index c6dcc024dacb..09e5e956147e 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -15,7 +15,6 @@ import type { LogOutResponse, Portfolio1, ProposalOpenContract, - ResidenceList, SetFinancialAssessmentRequest, SetFinancialAssessmentResponse, StatesList, @@ -543,7 +542,7 @@ type TClientStore = { upload_file?: string; poi_state?: string; }; - residence_list: ResidenceList; + residence_list: TResidenceList; // TODO: replace this with ResidenceList from @deriv/api-types once account_opening_self_declaration_required is available should_restrict_bvi_account_creation: boolean; should_restrict_vanuatu_account_creation: boolean; should_show_eu_content: boolean; @@ -607,6 +606,44 @@ type TClientStore = { setRealAccountSignupFormStep: (step: number) => void; }; +// TODO: This is a temporary type. It should be replaced with the actual type from deriv/api-types +type TResidenceList = { + account_opening_self_declaration_required?: boolean; + disabled?: string; + identity?: { + services?: { + idv?: { + documents_supported?: { + [k: string]: { + additional?: { + display_name?: string; + format?: string; + }; + display_name?: string; + format?: string; + }; + }; + has_visual_sample?: 0 | 1; + is_country_supported?: 0 | 1; + }; + onfido?: { + documents_supported?: { + [k: string]: { + display_name?: string; + format?: string; + }; + }; + is_country_supported?: 0 | 1; + }; + }; + }; + phone_idd?: null | string; + selected?: string; + text?: string; + tin_format?: string[]; + value?: string; +}[]; + type TCommonStoreError = { header?: string | JSX.Element; message: string | JSX.Element;