diff --git a/apps/cowswap-frontend/src/common/constants/common.ts b/apps/cowswap-frontend/src/common/constants/common.ts index 151381d3f4..a0bd34d461 100644 --- a/apps/cowswap-frontend/src/common/constants/common.ts +++ b/apps/cowswap-frontend/src/common/constants/common.ts @@ -5,8 +5,6 @@ import ms from 'ms.macro' export const HIGH_FEE_WARNING_PERCENTAGE = new Percent(1, 10) -export const SAFE_COW_APP_LINK = 'https://app.safe.global/share/safe-app?appUrl=https%3A%2F%2Fswap.cow.fi&chain=eth' - export const MAX_ORDER_DEADLINE = ms`182d` + ms`12h` // 6 months, matching backend's https://github.com/cowprotocol/infrastructure/blob/901ed8e2fe3ea57956585f107bdd7539c2e7d3d1/services/Pulumi.yaml#L15 // Use a 150K gas as a fallback if there's issue calculating the gas estimation (fixes some issues with some nodes failing to calculate gas costs for SC wallets) diff --git a/apps/cowswap-frontend/src/legacy/state/price/hooks.ts b/apps/cowswap-frontend/src/legacy/state/price/hooks.ts index 798c0e7bd9..b6f4ee84e6 100644 --- a/apps/cowswap-frontend/src/legacy/state/price/hooks.ts +++ b/apps/cowswap-frontend/src/legacy/state/price/hooks.ts @@ -5,15 +5,15 @@ import { SupportedChainId as ChainId } from '@cowprotocol/cow-sdk' import { useDispatch, useSelector } from 'react-redux' import { - updateQuote, - UpdateQuoteParams, ClearQuoteParams, getNewQuote, GetQuoteParams, refreshQuote, - SetQuoteErrorParams, - setQuoteError, RefreshQuoteParams, + setQuoteError, + SetQuoteErrorParams, + updateQuote, + UpdateQuoteParams, } from './actions' import { QuoteInformationObject, QuotesMap } from './reducer' diff --git a/apps/cowswap-frontend/src/legacy/state/swap/TradeGp.ts b/apps/cowswap-frontend/src/legacy/state/swap/TradeGp.ts index d98caa57cd..6f80a40406 100644 --- a/apps/cowswap-frontend/src/legacy/state/swap/TradeGp.ts +++ b/apps/cowswap-frontend/src/legacy/state/swap/TradeGp.ts @@ -13,7 +13,7 @@ interface FeeInformation { amount: string } -export type FeeForTrade = { feeAsCurrency: CurrencyAmount } & Pick +export type FeeForTrade = { feeAsCurrency: CurrencyAmount } & FeeInformation type TradeExecutionPrice = CanonicalMarketParams | undefined> & { price?: PriceInformation } diff --git a/apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx b/apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx index cf6fb72d35..be106c7004 100644 --- a/apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx +++ b/apps/cowswap-frontend/src/modules/account/containers/Transaction/ActivityDetails.tsx @@ -6,7 +6,15 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' import { useENS } from '@cowprotocol/ens' import { TokenLogo, useTokenBySymbolOrAddress } from '@cowprotocol/tokens' import { UiOrderType } from '@cowprotocol/types' -import { ExternalLink, TokenAmount, UI } from '@cowprotocol/ui' +import { + ExternalLink, + TokenAmount, + UI, + Icon, + IconType, + BannerOrientation, + CustomRecipientWarningBanner, +} from '@cowprotocol/ui' import { CurrencyAmount } from '@uniswap/sdk-core' import { OrderProgressBar } from 'legacy/components/OrderProgressBar' @@ -19,8 +27,6 @@ import { EthFlowStepper } from 'modules/swap/containers/EthFlowStepper' import { useCancelOrder } from 'common/hooks/useCancelOrder' import { isPending } from 'common/hooks/useCategorizeRecentActivity' import { useGetSurplusData } from 'common/hooks/useGetSurplusFiatValue' -import { Icon, IconType } from 'common/pure/Icon' -import { BannerOrientation, CustomRecipientWarningBanner } from 'common/pure/InlineBanner/banners' import { RateInfo, RateInfoParams } from 'common/pure/RateInfo' import { SafeWalletLink } from 'common/pure/SafeWalletLink' import { diff --git a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx index 70f45d4185..d4bcbc9739 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx @@ -1,6 +1,5 @@ import { CHRISTMAS_THEME_ENABLED } from '@cowprotocol/common-const' import { isInjectedWidget } from '@cowprotocol/common-utils' -import { OnlyUniswapListAvailableBanner } from '@cowprotocol/tokens' import ErrorBoundary from 'legacy/components/ErrorBoundary' import Footer from 'legacy/components/Footer' @@ -32,7 +31,6 @@ export function App() { - diff --git a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx index 9f21517eca..3a84037d04 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/limitOrders/containers/LimitOrdersWarnings/index.tsx @@ -2,6 +2,7 @@ import { useAtomValue, useSetAtom } from 'jotai' import React, { useCallback, useEffect } from 'react' import { isFractionFalsy } from '@cowprotocol/common-utils' +import { BundleTxApprovalBanner, BundleTxSafeWcBanner, SmallVolumeWarningBanner } from '@cowprotocol/ui' import { useIsSafeViaWc, useWalletInfo } from '@cowprotocol/wallet' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' @@ -23,11 +24,6 @@ import { useTradeQuote } from 'modules/tradeQuote' import { useShouldZeroApprove } from 'modules/zeroApproval' import { HIGH_FEE_WARNING_PERCENTAGE } from 'common/constants/common' -import { - BundleTxApprovalBanner, - BundleTxSafeWcBanner, - SmallVolumeWarningBanner, -} from 'common/pure/InlineBanner/banners' import { ZeroApprovalWarning } from 'common/pure/ZeroApprovalWarning' import { calculatePercentageInRelationToReference } from 'utils/orderUtils/calculatePercentageInRelationToReference' diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx index 27838520c3..092eb8b93c 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/OrderRow/index.tsx @@ -7,7 +7,7 @@ import { getAddress, getEtherscanLink } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { TokenLogo } from '@cowprotocol/tokens' import { Command, UiOrderType } from '@cowprotocol/types' -import { Loader, TokenAmount, TokenSymbol, UI } from '@cowprotocol/ui' +import { Loader, TokenAmount, TokenSymbol, UI, ButtonSecondary } from '@cowprotocol/ui' import { Currency, CurrencyAmount, Percent, Price } from '@uniswap/sdk-core' import SVG from 'react-inlinesvg' @@ -28,7 +28,6 @@ import { OrderStatusBox } from 'modules/ordersTable/pure/OrderStatusBox' import { getIsEthFlowOrder } from 'modules/swap/containers/EthFlowStepper' import { useSafeMemo } from 'common/hooks/useSafeMemo' -import { ButtonSecondary } from 'common/pure/ButtonSecondary' import { RateInfo } from 'common/pure/RateInfo' import { getQuoteCurrency } from 'common/services/getQuoteCurrency' import { isOrderCancellable } from 'common/utils/isOrderCancellable' diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx index 10c332437b..e179f7b3bf 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTableContainer/index.tsx @@ -4,7 +4,7 @@ import cowMeditatingV2 from '@cowprotocol/assets/cow-swap/meditating-cow-v2.svg' import imageConnectWallet from '@cowprotocol/assets/cow-swap/wallet-plus.svg' import { isInjectedWidget } from '@cowprotocol/common-utils' import { ExternalLink } from '@cowprotocol/ui' -import { UI } from '@cowprotocol/ui' +import { UI, CowSwapSafeAppLink } from '@cowprotocol/ui' import { Trans } from '@lingui/macro' import SVG from 'react-inlinesvg' @@ -12,8 +12,6 @@ import styled from 'styled-components/macro' import { Web3Status } from 'modules/wallet/containers/Web3Status' -import { CowSwapSafeAppLink } from 'common/pure/CowSwapSafeAppLink' - import { OrdersTable, OrdersTableProps } from './OrdersTable' import { OrdersTabs, OrdersTabsProps } from './OrdersTabs' diff --git a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/index.tsx b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/index.tsx index 67f2ee62ef..c43374d4a2 100644 --- a/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/ordersTable/pure/ReceiptModal/index.tsx @@ -1,7 +1,15 @@ import { ExplorerDataType, getExplorerLink, isSellOrder, shortenAddress } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { Command } from '@cowprotocol/types' -import { ExternalLink, UI } from '@cowprotocol/ui' +import { + UI, + Icon, + IconType, + ExternalLink, + InlineBanner, + BannerOrientation, + CustomRecipientWarningBanner, +} from '@cowprotocol/ui' import { CurrencyAmount, Fraction, Token } from '@uniswap/sdk-core' import { OrderStatus } from 'legacy/state/orders/actions' @@ -10,9 +18,6 @@ import { CloseIcon } from 'legacy/theme' import { TwapOrderItem } from 'modules/twap/types' import { isPending } from 'common/hooks/useCategorizeRecentActivity' -import { Icon, IconType } from 'common/pure/Icon' -import { InlineBanner } from 'common/pure/InlineBanner' -import { BannerOrientation, CustomRecipientWarningBanner } from 'common/pure/InlineBanner/banners' import { CowModal } from 'common/pure/Modal' import { useHideReceiverWalletBanner, diff --git a/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts b/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts index f1ef7b1ee6..5404004120 100644 --- a/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts +++ b/apps/cowswap-frontend/src/modules/swap/helpers/getSwapButtonState.ts @@ -1,9 +1,11 @@ import { Token } from '@uniswap/sdk-core' import { QuoteError } from 'legacy/state/price/actions' +import { QuoteInformationObject } from 'legacy/state/price/reducer' import TradeGp from 'legacy/state/swap/TradeGp' import { getEthFlowEnabled } from 'modules/swap/helpers/getEthFlowEnabled' +import { isQuoteExpired } from 'modules/tradeQuote/utils/isQuoteExpired' import { ApprovalState } from 'common/hooks/useApproveState' @@ -18,6 +20,7 @@ export enum SwapButtonState { TransferToSmartContract = 'TransferToSmartContract', UnsupportedToken = 'UnsupportedToken', FetchQuoteError = 'FetchQuoteError', + QuoteExpired = 'QuoteExpired', OfflineBrowser = 'OfflineBrowser', Loading = 'Loading', WalletIsNotConnected = 'WalletIsNotConnected', @@ -39,7 +42,7 @@ export interface SwapButtonStateParams { isReadonlyGnosisSafeUser: boolean isSwapUnsupported: boolean isBundlingSupported: boolean - quoteError: QuoteError | undefined | null + quote: QuoteInformationObject | undefined | null inputError?: string approvalState: ApprovalState feeWarningAccepted: boolean @@ -66,7 +69,8 @@ const quoteErrorToSwapButtonState: { [key in QuoteError]: SwapButtonState | null } export function getSwapButtonState(input: SwapButtonStateParams): SwapButtonState { - const { quoteError, approvalState, isPermitSupported, amountsForSignature } = input + const { quote, approvalState, isPermitSupported, amountsForSignature } = input + const quoteError = quote?.error // show approve flow when: no error on inputs, not approved or pending, or approved in current session // never show if price impact is above threshold @@ -104,6 +108,10 @@ export function getSwapButtonState(input: SwapButtonStateParams): SwapButtonStat return SwapButtonState.ReadonlyGnosisSafeUser } + if (isQuoteExpired(quote?.fee?.expirationDate) === true) { + return SwapButtonState.QuoteExpired + } + if (!input.isNativeIn && showApproveFlow) { if (input.isBundlingSupported) { return SwapButtonState.ApproveAndSwap diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts index da502128cb..482e725397 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useFlowContext.ts @@ -89,7 +89,7 @@ export function useBaseFlowContextSetup(): BaseFlowContextSetup { const { allowsOffchainSigning } = useWalletDetails() const gnosisSafeInfo = useGnosisSafeInfo() const { recipient } = useSwapState() - const { v2Trade: trade } = useDerivedSwapInfo() + const { v2Trade: trade, allowedSlippage } = useDerivedSwapInfo() const swapZeroFee = useSwapZeroFee() const appData = useAppData() @@ -107,7 +107,6 @@ export function useBaseFlowContextSetup(): BaseFlowContextSetup { const isSafeEthFlow = useIsSafeEthFlow() const getCachedPermit = useGetCachedPermit() - const { allowedSlippage } = useDerivedSwapInfo() const [inputAmountWithSlippage, outputAmountWithSlippage] = useSwapAmountsWithSlippage() const sellTokenContract = useTokenContract(getAddress(inputAmountWithSlippage?.currency) || undefined, true) diff --git a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts index 4796cf66dd..632560b33f 100644 --- a/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts +++ b/apps/cowswap-frontend/src/modules/swap/hooks/useSwapButtonContext.ts @@ -132,7 +132,7 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext isSwapUnsupported, isNativeIn: isNativeInSwap, wrappedToken, - quoteError: quote?.error, + quote, inputError: swapInputError, approvalState, feeWarningAccepted, @@ -166,6 +166,6 @@ function useHasEnoughWrappedBalanceForSwap(inputAmount?: CurrencyAmountError loading price. Try again later. ), + [SwapButtonState.QuoteExpired]: () => ( + + Quote expired. Refreshing... + + ), [SwapButtonState.UnsupportedToken]: () => ( Unsupported token diff --git a/apps/cowswap-frontend/src/modules/swap/pure/TradeRates/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/swap/pure/TradeRates/index.cosmos.tsx index c23d86bc0f..c0c915b593 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/TradeRates/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/TradeRates/index.cosmos.tsx @@ -20,7 +20,11 @@ const trade = new TradeGp({ inputAmountWithoutFee: CurrencyAmount.fromRawAmount(currency, amount * 10 ** 18), outputAmount: CurrencyAmount.fromRawAmount(currency, output * 10 ** 18), outputAmountWithoutFee: CurrencyAmount.fromRawAmount(currency, (output - 3) * 10 ** 18), - fee: { feeAsCurrency: CurrencyAmount.fromRawAmount(currency, 3 * 10 ** 18), amount: '50' }, + fee: { + feeAsCurrency: CurrencyAmount.fromRawAmount(currency, 3 * 10 ** 18), + amount: '50', + expirationDate: new Date().toISOString(), + }, executionPrice: new Price(currency, currencyOut, 1, 4), tradeType: TradeType.EXACT_INPUT, quoteId: 10000, diff --git a/apps/cowswap-frontend/src/modules/swap/pure/TradeSummary/index.cosmos.tsx b/apps/cowswap-frontend/src/modules/swap/pure/TradeSummary/index.cosmos.tsx index e6029d9b64..531636f97b 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/TradeSummary/index.cosmos.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/TradeSummary/index.cosmos.tsx @@ -1,6 +1,6 @@ import { COW, GNO } from '@cowprotocol/common-const' import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { CurrencyAmount, TradeType, Price, Percent } from '@uniswap/sdk-core' +import { CurrencyAmount, Percent, Price, TradeType } from '@uniswap/sdk-core' import TradeGp from 'legacy/state/swap/TradeGp' @@ -20,7 +20,11 @@ const trade = new TradeGp({ inputAmountWithoutFee: CurrencyAmount.fromRawAmount(currency, amount * 10 ** 18), outputAmount: CurrencyAmount.fromRawAmount(currency, output * 10 ** 18), outputAmountWithoutFee: CurrencyAmount.fromRawAmount(currency, (output - 3) * 10 ** 18), - fee: { feeAsCurrency: CurrencyAmount.fromRawAmount(currency, 3 * 10 ** 18), amount: '50' }, + fee: { + feeAsCurrency: CurrencyAmount.fromRawAmount(currency, 3 * 10 ** 18), + amount: '50', + expirationDate: new Date().toISOString(), + }, executionPrice: new Price(currency, currencyOut, 1, 4), tradeType: TradeType.EXACT_INPUT, quoteId: 10000, diff --git a/apps/cowswap-frontend/src/modules/swap/pure/banners/TwapSuggestionBanner.tsx b/apps/cowswap-frontend/src/modules/swap/pure/banners/TwapSuggestionBanner.tsx index c5a3b550fa..e37bf9c095 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/banners/TwapSuggestionBanner.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/banners/TwapSuggestionBanner.tsx @@ -1,4 +1,5 @@ import { OrderKind, SupportedChainId } from '@cowprotocol/cow-sdk' +import { InlineBanner } from '@cowprotocol/ui' import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import { NavLink } from 'react-router-dom' @@ -9,7 +10,6 @@ import { parameterizeTradeRoute } from 'modules/trade/utils/parameterizeTradeRou import { parameterizeTradeSearch } from 'modules/trade/utils/parameterizeTradeSearch' import { Routes } from 'common/constants/routes' -import { InlineBanner } from 'common/pure/InlineBanner' const StyledNavLink = styled(NavLink)` color: inherit; diff --git a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx index ed454f69a0..3958512cfd 100644 --- a/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx +++ b/apps/cowswap-frontend/src/modules/swap/pure/warnings.tsx @@ -2,6 +2,7 @@ import React from 'react' import { genericPropsChecker } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' +import { BundleTxApprovalBanner, BundleTxSafeWcBanner, BundleTxWrapBanner } from '@cowprotocol/ui' import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import styled from 'styled-components/macro' @@ -13,7 +14,6 @@ import { CompatibilityIssuesWarning } from 'modules/trade/pure/CompatibilityIssu import { NoImpactWarning } from 'modules/trade/pure/NoImpactWarning' import { TradeUrlParams } from 'modules/trade/types/TradeRawState' -import { BundleTxApprovalBanner, BundleTxSafeWcBanner, BundleTxWrapBanner } from 'common/pure/InlineBanner/banners' import { ZeroApprovalWarning } from 'common/pure/ZeroApprovalWarning' import { TwapSuggestionBanner } from './banners/TwapSuggestionBanner' diff --git a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts index 8c2b32b226..994a58f4b6 100644 --- a/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts +++ b/apps/cowswap-frontend/src/modules/swap/services/ethFlow/index.ts @@ -1,4 +1,4 @@ -import { reportAppDataWithHooks } from '@cowprotocol/common-utils' +import { reportAppDataWithHooks, reportPlaceOrderWithExpiredQuote } from '@cowprotocol/common-utils' import { CowEvents } from '@cowprotocol/events' import { Percent } from '@uniswap/sdk-core' @@ -14,6 +14,7 @@ import { addPendingOrderStep } from 'modules/trade/utils/addPendingOrderStep' import { tradeFlowAnalytics } from 'modules/trade/utils/analytics' import { logTradeFlow } from 'modules/trade/utils/logger' import { getSwapErrorMessage } from 'modules/trade/utils/swapErrorHelper' +import { isQuoteExpired } from 'modules/tradeQuote/utils/isQuoteExpired' import { calculateUniqueOrderId } from './steps/calculateUniqueOrderId' @@ -36,7 +37,7 @@ export async function ethFlow( swapZeroFee, } = ethFlowContext const { - trade: { inputAmount, outputAmount }, + trade: { inputAmount, outputAmount, fee }, } = context const tradeAmounts = { inputAmount, outputAmount } @@ -66,6 +67,12 @@ export async function ethFlow( ) try { + // Do not proceed if fee is expired + if (isQuoteExpired(fee.expirationDate)) { + reportPlaceOrderWithExpiredQuote({ ...orderParamsOriginal, fee }) + throw new Error('Quote expired. Please refresh.') + } + logTradeFlow('ETH FLOW', 'STEP 4: sign order') const { order, txReceipt } = await signEthFlowOrderStep(orderId, orderParams, contract, addInFlightOrderId).finally( () => { diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx index 59e409900b..f730facf1c 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx @@ -1,6 +1,7 @@ import { useCallback, useMemo, useRef } from 'react' import { TokenWithLogo } from '@cowprotocol/common-const' +import { BannerOrientation, ExternalLink, InlineBanner, LINK_GUIDE_ADD_CUSTOM_TOKEN } from '@cowprotocol/ui' import { useVirtualizer } from '@tanstack/react-virtual' import ms from 'ms.macro' @@ -63,6 +64,19 @@ export function TokensVirtualList(props: TokensVirtualListProps) { + +

