From 0553c8257be37e98e197142d2636ecb20ede97fc Mon Sep 17 00:00:00 2001 From: Farabi <102643568+farabi-deriv@users.noreply.github.com> Date: Tue, 25 Jul 2023 19:18:00 +0800 Subject: [PATCH 01/20] farabi/webrel 806/translation and ui issue (#9454) * fix: alignment issue on different languages * fix: change word break from all to word * fix: truncated text for recent bots list --- .../dashboard/dashboard-component/index.scss | 21 ++++++++++++------- .../load-bot-preview/index.scss | 4 ++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.scss index 7ae97dbcae0d..b3ac0f19f376 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.scss +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/index.scss @@ -4,6 +4,16 @@ align-items: center; } +@mixin recent-list-properties { + display: -webkit-box; + max-width: 90%; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; +} + .bot-dashboard { position: relative; height: calc(100vh - 8.4rem); @@ -480,14 +490,9 @@ font-size: var(--text-size-xs); } - &__recent-item-text { - display: -webkit-box; - max-width: 90%; - -webkit-line-clamp: 1; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - word-break: break-all; + &__recent-item-text, + &__recent-item-saved { + @include recent-list-properties; } } } diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss index 04712e16f0c0..65713b1a55ca 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss @@ -283,7 +283,6 @@ @include mobile { width: unset; font-size: var(--text-size-xxs); - white-space: nowrap; } } @@ -438,7 +437,8 @@ } span { - padding-right: 1.5rem; + padding-right: 1.6rem; + word-break: break-word; } } } From 5d6150f53da663443cb80493228a4ad2d4e07e72 Mon Sep 17 00:00:00 2001 From: Likhith Kolayari <98398322+likhith-deriv@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:28:22 +0400 Subject: [PATCH 02/20] Likhith/87594/trigger onfido checks for high risk client (#7646) * feat: added onfido check for high risk client * feat: :zap: Incorporated POA for Labuan when status is idv_photoid * fix: failing testcase * feat: reverted the change * feat: added missing config * feat: added POA * feat: :sparkles: added POA trigger for IDV+photot status * fix: :recycle: incorporated review comments * chore: incorporated review comments * feat: :sparkles: incorporated IDV revoked status * refactor: :recycle: refactored code * refactor: :recycle: incorporated review comments * feat: :recycle: incorporated review comments * feat: resolved issue with POA * fix: :bug: Trigger POA only in user is idv+photo verified for labuan * fix: :sparkles: incorporated Resubmission triggers * feat: incorporated testcases * chore: trigger rebuild * fix: resolved issue with POA trigger for idv+photo clients * chore: :green_heart: trigger build * fix: :bug: resolved bugs regd high risk DBVI account * Likhith/incorporate deriv hooks (#42) * feat: :sparkles: installed deriv-hooks * feat: :sparkles: incorporated a new hook * fix: :white_check_mark: added testcase * feat: incorporated hook * fix: :art: reverted changes of hooks implementation * fix: :zap: added testcases --------- Co-authored-by: Matin shafiei --- packages/account/package.json | 1 + .../poa/status/status-codes/status-codes.ts | 4 +- .../account/src/Types/common-prop.type.ts | 4 + .../src/components/cfds-listing/index.tsx | 76 ++++++++++++++---- packages/cfd/package.json | 1 + packages/cfd/src/Components/cfd-poa.tsx | 36 ++++++--- ...-financial-stp-real-account-signup.spec.js | 79 +++++++++++++++---- .../src/Containers/cfd-dbvi-onboarding.tsx | 20 ++++- .../cfd-financial-stp-real-account-signup.tsx | 42 +++++++--- .../jurisdiction-modal-content-wrapper.tsx | 8 +- packages/cfd/types.ts | 3 + .../useIsAccountStatusPresent.spec.tsx | 42 ++++++++++ packages/hooks/src/index.ts | 1 + .../hooks/src/useIsAccountStatusPresent.ts | 59 ++++++++++++++ packages/shared/src/utils/cfd/cfd.ts | 25 ++++-- 15 files changed, 336 insertions(+), 65 deletions(-) create mode 100644 packages/hooks/src/__tests__/useIsAccountStatusPresent.spec.tsx create mode 100644 packages/hooks/src/useIsAccountStatusPresent.ts diff --git a/packages/account/package.json b/packages/account/package.json index 6feecfbc8024..bee6b47f0d0f 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -30,6 +30,7 @@ "@binary-com/binary-document-uploader": "^2.4.7", "@deriv/api-types": "^1.0.94", "@deriv/components": "^1.0.0", + "@deriv/hooks": "^1.0.0", "@deriv/shared": "^1.0.0", "@deriv/stores":"^1.0.0", "@deriv/translations": "^1.0.0", diff --git a/packages/account/src/Components/poa/status/status-codes/status-codes.ts b/packages/account/src/Components/poa/status/status-codes/status-codes.ts index 913d00353861..8a44a987f1a2 100644 --- a/packages/account/src/Components/poa/status/status-codes/status-codes.ts +++ b/packages/account/src/Components/poa/status/status-codes/status-codes.ts @@ -1,6 +1,6 @@ -type TPoaStatusCodes = Readonly>; +import { TVerificationStatus } from 'Types'; -export const poa_status_codes: TPoaStatusCodes = { +export const poa_status_codes: TVerificationStatus = { none: 'none', pending: 'pending', rejected: 'rejected', diff --git a/packages/account/src/Types/common-prop.type.ts b/packages/account/src/Types/common-prop.type.ts index b964c0e584f6..204e11a8985e 100644 --- a/packages/account/src/Types/common-prop.type.ts +++ b/packages/account/src/Types/common-prop.type.ts @@ -149,3 +149,7 @@ export type TPersonalDetailsForm = { } & FormikProps; export type TInputFieldValues = Record; + +export type TVerificationStatus = Readonly< + Record<'none' | 'pending' | 'rejected' | 'verified' | 'expired' | 'suspected', string> +>; diff --git a/packages/appstore/src/components/cfds-listing/index.tsx b/packages/appstore/src/components/cfds-listing/index.tsx index f70705787c46..2b2986b48f75 100644 --- a/packages/appstore/src/components/cfds-listing/index.tsx +++ b/packages/appstore/src/components/cfds-listing/index.tsx @@ -1,18 +1,18 @@ import React from 'react'; +import { observer } from 'mobx-react-lite'; import { Text, StaticUrl } from '@deriv/components'; +import { isMobile, formatMoney, getAuthenticationStatusInfo, Jurisdiction } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import ListingContainer from 'Components/containers/listing-container'; -import './cfds-listing.scss'; -import { useStores } from 'Stores/index'; -import { observer } from 'mobx-react-lite'; import AddOptionsAccount from 'Components/add-options-account'; -import { isMobile, formatMoney } from '@deriv/shared'; import TradingAppCard from 'Components/containers/trading-app-card'; -import { AvailableAccount, TDetailsOfEachMT5Loginid } from 'Types'; import PlatformLoader from 'Components/pre-loader/platform-loader'; import GetMoreAccounts from 'Components/get-more-accounts'; import { Actions } from 'Components/containers/trading-app-card-actions'; import { getHasDivider } from 'Constants/utils'; +import { useStores } from 'Stores/index'; +import { AvailableAccount, TDetailsOfEachMT5Loginid } from 'Types'; +import './cfds-listing.scss'; type TDetailedExistingAccount = AvailableAccount & TDetailsOfEachMT5Loginid & @@ -55,18 +55,57 @@ const CFDsListing = () => { } = traders_hub; const { toggleCompareAccountsModal, setAccountType } = cfd; - const { is_landing_company_loaded, real_account_creation_unlock_date } = client; + const { is_landing_company_loaded, real_account_creation_unlock_date, account_status } = client; const { setAppstorePlatform } = common; const { openDerivRealAccountNeededModal, setShouldShowCooldownModal } = ui; const has_no_real_account = !has_any_real_account; const accounts_sub_text = !is_eu_user || is_demo_low_risk ? localize('Compare accounts') : localize('Account Information'); - const getMT5AccountAuthStatus = (current_acc_status: string) => { - if (current_acc_status === 'proof_failed') { - return 'failed'; - } else if (current_acc_status === 'verification_pending') { - return 'pending'; + const { poi_pending_for_bvi_labuan, poi_resubmit_for_bvi_labuan, poa_resubmit_for_labuan, is_idv_revoked } = + getAuthenticationStatusInfo(account_status); + + const getAuthStatus = (status_list: boolean[]) => status_list.some(status => status); + + const getMT5AccountAuthStatus = (current_acc_status: string, jurisdiction?: string) => { + if (jurisdiction) { + switch (jurisdiction) { + case Jurisdiction.BVI: { + if ( + getAuthStatus([ + is_idv_revoked, + poi_resubmit_for_bvi_labuan, + current_acc_status === 'proof_failed', + ]) + ) { + return 'failed'; + } else if ( + getAuthStatus([poi_pending_for_bvi_labuan, current_acc_status === 'verification_pending']) + ) { + return 'pending'; + } + return null; + } + case Jurisdiction.LABUAN: { + if ( + getAuthStatus([ + poa_resubmit_for_labuan, + is_idv_revoked, + poi_resubmit_for_bvi_labuan, + current_acc_status === 'proof_failed', + ]) + ) { + return 'failed'; + } else if ( + getAuthStatus([poi_pending_for_bvi_labuan, current_acc_status === 'verification_pending']) + ) { + return 'pending'; + } + return null; + } + default: + return null; + } } return null; }; @@ -137,12 +176,17 @@ const CFDsListing = () => { {is_landing_company_loaded ? ( - <> + {combined_cfd_mt5_accounts.map((existing_account: TDetailedExistingAccount, index: number) => { const list_size = combined_cfd_mt5_accounts.length; - const has_mt5_account_status = existing_account.status - ? getMT5AccountAuthStatus(existing_account.status) - : null; + const has_mt5_account_status = + existing_account.status || is_idv_revoked + ? getMT5AccountAuthStatus( + existing_account.status, + existing_account?.short_code_and_region?.toLowerCase() + ) + : null; + return ( { description={localize('Get more Deriv MT5 account with different type and jurisdiction.')} /> )} - + ) : ( )} diff --git a/packages/cfd/package.json b/packages/cfd/package.json index 8846f3b980c5..25b62e5b2b99 100644 --- a/packages/cfd/package.json +++ b/packages/cfd/package.json @@ -86,6 +86,7 @@ "@deriv/api-types": "^1.0.94", "@deriv/components": "^1.0.0", "@deriv/deriv-api": "^1.0.11", + "@deriv/hooks": "^1.0.0", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", diff --git a/packages/cfd/src/Components/cfd-poa.tsx b/packages/cfd/src/Components/cfd-poa.tsx index b1961950dfc6..6d8cf3f75657 100644 --- a/packages/cfd/src/Components/cfd-poa.tsx +++ b/packages/cfd/src/Components/cfd-poa.tsx @@ -1,24 +1,25 @@ -import { Field, Formik, FieldProps, FormikHelpers, FormikProps, FormikErrors } from 'formik'; import React from 'react'; +import { Field, FieldProps, Formik, FormikErrors, FormikHelpers, FormikProps } from 'formik'; +import { AccountStatusResponse, GetSettings, StatesList } from '@deriv/api-types'; import { AutoHeightWrapper, - FormSubmitButton, - ThemedScrollbars, + DesktopWrapper, + Div100vhContainer, Dropdown, + FormSubmitButton, Loading, - Div100vhContainer, + MobileWrapper, Modal, SelectNative, - DesktopWrapper, - MobileWrapper, - useStateCallback, Text, + ThemedScrollbars, + useStateCallback, } from '@deriv/components'; import { FileUploaderContainer, FormSubHeader, PoaStatusCodes } from '@deriv/account'; import { localize } from '@deriv/translations'; -import { isDesktop, isMobile, validAddress, validLength, validLetterSymbol, validPostCode, WS } from '@deriv/shared'; +import { WS, isDesktop, isMobile, validAddress, validLength, validLetterSymbol, validPostCode } from '@deriv/shared'; import { InputField } from './cfd-personal-details-form'; -import { GetSettings, StatesList, AccountStatusResponse } from '@deriv/api-types'; +import { TJurisdiction } from '../../types'; type TErrors = { code: string; @@ -79,11 +80,13 @@ export type TCFDPOAProps = { onSubmit: (index: number, value: TFormValues) => void; refreshNotifications: () => void; form_error: string; - get_settings: GetSettings; + account_settings: GetSettings; height: string; states_list: StatesList; storeProofOfAddress: TStoreProofOfAddress; value: TFormValue; + jurisdiction_selected_shortcode: TJurisdiction; + is_authenticated_with_idv_photoid: boolean; }; type TUpload = { upload: () => void; @@ -91,7 +94,15 @@ type TUpload = { let file_uploader_ref: React.RefObject; -const CFDPOA = ({ onSave, index, onSubmit, refreshNotifications, ...props }: TCFDPOAProps) => { +const CFDPOA = ({ + onSave, + index, + onSubmit, + refreshNotifications, + jurisdiction_selected_shortcode, + is_authenticated_with_idv_photoid, + ...props +}: TCFDPOAProps) => { const form = React.useRef | null>(null); const [is_loading, setIsLoading] = React.useState(true); @@ -259,7 +270,8 @@ const CFDPOA = ({ onSave, index, onSubmit, refreshNotifications, ...props }: TCF } = props; const { form_error, poa_status } = form_state; - const is_form_visible = !is_loading && poa_status !== PoaStatusCodes.verified; + const is_form_visible = + !is_loading && (poa_status !== PoaStatusCodes.verified || is_authenticated_with_idv_photoid); return ( ({ ...jest.requireActual('@deriv/account'), FormSubHeader: () =>
FormSubHeader
, })); -jest.mock('../../Components/cfd-poa', () => jest.fn(() =>
CFDPOA
)); +const MockComponent = ({ prevStep, nextStep }) => ( +
+ + +
+); + +jest.mock('../../Components/cfd-poa', () => + jest.fn(({ onCancel, onSubmit }) => ( +
+ CFDPOA + +
+ )) +); jest.mock('../../Components/cfd-poi', () => - jest.fn(({ onSubmit }) => ( -
onSubmit(0, {})}> + jest.fn(({ onCancel, onSubmit }) => ( +
CFDPOI +
)) ); +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + getAuthenticationStatusInfo: jest.fn().mockReturnValue({}), +})); + const getByTextFn = (text, should_be) => { if (should_be) { expect(screen.getByText(text)).toBeInTheDocument(); @@ -26,14 +47,6 @@ const getByTextFn = (text, should_be) => { } }; -const toHavaClassFn = (item, class_name, should_have) => { - if (should_have) { - expect(item).toHaveClass(class_name); - } else { - expect(item).not.toHaveClass(class_name); - } -}; - const testAllStepsFn = (steps, step_no) => { steps.map((step, index) => { if (index === step_no) { @@ -181,6 +194,7 @@ describe('', () => { }); it('should render properly for the first step content', () => { + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_bvi_labuan: true }); render(, { wrapper: ({ children }) => {children}, }); @@ -189,14 +203,51 @@ describe('', () => { }); it('should render properly for the second step content', () => { + getAuthenticationStatusInfo.mockReturnValueOnce({ poa_resubmit_for_labuan: true }); const { getByTestId } = render(, { wrapper: ({ children }) => {children}, }); - const div = getByTestId('poa-form'); + testAllStepsFn(steps, 1); + }); - fireEvent.click(div); + it('should check for POI status when Jurisdiction is Vanuatu or maltainvest', () => { + const new_mock_store = { + ...mockRootStore, + modules: { + ...mockRootStore.modules, + cfd: { + ...mockRootStore.modules.cfd, + jurisdiction_selected_shortcode: 'vanuatu', + }, + }, + }; + + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_vanuatu_maltainvest: true }); + + render(, { + wrapper: ({ children }) => {children}, + }); + testAllStepsFn(steps, 0); + }); + it('should check for POA status when Jurisdiction is Labuan and resubmit status is set to true', () => { + const new_mock_store = { + ...mockRootStore, + modules: { + ...mockRootStore.modules, + cfd: { + ...mockRootStore.modules.cfd, + jurisdiction_selected_shortcode: 'labuan', + }, + }, + }; + + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_vanuatu_maltainvest: true }); + + render(, { + wrapper: ({ children }) => {children}, + }); testAllStepsFn(steps, 1); }); }); diff --git a/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx b/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx index 35899f963d04..75cbbd09df72 100644 --- a/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx +++ b/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx @@ -1,4 +1,6 @@ import React from 'react'; +import { PoiPoaDocsSubmitted } from '@deriv/account'; +import { AccountStatusResponse } from '@deriv/api-types'; import { Button, DesktopWrapper, @@ -11,9 +13,7 @@ import { UILoader, } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { PoiPoaDocsSubmitted } from '@deriv/account'; import { getAuthenticationStatusInfo, isMobile, WS, Jurisdiction } from '@deriv/shared'; -import { AccountStatusResponse } from '@deriv/api-types'; import CFDFinancialStpRealAccountSignup from './cfd-financial-stp-real-account-signup'; import { observer, useStore } from '@deriv/stores'; import { useCfdStore } from '../Stores/Modules/CFD/Helpers/useCfdStores'; @@ -59,8 +59,14 @@ const CFDDbviOnboarding = observer(() => { const { get_account_status } = response; if (get_account_status?.authentication) { - const { poi_acknowledged_for_vanuatu_maltainvest, poi_acknowledged_for_bvi_labuan, poa_acknowledged } = - getAuthenticationStatusInfo(get_account_status); + const { + poi_acknowledged_for_vanuatu_maltainvest, + poi_acknowledged_for_bvi_labuan, + poa_acknowledged, + poa_resubmit_for_labuan, + need_poa_submission, + } = getAuthenticationStatusInfo(get_account_status); + if (jurisdiction_selected_shortcode === Jurisdiction.VANUATU) { setShowSubmittedModal( poi_acknowledged_for_vanuatu_maltainvest && @@ -69,6 +75,12 @@ const CFDDbviOnboarding = observer(() => { ); } else if (jurisdiction_selected_shortcode === Jurisdiction.MALTA_INVEST) { setShowSubmittedModal(poi_acknowledged_for_vanuatu_maltainvest && poa_acknowledged); + } else if (jurisdiction_selected_shortcode === Jurisdiction.LABUAN) { + /* When verified with IDV+ Photo ID, POA is auto verified */ + const is_poa_submitted = poa_resubmit_for_labuan ? false : !need_poa_submission; + setShowSubmittedModal( + poi_acknowledged_for_bvi_labuan && has_submitted_cfd_personal_details && is_poa_submitted + ); } else setShowSubmittedModal( poi_acknowledged_for_bvi_labuan && poa_acknowledged && has_submitted_cfd_personal_details diff --git a/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx b/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx index 8f34449b3e2e..14dd74e08b47 100644 --- a/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx +++ b/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Div100vhContainer } from '@deriv/components'; +import { useIsAccountStatusPresent } from '@deriv/hooks'; import { isDesktop, getAuthenticationStatusInfo, Jurisdiction } from '@deriv/shared'; import CFDPOA from '../Components/cfd-poa'; import CFDPOI from '../Components/cfd-poi'; @@ -87,6 +88,8 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt const { need_poi_for_vanuatu_maltainvest, need_poi_for_bvi_labuan } = getAuthenticationStatusInfo(account_status); + const is_authenticated_with_idv_photoid = useIsAccountStatusPresent('authenticated_with_idv_photoid'); + const poi_config: TItemsState = { body: CFDPOI, form_value: { @@ -112,7 +115,13 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt address_postcode: account_settings.address_postcode, upload_file: '', }, - forwarded_props: ['states_list', 'account_settings', 'storeProofOfAddress', 'refreshNotifications'], + forwarded_props: [ + 'states_list', + 'account_settings', + 'storeProofOfAddress', + 'refreshNotifications', + 'jurisdiction_selected_shortcode', + ], }; const personal_details_config: TItemsState = { @@ -133,14 +142,20 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt } return need_poi_for_bvi_labuan; }; - const should_show_poa = !['pending', 'verified'].includes(authentication_status.document_status); + + const shouldShowPOA = () => { + if (Jurisdiction.LABUAN === jurisdiction_selected_shortcode && is_authenticated_with_idv_photoid) { + return true; + } + return !['pending', 'verified'].includes(authentication_status.document_status); + }; const should_show_personal_details = !has_submitted_cfd_personal_details && jurisdiction_selected_shortcode !== Jurisdiction.MALTA_INVEST; const verification_configs = [ ...(should_show_poi() ? [poi_config] : []), - ...(should_show_poa ? [poa_config] : []), + ...(shouldShowPOA() ? [poa_config] : []), ...(should_show_personal_details ? [personal_details_config] : []), ]; @@ -193,13 +208,20 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt const form_value = getCurrent('form_value'); - const passthrough = ( - (getCurrent('forwarded_props') || []) as TItemsState['forwarded_props'] - ).reduce((forwarded_prop, item) => { - return Object.assign(forwarded_prop, { - [item]: passthroughProps[item], - }); - }, {}); + const passthrough: Partial & { + is_authenticated_with_idv_photoid?: boolean; + } = ((getCurrent('forwarded_props') || []) as TItemsState['forwarded_props']).reduce( + (forwarded_prop, item) => { + return Object.assign(forwarded_prop, { + [item]: passthroughProps[item], + }); + }, + {} + ); + + if (shouldShowPOA()) { + passthrough.is_authenticated_with_idv_photoid = is_authenticated_with_idv_photoid; + } return ( { @@ -166,7 +167,12 @@ const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisd toggleCFDVerificationModal(); } } else if (is_labuan_selected) { - if (poi_acknowledged_for_bvi_labuan && poa_acknowledged && has_submitted_cfd_personal_details) { + if ( + poi_acknowledged_for_bvi_labuan && + poa_acknowledged && + has_submitted_cfd_personal_details && + !poa_resubmit_for_labuan + ) { openPasswordModal(type_of_account); } else { toggleCFDVerificationModal(); diff --git a/packages/cfd/types.ts b/packages/cfd/types.ts index 6389dd6f436a..9474ea0e54be 100644 --- a/packages/cfd/types.ts +++ b/packages/cfd/types.ts @@ -1,3 +1,6 @@ +import { Jurisdiction } from '@deriv/shared'; import type { TCoreStores } from '@deriv/stores/types'; export type TTradingPlatformAvailableAccount = TCoreStores['client']['trading_platform_available_accounts'][number]; + +export type TJurisdiction = typeof Jurisdiction[keyof typeof Jurisdiction]; diff --git a/packages/hooks/src/__tests__/useIsAccountStatusPresent.spec.tsx b/packages/hooks/src/__tests__/useIsAccountStatusPresent.spec.tsx new file mode 100644 index 000000000000..eeffe186e82a --- /dev/null +++ b/packages/hooks/src/__tests__/useIsAccountStatusPresent.spec.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +import { renderHook } from '@testing-library/react-hooks'; +import { useIsAccountStatusPresent } from '../useIsAccountStatusPresent'; + +describe('useIsAccountStatusPresent', () => { + it('should return false when the status is not present', () => { + const mock = mockStore({ + client: { + account_status: { + status: [], + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useIsAccountStatusPresent('cashier_locked'), { wrapper }); + + expect(result.current).toBeFalsy(); + }); + + it('should return true when the status is present', () => { + const mock = mockStore({ + client: { + account_status: { + status: ['cashier_locked', 'unwelcome'], + }, + }, + }); + + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + const { result } = renderHook(() => useIsAccountStatusPresent('cashier_locked'), { wrapper }); + + expect(result.current).toBeTruthy(); + }); +}); diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 4442969638f9..e0f6157a6d73 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -42,3 +42,4 @@ export { default as usePlatformRealAccounts } from './usePlatformRealAccounts'; export { default as useRealSTPAccount } from './useRealSTPAccount'; export { default as useTotalAccountBalance } from './useTotalAccountBalance'; export { default as useVerifyEmail } from './useVerifyEmail'; +export { useIsAccountStatusPresent } from './useIsAccountStatusPresent'; diff --git a/packages/hooks/src/useIsAccountStatusPresent.ts b/packages/hooks/src/useIsAccountStatusPresent.ts new file mode 100644 index 000000000000..f6b467cd0089 --- /dev/null +++ b/packages/hooks/src/useIsAccountStatusPresent.ts @@ -0,0 +1,59 @@ +import React from 'react'; +import { useStore } from '@deriv/stores'; + +const AccountStatusList = [ + 'address_verified', + 'age_verification', + 'allow_document_upload', + 'allow_poa_resubmission', + 'allow_poi_resubmission', + 'authenticated', + 'authenticated_with_idv_photoid', + 'cashier_locked', + 'crs_tin_information', + 'deposit_attempt', + 'deposit_locked', + 'df_deposit_requires_poi', + 'disabled', + 'document_expired', + 'document_expiring_soon', + 'document_under_review', + 'dxtrade_password_not_set', + 'financial_assessment_not_complete', + 'financial_information_not_complete', + 'financial_risk_approval', + 'idv_revoked', + 'max_turnover_limit_not_set', + 'mt5_password_not_set', + 'mt5_withdrawal_locked', + 'needs_affiliate_coc_approval', + 'no_trading', + 'no_withdrawal_or_trading', + 'p2p_blocked_for_pa', + 'pa_withdrawal_explicitly_allowed', + 'password_reset_required', + 'personal_details_locked', + 'poi_name_mismatch', + 'professional', + 'professional_requested', + 'professional_rejected', + 'shared_payment_method', + 'social_signup', + 'transfers_blocked', + 'trading_experience_not_complete', + 'ukgc_funds_protection', + 'unwelcome', + 'withdrawal_locked', +] as const; + +type TAccountStatus = typeof AccountStatusList[number]; + +export const useIsAccountStatusPresent = (status: TAccountStatus) => { + const { + client: { account_status }, + } = useStore(); + + const status_list = account_status?.status; + + return React.useMemo(() => status_list?.includes(status) ?? false, [status_list, status]); +}; diff --git a/packages/shared/src/utils/cfd/cfd.ts b/packages/shared/src/utils/cfd/cfd.ts index e1393316bec2..ed40356091ef 100644 --- a/packages/shared/src/utils/cfd/cfd.ts +++ b/packages/shared/src/utils/cfd/cfd.ts @@ -1,6 +1,6 @@ -import { CFD_PLATFORMS } from '../platform'; -import { LandingCompany, GetAccountStatus, DetailsOfEachMT5Loginid } from '@deriv/api-types'; +import { DetailsOfEachMT5Loginid, GetAccountStatus, LandingCompany } from '@deriv/api-types'; import { localize } from '@deriv/translations'; +import { CFD_PLATFORMS } from '../platform'; let CFD_text_translated: { [key: string]: () => void }; @@ -256,6 +256,8 @@ export const isLandingCompanyEnabled = ({ landing_companies, platform, type }: T }; export const getAuthenticationStatusInfo = (account_status: GetAccountStatus) => { + const risk_classification = account_status.risk_classification; + const poa_status = account_status?.authentication?.document?.status || ''; const poi_status = account_status?.authentication?.identity?.status || ''; @@ -263,6 +265,9 @@ export const getAuthenticationStatusInfo = (account_status: GetAccountStatus) => const onfido_status = account_status?.authentication?.identity?.services?.onfido?.status; const manual_status = account_status?.authentication?.identity?.services?.manual?.status; + const is_authenticated_with_idv_photoid = account_status?.status?.includes('authenticated_with_idv_photoid'); + const is_idv_revoked = account_status?.status?.includes('idv_revoked'); + const acknowledged_status = ['pending', 'verified']; const failed_cases = ['rejected', 'expired', 'suspected']; @@ -301,10 +306,15 @@ export const getAuthenticationStatusInfo = (account_status: GetAccountStatus) => const poi_poa_verified_for_vanuatu_maltainvest = poi_verified_for_vanuatu_maltainvest && poa_verified; //bvi-labuan - const poi_acknowledged_for_bvi_labuan = - (idv_status && acknowledged_status.includes(idv_status)) || - (onfido_status && acknowledged_status.includes(onfido_status)) || - (manual_status && acknowledged_status.includes(manual_status)); + let poi_acknowledged_for_bvi_labuan; + if (risk_classification === 'high') { + poi_acknowledged_for_bvi_labuan = onfido_status && acknowledged_status.includes(onfido_status); + } else { + poi_acknowledged_for_bvi_labuan = + (idv_status && acknowledged_status.includes(idv_status)) || + (onfido_status && acknowledged_status.includes(onfido_status)) || + (manual_status && acknowledged_status.includes(manual_status)); + } const need_poi_for_bvi_labuan = !poi_acknowledged_for_bvi_labuan; const poi_not_submitted_for_bvi_labuan = @@ -325,6 +335,7 @@ export const getAuthenticationStatusInfo = (account_status: GetAccountStatus) => const poi_resubmit_for_bvi_labuan = !poi_pending_for_bvi_labuan && !poi_not_submitted_for_bvi_labuan && !poi_verified_for_bvi_labuan; const poi_poa_verified_for_bvi_labuan = poi_verified_for_bvi_labuan && poa_verified; + const poa_resubmit_for_labuan = is_authenticated_with_idv_photoid; return { poa_status, @@ -355,5 +366,7 @@ export const getAuthenticationStatusInfo = (account_status: GetAccountStatus) => poi_resubmit_for_vanuatu_maltainvest, poi_resubmit_for_bvi_labuan, poa_pending, + poa_resubmit_for_labuan, + is_idv_revoked, }; }; From 1af1cc5355c378321772d433e8a6b4a28b93042b Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Wed, 26 Jul 2023 15:32:13 +0800 Subject: [PATCH 03/20] shahzaib / KYC-93 / add issuing country to uploading files in manual verification (#9035) * chore: add issuing country to uploading file meta data * chore: update binary-document-uploader package * chore: add document issuing country in poa & poo document uploading * chore: reverted the null check line change * chore: add missing country_code upon retrying verification * chore: issuing country is not required at the moment from poo & poa * chore: empty commit * chore: update import path to fix eslint error message * ci: update package.json * chore: revert files affected by formatting --------- Co-authored-by: Likhith Kolayari <98398322+likhith-deriv@users.noreply.github.com> --- packages/account/package.json | 2 +- .../file-uploader-container.spec.tsx | 1 + .../__tests__/file-uploader.spec.tsx | 4 +++- .../file-uploader-container.tsx | 8 +++++--- .../file-uploader-container/file-uploader.tsx | 7 ++++--- .../status/unsupported/detail-component.tsx | 1 + .../__test__/proof-of-ownership-form.spec.js | 3 +++ .../__test__/proof-of-ownership.spec.js | 15 ++++++++++++++ .../proof-of-ownership-form.jsx | 1 + .../ProofOfOwnership/proof-of-ownership.jsx | 3 +++ .../cellmeasurer/CellMeasurerCache.d.ts | 1 + .../date-picker/date-picker-input.tsx | 2 +- .../src/utils/files/file-uploader-utils.ts | 3 ++- .../Components/InfoBox/info-box-longcode.jsx | 20 ++++++++++++------- 14 files changed, 54 insertions(+), 17 deletions(-) diff --git a/packages/account/package.json b/packages/account/package.json index bee6b47f0d0f..4e8c7a4c4c3b 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -27,7 +27,7 @@ "deploy:production": "echo \"No deploy:production specified\"" }, "dependencies": { - "@binary-com/binary-document-uploader": "^2.4.7", + "@binary-com/binary-document-uploader": "^2.4.8", "@deriv/api-types": "^1.0.94", "@deriv/components": "^1.0.0", "@deriv/hooks": "^1.0.0", diff --git a/packages/account/src/Components/file-uploader-container/__tests__/file-uploader-container.spec.tsx b/packages/account/src/Components/file-uploader-container/__tests__/file-uploader-container.spec.tsx index 6f260a3817eb..f130d2324d37 100644 --- a/packages/account/src/Components/file-uploader-container/__tests__/file-uploader-container.spec.tsx +++ b/packages/account/src/Components/file-uploader-container/__tests__/file-uploader-container.spec.tsx @@ -30,6 +30,7 @@ describe('', () => { getSocket: jest.fn(), onFileDrop: jest.fn(), onRef: jest.fn(), + settings: {}, }; const file_size_msg = /less than 8mb/i; diff --git a/packages/account/src/Components/file-uploader-container/__tests__/file-uploader.spec.tsx b/packages/account/src/Components/file-uploader-container/__tests__/file-uploader.spec.tsx index af60693389b3..ea1245d225b4 100644 --- a/packages/account/src/Components/file-uploader-container/__tests__/file-uploader.spec.tsx +++ b/packages/account/src/Components/file-uploader-container/__tests__/file-uploader.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import { compressImageFiles, isMobile, isDesktop, readFiles } from '@deriv/shared'; +import { compressImageFiles, isMobile, isDesktop, readFiles, TSettings } from '@deriv/shared'; import FileUploader from '../file-uploader'; jest.mock('@deriv/shared', () => ({ @@ -24,10 +24,12 @@ describe('', () => { onFileDrop: (file: File | undefined) => void; getSocket: () => WebSocket; ref: React.RefObject; + settings: TSettings; } = { onFileDrop: jest.fn(), getSocket: jest.fn(), ref: React.createRef(), + settings: {}, }; const large_file_error_msg = /file size should be 8mb or less/i; diff --git a/packages/account/src/Components/file-uploader-container/file-uploader-container.tsx b/packages/account/src/Components/file-uploader-container/file-uploader-container.tsx index 4eb092584d22..de6375f07109 100644 --- a/packages/account/src/Components/file-uploader-container/file-uploader-container.tsx +++ b/packages/account/src/Components/file-uploader-container/file-uploader-container.tsx @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import { Icon, Text } from '@deriv/components'; -import { PlatformContext, isDesktop, WS } from '@deriv/shared'; +import { PlatformContext, isDesktop, WS, TSettings } from '@deriv/shared'; import { Localize, localize } from '@deriv/translations'; import FileUploader from './file-uploader'; import { TFile, TPlatformContext } from 'Types'; @@ -11,6 +11,7 @@ export type TFileUploaderContainer = { getSocket: () => WebSocket; onFileDrop: (file: TFile | undefined) => void; onRef: (ref: React.RefObject | undefined) => void; + settings: TSettings; }; const FileProperties = () => { @@ -49,6 +50,7 @@ const FileUploaderContainer = ({ getSocket, onFileDrop, onRef, + settings, }: TFileUploaderContainer) => { const { is_appstore } = React.useContext>(PlatformContext); const ref = React.useRef(); @@ -67,7 +69,7 @@ const FileUploaderContainer = ({
- +
@@ -130,7 +132,7 @@ const FileUploaderContainer = ({ 'account-poa__upload-file--dashboard': isDesktop() && is_appstore, })} > - + ); diff --git a/packages/account/src/Components/file-uploader-container/file-uploader.tsx b/packages/account/src/Components/file-uploader-container/file-uploader.tsx index 69d40bec4efb..4efefe6d25b1 100644 --- a/packages/account/src/Components/file-uploader-container/file-uploader.tsx +++ b/packages/account/src/Components/file-uploader-container/file-uploader.tsx @@ -10,6 +10,7 @@ import { getSupportedFiles, max_document_size, supported_filetypes, + TSettings, } from '@deriv/shared'; import { TFile } from 'Types'; @@ -34,8 +35,8 @@ const fileReadErrorMessage = (filename: string) => { const FileUploader = React.forwardRef< HTMLElement, - { onFileDrop: (file: TFile | undefined) => void; getSocket: () => WebSocket } ->(({ onFileDrop, getSocket }, ref) => { + { onFileDrop: (file: TFile | undefined) => void; getSocket: () => WebSocket; settings: TSettings } +>(({ onFileDrop, getSocket, settings = {} }, ref) => { const [document_file, setDocumentFile] = useStateCallback({ files: [], error_message: null }); const handleAcceptedFiles = (files: TFileObject[]) => { @@ -72,7 +73,7 @@ const FileUploader = React.forwardRef< return new Promise((resolve, reject) => { compressImageFiles(document_file.files) .then(files_to_process => { - readFiles(files_to_process, fileReadErrorMessage) + readFiles(files_to_process, fileReadErrorMessage, settings) .then(processed_files => { processed_files.forEach(file => { if (file.message) { diff --git a/packages/account/src/Components/poi/status/unsupported/detail-component.tsx b/packages/account/src/Components/poi/status/unsupported/detail-component.tsx index 15d02741ddb9..1405cd64e982 100644 --- a/packages/account/src/Components/poi/status/unsupported/detail-component.tsx +++ b/packages/account/src/Components/poi/status/unsupported/detail-component.tsx @@ -85,6 +85,7 @@ const DetailComponent = ({ expirationDate: expiration_date, documentId: data.document_id || '', lifetimeValid: +(lifetime_valid && !expiration_date), + document_issuing_country: country_code_key, }) .then(response => { file_to_upload_index += 1; diff --git a/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership-form.spec.js b/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership-form.spec.js index 47d6991b8b11..b26bda1e2ff1 100644 --- a/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership-form.spec.js +++ b/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership-form.spec.js @@ -12,6 +12,7 @@ describe('proof-of-ownership-form.jsx', () => { refreshNotifications={jest.fn()} is_dark_mode={false} client_email={'test@testing.com'} + citizen='id' /> ); const cardItems = screen.getAllByRole('card-item'); @@ -25,6 +26,7 @@ describe('proof-of-ownership-form.jsx', () => { refreshNotifications={jest.fn()} is_dark_mode={false} client_email={'test@testing.com'} + citizen='id' /> ); const cardItems = screen.getAllByRole('card-item'); @@ -38,6 +40,7 @@ describe('proof-of-ownership-form.jsx', () => { refreshNotifications={jest.fn()} is_dark_mode={false} client_email={'test@testing.com'} + citizen='id' /> ); const poo_dropdown_button = await screen.findByTestId('dt_proof-of-ownership-button'); diff --git a/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership.spec.js b/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership.spec.js index 6bf1b94608e2..121fabf2f3bb 100644 --- a/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership.spec.js +++ b/packages/account/src/Sections/Verification/ProofOfOwnership/__test__/proof-of-ownership.spec.js @@ -25,6 +25,9 @@ describe('proof-of-ownership.jsx', () => { ownership: { requests: [], status: 'none' }, }, }, + account_settings: { + citizen: 'id', + }, }, }); render(); @@ -40,6 +43,9 @@ describe('proof-of-ownership.jsx', () => { ownership: { requests: [], status: 'verified' }, }, }, + account_settings: { + citizen: 'id', + }, }, }); render(); @@ -55,6 +61,9 @@ describe('proof-of-ownership.jsx', () => { ownership: { requests: [], status: 'pending' }, }, }, + account_settings: { + citizen: 'id', + }, }, }); render(); @@ -70,6 +79,9 @@ describe('proof-of-ownership.jsx', () => { ownership: { requests: [], status: 'rejected' }, }, }, + account_settings: { + citizen: 'id', + }, }, }); render(); @@ -86,6 +98,9 @@ describe('proof-of-ownership.jsx', () => { needs_verification: ['ownership'], }, }, + account_settings: { + citizen: 'id', + }, }, }); render(); diff --git a/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership-form.jsx b/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership-form.jsx index 5f3b310f4085..12157b2f0198 100644 --- a/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership-form.jsx +++ b/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership-form.jsx @@ -283,6 +283,7 @@ ProofOfOwnershipForm.propTypes = { grouped_payment_method_data: PropTypes.object, refreshNotifications: PropTypes.func, updateAccountStatus: PropTypes.func, + citizen: PropTypes.string, }; export default ProofOfOwnershipForm; diff --git a/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership.jsx b/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership.jsx index b0ce9a0cde8d..ab6ddc34bdd3 100644 --- a/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership.jsx +++ b/packages/account/src/Sections/Verification/ProofOfOwnership/proof-of-ownership.jsx @@ -14,6 +14,8 @@ export const ProofOfOwnership = observer(() => { const { is_dark_mode_on: is_dark_mode } = ui; const cards = account_status?.authentication?.ownership?.requests; const [status, setStatus] = useState(POO_STATUSES.none); + const citizen = client?.account_settings?.citizen || client?.account_settings?.country_code; + const grouped_payment_method_data = React.useMemo(() => { const groups = {}; const payment_methods_config = getPaymentMethodsConfig(); @@ -50,6 +52,7 @@ export const ProofOfOwnership = observer(() => { updateAccountStatus={updateAccountStatus} refreshNotifications={refreshNotifications} client_email={client_email} + citizen={citizen} /> ); // Proof of ownership is required. } diff --git a/packages/components/src/components/cellmeasurer/CellMeasurerCache.d.ts b/packages/components/src/components/cellmeasurer/CellMeasurerCache.d.ts index e422edf8791b..c8c5eb3c30a3 100644 --- a/packages/components/src/components/cellmeasurer/CellMeasurerCache.d.ts +++ b/packages/components/src/components/cellmeasurer/CellMeasurerCache.d.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-unresolved */ declare module '@enykeev/react-virtualized/dist/es/CellMeasurer/CellMeasurerCache' { import { CellMeasurerCache } from '@enykeev/react-virtualized/dist/es/CellMeasurer/CellMeasurerCache'; diff --git a/packages/components/src/components/date-picker/date-picker-input.tsx b/packages/components/src/components/date-picker/date-picker-input.tsx index ac6c0cfc1367..5c6dac964aab 100644 --- a/packages/components/src/components/date-picker/date-picker-input.tsx +++ b/packages/components/src/components/date-picker/date-picker-input.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import Icon from '../icon'; import Input from '../input'; -const DatePickerIcon = ({ icon, ...props }: React.ComponentProps) => ( +const DatePickerIcon = ({ icon, ...props }: React.ComponentProps) => ( ); diff --git a/packages/shared/src/utils/files/file-uploader-utils.ts b/packages/shared/src/utils/files/file-uploader-utils.ts index 14baeac8eb6f..552d71d4da88 100644 --- a/packages/shared/src/utils/files/file-uploader-utils.ts +++ b/packages/shared/src/utils/files/file-uploader-utils.ts @@ -2,7 +2,7 @@ import { compressImg, convertToBase64, isImageType, getFormatFromMIME, TImage } export type TFile = File & { file: Blob }; -type TSettings = { +export type TSettings = { documentType: { passport: string; national_identity_card: string; @@ -25,6 +25,7 @@ type TSettings = { expirationDate?: string; documentId?: string; lifetimeValid?: boolean; + document_issuing_country?: string; }; type TFileObject = TSettings & { diff --git a/packages/trader/src/Modules/Contract/Components/InfoBox/info-box-longcode.jsx b/packages/trader/src/Modules/Contract/Components/InfoBox/info-box-longcode.jsx index 301c067d5d08..4ca6bd3e1e10 100644 --- a/packages/trader/src/Modules/Contract/Components/InfoBox/info-box-longcode.jsx +++ b/packages/trader/src/Modules/Contract/Components/InfoBox/info-box-longcode.jsx @@ -10,21 +10,27 @@ const InfoBoxLongcode = ({ contract_info }) => { const [is_collapsed, setIsCollapsed] = React.useState(true); const handleToggle = () => { - setIsCollapsed(!is_collapsed); + setIsCollapsed(!is_collapsed); }; - + return (
- + {contract_info.longcode} {` `} - {contract_info.longcode.length > max_longcode_length && - - {is_collapsed ? localize('View more') : localize('View less')} - } + {contract_info.longcode.length > max_longcode_length && ( + + {is_collapsed ? localize('View more') : localize('View less')} + + )}
); From ca31c81f88661c12e3ade4d69888a0b5b1add300 Mon Sep 17 00:00:00 2001 From: Likhith Kolayari <98398322+likhith-deriv@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:35:29 +0400 Subject: [PATCH 04/20] shahzaib/WALL-756/allow idv for vanuatu jurisdiction on MT5 (#8907) * chore: allow idv for vanuatu jurisdiction on MT5 * chore: update document status message in jurisdiction cards regarding idv * refactor: shared util function to check idv supported resident * refactor: util function to get supported verification service * chore: added flag to check if idv is disallowed * fix: failing test cases * refactor: refactoring the condition for better readability and conciseness * refactor: nullish coalescing operator * refactor: updated type for authenticationStatusInfo util function * chore: updated conditions regarding the vanuatu idv support in cfd * chore: updated conditions regarding the vanuatu idv support in cfd * chore: minor condition update * chore: update poa condition to poi in card banner * chore: resolve review comments * chore: prevent idv for eu region selection * refactor: condition to check verification status for maltainvest in cfd * refactor: renamed status variable for better readability * refactor: renamed status variable name * chore: update jurisdiction content * chore: update jurisdiction title indicator * chore: update test case * chore: update test case for format-response * chore: update test case for cfd-poi * chore: empty commit * fix: :bug: resolved incorrect section display (#5) * fix: failing test cases --------- Co-authored-by: Shahzaib --- .../poi-poa-docs-submitted.tsx | 10 +- .../proof-of-identity-container-for-mt5.jsx | 6 +- .../proof-of-identity-submission-for-mt5.jsx | 16 +- .../failed-verification-modal.tsx | 12 +- .../src/Components/__tests__/cfd-poi.spec.js | 4 + packages/cfd/src/Components/cfd-poi.tsx | 4 +- ...-financial-stp-real-account-signup.spec.js | 7 +- .../src/Containers/cfd-dbvi-onboarding.tsx | 23 +- .../cfd-financial-stp-real-account-signup.tsx | 8 +- .../cfd/src/Containers/cfd-password-modal.tsx | 10 +- .../jurisdiction-modal-content-wrapper.tsx | 12 +- .../jurisdiction-title-indicator.tsx | 28 +-- .../Containers/mt5-compare-table-content.tsx | 12 +- packages/cfd/src/Containers/props.types.ts | 11 +- packages/shared/src/utils/cfd/cfd.ts | 201 +++++++++++------- .../helpers/__tests__/format-response.ts | 86 +++++++- .../src/utils/helpers/format-response.ts | 13 ++ 17 files changed, 296 insertions(+), 167 deletions(-) diff --git a/packages/account/src/Components/poi-poa-docs-submitted/poi-poa-docs-submitted.tsx b/packages/account/src/Components/poi-poa-docs-submitted/poi-poa-docs-submitted.tsx index 8cee4c674c0e..65c6ff92f3b4 100644 --- a/packages/account/src/Components/poi-poa-docs-submitted/poi-poa-docs-submitted.tsx +++ b/packages/account/src/Components/poi-poa-docs-submitted/poi-poa-docs-submitted.tsx @@ -41,14 +41,12 @@ const PoiPoaDocsSubmitted = ({ }; const getDescription = () => { - const { manual_status, poi_verified_for_vanuatu_maltainvest, poi_verified_for_bvi_labuan, poa_pending } = + const { manual_status, poi_verified_for_maltainvest, poi_verified_for_bvi_labuan_vanuatu, poa_pending } = getAuthenticationStatusInfo(account_status); - const is_vanuatu_or_maltainvest_selected = [Jurisdiction.VANUATU, Jurisdiction.MALTA_INVEST].includes( - jurisdiction_selected_shortcode - ); + const is_maltainvest_selected = jurisdiction_selected_shortcode === Jurisdiction.MALTA_INVEST; if ( - (is_vanuatu_or_maltainvest_selected && poi_verified_for_vanuatu_maltainvest && poa_pending) || - (!is_vanuatu_or_maltainvest_selected && poi_verified_for_bvi_labuan && poa_pending) || + (is_maltainvest_selected && poi_verified_for_maltainvest && poa_pending) || + (!is_maltainvest_selected && poi_verified_for_bvi_labuan_vanuatu && poa_pending) || manual_status === 'pending' ) { return localize('We’ll review your documents and notify you of its status within 1 - 3 working days.'); diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx index eb9da4c642c9..a931df72d4cc 100644 --- a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-container-for-mt5.jsx @@ -20,8 +20,7 @@ const ProofOfIdentityContainerForMt5 = ({ onStateChange, refreshNotifications, citizen_data, - jurisdiction_selected_shortcode, - updateAccountStatus, + is_eu_user, }) => { const [api_error, setAPIError] = React.useState(); const [residence_list, setResidenceList] = React.useState(); @@ -88,9 +87,8 @@ const ProofOfIdentityContainerForMt5 = ({ residence_list={residence_list} citizen_data={citizen_data} has_idv_error={has_idv_error} - jurisdiction_selected_shortcode={jurisdiction_selected_shortcode} getChangeableFields={getChangeableFields} - updateAccountStatus={updateAccountStatus} + is_eu_user={is_eu_user} /> ); }; diff --git a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx index 821e6b2094e3..f724b2988c46 100644 --- a/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx +++ b/packages/account/src/Sections/Verification/ProofOfIdentity/proof-of-identity-submission-for-mt5.jsx @@ -1,6 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import React from 'react'; -import { WS, Jurisdiction, IDV_NOT_APPLICABLE_OPTION } from '@deriv/shared'; +import { WS, isVerificationServiceSupported, IDV_NOT_APPLICABLE_OPTION } from '@deriv/shared'; import Unsupported from 'Components/poi/status/unsupported'; import OnfidoUpload from './onfido-sdk-view-container'; import { identity_status_codes, submission_status_code, service_code } from './proof-of-identity-utils'; @@ -18,7 +18,8 @@ const POISubmissionForMT5 = ({ refreshNotifications, citizen_data, has_idv_error, - jurisdiction_selected_shortcode, + residence_list, + is_eu_user, }) => { const [submission_status, setSubmissionStatus] = React.useState(); // submitting const [submission_service, setSubmissionService] = React.useState(); @@ -26,14 +27,9 @@ const POISubmissionForMT5 = ({ if (citizen_data) { const { submissions_left: idv_submissions_left } = idv; const { submissions_left: onfido_submissions_left } = onfido; - const is_idv_supported = citizen_data.identity.services.idv.is_country_supported; - const is_onfido_supported = citizen_data.identity.services.onfido.is_country_supported; - if ( - is_idv_supported && - Number(idv_submissions_left) > 0 && - !is_idv_disallowed && - jurisdiction_selected_shortcode !== Jurisdiction.VANUATU - ) { + const is_idv_supported = isVerificationServiceSupported(residence_list, account_settings, 'idv'); + const is_onfido_supported = isVerificationServiceSupported(residence_list, account_settings, 'onfido'); + if (is_idv_supported && Number(idv_submissions_left) > 0 && !is_idv_disallowed && !is_eu_user) { setSubmissionService(service_code.idv); } else if (onfido_submissions_left > 0 && is_onfido_supported) { setSubmissionService(service_code.onfido); diff --git a/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.tsx b/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.tsx index 9ec4d31bbcb9..3440bff2c4ef 100644 --- a/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.tsx +++ b/packages/appstore/src/components/modals/failed-veriification-modal/failed-verification-modal.tsx @@ -66,7 +66,7 @@ const FailedVerificationModal = () => { const { disableApp, enableApp } = ui; const is_from_multipliers = open_failed_verification_for === 'multipliers'; - const { poi_resubmit_for_vanuatu_maltainvest, poi_resubmit_for_bvi_labuan, need_poa_resubmission } = + const { poi_resubmit_for_maltainvest, poi_resubmit_for_bvi_labuan_vanuatu, need_poa_resubmission } = getAuthenticationStatusInfo(account_status); const history = useHistory(); @@ -88,14 +88,10 @@ const FailedVerificationModal = () => { }; const should_resubmit_poi = () => { - if ( - is_from_multipliers || - open_failed_verification_for === 'vanuatu' || - open_failed_verification_for === 'maltainvest' - ) { - return poi_resubmit_for_vanuatu_maltainvest; + if (is_from_multipliers || open_failed_verification_for === 'maltainvest') { + return poi_resubmit_for_maltainvest; } - return poi_resubmit_for_bvi_labuan; + return poi_resubmit_for_bvi_labuan_vanuatu; }; const should_resubmit_poa = need_poa_resubmission; const from_account_label = is_from_multipliers ? localize('Multipliers') : localize('MT5'); diff --git a/packages/cfd/src/Components/__tests__/cfd-poi.spec.js b/packages/cfd/src/Components/__tests__/cfd-poi.spec.js index 5e52132653c7..c9a2f2620cbd 100644 --- a/packages/cfd/src/Components/__tests__/cfd-poi.spec.js +++ b/packages/cfd/src/Components/__tests__/cfd-poi.spec.js @@ -85,6 +85,10 @@ describe('', () => { form_error: undefined, height: 'auto', index: 1, + is_loading: false, + is_switching: false, + is_virtual: false, + is_eu_user: false, onCancel: jest.fn(), onSave: jest.fn(), onSubmit: jest.fn(), diff --git a/packages/cfd/src/Components/cfd-poi.tsx b/packages/cfd/src/Components/cfd-poi.tsx index e37c56bf4c0b..5a021339c8a2 100644 --- a/packages/cfd/src/Components/cfd-poi.tsx +++ b/packages/cfd/src/Components/cfd-poi.tsx @@ -31,7 +31,7 @@ export type TCFDPOIProps = { }; const CFDPOI = observer(({ index, onSave, onSubmit, height, ...props }: TCFDPOIProps) => { - const { client, common, notifications } = useStore(); + const { client, common, notifications, traders_hub } = useStore(); const { account_status, @@ -48,6 +48,7 @@ const CFDPOI = observer(({ index, onSave, onSubmit, height, ...props }: TCFDPOIP } = client; const { routeBackInApp, app_routing_history } = common; const { refreshNotifications } = notifications; + const { is_eu_user } = traders_hub; const poi_props = { account_status, @@ -64,6 +65,7 @@ const CFDPOI = observer(({ index, onSave, onSubmit, height, ...props }: TCFDPOIP refreshNotifications, getChangeableFields, updateAccountStatus, + is_eu_user, ...props, }; diff --git a/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js b/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js index 275ef18014de..0014b09aa3d1 100644 --- a/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js +++ b/packages/cfd/src/Containers/__tests__/cfd-financial-stp-real-account-signup.spec.js @@ -183,6 +183,7 @@ describe('', () => { storeProofOfAddress: jest.fn(), }, }, + jurisdiction_selected_shortcode: 'svg', }; it('should render CFDFinancialStpRealAccountSignup component', () => { @@ -194,7 +195,7 @@ describe('', () => { }); it('should render properly for the first step content', () => { - getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_bvi_labuan: true }); + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_bvi_labuan_vanuatu: true }); render(, { wrapper: ({ children }) => {children}, }); @@ -223,7 +224,7 @@ describe('', () => { }, }; - getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_vanuatu_maltainvest: true }); + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_bvi_labuan_vanuatu: true }); render(, { wrapper: ({ children }) => {children}, @@ -243,7 +244,7 @@ describe('', () => { }, }; - getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_vanuatu_maltainvest: true }); + getAuthenticationStatusInfo.mockReturnValueOnce({ need_poi_for_maltainvest: true }); render(, { wrapper: ({ children }) => {children}, diff --git a/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx b/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx index 75cbbd09df72..f02387d3d3c6 100644 --- a/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx +++ b/packages/cfd/src/Containers/cfd-dbvi-onboarding.tsx @@ -60,30 +60,27 @@ const CFDDbviOnboarding = observer(() => { if (get_account_status?.authentication) { const { - poi_acknowledged_for_vanuatu_maltainvest, - poi_acknowledged_for_bvi_labuan, + poi_acknowledged_for_maltainvest, + poi_acknowledged_for_bvi_labuan_vanuatu, poa_acknowledged, poa_resubmit_for_labuan, need_poa_submission, } = getAuthenticationStatusInfo(get_account_status); - - if (jurisdiction_selected_shortcode === Jurisdiction.VANUATU) { - setShowSubmittedModal( - poi_acknowledged_for_vanuatu_maltainvest && - poa_acknowledged && - has_submitted_cfd_personal_details - ); - } else if (jurisdiction_selected_shortcode === Jurisdiction.MALTA_INVEST) { - setShowSubmittedModal(poi_acknowledged_for_vanuatu_maltainvest && poa_acknowledged); + if (jurisdiction_selected_shortcode === Jurisdiction.MALTA_INVEST) { + setShowSubmittedModal(poi_acknowledged_for_maltainvest && poa_acknowledged); } else if (jurisdiction_selected_shortcode === Jurisdiction.LABUAN) { /* When verified with IDV+ Photo ID, POA is auto verified */ const is_poa_submitted = poa_resubmit_for_labuan ? false : !need_poa_submission; setShowSubmittedModal( - poi_acknowledged_for_bvi_labuan && has_submitted_cfd_personal_details && is_poa_submitted + poi_acknowledged_for_bvi_labuan_vanuatu && + has_submitted_cfd_personal_details && + is_poa_submitted ); } else setShowSubmittedModal( - poi_acknowledged_for_bvi_labuan && poa_acknowledged && has_submitted_cfd_personal_details + poi_acknowledged_for_bvi_labuan_vanuatu && + poa_acknowledged && + has_submitted_cfd_personal_details ); } diff --git a/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx b/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx index 14dd74e08b47..28d3b8f723fe 100644 --- a/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx +++ b/packages/cfd/src/Containers/cfd-financial-stp-real-account-signup.tsx @@ -86,7 +86,7 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt const state_index = step; let is_mounted = React.useRef(true).current; - const { need_poi_for_vanuatu_maltainvest, need_poi_for_bvi_labuan } = getAuthenticationStatusInfo(account_status); + const { need_poi_for_maltainvest, need_poi_for_bvi_labuan_vanuatu } = getAuthenticationStatusInfo(account_status); const is_authenticated_with_idv_photoid = useIsAccountStatusPresent('authenticated_with_idv_photoid'); @@ -137,10 +137,10 @@ const CFDFinancialStpRealAccountSignup = observer(({ onFinish }: TCFDFinancialSt }; const should_show_poi = () => { - if ([Jurisdiction.VANUATU, Jurisdiction.MALTA_INVEST].includes(jurisdiction_selected_shortcode)) { - return need_poi_for_vanuatu_maltainvest; + if (jurisdiction_selected_shortcode === Jurisdiction.MALTA_INVEST) { + return need_poi_for_maltainvest; } - return need_poi_for_bvi_labuan; + return need_poi_for_bvi_labuan_vanuatu; }; const shouldShowPOA = () => { diff --git a/packages/cfd/src/Containers/cfd-password-modal.tsx b/packages/cfd/src/Containers/cfd-password-modal.tsx index 6dc0a6e07537..3dbf3f6c213c 100644 --- a/packages/cfd/src/Containers/cfd-password-modal.tsx +++ b/packages/cfd/src/Containers/cfd-password-modal.tsx @@ -638,7 +638,7 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr const is_password_reset = error_type === 'PasswordReset'; const [is_sent_email_modal_open, setIsSentEmailModalOpen] = React.useState(false); - const { poi_verified_for_bvi_labuan, poi_verified_for_vanuatu_maltainvest, poa_verified, manual_status } = + const { poi_verified_for_bvi_labuan_vanuatu, poi_verified_for_maltainvest, poa_verified, manual_status } = getAuthenticationStatusInfo(account_status); const [is_selected_mt5_verified, setIsSelectedMT5Verified] = React.useState(false); @@ -649,16 +649,14 @@ const CFDPasswordModal = observer(({ form_error, platform }: TCFDPasswordModalPr setIsSelectedMT5Verified(true); break; case Jurisdiction.BVI: - setIsSelectedMT5Verified(poi_verified_for_bvi_labuan); - break; case Jurisdiction.VANUATU: - setIsSelectedMT5Verified(poi_verified_for_vanuatu_maltainvest); + setIsSelectedMT5Verified(poi_verified_for_bvi_labuan_vanuatu); break; case Jurisdiction.LABUAN: - setIsSelectedMT5Verified(poi_verified_for_bvi_labuan && poa_verified); + setIsSelectedMT5Verified(poi_verified_for_bvi_labuan_vanuatu && poa_verified); break; case Jurisdiction.MALTA_INVEST: - setIsSelectedMT5Verified(poi_verified_for_vanuatu_maltainvest && poa_verified); + setIsSelectedMT5Verified(poi_verified_for_maltainvest && poa_verified); break; default: } diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx index a51e32d5a465..aecf6eea4673 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-modal-content-wrapper.tsx @@ -44,8 +44,8 @@ const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisd const { poi_or_poa_not_submitted, - poi_acknowledged_for_bvi_labuan, - poi_acknowledged_for_vanuatu_maltainvest, + poi_acknowledged_for_bvi_labuan_vanuatu, + poi_acknowledged_for_maltainvest, poa_acknowledged, need_poa_resubmission, poa_resubmit_for_labuan, @@ -144,7 +144,7 @@ const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisd openPasswordModal(type_of_account); } else if (is_vanuatu_selected) { if ( - poi_acknowledged_for_vanuatu_maltainvest && + poi_acknowledged_for_bvi_labuan_vanuatu && !poi_or_poa_not_submitted && !should_restrict_vanuatu_account_creation && poa_acknowledged && @@ -156,7 +156,7 @@ const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisd } } else if (is_bvi_selected) { if ( - poi_acknowledged_for_bvi_labuan && + poi_acknowledged_for_bvi_labuan_vanuatu && !poi_or_poa_not_submitted && !should_restrict_bvi_account_creation && poa_acknowledged && @@ -168,7 +168,7 @@ const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisd } } else if (is_labuan_selected) { if ( - poi_acknowledged_for_bvi_labuan && + poi_acknowledged_for_bvi_labuan_vanuatu && poa_acknowledged && has_submitted_cfd_personal_details && !poa_resubmit_for_labuan @@ -178,7 +178,7 @@ const JurisdictionModalContentWrapper = observer(({ openPasswordModal }: TJurisd toggleCFDVerificationModal(); } } else if (is_maltainvest_selected) { - if (poi_acknowledged_for_vanuatu_maltainvest && poa_acknowledged) { + if (poi_acknowledged_for_maltainvest && poa_acknowledged) { openPasswordModal(type_of_account); } else { toggleCFDVerificationModal(); diff --git a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-title-indicator.tsx b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-title-indicator.tsx index 5583b06c4806..b36ada1d9689 100644 --- a/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-title-indicator.tsx +++ b/packages/cfd/src/Containers/jurisdiction-modal/jurisdiction-title-indicator.tsx @@ -13,12 +13,12 @@ const JurisdictionTitleIndicator = ({ verification_docs, }: TJurisdictionTitleIndicatorProps) => { const { - poi_pending_for_bvi_labuan, - poi_resubmit_for_bvi_labuan, - poi_verified_for_bvi_labuan, - poi_pending_for_vanuatu_maltainvest, - poi_resubmit_for_vanuatu_maltainvest, - poi_verified_for_vanuatu_maltainvest, + poi_pending_for_bvi_labuan_vanuatu, + poi_resubmit_for_bvi_labuan_vanuatu, + poi_verified_for_bvi_labuan_vanuatu, + poi_pending_for_maltainvest, + poi_resubmit_for_maltainvest, + poi_verified_for_maltainvest, poa_pending, need_poa_resubmission, poa_verified, @@ -26,23 +26,23 @@ const JurisdictionTitleIndicator = ({ const getVerificationIconVariant = (verification_document: TJurisdictionCardItemVerificationItem): string => { let icon_variant: TJurisdictionCardVerificationStatus = 'Default'; - if ([Jurisdiction.BVI, Jurisdiction.LABUAN].includes(type_of_card)) { + if ([Jurisdiction.BVI, Jurisdiction.LABUAN, Jurisdiction.VANUATU].includes(type_of_card)) { if (['document_number', 'selfie', 'identity_document'].includes(verification_document)) { - if (poi_pending_for_bvi_labuan) { + if (poi_pending_for_bvi_labuan_vanuatu) { icon_variant = 'Pending'; - } else if (poi_resubmit_for_bvi_labuan) { + } else if (poi_resubmit_for_bvi_labuan_vanuatu) { icon_variant = 'Failed'; - } else if (poi_verified_for_bvi_labuan) { + } else if (poi_verified_for_bvi_labuan_vanuatu) { icon_variant = 'Verified'; } } - } else if ([Jurisdiction.VANUATU, Jurisdiction.MALTA_INVEST].includes(type_of_card)) { + } else if (Jurisdiction.MALTA_INVEST === type_of_card) { if (['document_number', 'selfie', 'identity_document'].includes(verification_document)) { - if (poi_pending_for_vanuatu_maltainvest) { + if (poi_pending_for_maltainvest) { icon_variant = 'Pending'; - } else if (poi_resubmit_for_vanuatu_maltainvest) { + } else if (poi_resubmit_for_maltainvest) { icon_variant = 'Failed'; - } else if (poi_verified_for_vanuatu_maltainvest) { + } else if (poi_verified_for_maltainvest) { icon_variant = 'Verified'; } } diff --git a/packages/cfd/src/Containers/mt5-compare-table-content.tsx b/packages/cfd/src/Containers/mt5-compare-table-content.tsx index e99f193913b2..bcea6221dcbf 100755 --- a/packages/cfd/src/Containers/mt5-compare-table-content.tsx +++ b/packages/cfd/src/Containers/mt5-compare-table-content.tsx @@ -233,8 +233,8 @@ const DMT5CompareModalContent = observer( const is_high_risk_for_mt5 = synthetic_accounts_count === 1 && financial_accounts_count === 1; const { poi_or_poa_not_submitted, - poi_acknowledged_for_vanuatu_maltainvest, - poi_acknowledged_for_bvi_labuan, + poi_acknowledged_for_maltainvest, + poi_acknowledged_for_bvi_labuan_vanuatu, poa_acknowledged, poa_pending, } = getAuthenticationStatusInfo(account_status); @@ -346,7 +346,7 @@ const DMT5CompareModalContent = observer( setAppstorePlatform(CFD_PLATFORMS.MT5); setJurisdictionSelectedShortcode(Jurisdiction.BVI); if ( - poi_acknowledged_for_bvi_labuan && + poi_acknowledged_for_bvi_labuan_vanuatu && !poi_or_poa_not_submitted && !should_restrict_bvi_account_creation && has_submitted_personal_details && @@ -362,7 +362,7 @@ const DMT5CompareModalContent = observer( setAppstorePlatform(CFD_PLATFORMS.MT5); setJurisdictionSelectedShortcode(Jurisdiction.VANUATU); if ( - poi_acknowledged_for_vanuatu_maltainvest && + poi_acknowledged_for_bvi_labuan_vanuatu && !poi_or_poa_not_submitted && !should_restrict_vanuatu_account_creation && has_submitted_personal_details && @@ -376,7 +376,7 @@ const DMT5CompareModalContent = observer( case 'financial_labuan': setAppstorePlatform(CFD_PLATFORMS.MT5); setJurisdictionSelectedShortcode(Jurisdiction.LABUAN); - if (poi_acknowledged_for_bvi_labuan && poa_acknowledged && has_submitted_personal_details) { + if (poi_acknowledged_for_bvi_labuan_vanuatu && poa_acknowledged && has_submitted_personal_details) { openPasswordModal(type_of_account); } else { toggleCFDVerificationModal(); @@ -385,7 +385,7 @@ const DMT5CompareModalContent = observer( case 'financial_maltainvest': setAppstorePlatform(CFD_PLATFORMS.MT5); setJurisdictionSelectedShortcode(Jurisdiction.MALTA_INVEST); - if ((poi_acknowledged_for_vanuatu_maltainvest && poa_acknowledged) || is_demo_tab) { + if ((poi_acknowledged_for_maltainvest && poa_acknowledged) || is_demo_tab) { openPasswordModal(type_of_account); } else { toggleCFDVerificationModal(); diff --git a/packages/cfd/src/Containers/props.types.ts b/packages/cfd/src/Containers/props.types.ts index 156d289a0a0e..d86bd26f8f4a 100644 --- a/packages/cfd/src/Containers/props.types.ts +++ b/packages/cfd/src/Containers/props.types.ts @@ -1,5 +1,11 @@ import React from 'react'; -import { DetailsOfEachMT5Loginid, GetAccountStatus, VerifyEmailResponse } from '@deriv/api-types'; +import { + DetailsOfEachMT5Loginid, + GetAccountStatus, + GetSettings, + ResidenceList, + VerifyEmailResponse, +} from '@deriv/api-types'; import { FormikHelpers as FormikActions } from 'formik'; import { TCFDPasswordFormValues } from './cfd-password-modal'; import { @@ -194,6 +200,8 @@ export type TJurisdictionCardSectionProps = { export type TJurisdictionCardType = 'svg' | 'bvi' | 'vanuatu' | 'labuan' | 'maltainvest'; export type TVerificationStatusBannerProps = { + account_status: GetAccountStatus; + account_settings: GetSettings; account_type: string; card_classname: string; disabled: boolean; @@ -203,6 +211,7 @@ export type TVerificationStatusBannerProps = { real_swapfree_accounts_existing_data: TExistingData; should_restrict_bvi_account_creation: boolean; should_restrict_vanuatu_account_creation: boolean; + residence_list: ResidenceList; }; export type TJurisdictionCheckBoxProps = { diff --git a/packages/shared/src/utils/cfd/cfd.ts b/packages/shared/src/utils/cfd/cfd.ts index ed40356091ef..6d8241df2bdf 100644 --- a/packages/shared/src/utils/cfd/cfd.ts +++ b/packages/shared/src/utils/cfd/cfd.ts @@ -255,86 +255,123 @@ export const isLandingCompanyEnabled = ({ landing_companies, platform, type }: T return false; }; -export const getAuthenticationStatusInfo = (account_status: GetAccountStatus) => { +// Define the AuthenticationStatusInfo type +type TAuthenticationStatusInfo = { + poa_status?: string; + poi_status?: string; + idv_status?: string; + onfido_status?: string; + manual_status?: string; + acknowledged_status: string[]; + poi_acknowledged_for_maltainvest: boolean; + poi_poa_verified_for_bvi_labuan_vanuatu: boolean; + poa_acknowledged: boolean; + poi_poa_verified_for_maltainvest: boolean; + need_poa_submission: boolean; + poi_verified_for_maltainvest: boolean; + poi_acknowledged_for_bvi_labuan_vanuatu: boolean; + poi_verified_for_bvi_labuan_vanuatu: boolean; + poa_verified: boolean; + poi_or_poa_not_submitted: boolean; + need_poa_resubmission: boolean; + poi_and_poa_not_submitted: boolean; + poa_not_submitted: boolean; + poi_not_submitted: boolean; + need_poi_for_maltainvest: boolean; + need_poi_for_bvi_labuan_vanuatu: boolean; + poi_not_submitted_for_maltainvest: boolean; + poi_pending_for_bvi_labuan_vanuatu: boolean; + poi_pending_for_maltainvest: boolean; + poi_resubmit_for_maltainvest: boolean; + poi_resubmit_for_bvi_labuan_vanuatu: boolean; + poa_pending: boolean; + poa_resubmit_for_labuan: boolean; + is_idv_revoked: boolean; +}; + +export const getAuthenticationStatusInfo = (account_status: GetAccountStatus): TAuthenticationStatusInfo => { const risk_classification = account_status.risk_classification; - const poa_status = account_status?.authentication?.document?.status || ''; - const poi_status = account_status?.authentication?.identity?.status || ''; + const poa_status: string = account_status?.authentication?.document?.status || ''; + const poi_status: string = account_status?.authentication?.identity?.status || ''; - const idv_status = account_status?.authentication?.identity?.services?.idv?.status; - const onfido_status = account_status?.authentication?.identity?.services?.onfido?.status; - const manual_status = account_status?.authentication?.identity?.services?.manual?.status; + const services = account_status?.authentication?.identity?.services ?? {}; + const { + idv: { status: idv_status } = {}, + onfido: { status: onfido_status } = {}, + manual: { status: manual_status } = {}, + } = services; const is_authenticated_with_idv_photoid = account_status?.status?.includes('authenticated_with_idv_photoid'); const is_idv_revoked = account_status?.status?.includes('idv_revoked'); - const acknowledged_status = ['pending', 'verified']; - const failed_cases = ['rejected', 'expired', 'suspected']; - - const poa_not_submitted = poa_status === 'none'; - const poa_acknowledged = acknowledged_status.includes(poa_status); - const need_poa_submission = !poa_acknowledged; - const need_poa_resubmission = failed_cases.includes(poa_status); - const poa_verified = poa_status === 'verified'; - const poa_pending = poa_status === 'pending'; - - const poi_not_submitted = poi_status === 'none'; - const poi_or_poa_not_submitted = poa_not_submitted || poi_not_submitted; - const poi_and_poa_not_submitted = poa_not_submitted && poi_not_submitted; - - //vanuatu-maltainvest - - const poi_verified_for_vanuatu_maltainvest = [onfido_status, manual_status].includes('verified'); - const poi_acknowledged_for_vanuatu_maltainvest = - (onfido_status && acknowledged_status.includes(onfido_status)) || - (manual_status && acknowledged_status.includes(manual_status)); - - const poi_pending_for_vanuatu_maltainvest = - onfido_status && - manual_status && - [onfido_status, manual_status].includes('pending') && - !poi_verified_for_vanuatu_maltainvest; - - const need_poi_for_vanuatu_maltainvest = !poi_acknowledged_for_vanuatu_maltainvest; - const poi_not_submitted_for_vanuatu_maltainvest = - onfido_status && manual_status && [onfido_status, manual_status].every(status => status === 'none'); - const poi_resubmit_for_vanuatu_maltainvest = - !poi_pending_for_vanuatu_maltainvest && - !poi_not_submitted_for_vanuatu_maltainvest && - !poi_verified_for_vanuatu_maltainvest; - - const poi_poa_verified_for_vanuatu_maltainvest = poi_verified_for_vanuatu_maltainvest && poa_verified; - - //bvi-labuan - let poi_acknowledged_for_bvi_labuan; + const acknowledged_status: string[] = ['pending', 'verified']; + const failed_cases: string[] = ['rejected', 'expired', 'suspected']; + + const poa_not_submitted: boolean = poa_status === 'none'; + const need_poa_submission = !acknowledged_status.includes(poa_status); + const need_poa_resubmission: boolean = failed_cases.includes(poa_status); + const poa_verified: boolean = poa_status === 'verified'; + const poa_pending: boolean = poa_status === 'pending'; + const poa_acknowledged: boolean = acknowledged_status.includes(poa_status); + + const poi_not_submitted: boolean = poi_status === 'none'; + const poi_or_poa_not_submitted: boolean = poa_not_submitted || poi_not_submitted; + const poi_and_poa_not_submitted: boolean = poa_not_submitted && poi_not_submitted; + + //maltainvest + + // mf = maltainvest: only require onfido and manual + const mf_jurisdiction_statuses: string[] = [onfido_status, manual_status].filter( + (status: string | undefined) => status + ) as string[]; + // bvi_labuan_vanuatu jurisdictions: require idv, onfido and manual + const bvi_labuan_vanuatu_jurisdiction_statuses: string[] = [idv_status, onfido_status, manual_status].filter( + status => status + ) as string[]; + + const poi_verified_for_maltainvest: boolean = mf_jurisdiction_statuses.includes('verified'); + const poi_acknowledged_for_maltainvest: boolean = mf_jurisdiction_statuses.some(status => + acknowledged_status.includes(status) + ); + const poi_pending_for_maltainvest: boolean = + mf_jurisdiction_statuses.some(status => status === 'pending') && !poi_verified_for_maltainvest; + + const need_poi_for_maltainvest = !poi_acknowledged_for_maltainvest; + const poi_not_submitted_for_maltainvest: boolean = mf_jurisdiction_statuses.every(status => status === 'none'); + + const poi_resubmit_for_maltainvest: boolean = + !poi_pending_for_maltainvest && !poi_not_submitted_for_maltainvest && !poi_verified_for_maltainvest; + + const poi_poa_verified_for_maltainvest = poi_verified_for_maltainvest && poa_verified; + + //bvi-labuan-vanuatu + let poi_acknowledged_for_bvi_labuan_vanuatu: boolean = bvi_labuan_vanuatu_jurisdiction_statuses.some(status => + acknowledged_status.includes(status) + ); if (risk_classification === 'high') { - poi_acknowledged_for_bvi_labuan = onfido_status && acknowledged_status.includes(onfido_status); + poi_acknowledged_for_bvi_labuan_vanuatu = Boolean(onfido_status && acknowledged_status.includes(onfido_status)); } else { - poi_acknowledged_for_bvi_labuan = - (idv_status && acknowledged_status.includes(idv_status)) || - (onfido_status && acknowledged_status.includes(onfido_status)) || - (manual_status && acknowledged_status.includes(manual_status)); + poi_acknowledged_for_bvi_labuan_vanuatu = bvi_labuan_vanuatu_jurisdiction_statuses.some(status => + acknowledged_status.includes(status) + ); } + const need_poi_for_bvi_labuan_vanuatu = !poi_acknowledged_for_bvi_labuan_vanuatu; + const poi_not_submitted_for_bvi_labuan_vanuatu: boolean = bvi_labuan_vanuatu_jurisdiction_statuses.every( + status => status === 'none' + ); + + const poi_verified_for_bvi_labuan_vanuatu: boolean = bvi_labuan_vanuatu_jurisdiction_statuses.includes('verified'); + + const poi_pending_for_bvi_labuan_vanuatu: boolean = + bvi_labuan_vanuatu_jurisdiction_statuses.includes('pending') && !poi_verified_for_bvi_labuan_vanuatu; + + const poi_resubmit_for_bvi_labuan_vanuatu: boolean = + !poi_pending_for_bvi_labuan_vanuatu && + !poi_not_submitted_for_bvi_labuan_vanuatu && + !poi_verified_for_bvi_labuan_vanuatu; - const need_poi_for_bvi_labuan = !poi_acknowledged_for_bvi_labuan; - const poi_not_submitted_for_bvi_labuan = - idv_status && - onfido_status && - manual_status && - [idv_status, onfido_status, manual_status].every(status => status === 'none'); - - const poi_verified_for_bvi_labuan = [idv_status, onfido_status, manual_status].includes('verified'); - - const poi_pending_for_bvi_labuan = - idv_status && - onfido_status && - manual_status && - [idv_status, onfido_status, manual_status].includes('pending') && - !poi_verified_for_bvi_labuan; - - const poi_resubmit_for_bvi_labuan = - !poi_pending_for_bvi_labuan && !poi_not_submitted_for_bvi_labuan && !poi_verified_for_bvi_labuan; - const poi_poa_verified_for_bvi_labuan = poi_verified_for_bvi_labuan && poa_verified; + const poi_poa_verified_for_bvi_labuan_vanuatu: boolean = poi_verified_for_bvi_labuan_vanuatu && poa_verified; const poa_resubmit_for_labuan = is_authenticated_with_idv_photoid; return { @@ -344,27 +381,27 @@ export const getAuthenticationStatusInfo = (account_status: GetAccountStatus) => onfido_status, manual_status, acknowledged_status, - poi_acknowledged_for_vanuatu_maltainvest, - poi_poa_verified_for_bvi_labuan, + poi_acknowledged_for_maltainvest, + poi_poa_verified_for_bvi_labuan_vanuatu, poa_acknowledged, - poi_poa_verified_for_vanuatu_maltainvest, + poi_poa_verified_for_maltainvest, need_poa_submission, - poi_verified_for_vanuatu_maltainvest, - poi_acknowledged_for_bvi_labuan, - poi_verified_for_bvi_labuan, + poi_verified_for_maltainvest, + poi_acknowledged_for_bvi_labuan_vanuatu, + poi_verified_for_bvi_labuan_vanuatu, poa_verified, poi_or_poa_not_submitted, need_poa_resubmission, poi_and_poa_not_submitted, poa_not_submitted, poi_not_submitted, - need_poi_for_vanuatu_maltainvest, - need_poi_for_bvi_labuan, - poi_not_submitted_for_vanuatu_maltainvest, - poi_pending_for_bvi_labuan, - poi_pending_for_vanuatu_maltainvest, - poi_resubmit_for_vanuatu_maltainvest, - poi_resubmit_for_bvi_labuan, + need_poi_for_maltainvest, + need_poi_for_bvi_labuan_vanuatu, + poi_not_submitted_for_maltainvest, + poi_pending_for_bvi_labuan_vanuatu, + poi_pending_for_maltainvest, + poi_resubmit_for_maltainvest, + poi_resubmit_for_bvi_labuan_vanuatu, poa_pending, poa_resubmit_for_labuan, is_idv_revoked, diff --git a/packages/shared/src/utils/helpers/__tests__/format-response.ts b/packages/shared/src/utils/helpers/__tests__/format-response.ts index a4ecc34cbea8..c1b54cf27d52 100644 --- a/packages/shared/src/utils/helpers/__tests__/format-response.ts +++ b/packages/shared/src/utils/helpers/__tests__/format-response.ts @@ -1,6 +1,7 @@ -import { formatPortfolioPosition } from '../format-response'; +import { GetSettings, ResidenceList } from '@deriv/api-types'; +import { formatPortfolioPosition, isVerificationServiceSupported } from '../format-response'; -describe('formatPortfolioPosition', () => { +describe('format-response', () => { const mock_active_symbols = [{ display_name: 'Volatility 25 Index', symbol: 'R_25' }]; const portfolio_pos = { buy_price: 2500.5, @@ -13,7 +14,82 @@ describe('formatPortfolioPosition', () => { transaction_id: 5678, }; - it('should return an object with values in object passed as argument', () => { + const get_settings: GetSettings = { + account_opening_reason: '', + address_city: 'MUDGEERABA', + address_line_1: "29 Ross Street, .'", + address_line_2: ".'", + address_postcode: '111', + address_state: '', + allow_copiers: 0, + citizen: '', + client_tnc_status: 'Version 4.2.0 2020-08-07', + country: 'Singapore', + country_code: 'sg', + date_of_birth: 984960000, + email: 'mock@gmail.com', + email_consent: 1, + feature_flag: { + wallet: 0, + }, + first_name: 'deriv', + has_secret_answer: 1, + immutable_fields: ['residence'], + is_authenticated_payment_agent: 0, + last_name: 'am', + non_pep_declaration: 1, + phone: '+651213456', + place_of_birth: null, + preferred_language: 'EN', + request_professional_status: 0, + residence: 'Singapore', + salutation: '', + tax_identification_number: null, + tax_residence: null, + user_hash: 'mock_hash', + }; + + const residence_list: ResidenceList = [ + { + disabled: 'DISABLED', + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + driving_licence: { + display_name: 'Driving Licence', + }, + national_identity_card: { + display_name: 'National Identity Card', + }, + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 1, + }, + }, + }, + phone_idd: '65', + text: 'Singapore', + tin_format: [ + '^[SsTtFfGg]\\d{7}[A-Za-z]$', + '^[A-Za-z]{9,10}$', + '^[Ff]0000\\d{6}$', + '^[Ff]\\d{9}$', + '^([Ss]|[Tt][4])\\d{9}$', + '^[Aa]\\d{9}$', + ], + value: 'sg', + }, + ]; + + it('should return an object with values in object passed as argument to formatPortfolioPosition', () => { expect(formatPortfolioPosition(portfolio_pos, mock_active_symbols)).toEqual({ details: 'test
test
test', display_name: 'Volatility 25 Index', @@ -28,4 +104,8 @@ describe('formatPortfolioPosition', () => { contract_info: portfolio_pos, }); }); + + it('should return true if residence is in the list of supported countries for onfido', () => { + expect(isVerificationServiceSupported(residence_list, get_settings, 'onfido')).toBeTruthy(); + }); }); diff --git a/packages/shared/src/utils/helpers/format-response.ts b/packages/shared/src/utils/helpers/format-response.ts index 5cb8f2baac36..7f111cd1c063 100644 --- a/packages/shared/src/utils/helpers/format-response.ts +++ b/packages/shared/src/utils/helpers/format-response.ts @@ -1,3 +1,4 @@ +import { GetSettings, ResidenceList } from '@deriv/api-types'; import { getUnsupportedContracts } from '../constants'; import { getSymbolDisplayName, TActiveSymbols } from './active-symbols'; import { getMarketInformation } from './market-underlying'; @@ -54,3 +55,15 @@ export const formatPortfolioPosition = ( contract_update: portfolio_pos.limit_order, }; }; + +export const isVerificationServiceSupported = ( + residence_list: ResidenceList, + account_settings: GetSettings, + service: 'idv' | 'onfido' +): boolean => { + const citizen = account_settings?.citizen || account_settings?.country_code; + if (!citizen) return false; + const citizen_data = residence_list.find(item => item.value === citizen); + + return !!citizen_data?.identity?.services?.[service]?.is_country_supported; +}; From e17e9d9951826f6140e6d2e1d64d1cc45c52bd49 Mon Sep 17 00:00:00 2001 From: hirad-deriv Date: Sun, 30 Jul 2023 11:56:46 +0800 Subject: [PATCH 05/20] Hirad/chore: updated mt5 links (#9497) * chore: updated mt5 links * fix: fixed the tests issues --- .../src/Components/__tests__/cfd-download-container.spec.js | 4 ++-- packages/cfd/src/Helpers/constants.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cfd/src/Components/__tests__/cfd-download-container.spec.js b/packages/cfd/src/Components/__tests__/cfd-download-container.spec.js index 6c4c4dfb75b5..c89d2822ac89 100644 --- a/packages/cfd/src/Components/__tests__/cfd-download-container.spec.js +++ b/packages/cfd/src/Components/__tests__/cfd-download-container.spec.js @@ -52,7 +52,7 @@ describe('CFDDownloadContainer', () => { render(); expect(screen.getByText(/IcInstallationWindows/i).closest('a')).toHaveAttribute( 'href', - 'https://download.mql5.com/cdn/web/deriv.limited/mt5/derivmt5setup.exe' + 'https://download.mql5.com/cdn/web/deriv.holdings.guernsey/mt5/deriv5setup.exe' ); expect(screen.getByText(/IcInstallationMacos/i).closest('a')).toHaveAttribute( 'href', @@ -64,7 +64,7 @@ describe('CFDDownloadContainer', () => { ); expect(screen.getByText(/IcInstallationGoogle/i).closest('a')).toHaveAttribute( 'href', - 'https://download.mql5.com/cdn/mobile/mt5/android?server=Deriv-Demo,Deriv-Server' + 'https://download.mql5.com/cdn/mobile/mt5/android?server=Deriv-Demo,Deriv-Server,Deriv-Server-02' ); expect(screen.getByText(/IcInstallationHuawei/i).closest('a')).toHaveAttribute( 'href', diff --git a/packages/cfd/src/Helpers/constants.ts b/packages/cfd/src/Helpers/constants.ts index b65930b3f05c..f887d5c0ce74 100644 --- a/packages/cfd/src/Helpers/constants.ts +++ b/packages/cfd/src/Helpers/constants.ts @@ -90,17 +90,17 @@ const getPlatformDerivEZDownloadLink = (platform: 'ios' | 'android' | 'huawei') const getPlatformMt5DownloadLink = (platform: string | undefined = undefined) => { switch (platform || OSDetect()) { case 'windows': - return 'https://download.mql5.com/cdn/web/deriv.limited/mt5/derivmt5setup.exe'; + return 'https://download.mql5.com/cdn/web/deriv.holdings.guernsey/mt5/deriv5setup.exe'; case 'linux': return 'https://www.metatrader5.com/en/terminal/help/start_advanced/install_linux'; case 'ios': - return 'https://apps.apple.com/us/app/metatrader-5/id413251709'; + return 'https://download.mql5.com/cdn/mobile/mt5/ios?server=Deriv-Demo,Deriv-Server,Deriv-Server-02'; case 'macos': return 'https://download.mql5.com/cdn/web/metaquotes.software.corp/mt5/MetaTrader5.dmg'; case 'huawei': return 'https://appgallery.huawei.com/#/app/C102015329'; case 'android': - return 'https://download.mql5.com/cdn/mobile/mt5/android?server=Deriv-Demo,Deriv-Server'; + return 'https://download.mql5.com/cdn/mobile/mt5/android?server=Deriv-Demo,Deriv-Server,Deriv-Server-02'; default: return getMT5WebTerminalLink({ category: 'real' }); // Web } From e1ea94c827544f1cc170d2c9018b9e2cd6663b7a Mon Sep 17 00:00:00 2001 From: Muhammad Hamza <120543468+hamza-deriv@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:22:24 +0800 Subject: [PATCH 06/20] chore: update the broker name in mt5 trade modal (#9498) --- .../src/components/cfds-listing/cfds-listing.scss | 3 +++ packages/cfd/src/Containers/dmt5-trade-modal.tsx | 12 +++++++++--- packages/cfd/src/Helpers/constants.ts | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index 1ce3319d8701..bba932feb054 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -449,6 +449,9 @@ &__spec-text { font-family: 'Courier', monospace; overflow: hidden; + &-broker { + margin-left: 4rem; + } } &__download-center { border-top: 2px solid var(--general-section-1); diff --git a/packages/cfd/src/Containers/dmt5-trade-modal.tsx b/packages/cfd/src/Containers/dmt5-trade-modal.tsx index 7a8753cacc8d..ff0edf8ad8b9 100644 --- a/packages/cfd/src/Containers/dmt5-trade-modal.tsx +++ b/packages/cfd/src/Containers/dmt5-trade-modal.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import classNames from 'classnames'; import { Text, Button, Icon, Money, Popover } from '@deriv/components'; import { TPasswordBoxProps, TTradingPlatformAccounts } from '../Components/props.types'; import { DetailsOfEachMT5Loginid } from '@deriv/api-types'; @@ -33,11 +34,16 @@ type TMT5TradeModalProps = { export type TSpecBoxProps = { value: string | undefined; is_bold?: boolean; + is_broker?: boolean; }; -const SpecBox = ({ value, is_bold }: TSpecBoxProps) => ( +const SpecBox = ({ value, is_bold, is_broker }: TSpecBoxProps) => (
- + {value} @@ -150,7 +156,7 @@ const DMT5TradeModal = ({
{localize('Broker')} - +
{localize('Server')} diff --git a/packages/cfd/src/Helpers/constants.ts b/packages/cfd/src/Helpers/constants.ts index f887d5c0ce74..c272393ed11b 100644 --- a/packages/cfd/src/Helpers/constants.ts +++ b/packages/cfd/src/Helpers/constants.ts @@ -52,7 +52,7 @@ const DXTRADE_IOS_APP_URL = 'https://apps.apple.com/us/app/deriv-x/id1563337503' const DXTRADE_ANDROID_APP_URL = 'https://play.google.com/store/apps/details?id=com.deriv.dx'; const DXTRADE_HUAWEI_APP_URL = 'https://appgallery.huawei.com/app/C104633219'; -const getBrokerName = () => 'Deriv Limited'; +const getBrokerName = () => 'Deriv Holdings (Guernsey) Limited'; const getTopUpConfig = () => { return { From 32d1c7a3d87139757f13de7edcaca1310ec69449 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza <120543468+hamza-deriv@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:51:41 +0800 Subject: [PATCH 07/20] fix: alignment changed (#9502) --- packages/appstore/src/components/cfds-listing/cfds-listing.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/appstore/src/components/cfds-listing/cfds-listing.scss b/packages/appstore/src/components/cfds-listing/cfds-listing.scss index bba932feb054..7a7bd9fd83b8 100644 --- a/packages/appstore/src/components/cfds-listing/cfds-listing.scss +++ b/packages/appstore/src/components/cfds-listing/cfds-listing.scss @@ -450,7 +450,7 @@ font-family: 'Courier', monospace; overflow: hidden; &-broker { - margin-left: 4rem; + text-align: right; } } &__download-center { From 41c941e4edca27ab99614b66e3d49f3482273232 Mon Sep 17 00:00:00 2001 From: amina-deriv <84661147+amina-deriv@users.noreply.github.com> Date: Mon, 31 Jul 2023 16:52:59 +0400 Subject: [PATCH 08/20] Amina/fix: empty currency selector screen (#9389) * fix: test * fix: test * fix: test * fix: test * feat: test case * fix: test --- .../__tests__/account-wizard.spec.tsx | 188 ++++++++++++++++++ .../RealAccountSignup/account-wizard.jsx | 82 ++++---- packages/core/src/Stores/client-store.js | 1 + 3 files changed, 236 insertions(+), 35 deletions(-) create mode 100644 packages/core/src/App/Containers/RealAccountSignup/__tests__/account-wizard.spec.tsx diff --git a/packages/core/src/App/Containers/RealAccountSignup/__tests__/account-wizard.spec.tsx b/packages/core/src/App/Containers/RealAccountSignup/__tests__/account-wizard.spec.tsx new file mode 100644 index 000000000000..f40e05d982f8 --- /dev/null +++ b/packages/core/src/App/Containers/RealAccountSignup/__tests__/account-wizard.spec.tsx @@ -0,0 +1,188 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import AccountWizard from '../account-wizard'; + +jest.mock('Stores/connect', () => ({ + __esModule: true, + default: 'mockedDefaultExport', + connect: () => (Component: React.ReactElement) => Component, +})); + +jest.mock('@deriv/components', () => ({ + ...jest.requireActual('@deriv/components'), + Wizard: jest.fn(({ children }) =>
{children}
), +})); + +jest.mock('../account-wizard-form', () => ({ + getItems: jest.fn(() => [ + { + header: { + active_title: 'Sample Active Title', + title: ' Sample CURRENCY', + }, + body: 'Test', + form_value: [], + props: {}, + passthrough: [''], + icon:
Icon
, + }, + ]), +})); + +const Test = () =>
TestComponent
; + +jest.mock('../account-wizard-form', () => ({ + getItems: jest.fn(() => [ + { + header: { + active_title: 'Sample Active Title', + title: ' Sample CURRENCY', + }, + body: Test, + form_value: [], + props: {}, + passthrough: [''], + icon:
Icon
, + }, + ]), +})); + +describe('', () => { + const mock_props = { + account_status: { + currency_config: { usd: {} }, + p2p_status: 'none', + risk_classification: '', + status: [], + }, + closeRealAccountSignup: jest.fn(), + content_flag: '', + fetchAccountSettings: jest.fn(), + fetchResidenceList: jest.fn(), + fetchStatesList: jest.fn(), + financial_assessment: [], + has_currency: true, + has_real_account: false, + has_residence: true, + is_virtual: true, + real_account_signup_target: 'svg', + realAccountSignup: jest.fn(), + refreshNotifications: jest.fn(), + residence: 'id', + setIsRealAccountSignupModalVisible: jest.fn(), + setIsTradingAssessmentForNewUserEnabled: jest.fn(), + setShouldShowAppropriatenessWarningModal: jest.fn(), + setShouldShowRiskWarningModal: jest.fn(), + upgrade_info: '', + setSubSectionIndex: jest.fn(), + sub_section_index: 0, + getChangeableFields: jest.fn(), + openPendingDialog: jest.fn(), + removeNotificationByKey: jest.fn(), + removeNotificationMessage: jest.fn(), + storeProofOfAddress: jest.fn(), + toggleModal: jest.fn(), + account_settings: { + account_opening_reason: '', + address_city: 'MUDGEERABA', + address_line_1: "29 Ross Street, .'", + address_line_2: ".'", + address_postcode: '111', + address_state: '', + allow_copiers: 0, + citizen: '', + client_tnc_status: 'Version 4.2.0 2020-08-07', + country: 'Singapore', + country_code: 'sg', + date_of_birth: 984960000, + email: 'mock@gmail.com', + email_consent: 1, + feature_flag: { + wallet: 0, + }, + first_name: 'mock_name', + has_secret_answer: 1, + immutable_fields: ['residence'], + is_authenticated_payment_agent: 0, + last_name: 'am', + non_pep_declaration: 1, + phone: '+651213456', + place_of_birth: null, + preferred_language: 'EN', + request_professional_status: 0, + residence: 'Singapore', + salutation: '', + tax_identification_number: null, + tax_residence: null, + user_hash: '823341c18bfccb391b6bb5d77ab7e6a83991f82669c1ba4e5b01dbd2fd71c7fe', + }, + is_fully_authenticated: true, + landing_company: { + config: { + tax_details_required: 1, + tin_format: ['^\\d{15}$'], + tin_format_description: '999999999999999', + }, + dxtrade_financial_company: {}, + dxtrade_gaming_company: {}, + financial_company: {}, + gaming_company: {}, + id: 'id', + minimum_age: 18, + mt_financial_company: {}, + mt_gaming_company: {}, + name: 'Indonesia', + virtual_company: 'virtual', + }, + residence_list: [ + { + identity: { + services: { + idv: { + documents_supported: {}, + has_visual_sample: 0, + is_country_supported: 0, + }, + onfido: { + documents_supported: { + passport: { + display_name: 'Passport', + }, + }, + is_country_supported: 0, + }, + }, + }, + phone_idd: '93', + text: 'Afghanistan', + value: 'af', + }, + ], + states_list: [ + { + text: 'Central Singapore', + value: '01', + }, + ], + setLoading: jest.fn(), + }; + + it('should render AccountWizard component', () => { + render(); + expect(screen.getByTestId('dt_wizard')).toBeInTheDocument(); + expect(screen.getByText('TestComponent')).toBeInTheDocument(); + }); + + it('should fetch ResidenceList if ResidenceList is empty ', () => { + render(); + expect(mock_props.fetchResidenceList).toBeCalledTimes(1); + expect(screen.getByTestId('dt_wizard')).toBeInTheDocument(); + expect(screen.getByText('TestComponent')).toBeInTheDocument(); + }); + + it('should fetch StatesList if StatesList is empty ', () => { + render(); + expect(mock_props.fetchStatesList).toBeCalledTimes(1); + expect(screen.getByText('TestComponent')).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx index 61e1dee9963f..4ed0eaaf60e4 100644 --- a/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx +++ b/packages/core/src/App/Containers/RealAccountSignup/account-wizard.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, FormProgress, MobileWrapper, Text, Wizard } from '@deriv/components'; -import { WS, getLocation, makeCancellablePromise, toMoment, IDV_NOT_APPLICABLE_OPTION } from '@deriv/shared'; +import { WS, getLocation, toMoment, IDV_NOT_APPLICABLE_OPTION } from '@deriv/shared'; import { Localize } from '@deriv/translations'; import { connect } from 'Stores/connect'; import AcceptRiskForm from './accept-risk-form.jsx'; @@ -59,30 +59,37 @@ const AccountWizard = props => { const [state_items, setStateItems] = React.useState([]); const [should_accept_financial_risk, setShouldAcceptFinancialRisk] = React.useState(false); - React.useEffect(() => { - props.setIsTradingAssessmentForNewUserEnabled(true); - props.fetchStatesList(); - const { cancel, promise } = makeCancellablePromise(props.fetchResidenceList()); - const { cancel: cancelFinancialAssessment, promise: financial_assessment_promise } = makeCancellablePromise( - props.fetchFinancialAssessment() - ); + const { + setIsTradingAssessmentForNewUserEnabled, + residence_list, + states_list, + fetchResidenceList, + fetchStatesList, + has_residence, + setLoading, + } = props; + + const getData = async () => { + setLoading(true); + if (!residence_list.length) await fetchResidenceList(); + if (has_residence && !states_list.length) { + await fetchStatesList(); + } + setLoading(false); + }; - Promise.all([promise, financial_assessment_promise]).then(() => { - setStateItems(previous_state => { - if (!previous_state.length) { - return getItems(props); - } - return previous_state; - }); - setPreviousData(fetchFromStorage()); - setMounted(true); + React.useEffect(() => { + setIsTradingAssessmentForNewUserEnabled(true); + getData(); + setStateItems(previous_state => { + if (!previous_state.length) { + return getItems(props); + } + return previous_state; }); - - return () => { - cancel(); - cancelFinancialAssessment(); - }; - }, []); + setPreviousData(fetchFromStorage()); + setMounted(true); + }, [residence_list, states_list, fetchResidenceList, fetchStatesList, has_residence]); React.useEffect(() => { if (previous_data.length > 0) { @@ -98,7 +105,7 @@ const AccountWizard = props => { }, [previous_data]); React.useEffect(() => { - if (props.residence_list.length) { + if (residence_list.length) { const setDefaultPhone = country_code => { let items; if (state_items.length) { @@ -112,9 +119,9 @@ const AccountWizard = props => { setStateItems(items); } }; - getCountryCode(props.residence_list).then(setDefaultPhone); + getCountryCode(residence_list).then(setDefaultPhone); } - }, [props.residence_list]); + }, [residence_list]); const fetchFromStorage = () => { const stored_items = localStorage.getItem('real_account_signup_wizard'); @@ -128,8 +135,8 @@ const AccountWizard = props => { } }; - const getCountryCode = async residence_list => { - const response = residence_list.find(item => item.value === props.residence); + const getCountryCode = async residences => { + const response = residences.find(item => item.value === props.residence); if (!response || !response.phone_idd) return ''; return `+${response.phone_idd}`; }; @@ -151,16 +158,16 @@ const AccountWizard = props => { } if (values.place_of_birth) { values.place_of_birth = values.place_of_birth - ? getLocation(props.residence_list, values.place_of_birth, 'value') + ? getLocation(residence_list, values.place_of_birth, 'value') : ''; } if (values.citizen) { - values.citizen = values.citizen ? getLocation(props.residence_list, values.citizen, 'value') : ''; + values.citizen = values.citizen ? getLocation(residence_list, values.citizen, 'value') : ''; } if (values.tax_residence) { values.tax_residence = values.tax_residence - ? getLocation(props.residence_list, values.tax_residence, 'value') + ? getLocation(residence_list, values.tax_residence, 'value') : values.tax_residence; } @@ -264,7 +271,7 @@ const AccountWizard = props => { }; const createRealAccount = (payload = undefined) => { - props.setLoading(true); + setLoading(true); const form_data = { ...form_values() }; submitForm(payload) .then(response => { @@ -298,7 +305,7 @@ const AccountWizard = props => { } }) .finally(() => { - props.setLoading(false); + setLoading(false); localStorage.removeItem('current_question_index'); }); }; @@ -319,6 +326,7 @@ const AccountWizard = props => { } if (!mounted) return null; + if (!finished) { const wizard_steps = state_items.map((step, step_index) => { const passthrough = getPropsForChild(step_index); @@ -376,11 +384,12 @@ AccountWizard.propTypes = { account_status: PropTypes.object, closeRealAccountSignup: PropTypes.func, content_flag: PropTypes.string, - fetchFinancialAssessment: PropTypes.func, fetchResidenceList: PropTypes.func, + fetchAccountSettings: PropTypes.func, fetchStatesList: PropTypes.func, has_currency: PropTypes.bool, has_real_account: PropTypes.bool, + has_residence: PropTypes.bool, is_loading: PropTypes.bool, is_virtual: PropTypes.bool, onClose: PropTypes.func, @@ -392,8 +401,11 @@ AccountWizard.propTypes = { realAccountSignup: PropTypes.func, residence_list: PropTypes.array, residence: PropTypes.string, + states_list: PropTypes.array, + setIsTradingAssessmentForNewUserEnabled: PropTypes.func, setIsRiskWarningVisible: PropTypes.func, setLoading: PropTypes.func, + setShouldShowRiskWarningModal: PropTypes.func, setSubSectionIndex: PropTypes.func, sub_section_index: PropTypes.number, }; @@ -404,12 +416,12 @@ export default connect(({ client, notifications, ui, traders_hub }) => ({ closeRealAccountSignup: ui.closeRealAccountSignup, content_flag: traders_hub.content_flag, fetchAccountSettings: client.fetchAccountSettings, - fetchFinancialAssessment: client.fetchFinancialAssessment, fetchResidenceList: client.fetchResidenceList, fetchStatesList: client.fetchStatesList, financial_assessment: client.financial_assessment, has_currency: !!client.currency, has_real_account: client.has_active_real_account, + has_residence: client.residence, is_fully_authenticated: client.is_fully_authenticated, is_virtual: client.is_virtual, real_account_signup_target: ui.real_account_signup_target, diff --git a/packages/core/src/Stores/client-store.js b/packages/core/src/Stores/client-store.js index 7e570d2986de..1df9a795f509 100644 --- a/packages/core/src/Stores/client-store.js +++ b/packages/core/src/Stores/client-store.js @@ -1732,6 +1732,7 @@ export default class ClientStore extends BaseStore { } if (this.residence) { await WS.authorized.cache.landingCompany(this.residence).then(this.responseLandingCompany); + await this.fetchStatesList(); } if (!this.is_virtual) await this.getLimits(); From 9b61ce5031b89917f27cef30a6a4eff79adc116c Mon Sep 17 00:00:00 2001 From: vinu-deriv <100689171+vinu-deriv@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:21:02 +0400 Subject: [PATCH 09/20] Refactor connect method dbot (#8873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Shafin/bot 188/refactor connect removal in master (#8836) * Revert "chore: change TToastConfig key to string" This reverts commit ab5073b1421125d499efba8ed413ed9fb5b792fc. * refactor: self-exclusion, summary-card, summary, trade-animation * refactor: dashboard, tour-guide, tour-slider, tour-trigger-dialog * refactor: journal, load-modal, local-footer, local * refactor: revert contract types * refactor: delete-dialog, google-drive, local-footer * refactor: remove connect from transaction, transactions * refactor: cards, info-panel, quick-strategy * fix: refeactored components (#8833) * refactor: replaced connect blockly-loading,bot-footer-extensions,bot-notification-message and chart (#8832) * refactor: replace connect method in bot-builder,workspace-wrapper,toolbar,toolbx and workspace-group (#8831) * refactor: replace connect function in bot-notification, faq-content, guide-content and sidebar (#8827) * refactor: replaced connect function in download, flyout-block, flyout and flyout-help-base (#8826) * Vinu/bot 179/replace connect function (#8829) * refactor: replaced connect in local, recet-footer, recent-workspace and recent files in bot-web-ui * fix: add missing import in recent.tsx * refactor: replace connect in recent-footer, recent-workspace and recent * Vinu/refactor: replaced connect method in save-modal, stop-bot-modal and w… (#8828) * refactor: replaced connect method in save-modal, stop-bot-modal and workspace-control files * fix: resolved linting issue in workspace-control file * Rupato/fix: refactored code (#8920) * fix: refactored code * fix: network component changes * chore: remove connect files completely from Dbot (#8934) * chore: destructure populateFooterExtensions from ui in bot footer * chore: remove connect methods and MobxContentProvider completely * chore: remove connect from test cases * chore: remove unused props * fix: removed any from toolbox and toolbar spec * fix: Empty-Commit * fix: write test cases (#9244) * fix: removed save-collection radio button (#9275) * fix: remove unused variables - code smells (#9292) * fix: Trigger Build * fix: fix console errors arising from bot-skeleton related to datadog (#9353) * fix: fix console errors arising from bot-skeleton related to datadog * fix: fixed the failing test cases --------- Co-authored-by: Shafin Al Karim <129021108+shafin-deriv@users.noreply.github.com> Co-authored-by: rupato-deriv <97010868+rupato-deriv@users.noreply.github.com> Co-authored-by: Ali(Ako) Hosseini --- .../api/__tests__/datadog-middleware.spec.ts | 1 + .../src/services/api/api-middleware.js | 24 +- packages/bot-web-ui/src/app/app-content.jsx | 6 +- .../__tests__/blockly-loading.spec.tsx | 74 +++++ .../blockly-loading/blockly-loading.tsx | 34 ++- .../bot-notification-messages.tsx | 52 ++-- .../bot-web-ui/src/components/chart/chart.tsx | 101 +++---- .../dashboard/bot-builder/bot-builder.tsx | 42 +-- .../toolbar/__tests__/toolbar.spec.tsx | 75 +++--- .../dashboard/bot-builder/toolbar/toolbar.tsx | 82 +++--- .../bot-builder/toolbar/workspace-group.tsx | 151 ++++++----- .../toolbox/__tests__/toolbox.spec.tsx | 35 +-- .../dashboard/bot-builder/toolbox/toolbox.tsx | 78 ++---- .../bot-builder/workspace-wrapper.tsx | 21 +- .../components/dashboard/bot-notification.tsx | 21 +- .../__tests__/dashboard-component.spec.tsx | 20 -- .../dashboard/dashboard-component/cards.tsx | 69 ++--- .../dashboard-component/info-panel.tsx | 38 +-- .../load-bot-preview/delete-dialog.tsx | 34 +-- .../load-bot-preview/google-drive.scss | 3 + .../load-bot-preview/google-drive.tsx | 33 +-- .../load-bot-preview/index.scss | 1 + .../load-bot-preview/local-footer.tsx | 33 +-- .../load-bot-preview/local.tsx | 61 +---- .../load-bot-preview/recent-footer.tsx | 20 +- .../load-bot-preview/recent-workspace.tsx | 64 ++--- .../load-bot-preview/recent.tsx | 36 +-- .../load-bot-preview}/save-modal.scss | 19 ++ .../load-bot-preview/save-modal.tsx | 75 +++--- .../load-bot-preview/stop-bot-modal.tsx | 62 ++--- .../load-bot-preview/workspace-control.tsx | 48 ++-- .../src/components/dashboard/dashboard.tsx | 110 ++------ .../quick-strategy/quick-strategy.tsx | 98 ++++--- .../src/components/dashboard/tour-guide.tsx | 18 +- .../src/components/dashboard/tour-slider.tsx | 39 +-- .../dashboard/tour-trigger-dialog.spec.tsx | 191 +++++++++++-- .../dashboard/tour-trigger-dialog.tsx | 33 +-- .../dashboard/tutorial-tab/faq-content.tsx | 19 +- .../dashboard/tutorial-tab/guide-content.tsx | 58 ++-- .../dashboard/tutorial-tab/sidebar.tsx | 32 +-- .../src/components/download/download.tsx | 26 +- .../src/components/flyout/flyout-block.jsx | 22 +- .../src/components/flyout/flyout.jsx | 67 ++--- .../flyout/help-contents/flyout-help-base.jsx | 59 ++-- packages/bot-web-ui/src/components/index.js | 1 - .../src/components/journal/journal.tsx | 49 ++-- .../src/components/load-modal/load-modal.tsx | 54 ++-- .../components/load-modal/local-footer.tsx | 35 +-- .../src/components/load-modal/local.tsx | 39 +-- .../components/load-modal/recent-footer.tsx | 29 +- .../load-modal/recent-workspace.tsx | 27 +- .../src/components/load-modal/recent.tsx | 28 +- .../load-modal/workspace-control.tsx | 43 ++- .../components/network-toast-popup/index.js | 3 - .../components/network-toast-popup/index.ts | 3 + ...oast-popup.jsx => network-toast-popup.tsx} | 23 +- .../route-prompt-dialog.tsx | 35 +-- .../run-panel/{index.js => index.ts} | 2 +- .../{run-panel.jsx => run-panel.tsx} | 156 ++++++----- .../components/save-modal/google-drive.scss | 3 - .../src/components/save-modal/index.js | 5 - .../src/components/save-modal/save-modal.jsx | 254 ------------------ .../self-exclusion/self-exclusion.jsx | 63 +++-- .../src/components/summary/summary-card.tsx | 48 +--- .../components/summary/summary-card.types.ts | 24 +- .../src/components/summary/summary.tsx | 18 +- .../trade-animation/trade-animation.jsx | 64 ++--- .../components/transactions/transaction.jsx | 141 +++++----- .../components/transactions/transactions.jsx | 22 +- packages/bot-web-ui/src/stores/connect.js | 31 --- .../bot-web-ui/src/stores/dashboard-store.ts | 1 + .../bot-web-ui/src/stores/load-modal-store.ts | 1 + .../bot-web-ui/src/stores/run-panel-store.js | 1 - .../bot-web-ui/src/stores/useDBotStore.tsx | 7 +- .../src/utils/journal-notifications.js | 2 +- packages/bot-web-ui/src/utils/multiplier.js | 12 - packages/stores/src/mockStore.ts | 3 + packages/stores/types.ts | 13 + 78 files changed, 1347 insertions(+), 2078 deletions(-) create mode 100644 packages/bot-web-ui/src/components/blockly-loading/__tests__/blockly-loading.spec.tsx rename packages/bot-web-ui/src/components/{save-modal => dashboard/dashboard-component/load-bot-preview}/save-modal.scss (99%) delete mode 100644 packages/bot-web-ui/src/components/network-toast-popup/index.js create mode 100644 packages/bot-web-ui/src/components/network-toast-popup/index.ts rename packages/bot-web-ui/src/components/network-toast-popup/{network-toast-popup.jsx => network-toast-popup.tsx} (71%) rename packages/bot-web-ui/src/components/run-panel/{index.js => index.ts} (56%) rename packages/bot-web-ui/src/components/run-panel/{run-panel.jsx => run-panel.tsx} (80%) delete mode 100644 packages/bot-web-ui/src/components/save-modal/google-drive.scss delete mode 100644 packages/bot-web-ui/src/components/save-modal/index.js delete mode 100644 packages/bot-web-ui/src/components/save-modal/save-modal.jsx delete mode 100644 packages/bot-web-ui/src/stores/connect.js diff --git a/packages/bot-skeleton/src/services/api/__tests__/datadog-middleware.spec.ts b/packages/bot-skeleton/src/services/api/__tests__/datadog-middleware.spec.ts index 1a05a301c2c4..98628eee1b16 100644 --- a/packages/bot-skeleton/src/services/api/__tests__/datadog-middleware.spec.ts +++ b/packages/bot-skeleton/src/services/api/__tests__/datadog-middleware.spec.ts @@ -45,6 +45,7 @@ describe('APIMiddleware', () => { }); api_middleware = new APIMiddleware(); + process.env.DATADOG_CLIENT_TOKEN_LOGS = '123'; }); it('Should get measure for each request, invoke method log(), clear measures', () => { diff --git a/packages/bot-skeleton/src/services/api/api-middleware.js b/packages/bot-skeleton/src/services/api/api-middleware.js index 6fbf57d73583..db2c79dc887c 100644 --- a/packages/bot-skeleton/src/services/api/api-middleware.js +++ b/packages/bot-skeleton/src/services/api/api-middleware.js @@ -18,15 +18,17 @@ if (isProduction) { dataDogEnv = 'staging'; } -datadogLogs.init({ - clientToken: DATADOG_CLIENT_TOKEN_LOGS, - site: 'datadoghq.com', - forwardErrorsToLogs: false, - service: 'Dbot', - sessionSampleRate: dataDogSessionSampleRate, - version: dataDogVersion, - env: dataDogEnv, -}); +if (DATADOG_CLIENT_TOKEN_LOGS) { + datadogLogs.init({ + clientToken: DATADOG_CLIENT_TOKEN_LOGS, + site: 'datadoghq.com', + forwardErrorsToLogs: false, + service: 'Dbot', + sessionSampleRate: dataDogSessionSampleRate, + version: dataDogVersion, + env: dataDogEnv, + }); +} export const REQUESTS = [ 'active_symbols', @@ -109,7 +111,9 @@ class APIMiddleware { REQUESTS.forEach(req_type => { const measure = performance.getEntriesByName(req_type); if (measure && measure.length) { - this.log(measure, is_bot_running, req_type); + if (process.env.DATADOG_CLIENT_TOKEN_LOGS) { + this.log(measure, is_bot_running, req_type); + } } }); performance.clearMeasures(); diff --git a/packages/bot-web-ui/src/app/app-content.jsx b/packages/bot-web-ui/src/app/app-content.jsx index 047458070545..745e1aa9aadb 100644 --- a/packages/bot-web-ui/src/app/app-content.jsx +++ b/packages/bot-web-ui/src/app/app-content.jsx @@ -5,7 +5,6 @@ import { observer, useStore } from '@deriv/stores'; import { Audio, BotNotificationMessages, Dashboard, NetworkToastPopup, RoutePromptDialog } from 'Components'; import BotBuilder from 'Components/dashboard/bot-builder'; import GTM from 'Utils/gtm'; -import { MobxContentProvider } from 'Stores/connect'; import { useDBotStore } from 'Stores/useDBotStore'; import BlocklyLoading from '../components/blockly-loading'; import './app.scss'; @@ -99,8 +98,7 @@ const AppContent = observer(() => { return is_loading ? ( ) : ( - // TODO: remove MobxContentProvider when all connect method is removed - + <>
-
+ ); }); diff --git a/packages/bot-web-ui/src/components/blockly-loading/__tests__/blockly-loading.spec.tsx b/packages/bot-web-ui/src/components/blockly-loading/__tests__/blockly-loading.spec.tsx new file mode 100644 index 000000000000..8341cdf9f534 --- /dev/null +++ b/packages/bot-web-ui/src/components/blockly-loading/__tests__/blockly-loading.spec.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { mockStore, StoreProvider } from '@deriv/stores'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { act, render, screen } from '@testing-library/react'; +import RootStore from '../../../stores/root-store'; +import { DBotStoreProvider, mockDBotStore } from '../../../stores/useDBotStore'; +import BlocklyLoading from '../blockly-loading'; + +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + isMobile: jest.fn(() => false), +})); + +jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({ + saveRecentWorkspace: jest.fn(), + unHighlightAllBlocks: jest.fn(), +})); +jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => jest.fn()); + +const mock_ws = { + authorized: { + subscribeProposalOpenContract: jest.fn(), + send: jest.fn(), + }, + storage: { + send: jest.fn(), + }, + contractUpdate: jest.fn(), + subscribeTicksHistory: jest.fn(), + forgetStream: jest.fn(), + activeSymbols: jest.fn(), + send: jest.fn(), +}; + +describe('BlocklyLoading', () => { + let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; + + beforeAll(() => { + const mock_store = mockStore({}); + mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + + {children} + + + ); + }); + it('should render BlocklyLoading', () => { + const { container } = render(, { + wrapper, + }); + expect(container).toBeInTheDocument(); + }); + + it('should not render BlocklyLoading loader', () => { + render(, { + wrapper, + }); + expect(screen.queryByTestId('blockly-loader')).not.toBeInTheDocument(); + }); + + it('should render BlocklyLoading loader', () => { + act(() => { + mock_DBot_store?.blockly_store?.setLoading(true); + }); + render(, { + wrapper, + }); + expect(screen.getByTestId('blockly-loader')).toBeInTheDocument(); + }); +}); diff --git a/packages/bot-web-ui/src/components/blockly-loading/blockly-loading.tsx b/packages/bot-web-ui/src/components/blockly-loading/blockly-loading.tsx index c7338c44f42f..3c1cc738130e 100644 --- a/packages/bot-web-ui/src/components/blockly-loading/blockly-loading.tsx +++ b/packages/bot-web-ui/src/components/blockly-loading/blockly-loading.tsx @@ -1,22 +1,20 @@ import React from 'react'; import { Loading } from '@deriv/components'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from '../../stores/useDBotStore'; -type TBlocklyLoadingProps = { - is_loading: boolean; -}; +const BlocklyLoading = observer(() => { + const { blockly_store } = useDBotStore(); + const { is_loading } = blockly_store; + return ( + <> + {is_loading && ( +
+ +
+ )} + + ); +}); -const BlocklyLoading = ({ is_loading }: TBlocklyLoadingProps) => ( - <> - {is_loading && ( -
- -
- )} - -); - -export default connect(({ blockly_store }: RootStore) => ({ - is_loading: blockly_store.is_loading, -}))(BlocklyLoading); +export default BlocklyLoading; diff --git a/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.tsx b/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.tsx index a61931436508..34bc22b1a3a6 100644 --- a/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.tsx +++ b/packages/bot-web-ui/src/components/bot-notification-messages/bot-notification-messages.tsx @@ -1,37 +1,29 @@ import React from 'react'; import classNames from 'classnames'; +import { observer, useStore } from '@deriv/stores'; import { DBOT_TABS } from 'Constants/bot-contents'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; - -interface TBotNotificationMessagesProps { - is_drawer_open: boolean; - active_tab: number; - is_info_panel_visible: boolean; - Notifications: React.ComponentType; -} +import { useDBotStore } from 'Stores/useDBotStore'; const { BOT_BUILDER, CHART } = DBOT_TABS; -const BotNotificationMessages = ({ - is_drawer_open, - Notifications, - is_info_panel_visible, - active_tab, -}: TBotNotificationMessagesProps) => ( -
- -
-); +const BotNotificationMessages = observer(() => { + const { ui } = useStore(); + const { run_panel, dashboard } = useDBotStore(); + + const { is_drawer_open } = run_panel; + const Notifications = ui.notification_messages_ui; + const { active_tab, is_info_panel_visible } = dashboard; + + return ( +
+ +
+ ); +}); -export default connect(({ core, run_panel, dashboard }: RootStore) => ({ - is_drawer_open: run_panel.is_drawer_open, - Notifications: core.ui.notification_messages_ui, - active_tab: dashboard.active_tab, - is_info_panel_visible: dashboard.is_info_panel_visible, -}))(BotNotificationMessages); +export default BotNotificationMessages; diff --git a/packages/bot-web-ui/src/components/chart/chart.tsx b/packages/bot-web-ui/src/components/chart/chart.tsx index 954002992b9a..221a8ddff372 100644 --- a/packages/bot-web-ui/src/components/chart/chart.tsx +++ b/packages/bot-web-ui/src/components/chart/chart.tsx @@ -1,53 +1,44 @@ import React from 'react'; import classNames from 'classnames'; -import { ActiveSymbols, ForgetRequest } from '@deriv/api-types'; // TODO Remove this after smartcharts is replaced // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { ChartTitle, SmartChart } from '@deriv/deriv-charts'; import { isDesktop, isMobile } from '@deriv/shared'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { observer, useStore } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; import ToolbarWidgets from './toolbar-widgets'; -interface TChartProps { - chart_type: string; - getMarketsOrder: (active_symbols: ActiveSymbols) => string[]; - granularity: number; - is_drawer_open: boolean; - is_socket_opened: boolean; - onSymbolChange: (symbol: string) => void; - setChartStatus: (status: boolean) => void; - settings: object; - show_digits_stats: boolean; - symbol: object; - updateChartType: (chart_type: string) => void; - updateGranularity: (granularity: number) => void; - wsForget: (request: ForgetRequest) => void; - wsForgetStream: (stream_id: string) => void; - wsSendRequest: (req: { [k: string]: unknown }) => void; - wsSubscribe: (req: { [k: string]: unknown }, callback: () => void) => void; -} - -const Chart = ({ - chart_type, - getMarketsOrder, - granularity, - is_drawer_open, - is_socket_opened, - onSymbolChange, - setChartStatus, - settings, - show_digits_stats, - symbol, - updateChartType, - updateGranularity, - wsForget, - wsForgetStream, - wsSendRequest, - wsSubscribe, -}: TChartProps) => { +const Chart = observer(({ show_digits_stats }: { show_digits_stats: boolean }) => { const barriers: [] = []; + const { common, ui } = useStore(); + const { chart_store, run_panel } = useDBotStore(); + + const { + chart_type, + getMarketsOrder, + granularity, + onSymbolChange, + setChartStatus, + symbol, + updateChartType, + updateGranularity, + wsForget, + wsForgetStream, + wsSendRequest, + wsSubscribe, + } = chart_store; + const { is_drawer_open } = run_panel; + const is_socket_opened = common.is_socket_opened; + const settings = { + assetInformation: false, // ui.is_chart_asset_info_visible, + countdown: true, + isHighestLowestMarkerEnabled: false, // TODO: Pending UI, + language: common.current_language.toLowerCase(), + position: ui.is_chart_layout_default ? 'bottom' : 'left', + theme: ui.is_dark_mode_on ? 'dark' : 'light', + }; + return (
); -}; +}); -export default connect(({ chart_store, common, ui, run_panel }: RootStore) => ({ - chart_type: chart_store.chart_type, - getMarketsOrder: chart_store.getMarketsOrder, - granularity: chart_store.granularity, - last_contract: { - is_digit_contract: false, - }, - is_drawer_open: run_panel.is_drawer_open, - is_socket_opened: common.is_socket_opened, - onSymbolChange: chart_store.onSymbolChange, - setChartStatus: chart_store.setChartStatus, - settings: { - assetInformation: false, // ui.is_chart_asset_info_visible, - countdown: true, - isHighestLowestMarkerEnabled: false, // TODO: Pending UI, - language: common.current_language.toLowerCase(), - position: ui.is_chart_layout_default ? 'bottom' : 'left', - theme: ui.is_dark_mode_on ? 'dark' : 'light', - }, - symbol: chart_store.symbol, - updateChartType: chart_store.updateChartType, - updateGranularity: chart_store.updateGranularity, - wsForget: chart_store.wsForget, - wsForgetStream: chart_store.wsForgetStream, - wsSendRequest: chart_store.wsSendRequest, - wsSubscribe: chart_store.wsSubscribe, -}))(Chart); +export default Chart; diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx index 2c67df5bcdd9..382a156b57aa 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/bot-builder.tsx @@ -1,37 +1,20 @@ import React from 'react'; import classNames from 'classnames'; import { DesktopWrapper, MobileWrapper } from '@deriv/components'; -import LoadModal from 'Components/load-modal'; -import SaveModal from 'Components/save-modal'; -import AppStore from 'Stores/app-store'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from '../../../stores/useDBotStore'; +import LoadModal from '../../load-modal'; +import SaveModal from '../dashboard-component/load-bot-preview/save-modal'; import { BOT_BUILDER_TOUR } from '../joyride-config'; import QuickStrategy from '../quick-strategy'; import ReactJoyrideWrapper from '../react-joyride-wrapper'; import TourSlider from '../tour-slider'; import WorkspaceWrapper from './workspace-wrapper'; -type TBotBuilder = { - app: AppStore; - active_tab: number; - has_started_onboarding_tour: boolean; - has_started_bot_builder_tour: boolean; - is_preview_on_popup: boolean; - is_dark_mode_on: boolean; - setOnBoardTourRunState: (has_started_onboarding_tour: boolean) => boolean; - loadFileFromRecent: () => void; - selected_strategy_id: string; - previewRecentStrategy: (selected_strategy_id: string) => void; -}; +const BotBuilder = observer(() => { + const { dashboard, app } = useDBotStore(); + const { active_tab, has_started_onboarding_tour, has_started_bot_builder_tour, is_preview_on_popup } = dashboard; -const BotBuilder = ({ - app, - active_tab, - has_started_onboarding_tour, - has_started_bot_builder_tour, - is_preview_on_popup, -}: TBotBuilder) => { const [is_tour_running] = React.useState(true); const { onMount, onUnmount } = app; const el_ref = React.useRef(null); @@ -91,13 +74,6 @@ const BotBuilder = ({ ); -}; +}); -export default connect(({ app, dashboard }: RootStore) => ({ - app, - active_tab: dashboard.active_tab, - has_started_onboarding_tour: dashboard.has_started_onboarding_tour, - has_started_bot_builder_tour: dashboard.has_started_bot_builder_tour, - is_preview_on_popup: dashboard.is_preview_on_popup, - setOnBoardTourRunState: dashboard.setOnBoardTourRunState, -}))(BotBuilder); +export default BotBuilder; diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx index 1e8d30f85574..4110c8d34bdf 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/__tests__/toolbar.spec.tsx @@ -1,15 +1,36 @@ import React from 'react'; import { isDesktop, isMobile } from '@deriv/shared'; import { render, screen } from '@testing-library/react'; +import { useDBotStore } from 'Stores/useDBotStore'; import Toolbar from '..'; -jest.mock('Stores/connect', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: - () => - (Component: T) => - Component, +const mockDbotStore = { + run_panel: { + is_running: false, + }, + save_modal: { + toggleSaveModal: jest.fn(), + }, + load_modal: { + toggleLoadModal: jest.fn(), + }, + toolbar: { + has_redo_stack: false, + has_undo_stack: false, + closeResetDialog: jest.fn(), + onResetOkButtonClick: jest.fn(), + onResetClick: jest.fn(), + onSortClick: jest.fn(), + onUndoClick: jest.fn(), + onZoomInOutClick: jest.fn(), + is_dialog_open: false, + }, + quick_strategy: {}, + dashboard: {}, +}; + +jest.mock('Stores/useDBotStore', () => ({ + useDBotStore: jest.fn(() => mockDbotStore), })); jest.mock('@deriv/shared', () => ({ @@ -19,48 +40,30 @@ jest.mock('@deriv/shared', () => ({ })); describe('Toolbar component', () => { - const mocked_props = { - active_tab: '0', - file_name: 'qwe', - has_redo_stack: false, - has_undo_stack: false, - is_drawer_open: false, - is_stop_button_disabled: false, - is_stop_button_visible: false, - closeResetDialog: jest.fn(), - onOkButtonClick: jest.fn(), - onResetClick: jest.fn(), - onRunButtonClick: jest.fn(), - onSortClick: jest.fn(), - onUndoClick: jest.fn(), - onZoomInOutClick: jest.fn(), - toggleSaveLoadModal: jest.fn(), - toggleLoadModal: jest.fn(), - toggleSaveModal: jest.fn(), - }; - beforeEach(() => { - isDesktop.mockReturnValue(true); - isMobile.mockReturnValue(false); - jest.clearAllMocks(); + (useDBotStore as jest.Mock).mockReturnValue(mockDbotStore); }); - it('should render Toolbar', () => { - render(); + render(); expect(screen.getByTestId('dashboard__toolbar')).toBeInTheDocument(); }); it('Toolbar should renders a modal window, when the bot is running and dialog is open', () => { - render(); + (useDBotStore as jest.Mock).mockReturnValue({ + ...mockDbotStore, + run_panel: { ...mockDbotStore.run_panel, is_running: true }, + toolbar: { ...mockDbotStore.toolbar, is_dialog_open: true }, + }); + render(); expect(screen.getByTestId('dashboard__toolbar')).toBeInTheDocument(); expect(screen.getByRole('dialog')).toBeInTheDocument(); expect(screen.getByTestId('toolbar__dialog-text--second')).toBeInTheDocument(); }); it('Toolbar should renders a button, when it is mobile version', async () => { - isDesktop.mockReturnValue(false); - isMobile.mockReturnValue(true); - render(); + (isDesktop as jest.Mock).mockReturnValue(false); + (isMobile as jest.Mock).mockReturnValue(true); + render(); expect(await screen.findByRole('button')).toBeInTheDocument(); }); }); diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx index baeeb6daf0fb..4b1fcd1b3141 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/toolbar.tsx @@ -1,37 +1,30 @@ import React from 'react'; import { Dialog } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import ToolbarButton from './toolbar-button'; import WorkspaceGroup from './workspace-group'; -type TToolbar = { - active_tab: string; - file_name: string; - has_redo_stack: boolean; - has_undo_stack: boolean; - is_dialog_open: boolean; - is_drawer_open: boolean; - is_running: boolean; - is_stop_button_disabled: boolean; - is_stop_button_visible: boolean; - closeResetDialog: () => void; - onOkButtonClick: () => void; - onResetClick: () => void; - onRunButtonClick: () => void; - onSortClick: () => void; - onUndoClick: () => void; - onZoomInOutClick: () => void; - toggleSaveLoadModal: () => void; - toggleLoadModal: () => void; - toggleSaveModal: () => void; - loadDataStrategy: () => void; -}; +const Toolbar = observer(() => { + const { run_panel, save_modal, load_modal, toolbar, quick_strategy } = useDBotStore(); + const { + has_redo_stack, + has_undo_stack, + is_dialog_open, + closeResetDialog, + onResetOkButtonClick: onOkButtonClick, + onResetClick, + onSortClick, + onUndoClick, + onZoomInOutClick, + } = toolbar; + const { toggleSaveModal } = save_modal; + const { toggleLoadModal } = load_modal; + const { loadDataStrategy } = quick_strategy; + const { is_running } = run_panel; -const Toolbar = (props: TToolbar) => { - const { is_running, is_dialog_open, onOkButtonClick, closeResetDialog } = props; const confirm_button_text = is_running ? localize('Yes') : localize('OK'); const cancel_button_text = is_running ? localize('No') : localize('Cancel'); @@ -44,11 +37,20 @@ const Toolbar = (props: TToolbar) => { popover_message={localize('Click here to start building your Deriv Bot.')} button_id='db-toolbar__get-started-button' button_classname='toolbar__btn toolbar__btn--icon toolbar__btn--start' - buttonOnClick={props.loadDataStrategy} + buttonOnClick={loadDataStrategy} button_text={localize('Quick strategy')} /> )} - +
{ ); -}; +}); -export default connect(({ blockly_store, run_panel, save_modal, load_modal, toolbar, quick_strategy }: RootStore) => ({ - active_tab: blockly_store.active_tab, - file_name: toolbar.file_name, - has_redo_stack: toolbar.has_redo_stack, - has_undo_stack: toolbar.has_undo_stack, - is_dialog_open: toolbar.is_dialog_open, - is_drawer_open: run_panel.is_drawer_open, - is_running: run_panel.is_running, - is_stop_button_disabled: run_panel.is_stop_button_disabled, - is_stop_button_visible: run_panel.is_stop_button_visible, - closeResetDialog: toolbar.closeResetDialog, - onOkButtonClick: toolbar.onResetOkButtonClick, - onResetClick: toolbar.onResetClick, - onRunButtonClick: run_panel.onRunButtonClick, - onSortClick: toolbar.onSortClick, - onUndoClick: toolbar.onUndoClick, - onZoomInOutClick: toolbar.onZoomInOutClick, - toggleLoadModal: load_modal.toggleLoadModal, - toggleSaveModal: save_modal.toggleSaveModal, - loadDataStrategy: quick_strategy.loadDataStrategy, -}))(Toolbar); +export default Toolbar; diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/workspace-group.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/workspace-group.tsx index 140fde0fb6c0..fde776f620b4 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/workspace-group.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbar/workspace-group.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import ToolbarIcon from './toolbar-icon'; type TWorkspaceGroup = { @@ -11,81 +11,84 @@ type TWorkspaceGroup = { onSortClick: () => void; onUndoClick: (param?: boolean) => void; onZoomInOutClick: (param?: boolean) => void; - setPreviewOnPopup: (param: boolean) => void; toggleLoadModal: () => void; toggleSaveModal: () => void; }; -const WorkspaceGroup = ({ - has_redo_stack, - has_undo_stack, - onResetClick, - onSortClick, - onUndoClick, - onZoomInOutClick, - setPreviewOnPopup, - toggleLoadModal, - toggleSaveModal, -}: TWorkspaceGroup) => ( -
- - { - setPreviewOnPopup(true); - toggleLoadModal(); - }} - /> - - -
- onUndoClick(/* redo */ false)} - /> - onUndoClick(/* redo */ true)} - /> -
- onZoomInOutClick(/* in */ true)} - /> - onZoomInOutClick(/* in */ false)} - /> -
+const WorkspaceGroup = observer( + ({ + has_redo_stack, + has_undo_stack, + onResetClick, + onSortClick, + onUndoClick, + onZoomInOutClick, + toggleLoadModal, + toggleSaveModal, + }: TWorkspaceGroup) => { + const { dashboard } = useDBotStore(); + const { setPreviewOnPopup } = dashboard; + + return ( +
+ + { + setPreviewOnPopup(true); + toggleLoadModal(); + }} + /> + + +
+ onUndoClick(/* redo */ false)} + /> + onUndoClick(/* redo */ true)} + /> +
+ onZoomInOutClick(/* in */ true)} + /> + onZoomInOutClick(/* in */ false)} + /> +
+ ); + } ); -export default connect(({ dashboard }: RootStore) => ({ - setPreviewOnPopup: dashboard.setPreviewOnPopup, -}))(WorkspaceGroup); +export default WorkspaceGroup; diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/__tests__/toolbox.spec.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/__tests__/toolbox.spec.tsx index 1799cd6428f4..c9fb845b59b3 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/__tests__/toolbox.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/__tests__/toolbox.spec.tsx @@ -2,19 +2,11 @@ import React from 'react'; import { isMobile } from '@deriv/shared'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { useDBotStore } from 'Stores/useDBotStore'; import Toolbox from '../toolbox'; -jest.mock('Stores/connect', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: - () => - (Component: T) => - Component, -})); - -describe('Toolbox component', () => { - const mocked_props = { +const mockDbotStore = { + toolbox: { hasSubCategory: jest.fn(), is_search_loading: false, onMount: jest.fn(), @@ -25,19 +17,30 @@ describe('Toolbox component', () => { onToolboxItemClick: jest.fn(), onToolboxItemExpand: jest.fn(), onUnmount: jest.fn(), - setVisibility: jest.fn(), sub_category_index: [], + }, + flyout: { + setVisibility: jest.fn(), + }, + quick_strategy: { loadDataStrategy: jest.fn(), - }; + }, +}; + +jest.mock('Stores/useDBotStore', () => ({ + useDBotStore: jest.fn(() => mockDbotStore), +})); +describe('Toolbox component', () => { it('should render Toolbox with content wrapper is open', () => { - render(); + render(); expect(screen.getByTestId('dashboard__toolbox')).toBeInTheDocument(); expect(screen.getByTestId('db-toolbox__content-wrapper')).toHaveClass('db-toolbox__content-wrapper active'); }); it('should render Toolbox with content wrapper is open', () => { const setVisibility = jest.fn(); - render(); + (useDBotStore as jest.Mock).mockReturnValue({ ...mockDbotStore, flyout: { setVisibility } }); + render(); expect(screen.getByTestId('db-toolbox__title')).toBeInTheDocument(); userEvent.click(screen.getByTestId('db-toolbox__title')); @@ -45,7 +48,7 @@ describe('Toolbox component', () => { expect(setVisibility).toHaveBeenCalled(); }); it('should not render Toolbox if it is mobile version', () => { - render(); + render(); if (isMobile()) { expect(screen.getByRole('dashboard__toolbox')).toBeEmptyDOMElement(); } diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.tsx index 3163d8151967..ead591617037 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/toolbox/toolbox.tsx @@ -2,48 +2,33 @@ import React from 'react'; import classNames from 'classnames'; import { Icon, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from '../../../../stores/useDBotStore'; import ToolbarButton from '../toolbar/toolbar-button'; import SearchBox from './search-box'; import { ToolboxItems } from './toolbox-items'; -type TToolbox = { - hasSubCategory: (param: HTMLCollection) => boolean; - is_search_loading: boolean; - is_toolbox_open: boolean; - onMount: (param?: React.RefObject) => void; - onSearch: () => void; - onSearchBlur: () => void; - onSearchClear: () => void; - onSearchKeyUp: () => void; - onToolboxItemClick: (category: ChildNode) => void; - onToolboxItemExpand: (index: number) => void; - onUnmount: () => void; - setVisibility: (param: boolean) => void; - sub_category_index: number[]; - toggleDrawer: () => void; - toolbox_dom: HTMLElement; - loadDataStrategy: () => void; -}; +const Toolbox = observer(() => { + const { toolbox, flyout, quick_strategy } = useDBotStore(); + const { + hasSubCategory, + is_search_loading, + onMount, + onSearch, + onSearchBlur, + onSearchClear, + onSearchKeyUp, + onToolboxItemClick, + onToolboxItemExpand, + onUnmount, + sub_category_index, + toolbox_dom, + } = toolbox; + + const { setVisibility } = flyout; + const { loadDataStrategy } = quick_strategy; -const Toolbox = ({ - hasSubCategory, - is_search_loading, - onMount, - onSearch, - onSearchBlur, - onSearchClear, - onSearchKeyUp, - onToolboxItemClick, - onToolboxItemExpand, - onUnmount, - setVisibility, - sub_category_index, - toolbox_dom, - loadDataStrategy, -}: TToolbox) => { const toolbox_ref = React.useRef(ToolboxItems); const [is_open, setOpen] = React.useState(true); @@ -164,23 +149,6 @@ const Toolbox = ({ ); } return null; -}; +}); -export default connect(({ toolbox, flyout, quick_strategy }: RootStore) => ({ - hasSubCategory: toolbox.hasSubCategory, - is_search_loading: toolbox.is_search_loading, - is_toolbox_open: toolbox.is_toolbox_open, - onMount: toolbox.onMount, - onSearch: toolbox.onSearch, - onSearchBlur: toolbox.onSearchBlur, - onSearchClear: toolbox.onSearchClear, - onSearchKeyUp: toolbox.onSearchKeyUp, - onToolboxItemClick: toolbox.onToolboxItemClick, - onToolboxItemExpand: toolbox.onToolboxItemExpand, - onUnmount: toolbox.onUnmount, - setVisibility: flyout.setVisibility, - sub_category_index: toolbox.sub_category_index, - toggleDrawer: toolbox.toggleDrawer, - toolbox_dom: toolbox.toolbox_dom, - loadDataStrategy: quick_strategy.loadDataStrategy, -}))(Toolbox); +export default Toolbox; diff --git a/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace-wrapper.tsx b/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace-wrapper.tsx index f51b5284f881..6d82d8a826cf 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace-wrapper.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-builder/workspace-wrapper.tsx @@ -1,19 +1,16 @@ import React from 'react'; +import { observer } from '@deriv/stores'; import Flyout from 'Components/flyout'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import StopBotModal from '../dashboard-component/load-bot-preview/stop-bot-modal'; import Toolbar from './toolbar'; import Toolbox from './toolbox'; import './workspace.scss'; -type TWorkspaceWrapper = { - onMount: () => void; - onUnmount: () => void; - is_loading: boolean; -}; +const WorkspaceWrapper = observer(() => { + const { blockly_store } = useDBotStore(); + const { onMount, onUnmount, is_loading } = blockly_store; -const WorkspaceWrapper = ({ onMount, onUnmount, is_loading }: TWorkspaceWrapper) => { React.useEffect(() => { onMount(); return () => { @@ -34,10 +31,6 @@ const WorkspaceWrapper = ({ onMount, onUnmount, is_loading }: TWorkspaceWrapper) ); return null; -}; +}); -export default connect(({ blockly_store }: RootStore) => ({ - onMount: blockly_store.onMount, - onUnmount: blockly_store.onUnmount, - is_loading: blockly_store.is_loading, -}))(WorkspaceWrapper); +export default WorkspaceWrapper; diff --git a/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx b/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx index 3f1573ed922b..681418d8463c 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx @@ -2,16 +2,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Toast } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/root-store'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; -type TBotNotification = { - show_toast: boolean; - toast_message: string; - setOpenSettings: (toast_message: string, show_toast: boolean) => void; -}; +const BotNotification = observer(() => { + const { dashboard } = useDBotStore(); + const { show_toast, toast_message, setOpenSettings } = dashboard; -const BotNotification = ({ show_toast, toast_message, setOpenSettings }: TBotNotification) => { React.useEffect(() => { setTimeout(() => { setOpenSettings(toast_message, false); @@ -34,10 +31,6 @@ const BotNotification = ({ show_toast, toast_message, setOpenSettings }: TBotNot ); } return null; -}; +}); -export default connect(({ dashboard }: RootStore) => ({ - show_toast: dashboard.show_toast, - toast_message: dashboard.toast_message, - setOpenSettings: dashboard.setOpenSettings, -}))(BotNotification); +export default BotNotification; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx index 3a945f237abf..80810f6ef4ca 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/__tests__/dashboard-component.spec.tsx @@ -5,26 +5,6 @@ import userEvent from '@testing-library/user-event'; import Sidebar from '../sidebar'; import UserGuide from '../user-guide'; -const mock_connect_props = { - dialog_options: { - title: 'string', - message: 'string', - ok_button_text: 'string', - cancel_button_text: 'string', - }, - setStrategySaveType: jest.fn(), -}; - -jest.mock('Stores/connect.js', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: - () => - (Component: T) => - props => - Component({ ...props, ...mock_connect_props }), -})); - jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/cards.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/cards.tsx index 35224f8fa3cd..dccc762ae6b8 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/cards.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/cards.tsx @@ -2,29 +2,16 @@ import React from 'react'; import classNames from 'classnames'; import { DesktopWrapper, Dialog, Icon, MobileFullPageModal, MobileWrapper, Text } from '@deriv/components'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { DBOT_TABS } from 'Constants/bot-contents'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; -import SaveModalStore from 'Stores/save-modal-store'; +import { useDBotStore } from 'Stores/useDBotStore'; import GoogleDrive from './load-bot-preview/google-drive'; import Recent from './load-bot-preview/recent'; type TCardProps = { - dialog_options: { [key: string]: string }; has_dashboard_strategies: boolean; - is_dialog_open: boolean; is_mobile: boolean; - save_modal: SaveModalStore; - closeResetDialog: () => void; - handleFileChange: (e: React.ChangeEvent, flag?: boolean) => boolean; - loadFileFromLocal: () => void; - loadDataStrategy: () => void; - setActiveTab: (active_tab: number) => void; - setFileLoaded: (param: boolean) => void; - setPreviewOnPopup: (show: boolean) => void; - setOpenSettings: (toast_message: string, show_toast?: boolean) => void; - showVideoDialog: (param: { [key: string]: string | React.ReactNode }) => void; }; type TCardArray = { @@ -33,21 +20,21 @@ type TCardArray = { method: () => void; }; -const Cards = ({ - closeResetDialog, - dialog_options, - handleFileChange, - has_dashboard_strategies, - is_dialog_open, - is_mobile, - loadFileFromLocal, - setActiveTab, - setFileLoaded, - setPreviewOnPopup, - setOpenSettings, - showVideoDialog, - loadDataStrategy, -}: TCardProps) => { +const Cards = observer(({ is_mobile, has_dashboard_strategies }: TCardProps) => { + const { dashboard, load_modal, quick_strategy } = useDBotStore(); + const { + onCloseDialog, + dialog_options, + is_dialog_open, + setActiveTab, + setFileLoaded, + setPreviewOnPopup, + setOpenSettings, + showVideoDialog, + } = dashboard; + const { handleFileChange, loadFileFromLocal } = load_modal; + const { loadDataStrategy } = quick_strategy; + const [is_file_supported, setIsFileSupported] = React.useState(true); const file_input_ref = React.useRef(null); @@ -141,7 +128,7 @@ const Cards = ({ { setPreviewOnPopup(false); - closeResetDialog(); + onCloseDialog(); }} height_offset='80px' page_overlay @@ -172,20 +159,6 @@ const Cards = ({ ), [is_dialog_open, has_dashboard_strategies] ); -}; +}); -export default connect(({ load_modal, dashboard, quick_strategy }: RootStore) => ({ - closeResetDialog: dashboard.onCloseDialog, - dialog_options: dashboard.dialog_options, - handleFileChange: load_modal.handleFileChange, - is_dialog_open: dashboard.is_dialog_open, - loadFileFromLocal: load_modal.loadFileFromLocal, - onDriveConnect: load_modal.onDriveConnect, - setActiveTab: dashboard.setActiveTab, - setFileLoaded: dashboard.setFileLoaded, - setLoadedLocalFile: load_modal.setLoadedLocalFile, - setPreviewOnPopup: dashboard.setPreviewOnPopup, - setOpenSettings: dashboard.setOpenSettings, - showVideoDialog: dashboard.showVideoDialog, - loadDataStrategy: quick_strategy.loadDataStrategy, -}))(Cards); +export default Cards; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx index 6b11a21f519d..7910e5ea95b8 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/info-panel.tsx @@ -2,27 +2,21 @@ import React from 'react'; import classNames from 'classnames'; import { DesktopWrapper, Icon, MobileWrapper, Modal, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { DBOT_TABS } from 'Constants/bot-contents'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import { SIDEBAR_INTRO } from './constants'; -type TInfoPanel = { - has_started_onboarding_tour: boolean; - is_info_panel_visible: boolean; - setActiveTab: (param: number) => void; - setActiveTabTutorial: (param: number) => void; - setInfoPanelVisibility: (state: boolean) => void; -}; - -const InfoPanel = ({ - has_started_onboarding_tour, - is_info_panel_visible, - setActiveTab, - setActiveTabTutorial, - setInfoPanelVisibility, -}: TInfoPanel) => { +const InfoPanel = observer(() => { const is_mobile = isMobile(); + const { dashboard } = useDBotStore(); + const { + has_started_onboarding_tour, + is_info_panel_visible, + setActiveTab, + setActiveTabTutorial, + setInfoPanelVisibility, + } = dashboard; const switchTab = (link: boolean, label: string) => { const tutorial_link = link ? setActiveTab(DBOT_TABS.TUTORIAL) : null; const tutorial_label = label === 'Guide' ? setActiveTabTutorial(0) : setActiveTabTutorial(1); @@ -96,12 +90,6 @@ const InfoPanel = ({ ); -}; +}); -export default connect(({ dashboard }: RootStore) => ({ - has_started_onboarding_tour: dashboard.has_started_onboarding_tour, - is_info_panel_visible: dashboard.is_info_panel_visible, - setActiveTab: dashboard.setActiveTab, - setActiveTabTutorial: dashboard.setActiveTabTutorial, - setInfoPanelVisibility: dashboard.setInfoPanelVisibility, -}))(InfoPanel); +export default InfoPanel; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.tsx index 0702af559996..78be1c6e154a 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/delete-dialog.tsx @@ -3,26 +3,15 @@ import localForage from 'localforage'; import LZString from 'lz-string'; import { getSavedWorkspaces } from '@deriv/bot-skeleton'; import { Dialog, Text } from '@deriv/components'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; +import { useDBotStore } from 'Stores/useDBotStore'; -type TDeleteDialog = { - is_delete_modal_open: boolean; - onToggleDeleteDialog: (param: boolean) => void; - removeBotStrategy: (param: string) => void; - selected_strategy_id: string; - setDashboardStrategies: (param: string[]) => void; - setStrategies: (param: string[]) => void; - setOpenSettings: (toast_message: string, show_toast: boolean) => void; -}; +const DeleteDialog = observer(() => { + const { load_modal, dashboard } = useDBotStore(); + const { is_delete_modal_open, onToggleDeleteDialog, selected_strategy_id, setDashboardStrategies } = load_modal; + const { setOpenSettings } = dashboard; -const DeleteDialog = ({ - is_delete_modal_open, - onToggleDeleteDialog, - selected_strategy_id, - setDashboardStrategies, - setOpenSettings, -}: TDeleteDialog) => { const removeBotStrategy = async (strategy_id: string) => { const workspaces = await getSavedWorkspaces(); workspaces.map((strategy_from_workspace: string[] | { [key: string]: string }, index: number) => { @@ -77,13 +66,6 @@ const DeleteDialog = ({
); -}; +}); -export default connect(({ toolbar, load_modal, dashboard }) => ({ - is_dialog_open: toolbar.is_dialog_open, - is_delete_modal_open: load_modal.is_delete_modal_open, - onToggleDeleteDialog: load_modal.onToggleDeleteDialog, - selected_strategy_id: load_modal.selected_strategy_id, - setDashboardStrategies: load_modal.setDashboardStrategies, - setOpenSettings: dashboard.setOpenSettings, -}))(DeleteDialog); +export default DeleteDialog; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.scss index 920ce9cf06e4..64eae6defa58 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.scss +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.scss @@ -20,3 +20,6 @@ } } } +.picker-dialog { + z-index: 9999 !important; +} diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.tsx index 1b1fcee97c26..672ae2b017e1 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/google-drive.tsx @@ -2,25 +2,16 @@ import React from 'react'; import classnames from 'classnames'; import { Button, Icon, StaticUrl } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/root-store'; +import { useDBotStore } from 'Stores/useDBotStore'; -type TGoogleDriveProps = { - is_authorised: boolean; - is_open_button_loading: boolean; - onDriveConnect: () => void; - onDriveOpen: () => void; - setOpenSettings: (toast_message: string, show_toast?: boolean) => void; -}; +const GoogleDrive = observer(() => { + const { google_drive, load_modal, dashboard } = useDBotStore(); + const { is_authorised } = google_drive; + const { is_open_button_loading, onDriveConnect, onDriveOpen } = load_modal; + const { setOpenSettings } = dashboard; -const GoogleDrive = ({ - is_authorised, - is_open_button_loading, - onDriveConnect, - onDriveOpen, - setOpenSettings, -}: TGoogleDriveProps) => { return (
@@ -79,12 +70,6 @@ const GoogleDrive = ({
); -}; +}); -export default connect(({ load_modal, google_drive, dashboard }: RootStore) => ({ - is_authorised: google_drive.is_authorised, - is_open_button_loading: load_modal.is_open_button_loading, - onDriveConnect: load_modal.onDriveConnect, - onDriveOpen: load_modal.onDriveOpen, - setOpenSettings: dashboard.setOpenSettings, -}))(GoogleDrive); +export default GoogleDrive; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss index 65713b1a55ca..c98f27da3e14 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/index.scss @@ -1,5 +1,6 @@ @import './google-drive.scss'; @import './delete-dialog.scss'; +@import './save-modal.scss'; .load-strategy { &__wrapper { diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local-footer.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local-footer.tsx index bd73f3be1675..2666dbb66e76 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local-footer.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local-footer.tsx @@ -1,27 +1,16 @@ import React from 'react'; import { Button } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import './index.scss'; -type Nullable = T | null; -type TLocalFooter = { - is_open_button_loading: boolean; - loadFileFromLocal: () => void; - setLoadedLocalFile: (data: Nullable) => void; - setPreviewOnPopup: (param: boolean) => boolean; - toggleLoadModal: () => void; -}; +const LocalFooter = observer(() => { + const { load_modal, dashboard } = useDBotStore(); + const { is_open_button_loading, loadFileFromLocal, setLoadedLocalFile, toggleLoadModal } = load_modal; + const { setPreviewOnPopup } = dashboard; -const LocalFooter = ({ - is_open_button_loading, - loadFileFromLocal, - setLoadedLocalFile, - setPreviewOnPopup, - toggleLoadModal, -}: TLocalFooter) => { const is_mobile = isMobile(); const Wrapper = is_mobile ? Button.Group : React.Fragment; return ( @@ -43,12 +32,6 @@ const LocalFooter = ({ /> ); -}; +}); -export default connect(({ load_modal, dashboard }: RootStore) => ({ - is_open_button_loading: load_modal.is_open_button_loading, - loadFileFromLocal: load_modal.loadFileFromLocal, - setLoadedLocalFile: load_modal.setLoadedLocalFile, - setPreviewOnPopup: dashboard.setPreviewOnPopup, - toggleLoadModal: load_modal.toggleLoadModal, -}))(LocalFooter); +export default LocalFooter; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local.tsx index 85b0ff73bcfd..405461ecd1af 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/local.tsx @@ -2,49 +2,20 @@ import React from 'react'; import classNames from 'classnames'; import { Dialog, Icon, MobileWrapper, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; import { DBOT_TABS } from 'Constants/bot-contents'; import { clearInjectionDiv } from 'Constants/load-modal'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import BotPreview from './bot-preview'; import './index.scss'; -type TWorkspace = { - id: string; - xml: string; - name: string; - timestamp: number; - save_type: string; -}; +const LocalComponent = observer(() => { + const { load_modal, save_modal, dashboard } = useDBotStore(); + const { handleFileChange, loadFileFromRecent, dashboard_strategies } = load_modal; + const { onConfirmSave } = save_modal; + const { setActiveTab, setPreviewOnDialog, has_mobile_preview_loaded, setActiveTabTutorial } = dashboard; -type Nullable = T | null; -type TLocalComponent = { - handleFileChange: (e: React.ChangeEvent, data: boolean) => boolean; - loadFileFromRecent: () => void; - onConfirmSave: () => void; - onDrop: () => void; - previewRecentStrategy: () => void; - setActiveTab: (param: number) => void; - dashboard_strategies: Array; - setFileLoaded: (param: boolean) => void; - setLoadedLocalFile: (data: Nullable) => void; - setTourDialogVisibility: (param: boolean) => boolean; - setPreviewOnDialog: (param: boolean) => boolean; - has_mobile_preview_loaded: boolean; - setActiveTabTutorial: (param: boolean) => void; -}; - -const LocalComponent = ({ - handleFileChange, - loadFileFromRecent, - onConfirmSave, - setActiveTab, - dashboard_strategies, - setPreviewOnDialog, - has_mobile_preview_loaded, - setActiveTabTutorial, -}: TLocalComponent) => { const file_input_ref = React.useRef(null); const [is_file_supported, setIsFileSupported] = React.useState(true); const el_ref = React.useRef(null); @@ -147,20 +118,6 @@ const LocalComponent = ({ )}
); -}; - -const Local = connect(({ load_modal, save_modal, dashboard }: RootStore) => ({ - handleFileChange: load_modal.handleFileChange, - is_open_button_loading: load_modal.is_open_button_loading, - setLoadedLocalFile: load_modal.setLoadedLocalFile, - dashboard_strategies: load_modal.dashboard_strategies, - onConfirmSave: save_modal.onConfirmSave, - setActiveTab: dashboard.setActiveTab, - loadFileFromRecent: load_modal.loadFileFromRecent, - setFileLoaded: dashboard.setFileLoaded, - setPreviewOnDialog: dashboard.setPreviewOnDialog, - setActiveTabTutorial: dashboard.setActiveTabTutorial, - has_mobile_preview_loaded: dashboard.has_mobile_preview_loaded, -}))(LocalComponent); +}); -export default Local; +export default LocalComponent; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx index a70673bf0266..b916a5e66a72 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx @@ -1,16 +1,13 @@ import React from 'react'; import { Button } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; import './index.scss'; +import { observer } from 'mobx-react'; +import { useDBotStore } from 'Stores/useDBotStore'; -type TRecentFooter = { - is_open_button_loading: boolean; - loadFileFromRecent: () => void; -}; - -const RecentFooter = ({ is_open_button_loading, loadFileFromRecent }: TRecentFooter) => { +const RecentFooter = observer(() => { + const { load_modal } = useDBotStore(); + const { is_open_button_loading, loadFileFromRecent } = load_modal; return (
); -}; +}); -export default connect(({ load_modal, dashboard, save_modal }: RootStore) => ({ - active_tab: dashboard.active_tab, - dashboard_strategies: load_modal.dashboard_strategies, - getRecentFileIcon: load_modal.getRecentFileIcon, - getSaveType: load_modal.getSaveType, - getSelectedStrategyID: load_modal.getSelectedStrategyID, - loadFileFromRecent: load_modal.loadFileFromRecent, - onToggleDeleteDialog: load_modal.onToggleDeleteDialog, - previewRecentStrategy: load_modal.previewRecentStrategy, - selected_strategy_id: load_modal.selected_strategy_id, - setActiveTab: dashboard.setActiveTab, - setPreviewOnDialog: dashboard.setPreviewOnDialog, - toggleSaveModal: save_modal.toggleSaveModal, -}))(RecentWorkspace); +export default RecentWorkspace; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent.tsx index 537c1fa65ea1..9e67453d909a 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent.tsx @@ -3,30 +3,21 @@ import classNames from 'classnames'; import { getSavedWorkspaces } from '@deriv/bot-skeleton'; import { MobileWrapper, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; -import { TWorkspace } from 'Stores/load-modal-store'; -import SaveModal from '../../../save-modal'; +import { useDBotStore } from 'Stores/useDBotStore'; import DeleteDialog from './delete-dialog'; import RecentWorkspace from './recent-workspace'; +import SaveModal from './save-modal'; import './index.scss'; -type TRecentComponent = { - dashboard_strategies: Array; - setDashboardStrategies: (strategies: Array) => void; - setStrategySaveType: (param: string) => void; - strategy_save_type: string; -}; - const HEADERS = [localize('Bot name'), localize('Last modified'), localize('Status')]; -const RecentComponent = ({ - dashboard_strategies, - setDashboardStrategies, - setStrategySaveType, - strategy_save_type, -}: TRecentComponent) => { +const RecentComponent = observer(() => { + const { load_modal, dashboard } = useDBotStore(); + const { setDashboardStrategies, dashboard_strategies } = load_modal; + const { setStrategySaveType, strategy_save_type } = dashboard; + React.useEffect(() => { setStrategySaveType(''); const getStrategies = async () => { @@ -75,13 +66,6 @@ const RecentComponent = ({
); -}; - -const Recent = connect(({ load_modal, dashboard }: RootStore) => ({ - dashboard_strategies: load_modal.dashboard_strategies, - setDashboardStrategies: load_modal.setDashboardStrategies, - setStrategySaveType: dashboard.setStrategySaveType, - strategy_save_type: dashboard.strategy_save_type, -}))(RecentComponent); +}); -export default Recent; +export default RecentComponent; diff --git a/packages/bot-web-ui/src/components/save-modal/save-modal.scss b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.scss similarity index 99% rename from packages/bot-web-ui/src/components/save-modal/save-modal.scss rename to packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.scss index 28b050149b7e..5a92cc287b7d 100644 --- a/packages/bot-web-ui/src/components/save-modal/save-modal.scss +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.scss @@ -8,6 +8,7 @@ .modal__content { padding: 16px 24px; } + .modal__footer { text-align: right; border-top: 2px solid var(--general-section-2); @@ -17,11 +18,13 @@ display: inline-block; } } + .modal__content-row { display: flex; align-items: center; justify-content: center; } + & form { height: 445px; display: flex; @@ -55,18 +58,22 @@ div.radio-group { font-weight: 700; } } + &-disabled { .save-type__text { color: var(--general-disabled) !important; } + .dc-radio-group__circle { border-color: var(--general-disabled) !important; } } } + &__circle { display: none; } + &__label { align-self: center; } @@ -79,22 +86,27 @@ div.radio-group { &__container { text-align: center; } + &__input { // TODO: [fix-dc-bundle] Fix import issue with Deriv Component stylesheets (app should take precedence, and not repeat) margin: 2rem 0 0 !important; } + &__radio { text-align: center; } + &__radio-text { letter-spacing: normal; } + &__drive-status { cursor: pointer; position: absolute; margin-top: 5px; width: 71px; } + &__icon { &--disabled { opacity: 0.32; @@ -106,6 +118,7 @@ div.radio-group { position: fixed; top: 40px; z-index: 10; + @include mobile { form { display: flex; @@ -113,21 +126,26 @@ div.radio-group { justify-content: space-between; height: 100%; } + & .dc-input { width: 100% !important; margin: 3rem 0 0 !important; } + & .dc-radio-group__item { width: calc(50vw - 24px) !important; height: 35vw; } + & .save-type__drive-status { position: relative; } + & .modal__content { padding: 3rem 1.6rem; height: calc(100% - #{$MOBILE_WRAPPER_FOOTER_HEIGHT}); } + & .modal__footer { position: fixed; bottom: 0px; @@ -147,6 +165,7 @@ div.radio-group { left: unset; bottom: unset; } + &--button { float: right; margin-left: 0.8rem; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.tsx index 47f800ece5e5..ea89a0d7e85a 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/save-modal.tsx @@ -2,20 +2,11 @@ import React from 'react'; import classNames from 'classnames'; import { Field, Form, Formik } from 'formik'; import { config, save_types } from '@deriv/bot-skeleton'; -import { - Button, - Checkbox, - Icon, - Input, - MobileFullPageModal, - Modal, - RadioGroup, - Text, - ThemedScrollbars, -} from '@deriv/components'; +import { Button, Icon, Input, MobileFullPageModal, Modal, RadioGroup, Text, ThemedScrollbars } from '@deriv/components'; import { isMobile } from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; +import { localize } from '@deriv/translations'; +import { useDBotStore } from '../../../../stores/useDBotStore'; import IconRadio from './icon-radio'; type TSaveModalForm = { @@ -56,12 +47,18 @@ const SaveModalForm = ({ validate={validateBotName} onSubmit={onConfirmSave} > - {({ values: { is_local, save_as_collection }, setFieldValue, touched, errors }) => { + {({ values: { is_local }, setFieldValue, touched, errors }) => { const content_height = !is_mobile ? '500px' : `calc(100%)`; return (
+ + {localize( + 'Enter your bot name, choose to save on your computer or Google Drive, and hit ' + )} + {localize('Save.')} +
{({ field }) => ( @@ -70,7 +67,7 @@ const SaveModalForm = ({ type='text' placeholder={localize('Untitled Strategy')} error={touched[field.name] && errors[field.name]} - label={localize('Strategy name')} + label={localize('Bot name')} onFocus={e => setCurrentFocus(e.currentTarget.name)} onBlur={() => setCurrentFocus(null)} {...field} @@ -116,7 +113,8 @@ const SaveModalForm = ({ />
- <> + {/* removed this from the save modal popup because it is not there in the design */} + {/* <> {({ field }) => ( - + */}
); -const SaveModal = ({ - bot_name, - button_status, - is_authorised, - is_save_modal_open, - onConfirmSave, - onDriveConnect, - toggleSaveModal, - validateBotName, - setCurrentFocus, - is_onscreen_keyboard_active, -}: TSaveModalForm) => { +const SaveModal = observer(() => { + const { save_modal, google_drive } = useDBotStore(); + const { ui } = useStore(); + + const { + button_status, + is_save_modal_open, + onConfirmSave, + onDriveConnect, + toggleSaveModal, + validateBotName, + bot_name, + } = save_modal; + const { is_authorised } = google_drive; + const { is_onscreen_keyboard_active, setCurrentFocus } = ui; + const is_mobile = isMobile(); return is_mobile ? ( ); -}; +}); -export default connect(({ save_modal, google_drive, ui }) => ({ - button_status: save_modal.button_status, - is_authorised: google_drive.is_authorised, - is_save_modal_open: save_modal.is_save_modal_open, - is_onscreen_keyboard_active: ui.is_onscreen_keyboard_active, - onConfirmSave: save_modal.onConfirmSave, - onDriveConnect: save_modal.onDriveConnect, - toggleSaveModal: save_modal.toggleSaveModal, - validateBotName: save_modal.validateBotName, - bot_name: save_modal.bot_name, - setCurrentFocus: ui.setCurrentFocus, -}))(SaveModal); +export default SaveModal; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/stop-bot-modal.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/stop-bot-modal.tsx index e0a6fbafbf1b..2aefb83c5a34 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/stop-bot-modal.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/stop-bot-modal.tsx @@ -1,43 +1,27 @@ import React from 'react'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/root-store'; -import StopBotModalContent, { TStopBotModalContent } from '../stop-bot-modal-content'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from '../../../../stores/useDBotStore'; +import StopBotModalContent from '../stop-bot-modal-content'; -type TStopBotModal = TStopBotModalContent & { - stopMyBot: () => void; -}; +const StopBotModal = observer(() => { + const { run_panel, quick_strategy, summary_card } = useDBotStore(); -const StopBotModal = ({ - is_running, - is_contract_dialog_open, - is_stop_bot_dialog_open, - is_multiplier, - is_dialog_open, - closeMultiplierContract, - stopMyBot, - toggleStopBotDialog, -}: TStopBotModal) => ( - -); + const { is_contract_dialog_open, is_stop_bot_dialog_open, toggleStopBotDialog } = quick_strategy; + const { is_running, closeMultiplierContract, stopMyBot, is_dialog_open } = run_panel; + const { is_multiplier } = summary_card; -export default connect(({ run_panel, quick_strategy, summary_card }: RootStore) => ({ - is_dialog_open: quick_strategy.is_dialog_open, - is_running: run_panel.is_running, - is_multiplier: summary_card.is_multiplier, - is_contract_dialog_open: quick_strategy.is_contract_dialog_open, - is_stop_bot_dialog_open: quick_strategy.is_stop_bot_dialog_open, - closeMultiplierContract: run_panel.closeMultiplierContract, - onOkButtonClick: run_panel.onOkButtonClick, - stopMyBot: run_panel.stopMyBot, - toggleStopBotDialog: quick_strategy.toggleStopBotDialog, - is_strategy_modal_open: quick_strategy.is_strategy_modal_open, -}))(StopBotModal); + return ( + + ); +}); + +export default StopBotModal; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/workspace-control.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/workspace-control.tsx index f657c674529a..df38f5715cda 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/workspace-control.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/workspace-control.tsx @@ -1,29 +1,27 @@ import React from 'react'; import { Icon } from '@deriv/components'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; -type TWorkspaceControl = { - onZoomInOutClick: (param: boolean) => void; -}; +const WorkspaceControl = observer(() => { + const { dashboard } = useDBotStore(); + const { onZoomInOutClick } = dashboard; + return ( +
+ onZoomInOutClick(true)} + /> + onZoomInOutClick(false)} + /> +
+ ); +}); -const WorkspaceControl = ({ onZoomInOutClick }: TWorkspaceControl) => ( -
- onZoomInOutClick(true)} - /> - onZoomInOutClick(false)} - /> -
-); - -export default connect(({ dashboard }: RootStore) => ({ - onZoomInOutClick: dashboard.onZoomInOutClick, -}))(WorkspaceControl); +export default WorkspaceControl; diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx index 4173b0a8efc3..9386525b9230 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard.tsx @@ -3,12 +3,11 @@ import classNames from 'classnames'; import { initTrashCan } from '@deriv/bot-skeleton/src/scratch/hooks/trashcan'; import { DesktopWrapper, Dialog, MobileWrapper, Tabs } from '@deriv/components'; import { isMobile } from '@deriv/shared'; -import { useStore } from '@deriv/stores'; +import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; import Chart from 'Components/chart'; import { DBOT_TABS, TAB_IDS } from 'Constants/bot-contents'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import RunPanel from '../run-panel'; import RunStrategy from './dashboard-component/run-strategy'; import BotNotification from './bot-notification'; @@ -26,62 +25,29 @@ import TourSlider from './tour-slider'; import TourTriggrerDialog from './tour-trigger-dialog'; import Tutorial from './tutorial-tab'; -type TDialogOptions = { - title: string; - message: string; - cancel_button_text?: string; - ok_button_text?: string; -}; +const Dashboard = observer(() => { + const { dashboard, load_modal, run_panel, quick_strategy } = useDBotStore(); + const { + active_tab, + has_file_loaded, + has_tour_started, + setTourActive, + has_started_onboarding_tour, + setOnBoardTourRunState, + setBotBuilderTourState, + setTourDialogVisibility, + setHasTourEnded, + has_started_bot_builder_tour, + is_tour_dialog_visible, + setActiveTab, + setBotBuilderTokenCheck, + setOnBoardingTokenCheck, + } = dashboard; + const { onEntered } = load_modal; + const { is_dialog_open, is_drawer_open, dialog_options, onCancelButtonClick, onCloseDialog, onOkButtonClick } = + run_panel; + const { is_strategy_modal_open } = quick_strategy; -type TDashboard = { - active_tab: number; - dialog_options: TDialogOptions; - has_started_bot_builder_tour: boolean; - has_file_loaded: boolean; - has_started_onboarding_tour: boolean; - has_tour_started: boolean; - is_dialog_open: boolean; - is_drawer_open: boolean; - is_tour_dialog_visible: boolean; - is_strategy_modal_open: boolean; - onCancelButtonClick: () => void; - onCloseDialog: () => void; - onEntered: () => void; - onOkButtonClick: () => void; - setActiveTab: (active_tab: number) => void; - setBotBuilderTourState: (param: boolean) => void; - setOnBoardTourRunState: (param: boolean) => void; - setBotBuilderTokenCheck: (param: string | number) => void; - setOnBoardingTokenCheck: (param: string | number) => void; - setTourActive: (param: boolean) => void; - setTourDialogVisibility: (param: boolean) => void; - setHasTourEnded: (param: boolean) => void; -}; - -const Dashboard = ({ - active_tab, - is_drawer_open, - dialog_options, - has_file_loaded, - has_tour_started, - has_started_onboarding_tour, - has_started_bot_builder_tour, - is_dialog_open, - is_tour_dialog_visible, - is_strategy_modal_open, - onCancelButtonClick, - onCloseDialog, - onEntered, - onOkButtonClick, - setActiveTab, - setBotBuilderTokenCheck, - setBotBuilderTourState, - setOnBoardingTokenCheck, - setOnBoardTourRunState, - setTourActive, - setTourDialogVisibility, - setHasTourEnded, -}: TDashboard) => { const { DASHBOARD, BOT_BUILDER, CHART, TUTORIAL } = DBOT_TABS; const is_tour_complete = React.useRef(true); let bot_tour_token: string | number = ''; @@ -299,30 +265,6 @@ const Dashboard = ({ ); -}; +}); -export default connect(({ dashboard, run_panel, load_modal, quick_strategy }: RootStore) => ({ - active_tab: dashboard.active_tab, - has_file_loaded: dashboard.has_file_loaded, - has_tour_started: dashboard.has_tour_started, - setTourActive: dashboard.setTourActive, - has_started_onboarding_tour: dashboard.has_started_onboarding_tour, - setOnBoardTourRunState: dashboard.setOnBoardTourRunState, - setBotBuilderTourState: dashboard.setBotBuilderTourState, - onEntered: load_modal.onEntered, - setTourDialogVisibility: dashboard.setTourDialogVisibility, - setHasTourEnded: dashboard.setHasTourEnded, - is_dialog_open: run_panel.is_dialog_open, - is_drawer_open: run_panel.is_drawer_open, - has_started_bot_builder_tour: dashboard.has_started_bot_builder_tour, - is_tour_dialog_visible: dashboard.is_tour_dialog_visible, - dialog_options: run_panel.dialog_options, - onCancelButtonClick: run_panel.onCancelButtonClick, - onCloseDialog: run_panel.onCloseDialog, - onOkButtonClick: run_panel.onOkButtonClick, - setActiveTab: dashboard.setActiveTab, - setBotBuilderTokenCheck: dashboard.setBotBuilderTokenCheck, - setOnBoardingTokenCheck: dashboard.setOnBoardingTokenCheck, - has_tour_ended: dashboard.has_tour_ended, - is_strategy_modal_open: quick_strategy.is_strategy_modal_open, -}))(Dashboard); +export default Dashboard; diff --git a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.tsx b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.tsx index 9401f3905e71..b40dc76bef99 100755 --- a/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.tsx +++ b/packages/bot-web-ui/src/components/dashboard/quick-strategy/quick-strategy.tsx @@ -1,15 +1,68 @@ import React from 'react'; import { MobileFullPageModal, Modal } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; -import { TQuickStrategyProps } from './quick-strategy.types'; +import { useDBotStore } from 'Stores/useDBotStore'; import { QuickStrategyContainer } from './quick-strategy-components'; -const QuickStrategy = (props: TQuickStrategyProps) => { +const QuickStrategy = observer(() => { const is_mobile = isMobile(); - const { is_strategy_modal_open, loadDataStrategy } = props; + const { quick_strategy, run_panel } = useDBotStore(); + const { ui } = useStore(); + const { + is_strategy_modal_open, + loadDataStrategy, + active_index, + description, + createStrategy, + duration_unit_dropdown, + types_strategies_dropdown, + getSizeDesc, + initial_values, + onChangeDropdownItem, + onChangeInputValue, + onHideDropdownList, + onScrollStopDropdownList, + selected_symbol, + selected_trade_type, + selected_duration_unit, + selected_type_strategy, + symbol_dropdown, + trade_type_dropdown, + toggleStopBotDialog, + is_contract_dialog_open, + is_stop_bot_dialog_open, + } = quick_strategy; + const { is_stop_button_visible, is_running } = run_panel; + const { is_onscreen_keyboard_active, setCurrentFocus } = ui; + + const quick_strategy_props = { + symbol_dropdown, + trade_type_dropdown, + active_index, + description, + duration_unit_dropdown, + types_strategies_dropdown, + initial_values, + is_onscreen_keyboard_active, + is_stop_button_visible, + selected_symbol, + selected_trade_type, + selected_duration_unit, + selected_type_strategy, + is_running, + is_contract_dialog_open, + is_stop_bot_dialog_open, + createStrategy, + getSizeDesc, + onChangeDropdownItem, + onChangeInputValue, + onHideDropdownList, + onScrollStopDropdownList, + setCurrentFocus, + toggleStopBotDialog, + }; return ( <> @@ -21,7 +74,7 @@ const QuickStrategy = (props: TQuickStrategyProps) => { onClickClose={loadDataStrategy} height_offset='8rem' > - +
) : ( { width={'78rem'} >
- +
)} ); -}; +}); -export default connect(({ run_panel, quick_strategy, ui }: RootStore) => ({ - active_index: quick_strategy.active_index, - description: quick_strategy.description, - createStrategy: quick_strategy.createStrategy, - duration_unit_dropdown: quick_strategy.duration_unit_dropdown, - types_strategies_dropdown: quick_strategy.types_strategies_dropdown, - getSizeDesc: quick_strategy.getSizeDesc, - initial_values: quick_strategy.initial_values, - is_onscreen_keyboard_active: ui.is_onscreen_keyboard_active, - is_stop_button_visible: run_panel.is_stop_button_visible, - onChangeDropdownItem: quick_strategy.onChangeDropdownItem, - onChangeInputValue: quick_strategy.onChangeInputValue, - onHideDropdownList: quick_strategy.onHideDropdownList, - onScrollStopDropdownList: quick_strategy.onScrollStopDropdownList, - selected_symbol: quick_strategy.selected_symbol, - selected_trade_type: quick_strategy.selected_trade_type, - selected_duration_unit: quick_strategy.selected_duration_unit, - selected_type_strategy: quick_strategy.selected_type_strategy, - symbol_dropdown: quick_strategy.symbol_dropdown, - loadDataStrategy: quick_strategy.loadDataStrategy, - trade_type_dropdown: quick_strategy.trade_type_dropdown, - is_strategy_modal_open: quick_strategy.is_strategy_modal_open, - setCurrentFocus: ui.setCurrentFocus, - toggleStopBotDialog: quick_strategy.toggleStopBotDialog, - is_running: run_panel.is_running, - is_contract_dialog_open: quick_strategy.is_contract_dialog_open, - is_stop_bot_dialog_open: quick_strategy.is_stop_bot_dialog_open, -}))(QuickStrategy); +export default QuickStrategy; diff --git a/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx b/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx index 57519e50dd96..1dadba2b5d58 100644 --- a/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tour-guide.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { Loading, Text } from '@deriv/components'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; type TTourGuide = { content: string[]; @@ -12,7 +12,10 @@ type TTourGuide = { step_index: number; }; -const TourGuide = ({ content, img, label, onCloseTour, step_index }: TTourGuide) => { +const TourGuide = observer(({ content, img, label, step_index }: TTourGuide) => { + const { dashboard } = useDBotStore(); + const { onCloseTour } = dashboard; + const [has_image_loaded, setImageLoaded] = React.useState(false); React.useEffect(() => { @@ -62,9 +65,6 @@ const TourGuide = ({ content, img, label, onCloseTour, step_index }: TTourGuide)
); -}; -export default connect(({ dashboard }: RootStore) => ({ - setOnBoardTourRunState: dashboard.setOnBoardTourRunState, - setTourActive: dashboard.setTourActive, - onCloseTour: dashboard.onCloseTour, -}))(TourGuide); +}); + +export default TourGuide; diff --git a/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx b/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx index 93d11e358546..d277a0a7f6aa 100644 --- a/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tour-slider.tsx @@ -1,9 +1,9 @@ import React from 'react'; import classNames from 'classnames'; import { Icon, ProgressBarOnboarding, Text } from '@deriv/components'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import { BOT_BUILDER_MOBILE, DBOT_ONBOARDING_MOBILE, TStepMobile } from './joyride-config'; type TTourButton = { @@ -12,15 +12,6 @@ type TTourButton = { label: string; }; -type TTourSlider = { - has_started_bot_builder_tour: boolean; - has_started_onboarding_tour: boolean; - onCloseTour: () => void; - onTourEnd: (step: number, has_started_onboarding_tour: boolean) => void; - setTourActiveStep: (param: number) => void; - toggleTourLoadModal: (toggle: boolean) => void; -}; - type TAccordion = { content_data: TStepMobile; expanded: boolean; @@ -67,14 +58,11 @@ const Accordion = ({ content_data, expanded = false, ...props }: TAccordion) => ); }; -const TourSlider = ({ - onCloseTour, - onTourEnd, - has_started_onboarding_tour, - has_started_bot_builder_tour, - setTourActiveStep, - toggleTourLoadModal, -}: TTourSlider) => { +const TourSlider = observer(() => { + const { dashboard, load_modal } = useDBotStore(); + const { has_started_bot_builder_tour, has_started_onboarding_tour, onCloseTour, onTourEnd, setTourActiveStep } = + dashboard; + const { toggleTourLoadModal } = load_modal; const [step, setStep] = React.useState(1); const [slider_content, setContent] = React.useState(''); const [slider_header, setheader] = React.useState(''); @@ -226,15 +214,6 @@ const TourSlider = ({ ); -}; +}); -export default connect(({ dashboard, load_modal }: RootStore) => ({ - active_tab: dashboard.active_tab, - has_started_bot_builder_tour: dashboard.has_started_bot_builder_tour, - has_started_onboarding_tour: dashboard.has_started_onboarding_tour, - onCloseTour: dashboard.onCloseTour, - onTourEnd: dashboard.onTourEnd, - setActiveTab: dashboard.setActiveTab, - setTourActiveStep: dashboard.setTourActiveStep, - toggleTourLoadModal: load_modal.toggleTourLoadModal, -}))(TourSlider); +export default TourSlider; diff --git a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx index 7f0516d763ee..d9bbd0d994ec 100644 --- a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.spec.tsx @@ -1,31 +1,180 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { mocked_props } from './dashboard-component/__tests__/dashboard-component.spec'; +import { mockStore, StoreProvider } from '@deriv/stores'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { act, fireEvent, render, screen } from '@testing-library/react'; +import RootStore from '../../stores/root-store'; +import { DBotStoreProvider, mockDBotStore } from '../../stores/useDBotStore'; +import { setTourType } from './joyride-config'; import TourTriggrerDialog from './tour-trigger-dialog'; -const mock_connect_props = { - dialog_options: { - title: 'string', - message: 'string', - ok_button_text: 'string', - cancel_button_text: 'string', - }, - setStrategySaveType: jest.fn(), -}; +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + isMobile: jest.fn(() => false), +})); -jest.mock('Stores/connect.js', () => ({ - __esModule: true, - default: 'mockedDefaultExport', - connect: - () => - (Component: T) => - props => - Component({ ...props, ...mock_connect_props }), +jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn()); +jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({ + saveRecentWorkspace: jest.fn(), + unHighlightAllBlocks: jest.fn(), +})); +jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => ({ + blocksCoordinate: jest.fn(), })); +const mock_ws = { + authorized: { + subscribeProposalOpenContract: jest.fn(), + send: jest.fn(), + }, + storage: { + send: jest.fn(), + }, + contractUpdate: jest.fn(), + subscribeTicksHistory: jest.fn(), + forgetStream: jest.fn(), + activeSymbols: jest.fn(), + send: jest.fn(), +}; + describe('', () => { - it('renders tour trigger dialog', () => { - render(); + let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined; + + beforeAll(() => { + const mock_store = mockStore({}); + mock_DBot_store = mockDBotStore(mock_store, mock_ws); + + wrapper = ({ children }: { children: JSX.Element }) => ( + + + {children} + + + ); + }); + + it('renders tour trigger component', () => { + const { container } = render(, { + wrapper, + }); + expect(container).toBeInTheDocument(); + }); + + it('should open tour trigger dialog', () => { + act(() => { + mock_DBot_store?.dashboard?.setTourDialogVisibility(true); + }); + render(, { + wrapper, + }); expect(screen.getByText(/Get started on Deriv Bot/i)).toBeInTheDocument(); }); + + it('should show tour end message', () => { + act(() => { + mock_DBot_store?.dashboard?.setTourDialogVisibility(true); + mock_DBot_store?.dashboard?.setHasTourEnded(true); + }); + render(, { + wrapper, + }); + expect(screen.getByText(/Want to retake the tour?/i)).toBeInTheDocument(); + }); + + it('should start onboarding tour', () => { + act(() => { + mock_DBot_store?.dashboard?.setActiveTab(1); + mock_DBot_store?.dashboard?.setTourDialogVisibility(true); + mock_DBot_store?.dashboard?.setHasTourEnded(false); + }); + render(, { + wrapper, + }); + + act(() => { + const buttonElement = screen.getByText('Start', { selector: 'span' }); + fireEvent.click(buttonElement); + }); + + expect(screen.getByText('OK')).toBeInTheDocument(); + }); + + it('should show tour success message', () => { + act(() => { + mock_DBot_store?.dashboard?.setTourDialogVisibility(true); + mock_DBot_store?.dashboard?.setActiveTab(1); + mock_DBot_store?.dashboard?.setHasTourEnded(true); + }); + render(, { + wrapper, + }); + expect(screen.getByTestId('tour-success-message')).toBeInTheDocument(); + }); + + it('should cancel tour', () => { + act(() => { + mock_DBot_store?.dashboard?.setTourDialogVisibility(true); + mock_DBot_store?.dashboard?.setActiveTab(1); + mock_DBot_store?.dashboard?.setHasTourEnded(true); + }); + render(, { + wrapper, + }); + + act(() => { + const buttonElement = screen.getByText('Skip', { selector: 'span' }); + fireEvent.click(buttonElement); + }); + + expect(mock_DBot_store?.dashboard?.is_tour_dialog_visible).toBeFalsy(); + }); + + it('should render bot builder tour', () => { + act(() => { + mock_DBot_store?.dashboard?.setActiveTab(1); + mock_DBot_store?.dashboard?.setTourDialogVisibility(true); + mock_DBot_store?.dashboard?.setHasTourEnded(false); + }); + render(, { + wrapper, + }); + expect(screen.getByText("Let's build a Bot!")).toBeInTheDocument(); + }); + + it('should start bot builder tour', () => { + act(() => { + setTourType('bot_builder'); + mock_DBot_store?.dashboard?.setActiveTab(2); + mock_DBot_store?.dashboard?.setTourDialogVisibility(true); + mock_DBot_store?.dashboard?.setHasTourEnded(false); + }); + render(, { + wrapper, + }); + + act(() => { + const buttonElement = screen.getByText('Start', { selector: 'span' }); + fireEvent.click(buttonElement); + }); + + expect(screen.getByText('OK')).toBeInTheDocument(); + }); + + it('should exit bot builder tour', () => { + act(() => { + setTourType('bot_builder'); + mock_DBot_store?.dashboard?.setActiveTab(2); + mock_DBot_store?.dashboard?.setTourDialogVisibility(true); + mock_DBot_store?.dashboard?.setHasTourEnded(true); + }); + render(, { + wrapper, + }); + + act(() => { + const buttonElement = screen.getByText('Skip', { selector: 'span' }); + fireEvent.click(buttonElement); + }); + + expect(mock_DBot_store?.dashboard?.is_tour_dialog_visible).toBeFalsy(); + }); }); diff --git a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx index 0041c0f02ddf..a8746e9be11c 100644 --- a/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tour-trigger-dialog.tsx @@ -2,26 +2,15 @@ import React from 'react'; import classNames from 'classnames'; import { Dialog, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from '../../stores/useDBotStore'; import { setTourSettings, tour_status_ended, tour_type } from './joyride-config'; -type TTourTriggrerDialog = { - active_tab: number; - has_tour_ended: boolean; - is_tour_dialog_visible: boolean; - setTourDialogVisibility: (param: boolean) => void; - toggleOnConfirm: (active_tab: number, value: boolean) => void; -}; +const TourTriggrerDialog = observer(() => { + const { dashboard } = useDBotStore(); + const { active_tab, has_tour_ended, is_tour_dialog_visible, setTourDialogVisibility, toggleOnConfirm } = dashboard; -const TourTriggrerDialog = ({ - active_tab, - has_tour_ended, - is_tour_dialog_visible, - setTourDialogVisibility, - toggleOnConfirm, -}: TTourTriggrerDialog) => { const is_mobile = isMobile(); const toggleTour = (value: boolean, type: string) => { @@ -110,7 +99,7 @@ const TourTriggrerDialog = ({ ) : ( <> -
+
); -}; +}); -export default connect(({ dashboard }: RootStore) => ({ - active_tab: dashboard.active_tab, - has_tour_ended: dashboard.has_tour_ended, - is_tour_dialog_visible: dashboard.is_tour_dialog_visible, - setTourDialogVisibility: dashboard.setTourDialogVisibility, - toggleOnConfirm: dashboard.toggleOnConfirm, -}))(TourTriggrerDialog); +export default TourTriggrerDialog; diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx index 79c65d973507..d830656c95aa 100644 --- a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx @@ -1,14 +1,13 @@ import React from 'react'; -import { Accordion, Text } from '@deriv/components'; +import { Text, Accordion } from '@deriv/components'; import { isMobile } from '@deriv/shared'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; import { TDescription } from './tutorial-content'; type TFAQContent = { faq_list: TFAQList[]; - faq_search_value: string; hide_header?: boolean; }; @@ -34,7 +33,11 @@ const FAQ = ({ type, content, src }: TDescription) => { ); }; -const FAQContent = ({ faq_list, faq_search_value, hide_header = false }: TFAQContent) => { +const FAQContent = observer(({ faq_list, hide_header = false }: TFAQContent) => { + const { dashboard } = useDBotStore(); + + const { faq_search_value } = dashboard; + const getList = () => { return faq_list.map(({ title, description }: TFAQList) => ({ header: ( @@ -75,8 +78,6 @@ const FAQContent = ({ faq_list, faq_search_value, hide_header = false }: TFAQCon
); -}; +}); -export default connect(({ dashboard }: RootStore) => ({ - faq_search_value: dashboard.faq_search_value, -}))(FAQContent); +export default FAQContent; diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx index 7cc5418151a0..7f5486abc4d0 100644 --- a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/guide-content.tsx @@ -2,40 +2,32 @@ import React from 'react'; import classNames from 'classnames'; import { Dialog, Icon, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { DBOT_TABS } from 'Constants/bot-contents'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import { removeKeyValue } from '../../../utils/settings'; import { tour_type } from '../joyride-config'; type TGuideContent = { - dialog_options: { [key: string]: string }; - faq_search_value: string; guide_list: []; - is_dialog_open: boolean; - onOkButtonClick: () => void; - setActiveTab: (tab_title: number) => void; - setHasTourEnded: (param: boolean) => boolean; - setOnBoardTourRunState: (param: boolean) => boolean; - setTourActive: (param: boolean) => boolean; - setTourDialogVisibility: (param: boolean) => boolean; - showVideoDialog: (param: { [key: string]: string }) => void; }; -const GuideContent = ({ - dialog_options, - faq_search_value, - guide_list, - is_dialog_open, - onOkButtonClick, - setActiveTab, - setHasTourEnded, - setOnBoardTourRunState, - setTourActive, - setTourDialogVisibility, - showVideoDialog, -}: TGuideContent) => { +const GuideContent = observer(({ guide_list }: TGuideContent) => { + const { dashboard } = useDBotStore(); + const { + dialog_options, + faq_search_value, + is_dialog_open, + onCloseDialog: onOkButtonClick, + setActiveTab, + setHasTourEnded, + setOnBoardTourRunState, + setTourActive, + setTourDialogVisibility, + showVideoDialog, + } = dashboard; + const triggerTour = (type: string) => { const storage = JSON.parse(localStorage?.dbot_settings); if (type === 'OnBoard') { @@ -180,18 +172,6 @@ const GuideContent = ({ ), [guide_list, is_dialog_open] ); -}; +}); -export default connect(({ dashboard, load_modal }: RootStore) => ({ - dialog_options: dashboard.dialog_options, - faq_search_value: dashboard.faq_search_value, - is_dialog_open: dashboard.is_dialog_open, - onOkButtonClick: dashboard.onCloseDialog, - setActiveTab: dashboard.setActiveTab, - setHasTourEnded: dashboard.setHasTourEnded, - setOnBoardTourRunState: dashboard.setOnBoardTourRunState, - setTourActive: dashboard.setTourActive, - setTourDialogVisibility: dashboard.setTourDialogVisibility, - showVideoDialog: dashboard.showVideoDialog, - toggleLoadModal: load_modal.toggleLoadModal, -}))(GuideContent); +export default GuideContent; diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.tsx index 33b21097cc37..8e293ef1c166 100644 --- a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/sidebar.tsx @@ -3,28 +3,16 @@ import classNames from 'classnames'; import debounce from 'lodash.debounce'; import { DesktopWrapper, Icon, MobileWrapper, SelectNative, Tabs } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { useDBotStore } from 'Stores/useDBotStore'; import FAQContent from './faq-content'; import GuideContent from './guide-content'; import { faq_content, guide_content, user_guide_content } from './tutorial-content'; -type TSidebarProps = { - active_tab_tutorials: number; - active_tab: number; - faq_search_value: string; - setActiveTabTutorial: (active_tab_tutorials: number) => void; - setFAQSearchValue: (setFAQSearchValue: string) => void; -}; - -const Sidebar = ({ - active_tab_tutorials, - active_tab, - faq_search_value, - setActiveTabTutorial, - setFAQSearchValue, -}: TSidebarProps) => { +const Sidebar = observer(() => { + const { dashboard } = useDBotStore(); + const { active_tab_tutorials, active_tab, faq_search_value, setActiveTabTutorial, setFAQSearchValue } = dashboard; const guide_tab_content = [...user_guide_content, ...guide_content]; const [search_filtered_list, setsearchFilteredList] = React.useState(guide_tab_content); const [search_faq_list, setsearchFAQList] = React.useState(faq_content); @@ -123,12 +111,6 @@ const Sidebar = ({ ); -}; +}); -export default connect(({ dashboard }: RootStore) => ({ - active_tab_tutorials: dashboard.active_tab_tutorials, - active_tab: dashboard.active_tab, - faq_search_value: dashboard.faq_search_value, - setActiveTabTutorial: dashboard.setActiveTabTutorial, - setFAQSearchValue: dashboard.setFAQSearchValue, -}))(Sidebar); +export default Sidebar; diff --git a/packages/bot-web-ui/src/components/download/download.tsx b/packages/bot-web-ui/src/components/download/download.tsx index 40424fca8338..5290186132be 100644 --- a/packages/bot-web-ui/src/components/download/download.tsx +++ b/packages/bot-web-ui/src/components/download/download.tsx @@ -1,22 +1,18 @@ import React from 'react'; import { Button, Icon, Popover } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; type TDownloadProps = { - onClickDownloadTransaction: () => void; - onClickDownloadJournal: () => void; tab: string; - is_clear_stat_disabled: boolean; }; -const Download = ({ - tab, - onClickDownloadTransaction, - onClickDownloadJournal, - is_clear_stat_disabled, -}: TDownloadProps) => { +const Download = observer(({ tab }: TDownloadProps) => { + const { download, run_panel } = useDBotStore(); + const { is_clear_stat_disabled } = run_panel; + const { onClickDownloadTransaction, onClickDownloadJournal } = download; + let clickFunction, popover_message; if (tab === 'transactions') { clickFunction = onClickDownloadTransaction; @@ -44,10 +40,6 @@ const Download = ({ /> ); -}; +}); -export default connect(({ download, run_panel }: RootStore) => ({ - onClickDownloadTransaction: download.onClickDownloadTransaction, - onClickDownloadJournal: download.onClickDownloadJournal, - is_clear_stat_disabled: run_panel.is_clear_stat_disabled, -}))(Download); +export default Download; diff --git a/packages/bot-web-ui/src/components/flyout/flyout-block.jsx b/packages/bot-web-ui/src/components/flyout/flyout-block.jsx index 3e33e5c543da..a94c7dbbd2d7 100644 --- a/packages/bot-web-ui/src/components/flyout/flyout-block.jsx +++ b/packages/bot-web-ui/src/components/flyout/flyout-block.jsx @@ -1,9 +1,12 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import { connect } from 'Stores/connect'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; + +const FlyoutBlock = observer(({ block_node, should_center_block, should_hide_display_name }) => { + const { flyout } = useDBotStore(); + const { initBlockWorkspace } = flyout; -const FlyoutBlock = ({ initBlockWorkspace, block_node, should_center_block, should_hide_display_name }) => { let el_block_workspace = React.useRef(); React.useEffect(() => { @@ -20,15 +23,6 @@ const FlyoutBlock = ({ initBlockWorkspace, block_node, should_center_block, shou })} /> ); -}; - -FlyoutBlock.propTypes = { - block_node: PropTypes.any, - initBlockWorkspace: PropTypes.func, - should_center_block: PropTypes.bool, - should_hide_display_name: PropTypes.bool, -}; +}); -export default connect(({ flyout }) => ({ - initBlockWorkspace: flyout.initBlockWorkspace, -}))(FlyoutBlock); +export default FlyoutBlock; diff --git a/packages/bot-web-ui/src/components/flyout/flyout.jsx b/packages/bot-web-ui/src/components/flyout/flyout.jsx index b30fa383f9f1..4a419eb83484 100644 --- a/packages/bot-web-ui/src/components/flyout/flyout.jsx +++ b/packages/bot-web-ui/src/components/flyout/flyout.jsx @@ -1,11 +1,11 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { Icon, Input, Text, ThemedScrollbars } from '@deriv/components'; import { getPlatformSettings } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { help_content_config } from 'Utils/help-content/help-content.config'; -import { connect } from 'Stores/connect'; +import { useDBotStore } from 'Stores/useDBotStore'; import FlyoutBlockGroup from './flyout-block-group.jsx'; import HelpBase from './help-contents'; @@ -159,7 +159,10 @@ const FlyoutContent = props => { ); }; -const Flyout = props => { +const Flyout = observer(() => { + const { flyout, flyout_help } = useDBotStore(); + const { gtm } = useStore(); + const { active_helper, initFlyoutHelp, setHelpContent } = flyout_help; const { flyout_content, flyout_width, @@ -168,9 +171,11 @@ const Flyout = props => { is_visible, onMount, onUnmount, - pushDataLayer, search_term, - } = props; + selected_category, + first_get_variable_block_index, + } = flyout; + const { pushDataLayer } = gtm; React.useEffect(() => { onMount(); @@ -198,43 +203,23 @@ const Flyout = props => { {is_search_flyout && !is_help_content && ( )} - {is_help_content ? : } + {is_help_content ? ( + + ) : ( + + )} ) ); -}; - -Flyout.propTypes = { - active_helper: PropTypes.string, - flyout_content: PropTypes.any, - flyout_width: PropTypes.number, - initFlyoutHelp: PropTypes.func, - is_help_content: PropTypes.bool, - is_search_flyout: PropTypes.bool, - is_visible: PropTypes.bool, - onMount: PropTypes.func, - onUnmount: PropTypes.func, - setActiveHelper: PropTypes.func, - search_term: PropTypes.string, - setHelpContent: PropTypes.func, - selected_category: PropTypes.object, - first_get_variable_block_index: PropTypes.number, -}; +}); -export default connect(({ flyout, flyout_help, gtm }) => ({ - active_helper: flyout_help.active_helper, - pushDataLayer: gtm.pushDataLayer, - flyout_content: flyout.flyout_content, - flyout_width: flyout.flyout_width, - initFlyoutHelp: flyout_help.initFlyoutHelp, - is_help_content: flyout.is_help_content, - is_search_flyout: flyout.is_search_flyout, - is_visible: flyout.is_visible, - onMount: flyout.onMount, - onUnmount: flyout.onUnmount, - setActiveHelper: flyout_help.setActiveHelper, - search_term: flyout.search_term, - setHelpContent: flyout_help.setHelpContent, - selected_category: flyout.selected_category, - first_get_variable_block_index: flyout.first_get_variable_block_index, -}))(Flyout); +export default Flyout; diff --git a/packages/bot-web-ui/src/components/flyout/help-contents/flyout-help-base.jsx b/packages/bot-web-ui/src/components/flyout/help-contents/flyout-help-base.jsx index 1d967b8db994..b9c766eebef7 100644 --- a/packages/bot-web-ui/src/components/flyout/help-contents/flyout-help-base.jsx +++ b/packages/bot-web-ui/src/components/flyout/help-contents/flyout-help-base.jsx @@ -1,26 +1,29 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Button, Icon, Text } from '@deriv/components'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { help_content_config, help_content_types } from 'Utils/help-content/help-content.config'; -import { connect } from 'Stores/connect'; +import { useDBotStore } from 'Stores/useDBotStore'; import FlyoutBlock from '../flyout-block.jsx'; import FlyoutImage from './flyout-img.jsx'; import FlyoutText from './flyout-text.jsx'; import FlyoutVideo from './flyout-video.jsx'; -const HelpBase = ({ - block_node, - block_type, - examples, - help_string, - is_search_flyout, - onBackClick, - onSequenceClick, - should_next_disable, - should_previous_disable, - title, -}) => { +const HelpBase = observer(() => { + const { flyout, flyout_help } = useDBotStore(); + const { + block_node, + block_type, + examples, + help_string, + onBackClick, + onSequenceClick, + should_next_disable, + should_previous_disable, + title, + } = flyout_help; + const { is_search_flyout } = flyout; + const block_help_component = help_string && help_content_config(__webpack_public_path__)[block_type]; let text_count = 0; @@ -122,30 +125,6 @@ const HelpBase = ({ )} ); -}; - -HelpBase.propTypes = { - block_node: PropTypes.object, - block_type: PropTypes.string, - examples: PropTypes.array, - help_string: PropTypes.object, - is_search_flyout: PropTypes.bool, - onBackClick: PropTypes.func, - onSequenceClick: PropTypes.func, - should_next_disable: PropTypes.bool, - should_previous_disable: PropTypes.bool, - title: PropTypes.string, -}; +}); -export default connect(({ flyout, flyout_help }) => ({ - block_node: flyout_help.block_node, - block_type: flyout_help.block_type, - examples: flyout_help.examples, - help_string: flyout_help.help_string, - is_search_flyout: flyout.is_search_flyout, - onBackClick: flyout_help.onBackClick, - onSequenceClick: flyout_help.onSequenceClick, - should_next_disable: flyout_help.should_next_disable, - should_previous_disable: flyout_help.should_previous_disable, - title: flyout_help.title, -}))(HelpBase); +export default HelpBase; diff --git a/packages/bot-web-ui/src/components/index.js b/packages/bot-web-ui/src/components/index.js index 192befc9f2fd..a207acea4350 100644 --- a/packages/bot-web-ui/src/components/index.js +++ b/packages/bot-web-ui/src/components/index.js @@ -13,7 +13,6 @@ export { default as NetworkToastPopup } from './network-toast-popup'; export { arrayAsMessage, messageWithButton, messageWithImage } from './notify-item'; export { default as RoutePromptDialog } from './route-prompt-dialog'; export { default as RunPanel } from './run-panel'; -export { default as SaveModal } from './save-modal'; export { default as SelfExclusion } from './self-exclusion'; export { default as Summary } from './summary'; export { default as TradeAnimation } from './trade-animation'; diff --git a/packages/bot-web-ui/src/components/journal/journal.tsx b/packages/bot-web-ui/src/components/journal/journal.tsx index 42a37429c97c..9dea7b543b1b 100644 --- a/packages/bot-web-ui/src/components/journal/journal.tsx +++ b/packages/bot-web-ui/src/components/journal/journal.tsx @@ -2,23 +2,28 @@ import React from 'react'; import classnames from 'classnames'; import { DataList, Icon, Text } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { contract_stages } from 'Constants/contract-stage'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/index'; -import { TCheckedFilters, TFilterMessageValues, TJournalDataListArgs, TJournalProps } from './journal.types'; +import { useDBotStore } from 'Stores/useDBotStore'; +import { TCheckedFilters, TFilterMessageValues, TJournalDataListArgs } from './journal.types'; import { JournalItem, JournalLoader, JournalTools } from './journal-components'; -const Journal = ({ - contract_stage, - filtered_messages, - is_stop_button_visible, - unfiltered_messages, - ...props -}: TJournalProps) => { +const Journal = observer(() => { + const { journal, run_panel } = useDBotStore(); + const { + checked_filters, + filterMessage, + filters, + filtered_messages, + is_filter_dialog_visible, + toggleFilterDialog, + unfiltered_messages, + } = journal; + const { is_stop_button_visible, contract_stage } = run_panel; + const filtered_messages_length = Array.isArray(filtered_messages) && filtered_messages.length; const unfiltered_messages_length = Array.isArray(unfiltered_messages) && unfiltered_messages.length; - const { checked_filters } = props; const is_mobile = isMobile(); return ( @@ -27,7 +32,13 @@ const Journal = ({ 'run-panel-tab__content': !is_mobile, })} > - +
{filtered_messages_length ? (
); -}; +}); -export default connect(({ journal, run_panel }: RootStore) => ({ - checked_filters: journal.checked_filters, - filterMessage: journal.filterMessage, - filters: journal.filters, - filtered_messages: journal.filtered_messages, - is_filter_dialog_visible: journal.is_filter_dialog_visible, - toggleFilterDialog: journal.toggleFilterDialog, - unfiltered_messages: journal.unfiltered_messages, - is_stop_button_visible: run_panel.is_stop_button_visible, - contract_stage: run_panel.contract_stage, -}))(Journal); +export default Journal; diff --git a/packages/bot-web-ui/src/components/load-modal/load-modal.tsx b/packages/bot-web-ui/src/components/load-modal/load-modal.tsx index 3e07e73567b0..60c9de91c256 100644 --- a/packages/bot-web-ui/src/components/load-modal/load-modal.tsx +++ b/packages/bot-web-ui/src/components/load-modal/load-modal.tsx @@ -1,39 +1,29 @@ import React from 'react'; import { MobileFullPageModal, Modal, Tabs } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { tabs_title } from 'Constants/load-modal'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/root-store'; +import { useDBotStore } from 'Stores/useDBotStore'; import GoogleDrive from '../dashboard/dashboard-component/load-bot-preview/google-drive'; import Local from './local'; import LocalFooter from './local-footer'; import Recent from './recent'; import RecentFooter from './recent-footer'; -type TLoadModalProps = { - active_index: number; - is_load_modal_open: boolean; - loaded_local_file: string; - onEntered: () => void; - recent_strategies: any[]; - setActiveTabIndex: () => void; - setPreviewOnPopup: (show: boolean) => void; - tab_name: string; - toggleLoadModal: () => void; -}; - -const LoadModal = ({ - active_index, - is_load_modal_open, - loaded_local_file, - onEntered, - recent_strategies, - setActiveTabIndex, - setPreviewOnPopup, - tab_name, - toggleLoadModal, -}: TLoadModalProps) => { +const LoadModal = observer(() => { + const { load_modal, dashboard } = useDBotStore(); + const { + active_index, + is_load_modal_open, + loaded_local_file, + onEntered, + recent_strategies, + setActiveTabIndex, + toggleLoadModal, + tab_name, + } = load_modal; + const { setPreviewOnPopup } = dashboard; const header_text = localize('Load strategy'); if (isMobile()) { @@ -100,16 +90,6 @@ const LoadModal = ({ )} ); -}; +}); -export default connect(({ load_modal, dashboard }: RootStore) => ({ - active_index: load_modal.active_index, - is_load_modal_open: load_modal.is_load_modal_open, - loaded_local_file: load_modal.loaded_local_file, - onEntered: load_modal.onEntered, - recent_strategies: load_modal.recent_strategies, - setActiveTabIndex: load_modal.setActiveTabIndex, - tab_name: load_modal.tab_name, - toggleLoadModal: load_modal.toggleLoadModal, - setPreviewOnPopup: dashboard.setPreviewOnPopup, -}))(LoadModal); +export default LoadModal; diff --git a/packages/bot-web-ui/src/components/load-modal/local-footer.tsx b/packages/bot-web-ui/src/components/load-modal/local-footer.tsx index 384056add341..a17e70de1e1f 100644 --- a/packages/bot-web-ui/src/components/load-modal/local-footer.tsx +++ b/packages/bot-web-ui/src/components/load-modal/local-footer.tsx @@ -1,27 +1,15 @@ import React from 'react'; import { Button } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/root-store'; +import { useDBotStore } from 'Stores/useDBotStore'; -type TLocalFooterProps = { - is_open_button_loading: boolean; - loadFileFromLocal: () => void; - setLoadedLocalFile: (loaded_local_file: boolean | null) => void; - setOpenSettings: (toast_message: string, show_toast?: boolean) => void; - setPreviewOnPopup: (show: boolean) => void; - toggleLoadModal: () => void; -}; +const LocalFooter = observer(() => { + const { load_modal, dashboard } = useDBotStore(); + const { is_open_button_loading, loadFileFromLocal, setLoadedLocalFile, toggleLoadModal } = load_modal; + const { setOpenSettings, setPreviewOnPopup } = dashboard; -const LocalFooter = ({ - is_open_button_loading, - loadFileFromLocal, - setLoadedLocalFile, - setOpenSettings, - setPreviewOnPopup, - toggleLoadModal, -}: TLocalFooterProps) => { const is_mobile = isMobile(); const Wrapper = is_mobile ? Button.Group : React.Fragment; @@ -45,13 +33,6 @@ const LocalFooter = ({ /> ); -}; +}); -export default connect(({ load_modal, dashboard }: RootStore) => ({ - is_open_button_loading: load_modal.is_open_button_loading, - loadFileFromLocal: load_modal.loadFileFromLocal, - setLoadedLocalFile: load_modal.setLoadedLocalFile, - setOpenSettings: dashboard.setOpenSettings, - setPreviewOnPopup: dashboard.setPreviewOnPopup, - toggleLoadModal: load_modal.toggleLoadModal, -}))(LocalFooter); +export default LocalFooter; diff --git a/packages/bot-web-ui/src/components/load-modal/local.tsx b/packages/bot-web-ui/src/components/load-modal/local.tsx index bdbc581830dd..ef18b5150717 100644 --- a/packages/bot-web-ui/src/components/load-modal/local.tsx +++ b/packages/bot-web-ui/src/components/load-modal/local.tsx @@ -2,31 +2,17 @@ import React from 'react'; import classNames from 'classnames'; import { Button, Icon } from '@deriv/components'; import { isMobile } from '@deriv/shared'; +import { observer } from '@deriv/stores'; import { Localize, localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/root-store'; +import { useDBotStore } from 'Stores/useDBotStore'; import LocalFooter from './local-footer'; import WorkspaceControl from './workspace-control'; -type TLocalComponentProps = { - active_tab: number; - has_started_bot_builder_tour: boolean; - handleFileChange: ( - e: React.MouseEvent | React.FormEvent | DragEvent, - is_body?: boolean - ) => boolean; - is_open_button_loading: boolean; - loaded_local_file: string; - setLoadedLocalFile: (loaded_local_file: boolean | null) => void; -}; +const LocalComponent = observer(() => { + const { dashboard, load_modal } = useDBotStore(); + const { active_tab, has_started_bot_builder_tour } = dashboard; + const { handleFileChange, loaded_local_file, setLoadedLocalFile } = load_modal; -const LocalComponent = ({ - active_tab, - has_started_bot_builder_tour, - handleFileChange, - loaded_local_file, - setLoadedLocalFile, -}: TLocalComponentProps) => { const file_input_ref = React.useRef(null); const [is_file_supported, setIsFileSupported] = React.useState(true); const is_mobile = isMobile(); @@ -104,15 +90,6 @@ const LocalComponent = ({ ); -}; +}); -const Local = connect(({ load_modal, dashboard }: RootStore) => ({ - active_tab: dashboard.active_tab, - has_started_bot_builder_tour: dashboard.has_started_bot_builder_tour, - handleFileChange: load_modal.handleFileChange, - is_open_button_loading: load_modal.is_open_button_loading, - loaded_local_file: load_modal.loaded_local_file, - setLoadedLocalFile: load_modal.setLoadedLocalFile, -}))(LocalComponent); - -export default Local; +export default LocalComponent; diff --git a/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx b/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx index 158f33b1b1a8..f41830c5cb22 100644 --- a/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx +++ b/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx @@ -1,22 +1,14 @@ import React from 'react'; import { Button } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; -import RootStore from 'Stores/root-store'; +import { observer } from '@deriv/stores'; +import { useDBotStore } from 'Stores/useDBotStore'; -type TRecentFooterProps = { - is_open_button_loading: boolean; - loadFileFromRecent: () => void; - setOpenSettings: (toast_message: string, show_toast?: boolean) => void; - toggleLoadModal: () => void; -}; +const RecentFooter = observer(() => { + const { load_modal, dashboard } = useDBotStore(); + const { is_open_button_loading, loadFileFromRecent, toggleLoadModal } = load_modal; + const { setOpenSettings } = dashboard; -const RecentFooter = ({ - is_open_button_loading, - loadFileFromRecent, - setOpenSettings, - toggleLoadModal, -}: TRecentFooterProps) => { return ( + } + > + + + )} + +
+ {!has_applauncher_account && ( + + )} + {isMobile() && } +
- return ( - -
- {(isDesktop() || (isMobile() && index === 0)) && ( -
-
+ {eu_user ? ( +
+ +
+ ) : ( +
+ +
+ )} + + {!eu_user && ( + +
+ +
+
+ +
+
+ +
+
+ +
+
+ )} +
+
+ )} + + {(isDesktop() || (isMobile() && index === 1)) && ( +
+
{isMobile() ? ( ) => { setIndex(Number(item.target.value)); }} - name='Options' + name='CFDs' value={index} /> @@ -135,371 +355,136 @@ const StaticDashboard = ({ - {is_eu_title} + , + ]} + /> )} -
- - {eu_user ? ( - , - ]} - /> - ) : ( - , - , - ]} - /> - )} - -
- {has_account && ( - - {eu_user ? localize(`EUR`) : localize(`US Dollar`)} - - } - icon={eu_user ? 'EUR' : 'USD'} - actions={ - - } - > - - - )} -
-
- {!has_applauncher_account && ( - - )} - {isMobile() && } -
- -
- {eu_user ? ( -
- -
- ) : ( -
- -
- )} - - {!eu_user && ( - -
- -
-
- -
-
- -
-
- -
-
- )} -
-
- )} - - {(isDesktop() || (isMobile() && index === 1)) && ( -
-
- {isMobile() ? ( - - ) => { - setIndex(Number(item.target.value)); - }} - name='CFDs' - value={index} - /> - - ) : ( +
, ]} /> - )} -
-
- - , - ]} - /> - -
- - {isMobile() && ( - - {compare_accounts_title} - - )} - -
- - {localize('Deriv MT5')} - -
+
-
- {!is_eu_user && !CFDs_restricted_countries && ( - - )} - {isMobile() && !has_account && } - {is_eu_user && !financial_restricted_countries && ( - + {isMobile() && ( + + {compare_accounts_title} + )} - {!is_eu_user && !CFDs_restricted_countries && ( - +
+ + {localize('Deriv MT5')} + +
+ +
+ {!is_eu_user && !CFDs_restricted_countries && ( - {isMobile() && !has_account && } - {!financial_restricted_countries && ( + )} + {isMobile() && !has_account && } + {is_eu_user && !financial_restricted_countries && ( + + )} + + {!is_eu_user && !CFDs_restricted_countries && ( + - )} + {isMobile() && !has_account && } + {!financial_restricted_countries && ( + + )} + + )} + {isDesktop() && has_account && !eu_user && ( + + )} +
+ + {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && ( + + +
+ + {localize('Other CFD Platforms')} + +
)} - {isDesktop() && has_account && !eu_user && ( - - )} -
- - {!is_eu_user && !CFDs_restricted_countries && !financial_restricted_countries && ( - - -
- - {localize('Other CFD Platforms')} - -
-
- )} - {!is_eu_user && !CFDs_restricted_countries && ( -
- - {/* + + {/* */} -
- )} -
- )} -
-
- ); -}; + + )} + + )} + +
+ ); + } +); -export default observer(StaticDashboard); +export default StaticDashboard; diff --git a/packages/appstore/src/components/trade-button/trade-button.tsx b/packages/appstore/src/components/trade-button/trade-button.tsx index b26623f33324..f517ca368921 100644 --- a/packages/appstore/src/components/trade-button/trade-button.tsx +++ b/packages/appstore/src/components/trade-button/trade-button.tsx @@ -1,66 +1,68 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; import { Button } from '@deriv/components'; +import { observer, useStore } from '@deriv/stores'; import { localize } from '@deriv/translations'; import { Actions } from 'Components/containers/trading-app-card-actions'; -import React from 'react'; -import { Link } from 'react-router-dom'; -import { useStores } from 'Stores'; -const TradeButton = ({ - link_to, - onAction, - is_external, - is_buttons_disabled, - new_tab, -}: Pick) => { - const { traders_hub, modules } = useStores(); - const { is_demo } = traders_hub; - const { dxtrade_tokens } = modules.cfd; - const REAL_DXTRADE_URL = 'https://dx.deriv.com'; - const DEMO_DXTRADE_URL = 'https://dx-demo.deriv.com'; +const TradeButton = observer( + ({ + link_to, + onAction, + is_external, + is_buttons_disabled, + new_tab, + }: Pick) => { + const { traders_hub, modules } = useStore(); + const { is_demo } = traders_hub; + const { dxtrade_tokens } = modules.cfd; + const REAL_DXTRADE_URL = 'https://dx.deriv.com'; + const DEMO_DXTRADE_URL = 'https://dx-demo.deriv.com'; - if (link_to) { - if (is_external) { - if (new_tab) { + if (link_to) { + if (is_external) { + if (new_tab) { + return ( + + + + ); + } return ( - + ); } return ( - + - + + ); + } else if (onAction) { + return ( + ); } + return ( - - - - ); - } else if (onAction) { - return ( - + + + ); } - - return ( - - - - ); -}; +); export default TradeButton; diff --git a/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx b/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx new file mode 100644 index 000000000000..fd2f3eddabd5 --- /dev/null +++ b/packages/appstore/src/modules/traders-hub/__tests__/index.spec.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { ContentFlag } from '@deriv/shared'; +import { StoreProvider, mockStore } from '@deriv/stores'; +import { render, screen } from '@testing-library/react'; +import TradersHub from '..'; + +jest.mock('Components/modals/modal-manager', () => jest.fn(() => 'mockedModalManager')); +jest.mock('Components/cfds-listing', () => jest.fn(() => 'mockedCFDsListing')); +jest.mock('Components/options-multipliers-listing', () => jest.fn(() => 'mocked')); + +describe('TradersHub', () => { + const render_container = (mock_store_override = {}) => { + const mock_store = mockStore(mock_store_override); + const wrapper = ({ children }: { children: JSX.Element }) => ( + {children} + ); + + return render(, { + wrapper, + }); + }; + + it('should display the component', () => { + const { container } = render_container({ client: { is_logged_in: true } }); + expect(container).toBeInTheDocument(); + }); + + it('should display both CFDs and Multipliers section', () => { + render_container({ client: { is_logged_in: true } }); + const dashboard_sections = screen.getByTestId('dt_traders_hub'); + expect(dashboard_sections?.textContent?.match(/Multipliers/)).not.toBeNull(); + expect(dashboard_sections?.textContent?.match(/CFDs/)).not.toBeNull(); + }); + + it('should display Multipliers and CFDs section in order if the user is non eu', () => { + render_container({ + client: { is_logged_in: true }, + traders_hub: { is_eu_user: false }, + }); + const dashboard_sections = screen.getByTestId('dt_traders_hub'); + expect(dashboard_sections).not.toHaveClass('traders-hub__main-container-reversed'); + }); + + it('should display Multipliers and CFDs section in reverse order if the user is eu', () => { + render_container({ client: { is_logged_in: true }, traders_hub: { is_eu_user: true } }); + const dashboard_sections = screen.getByTestId('dt_traders_hub'); + expect(dashboard_sections).toHaveClass('traders-hub__main-container-reversed'); + }); + + it('should display disclaimer if the user is from low risk eu country', () => { + render_container({ + client: { is_logged_in: true }, + traders_hub: { content_flag: ContentFlag.LOW_RISK_CR_EU }, + }); + const disclaimer = screen.getByTestId('dt_traders_hub_disclaimer'); + expect(disclaimer).toBeInTheDocument(); + }); +}); diff --git a/packages/appstore/src/modules/traders-hub/index.tsx b/packages/appstore/src/modules/traders-hub/index.tsx index ec98e56b6194..ac99a42f880d 100644 --- a/packages/appstore/src/modules/traders-hub/index.tsx +++ b/packages/appstore/src/modules/traders-hub/index.tsx @@ -1,22 +1,20 @@ import React from 'react'; -import { observer } from 'mobx-react-lite'; +import { DesktopWrapper, MobileWrapper, ButtonToggle, Div100vhContainer, Text } from '@deriv/components'; +import { isDesktop, routes, ContentFlag } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; +import { Localize, localize } from '@deriv/translations'; import CFDsListing from 'Components/cfds-listing'; import ModalManager from 'Components/modals/modal-manager'; import MainTitleBar from 'Components/main-title-bar'; -import TourGuide from 'Modules/tour-guide/tour-guide'; import OptionsAndMultipliersListing from 'Components/options-multipliers-listing'; import ButtonToggleLoader from 'Components/pre-loader/button-toggle-loader'; -import { useStores } from 'Stores/index'; -import { isDesktop, routes, ContentFlag, isMobile } from '@deriv/shared'; -import { DesktopWrapper, MobileWrapper, ButtonToggle, Div100vhContainer, Text } from '@deriv/components'; -import { Localize } from '@deriv/translations'; import classNames from 'classnames'; - +import TourGuide from '../tour-guide/tour-guide'; import './traders-hub.scss'; const TradersHub = () => { - const { traders_hub, client, ui } = useStores(); - const { notification_messages_ui: Notifications } = ui; + const { traders_hub, client, ui } = useStore(); + const { notification_messages_ui: Notifications, is_mobile } = ui; const { is_landing_company_loaded, is_logged_in, is_switching, is_logging_in, is_account_setting_loaded } = client; const { selected_platform_type, setTogglePlatformType, is_tour_open, content_flag, is_eu_user } = traders_hub; const traders_hub_ref = React.useRef() as React.MutableRefObject; @@ -33,6 +31,7 @@ const TradersHub = () => { }; React.useEffect(() => { + if (is_eu_user) setTogglePlatformType('cfd'); setTimeout(() => { handleScroll(); setTimeout(() => { @@ -45,10 +44,12 @@ const TradersHub = () => { const is_eu_low_risk = content_flag === ContentFlag.LOW_RISK_CR_EU; - const platform_toggle_options = [ - { text: `${eu_title ? 'Multipliers' : 'Options & Multipliers'}`, value: 'options' }, - { text: 'CFDs', value: 'cfd' }, + const getPlatformToggleOptions = () => [ + { text: eu_title ? localize('Multipliers') : localize('Options & Multipliers'), value: 'options' }, + { text: localize('CFDs'), value: 'cfd' }, ]; + const platform_toggle_options = getPlatformToggleOptions(); + const platform_toggle_options_eu = getPlatformToggleOptions().reverse(); const platformTypeChange = (event: { target: { @@ -60,15 +61,16 @@ const TradersHub = () => { }; if (!is_logged_in) return null; - const EUDisclamer = () => { + const renderOrderedPlatformSections = (is_cfd_visible = true, is_options_and_multipliers_visible = true) => { return ( -
- - ]} - /> - +
+ {is_options_and_multipliers_visible && } + {is_cfd_visible && }
); }; @@ -85,16 +87,11 @@ const TradersHub = () => { {can_show_notify && }
- -
- - -
-
+ {renderOrderedPlatformSections()} {is_landing_company_loaded ? ( { ) : ( )} - {selected_platform_type === 'options' && } - {selected_platform_type === 'cfd' && } + {renderOrderedPlatformSections( + selected_platform_type === 'cfd', + selected_platform_type === 'options' + )} {scrolled && }
- {is_eu_low_risk && } + {is_eu_low_risk && ( +
+ + ]} + /> + +
+ )} ); }; diff --git a/packages/appstore/src/modules/traders-hub/traders-hub.scss b/packages/appstore/src/modules/traders-hub/traders-hub.scss index ab9fd05592b9..49b9474a391a 100644 --- a/packages/appstore/src/modules/traders-hub/traders-hub.scss +++ b/packages/appstore/src/modules/traders-hub/traders-hub.scss @@ -12,6 +12,9 @@ display: flex; flex-direction: column; gap: 2.4rem; + &-reversed { + flex-direction: column-reverse; + } } &__button-toggle { @@ -36,7 +39,7 @@ } } -.disclamer { +.disclaimer { position: fixed; bottom: 3.6rem; width: 100%; diff --git a/packages/components/src/components/button-toggle/button-toggle.tsx b/packages/components/src/components/button-toggle/button-toggle.tsx index d25113908895..268d839b2e89 100644 --- a/packages/components/src/components/button-toggle/button-toggle.tsx +++ b/packages/components/src/components/button-toggle/button-toggle.tsx @@ -5,7 +5,7 @@ import Counter from '../counter/counter'; import Button from '../button/button'; type TButtonToggleProps = { - buttons_arr: Array<{ text: string; value: string; count?: number }>; + buttons_arr: Array<{ text: string; value: number | string; count?: number }>; className?: string; id?: string; is_animated?: boolean; @@ -50,7 +50,7 @@ const ButtonToggle = ({