Skip to content

Commit

Permalink
add notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
aforaleka committed Sep 13, 2024
1 parent 2e82f75 commit 2916450
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 25 deletions.
10 changes: 10 additions & 0 deletions src/constants/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,14 @@ export type LocalCancelOrderData = {
orderId: string;
submissionStatus: CancelOrderStatuses;
errorParams?: ErrorParams;
isSubmittedThroughCancelAll?: boolean;
};

export const CANCEL_ALL_ORDERS_KEY = 'all';
export type LocalCancelAllData = {
key: string;
orderIds: string[];
canceledOrderIds?: string[];
failedOrderIds?: string[];
errorParams?: ErrorParams;
};
32 changes: 32 additions & 0 deletions src/hooks/useNotificationTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { Link } from '@/components/Link';
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 { IncentiveSeasonDistributionNotification } from '@/views/notifications/IncentiveSeasonDistributionNotification';
import { MarketLaunchTrumpwinNotification } from '@/views/notifications/MarketLaunchTrumpwinNotification';
import { OrderCancelNotification } from '@/views/notifications/OrderCancelNotification';
Expand All @@ -51,6 +52,7 @@ import { TradeNotification } from '@/views/notifications/TradeNotification';
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';

import {
getLocalCancelAlls,
getLocalCancelOrders,
getLocalPlaceOrders,
getSubaccountFills,
Expand Down Expand Up @@ -644,6 +646,8 @@ export const notificationTypes: NotificationTypeConfig[] = [
useTrigger: ({ trigger }) => {
const localPlaceOrders = useAppSelector(getLocalPlaceOrders, shallowEqual);
const localCancelOrders = useAppSelector(getLocalCancelOrders, shallowEqual);
const localCancelAlls = useAppSelector(getLocalCancelAlls, shallowEqual);

const allOrders = useAppSelector(getSubaccountOrders, shallowEqual);
const stringGetter = useStringGetter();

Expand Down Expand Up @@ -680,6 +684,9 @@ export const notificationTypes: NotificationTypeConfig[] = [
const existingOrder = allOrders?.find((order) => order.id === localCancel.orderId);
if (!existingOrder) return;

// skip if this is from a cancel all operation and isn't an error
if (localCancel.isSubmittedThroughCancelAll && !localCancel.errorParams) return;

// share same notification with existing local order if exists
// so that canceling a local order will not add an extra notification
const key = (existingOrder.clientId ?? localCancel.orderId).toString();
Expand All @@ -705,6 +712,31 @@ export const notificationTypes: NotificationTypeConfig[] = [
);
}
}, [localCancelOrders]);

useEffect(() => {
// eslint-disable-next-line no-restricted-syntax
for (const cancelAll of Object.values(localCancelAlls)) {
trigger(
cancelAll.key,
{
icon: null,
title: 'Cancel all orders',
toastSensitivity: 'background',
groupKey: cancelAll.key,
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
renderCustomBody: ({ isToast, notification }) => (
<CancelAllNotification
isToast={isToast}
localCancelAll={cancelAll}
notification={notification}
/>
),
},
[cancelAll.canceledOrderIds, cancelAll.failedOrderIds, cancelAll.errorParams],
true
);
}
}, [localCancelAlls]);
},
useNotificationAction: () => {
const dispatch = useAppDispatch();
Expand Down
31 changes: 24 additions & 7 deletions src/hooks/useSubaccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import { TradeTypes } from '@/constants/trade';
import { DydxAddress, WalletType } from '@/constants/wallets';

import {
cancelAllOrderConfirmed,
cancelAllOrderFailed,
cancelAllSubmitted,
cancelOrderConfirmed,
cancelOrderFailed,
cancelOrderSubmitted,
Expand Down Expand Up @@ -570,14 +573,28 @@ const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWall
const cancelAllOrders = useCallback(
(marketId?: string) => {
// this is for each single cancel transaction
const callback = () =>
// success: boolean,
// parsingError?: Nullable<ParsingError>,
// data?: Nullable<HumanReadableCancelOrderPayload>
{
// TODO(@aforaleka): Add this back to update local cancel all state for notifications
};
const callback = (
success: boolean,
parsingError?: Nullable<ParsingError>,
data?: Nullable<HumanReadableCancelOrderPayload>
) => {
if (success) {
if (data?.orderId) dispatch(cancelAllOrderConfirmed(data.orderId));
} else {
const errorParams = getValidErrorParamsFromParsingError(parsingError);
if (data?.orderId) {
dispatch(
cancelAllOrderFailed({
orderId: data.orderId,
errorParams,
})
);
}
}
};

const orderIds = abacusStateManager.getCancelableOrderIds(marketId);
dispatch(cancelAllSubmitted({ marketId, orderIds }));
abacusStateManager.cancelAllOrders(marketId, callback);
},
[dispatch]
Expand Down
135 changes: 117 additions & 18 deletions src/state/account.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import _ from 'lodash';

import {
type AccountBalance,
Expand All @@ -23,7 +24,9 @@ import { OnboardingGuard, OnboardingState } from '@/constants/account';
import { DEFAULT_SOMETHING_WENT_WRONG_ERROR_PARAMS, ErrorParams } from '@/constants/errors';
import { LocalStorageKey } from '@/constants/localStorage';
import {
CANCEL_ALL_ORDERS_KEY,
CancelOrderStatuses,
LocalCancelAllData,
PlaceOrderStatuses,
type LocalCancelOrderData,
type LocalPlaceOrderData,
Expand Down Expand Up @@ -72,6 +75,7 @@ export type AccountState = {
uncommittedOrderClientIds: string[];
localPlaceOrders: LocalPlaceOrderData[];
localCancelOrders: LocalCancelOrderData[];
localCancelAlls: Record<string, LocalCancelAllData>;

restriction?: Nullable<UsageRestriction>;
compliance?: Compliance;
Expand Down Expand Up @@ -111,6 +115,7 @@ const initialState: AccountState = {
historicalPnlPeriod: undefined,
localPlaceOrders: [],
localCancelOrders: [],
localCancelAlls: {},

// Restriction
restriction: undefined,
Expand Down Expand Up @@ -157,7 +162,7 @@ export const accountSlice = createSlice({
: order
)
: state.localPlaceOrders,
submittedCanceledOrders: hasNewFillUpdates
localCancelOrders: hasNewFillUpdates
? state.localCancelOrders.filter((order) => !filledOrderIds.includes(order.orderId))
: state.localCancelOrders,
};
Expand Down Expand Up @@ -246,28 +251,43 @@ export const accountSlice = createSlice({
.filter((order) => isOrderStatusCanceled(order.status))
.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) =>
canceledOrderIdsInPayload.includes(orderId) &&
!state.localCancelOrders.map((order) => order.orderId).includes(orderId);
const getNewCanceledOrderIds = (batch: LocalCancelAllData) => {
const newCanceledOrderIds = _.intersection(batch.orderIds, canceledOrderIdsInPayload);
return _.uniq([...(batch.canceledOrderIds ?? []), ...newCanceledOrderIds]);
};

return {
...state,
subaccount: action.payload,
hasUnseenOrderUpdates: hasNewOrderUpdates,
localPlaceOrders: canceledOrderIdsInPayload.length
? state.localPlaceOrders.map((order) =>
order.submissionStatus !== PlaceOrderStatuses.Canceled &&
order.orderId &&
isOrderCanceledByBackend(order.orderId)
const cancelUpdates = canceledOrderIdsInPayload.length
? {
localPlaceOrders: state.localPlaceOrders.map((order) =>
order.orderId && canceledOrderIdsInPayload.includes(order.orderId)
? {
...order,
submissionStatus: PlaceOrderStatuses.Canceled,
}
: order
)
: state.localPlaceOrders,
),
localCancelOrders: state.localCancelOrders.map((order) =>
canceledOrderIdsInPayload.includes(order.orderId)
? { ...order, submissionStatus: CancelOrderStatuses.Canceled }
: order
),
localCancelAlls: Object.fromEntries(
Object.entries(state.localCancelAlls).map(([marketId, batch]) => [
marketId,
{
...batch,
canceledOrderIds: getNewCanceledOrderIds(batch),
},
])
),
}
: {};

return {
...state,
subaccount: action.payload,
hasUnseenOrderUpdates: hasNewOrderUpdates,
...cancelUpdates,
};
},
setChildSubaccount: (
Expand Down Expand Up @@ -372,10 +392,56 @@ export const accountSlice = createSlice({
) => {
state.localCancelOrders = state.localCancelOrders.map((order) =>
order.orderId === action.payload.orderId
? { ...order, errorParams: action.payload.errorParams }
? {
...order,
errorParams: action.payload.errorParams,
}
: order
);
},
cancelAllSubmitted: (
state,
action: PayloadAction<{ marketId?: string; orderIds: string[] }>
) => {
const { marketId, orderIds } = action.payload;
const cancelAllKey = marketId ?? CANCEL_ALL_ORDERS_KEY;

state.localCancelAlls[cancelAllKey] = {
key: cancelAllKey,
orderIds,
};

state.localCancelOrders = [
...state.localCancelOrders,
...orderIds.map((orderId) => ({
orderId,
submissionStatus: CancelOrderStatuses.Submitted,
isSubmittedThroughCancelAll: true, // track this to skip certain notifications
})),
];
},
cancelAllOrderConfirmed: (state, action: PayloadAction<string>) => {
const orderId = action.payload;
const order = state.subaccount?.orders?.toArray()?.find((o) => o.id === orderId);
if (!order) return;

updateCancelAllOrderIds(state, orderId, 'canceledOrderIds', order.marketId);
cancelOrderConfirmed(orderId);
},
cancelAllOrderFailed: (
state,
action: PayloadAction<{
orderId: string;
errorParams?: ErrorParams;
}>
) => {
const { orderId, errorParams } = action.payload;
const order = state.subaccount?.orders?.toArray()?.find((o) => o.id === orderId);
if (!order) return;

updateCancelAllOrderIds(state, orderId, 'failedOrderIds', order.marketId);
if (errorParams) cancelOrderFailed({ orderId, errorParams });
},
},
});

Expand Down Expand Up @@ -408,4 +474,37 @@ export const {
cancelOrderSubmitted,
cancelOrderConfirmed,
cancelOrderFailed,
cancelAllSubmitted,
cancelAllOrderConfirmed,
cancelAllOrderFailed,
} = accountSlice.actions;

// helper functions
const updateCancelAllOrderIds = (
state: AccountState,
orderId: string,
key: 'canceledOrderIds' | 'failedOrderIds',
cancelAllKey: string
) => {
const getNewOrderIds = (cancelAll: LocalCancelAllData) => {
const existingOrderIds = cancelAll[key] ?? [];
return cancelAll.orderIds.includes(orderId) && !existingOrderIds.includes(orderId)
? [...existingOrderIds, orderId]
: existingOrderIds;
};

const updateKey = (currentKey: string) => ({
...state.localCancelAlls[currentKey],
[key]: getNewOrderIds(state.localCancelAlls[currentKey]),
});

state.localCancelAlls = {
...state.localCancelAlls,
...(state.localCancelAlls[CANCEL_ALL_ORDERS_KEY] && {
[CANCEL_ALL_ORDERS_KEY]: updateKey(CANCEL_ALL_ORDERS_KEY),
}),
...(state.localCancelAlls[cancelAllKey] && {
[cancelAllKey]: updateKey(cancelAllKey),
}),
};
};
Loading

0 comments on commit 2916450

Please sign in to comment.