+ Can't find your token on the list?{' '} + Read our guide on how to add custom + tokens. +

+
+ {items.map((virtualRow) => { const token = sortedTokens[virtualRow.index] const addressLowerCase = token.address.toLowerCase() diff --git a/apps/cowswap-frontend/src/modules/trade/containers/SellNativeWarningBanner/index.tsx b/apps/cowswap-frontend/src/modules/trade/containers/SellNativeWarningBanner/index.tsx index b8794a2dd9..fb562c967a 100644 --- a/apps/cowswap-frontend/src/modules/trade/containers/SellNativeWarningBanner/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/containers/SellNativeWarningBanner/index.tsx @@ -1,8 +1,8 @@ import { OrderKind } from '@cowprotocol/cow-sdk' +import { SellNativeWarningBanner as Pure } from '@cowprotocol/ui' import { Field } from 'legacy/state/types' -import { SellNativeWarningBanner as Pure } from 'common/pure/InlineBanner/banners' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import { useDerivedTradeState } from '../../hooks/useDerivedTradeState' diff --git a/apps/cowswap-frontend/src/modules/trade/pure/PriceUpdatedBanner/index.tsx b/apps/cowswap-frontend/src/modules/trade/pure/PriceUpdatedBanner/index.tsx index 65fe8d9243..81448f4f5f 100644 --- a/apps/cowswap-frontend/src/modules/trade/pure/PriceUpdatedBanner/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/pure/PriceUpdatedBanner/index.tsx @@ -1,9 +1,9 @@ import { UI } from '@cowprotocol/ui' +import { InlineBanner } from '@cowprotocol/ui' import { Trans } from '@lingui/macro' import styled from 'styled-components/macro' -import { InlineBanner } from 'common/pure/InlineBanner' const Wrapper = styled.div` display: flex; diff --git a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx index 94afa698dc..f41d9f18ca 100644 --- a/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx +++ b/apps/cowswap-frontend/src/modules/trade/pure/TradeConfirmation/index.tsx @@ -1,6 +1,14 @@ import React, { useEffect, useRef, useState } from 'react' -import { ButtonSize, ButtonPrimary, BackButton, CenteredDots, LongLoadText } from '@cowprotocol/ui' +import { + ButtonSize, + ButtonPrimary, + BackButton, + CenteredDots, + LongLoadText, + BannerOrientation, + CustomRecipientWarningBanner, +} from '@cowprotocol/ui' import { Trans } from '@lingui/macro' import ms from 'ms.macro' @@ -9,7 +17,6 @@ import { useMediaQuery, upToMedium } from 'legacy/hooks/useMediaQuery' import { PriceImpact } from 'legacy/hooks/usePriceImpact' import { CurrencyAmountPreview, CurrencyPreviewInfo } from 'common/pure/CurrencyInputPanel' -import { BannerOrientation, CustomRecipientWarningBanner } from 'common/pure/InlineBanner/banners' import { QuoteCountdown } from './CountDown' import { useIsPriceChanged } from './hooks/useIsPriceChanged' diff --git a/apps/cowswap-frontend/src/modules/tradeFormValidation/pure/TradeFormButtons/tradeButtonsMap.tsx b/apps/cowswap-frontend/src/modules/tradeFormValidation/pure/TradeFormButtons/tradeButtonsMap.tsx index 19dbfa1523..84f624b44e 100644 --- a/apps/cowswap-frontend/src/modules/tradeFormValidation/pure/TradeFormButtons/tradeButtonsMap.tsx +++ b/apps/cowswap-frontend/src/modules/tradeFormValidation/pure/TradeFormButtons/tradeButtonsMap.tsx @@ -96,6 +96,7 @@ export const tradeButtonsMap: Record ) }, + [TradeFormValidation.QuoteExpired]: { text: 'Quote expired. Refreshing...' }, [TradeFormValidation.WalletNotConnected]: (context) => { return ( diff --git a/apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts b/apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts index 9fe854ae6f..2767e3e935 100644 --- a/apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts +++ b/apps/cowswap-frontend/src/modules/tradeFormValidation/services/validateTradeForm.ts @@ -1,5 +1,8 @@ import { getIsNativeToken, isAddress, isFractionFalsy } from '@cowprotocol/common-utils' +import { TradeType } from 'modules/trade' +import { isQuoteExpired } from 'modules/tradeQuote/utils/isQuoteExpired' + import { ApprovalState } from 'common/hooks/useApproveState' import { TradeFormValidation, TradeFormValidationContext } from '../types' @@ -88,5 +91,14 @@ export function validateTradeForm(context: TradeFormValidationContext): TradeFor return TradeFormValidation.ApproveRequired } + if ( + !isWrapUnwrap && + derivedTradeState.tradeType !== TradeType.LIMIT_ORDER && + !tradeQuote.isLoading && + isQuoteExpired(tradeQuote.response?.expiration) === true + ) { + return TradeFormValidation.QuoteExpired + } + return null } diff --git a/apps/cowswap-frontend/src/modules/tradeFormValidation/types.ts b/apps/cowswap-frontend/src/modules/tradeFormValidation/types.ts index d2cbfad2b9..ea3cc9364c 100644 --- a/apps/cowswap-frontend/src/modules/tradeFormValidation/types.ts +++ b/apps/cowswap-frontend/src/modules/tradeFormValidation/types.ts @@ -25,6 +25,7 @@ export enum TradeFormValidation { // Quote loading indicator QuoteLoading, + QuoteExpired, // Balances BalancesNotLoaded, diff --git a/apps/cowswap-frontend/src/modules/tradeQuote/utils/isQuoteExpired.ts b/apps/cowswap-frontend/src/modules/tradeQuote/utils/isQuoteExpired.ts new file mode 100644 index 0000000000..8b7c8bc4ff --- /dev/null +++ b/apps/cowswap-frontend/src/modules/tradeQuote/utils/isQuoteExpired.ts @@ -0,0 +1,14 @@ +import ms from 'ms.macro' +import { Nullish } from 'types' + +const EXPIRATION_TIME_DELTA = ms`5s` + +export function isQuoteExpired(expirationDate: Nullish): boolean | undefined { + if (!expirationDate) { + return undefined + } + + const expirationTime = new Date(expirationDate).getTime() + + return Date.now() > expirationTime - EXPIRATION_TIME_DELTA +} diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx index 1e226b389b..fb8da5f031 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/index.tsx @@ -2,6 +2,7 @@ import { useAtomValue, useSetAtom } from 'jotai' import { useCallback } from 'react' import { modifySafeHandlerAnalytics } from '@cowprotocol/analytics' +import { BundleTxApprovalBanner } from '@cowprotocol/ui' import { useIsSafeViaWc, useWalletInfo } from '@cowprotocol/wallet' import { useAdvancedOrdersDerivedState } from 'modules/advancedOrders' @@ -12,7 +13,6 @@ import { TradeFormValidation, useGetTradeFormValidation } from 'modules/tradeFor import { useTradeQuoteFeeFiatAmount } from 'modules/tradeQuote' import { useShouldZeroApprove } from 'modules/zeroApproval' -import { BundleTxApprovalBanner } from 'common/pure/InlineBanner/banners' import { ZeroApprovalWarning } from 'common/pure/ZeroApprovalWarning' import { diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/BigPartTimeWarning.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/BigPartTimeWarning.tsx index c209b5ca65..53ce08df83 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/BigPartTimeWarning.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/BigPartTimeWarning.tsx @@ -1,4 +1,4 @@ -import { InlineBanner } from 'common/pure/InlineBanner' +import { InlineBanner } from '@cowprotocol/ui' import { MAX_PART_TIME } from '../../../const' import { deadlinePartsDisplay } from '../../../utils/deadlinePartsDisplay' diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/FallbackHandlerWarning.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/FallbackHandlerWarning.tsx index 76be392d47..0fcd691ba8 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/FallbackHandlerWarning.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/FallbackHandlerWarning.tsx @@ -1,11 +1,9 @@ -import { ExternalLink } from '@cowprotocol/ui' +import { ExternalLink, InlineBanner } from '@cowprotocol/ui' import styled from 'styled-components/macro' import { UNSUPPORTED_SAFE_LINK } from 'modules/twap/const' -import { InlineBanner } from 'common/pure/InlineBanner' - const Wrapper = styled.div` display: flex; flex-flow: column wrap; diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPartTimeWarning.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPartTimeWarning.tsx index 9f7d177e55..7b8143a16a 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPartTimeWarning.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPartTimeWarning.tsx @@ -1,4 +1,4 @@ -import { InlineBanner } from 'common/pure/InlineBanner' +import { InlineBanner } from '@cowprotocol/ui' import { MINIMUM_PART_TIME } from '../../../const' import { deadlinePartsDisplay } from '../../../utils/deadlinePartsDisplay' diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPartVolumeWarning.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPartVolumeWarning.tsx index a72afcfb3b..13d9c31f14 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPartVolumeWarning.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPartVolumeWarning.tsx @@ -1,7 +1,5 @@ import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { TokenAmount } from '@cowprotocol/ui' - -import { InlineBanner } from 'common/pure/InlineBanner' +import { TokenAmount, InlineBanner } from '@cowprotocol/ui' import { MINIMUM_PART_SELL_AMOUNT_FIAT } from '../../../const' diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPriceProtectionWarning.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPriceProtectionWarning.tsx index 8aa300dc92..77d3b126c7 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPriceProtectionWarning.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SmallPriceProtectionWarning.tsx @@ -1,4 +1,4 @@ -import { InlineBanner } from 'common/pure/InlineBanner' +import { InlineBanner } from '@cowprotocol/ui' export function SmallPriceProtectionWarning() { return ( diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SwapPriceDifferenceWarning.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SwapPriceDifferenceWarning.tsx index 5500199b33..adf96b976d 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SwapPriceDifferenceWarning.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/SwapPriceDifferenceWarning.tsx @@ -1,4 +1,4 @@ -import { FiatAmount, TokenAmount } from '@cowprotocol/ui' +import { FiatAmount, TokenAmount, InlineBanner } from '@cowprotocol/ui' import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { NavLink } from 'react-router-dom' @@ -8,7 +8,6 @@ import { TradeUrlParams } from 'modules/trade/types/TradeRawState' import { parameterizeTradeRoute } from 'modules/trade/utils/parameterizeTradeRoute' import { Routes } from 'common/constants/routes' -import { InlineBanner } from 'common/pure/InlineBanner' import { SwapAmountDifference } from '../../../state/swapAmountDifferenceAtom' diff --git a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/UnsupportedWalletWarning.tsx b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/UnsupportedWalletWarning.tsx index 80d87b6fac..37d1457dad 100644 --- a/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/UnsupportedWalletWarning.tsx +++ b/apps/cowswap-frontend/src/modules/twap/containers/TwapFormWarnings/warnings/UnsupportedWalletWarning.tsx @@ -1,10 +1,7 @@ -import { ExternalLink } from '@cowprotocol/ui' +import { ExternalLink, InlineBanner, CowSwapSafeAppLink } from '@cowprotocol/ui' import { UNSUPPORTED_WALLET_LINK } from 'modules/twap/const' -import { CowSwapSafeAppLink } from 'common/pure/CowSwapSafeAppLink' -import { InlineBanner } from 'common/pure/InlineBanner' - export function UnsupportedWalletWarning({ isSafeViaWc }: { isSafeViaWc: boolean }) { if (isSafeViaWc) { return ( diff --git a/apps/cowswap-frontend/src/pages/Claim/CanUserClaimMessage.tsx b/apps/cowswap-frontend/src/pages/Claim/CanUserClaimMessage.tsx index 7605a44820..0761aca016 100644 --- a/apps/cowswap-frontend/src/pages/Claim/CanUserClaimMessage.tsx +++ b/apps/cowswap-frontend/src/pages/Claim/CanUserClaimMessage.tsx @@ -8,10 +8,10 @@ import { Trans } from '@lingui/macro' import SVG from 'react-inlinesvg' import { ClaimStatus } from 'legacy/state/claim/actions' -import { useClaimState, useClaimTimeInfo, useClaimLinks } from 'legacy/state/claim/hooks' +import { useClaimLinks, useClaimState, useClaimTimeInfo } from 'legacy/state/claim/hooks' import { ClaimCommonTypes } from 'legacy/state/claim/types' -import { IntroDescription, BannerExplainer } from './styled' +import { BannerExplainer, IntroDescription } from './styled' type ClaimIntroductionProps = Pick< ClaimCommonTypes, diff --git a/apps/cowswap-frontend/src/pages/Claim/ClaimAddress.tsx b/apps/cowswap-frontend/src/pages/Claim/ClaimAddress.tsx index 954752f675..2723fe677e 100644 --- a/apps/cowswap-frontend/src/pages/Claim/ClaimAddress.tsx +++ b/apps/cowswap-frontend/src/pages/Claim/ClaimAddress.tsx @@ -12,7 +12,7 @@ import { useClaimDispatchers, useClaimState } from 'legacy/state/claim/hooks' import { ClaimCommonTypes } from 'legacy/state/claim/types' import { CustomLightSpinner, ThemedText } from 'legacy/theme' -import { CheckAddress, InputField, InputFieldTitle, InputErrorText } from './styled' +import { CheckAddress, InputErrorText, InputField, InputFieldTitle } from './styled' export type ClaimAddressProps = Pick & { toggleWalletModal: Command | null diff --git a/apps/cowswap-frontend/src/pages/Claim/ClaimNav.tsx b/apps/cowswap-frontend/src/pages/Claim/ClaimNav.tsx index 356e317e2f..0f3dde7dce 100644 --- a/apps/cowswap-frontend/src/pages/Claim/ClaimNav.tsx +++ b/apps/cowswap-frontend/src/pages/Claim/ClaimNav.tsx @@ -7,7 +7,7 @@ import { ClaimStatus } from 'legacy/state/claim/actions' import { useClaimDispatchers, useClaimState } from 'legacy/state/claim/hooks' import { ClaimCommonTypes } from 'legacy/state/claim/types' -import { TopNav, ClaimAccount, ClaimAccountButtons } from './styled' +import { ClaimAccount, ClaimAccountButtons, TopNav } from './styled' type ClaimNavProps = Pick diff --git a/apps/cowswap-frontend/src/pages/Claim/ClaimingStatus.tsx b/apps/cowswap-frontend/src/pages/Claim/ClaimingStatus.tsx index 35b1c9692a..123b3a58a9 100644 --- a/apps/cowswap-frontend/src/pages/Claim/ClaimingStatus.tsx +++ b/apps/cowswap-frontend/src/pages/Claim/ClaimingStatus.tsx @@ -5,8 +5,7 @@ import discordImage from '@cowprotocol/assets/cow-swap/discord.svg' import twitterImage from '@cowprotocol/assets/cow-swap/twitter.svg' import { V_COW } from '@cowprotocol/common-const' import { shortenAddress } from '@cowprotocol/common-utils' -import { TokenAmount, ButtonSecondary } from '@cowprotocol/ui' -import { ExternalLink } from '@cowprotocol/ui' +import { ExternalLink, TokenAmount, ButtonSecondary } from '@cowprotocol/ui' import { useWalletInfo } from '@cowprotocol/wallet' import { CurrencyAmount } from '@uniswap/sdk-core' diff --git a/libs/common-utils/src/sentry.ts b/libs/common-utils/src/sentry.ts index 90745bce86..06636f5d01 100644 --- a/libs/common-utils/src/sentry.ts +++ b/libs/common-utils/src/sentry.ts @@ -15,3 +15,10 @@ export function reportAppDataWithHooks(params: Record): void { contexts: { params }, }) } + +export function reportPlaceOrderWithExpiredQuote(params: Record): void { + Sentry.captureException('Attempt to place order with expired quote', { + tags: { errorType: 'placeOrderWithExpiredQuote' }, + contexts: { params }, + }) +} diff --git a/libs/tokens/src/containers/OnlyUniswapListAvailableBanner/index.tsx b/libs/tokens/src/containers/OnlyUniswapListAvailableBanner/index.tsx deleted file mode 100644 index 5edcf93dfd..0000000000 --- a/libs/tokens/src/containers/OnlyUniswapListAvailableBanner/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useIsCuratedListAvailable } from '../../hooks/lists/useIsCuratedListAvailable' -import styled from 'styled-components/macro' -import { UI } from '@cowprotocol/ui' -import { atomWithStorage } from 'jotai/utils' -import { useAtom } from 'jotai' -import { X } from 'react-feather' - -const Wrapper = styled.div` - width: 100%; - padding: 6px 0; - font-size: 14px; - text-align: center; - background-color: var(${UI.COLOR_PRIMARY}); - color: var(${UI.COLOR_WHITE}); - position: relative; -` - -const CloseIcon = styled(X)` - height: 28px; - width: 28px; - opacity: 0.6; - transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out; - position: absolute; - right: 10px; - top: 50%; - margin-top: -15px; - - &:hover { - cursor: pointer; - opacity: 1; - } - - > line { - stroke: var(${UI.COLOR_WHITE}); - } -` - -const bannerClosedAtom = atomWithStorage('OnlyUniswapListAvailableBanner', false) - -/** - * TODO: get review from Michel - */ -export function OnlyUniswapListAvailableBanner() { - const [isBannerClose, setBannerClosed] = useAtom(bannerClosedAtom) - const isOnlyUniswapListAvailable = useIsCuratedListAvailable() - - if (!isOnlyUniswapListAvailable || isBannerClose) return null - - return ( - -

The list of assets for trading is restricted due to your geographical location

- setBannerClosed(true)} /> -
- ) -} diff --git a/libs/tokens/src/index.ts b/libs/tokens/src/index.ts index 7759e144c8..c114893645 100644 --- a/libs/tokens/src/index.ts +++ b/libs/tokens/src/index.ts @@ -1,6 +1,5 @@ // Containers import { userAddedTokenListsAtomv2Migration } from './migrations/userAddedTokenListsAtomv2Migration' -export { OnlyUniswapListAvailableBanner } from './containers/OnlyUniswapListAvailableBanner' // Run migrations first of all // TODO: remove it after 01.04.2024 diff --git a/libs/ui/src/consts.ts b/libs/ui/src/consts.ts index 2ff084b7c6..bc5e196cf6 100644 --- a/libs/ui/src/consts.ts +++ b/libs/ui/src/consts.ts @@ -1 +1,3 @@ export const AMOUNTS_FORMATTING_FEATURE_FLAG = 'highlight-amounts-formatting' +export const SAFE_COW_APP_LINK = 'https://app.safe.global/share/safe-app?appUrl=https%3A%2F%2Fswap.cow.fi&chain=eth' +export const LINK_GUIDE_ADD_CUSTOM_TOKEN = 'https://blog.cow.fi/how-to-add-custom-tokens-on-cow-swap-a72d677c78c0' diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts index fc52b7318e..18a7a12f08 100644 --- a/libs/ui/src/index.ts +++ b/libs/ui/src/index.ts @@ -1,6 +1,12 @@ export * from './pure/Button' +export * from './pure/ButtonSecondaryAlt' +export * from './pure/CowSwapSafeAppLink' export * from './pure/CenteredDots' export * from './pure/LongLoadText' +export * from './pure/InlineBanner' +export * from './pure/InlineBanner/banners' +export * from './pure/Icon' +export * from './pure/LinkStyledButton' export * from './pure/Loader' export { loadingOpacityMixin, LoadingRows } from './pure/Loader/styled' export * from './pure/Row' @@ -15,3 +21,4 @@ export * from './pure/ExternalLink' export * from './pure/BackButton' export * from './enum' export * from './types' +export * from './consts' diff --git a/libs/ui/src/pure/Button/index.tsx b/libs/ui/src/pure/Button/index.tsx index b8ec23a0c2..8710a9655b 100644 --- a/libs/ui/src/pure/Button/index.tsx +++ b/libs/ui/src/pure/Button/index.tsx @@ -5,7 +5,6 @@ import { ButtonProps } from 'rebass/styled-components' import styled from 'styled-components' import { RowBetween } from '../Row' -import { ButtonSize, UI } from '../../enum' import { ButtonConfirmedStyle as ButtonConfirmedStyleMod, @@ -14,6 +13,7 @@ import { ButtonOutlined as ButtonOutlinedMod, ButtonPrimary as ButtonPrimaryMod, } from './ButtonMod' +import { ButtonSize, UI } from '../../enum' export * from './ButtonMod' diff --git a/apps/cowswap-frontend/src/common/pure/ButtonSecondary/index.tsx b/libs/ui/src/pure/ButtonSecondaryAlt/index.tsx similarity index 80% rename from apps/cowswap-frontend/src/common/pure/ButtonSecondary/index.tsx rename to libs/ui/src/pure/ButtonSecondaryAlt/index.tsx index fbc6374c73..16f56ab8df 100644 --- a/apps/cowswap-frontend/src/common/pure/ButtonSecondary/index.tsx +++ b/libs/ui/src/pure/ButtonSecondaryAlt/index.tsx @@ -1,8 +1,8 @@ -import { UI } from '@cowprotocol/ui' +import { UI } from '../../enum' import styled from 'styled-components/macro' -export const ButtonSecondary = styled.button<{ padding?: string; minHeight?: string }>` +export const ButtonSecondaryAlt = styled.button<{ padding?: string; minHeight?: string }>` background: var(${UI.COLOR_PRIMARY}); color: var(${UI.COLOR_BUTTON_TEXT}); font-size: 12px; diff --git a/apps/cowswap-frontend/src/common/pure/CowSwapSafeAppLink/index.tsx b/libs/ui/src/pure/CowSwapSafeAppLink/index.tsx similarity index 53% rename from apps/cowswap-frontend/src/common/pure/CowSwapSafeAppLink/index.tsx rename to libs/ui/src/pure/CowSwapSafeAppLink/index.tsx index 3b6ca0db35..87b09bb1fc 100644 --- a/apps/cowswap-frontend/src/common/pure/CowSwapSafeAppLink/index.tsx +++ b/libs/ui/src/pure/CowSwapSafeAppLink/index.tsx @@ -1,6 +1,5 @@ -import { ExternalLink } from '@cowprotocol/ui' - -import { SAFE_COW_APP_LINK } from 'common/constants/common' +import { ExternalLink } from '../ExternalLink' +import { SAFE_COW_APP_LINK } from '../../consts' export function CowSwapSafeAppLink() { return CoW Swap Safe App↗ diff --git a/apps/cowswap-frontend/src/common/pure/Icon/index.cosmos.tsx b/libs/ui/src/pure/Icon/index.cosmos.tsx similarity index 84% rename from apps/cowswap-frontend/src/common/pure/Icon/index.cosmos.tsx rename to libs/ui/src/pure/Icon/index.cosmos.tsx index 377bc4204d..7415703ce6 100644 --- a/apps/cowswap-frontend/src/common/pure/Icon/index.cosmos.tsx +++ b/libs/ui/src/pure/Icon/index.cosmos.tsx @@ -1,8 +1,6 @@ -import { UI } from '@cowprotocol/ui' - import styled from 'styled-components/macro' - -import { Icon, IconType } from './index' +import { UI } from '../../enum' +import { Icon, IconType } from '.' const Wrapper = styled.div` width: 400px; diff --git a/apps/cowswap-frontend/src/common/pure/Icon/index.tsx b/libs/ui/src/pure/Icon/index.tsx similarity index 98% rename from apps/cowswap-frontend/src/common/pure/Icon/index.tsx rename to libs/ui/src/pure/Icon/index.tsx index fe70dc4fe4..88ddb0f484 100644 --- a/apps/cowswap-frontend/src/common/pure/Icon/index.tsx +++ b/libs/ui/src/pure/Icon/index.tsx @@ -2,7 +2,8 @@ import iconInformation from '@cowprotocol/assets/cow-swap/alert-circle.svg' import iconAlert from '@cowprotocol/assets/cow-swap/alert.svg' import iconDanger from '@cowprotocol/assets/cow-swap/alert.svg' import iconSuccess from '@cowprotocol/assets/cow-swap/check.svg' -import { UI } from '@cowprotocol/ui' + +import { UI } from '../../enum' import SVG from 'react-inlinesvg' import styled from 'styled-components/macro' diff --git a/apps/cowswap-frontend/src/common/pure/InlineBanner/banners.tsx b/libs/ui/src/pure/InlineBanner/banners.tsx similarity index 96% rename from apps/cowswap-frontend/src/common/pure/InlineBanner/banners.tsx rename to libs/ui/src/pure/InlineBanner/banners.tsx index 6cee55e2e0..9dbecf329b 100644 --- a/apps/cowswap-frontend/src/common/pure/InlineBanner/banners.tsx +++ b/libs/ui/src/pure/InlineBanner/banners.tsx @@ -1,13 +1,11 @@ import { Command } from '@cowprotocol/types' -import { TokenAmount } from '@cowprotocol/ui' import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import styled from 'styled-components/macro' -import { Nullish } from 'types' - -import { LinkStyledButton } from 'legacy/theme' - -import { ButtonSecondary } from '../ButtonSecondary' +import { TokenAmount } from '../TokenAmount' +import { Nullish } from '../../types' +import { LinkStyledButton } from '../LinkStyledButton' +import { ButtonSecondary } from '../Button' import { CowSwapSafeAppLink } from '../CowSwapSafeAppLink' import { InlineBanner, InlineBannerProps } from './index' diff --git a/apps/cowswap-frontend/src/common/pure/InlineBanner/index.cosmos.tsx b/libs/ui/src/pure/InlineBanner/index.cosmos.tsx similarity index 100% rename from apps/cowswap-frontend/src/common/pure/InlineBanner/index.cosmos.tsx rename to libs/ui/src/pure/InlineBanner/index.cosmos.tsx diff --git a/apps/cowswap-frontend/src/common/pure/InlineBanner/index.tsx b/libs/ui/src/pure/InlineBanner/index.tsx similarity index 80% rename from apps/cowswap-frontend/src/common/pure/InlineBanner/index.tsx rename to libs/ui/src/pure/InlineBanner/index.tsx index a3550e72c2..e5a0b5abb7 100644 --- a/apps/cowswap-frontend/src/common/pure/InlineBanner/index.tsx +++ b/libs/ui/src/pure/InlineBanner/index.tsx @@ -1,12 +1,11 @@ import { ReactNode } from 'react' -import { UI } from '@cowprotocol/ui' +import { UI } from '../../enum' +import { Icon, IconType } from '../Icon' +import { BannerOrientation } from './banners' import styled from 'styled-components/macro' -import { Icon, IconType } from 'common/pure/Icon' -import { BannerOrientation } from 'common/pure/InlineBanner/banners' - export type BannerType = 'alert' | 'information' | 'success' | 'danger' | 'savings' interface ColorEnums { @@ -60,6 +59,8 @@ const Wrapper = styled.span<{ orientation?: BannerOrientation iconSize?: number padding?: string + margin?: string + width?: string }>` display: flex; align-items: center; @@ -68,13 +69,14 @@ const Wrapper = styled.span<{ color: ${({ colorEnums }) => `var(${colorEnums.text})`}; gap: 24px 10px; border-radius: ${({ borderRadius = '16px' }) => borderRadius}; - margin: auto; + margin: ${({ margin = 'auto' }) => margin}; padding: ${({ padding = '16px' }) => padding}; font-size: 14px; font-weight: 400; line-height: 1.2; - width: 100%; + width: ${({ width = '100%' }) => width}; + // Icon + Text content wrapper > span { display: flex; justify-content: center; @@ -83,21 +85,31 @@ const Wrapper = styled.span<{ orientation === BannerOrientation.Horizontal ? 'row' : 'column wrap'}; gap: 10px; width: 100%; + } + + // Text content + > span > span { + display: flex; + flex-flow: row wrap; + align-items: center; + gap: 10px; + justify-content: ${({ orientation = BannerOrientation.Vertical }) => + orientation === BannerOrientation.Horizontal ? 'flex-start' : 'center'}; + } - ${({ theme }) => theme.mediaWidth.upToSmall` - flex-flow: column wrap; - gap: 16px; - `}; + > span > span a { + color: inherit; + text-decoration: underline; } - > span > strong { + > span > span > strong { display: flex; align-items: center; gap: 6px; color: ${({ colorEnums }) => `var(${colorEnums.text})`}; } - > span > p { + > span > span > p { line-height: 1.4; margin: auto; padding: 0; @@ -106,7 +118,7 @@ const Wrapper = styled.span<{ orientation === BannerOrientation.Horizontal ? 'left' : 'center'}; } - > span > i { + > span > span > i { font-style: normal; font-size: 32px; line-height: 1; @@ -123,6 +135,8 @@ export type InlineBannerProps = { iconSize?: number iconPadding?: string padding?: string + margin?: string + width?: string } export function InlineBanner({ @@ -133,8 +147,10 @@ export function InlineBanner({ borderRadius, orientation, iconSize, - iconPadding, + iconPadding = '0', padding, + margin, + width, }: InlineBannerProps) { const effectiveBannerType = bannerType || 'alert' const colorEnums = getColorEnums(effectiveBannerType) @@ -146,6 +162,8 @@ export function InlineBanner({ borderRadius={borderRadius} orientation={orientation} padding={padding} + margin={margin} + width={width} > {!hideIcon && colorEnums.icon && ( @@ -158,7 +176,7 @@ export function InlineBanner({ /> )} {!hideIcon && colorEnums.iconText && {colorEnums.iconText}} - {children} + {children} ) diff --git a/libs/ui/src/pure/LinkStyledButton/index.tsx b/libs/ui/src/pure/LinkStyledButton/index.tsx new file mode 100644 index 0000000000..ef1169bacd --- /dev/null +++ b/libs/ui/src/pure/LinkStyledButton/index.tsx @@ -0,0 +1,25 @@ +import styled from 'styled-components/macro' + +// A button that triggers some onClick result, but looks like a link. +export const LinkStyledButton = styled.button<{ disabled?: boolean; bg?: boolean; isCopied?: boolean }>` + border: none; + text-decoration: none; + background: none; + cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')}; + color: inherit; + font-weight: 500; + opacity: ${({ disabled }) => (disabled ? 0.7 : 1)}; + + :hover { + text-decoration: ${({ disabled }) => (disabled ? null : 'underline')}; + } + + :focus { + outline: none; + text-decoration: ${({ disabled }) => (disabled ? null : 'underline')}; + } + + :active { + text-decoration: none; + } +` diff --git a/libs/ui/src/pure/Popover/index.tsx b/libs/ui/src/pure/Popover/index.tsx index b239845868..f7186f6472 100644 --- a/libs/ui/src/pure/Popover/index.tsx +++ b/libs/ui/src/pure/Popover/index.tsx @@ -1,8 +1,7 @@ import { UI } from '../../enum' import styled from 'styled-components' -import PopoverMod, { Arrow as ArrowMod, PopoverContainer as PopoverContainerMod } from './PopoverMod' -import { PopoverProps } from './PopoverMod' +import PopoverMod, { Arrow as ArrowMod, PopoverContainer as PopoverContainerMod, PopoverProps } from './PopoverMod' export * from './PopoverMod'