11import { useMemo } from 'react' ;
22
33import { PlaceOrderPayload } from '@/bonsai/forms/triggers/types' ;
4+ import { getSimpleOrderStatus } from '@/bonsai/lib/subaccountUtils' ;
45import { 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
79import { ButtonAction , ButtonSize , ButtonType } from '@/constants/buttons' ;
810import { STRING_KEYS } from '@/constants/localization' ;
911import { ORDER_TYPE_STRINGS , SimpleUiTradeDialogSteps } from '@/constants/trade' ;
10- import { IndexerOrderSide } from '@/types/indexer/indexerApiGen' ;
12+ import { IndexerOrderSide , IndexerOrderType } from '@/types/indexer/indexerApiGen' ;
1113
1214import { useAppSelectorWithArgs } from '@/hooks/useParameterizedSelector' ;
1315import { useStringGetter } from '@/hooks/useStringGetter' ;
@@ -17,15 +19,17 @@ import { Icon, IconName } from '@/components/Icon';
1719import { LoadingSpinner } from '@/components/Loading/LoadingSpinner' ;
1820import { Output , OutputType } from '@/components/Output' ;
1921
20- import { getOrderByClientId } from '@/state/accountSelectors' ;
22+ import { getAverageFillPriceForOrder , getOrderByClientId } from '@/state/accountSelectors' ;
2123import { useAppSelector } from '@/state/appTypes' ;
2224
25+ import { assertNever } from '@/lib/assertNever' ;
2326import { getDisplayableAssetFromTicker } from '@/lib/assetUtils' ;
27+ import { runFn } from '@/lib/do' ;
2428import {
2529 getIndexerOrderTypeStringKey ,
2630 getPositionSideStringKeyFromOrderSide ,
2731} from '@/lib/enumToStringKeyHelpers' ;
28- import { MustBigNumber } from '@/lib/numbers' ;
32+ import { MustBigNumber , MustNumber } from '@/lib/numbers' ;
2933import { orEmptyObj } from '@/lib/typeUtils' ;
3034
3135export 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