From e3e00f4f1ec64e8d27da6658c689c61c966ad32e Mon Sep 17 00:00:00 2001
From: moo-onthelawn <70078372+moo-onthelawn@users.noreply.github.com>
Date: Tue, 13 Feb 2024 16:00:46 -0500
Subject: [PATCH] CT-145 Add warning icon + tooltip to trade order button CTA
(#297)
* update
* rename to config
---
src/views/forms/ClosePositionForm.tsx | 9 +-
src/views/forms/TradeForm.tsx | 46 +++++++-
.../TradeForm/PlaceOrderButtonAndReceipt.tsx | 105 ++++++++++--------
3 files changed, 109 insertions(+), 51 deletions(-)
diff --git a/src/views/forms/ClosePositionForm.tsx b/src/views/forms/ClosePositionForm.tsx
index 7e6ce6c..82f9565 100644
--- a/src/views/forms/ClosePositionForm.tsx
+++ b/src/views/forms/ClosePositionForm.tsx
@@ -14,6 +14,7 @@ import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/b
import { TOKEN_DECIMALS } from '@/constants/numbers';
import { STRING_KEYS } from '@/constants/localization';
import { MobilePlaceOrderSteps } from '@/constants/trade';
+
import { useBreakpoints, useIsFirstRender, useStringGetter, useSubaccount } from '@/hooks';
import { useOnLastOrderIndexed } from '@/hooks/useOnLastOrderIndexed';
@@ -35,7 +36,6 @@ import { Orderbook, orderbookMixins, type OrderbookScrollBehavior } from '@/view
import { PositionPreview } from '@/views/forms/TradeForm/PositionPreview';
import { getCurrentMarketPositionData } from '@/state/accountSelectors';
-
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getClosePositionInputErrors, getInputClosePositionData } from '@/state/inputsSelectors';
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
@@ -291,9 +291,14 @@ export const ClosePositionForm = ({
isLoading={isClosingPosition}
hasValidationErrors={hasInputErrors}
actionStringKey={inputAlert?.actionStringKey}
+ validationErrorString={alertContent}
summary={summary ?? undefined}
currentStep={currentStep}
- isClosePosition
+ confirmButtonConfig={{
+ stringKey: STRING_KEYS.CLOSE_ORDER,
+ buttonTextStringKey: STRING_KEYS.CLOSE_POSITION,
+ buttonAction: ButtonAction.Destroy,
+ }}
/>
diff --git a/src/views/forms/TradeForm.tsx b/src/views/forms/TradeForm.tsx
index 921c356..3855f89 100644
--- a/src/views/forms/TradeForm.tsx
+++ b/src/views/forms/TradeForm.tsx
@@ -1,8 +1,10 @@
-import { type FormEvent, useState, Ref, useCallback, useEffect } from 'react';
+import { type FormEvent, useState, Ref, useCallback } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import type { NumberFormatValues, SourceInfo } from 'react-number-format';
+import { OrderSide } from '@dydxprotocol/v4-client-js';
+
import { AlertType } from '@/constants/alerts';
import {
@@ -16,7 +18,12 @@ import {
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { USD_DECIMALS } from '@/constants/numbers';
-import { InputErrorData, TradeBoxKeys, MobilePlaceOrderSteps } from '@/constants/trade';
+import {
+ InputErrorData,
+ TradeBoxKeys,
+ MobilePlaceOrderSteps,
+ ORDER_TYPE_STRINGS,
+} from '@/constants/trade';
import { breakpoints } from '@/styles';
import { useStringGetter, useSubaccount } from '@/hooks';
@@ -37,11 +44,16 @@ import { WithTooltip } from '@/components/WithTooltip';
import { Orderbook } from '@/views/tables/Orderbook';
import { setTradeFormInputs } from '@/state/inputs';
-import { getCurrentInput, getTradeFormInputs, useTradeFormData } from '@/state/inputsSelectors';
+import {
+ getCurrentInput,
+ getInputTradeData,
+ getTradeFormInputs,
+ useTradeFormData,
+} from '@/state/inputsSelectors';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
import abacusStateManager from '@/lib/abacus';
-import { getTradeInputAlert } from '@/lib/tradeData';
+import { getSelectedOrderSide, getSelectedTradeType, getTradeInputAlert } from '@/lib/tradeData';
import { AdvancedTradeOptions } from './TradeForm/AdvancedTradeOptions';
import { TradeSizeInputs } from './TradeForm/TradeSizeInputs';
@@ -108,8 +120,21 @@ export const TradeForm = ({
const tradeFormInputValues = useSelector(getTradeFormInputs, shallowEqual);
const { limitPriceInput, triggerPriceInput, trailingPercentInput } = tradeFormInputValues;
+ const currentTradeData = useSelector(getInputTradeData, shallowEqual);
+
+ const { side, type } = currentTradeData || {};
+
+ const selectedTradeType = getSelectedTradeType(type);
+ const selectedOrderSide = getSelectedOrderSide(side);
+
const needsAdvancedOptions =
- needsGoodUntil || timeInForceOptions || executionOptions || (needsPostOnly || postOnlyTooltip) || (needsReduceOnly || reduceOnlyTooltip);
+ needsGoodUntil ||
+ timeInForceOptions ||
+ executionOptions ||
+ needsPostOnly ||
+ postOnlyTooltip ||
+ needsReduceOnly ||
+ reduceOnlyTooltip;
const tradeFormInputs: TradeBoxInputConfig[] = [];
@@ -139,6 +164,11 @@ export const TradeForm = ({
alertType = inputAlert?.type;
}
+ const orderSideAction = {
+ [OrderSide.BUY]: ButtonAction.Create,
+ [OrderSide.SELL]: ButtonAction.Destroy,
+ }[selectedOrderSide];
+
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
@@ -319,9 +349,15 @@ export const TradeForm = ({
isLoading={isPlacingOrder}
hasValidationErrors={hasInputErrors}
actionStringKey={inputAlert?.actionStringKey}
+ validationErrorString={alertContent}
summary={summary ?? undefined}
currentStep={currentStep}
showDeposit={inputAlert?.errorAction === TradeInputErrorAction.DEPOSIT}
+ confirmButtonConfig={{
+ stringKey: ORDER_TYPE_STRINGS[selectedTradeType].orderTypeKey,
+ buttonTextStringKey: STRING_KEYS.PLACE_ORDER,
+ buttonAction: orderSideAction,
+ }}
/>
diff --git a/src/views/forms/TradeForm/PlaceOrderButtonAndReceipt.tsx b/src/views/forms/TradeForm/PlaceOrderButtonAndReceipt.tsx
index 3770ad5..d55c046 100644
--- a/src/views/forms/TradeForm/PlaceOrderButtonAndReceipt.tsx
+++ b/src/views/forms/TradeForm/PlaceOrderButtonAndReceipt.tsx
@@ -1,16 +1,17 @@
-import { shallowEqual, useDispatch, useSelector } from 'react-redux';
-import { OrderSide } from '@dydxprotocol/v4-client-js';
+import { useDispatch, useSelector } from 'react-redux';
+import styled, { type AnyStyledComponent, css } from 'styled-components';
import type { TradeInputSummary } from '@/constants/abacus';
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
-import { ORDER_TYPE_STRINGS, MobilePlaceOrderSteps } from '@/constants/trade';
+import { MobilePlaceOrderSteps } from '@/constants/trade';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
import { Button } from '@/components/Button';
+import { Icon, IconName } from '@/components/Icon';
import { Output, OutputType, ShowSign } from '@/components/Output';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
import { WithTooltip } from '@/components/WithTooltip';
@@ -20,30 +21,34 @@ import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton
import { calculateCanAccountTrade } from '@/state/accountCalculators';
import { getSubaccountId } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
-import { getCurrentInput, getInputTradeData } from '@/state/inputsSelectors';
+import { getCurrentInput } from '@/state/inputsSelectors';
-import { getSelectedOrderSide, getSelectedTradeType } from '@/lib/tradeData';
+type ConfirmButtonConfig = {
+ stringKey: string;
+ buttonTextStringKey: string;
+ buttonAction: ButtonAction;
+};
type ElementProps = {
isLoading: boolean;
- isClosePosition?: boolean;
actionStringKey?: string;
summary?: TradeInputSummary;
hasValidationErrors?: boolean;
+ validationErrorString?: string;
currentStep?: MobilePlaceOrderSteps;
showDeposit?: boolean;
- showConnectWallet?: boolean;
+ confirmButtonConfig: ConfirmButtonConfig;
};
export const PlaceOrderButtonAndReceipt = ({
isLoading,
- isClosePosition,
actionStringKey,
summary,
hasValidationErrors,
+ validationErrorString,
currentStep,
showDeposit,
- showConnectWallet,
+ confirmButtonConfig,
}: ElementProps) => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
@@ -52,18 +57,12 @@ export const PlaceOrderButtonAndReceipt = ({
const canAccountTrade = useSelector(calculateCanAccountTrade);
const subaccountNumber = useSelector(getSubaccountId);
const currentInput = useSelector(getCurrentInput);
- const currentTradeData = useSelector(getInputTradeData, shallowEqual);
const hasMissingData = subaccountNumber === undefined;
const shouldEnableTrade =
canAccountTrade && !hasMissingData && !hasValidationErrors && currentInput !== 'transfer';
- const { side, type } = currentTradeData || {};
-
- const selectedTradeType = getSelectedTradeType(type);
- const selectedOrderSide = getSelectedOrderSide(side);
-
const { fee, price: expectedPrice, total, reward } = summary || {};
const items = [
@@ -110,11 +109,6 @@ export const PlaceOrderButtonAndReceipt = ({
},
];
- const orderSideAction = {
- [OrderSide.BUY]: ButtonAction.Create,
- [OrderSide.SELL]: ButtonAction.Destroy,
- }[selectedOrderSide];
-
const buttonStatesPerStep = {
[MobilePlaceOrderSteps.EditOrder]: {
buttonTextStringKey: shouldEnableTrade
@@ -128,7 +122,7 @@ export const PlaceOrderButtonAndReceipt = ({
[MobilePlaceOrderSteps.PreviewOrder]: {
buttonTextStringKey: STRING_KEYS.CONFIRM_ORDER,
- buttonAction: isClosePosition ? ButtonAction.Destroy : orderSideAction,
+ buttonAction: confirmButtonConfig.buttonAction,
buttonState: { isLoading },
},
[MobilePlaceOrderSteps.PlacingOrder]: {
@@ -145,15 +139,13 @@ export const PlaceOrderButtonAndReceipt = ({
const buttonAction = currentStep
? buttonStatesPerStep[currentStep].buttonAction
- : isClosePosition
- ? ButtonAction.Destroy
- : orderSideAction;
+ : confirmButtonConfig.buttonAction;
let buttonTextStringKey = STRING_KEYS.UNAVAILABLE;
if (currentStep) {
buttonTextStringKey = buttonStatesPerStep[currentStep].buttonTextStringKey;
} else if (shouldEnableTrade) {
- buttonTextStringKey = isClosePosition ? STRING_KEYS.CLOSE_POSITION : STRING_KEYS.PLACE_ORDER;
+ buttonTextStringKey = confirmButtonConfig.buttonTextStringKey;
} else if (actionStringKey) {
buttonTextStringKey = actionStringKey;
}
@@ -165,31 +157,56 @@ export const PlaceOrderButtonAndReceipt = ({
isLoading: isLoading || hasMissingData,
};
+ const depositButton = (
+
+ );
+
+ const submitButton = (
+ : undefined
+ }
+ >
+ {stringGetter({
+ key: buttonTextStringKey,
+ params: {
+ ORDER: stringGetter({
+ key: confirmButtonConfig.stringKey,
+ }),
+ },
+ })}
+
+ );
+
return (
- {!canAccountTrade || showConnectWallet ? (
+ {!canAccountTrade ? (
) : showDeposit ? (
-
+ depositButton
) : (
-
+
+ {submitButton}
+
)}
);
};
+
+const Styled: Record = {};
+
+Styled.Button = styled(Button)`
+ width: 100%;
+`;
+
+Styled.WarningIcon = styled(Icon)`
+ color: var(--color-warning);
+`;