Skip to content

Commit 8192efd

Browse files
authored
fix: more specific market order treatment on simple order confirm screen (#1906)
1 parent ceabd2b commit 8192efd

File tree

1 file changed

+139
-66
lines changed

1 file changed

+139
-66
lines changed

src/views/dialogs/SimpleUiTradeDialog/SimpleTradeSteps.tsx

Lines changed: 139 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { useMemo } from 'react';
22

33
import { PlaceOrderPayload } from '@/bonsai/forms/triggers/types';
4+
import { getSimpleOrderStatus } from '@/bonsai/lib/subaccountUtils';
45
import { BonsaiHelpers } from '@/bonsai/ontology';
5-
import { OrderSide } from '@dydxprotocol/v4-client-js';
6+
import { OrderStatus } from '@/bonsai/types/summaryTypes';
7+
import { OrderSide, OrderType } from '@dydxprotocol/v4-client-js';
68

79
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
810
import { STRING_KEYS } from '@/constants/localization';
911
import { ORDER_TYPE_STRINGS, SimpleUiTradeDialogSteps } from '@/constants/trade';
10-
import { IndexerOrderSide } from '@/types/indexer/indexerApiGen';
12+
import { IndexerOrderSide, IndexerOrderType } from '@/types/indexer/indexerApiGen';
1113

1214
import { useAppSelectorWithArgs } from '@/hooks/useParameterizedSelector';
1315
import { useStringGetter } from '@/hooks/useStringGetter';
@@ -17,15 +19,17 @@ import { Icon, IconName } from '@/components/Icon';
1719
import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
1820
import { Output, OutputType } from '@/components/Output';
1921

20-
import { getOrderByClientId } from '@/state/accountSelectors';
22+
import { getAverageFillPriceForOrder, getOrderByClientId } from '@/state/accountSelectors';
2123
import { useAppSelector } from '@/state/appTypes';
2224

25+
import { assertNever } from '@/lib/assertNever';
2326
import { getDisplayableAssetFromTicker } from '@/lib/assetUtils';
27+
import { runFn } from '@/lib/do';
2428
import {
2529
getIndexerOrderTypeStringKey,
2630
getPositionSideStringKeyFromOrderSide,
2731
} from '@/lib/enumToStringKeyHelpers';
28-
import { MustBigNumber } from '@/lib/numbers';
32+
import { MustBigNumber, MustNumber } from '@/lib/numbers';
2933
import { orEmptyObj } from '@/lib/typeUtils';
3034

3135
export const SimpleTradeSteps = ({
@@ -43,60 +47,101 @@ export const SimpleTradeSteps = ({
4347
}) => {
4448
const stringGetter = useStringGetter();
4549
const orderFromClientId = orEmptyObj(useAppSelectorWithArgs(getOrderByClientId, clientId ?? ''));
50+
const averageFillPriceForOrder = useAppSelectorWithArgs(
51+
getAverageFillPriceForOrder,
52+
orderFromClientId.id
53+
);
4654

4755
const { stepSizeDecimals, tickSizeDecimals, stepSize } = orEmptyObj(
4856
useAppSelector(BonsaiHelpers.currentMarket.stableMarketInfo)
4957
);
5058

51-
const { side, price, marketId, typeString, sideString, sideColor, size } = useMemo(() => {
52-
if (currentStep === SimpleUiTradeDialogSteps.Submit) {
53-
const orderTypeKey = payload?.type && ORDER_TYPE_STRINGS[payload.type].orderTypeKey;
54-
const orderSideKey = payload?.side ? getPositionSideStringKeyFromOrderSide(payload.side) : '';
55-
56-
return {
57-
side: payload?.side,
58-
totalFilled: payload?.size,
59-
price: payload?.price,
60-
type: payload?.type,
61-
marketId: payload?.marketId,
62-
typeString: orderTypeKey && stringGetter({ key: orderTypeKey }),
63-
sideString: orderSideKey && stringGetter({ key: orderSideKey }),
64-
sideColor: {
65-
[OrderSide.BUY]: 'var(--color-positive)',
66-
[OrderSide.SELL]: 'var(--color-negative)',
67-
}[payload?.side ?? OrderSide.BUY],
68-
size: payload?.size ?? orderFromClientId.size,
69-
};
59+
const { effectiveCurrentStep, errorOverride, isMarketPartialFill } = runFn(() => {
60+
if (
61+
currentStep === SimpleUiTradeDialogSteps.Confirm &&
62+
orderFromClientId.type != null &&
63+
orderFromClientId.type === IndexerOrderType.MARKET &&
64+
orderFromClientId.status != null
65+
) {
66+
const status = getSimpleOrderStatus(orderFromClientId.status);
67+
if (status === OrderStatus.Open) {
68+
return { effectiveCurrentStep: SimpleUiTradeDialogSteps.Submit };
69+
}
70+
if (status === OrderStatus.Canceled) {
71+
if (MustNumber(orderFromClientId.totalFilled) === 0) {
72+
return {
73+
effectiveCurrentStep: SimpleUiTradeDialogSteps.Error,
74+
errorOverride: stringGetter({ key: STRING_KEYS.COULD_NOT_FILL }),
75+
};
76+
}
77+
return { effectiveCurrentStep: currentStep, isMarketPartialFill: true };
78+
}
79+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
80+
if (status === OrderStatus.Filled) {
81+
return { effectiveCurrentStep: currentStep };
82+
}
83+
assertNever(status);
7084
}
85+
return { effectiveCurrentStep: currentStep };
86+
});
7187

72-
if (currentStep === SimpleUiTradeDialogSteps.Confirm) {
73-
const orderTypeKey =
74-
orderFromClientId.type && getIndexerOrderTypeStringKey(orderFromClientId.type);
75-
const orderSideKey =
76-
orderFromClientId.side && getPositionSideStringKeyFromOrderSide(orderFromClientId.side);
77-
78-
return {
79-
side: orderFromClientId.side,
80-
totalFilled: orderFromClientId.totalFilled,
81-
price: orderFromClientId.price,
82-
type: orderFromClientId.type,
83-
marketId: orderFromClientId.marketId,
84-
typeString: orderTypeKey && stringGetter({ key: orderTypeKey }),
85-
sideString: orderSideKey && stringGetter({ key: orderSideKey }),
86-
sideColor: {
87-
[IndexerOrderSide.BUY]: 'var(--color-positive)',
88-
[IndexerOrderSide.SELL]: 'var(--color-negative)',
89-
}[orderFromClientId.side ?? IndexerOrderSide.BUY],
90-
size: orderFromClientId.size,
91-
};
92-
}
88+
const { side, price, marketId, typeString, sideString, sideColor, size, fillSize } =
89+
useMemo(() => {
90+
if (effectiveCurrentStep === SimpleUiTradeDialogSteps.Submit) {
91+
const orderTypeKey = payload?.type && ORDER_TYPE_STRINGS[payload.type].orderTypeKey;
92+
const orderSideKey = payload?.side
93+
? getPositionSideStringKeyFromOrderSide(payload.side)
94+
: '';
95+
96+
return {
97+
side: payload?.side,
98+
totalFilled: payload?.size,
99+
price: payload?.type === OrderType.MARKET ? null : payload?.price,
100+
type: payload?.type,
101+
marketId: payload?.marketId,
102+
typeString: orderTypeKey && stringGetter({ key: orderTypeKey }),
103+
sideString: orderSideKey && stringGetter({ key: orderSideKey }),
104+
sideColor: {
105+
[OrderSide.BUY]: 'var(--color-positive)',
106+
[OrderSide.SELL]: 'var(--color-negative)',
107+
}[payload?.side ?? OrderSide.BUY],
108+
size: payload?.size ?? orderFromClientId.size,
109+
fillSize: orderFromClientId.totalFilled,
110+
};
111+
}
112+
113+
if (effectiveCurrentStep === SimpleUiTradeDialogSteps.Confirm) {
114+
const orderTypeKey =
115+
orderFromClientId.type && getIndexerOrderTypeStringKey(orderFromClientId.type);
116+
const orderSideKey =
117+
orderFromClientId.side && getPositionSideStringKeyFromOrderSide(orderFromClientId.side);
118+
119+
return {
120+
side: orderFromClientId.side,
121+
totalFilled: orderFromClientId.totalFilled,
122+
price:
123+
orderFromClientId.type === IndexerOrderType.MARKET
124+
? averageFillPriceForOrder ?? null
125+
: orderFromClientId.price,
126+
type: orderFromClientId.type,
127+
marketId: orderFromClientId.marketId,
128+
typeString: orderTypeKey && stringGetter({ key: orderTypeKey }),
129+
sideString: orderSideKey && stringGetter({ key: orderSideKey }),
130+
sideColor: {
131+
[IndexerOrderSide.BUY]: 'var(--color-positive)',
132+
[IndexerOrderSide.SELL]: 'var(--color-negative)',
133+
}[orderFromClientId.side ?? IndexerOrderSide.BUY],
134+
size: orderFromClientId.size,
135+
fillSize: orderFromClientId.totalFilled,
136+
};
137+
}
93138

94-
return {};
95-
}, [currentStep, payload, orderFromClientId, stringGetter]);
139+
return {};
140+
}, [effectiveCurrentStep, averageFillPriceForOrder, payload, orderFromClientId, stringGetter]);
96141

97142
const renderContent = () => {
98-
if (currentStep === SimpleUiTradeDialogSteps.Error) {
99-
return <span tw="text-center text-color-text-2">{placeOrderError}</span>;
143+
if (effectiveCurrentStep === SimpleUiTradeDialogSteps.Error) {
144+
return <span tw="text-center text-color-text-2">{errorOverride ?? placeOrderError}</span>;
100145
}
101146

102147
if (!marketId || !side) return null;
@@ -109,39 +154,67 @@ export const SimpleTradeSteps = ({
109154
<div tw="flexColumn gap-0.5 text-center">
110155
<div tw="row gap-[0.5ch] text-color-text-2 font-extra-large-bold">
111156
<span css={{ color: sideColor }}>{sideString}</span>
112-
<Output
113-
type={canCompactNumber ? OutputType.CompactNumber : OutputType.Number}
114-
value={size}
115-
fractionDigits={stepSizeDecimals}
116-
/>
157+
<div tw="row items-end gap-0.125">
158+
<Output
159+
type={canCompactNumber ? OutputType.CompactNumber : OutputType.Number}
160+
value={fillSize?.gt(0) ? fillSize : size}
161+
fractionDigits={stepSizeDecimals}
162+
/>
163+
{size != null && fillSize?.gt(0) && fillSize.lt(size) ? (
164+
<div tw="row relative top-[-3px] gap-0.125 text-color-text-0 font-medium-bold">
165+
<span tw="block">/</span>
166+
<Output
167+
type={canCompactNumber ? OutputType.CompactNumber : OutputType.Number}
168+
value={size}
169+
fractionDigits={stepSizeDecimals}
170+
/>
171+
</div>
172+
) : undefined}
173+
</div>
117174
<span>{displayableAsset}</span>
118175
</div>
119176

120177
<span tw="font-medium-book">
121-
<span tw="text-color-text-2">{typeString}</span>
122-
<span tw="text-color-text-0"> @ </span>
123-
<Output
124-
withSubscript
125-
tw="inline text-color-text-1"
126-
type={OutputType.Fiat}
127-
value={price}
128-
fractionDigits={tickSizeDecimals}
129-
/>
178+
<span tw="text-color-text-0">{typeString}</span>
179+
{isMarketPartialFill && (
180+
<span tw="ml-0.25 text-color-text-0">
181+
{stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })}
182+
</span>
183+
)}
184+
{price != null && (
185+
<>
186+
<span tw="text-color-text-0"> @ </span>
187+
<Output
188+
withSubscript
189+
tw="inline text-color-text-1"
190+
type={OutputType.Fiat}
191+
value={price}
192+
fractionDigits={tickSizeDecimals}
193+
/>
194+
</>
195+
)}
130196
</span>
131197
</div>
132198
);
133199
};
134200

135201
const renderIcon = () => {
136-
if (currentStep === SimpleUiTradeDialogSteps.Error) {
202+
if (effectiveCurrentStep === SimpleUiTradeDialogSteps.Error) {
137203
return (
138204
<div tw="row size-4 justify-center rounded-[50%] bg-color-gradient-error">
139205
<Icon iconName={IconName.ErrorExclamation} tw="size-2 text-color-error" />
140206
</div>
141207
);
142208
}
143209

144-
if (currentStep === SimpleUiTradeDialogSteps.Confirm) {
210+
if (effectiveCurrentStep === SimpleUiTradeDialogSteps.Confirm) {
211+
if (isMarketPartialFill) {
212+
return (
213+
<div tw="row size-4 justify-center rounded-[50%] bg-color-gradient-warning">
214+
<Icon iconName={IconName.OrderPartiallyFilled} tw="size-2 text-color-warning" />
215+
</div>
216+
);
217+
}
145218
return (
146219
<div tw="row size-4 justify-center rounded-[50%] bg-color-gradient-success">
147220
<Icon iconName={IconName.Check} tw="size-2 text-color-success" />
@@ -153,7 +226,7 @@ export const SimpleTradeSteps = ({
153226
};
154227

155228
const renderGradient = () => {
156-
if (currentStep === SimpleUiTradeDialogSteps.Confirm) {
229+
if (effectiveCurrentStep === SimpleUiTradeDialogSteps.Confirm && !isMarketPartialFill) {
157230
const gradientStops =
158231
side === OrderSide.BUY
159232
? [
@@ -201,7 +274,7 @@ export const SimpleTradeSteps = ({
201274
state={{
202275
isLoading: currentStep === SimpleUiTradeDialogSteps.Submit,
203276
}}
204-
action={ButtonAction.SimplePrimary}
277+
action={ButtonAction.SimpleSecondary}
205278
size={ButtonSize.Large}
206279
onClick={onClose}
207280
>

0 commit comments

Comments
 (0)