Skip to content

Commit

Permalink
[UPM] Sergei / UPM - 1209 / Notification and Banner for Senegal busin…
Browse files Browse the repository at this point in the history
…ess closure (deriv-com#16349)

* feat: make sketch of banner

* fix: unit tests for traders hub

* feat: done with senegal banner and notification

* feat: create trading-disabled-modal and write unit tests

* feat: add purchase handler

* refactor: remove comments

* feat: write unit tests for business-closure-banner

* feat: add message to lock deposit

* feat: get back changes in client store

* feat: add pathname check for cashier deposit
  • Loading branch information
sergei-deriv authored Aug 12, 2024
1 parent 2fb91cb commit 3a4c990
Show file tree
Hide file tree
Showing 23 changed files with 379 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { StoreProvider, mockStore } from '@deriv/stores';
import BusinessClosureBanner from '../business-closure-banner';

describe('<BusinessClosureBanner />', () => {
const mockDefault = mockStore({ client: { is_account_to_be_closed_by_residence: false } });

const wrapper = (mock: ReturnType<typeof mockStore> = mockDefault) => {
const Component = ({ children }: { children: JSX.Element }) => (
<StoreProvider store={mock}>{children}</StoreProvider>
);
return Component;
};

it('should render nothing by default', () => {
render(<BusinessClosureBanner />, {
wrapper: wrapper(),
});

expect(screen.queryByText(/Due to business changes/)).not.toBeInTheDocument();
});

it('should render banner content if is_account_to_be_closed_by_residence === true ', () => {
const mock = mockStore({ client: { is_account_to_be_closed_by_residence: true } });

render(<BusinessClosureBanner />, {
wrapper: wrapper(mock),
});

expect(screen.queryByText(/Due to business changes/)).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.business-closure-banner {
padding-inline: 1.6rem;
// One more solution just change the color and make it without opacity instead of height calc (background-color: #fff4e4;)

@include desktop-screen {
position: sticky;
top: 0;
z-index: 2;

& + div {
height: calc(100vh - $HEADER_HEIGHT - $FOOTER_HEIGHT - 3.4rem);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { InlineMessage, Text } from '@deriv-com/ui';
import { observer, useStore } from '@deriv/stores';
import { Localize } from '@deriv/translations';
import { formatDate } from '@deriv/shared';
import './business-closure-banner.scss';

const BusinessClosureBanner = observer(() => {
const { client } = useStore();
const { is_account_to_be_closed_by_residence, account_time_of_closure } = client;

if (!is_account_to_be_closed_by_residence) return null;

return (
<InlineMessage type='filled' variant='warning' className='business-closure-banner'>
<Text size='xs'>
<Localize
i18n_default_text='Due to business changes, client accounts in Senegal are to be closed. Deposits and trading are disabled.
Withdraw your funds by {{date}}.'
values={{
date: formatDate(account_time_of_closure, 'DD MMM YYYY'),
}}
/>
</Text>
</InlineMessage>
);
});

export default BusinessClosureBanner;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import BusinessClosureBanner from './business-closure-banner';

export default BusinessClosureBanner;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jest.mock('Components/cfds-listing', () => jest.fn(() => 'mockedCFDsListing'));
jest.mock('Components/options-multipliers-listing', () => jest.fn(() => 'mocked<OptionsAndMultipliersListing>'));

jest.mock('@deriv-com/ui', () => ({
...jest.requireActual('@deriv-com/ui'),
useDevice: jest.fn(() => ({
isDesktop: true,
isMobile: false,
Expand Down
2 changes: 2 additions & 0 deletions packages/appstore/src/modules/traders-hub/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import OptionsAndMultipliersListing from 'Components/options-multipliers-listing
import ButtonToggleLoader from 'Components/pre-loader/button-toggle-loader';
import AfterSignupFlow from 'Components/after-signup-flow';
import Disclaimer from 'Components/disclaimer';
import BusinessClosureBanner from 'Components/banners/business-closure-banner';
import { useContentFlag, useGrowthbookGetFeatureValue } from '@deriv/hooks';
import classNames from 'classnames';
import './traders-hub.scss';
Expand Down Expand Up @@ -152,6 +153,7 @@ const TradersHub = observer(() => {
return (
<React.Fragment>
<AfterSignupFlow />
<BusinessClosureBanner />
<Div100vhContainer className='traders-hub--mobile' height_offset='50px' is_disabled={isDesktop}>
{can_show_notify && <Notifications />}
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type TProps = {
is_withdrawal_locked: boolean;
is_identity_verification_needed: boolean;
is_pending_verification: boolean;
is_account_to_be_closed_by_residence: boolean;
account_time_of_closure?: number;
};

const getMessage = ({
Expand All @@ -31,6 +33,8 @@ const getMessage = ({
is_withdrawal_locked,
is_identity_verification_needed,
is_pending_verification,
is_account_to_be_closed_by_residence,
account_time_of_closure,
}: TProps) => {
const no_residence = cashier_validation?.includes('no_residence');
const unwelcome_status = cashier_validation?.includes('unwelcome_status');
Expand All @@ -52,6 +56,21 @@ const getMessage = ({
const pa_commision_withdrawal_limit = cashier_validation?.includes('PACommisionWithdrawalLimit');
const pathname = history.location.pathname;

if (is_account_to_be_closed_by_residence && pathname === routes.cashier_deposit) {
return {
icon: 'IcCashierNoBalance',
title: localize('Deposits disabled'),
description: (
<Localize
i18n_default_text='Due to business changes, client accounts in Senegal are to be closed. Withdraw any remaining funds by {{date}}.'
values={{
date: formatDate(account_time_of_closure, 'DD MMM YYYY'),
}}
/>
),
};
}

if (is_system_maintenance) {
if (is_crypto && is_withdrawal_locked)
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const CashierLocked = observer(() => {
is_withdrawal_lock: is_withdrawal_locked,
loginid,
is_identity_verification_needed,
is_account_to_be_closed_by_residence,
account_time_of_closure,
} = client;
const mf_account_status = useMFAccountStatus();
const is_cashier_locked = useCashierLocked();
Expand All @@ -40,6 +42,8 @@ const CashierLocked = observer(() => {
is_withdrawal_locked,
is_identity_verification_needed,
is_pending_verification: mf_account_status === MT5_ACCOUNT_STATUS.PENDING,
is_account_to_be_closed_by_residence,
account_time_of_closure,
});

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const AppNotificationMessages = observer(
'trustpilot',
'unwelcome',
'additional_kyc_info',
'notify_account_is_to_be_closed_by_residence',
].includes(message.key) || message.type === 'p2p_completed_order'
: true;

Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/Stores/client-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ export default class ClientStore extends BaseStore {
is_mf_account: computed,
is_tradershub_tracking: observable,
setTradersHubTracking: action.bound,
account_time_of_closure: computed,
is_account_to_be_closed_by_residence: computed,
});

reaction(
Expand Down Expand Up @@ -2881,4 +2883,14 @@ export default class ClientStore extends BaseStore {
get is_mf_account() {
return this.loginid?.startsWith('MF');
}

get account_time_of_closure() {
return this.account_status?.account_closure?.find(
item => item?.status_codes?.includes('residence_closure') && item?.type === 'residence'
)?.time_of_closure;
}

get is_account_to_be_closed_by_residence() {
return this.account_time_of_closure && this.residence && this.residence === 'sn';
}
}
26 changes: 26 additions & 0 deletions packages/core/src/Stores/notification-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,14 @@ export default class NotificationStore extends BaseStore {
this.removeNotificationByKey({ key: this.client_notifications.enable_passkey });
}

if (this.root_store.client.is_account_to_be_closed_by_residence) {
this.addNotificationMessage(this.client_notifications.notify_account_is_to_be_closed_by_residence);
} else {
this.removeNotificationByKey({
key: this.client_notifications.notify_account_is_to_be_closed_by_residence,
});
}

const client = accounts[loginid];
if (client && !client.is_virtual) {
if (isEmptyObject(account_status)) return;
Expand Down Expand Up @@ -1541,6 +1549,24 @@ export default class NotificationStore extends BaseStore {
},
type: 'warning',
},
notify_account_is_to_be_closed_by_residence: {
action: {
route: routes.cashier_withdrawal,
text: localize('Withdraw funds'),
},
header: localize('Deposits and trading disabled'),
key: 'notify_account_is_to_be_closed_by_residence',
message: (
<Localize
i18n_default_text='Due to business changes, client accounts in Senegal are to be closed. Withdraw your funds by {{date}}.'
values={{
date: formatDate(this.root_store.client.account_time_of_closure, 'DD MMM YYYY'),
}}
/>
),
should_show_again: true,
type: 'warning',
},
};

this.client_notifications = notifications;
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/Stores/ui-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default class UIStore extends BaseStore {
is_update_email_modal_visible = false;
is_reset_trading_password_modal_visible = false;
is_mf_verification_pending_modal_visible = false;
is_trading_disabled_by_residence_modal_visible = false;
// @observable is_purchase_lock_on = false;
// SmartCharts Controls
// TODO: enable asset information
Expand Down Expand Up @@ -267,6 +268,7 @@ export default class UIStore extends BaseStore {
is_landscape: observable,
is_language_settings_modal_on: observable,
is_mf_verification_pending_modal_visible: observable,
is_trading_disabled_by_residence_modal_visible: observable,
is_mobile_language_menu_open: observable,
is_nativepicker_visible: observable,

Expand Down Expand Up @@ -397,6 +399,7 @@ export default class UIStore extends BaseStore {
setSubSectionIndex: action.bound,
setTopUpInProgress: action.bound,
setIsMFVericationPendingModal: action.bound,
setIsTradingDisabledByResidenceModal: action.bound,
setMT5MigrationModalEnabled: action.bound,
setMobileLanguageMenuOpen: action.bound,
toggleAccountsDialog: action.bound,
Expand Down Expand Up @@ -956,6 +959,10 @@ export default class UIStore extends BaseStore {
this.is_mf_verification_pending_modal_visible = value;
}

setIsTradingDisabledByResidenceModal(value) {
this.is_trading_disabled_by_residence_modal_visible = value;
}

toggleAdditionalKycInfoModal() {
this.is_additional_kyc_info_modal_open = !this.is_additional_kyc_info_modal_open;
}
Expand Down
10 changes: 8 additions & 2 deletions packages/hooks/src/useDepositLocked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import useNeedTNC from './useNeedTNC';

const useDepositLocked = () => {
const { client } = useStore();
const { is_deposit_lock, is_trading_experience_incomplete, landing_company_shortcode } = client;
const {
is_deposit_lock,
is_trading_experience_incomplete,
landing_company_shortcode,
is_account_to_be_closed_by_residence,
} = client;
const is_need_authentication = useNeedAuthentication();
const is_need_tnc = useNeedTNC();
const is_need_financial_assessment = useNeedFinancialAssessment();
Expand All @@ -18,7 +23,8 @@ const useDepositLocked = () => {
is_deposit_lock ||
is_need_authentication ||
is_need_tnc ||
is_trading_experience_incomplete_or_need_financial_assessment;
is_trading_experience_incomplete_or_need_financial_assessment ||
is_account_to_be_closed_by_residence;

return is_deposit_locked;
};
Expand Down
4 changes: 4 additions & 0 deletions packages/stores/src/mockStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ const mock = (): TStores & { is_mock: boolean } => {
is_mf_account: false,
is_tradershub_tracking: false,
setTradersHubTracking: jest.fn(),
account_time_of_closure: undefined,
is_account_to_be_closed_by_residence: false,
},
common: {
error: common_store_error,
Expand Down Expand Up @@ -481,6 +483,8 @@ const mock = (): TStores & { is_mock: boolean } => {
setShouldShowDepositNowOrLaterModal: jest.fn(),
should_show_crypto_transaction_processing_modal: false,
setShouldShowCryptoTransactionProcessingModal: jest.fn(),
is_trading_disabled_by_residence_modal_visible: false,
setIsTradingDisabledByResidenceModal: jest.fn(),
},
traders_hub: {
getAccount: jest.fn(),
Expand Down
4 changes: 4 additions & 0 deletions packages/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ type TClientStore = {
is_cr_account: boolean;
is_mf_account: boolean;
setTradersHubTracking: (value: boolean) => void;
account_time_of_closure?: number;
is_account_to_be_closed_by_residence: boolean;
};

type TCommonStoreError = {
Expand Down Expand Up @@ -826,6 +828,8 @@ type TUiStore = {
setShouldShowDepositNowOrLaterModal: (value: boolean) => void;
should_show_crypto_transaction_processing_modal: boolean;
setShouldShowCryptoTransactionProcessingModal: (value: boolean) => void;
is_trading_disabled_by_residence_modal_visible: boolean;
setIsTradingDisabledByResidenceModal: (value: boolean) => void;
};

type TPortfolioStore = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { StoreProvider, mockStore } from '@deriv/stores';
import { TradingDisabledByResidenceModalContent } from '../trading-disabled-by-residence-modal-content';

describe('<TradingDisabledByResidenceModalContent />', () => {
const mockDefault = mockStore({});

const wrapper = (mock: ReturnType<typeof mockStore> = mockDefault) => {
const Component = ({ children }: { children: JSX.Element }) => (
<StoreProvider store={mock}>{children}</StoreProvider>
);
return Component;
};

it('should render modal content with correct title', () => {
render(<TradingDisabledByResidenceModalContent />, {
wrapper: wrapper(),
});

expect(screen.getByText(/Trading disabled/)).toBeInTheDocument();
});
});
Loading

0 comments on commit 3a4c990

Please sign in to comment.