Skip to content

Commit

Permalink
feat: order status [5/n] cancel order toasts (#488)
Browse files Browse the repository at this point in the history
* add cancel order notif

* cancel in order detail dialog

* fix short term order canceled becomes filled flow

* rename
  • Loading branch information
aforaleka authored May 2, 2024
1 parent 7f61de8 commit 9af68d3
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 60 deletions.
17 changes: 14 additions & 3 deletions src/constants/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,28 @@ export const CLEARED_SIZE_INPUTS = {
leverageInput: '',
};

export enum OrderSubmissionStatuses {
export enum PlaceOrderStatuses {
Submitted,
Placed,
Filled,
}

export type LocalOrderData = {
export enum CancelOrderStatuses {
Submitted,
Canceled,
}

export type LocalPlaceOrderData = {
marketId: string;
clientId: number;
orderId?: string;
orderType: TradeTypes;
submissionStatus: OrderSubmissionStatuses;
submissionStatus: PlaceOrderStatuses;
errorStringKey?: string;
};

export type LocalCancelOrderData = {
orderId: string;
submissionStatus: CancelOrderStatuses;
errorStringKey?: string;
};
53 changes: 44 additions & 9 deletions src/hooks/useNotificationTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,17 @@ import { Link } from '@/components/Link';
import { Output, OutputType } from '@/components/Output';
import { BlockRewardNotification } from '@/views/notifications/BlockRewardNotification';
import { IncentiveSeasonDistributionNotification } from '@/views/notifications/IncentiveSeasonDistributionNotification';
import { OrderCancelNotification } from '@/views/notifications/OrderCancelNotification';
import { OrderStatusNotification } from '@/views/notifications/OrderStatusNotification';
import { TradeNotification } from '@/views/notifications/TradeNotification';
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';
import { TriggerOrderNotification } from '@/views/notifications/TriggerOrderNotification';

import {
getLocalCancelOrders,
getLocalPlaceOrders,
getSubaccountFills,
getSubaccountOrders,
getSubmittedOrders,
} from '@/state/accountSelectors';
import { getSelectedDydxChainId } from '@/state/appSelectors';
import { openDialog } from '@/state/dialogs';
Expand Down Expand Up @@ -77,7 +79,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
const abacusNotifications = useSelector(getAbacusNotifications, isEqual);
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
const ordersById = groupBy(orders, 'id');
const localOrdersData = useSelector(getSubmittedOrders, shallowEqual);
const localPlaceOrders = useSelector(getLocalPlaceOrders, shallowEqual);

useEffect(() => {
for (const abacusNotif of abacusNotifications) {
Expand All @@ -95,7 +97,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
const order = ordersById[id]?.[0];
const clientId: number | undefined = order?.clientId ?? undefined;
const localOrderExists =
clientId && localOrdersData.some((order) => order.clientId === clientId);
clientId && localPlaceOrders.some((order) => order.clientId === clientId);

if (localOrderExists) return; // already handled by OrderStatusNotification

Expand Down Expand Up @@ -497,12 +499,14 @@ export const notificationTypes: NotificationTypeConfig[] = [
{
type: NotificationType.OrderStatus,
useTrigger: ({ trigger }) => {
const localOrdersData = useSelector(getSubmittedOrders, shallowEqual);
const localPlaceOrders = useSelector(getLocalPlaceOrders, shallowEqual);
const localCancelOrders = useSelector(getLocalCancelOrders, shallowEqual);
const allOrders = useSelector(getSubaccountOrders, shallowEqual);
const stringGetter = useStringGetter();

useEffect(() => {
for (const localOrder of localOrdersData) {
const key = localOrder.clientId.toString();
for (const localPlace of localPlaceOrders) {
const key = localPlace.clientId.toString();
trigger(
key,
{
Expand All @@ -514,16 +518,47 @@ export const notificationTypes: NotificationTypeConfig[] = [
renderCustomBody: ({ isToast, notification }) => (
<OrderStatusNotification
isToast={isToast}
localOrder={localOrder}
localOrder={localPlace}
notification={notification}
/>
),
},
[localOrder.submissionStatus],
[localPlace.submissionStatus],
true
);
}
}, [localOrdersData]);
}, [localPlaceOrders]);

useEffect(() => {
for (const localCancel of localCancelOrders) {
// ensure order exists
const existingOrder = allOrders?.find((order) => order.id === localCancel.orderId);
if (!existingOrder) return;

// share same notification with existing local order if exists
const key = (existingOrder.clientId ?? localCancel.orderId).toString();

trigger(
key,
{
icon: null,
title: stringGetter({ key: STRING_KEYS.ORDER_STATUS }),
toastSensitivity: 'background',
groupKey: key,
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
renderCustomBody: ({ isToast, notification }) => (
<OrderCancelNotification
isToast={isToast}
localCancel={localCancel}
notification={notification}
/>
),
},
[localCancel.submissionStatus],
true
);
}
}, [localCancelOrders]);
},
useNotificationAction: () => {
const dispatch = useDispatch();
Expand Down
21 changes: 16 additions & 5 deletions src/hooks/useSubaccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ import { TradeTypes } from '@/constants/trade';
import { DydxAddress } from '@/constants/wallets';

import {
cancelOrderConfirmed,
cancelOrderFailed,
cancelOrderSubmitted,
placeOrderFailed,
placeOrderSubmitted,
setHistoricalPnl,
setSubaccount,
submittedOrder,
submittedOrderFailed,
} from '@/state/account';
import { getBalances } from '@/state/accountSelectors';

Expand Down Expand Up @@ -371,7 +374,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo

if (data?.clientId !== undefined) {
dispatch(
submittedOrderFailed({
placeOrderFailed({
clientId: data.clientId,
errorStringKey: parsingError?.stringKey ?? STRING_KEYS.SOMETHING_WENT_WRONG,
})
Expand All @@ -390,7 +393,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo

if (placeOrderParams?.clientId) {
dispatch(
submittedOrder({
placeOrderSubmitted({
marketId: placeOrderParams.marketId,
clientId: placeOrderParams.clientId,
orderType: placeOrderParams.type as TradeTypes,
Expand All @@ -415,7 +418,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
);

const cancelOrder = useCallback(
async ({
({
orderId,
onError,
onSuccess,
Expand All @@ -426,12 +429,20 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
}) => {
const callback = (success: boolean, parsingError?: Nullable<ParsingError>) => {
if (success) {
dispatch(cancelOrderConfirmed(orderId));
onSuccess?.();
} else {
dispatch(
cancelOrderFailed({
orderId,
errorStringKey: parsingError?.stringKey ?? STRING_KEYS.SOMETHING_WENT_WRONG,
})
);
onError?.({ errorStringKey: parsingError?.stringKey });
}
};

dispatch(cancelOrderSubmitted(orderId));
abacusStateManager.cancelOrder(orderId, callback);
},
[subaccountClient]
Expand Down
15 changes: 11 additions & 4 deletions src/lib/abacus/dydxChainTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { DydxChainId, isTestnet } from '@/constants/networks';
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';

import { RootStore } from '@/state/_store';
import { submittedOrderTimeout } from '@/state/account';
import { placeOrderTimeout } from '@/state/account';
import { setInitializationError } from '@/state/app';

import { signComplianceSignature } from '../compliance';
Expand Down Expand Up @@ -212,7 +212,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
} = params || {};

setTimeout(() => {
this.store?.dispatch(submittedOrderTimeout(clientId));
this.store?.dispatch(placeOrderTimeout(clientId));
}, UNCOMMITTED_ORDER_TIMEOUT_MS);

// Place order
Expand Down Expand Up @@ -267,8 +267,15 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
throw new Error('Missing compositeClient or localWallet');
}

const { subaccountNumber, clientId, orderFlags, clobPairId, goodTilBlock, goodTilBlockTime } =
params ?? {};
const {
orderId,
subaccountNumber,
clientId,
orderFlags,
clobPairId,
goodTilBlock,
goodTilBlockTime,
} = params ?? {};

try {
const tx = await this.compositeClient?.cancelRawOrder(
Expand Down
79 changes: 58 additions & 21 deletions src/state/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ import type {
import { OnboardingGuard, OnboardingState } from '@/constants/account';
import { LocalStorageKey } from '@/constants/localStorage';
import { STRING_KEYS } from '@/constants/localization';
import { OrderSubmissionStatuses, type LocalOrderData, type TradeTypes } from '@/constants/trade';
import {
CancelOrderStatuses,
PlaceOrderStatuses,
type LocalCancelOrderData,
type LocalPlaceOrderData,
type TradeTypes,
} from '@/constants/trade';
import { WalletType } from '@/constants/wallets';

import { getLocalStorage } from '@/lib/localStorage';
Expand All @@ -46,7 +52,8 @@ export type AccountState = {
latestOrder?: Nullable<SubaccountOrder>;
historicalPnlPeriod?: HistoricalPnlPeriods;
uncommittedOrderClientIds: number[];
submittedOrders: LocalOrderData[];
localPlaceOrders: LocalPlaceOrderData[];
localCancelOrders: LocalCancelOrderData[];

restriction?: Nullable<UsageRestriction>;
compliance?: Compliance;
Expand Down Expand Up @@ -86,7 +93,8 @@ const initialState: AccountState = {
latestOrder: undefined,
uncommittedOrderClientIds: [],
historicalPnlPeriod: undefined,
submittedOrders: [],
localPlaceOrders: [],
localCancelOrders: [],

// Restriction
restriction: undefined,
Expand All @@ -111,18 +119,21 @@ export const accountSlice = createSlice({
...state,
fills: action.payload,
hasUnseenFillUpdates: state.hasUnseenFillUpdates || hasNewFillUpdates,
submittedOrders: hasNewFillUpdates
? state.submittedOrders.map((order) =>
order.submissionStatus < OrderSubmissionStatuses.Filled &&
localPlaceOrders: hasNewFillUpdates
? state.localPlaceOrders.map((order) =>
order.submissionStatus < PlaceOrderStatuses.Filled &&
order.orderId &&
filledOrderIds.includes(order.orderId)
? {
...order,
submissionStatus: OrderSubmissionStatuses.Filled,
submissionStatus: PlaceOrderStatuses.Filled,
}
: order
)
: state.submittedOrders,
: state.localPlaceOrders,
submittedCanceledOrders: hasNewFillUpdates
? state.localCancelOrders.filter((order) => !filledOrderIds.includes(order.orderId))
: state.localCancelOrders,
};
},
setFundingPayments: (state, action: PayloadAction<any>) => {
Expand All @@ -139,12 +150,12 @@ export const accountSlice = createSlice({
state.uncommittedOrderClientIds = state.uncommittedOrderClientIds.filter(
(uncommittedClientId) => uncommittedClientId !== clientId
);
state.submittedOrders = state.submittedOrders.map((order) =>
order.clientId === clientId && order.submissionStatus < OrderSubmissionStatuses.Placed
state.localPlaceOrders = state.localPlaceOrders.map((order) =>
order.clientId === clientId && order.submissionStatus < PlaceOrderStatuses.Placed
? {
...order,
orderId: id,
submissionStatus: OrderSubmissionStatuses.Placed,
submissionStatus: PlaceOrderStatuses.Placed,
}
: order
);
Expand Down Expand Up @@ -216,21 +227,21 @@ export const accountSlice = createSlice({
setTradingRewards: (state, action: PayloadAction<TradingRewards>) => {
state.tradingRewards = action.payload;
},
submittedOrder: (
placeOrderSubmitted: (
state,
action: PayloadAction<{ marketId: string; clientId: number; orderType: TradeTypes }>
) => {
state.submittedOrders.push({
state.localPlaceOrders.push({
...action.payload,
submissionStatus: OrderSubmissionStatuses.Submitted,
submissionStatus: PlaceOrderStatuses.Submitted,
});
state.uncommittedOrderClientIds.push(action.payload.clientId);
},
submittedOrderFailed: (
placeOrderFailed: (
state,
action: PayloadAction<{ clientId: number; errorStringKey: string }>
) => {
state.submittedOrders = state.submittedOrders.map((order) =>
state.localPlaceOrders = state.localPlaceOrders.map((order) =>
order.clientId === action.payload.clientId
? {
...order,
Expand All @@ -242,14 +253,37 @@ export const accountSlice = createSlice({
(id) => id !== action.payload.clientId
);
},
submittedOrderTimeout: (state, action: PayloadAction<number>) => {
placeOrderTimeout: (state, action: PayloadAction<number>) => {
if (state.uncommittedOrderClientIds.includes(action.payload)) {
submittedOrderFailed({
placeOrderFailed({
clientId: action.payload,
errorStringKey: STRING_KEYS.SOMETHING_WENT_WRONG,
});
}
},
cancelOrderSubmitted: (state, action: PayloadAction<string>) => {
state.localCancelOrders.push({
orderId: action.payload,
submissionStatus: CancelOrderStatuses.Submitted,
});
},
cancelOrderConfirmed: (state, action: PayloadAction<string>) => {
state.localCancelOrders = state.localCancelOrders.map((order) =>
order.orderId === action.payload
? { ...order, submissionStatus: CancelOrderStatuses.Canceled }
: order
);
},
cancelOrderFailed: (
state,
action: PayloadAction<{ orderId: string; errorStringKey: string }>
) => {
state.localCancelOrders.map((order) =>
order.orderId === action.payload.orderId
? { ...order, errorStringKey: action.payload.errorStringKey }
: order
);
},
},
});

Expand All @@ -271,7 +305,10 @@ export const {
setBalances,
setStakingBalances,
setTradingRewards,
submittedOrder,
submittedOrderFailed,
submittedOrderTimeout,
placeOrderSubmitted,
placeOrderFailed,
placeOrderTimeout,
cancelOrderSubmitted,
cancelOrderConfirmed,
cancelOrderFailed,
} = accountSlice.actions;
Loading

0 comments on commit 9af68d3

Please sign in to comment.