Skip to content

Commit

Permalink
DTRA-2034 / Kate / [DTrader-V2] Errors handling (deriv-com#17095)
Browse files Browse the repository at this point in the history
* refactor: add action sheet gor handling 4 specific errors

* refactor: add tests ofr service error description

* feat: add snackbar to purchase btn

* feat: add disabling for purchase btn

* fix: disabling state for purchase button

* refactor: add logic from production for closing error modal

* refactor: exctract snackbar inro a separate component

* chore: show snackbar for other cases too

* refactor: quill ui update

* refactor: add error handlung for contract consillation

* refactor: trade params errors

* refactor: exctract duplicates into a function

* refactor: add tests

* refactor: apply suggestions

* chore: refactore more tests

* fix: add support for mf errors
  • Loading branch information
kate-deriv authored Oct 22, 2024
1 parent dd5d2e4 commit cac1e0c
Show file tree
Hide file tree
Showing 18 changed files with 551 additions and 168 deletions.
34 changes: 25 additions & 9 deletions packages/core/src/Stores/contract-replay-store.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { action, observable, makeObservable, override } from 'mobx';
import { routes, isEmptyObject, isForwardStarting, WS, contractCancelled, contractSold } from '@deriv/shared';
import {
routes,
isDtraderV2Enabled,
isEmptyObject,
isForwardStarting,
WS,
contractCancelled,
contractSold,
} from '@deriv/shared';
import { Money } from '@deriv/components';
import { Analytics } from '@deriv-com/analytics';
import { localize } from '@deriv/translations';
Expand Down Expand Up @@ -223,10 +231,14 @@ export default class ContractReplayStore extends BaseStore {
if (contract_id) {
WS.cancelContract(contract_id).then(response => {
if (response.error) {
this.root_store.common.setServicesError({
type: response.msg_type,
...response.error,
});
this.root_store.common.setServicesError(
{
type: response.msg_type,
...response.error,
},
// Temporary switching off old snackbar for DTrader-V2
isDtraderV2Enabled(this.root_store.ui.is_mobile)
);
} else {
this.root_store.notifications.addNotificationMessage(contractCancelled());
}
Expand All @@ -246,10 +258,14 @@ export default class ContractReplayStore extends BaseStore {
if (response.error) {
// If unable to sell due to error, give error via pop up if not in contract mode
this.is_sell_requested = false;
this.root_store.common.setServicesError({
type: response.msg_type,
...response.error,
});
this.root_store.common.setServicesError(
{
type: response.msg_type,
...response.error,
},
// Temporary switching off old snackbar for DTrader-V2
isDtraderV2Enabled(this.root_store.ui.is_mobile)
);
} else if (!response.error && response.sell) {
this.is_sell_requested = false;
// update contract store sell info after sell
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/Stores/contract-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ChartBarrierStore,
isAccumulatorContract,
isDigitContract,
isDtraderV2Enabled,
isEnded,
isEqualObject,
isMultiplierContract,
Expand Down Expand Up @@ -384,10 +385,14 @@ export default class ContractStore extends BaseStore {
Object.keys(limit_order).length !== 0 &&
WS.contractUpdate(this.contract_id, limit_order).then(response => {
if (response.error) {
this.root_store.common.setServicesError({
type: response.msg_type,
...response.error,
});
this.root_store.common.setServicesError(
{
type: response.msg_type,
...response.error,
},
// Temporary switching off old snackbar for DTrader-V2
isDtraderV2Enabled(this.root_store.ui.is_mobile)
);
return;
}

Expand Down
25 changes: 17 additions & 8 deletions packages/core/src/Stores/portfolio-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getEndTime,
getTradeNotificationMessage,
isAccumulatorContract,
isDtraderV2Enabled,
isEmptyObject,
isEnded,
isValidToSell,
Expand Down Expand Up @@ -313,10 +314,14 @@ export default class PortfolioStore extends BaseStore {
if (contract_id) {
WS.cancelContract(contract_id).then(response => {
if (response.error) {
this.root_store.common.setServicesError({
type: response.msg_type,
...response.error,
});
this.root_store.common.setServicesError(
{
type: response.msg_type,
...response.error,
},
// Temporary switching off old snackbar for DTrader-V2
isDtraderV2Enabled(this.root_store.ui.is_mobile)
);
} else if (window.location.pathname !== routes.trade || !this.root_store.ui.is_mobile) {
this.root_store.notifications.addNotificationMessage(contractCancelled());
}
Expand All @@ -343,10 +348,14 @@ export default class PortfolioStore extends BaseStore {

// invalidToken error will handle in socket-general.js
if (response.error.code !== 'InvalidToken') {
this.root_store.common.setServicesError({
type: response.msg_type,
...response.error,
});
this.root_store.common.setServicesError(
{
type: response.msg_type,
...response.error,
},
// Temporary switching off old snackbar for dTrader-V2
isDtraderV2Enabled(this.root_store.ui.is_mobile)
);
}
} else if (!response.error && response.sell) {
// update contract store sell info after sell
Expand Down
2 changes: 1 addition & 1 deletion packages/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ type TCommonStoreError = {
type?: string;
};

type TCommonStoreServicesError = {
export type TCommonStoreServicesError = {
code?: string;
message?: string;
type?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,4 @@ describe('PurchaseButtonContent', () => {

expect(container).toBeEmptyDOMElement();
});

it('should render error text as button content if error is not falsy', () => {
render(<PurchaseButtonContent {...mock_props} error='Mock error text' />);

expect(screen.getByText('Mock error text')).toBeInTheDocument();
});

it('should render error text as button content even if error is not falsy, but has_no_button_content === true', () => {
render(<PurchaseButtonContent {...mock_props} error='Mock error text' has_no_button_content />);

expect(screen.getByText('Mock error text')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { getLocalizedBasis } from '@deriv/shared';
import { Money } from '@deriv/components';

type TPurchaseButtonContent = {
error?: React.ReactNode;
has_no_button_content?: boolean;
info: ReturnType<typeof useTraderStore>['proposal_info'][0] | Record<string, never>;
is_reverse?: boolean;
Expand All @@ -17,7 +16,6 @@ type TPurchaseButtonContent = {

const PurchaseButtonContent = ({
currency,
error,
has_open_accu_contract,
has_no_button_content,
info,
Expand All @@ -26,7 +24,7 @@ const PurchaseButtonContent = ({
is_vanilla,
is_reverse,
}: TPurchaseButtonContent) => {
if (has_no_button_content && !error) return null;
if (has_no_button_content) return null;

const { payout, stake } = getLocalizedBasis();

Expand All @@ -50,30 +48,28 @@ const PurchaseButtonContent = ({
)}
data-testid='dt_purchase_button_wrapper'
>
{(!is_content_empty || error) && (
{!is_content_empty && (
<React.Fragment>
<CaptionText
as='span'
size='sm'
className={clsx(!has_open_accu_contract && 'purchase-button__information__item')}
color='quill-typography__color--prominent'
>
{!error && text_basis}
{text_basis}
</CaptionText>
<CaptionText
as='span'
size='sm'
className={clsx(!has_open_accu_contract && 'purchase-button__information__item')}
color='quill-typography__color--prominent'
>
{error || (
<Money
amount={amount}
currency={currency}
should_format={!is_turbos && !is_vanilla}
show_currency
/>
)}
<Money
amount={amount}
currency={currency}
should_format={!is_turbos && !is_vanilla}
show_currency
/>
</CaptionText>
</React.Fragment>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import clsx from 'clsx';
import { observer } from 'mobx-react';
import { Localize } from '@deriv/translations';
import { useStore } from '@deriv/stores';
import { useTraderStore } from 'Stores/useTraderStores';
import { Button, useNotifications } from '@deriv-com/quill-ui';
Expand All @@ -14,22 +13,26 @@ import {
isAccumulatorContract,
isOpen,
isValidToSell,
MT5_ACCOUNT_STATUS,
} from '@deriv/shared';
import { useMFAccountStatus } from '@deriv/hooks';
import PurchaseButtonContent from './purchase-button-content';
import { getTradeTypeTabsList } from 'AppV2/Utils/trade-params-utils';
import { StandaloneStopwatchRegularIcon } from '@deriv/quill-icons';
import { CSSTransition } from 'react-transition-group';
import { getDisplayedContractTypes } from 'AppV2/Utils/trade-types-utils';
import { usePrevious } from '@deriv/components';
import { checkIsServiceModalError } from 'AppV2/Utils/layout-utils';

const PurchaseButton = observer(() => {
const [loading_button_index, setLoadingButtonIndex] = React.useState<number | null>(null);
const { isMobile } = useDevice();
const { addBanner } = useNotifications();
const {
contract_replay: { is_market_closed },
portfolio: { all_positions, onClickSell, open_accu_contract, active_positions },
client: { is_logged_in },
common: { services_error },
ui: { is_mf_verification_pending_modal_visible, setIsMFVericationPendingModal },
} = useStore();
const {
contract_type,
Expand All @@ -44,6 +47,7 @@ const PurchaseButton = observer(() => {
is_vanilla_fx,
is_vanilla,
proposal_info,
purchase_info,
onPurchaseV2,
symbol,
trade_type_tab,
Expand All @@ -58,6 +62,7 @@ const PurchaseButton = observer(() => {
({ contract_info, type }) => isAccumulatorContract(type) && contract_info.underlying === symbol
)
);
const mf_account_status = useMFAccountStatus();

/*TODO: add error handling when design will be ready. validation_errors can be taken from useTraderStore
const hasError = (info: TTradeStore['proposal_info'][string]) => {
Expand Down Expand Up @@ -94,7 +99,7 @@ const PurchaseButton = observer(() => {
const current_stake =
(is_valid_to_sell && active_accu_contract && getIndicativePrice(active_accu_contract.contract_info)) || null;
const cardLabels = getCardLabelsV2();

const is_modal_error = checkIsServiceModalError({ services_error, is_mf_verification_pending_modal_visible });
const is_accu_sell_disabled = !is_valid_to_sell || active_accu_contract?.is_sell_requested;

const getButtonType = (index: number, trade_type: string) => {
Expand Down Expand Up @@ -148,28 +153,8 @@ const PurchaseButton = observer(() => {
const info = proposal_info?.[trade_type] || {};
const is_single_button = contract_types.length === 1;
const is_loading = loading_button_index === index;
const is_disabled = !is_trade_enabled_v2 || info.has_error;

const getErrorMessage = () => {
if (['amount', 'stake'].includes(info.error_field ?? '')) {
return <Localize i18n_default_text='Invalid stake' />;
}

/* TODO: stop using error text for is_max_payout_exceeded after validation_params are added to proposal API (both success & error response):
E.g., for is_max_payout_exceeded, we have to temporarily check the error text: Max payout error always contains 3 numbers, the check will work for any languages: */
const float_number_search_regex = /\d+(\.\d+)?/g;
const is_max_payout_exceeded =
info.has_error && info.message?.match(float_number_search_regex)?.length === 3;

if (is_max_payout_exceeded) {
return <Localize i18n_default_text='Exceeds max payout' />;
}

const api_error = info.has_error && !is_market_closed && !!info.message ? info.message : '';
return api_error;
};

const error_message = getErrorMessage();
const is_disabled =
!is_trade_enabled_v2 || info.has_error || (!!purchase_info.error && !is_modal_error);

return (
<React.Fragment key={trade_type}>
Expand All @@ -190,14 +175,17 @@ const PurchaseButton = observer(() => {
isOpaque
disabled={is_disabled && !is_loading}
onClick={() => {
setLoadingButtonIndex(index);
onPurchaseV2(trade_type, isMobile, addNotificationBannerCallback);
if (is_multiplier && mf_account_status === MT5_ACCOUNT_STATUS.PENDING) {
setIsMFVericationPendingModal(true);
} else {
setLoadingButtonIndex(index);
onPurchaseV2(trade_type, isMobile, addNotificationBannerCallback);
}
}}
>
{!is_loading && (
<PurchaseButtonContent
{...purchase_button_content_props}
error={error_message}
has_no_button_content={has_no_button_content}
info={info}
is_reverse={!!index}
Expand Down
Loading

0 comments on commit cac1e0c

Please sign in to comment.