diff --git a/src/components/ArrowIcon.tsx b/src/components/ArrowIcon.tsx new file mode 100644 index 0000000000..141b5b332a --- /dev/null +++ b/src/components/ArrowIcon.tsx @@ -0,0 +1,32 @@ +import styled, { css } from 'styled-components'; + +import { Icon, IconName } from './Icon'; + +export const ArrowIcon = (props: { direction: 'up' | 'down'; color: string }) => { + return <$ArrowIcon {...props} iconName={IconName.Arrow} />; +}; + +const $ArrowIcon = styled(Icon)<{ direction: 'up' | 'down'; color: 'green' | 'red' | string }>` + position: absolute; + ${({ direction }) => + ({ + up: css` + transform: rotate(-90deg); + `, + down: css` + transform: rotate(90deg); + `, + })[direction]} + ${({ color }) => + ({ + green: css` + color: var(--color-green); + `, + red: css` + color: var(--color-red); + `, + })[color] ?? + css` + color: var(${color}); + `}; +`; diff --git a/src/components/ComboboxMenu.tsx b/src/components/ComboboxMenu.tsx index 91060582bd..ba60ab4a26 100644 --- a/src/components/ComboboxMenu.tsx +++ b/src/components/ComboboxMenu.tsx @@ -10,6 +10,8 @@ import { popoverMixins } from '@/styles/popoverMixins'; import { Tag } from '@/components/Tag'; +import { SearchInput } from './SearchInput'; + type ElementProps = { items: MenuConfig; onItemSelected?: () => void; @@ -18,6 +20,7 @@ type ElementProps {withSearch && ( <$Header $withStickyLayout={withStickyLayout}> - <$Input - /** - * Mobile Issue: Search Input will always trigger mobile keyboard drawer. There is no fix. - * https://github.com/pacocoursey/cmdk/issues/127 - */ - autoFocus - value={searchValue} - onValueChange={setSearchValue} - placeholder={inputPlaceholder} - data-hj-allow - /> + {useSearchInputComponent ? ( + <$SearchInput + autoFocus + value={searchValue} + onChange={setSearchValue} + placeholder={inputPlaceholder} + data-hj-allow + /> + ) : ( + <$Input + /** + * Mobile Issue: Search Input will always trigger mobile keyboard drawer. There is no fix. + * https://github.com/pacocoursey/cmdk/issues/127 + */ + autoFocus + value={searchValue} + onValueChange={setSearchValue} + placeholder={inputPlaceholder} + data-hj-allow + /> + )} )} @@ -192,6 +206,7 @@ const $Command = styled(Command)<{ $withStickyLayout?: boolean }>` --comboboxMenu-item-gap: 0.5rem; --comboboxMenu-item-padding: 0.5em 1em; + box-shadow: none; display: grid; align-content: start; @@ -214,6 +229,14 @@ const $Command = styled(Command)<{ $withStickyLayout?: boolean }>` overflow-y: auto; } `} + + /* + Layout mixins withInnerHorizontalBorders forces all children components to have box shadow + This creates a border-like effect that we don't want for this dropdown component + */ + && > * { + box-shadow: none; + } `; const $Header = styled.header<{ $withStickyLayout?: boolean }>` @@ -279,7 +302,6 @@ const $List = styled(Command.List)<{ $withStickyLayout?: boolean }>` > [cmdk-list-sizer] { display: grid; - ${layoutMixins.withOuterAndInnerBorders} } @media (prefers-reduced-motion: no-preference) { @@ -342,3 +364,8 @@ const $ItemLabel = styled.div` min-width: 0; `; + +const $SearchInput = styled(SearchInput)` + margin-top: 0.75em; + margin-bottom: 0.5em; +`; diff --git a/src/components/FormInput.tsx b/src/components/FormInput.tsx index 14e7d872cb..ac76e4765f 100644 --- a/src/components/FormInput.tsx +++ b/src/components/FormInput.tsx @@ -13,6 +13,7 @@ import { WithLabel } from '@/components/WithLabel'; type StyleProps = { className?: string; + backgroundColorOverride?: string; }; type ElementProps = { @@ -28,15 +29,33 @@ type ElementProps = { export type FormInputProps = ElementProps & StyleProps & InputProps; export const FormInput = forwardRef( - ({ id, label, slotRight, className, validationConfig, ...otherProps }, ref) => ( + ( + { id, label, slotRight, className, validationConfig, backgroundColorOverride, ...otherProps }, + ref + ) => ( <$FormInputContainer className={className} isValidationAttached={validationConfig?.attached}> <$InputContainer hasLabel={!!label} hasSlotRight={!!slotRight}> {label ? ( - <$WithLabel label={label} inputID={id} disabled={otherProps?.disabled}> - + <$WithLabel + label={label} + inputID={id} + disabled={otherProps?.disabled} + backgroundColorOverride={backgroundColorOverride} + > + ) : ( - + )} {slotRight} @@ -65,9 +84,11 @@ const $FormInputContainer = styled.div<{ isValidationAttached?: boolean }>` `} `; -const $InputContainer = styled.div<{ hasLabel?: boolean; hasSlotRight?: boolean }>` +const $InputContainer = styled.div<{ + hasLabel?: boolean; + hasSlotRight?: boolean; +}>` ${formMixins.inputContainer} - input { ${({ hasLabel }) => !hasLabel && @@ -89,8 +110,13 @@ const $InputContainer = styled.div<{ hasLabel?: boolean; hasSlotRight?: boolean `} `; -const $WithLabel = styled(WithLabel)<{ disabled?: boolean }>` +const $WithLabel = styled(WithLabel)<{ disabled?: boolean; backgroundColorOverride?: string }>` ${formMixins.inputLabel} + ${({ backgroundColorOverride }) => + backgroundColorOverride && + css` + background-color: ${backgroundColorOverride}; + `} label { ${({ disabled }) => !disabled && 'cursor: text;'} diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index aa50bbee14..b9cfd0298b 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -97,6 +97,7 @@ import { } from '@/icons'; import { ChaosLabsIcon } from '@/icons/chaos-labs'; import { LogoShortIcon } from '@/icons/logo-short'; +import UsdcIcon from '@/icons/usdc.svg'; export enum IconName { AddressConnector = 'AddressConnector', @@ -188,6 +189,7 @@ export enum IconName { Translate = 'Translate', Triangle = 'Triangle', TryAgain = 'TryAgain', + Usdc = 'Usdc', Warning = 'Warning', Website = 'Website', Whitepaper = 'Whitepaper', @@ -284,6 +286,7 @@ const icons = { [IconName.Translate]: TranslateIcon, [IconName.Triangle]: TriangleIcon, [IconName.TryAgain]: TryAgainIcon, + [IconName.Usdc]: UsdcIcon, [IconName.Warning]: WarningIcon, [IconName.Website]: WebsiteIcon, [IconName.Whitepaper]: WhitepaperIcon, diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 2ce6ef907e..c6ea5bc1c5 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -26,6 +26,7 @@ export enum InputType { type StyleProps = { className?: string; + backgroundColorOverride?: string; }; type ElementProps = { @@ -81,6 +82,7 @@ export const Input = forwardRef( onFocus, onInput, type = InputType.Number, + backgroundColorOverride, ...otherProps }, ref @@ -126,6 +128,7 @@ export const Input = forwardRef( <$InputContainer className={className}> {type === InputType.Text || type === InputType.Search ? ( <$Input + backgroundColorOverride={backgroundColorOverride} // React ref={ref} id={id} @@ -145,6 +148,7 @@ export const Input = forwardRef( /> ) : ( <$NumericFormat + backgroundColorOverride={backgroundColorOverride} // React getInputRef={ref} id={id} @@ -234,11 +238,21 @@ const InputStyle = css` } `; -const $NumericFormat = styled(NumericFormat)` +const $NumericFormat = styled(NumericFormat)<{ backgroundColorOverride?: string }>` ${InputStyle} font-feature-settings: var(--fontFeature-monoNumbers); + ${({ backgroundColorOverride }) => + backgroundColorOverride && + css` + background-color: ${backgroundColorOverride}; + `} `; -const $Input = styled.input` +const $Input = styled.input<{ backgroundColorOverride?: string }>` ${InputStyle} + ${({ backgroundColorOverride }) => + backgroundColorOverride && + css` + background-color: ${backgroundColorOverride}; + `} `; diff --git a/src/components/SearchInput.tsx b/src/components/SearchInput.tsx index 6008ac8b1a..c25db0c61a 100644 --- a/src/components/SearchInput.tsx +++ b/src/components/SearchInput.tsx @@ -49,8 +49,8 @@ export const SearchInput = ({ placeholder, onTextChange, className }: SearchInpu const $Search = styled.div` ${layoutMixins.row} width: auto; - height: 2rem; - background-color: var(--color-layer-3); + height: 2.5rem; + background-color: var(--color-layer-5); color: ${({ theme }) => theme.textTertiary}; border-radius: 2.5rem; border: solid var(--border-width) var(--color-layer-6); diff --git a/src/components/SearchSelectMenu.tsx b/src/components/SearchSelectMenu.tsx index 48ec087a4f..bf92c3dd55 100644 --- a/src/components/SearchSelectMenu.tsx +++ b/src/components/SearchSelectMenu.tsx @@ -28,6 +28,8 @@ type ElementProps = { items: MenuConfig; withSearch?: boolean; withReceiptItems?: DetailsItem[]; + useSearchInputComponent?: boolean; + inputPlaceholder?: string; }; type StyleProps = { @@ -45,6 +47,8 @@ export const SearchSelectMenu = ({ items, withSearch = true, withReceiptItems, + useSearchInputComponent, + inputPlaceholder, }: SearchSelectMenuProps) => { const [open, setOpen] = useState(false); const searchSelectMenuRef = useRef(null); @@ -83,6 +87,8 @@ export const SearchSelectMenu = ({ onItemSelected={() => setOpen(false)} withStickyLayout $withSearch={withSearch} + inputPlaceholder={inputPlaceholder} + useSearchInputComponent={useSearchInputComponent} /> @@ -123,7 +129,6 @@ const $Popover = styled(Popover)` border: var(--border-width) solid var(--color-layer-6); border-radius: 0.5rem; z-index: 2; - box-shadow: none; `; type ComboboxMenuStyleProps = { $withSearch?: boolean }; diff --git a/src/icons/usdc.svg b/src/icons/usdc.svg new file mode 100644 index 0000000000..7c85365a2d --- /dev/null +++ b/src/icons/usdc.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/lib/testFlags.ts b/src/lib/testFlags.ts index 8aeccb4904..8f1fd6d7f2 100644 --- a/src/lib/testFlags.ts +++ b/src/lib/testFlags.ts @@ -65,6 +65,10 @@ class TestFlags { get uiRefresh() { return !!this.queryParams.uirefresh || isDev; } + + get onboardingRewrite() { + return !!this.queryParams.onboarding_rewrite; + } } export const testFlags = new TestFlags(); diff --git a/src/views/dialogs/DisplaySettingsDialog.tsx b/src/views/dialogs/DisplaySettingsDialog.tsx index 301dd3a908..fbe2d2e95b 100644 --- a/src/views/dialogs/DisplaySettingsDialog.tsx +++ b/src/views/dialogs/DisplaySettingsDialog.tsx @@ -207,7 +207,7 @@ const $Item = styled(Item)` --border-color: var(--color-accent); } - border: solid var(--border-width) var(--border-color); + /* border: solid var(--border-width) var(--border-color); */ border-radius: 0.875rem; padding: var(--item-padding); diff --git a/src/views/dialogs/WithdrawDialog.tsx b/src/views/dialogs/WithdrawDialog.tsx index 874a660d9a..9618657f96 100644 --- a/src/views/dialogs/WithdrawDialog.tsx +++ b/src/views/dialogs/WithdrawDialog.tsx @@ -9,22 +9,28 @@ import { useStringGetter } from '@/hooks/useStringGetter'; import { layoutMixins } from '@/styles/layoutMixins'; import { Dialog, DialogPlacement } from '@/components/Dialog'; +import { Icon, IconName } from '@/components/Icon'; import { WithdrawForm } from '@/views/forms/AccountManagementForms/WithdrawForm'; +import { WithdrawForm as WithdrawFormV2 } from '@/views/forms/AccountManagementFormsNew/WithdrawForm/WithdrawForm'; + +import { testFlags } from '@/lib/testFlags'; export const WithdrawDialog = ({ setIsOpen }: DialogProps) => { const stringGetter = useStringGetter(); const { isTablet } = useBreakpoints(); - return ( + + {stringGetter({ key: STRING_KEYS.WITHDRAW })} USDC + + } placement={isTablet ? DialogPlacement.FullScreen : DialogPlacement.Default} > - <$Content> - - + <$Content>{testFlags.onboardingRewrite ? : } ); }; diff --git a/src/views/forms/AccountManagementFormsNew/WithdrawForm/NetworkSelectMenu.tsx b/src/views/forms/AccountManagementFormsNew/WithdrawForm/NetworkSelectMenu.tsx index 97574b6eb8..829ceaabf9 100644 --- a/src/views/forms/AccountManagementFormsNew/WithdrawForm/NetworkSelectMenu.tsx +++ b/src/views/forms/AccountManagementFormsNew/WithdrawForm/NetworkSelectMenu.tsx @@ -133,17 +133,24 @@ export const NetworkSelectMenu = ({ ]; return ( - +
{selectedChainOption ? ( <> <$Img src={selectedChainOption.logoURI ?? undefined} alt="" />{' '} {selectedChainOption.chainName} + {getFeeDecoratorComponentForChainId(selectedChainOption.chainID)} ) : selectedExchangeOption ? ( <> <$Img src={selectedExchangeOption.icon ?? undefined} alt="" />{' '} {selectedExchangeOption.name} + ) : ( stringGetter({ key: STRING_KEYS.SELECT_CHAIN }) diff --git a/src/views/forms/AccountManagementFormsNew/WithdrawForm/TokenSelectMenu.tsx b/src/views/forms/AccountManagementFormsNew/WithdrawForm/TokenSelectMenu.tsx deleted file mode 100644 index 0589d25cbb..0000000000 --- a/src/views/forms/AccountManagementFormsNew/WithdrawForm/TokenSelectMenu.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Asset } from '@skip-go/client'; -import tw from 'twin.macro'; - -import { cctpTokensByDenomLowerCased, isLowFeeDenom } from '@/constants/cctp'; -import { NEUTRON_USDC_IBC_DENOM, OSMO_USDC_IBC_DENOM } from '@/constants/denoms'; -import { getNeutronChainId, getNobleChainId, getOsmosisChainId } from '@/constants/graz'; -import { STRING_KEYS } from '@/constants/localization'; -import { TransferType } from '@/constants/transfers'; -import { WalletType } from '@/constants/wallets'; - -import { useAccounts } from '@/hooks/useAccounts'; -import { useStringGetter } from '@/hooks/useStringGetter'; - -import { DiffArrow } from '@/components/DiffArrow'; -import { SearchSelectMenu } from '@/components/SearchSelectMenu'; -import { Tag } from '@/components/Tag'; - -import { LowestFeesDecoratorText } from '../LowestFeesText'; - -type ElementProps = { - selectedToken?: Asset; - selectedChainId?: string; - onSelectToken: (token: Asset) => void; - isExchange?: boolean; - assets: Asset[]; -}; - -export const TokenSelectMenu = ({ - selectedToken, - selectedChainId, - onSelectToken, - isExchange, - assets, -}: ElementProps) => { - const stringGetter = useStringGetter(); - const { sourceAccount } = useAccounts(); - - const isKeplrWallet = sourceAccount?.walletInfo?.name === WalletType.Keplr; - const tokens = assets; - const tokenItems = Object.values(tokens) - .map((token) => ({ - value: token.denom, - label: token.name, - onSelect: () => { - onSelectToken(token); - }, - slotBefore: ( - // the curve dao token svg causes the web app to lag when rendered - <$Img src={token.logoURI ?? undefined} alt="" /> - ), - slotAfter: isLowFeeDenom(token.denom, TransferType.Withdraw) && , - tag: token.symbol, - })) - .filter((token) => { - // For cosmos, return specific approved tokens for supported chains - if (isKeplrWallet) { - if (selectedChainId === getNobleChainId()) { - return true; - } - if (selectedChainId === getOsmosisChainId()) { - return token.value === OSMO_USDC_IBC_DENOM; - } - if (selectedChainId === getNeutronChainId()) { - return token.value === NEUTRON_USDC_IBC_DENOM; - } - } - // Only return CCTP tokens - return !!cctpTokensByDenomLowerCased[token.value.toLowerCase()]; - }) - // we want lowest fee tokens first followed by non-lowest fee cctp tokens - .sort((token) => (cctpTokensByDenomLowerCased[token.value.toLowerCase()] ? -1 : 1)) - .sort((token) => (isLowFeeDenom(token.value, TransferType.Withdraw) ? -1 : 1)); - // console.log('tokenitems', tokenItems); - return ( - - USDC - - {selectedToken?.symbol} - - ), - }, - ] - : undefined - } - > -
- {selectedToken ? ( - <> - <$Img src={selectedToken?.logoURI ?? undefined} alt="" /> {selectedToken?.name}{' '} - {selectedToken?.symbol} - - ) : ( - stringGetter({ key: STRING_KEYS.SELECT_ASSET }) - )} -
-
- ); -}; -const $Img = tw.img`h-1.25 w-1.25 rounded-[50%]`; diff --git a/src/views/forms/AccountManagementFormsNew/WithdrawForm/WithdrawButtonAndReceipt.tsx b/src/views/forms/AccountManagementFormsNew/WithdrawForm/WithdrawButtonAndReceipt.tsx index a7d49a464d..ff01798b89 100644 --- a/src/views/forms/AccountManagementFormsNew/WithdrawForm/WithdrawButtonAndReceipt.tsx +++ b/src/views/forms/AccountManagementFormsNew/WithdrawForm/WithdrawButtonAndReceipt.tsx @@ -26,17 +26,12 @@ import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton import { calculateCanAccountTrade } from '@/state/accountCalculators'; import { getSubaccount } from '@/state/accountSelectors'; import { useAppSelector } from '@/state/appTypes'; -import { getTransferInputs } from '@/state/inputsSelectors'; import { isTruthy } from '@/lib/isTruthy'; import { RouteWarningMessage } from '../RouteWarningMessage'; -import { SlippageEditor } from '../SlippageEditor'; type ElementProps = { - setSlippage: (slippage: number) => void; - - slippage: number; withdrawToken?: Asset; route?: RouteResponse; @@ -45,28 +40,40 @@ type ElementProps = { }; export const WithdrawButtonAndReceipt = ({ - setSlippage, - - slippage, withdrawToken, route, isDisabled, isLoading, }: ElementProps) => { - const [isEditingSlippage, setIsEditingSlipapge] = useState(false); const stringGetter = useStringGetter(); const { leverage } = useAppSelector(getSubaccount, shallowEqual) ?? {}; - // TODO: https://linear.app/dydx/issue/OTE-867/coinbase-withdrawals - const { exchange } = useAppSelector(getTransferInputs, shallowEqual) ?? {}; const canAccountTrade = useAppSelector(calculateCanAccountTrade, shallowEqual); - const { usdcLabel } = useTokenConfigs(); + const { usdcDecimals } = useTokenConfigs(); const { connectionError } = useApiState(); const fees = Number(route?.usdAmountIn) - Number(route?.usdAmountOut); const submitButtonReceipt = [ + { + key: 'amount-inputted', + label: Amount, + value: ( + + ), + }, { key: 'expected-amount-received', @@ -81,31 +88,16 @@ export const WithdrawButtonAndReceipt = ({ type={OutputType.Asset} value={ route?.amountOut - ? formatUnits(BigInt(route.amountOut ?? '0'), withdrawToken?.decimals ?? 0).toString() + ? formatUnits( + BigInt(route.amountOut ?? '0'), + withdrawToken?.decimals ?? usdcDecimals + ).toString() : undefined } fractionDigits={TOKEN_DECIMALS} /> ), }, - withdrawToken && - !withdrawToken.symbol?.toLowerCase().includes('usd') && { - key: 'expected-amount-received-usd', - - label: ( - <$RowWithGap> - {stringGetter({ key: STRING_KEYS.EXPECTED_AMOUNT_RECEIVED })} - {withdrawToken && {usdcLabel}} - - ), - value: ( - - ), - }, fees && { key: 'bridge-fees', label: ( @@ -115,17 +107,6 @@ export const WithdrawButtonAndReceipt = ({ ), value: , }, - !exchange && { - key: 'slippage', - label: {stringGetter({ key: STRING_KEYS.MAX_SLIPPAGE })}, - value: ( - - ), - }, { key: 'estimated-route-duration', label: {stringGetter({ key: STRING_KEYS.ESTIMATED_TIME })}, @@ -167,7 +148,6 @@ export const WithdrawButtonAndReceipt = ({ const requiresAcknowledgement = Boolean(route?.warning && !hasAcknowledged); const isFormValid = !isDisabled && - !isEditingSlippage && connectionError !== ConnectionErrorType.CHAIN_DISRUPTION && !requiresAcknowledgement; @@ -181,11 +161,13 @@ export const WithdrawButtonAndReceipt = ({ return ( <$WithReceipt slotReceipt={<$Details items={submitButtonReceipt} />}> - + {route?.warning && ( + + )} + + ); + } + return ( - <$Form onSubmit={onSubmit}> -
- {stringGetter({ - key: STRING_KEYS.LOWEST_FEE_WITHDRAWALS_SKIP, - params: { - LOWEST_FEE_TOKENS_TOOLTIP: ( - - {stringGetter({ - key: STRING_KEYS.SELECT_CHAINS, - })} - - ), - }, - })} -
+ <$Form> { } : undefined } + slotRight={ + setToAddress('')} + /> + } + /> + +
{stringGetter({ key: STRING_KEYS.AMOUNT })}
+
+
{freeCollateral?.current?.toFixed(2)} USDC Held
+
+ } + slotRight={ + (isPressed ? onClickMax() : setAmount(''))} + /> + } /> - + + + - - (isPressed ? onClickMax() : setAmount(''))} - /> - } - /> - {errorMessage && ( {errorMessage} )} - <$Footer>{/* TODO [onboarding-rewrite]: add preview */} +
+ +
); }; @@ -530,7 +518,6 @@ const $Form = styled.form` ${formMixins.transfersForm} `; -const $Footer = styled.footer` - ${formMixins.footer} - --stickyFooterBackdrop-outsetY: var(--dialog-content-paddingBottom); +const Arrow = styled.div` + border: solid var(--border-width) var(--color-layer-6); `;