Skip to content

Commit cac1e0c

Browse files
authored
DTRA-2034 / Kate / [DTrader-V2] Errors handling (deriv-com#17095)
* 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
1 parent dd5d2e4 commit cac1e0c

18 files changed

+551
-168
lines changed

packages/core/src/Stores/contract-replay-store.js

+25-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { action, observable, makeObservable, override } from 'mobx';
2-
import { routes, isEmptyObject, isForwardStarting, WS, contractCancelled, contractSold } from '@deriv/shared';
2+
import {
3+
routes,
4+
isDtraderV2Enabled,
5+
isEmptyObject,
6+
isForwardStarting,
7+
WS,
8+
contractCancelled,
9+
contractSold,
10+
} from '@deriv/shared';
311
import { Money } from '@deriv/components';
412
import { Analytics } from '@deriv-com/analytics';
513
import { localize } from '@deriv/translations';
@@ -223,10 +231,14 @@ export default class ContractReplayStore extends BaseStore {
223231
if (contract_id) {
224232
WS.cancelContract(contract_id).then(response => {
225233
if (response.error) {
226-
this.root_store.common.setServicesError({
227-
type: response.msg_type,
228-
...response.error,
229-
});
234+
this.root_store.common.setServicesError(
235+
{
236+
type: response.msg_type,
237+
...response.error,
238+
},
239+
// Temporary switching off old snackbar for DTrader-V2
240+
isDtraderV2Enabled(this.root_store.ui.is_mobile)
241+
);
230242
} else {
231243
this.root_store.notifications.addNotificationMessage(contractCancelled());
232244
}
@@ -246,10 +258,14 @@ export default class ContractReplayStore extends BaseStore {
246258
if (response.error) {
247259
// If unable to sell due to error, give error via pop up if not in contract mode
248260
this.is_sell_requested = false;
249-
this.root_store.common.setServicesError({
250-
type: response.msg_type,
251-
...response.error,
252-
});
261+
this.root_store.common.setServicesError(
262+
{
263+
type: response.msg_type,
264+
...response.error,
265+
},
266+
// Temporary switching off old snackbar for DTrader-V2
267+
isDtraderV2Enabled(this.root_store.ui.is_mobile)
268+
);
253269
} else if (!response.error && response.sell) {
254270
this.is_sell_requested = false;
255271
// update contract store sell info after sell

packages/core/src/Stores/contract-store.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ChartBarrierStore,
44
isAccumulatorContract,
55
isDigitContract,
6+
isDtraderV2Enabled,
67
isEnded,
78
isEqualObject,
89
isMultiplierContract,
@@ -384,10 +385,14 @@ export default class ContractStore extends BaseStore {
384385
Object.keys(limit_order).length !== 0 &&
385386
WS.contractUpdate(this.contract_id, limit_order).then(response => {
386387
if (response.error) {
387-
this.root_store.common.setServicesError({
388-
type: response.msg_type,
389-
...response.error,
390-
});
388+
this.root_store.common.setServicesError(
389+
{
390+
type: response.msg_type,
391+
...response.error,
392+
},
393+
// Temporary switching off old snackbar for DTrader-V2
394+
isDtraderV2Enabled(this.root_store.ui.is_mobile)
395+
);
391396
return;
392397
}
393398

packages/core/src/Stores/portfolio-store.js

+17-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
getEndTime,
1919
getTradeNotificationMessage,
2020
isAccumulatorContract,
21+
isDtraderV2Enabled,
2122
isEmptyObject,
2223
isEnded,
2324
isValidToSell,
@@ -313,10 +314,14 @@ export default class PortfolioStore extends BaseStore {
313314
if (contract_id) {
314315
WS.cancelContract(contract_id).then(response => {
315316
if (response.error) {
316-
this.root_store.common.setServicesError({
317-
type: response.msg_type,
318-
...response.error,
319-
});
317+
this.root_store.common.setServicesError(
318+
{
319+
type: response.msg_type,
320+
...response.error,
321+
},
322+
// Temporary switching off old snackbar for DTrader-V2
323+
isDtraderV2Enabled(this.root_store.ui.is_mobile)
324+
);
320325
} else if (window.location.pathname !== routes.trade || !this.root_store.ui.is_mobile) {
321326
this.root_store.notifications.addNotificationMessage(contractCancelled());
322327
}
@@ -343,10 +348,14 @@ export default class PortfolioStore extends BaseStore {
343348

344349
// invalidToken error will handle in socket-general.js
345350
if (response.error.code !== 'InvalidToken') {
346-
this.root_store.common.setServicesError({
347-
type: response.msg_type,
348-
...response.error,
349-
});
351+
this.root_store.common.setServicesError(
352+
{
353+
type: response.msg_type,
354+
...response.error,
355+
},
356+
// Temporary switching off old snackbar for dTrader-V2
357+
isDtraderV2Enabled(this.root_store.ui.is_mobile)
358+
);
350359
}
351360
} else if (!response.error && response.sell) {
352361
// update contract store sell info after sell

packages/stores/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ type TCommonStoreError = {
661661
type?: string;
662662
};
663663

664-
type TCommonStoreServicesError = {
664+
export type TCommonStoreServicesError = {
665665
code?: string;
666666
message?: string;
667667
type?: string;

packages/trader/src/AppV2/Components/PurchaseButton/__tests__/purchase-button-content.spec.tsx

-12
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,4 @@ describe('PurchaseButtonContent', () => {
7171

7272
expect(container).toBeEmptyDOMElement();
7373
});
74-
75-
it('should render error text as button content if error is not falsy', () => {
76-
render(<PurchaseButtonContent {...mock_props} error='Mock error text' />);
77-
78-
expect(screen.getByText('Mock error text')).toBeInTheDocument();
79-
});
80-
81-
it('should render error text as button content even if error is not falsy, but has_no_button_content === true', () => {
82-
render(<PurchaseButtonContent {...mock_props} error='Mock error text' has_no_button_content />);
83-
84-
expect(screen.getByText('Mock error text')).toBeInTheDocument();
85-
});
8674
});

packages/trader/src/AppV2/Components/PurchaseButton/purchase-button-content.tsx

+9-13
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { getLocalizedBasis } from '@deriv/shared';
66
import { Money } from '@deriv/components';
77

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

1817
const PurchaseButtonContent = ({
1918
currency,
20-
error,
2119
has_open_accu_contract,
2220
has_no_button_content,
2321
info,
@@ -26,7 +24,7 @@ const PurchaseButtonContent = ({
2624
is_vanilla,
2725
is_reverse,
2826
}: TPurchaseButtonContent) => {
29-
if (has_no_button_content && !error) return null;
27+
if (has_no_button_content) return null;
3028

3129
const { payout, stake } = getLocalizedBasis();
3230

@@ -50,30 +48,28 @@ const PurchaseButtonContent = ({
5048
)}
5149
data-testid='dt_purchase_button_wrapper'
5250
>
53-
{(!is_content_empty || error) && (
51+
{!is_content_empty && (
5452
<React.Fragment>
5553
<CaptionText
5654
as='span'
5755
size='sm'
5856
className={clsx(!has_open_accu_contract && 'purchase-button__information__item')}
5957
color='quill-typography__color--prominent'
6058
>
61-
{!error && text_basis}
59+
{text_basis}
6260
</CaptionText>
6361
<CaptionText
6462
as='span'
6563
size='sm'
6664
className={clsx(!has_open_accu_contract && 'purchase-button__information__item')}
6765
color='quill-typography__color--prominent'
6866
>
69-
{error || (
70-
<Money
71-
amount={amount}
72-
currency={currency}
73-
should_format={!is_turbos && !is_vanilla}
74-
show_currency
75-
/>
76-
)}
67+
<Money
68+
amount={amount}
69+
currency={currency}
70+
should_format={!is_turbos && !is_vanilla}
71+
show_currency
72+
/>
7773
</CaptionText>
7874
</React.Fragment>
7975
)}

packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.tsx

+16-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react';
22
import clsx from 'clsx';
33
import { observer } from 'mobx-react';
4-
import { Localize } from '@deriv/translations';
54
import { useStore } from '@deriv/stores';
65
import { useTraderStore } from 'Stores/useTraderStores';
76
import { Button, useNotifications } from '@deriv-com/quill-ui';
@@ -14,22 +13,26 @@ import {
1413
isAccumulatorContract,
1514
isOpen,
1615
isValidToSell,
16+
MT5_ACCOUNT_STATUS,
1717
} from '@deriv/shared';
18+
import { useMFAccountStatus } from '@deriv/hooks';
1819
import PurchaseButtonContent from './purchase-button-content';
1920
import { getTradeTypeTabsList } from 'AppV2/Utils/trade-params-utils';
2021
import { StandaloneStopwatchRegularIcon } from '@deriv/quill-icons';
2122
import { CSSTransition } from 'react-transition-group';
2223
import { getDisplayedContractTypes } from 'AppV2/Utils/trade-types-utils';
2324
import { usePrevious } from '@deriv/components';
25+
import { checkIsServiceModalError } from 'AppV2/Utils/layout-utils';
2426

2527
const PurchaseButton = observer(() => {
2628
const [loading_button_index, setLoadingButtonIndex] = React.useState<number | null>(null);
2729
const { isMobile } = useDevice();
2830
const { addBanner } = useNotifications();
2931
const {
30-
contract_replay: { is_market_closed },
3132
portfolio: { all_positions, onClickSell, open_accu_contract, active_positions },
3233
client: { is_logged_in },
34+
common: { services_error },
35+
ui: { is_mf_verification_pending_modal_visible, setIsMFVericationPendingModal },
3336
} = useStore();
3437
const {
3538
contract_type,
@@ -44,6 +47,7 @@ const PurchaseButton = observer(() => {
4447
is_vanilla_fx,
4548
is_vanilla,
4649
proposal_info,
50+
purchase_info,
4751
onPurchaseV2,
4852
symbol,
4953
trade_type_tab,
@@ -58,6 +62,7 @@ const PurchaseButton = observer(() => {
5862
({ contract_info, type }) => isAccumulatorContract(type) && contract_info.underlying === symbol
5963
)
6064
);
65+
const mf_account_status = useMFAccountStatus();
6166

6267
/*TODO: add error handling when design will be ready. validation_errors can be taken from useTraderStore
6368
const hasError = (info: TTradeStore['proposal_info'][string]) => {
@@ -94,7 +99,7 @@ const PurchaseButton = observer(() => {
9499
const current_stake =
95100
(is_valid_to_sell && active_accu_contract && getIndicativePrice(active_accu_contract.contract_info)) || null;
96101
const cardLabels = getCardLabelsV2();
97-
102+
const is_modal_error = checkIsServiceModalError({ services_error, is_mf_verification_pending_modal_visible });
98103
const is_accu_sell_disabled = !is_valid_to_sell || active_accu_contract?.is_sell_requested;
99104

100105
const getButtonType = (index: number, trade_type: string) => {
@@ -148,28 +153,8 @@ const PurchaseButton = observer(() => {
148153
const info = proposal_info?.[trade_type] || {};
149154
const is_single_button = contract_types.length === 1;
150155
const is_loading = loading_button_index === index;
151-
const is_disabled = !is_trade_enabled_v2 || info.has_error;
152-
153-
const getErrorMessage = () => {
154-
if (['amount', 'stake'].includes(info.error_field ?? '')) {
155-
return <Localize i18n_default_text='Invalid stake' />;
156-
}
157-
158-
/* TODO: stop using error text for is_max_payout_exceeded after validation_params are added to proposal API (both success & error response):
159-
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: */
160-
const float_number_search_regex = /\d+(\.\d+)?/g;
161-
const is_max_payout_exceeded =
162-
info.has_error && info.message?.match(float_number_search_regex)?.length === 3;
163-
164-
if (is_max_payout_exceeded) {
165-
return <Localize i18n_default_text='Exceeds max payout' />;
166-
}
167-
168-
const api_error = info.has_error && !is_market_closed && !!info.message ? info.message : '';
169-
return api_error;
170-
};
171-
172-
const error_message = getErrorMessage();
156+
const is_disabled =
157+
!is_trade_enabled_v2 || info.has_error || (!!purchase_info.error && !is_modal_error);
173158

174159
return (
175160
<React.Fragment key={trade_type}>
@@ -190,14 +175,17 @@ const PurchaseButton = observer(() => {
190175
isOpaque
191176
disabled={is_disabled && !is_loading}
192177
onClick={() => {
193-
setLoadingButtonIndex(index);
194-
onPurchaseV2(trade_type, isMobile, addNotificationBannerCallback);
178+
if (is_multiplier && mf_account_status === MT5_ACCOUNT_STATUS.PENDING) {
179+
setIsMFVericationPendingModal(true);
180+
} else {
181+
setLoadingButtonIndex(index);
182+
onPurchaseV2(trade_type, isMobile, addNotificationBannerCallback);
183+
}
195184
}}
196185
>
197186
{!is_loading && (
198187
<PurchaseButtonContent
199188
{...purchase_button_content_props}
200-
error={error_message}
201189
has_no_button_content={has_no_button_content}
202190
info={info}
203191
is_reverse={!!index}

0 commit comments

Comments
 (0)