From 8eb09b317da59c6f176972fefbb86cc5223f0a56 Mon Sep 17 00:00:00 2001 From: aforaleka Date: Wed, 25 Sep 2024 16:02:35 -0400 Subject: [PATCH 1/2] add market close all positions --- src/constants/abacus.ts | 2 + src/constants/dialogs.ts | 2 + src/constants/trade.ts | 7 ++ src/hooks/useNotificationTypes.tsx | 28 +++++ src/hooks/useSubaccount.tsx | 32 +++++ src/layout/DialogManager.tsx | 4 + src/lib/abacus/index.ts | 10 ++ src/state/localOrders.ts | 47 +++++++- src/state/localOrdersSelectors.ts | 6 + .../CloseAllPositionsConfirmationDialog.tsx | 34 ++++++ .../CloseAllPositionsNotification.tsx | 114 ++++++++++++++++++ .../CancelOrClearAllOrdersButton.tsx | 2 +- src/views/tables/PositionsTable.tsx | 20 +-- .../CloseAllPositionsButton.tsx | 42 +++++++ 14 files changed, 330 insertions(+), 20 deletions(-) create mode 100644 src/views/dialogs/CloseAllPositionsConfirmationDialog.tsx create mode 100644 src/views/notifications/CloseAllPositionsNotification.tsx create mode 100644 src/views/tables/PositionsTable/CloseAllPositionsButton.tsx diff --git a/src/constants/abacus.ts b/src/constants/abacus.ts index 978e18d04..fd39947e6 100644 --- a/src/constants/abacus.ts +++ b/src/constants/abacus.ts @@ -209,6 +209,8 @@ export const AbacusMarginMode = Abacus.exchange.dydx.abacus.output.input.MarginM export type HumanReadablePlaceOrderPayload = Abacus.exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload; +export type HumanReadableCloseAllPositionsPayload = + Abacus.exchange.dydx.abacus.state.manager.HumanReadableCloseAllPositionsPayload; export type HumanReadableCancelOrderPayload = Abacus.exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload; export type HumanReadableTriggerOrdersPayload = diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts index 2cf05c9ad..57c12542c 100644 --- a/src/constants/dialogs.ts +++ b/src/constants/dialogs.ts @@ -17,6 +17,7 @@ export type AdjustIsolatedMarginDialogProps = { }; export type AdjustTargetLeverageDialogProps = {}; export type ClosePositionDialogProps = {}; +export type CloseAllPositionsConfirmationDialogProps = {}; export type CancelAllOrdersConfirmationDialogProps = { marketId?: string }; export type CancelPendingOrdersDialogProps = { marketId: string }; export type ComplianceConfigDialogProps = {}; @@ -101,6 +102,7 @@ export const DialogTypes = unionize( AdjustTargetLeverage: ofType(), CancelAllOrdersConfirmation: ofType(), CancelPendingOrders: ofType(), + CloseAllPositionsConfirmation: ofType(), ClosePosition: ofType(), ComplianceConfig: ofType(), ConfirmPendingDeposit: ofType(), diff --git a/src/constants/trade.ts b/src/constants/trade.ts index 6d2141ef7..a32add44c 100644 --- a/src/constants/trade.ts +++ b/src/constants/trade.ts @@ -195,3 +195,10 @@ export type LocalCancelAllData = { failedOrderIds?: string[]; errorParams?: ErrorParams; }; + +export type LocalCloseAllPositionsData = { + submittedOrderClientIds: string[]; + filledOrderClientIds: string[]; + failedOrderClientIds: string[]; + errorParams?: ErrorParams; +}; diff --git a/src/hooks/useNotificationTypes.tsx b/src/hooks/useNotificationTypes.tsx index 8b698c229..4740ef27b 100644 --- a/src/hooks/useNotificationTypes.tsx +++ b/src/hooks/useNotificationTypes.tsx @@ -44,6 +44,7 @@ import { Output, OutputType } from '@/components/Output'; // eslint-disable-next-line import/no-cycle import { BlockRewardNotification } from '@/views/notifications/BlockRewardNotification'; import { CancelAllNotification } from '@/views/notifications/CancelAllNotification'; +import { CloseAllPositionsNotification } from '@/views/notifications/CloseAllPositionsNotification'; import { IncentiveSeasonDistributionNotification } from '@/views/notifications/IncentiveSeasonDistributionNotification'; import { MarketLaunchTrumpwinNotification } from '@/views/notifications/MarketLaunchTrumpwinNotification'; import { OrderCancelNotification } from '@/views/notifications/OrderCancelNotification'; @@ -58,6 +59,7 @@ import { openDialog } from '@/state/dialogs'; import { getLocalCancelAlls, getLocalCancelOrders, + getLocalCloseAllPositions, getLocalPlaceOrders, } from '@/state/localOrdersSelectors'; import { getAbacusNotifications, getCustomNotifications } from '@/state/notificationsSelectors'; @@ -664,6 +666,7 @@ export const notificationTypes: NotificationTypeConfig[] = [ const localPlaceOrders = useAppSelector(getLocalPlaceOrders, shallowEqual); const localCancelOrders = useAppSelector(getLocalCancelOrders, shallowEqual); const localCancelAlls = useAppSelector(getLocalCancelAlls, shallowEqual); + const localCloseAllPositions = useAppSelector(getLocalCloseAllPositions, shallowEqual); const allOrders = useAppSelector(getSubaccountOrders, shallowEqual); const stringGetter = useStringGetter(); @@ -754,6 +757,31 @@ export const notificationTypes: NotificationTypeConfig[] = [ ); } }, [localCancelAlls]); + + useEffect(() => { + if (!localCloseAllPositions) return; + const localCloseAllKey = localCloseAllPositions.submittedOrderClientIds.join('-'); + // eslint-disable-next-line no-restricted-syntax + trigger( + localCloseAllKey, + { + icon: null, + title: stringGetter({ key: STRING_KEYS.CLOSE_ALL_POSITIONS }), + toastSensitivity: 'background', + groupKey: localCloseAllKey, + toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS, + renderCustomBody: ({ isToast, notification }) => ( + + ), + }, + [localCloseAllPositions], + true + ); + }, [localCloseAllPositions]); }, useNotificationAction: () => { const dispatch = useAppDispatch(); diff --git a/src/hooks/useSubaccount.tsx b/src/hooks/useSubaccount.tsx index 884d2075a2..5b69bb192 100644 --- a/src/hooks/useSubaccount.tsx +++ b/src/hooks/useSubaccount.tsx @@ -40,6 +40,7 @@ import { cancelOrderFailed, cancelOrderSubmitted, clearLocalOrders, + closeAllPositionsSubmitted, placeOrderFailed, placeOrderSubmitted, } from '@/state/localOrders'; @@ -601,6 +602,36 @@ const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWall [dispatch, orders] ); + const closeAllPositions = useCallback(() => { + // this is for each single close position / place order transaction + const callback = ( + success: boolean, + parsingError?: Nullable, + data?: Nullable + ) => { + if (!success) { + const errorParams = getValidErrorParamsFromParsingError(parsingError); + if (data?.clientId !== undefined) { + dispatch( + placeOrderFailed({ + clientId: data.clientId, + errorParams, + }) + ); + } + } + }; + + const payload = abacusStateManager.closeAllPositions(callback); + if (payload) { + dispatch( + closeAllPositionsSubmitted( + payload.payloads.toArray().map((orderPayload) => orderPayload.clientId) + ) + ); + } + }, [dispatch]); + // ------ Trigger Orders Methods ------ // const placeTriggerOrders = useCallback( async ({ @@ -892,6 +923,7 @@ const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWall // Trading Methods placeOrder, closePosition, + closeAllPositions, cancelOrder, cancelAllOrders, placeTriggerOrders, diff --git a/src/layout/DialogManager.tsx b/src/layout/DialogManager.tsx index e62a19ae4..d89cb390e 100644 --- a/src/layout/DialogManager.tsx +++ b/src/layout/DialogManager.tsx @@ -6,6 +6,7 @@ import { AdjustIsolatedMarginDialog } from '@/views/dialogs/AdjustIsolatedMargin import { AdjustTargetLeverageDialog } from '@/views/dialogs/AdjustTargetLeverageDialog'; import { CancelAllOrdersConfirmationDialog } from '@/views/dialogs/CancelAllOrdersConfirmationDialog'; import { CancelPendingOrdersDialog } from '@/views/dialogs/CancelPendingOrdersDialog'; +import { CloseAllPositionsConfirmationDialog } from '@/views/dialogs/CloseAllPositionsConfirmationDialog'; import { ClosePositionDialog } from '@/views/dialogs/ClosePositionDialog'; import { ComplianceConfigDialog } from '@/views/dialogs/ComplianceConfigDialog'; import { ConfirmPendingDepositDialog } from '@/views/dialogs/ConfirmPendingDepositDialog'; @@ -69,6 +70,9 @@ export const DialogManager = () => { AdjustIsolatedMargin: (args) => , AdjustTargetLeverage: (args) => , ClosePosition: (args) => , + CloseAllPositionsConfirmation: (args) => ( + + ), CancelAllOrdersConfirmation: (args) => ( ), diff --git a/src/lib/abacus/index.ts b/src/lib/abacus/index.ts index 6b1eaf7df..2ca3f8455 100644 --- a/src/lib/abacus/index.ts +++ b/src/lib/abacus/index.ts @@ -8,6 +8,7 @@ import type { HistoricalTradingRewardsPeriod, HistoricalTradingRewardsPeriods, HumanReadableCancelOrderPayload, + HumanReadableCloseAllPositionsPayload, HumanReadablePlaceOrderPayload, HumanReadableSubaccountTransferPayload, HumanReadableTriggerOrdersPayload, @@ -402,6 +403,15 @@ class AbacusStateManager { ) => void ): Nullable => this.stateManager.commitClosePosition(callback); + closeAllPositions = ( + callback: ( + success: boolean, + parsingError: Nullable, + data: Nullable + ) => void + ): Nullable => + this.stateManager.closeAllPositions(callback); + cancelOrder = ( orderId: string, callback: ( diff --git a/src/state/localOrders.ts b/src/state/localOrders.ts index fd56dc494..b686c5d39 100644 --- a/src/state/localOrders.ts +++ b/src/state/localOrders.ts @@ -2,13 +2,14 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import _ from 'lodash'; -import { Nullable, SubaccountFill, SubaccountOrder } from '@/constants/abacus'; +import { AbacusOrderStatus, Nullable, SubaccountFill, SubaccountOrder } from '@/constants/abacus'; import { DEFAULT_SOMETHING_WENT_WRONG_ERROR_PARAMS, ErrorParams } from '@/constants/errors'; import { CANCEL_ALL_ORDERS_KEY, CancelOrderStatuses, LocalCancelAllData, LocalCancelOrderData, + LocalCloseAllPositionsData, LocalPlaceOrderData, PlaceOrderStatuses, TradeTypes, @@ -21,6 +22,7 @@ export interface LocalOrdersState { localPlaceOrders: LocalPlaceOrderData[]; localCancelOrders: LocalCancelOrderData[]; localCancelAlls: Record; + localCloseAllPositions?: LocalCloseAllPositionsData; latestOrder?: Nullable; } @@ -28,6 +30,7 @@ const initialState: LocalOrdersState = { localPlaceOrders: [], localCancelOrders: [], localCancelAlls: {}, + localCloseAllPositions: undefined, latestOrder: undefined, }; @@ -39,12 +42,19 @@ export const localOrdersSlice = createSlice({ return initialState; }, updateOrders: (state, action: PayloadAction) => { - const canceledOrderIdsInPayload = action.payload - .filter((order) => isOrderStatusCanceled(order.status)) - .map((order) => order.id); + const { payload: orders } = action; + let { localCloseAllPositions } = state; + const canceledOrders = orders.filter((order) => isOrderStatusCanceled(order.status)); + const filledOrderClientIds = orders + .filter((order) => order.status === AbacusOrderStatus.Filled) + .map((order) => order.clientId) + .filter(isTruthy); - if (!canceledOrderIdsInPayload) return state; + // no relevant cancel or filled orders + if (!canceledOrders.length && (!localCloseAllPositions || !filledOrderClientIds.length)) + return state; + const canceledOrderIdsInPayload = canceledOrders.map((order) => order.id); // ignore locally canceled orders since it's intentional and already handled // by local cancel tracking and notification const isOrderCanceledByBackend = (orderId: string) => @@ -56,6 +66,23 @@ export const localOrdersSlice = createSlice({ return _.uniq([...(batch.canceledOrderIds ?? []), ...newCanceledOrderIds]); }; + if (localCloseAllPositions) { + localCloseAllPositions = { + ...localCloseAllPositions, + filledOrderClientIds: _.uniq([ + ..._.intersection(localCloseAllPositions.submittedOrderClientIds, filledOrderClientIds), + ...localCloseAllPositions.filledOrderClientIds, + ]), + failedOrderClientIds: _.uniq([ + ..._.intersection( + localCloseAllPositions.submittedOrderClientIds, + canceledOrders.map((order) => order.clientId)?.filter(isTruthy) + ), + ...localCloseAllPositions.failedOrderClientIds, + ]), + }; + } + return { ...state, localPlaceOrders: state.localPlaceOrders.map((order) => @@ -77,6 +104,7 @@ export const localOrdersSlice = createSlice({ ...batch, canceledOrderIds: getNewCanceledOrderIds(batch), })), + localCloseAllPositions, }; }, updateFilledOrders: (state, action: PayloadAction) => { @@ -212,6 +240,13 @@ export const localOrdersSlice = createSlice({ updateCancelAllOrderIds(state, order.id, 'failedOrderIds', order.marketId); if (errorParams) cancelOrderFailed({ orderId: order.id, errorParams }); }, + closeAllPositionsSubmitted: (state, action: PayloadAction) => { + state.localCloseAllPositions = { + submittedOrderClientIds: action.payload, + filledOrderClientIds: [], + failedOrderClientIds: [], + }; + }, }, }); @@ -232,6 +267,8 @@ export const { cancelAllSubmitted, cancelAllOrderFailed, + + closeAllPositionsSubmitted, } = localOrdersSlice.actions; // helper functions diff --git a/src/state/localOrdersSelectors.ts b/src/state/localOrdersSelectors.ts index 81d7eff07..0262dcac9 100644 --- a/src/state/localOrdersSelectors.ts +++ b/src/state/localOrdersSelectors.ts @@ -32,6 +32,12 @@ export const getLocalCancelOrders = (state: RootState) => state.localOrders.loca */ export const getLocalCancelAlls = (state: RootState) => state.localOrders.localCancelAlls; +/** + * @returns the local close all positions data for the current FE session + */ +export const getLocalCloseAllPositions = (state: RootState) => + state.localOrders.localCloseAllPositions; + /** * @returns whether the subaccount has uncommitted orders (local orders that are only submitted and not placed) */ diff --git a/src/views/dialogs/CloseAllPositionsConfirmationDialog.tsx b/src/views/dialogs/CloseAllPositionsConfirmationDialog.tsx new file mode 100644 index 000000000..c489956c9 --- /dev/null +++ b/src/views/dialogs/CloseAllPositionsConfirmationDialog.tsx @@ -0,0 +1,34 @@ +import { useCallback } from 'react'; + +import { ButtonAction, ButtonType } from '@/constants/buttons'; +import { CloseAllPositionsConfirmationDialogProps, DialogProps } from '@/constants/dialogs'; +import { STRING_KEYS } from '@/constants/localization'; + +import { useStringGetter } from '@/hooks/useStringGetter'; +import { useSubaccount } from '@/hooks/useSubaccount'; + +import { Button } from '@/components/Button'; +import { Dialog } from '@/components/Dialog'; + +export const CloseAllPositionsConfirmationDialog = ({ + setIsOpen, +}: DialogProps) => { + const stringGetter = useStringGetter(); + const { closeAllPositions } = useSubaccount(); + + const onSubmit = useCallback(() => { + closeAllPositions(); + setIsOpen?.(false); + }, [closeAllPositions, setIsOpen]); + + return ( + +
+
{stringGetter({ key: STRING_KEYS.ARE_YOU_SURE_CLOSE_MULTI_POSITION_ALL })}
+ +
+
+ ); +}; diff --git a/src/views/notifications/CloseAllPositionsNotification.tsx b/src/views/notifications/CloseAllPositionsNotification.tsx new file mode 100644 index 000000000..0ef95df09 --- /dev/null +++ b/src/views/notifications/CloseAllPositionsNotification.tsx @@ -0,0 +1,114 @@ +import styled from 'styled-components'; + +import { AbacusOrderStatus } from '@/constants/abacus'; +import { ButtonAction, ButtonSize } from '@/constants/buttons'; +import { STRING_KEYS } from '@/constants/localization'; +import { LocalCloseAllPositionsData } from '@/constants/trade'; + +import { useStringGetter } from '@/hooks/useStringGetter'; +import { useSubaccount } from '@/hooks/useSubaccount'; + +import { Button } from '@/components/Button'; +import { Details } from '@/components/Details'; +import { LoadingSpinner } from '@/components/Loading/LoadingSpinner'; +// eslint-disable-next-line import/no-cycle +import { Notification, NotificationProps } from '@/components/Notification'; + +import { OrderStatusIcon } from '../OrderStatusIcon'; + +type ElementProps = { + localCloseAllPositions: LocalCloseAllPositionsData; +}; + +export const CloseAllPositionsNotification = ({ + isToast, + localCloseAllPositions, + notification, +}: NotificationProps & ElementProps) => { + const stringGetter = useStringGetter(); + const { closeAllPositions } = useSubaccount(); + + const numPositions = localCloseAllPositions.submittedOrderClientIds.length; + const numClosed = localCloseAllPositions.filledOrderClientIds.length; + const numFailed = localCloseAllPositions.failedOrderClientIds.length; + + const isPending = numClosed + numFailed < numPositions; + const allFilled = numClosed === numPositions; + const allCanceled = numFailed === numPositions; + + let closePositionsStatus = ; + if (!isPending) { + let [statusStringKey, statusForIcon] = [ + STRING_KEYS.PARTIALLY_FILLED, + AbacusOrderStatus.PartiallyCanceled.rawValue, + ]; + if (allFilled) { + [statusStringKey, statusForIcon] = [ + STRING_KEYS.ORDER_FILLED, + AbacusOrderStatus.Filled.rawValue, + ]; + } else if (allCanceled) { + [statusStringKey, statusForIcon] = [STRING_KEYS.CANCELED, AbacusOrderStatus.Pending.rawValue]; + } + + closePositionsStatus = ( + + {stringGetter({ key: statusStringKey })} + + + ); + } + + const showTryAgain = !isPending && numFailed > 0; + const tryAgainAction = ( + + ); + + const customContent = ( + <$Details + items={[ + { + key: 'positions-closed', + label: ( + {stringGetter({ key: STRING_KEYS.POSITIONS_CLOSED })} + ), + value: ( + + <$Highlighted>{numClosed} / {numPositions} + + ), + }, + ]} + /> + ); + + return ( + + ); +}; + +const $Details = styled(Details)` + --details-item-height: 1rem; + + div { + padding: 0.25rem 0 0; + } +`; + +const $Highlighted = styled.span` + color: var(--color-text-2); +`; diff --git a/src/views/tables/OrdersTable/CancelOrClearAllOrdersButton.tsx b/src/views/tables/OrdersTable/CancelOrClearAllOrdersButton.tsx index f3684921b..4c60d8e8d 100644 --- a/src/views/tables/OrdersTable/CancelOrClearAllOrdersButton.tsx +++ b/src/views/tables/OrdersTable/CancelOrClearAllOrdersButton.tsx @@ -46,7 +46,7 @@ export const CancelOrClearAllOrdersButton = ({ marketId }: ElementProps) => { }; const $ActionTextButton = styled(Button)<{ isHighlighted?: boolean }>` - --button-textColor: ${({ isHighlighted }) => (isHighlighted ? 'var(--color-accent)' : 'initial')}; + --button-textColor: ${({ isHighlighted }) => (isHighlighted ? 'var(--color-red)' : 'initial')}; --button-height: var(--item-height); --button-padding: 0 0.25rem; --button-backgroundColor: transparent; diff --git a/src/views/tables/PositionsTable.tsx b/src/views/tables/PositionsTable.tsx index afb204949..791929332 100644 --- a/src/views/tables/PositionsTable.tsx +++ b/src/views/tables/PositionsTable.tsx @@ -19,7 +19,6 @@ import { PositionSide } from '@/constants/trade'; import { MediaQueryKeys } from '@/hooks/useBreakpoints'; import { useEnvFeatures } from '@/hooks/useEnvFeatures'; -import { useShouldShowTriggers } from '@/hooks/useShouldShowTriggers'; import { useStringGetter } from '@/hooks/useStringGetter'; import { tradeViewMixins } from '@/styles/tradeViewMixins'; @@ -45,6 +44,7 @@ import { safeAssign } from '@/lib/objectHelpers'; import { getMarginModeFromSubaccountNumber, getPositionMargin } from '@/lib/tradeData'; import { orEmptyRecord } from '@/lib/typeUtils'; +import { CloseAllPositionsButton } from './PositionsTable/CloseAllPositionsButton'; import { PositionsActionsCell } from './PositionsTable/PositionsActionsCell'; import { PositionsMarginCell } from './PositionsTable/PositionsMarginCell'; import { PositionsTriggersCell } from './PositionsTable/PositionsTriggersCell'; @@ -82,16 +82,16 @@ const getPositionsTableColumnDef = ({ width, isAccountViewOnly, showClosePositionAction, - shouldRenderTriggers, navigateToOrders, + isSinglePosition, }: { key: PositionsTableColumnKey; stringGetter: StringGetterFunction; width?: ColumnSize; isAccountViewOnly: boolean; showClosePositionAction: boolean; - shouldRenderTriggers: boolean; navigateToOrders: (market: string) => void; + isSinglePosition: boolean; }) => ({ width, ...( @@ -342,14 +342,7 @@ const getPositionsTableColumnDef = ({ }, [PositionsTableColumnKey.Actions]: { columnKey: 'actions', - label: stringGetter({ - key: - showClosePositionAction && shouldRenderTriggers - ? STRING_KEYS.ACTIONS - : showClosePositionAction - ? STRING_KEYS.CLOSE - : STRING_KEYS.ACTION, - }), + label: showClosePositionAction && !isSinglePosition ? : '', isActionable: true, allowsSorting: false, hideOnBreakpoint: MediaQueryKeys.isTablet, @@ -426,7 +419,6 @@ export const PositionsTable = ({ const isAccountViewOnly = useAppSelector(calculateIsAccountViewOnly); const perpetualMarkets = orEmptyRecord(useAppSelector(getPerpetualMarkets, shallowEqual)); const assets = orEmptyRecord(useAppSelector(getAssets, shallowEqual)); - const shouldRenderTriggers = useShouldShowTriggers(); const openPositions = useAppSelector(getExistingOpenPositions, shallowEqual) ?? EMPTY_ARR; const positions = useMemo(() => { @@ -479,7 +471,7 @@ export const PositionsTable = ({ return ( <$Table key={currentMarket ?? 'positions'} - label="Positions" + label={stringGetter({ key: STRING_KEYS.POSITIONS })} defaultSortDescriptor={{ column: 'market', direction: 'ascending', @@ -492,8 +484,8 @@ export const PositionsTable = ({ width: columnWidths?.[key], isAccountViewOnly, showClosePositionAction, - shouldRenderTriggers, navigateToOrders, + isSinglePosition: positionsData.length === 1, }) )} getRowKey={(row: PositionTableRow) => row.id} diff --git a/src/views/tables/PositionsTable/CloseAllPositionsButton.tsx b/src/views/tables/PositionsTable/CloseAllPositionsButton.tsx new file mode 100644 index 000000000..a6e30315f --- /dev/null +++ b/src/views/tables/PositionsTable/CloseAllPositionsButton.tsx @@ -0,0 +1,42 @@ +import { useCallback } from 'react'; + +import { useDispatch } from 'react-redux'; +import styled from 'styled-components'; + +import { ButtonAction, ButtonSize } from '@/constants/buttons'; +import { DialogTypes } from '@/constants/dialogs'; +import { STRING_KEYS } from '@/constants/localization'; + +import { useStringGetter } from '@/hooks/useStringGetter'; + +import { Button } from '@/components/Button'; + +import { openDialog } from '@/state/dialogs'; + +export const CloseAllPositionsButton = () => { + const stringGetter = useStringGetter(); + const dispatch = useDispatch(); + + const onCloseAllClick = useCallback(() => { + dispatch(openDialog(DialogTypes.CloseAllPositionsConfirmation())); + }, [dispatch]); + + return ( + <$ActionTextButton + action={ButtonAction.Primary} + size={ButtonSize.XSmall} + onClick={onCloseAllClick} + > + {stringGetter({ key: STRING_KEYS.CLOSE_ALL })} + + ); +}; + +const $ActionTextButton = styled(Button)` + --button-textColor: var(--color-red); + --button-height: var(--item-height); + --button-padding: 0 0.25rem; + --button-backgroundColor: transparent; + --button-border: none; + pointer-events: auto; +`; From 269b56f053718bc64e52b3dce9e6f2d975616875 Mon Sep 17 00:00:00 2001 From: aforaleka Date: Thu, 26 Sep 2024 17:53:16 -0400 Subject: [PATCH 2/2] nit --- .../notifications/CloseAllPositionsNotification.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/views/notifications/CloseAllPositionsNotification.tsx b/src/views/notifications/CloseAllPositionsNotification.tsx index 0ef95df09..9d9fee443 100644 --- a/src/views/notifications/CloseAllPositionsNotification.tsx +++ b/src/views/notifications/CloseAllPositionsNotification.tsx @@ -28,9 +28,12 @@ export const CloseAllPositionsNotification = ({ const stringGetter = useStringGetter(); const { closeAllPositions } = useSubaccount(); - const numPositions = localCloseAllPositions.submittedOrderClientIds.length; - const numClosed = localCloseAllPositions.filledOrderClientIds.length; - const numFailed = localCloseAllPositions.failedOrderClientIds.length; + const { submittedOrderClientIds, filledOrderClientIds, failedOrderClientIds } = + localCloseAllPositions; + + const numPositions = submittedOrderClientIds.length; + const numClosed = filledOrderClientIds.length; + const numFailed = failedOrderClientIds.length; const isPending = numClosed + numFailed < numPositions; const allFilled = numClosed === numPositions;