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); +`;