From 6f53181a4fe695beddbec99a9e3e7db8b6d2a893 Mon Sep 17 00:00:00 2001 From: Sergei Baranovski <120570511+sergei-deriv@users.noreply.github.com> Date: Wed, 21 Feb 2024 06:57:21 +0300 Subject: [PATCH] [TRAH] Sergei / TRAH - 2831 / Citizenship selection modal (#13641) * feat: create useClientCountry hook * feat: intermediate result * feat: done with citizenship modal * feat: move changes back for AppContent * fix: sonarcloud issue * feat: implement review comments * feat: implement review comments #2 --- .../hooks/__tests__/useCountryList.spec.tsx | 56 ++++++++++++ packages/api/src/hooks/index.ts | 1 + packages/api/src/hooks/useClientCountry.ts | 20 +++++ .../CitizenshipModal/CitizenshipModal.tsx | 89 +++++++++++++++++++ .../flows/Signup/CitizenshipModal/index.ts | 1 + .../Signup/SignupScreens/SignupScreens.tsx | 24 +++++ .../src/flows/Signup/SignupScreens/index.ts | 1 + .../Signup/SignupWrapper/SignupWrapper.tsx | 43 +++++++++ .../src/flows/Signup/SignupWrapper/index.ts | 1 + packages/tradershub/src/flows/Signup/index.ts | 3 + .../src/helpers/signupModalHelpers.ts | 2 + 11 files changed, 241 insertions(+) create mode 100644 packages/api/src/hooks/__tests__/useCountryList.spec.tsx create mode 100644 packages/api/src/hooks/useClientCountry.ts create mode 100644 packages/tradershub/src/flows/Signup/CitizenshipModal/CitizenshipModal.tsx create mode 100644 packages/tradershub/src/flows/Signup/CitizenshipModal/index.ts create mode 100644 packages/tradershub/src/flows/Signup/SignupScreens/SignupScreens.tsx create mode 100644 packages/tradershub/src/flows/Signup/SignupScreens/index.ts create mode 100644 packages/tradershub/src/flows/Signup/SignupWrapper/SignupWrapper.tsx create mode 100644 packages/tradershub/src/flows/Signup/SignupWrapper/index.ts create mode 100644 packages/tradershub/src/flows/Signup/index.ts diff --git a/packages/api/src/hooks/__tests__/useCountryList.spec.tsx b/packages/api/src/hooks/__tests__/useCountryList.spec.tsx new file mode 100644 index 000000000000..b3701b45198d --- /dev/null +++ b/packages/api/src/hooks/__tests__/useCountryList.spec.tsx @@ -0,0 +1,56 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useQuery from '../../useQuery'; +import useClientCountry from '../useClientCountry'; + +jest.mock('../../useQuery'); + +const mockUseQuery = useQuery as jest.MockedFunction>; + +describe('useClientCountry', () => { + it('should return an undefined', () => { + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseQuery.mockReturnValue({ + data: { + website_status: undefined, + }, + }); + const { result } = renderHook(() => useClientCountry()); + + expect(result.current.data).toBeUndefined(); + }); + + it('should return Indonesia country code', () => { + // @ts-expect-error need to come up with a way to mock the return type of useFetch + mockUseQuery.mockReturnValue({ + data: { + website_status: { + api_call_limits: { + max_proposal_subscription: { + applies_to: '', + max: 0, + }, + max_requestes_general: { + applies_to: '', + hourly: 0, + minutely: 0, + }, + max_requests_outcome: { + applies_to: '', + hourly: 0, + minutely: 0, + }, + max_requests_pricing: { + applies_to: '', + hourly: 0, + minutely: 0, + }, + }, + currencies_config: {}, + clients_country: 'id', + }, + }, + }); + const { result } = renderHook(() => useClientCountry()); + expect(result.current.data).toBe('id'); + }); +}); diff --git a/packages/api/src/hooks/index.ts b/packages/api/src/hooks/index.ts index 53e6cb2584a1..72fbce58ac7a 100644 --- a/packages/api/src/hooks/index.ts +++ b/packages/api/src/hooks/index.ts @@ -77,3 +77,4 @@ export { default as useResetVirtualBalance } from './useResetVirtualBalance'; export { default as useExchangeRates } from './useExchangeRates'; export { default as useIsDIELEnabled } from './useIsDIELEnabled'; export { default as useKycAuthStatus } from './useKycAuthStatus'; +export { default as useClientCountry } from './useClientCountry'; diff --git a/packages/api/src/hooks/useClientCountry.ts b/packages/api/src/hooks/useClientCountry.ts new file mode 100644 index 000000000000..01d24c28544b --- /dev/null +++ b/packages/api/src/hooks/useClientCountry.ts @@ -0,0 +1,20 @@ +import { useMemo } from 'react'; +import useQuery from '../useQuery'; + +/** A custom hook that gets the client country. */ +const useClientCountry = () => { + const { data, ...website_status_rest } = useQuery('website_status'); + + /** Modify the client country. */ + const modified_client_country = useMemo(() => { + return data?.website_status?.clients_country; + }, [data]); + + return { + /** The client's country */ + data: modified_client_country, + ...website_status_rest, + }; +}; + +export default useClientCountry; diff --git a/packages/tradershub/src/flows/Signup/CitizenshipModal/CitizenshipModal.tsx b/packages/tradershub/src/flows/Signup/CitizenshipModal/CitizenshipModal.tsx new file mode 100644 index 000000000000..d53ce0fed3b3 --- /dev/null +++ b/packages/tradershub/src/flows/Signup/CitizenshipModal/CitizenshipModal.tsx @@ -0,0 +1,89 @@ +import React, { useEffect, useState } from 'react'; +import { useFormikContext } from 'formik'; +import { isCVMEnabled } from '@/helpers'; +import { useClientCountry, useResidenceList } from '@deriv/api'; +import { LabelPairedChevronDownMdRegularIcon } from '@deriv/quill-icons'; +import { Button, Checkbox, Dropdown, Text } from '@deriv-com/ui'; +import { TSignupFormValues } from '../SignupWrapper/SignupWrapper'; + +type TCitizenshipModal = { + onClickNext: VoidFunction; +}; + +const CitizenshipModal = ({ onClickNext }: TCitizenshipModal) => { + const { data: residenceList, isLoading: residenceListLoading } = useResidenceList(); + const { data: clientCountry, isLoading: clientCountryLoading } = useClientCountry(); + const [isCheckBoxChecked, setIsCheckBoxChecked] = useState(false); + const { values, setFieldValue } = useFormikContext(); + const isCheckboxVisible = isCVMEnabled(values.country); + + useEffect(() => { + if (residenceList?.length && clientCountry && values.country === '') { + setFieldValue('country', clientCountry); + } + }, [clientCountry, setFieldValue, residenceList, values.country]); + + // Add here later when it's created + if (clientCountryLoading && residenceListLoading) return null; + + return ( +
+
+ Select your country and citizenship: + } + errorMessage='Country of residence is where you currently live.' + label='Country of residence' + list={residenceList} + name='country' + onSelect={selectedItem => { + setFieldValue('country', selectedItem); + }} + value={values.country} + variant='comboBox' + /> + } + errorMessage='Select your citizenship/nationality as it appears on your passport or other government-issued ID.' + label='Citizenship' + list={residenceList} + name='citizenship' + onSelect={selectedItem => { + setFieldValue('citizenship', selectedItem); + }} + value={values.citizenship} + variant='comboBox' + /> + {isCheckboxVisible && ( + + I hereby confirm that my request for opening an account with Deriv to trade OTC products + issued and offered exclusively outside Brazil was initiated by me. I fully understand + that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a + relation with a foreign company. + + } + labelClassName='flex-1' + onChange={(event: React.ChangeEvent) => + setIsCheckBoxChecked(event.target.checked) + } + wrapperClassName='w-auto' + /> + )} + +
+
+ ); +}; + +export default CitizenshipModal; diff --git a/packages/tradershub/src/flows/Signup/CitizenshipModal/index.ts b/packages/tradershub/src/flows/Signup/CitizenshipModal/index.ts new file mode 100644 index 000000000000..8443a418e8e6 --- /dev/null +++ b/packages/tradershub/src/flows/Signup/CitizenshipModal/index.ts @@ -0,0 +1 @@ +export { default as CitizenshipModal } from './CitizenshipModal'; diff --git a/packages/tradershub/src/flows/Signup/SignupScreens/SignupScreens.tsx b/packages/tradershub/src/flows/Signup/SignupScreens/SignupScreens.tsx new file mode 100644 index 000000000000..7a98044f7d8c --- /dev/null +++ b/packages/tradershub/src/flows/Signup/SignupScreens/SignupScreens.tsx @@ -0,0 +1,24 @@ +import React, { Dispatch } from 'react'; +import { CitizenshipModal } from '../CitizenshipModal'; + +type TSignupScreens = { + setStep: Dispatch>; + step: number; +}; + +const SignupScreens = ({ step, setStep }: TSignupScreens) => { + switch (step) { + case 1: + return setStep(prev => prev + 1)} />; + case 2: + return ( +
+ Screen 2 +
+ ); + default: + return null; + } +}; + +export default SignupScreens; diff --git a/packages/tradershub/src/flows/Signup/SignupScreens/index.ts b/packages/tradershub/src/flows/Signup/SignupScreens/index.ts new file mode 100644 index 000000000000..2d0a6729a045 --- /dev/null +++ b/packages/tradershub/src/flows/Signup/SignupScreens/index.ts @@ -0,0 +1 @@ +export { default as SignupScreens } from './SignupScreens'; diff --git a/packages/tradershub/src/flows/Signup/SignupWrapper/SignupWrapper.tsx b/packages/tradershub/src/flows/Signup/SignupWrapper/SignupWrapper.tsx new file mode 100644 index 000000000000..e1d979d0247a --- /dev/null +++ b/packages/tradershub/src/flows/Signup/SignupWrapper/SignupWrapper.tsx @@ -0,0 +1,43 @@ +import React, { useEffect, useState } from 'react'; +import { Form, Formik } from 'formik'; +import ReactModal from 'react-modal'; +import { CUSTOM_STYLES } from '@/helpers'; +import { SignupScreens } from '../SignupScreens'; + +export type TSignupFormValues = { + citizenship: string; + country: string; + password: string; +}; + +const SignupWrapper = () => { + // setIsOpen will be added later when flow is completed + const [isOpen] = useState(false); + const [step, setStep] = useState(1); + + const initialValues = { + country: '', + citizenship: '', + password: '', + }; + + const handleSubmit = () => { + // will be added later + }; + + useEffect(() => { + ReactModal.setAppElement('#v2_modal_root'); + }, []); + + return ( + + +
+ + +
+
+ ); +}; + +export default SignupWrapper; diff --git a/packages/tradershub/src/flows/Signup/SignupWrapper/index.ts b/packages/tradershub/src/flows/Signup/SignupWrapper/index.ts new file mode 100644 index 000000000000..42b392f3bbde --- /dev/null +++ b/packages/tradershub/src/flows/Signup/SignupWrapper/index.ts @@ -0,0 +1 @@ +export { default as SignupWrapper } from './SignupWrapper'; diff --git a/packages/tradershub/src/flows/Signup/index.ts b/packages/tradershub/src/flows/Signup/index.ts new file mode 100644 index 000000000000..ac67bda90d2f --- /dev/null +++ b/packages/tradershub/src/flows/Signup/index.ts @@ -0,0 +1,3 @@ +import { SignupWrapper as Signup } from './SignupWrapper'; + +export default Signup; diff --git a/packages/tradershub/src/helpers/signupModalHelpers.ts b/packages/tradershub/src/helpers/signupModalHelpers.ts index 76df7b3b3c10..08e3efff4ce6 100644 --- a/packages/tradershub/src/helpers/signupModalHelpers.ts +++ b/packages/tradershub/src/helpers/signupModalHelpers.ts @@ -27,3 +27,5 @@ export const CUSTOM_STYLES: TCustomStyles = { zIndex: 9999, }, }; + +export const isCVMEnabled = (countryCode: string) => countryCode === 'br';