diff --git a/package.json b/package.json index 6068a33852..b89d25bbf3 100644 --- a/package.json +++ b/package.json @@ -200,6 +200,7 @@ }, "resolutions": { "@kyberswap/ks-sdk-core": "1.0.11", + "babel-plugin-lodash/@babel/types": "~7.20.0", "react-error-overlay": "6.0.9", "@lingui/babel-plugin-extract-messages": "3.14.0", "@lingui/cli": "3.14.0", diff --git a/src/components/SubscribeButton/NotificationPreference/index.tsx b/src/components/SubscribeButton/NotificationPreference/index.tsx index f6d7253857..2086585c85 100644 --- a/src/components/SubscribeButton/NotificationPreference/index.tsx +++ b/src/components/SubscribeButton/NotificationPreference/index.tsx @@ -137,7 +137,7 @@ export const useValidateEmail = (defaultEmail?: string) => { [defaultEmail], ) - return { inputEmail, onChangeEmail, errorInput, errorColor, hasErrorInput, reset } + return { inputEmail: inputEmail.trim(), onChangeEmail, errorInput, errorColor, hasErrorInput, reset } } function NotificationPreference({ diff --git a/src/components/SwapForm/SwapActionButton/SwapButtonWithPriceImpact.tsx b/src/components/SwapForm/SwapActionButton/SwapButtonWithPriceImpact.tsx index 7184a009d4..e841642f26 100644 --- a/src/components/SwapForm/SwapActionButton/SwapButtonWithPriceImpact.tsx +++ b/src/components/SwapForm/SwapActionButton/SwapButtonWithPriceImpact.tsx @@ -1,4 +1,5 @@ import { Trans, t } from '@lingui/macro' +import { ReactNode } from 'react' import { Info } from 'react-feather' import { Text } from 'rebass' import styled from 'styled-components' @@ -44,8 +45,8 @@ export const SwapButtonWithPriceImpact = ({ route: any disabled?: boolean showNoteGetRoute?: boolean - disabledText?: string - text?: string + disabledText?: ReactNode + text?: ReactNode showTooltipPriceImpact?: boolean }) => { const theme = useTheme() diff --git a/src/components/swapv2/LimitOrder/ActionButtonLimitOrder.tsx b/src/components/swapv2/LimitOrder/ActionButtonLimitOrder.tsx index b5a240d74a..5b10e548dd 100644 --- a/src/components/swapv2/LimitOrder/ActionButtonLimitOrder.tsx +++ b/src/components/swapv2/LimitOrder/ActionButtonLimitOrder.tsx @@ -1,4 +1,4 @@ -import { Currency } from '@kyberswap/ks-sdk-core' +import { Currency, WETH } from '@kyberswap/ks-sdk-core' import { Trans, t } from '@lingui/macro' import { Text } from 'rebass' @@ -15,11 +15,13 @@ import { RowBetween } from 'components/Row' import { useActiveWeb3React } from 'hooks' import { ApprovalState } from 'hooks/useApproveCallback' import { useWalletModalToggle } from 'state/application/hooks' +import { isTokenNative } from 'utils/tokenInfo' export default function ActionButtonLimitOrder({ showWrap, approval, currencyIn, + currencyOut, isWrappingEth, wrapInputError, approveCallback, @@ -34,6 +36,7 @@ export default function ActionButtonLimitOrder({ showWarning, }: { currencyIn: Currency | undefined + currencyOut: Currency | undefined approval: ApprovalState showWrap: boolean isWrappingEth: boolean @@ -59,7 +62,8 @@ export default function ActionButtonLimitOrder({ !!hasInputError || approval !== ApprovalState.APPROVED || isWrappingEth || - (showWrap && !isWrappingEth) + (showWrap && !isWrappingEth) || + (currencyIn?.equals(WETH[currencyIn.chainId]) && isTokenNative(currencyOut, currencyOut?.chainId)) const { account } = useActiveWeb3React() const toggleWalletModal = useWalletModalToggle() diff --git a/src/components/swapv2/LimitOrder/LimitOrderForm.tsx b/src/components/swapv2/LimitOrder/LimitOrderForm.tsx index e3bc3809a6..b8b3d55812 100644 --- a/src/components/swapv2/LimitOrder/LimitOrderForm.tsx +++ b/src/components/swapv2/LimitOrder/LimitOrderForm.tsx @@ -840,6 +840,7 @@ const LimitOrderForm = function LimitOrderForm({ } const BackText = styled.span` @@ -52,6 +53,7 @@ const SettingsPanel: React.FC = ({ onBack, onClickLiquiditySources, onClickGasPriceTracker, + swapActionsRef, }) => { const theme = useTheme() @@ -83,7 +85,7 @@ const SettingsPanel: React.FC = ({ const [showConfirmation, setShowConfirmation] = useState(false) const containerRef = useRef(null) - useOnClickOutside(containerRef, () => !showConfirmation && onBack()) + useOnClickOutside([containerRef, swapActionsRef], () => !showConfirmation && onBack()) return ( diff --git a/src/hooks/useOnClickOutside.tsx b/src/hooks/useOnClickOutside.tsx index 0debf94971..7a7fd7c35f 100644 --- a/src/hooks/useOnClickOutside.tsx +++ b/src/hooks/useOnClickOutside.tsx @@ -2,17 +2,18 @@ import { RefObject, useEffect, useRef } from 'react' import { isMobile } from 'react-device-detect' export function useOnClickOutside( - node: RefObject, + node: RefObject | RefObject[], handler: undefined | (() => void), ) { const handlerRef = useRef void)>(handler) - useEffect(() => { - handlerRef.current = handler - }, [handler]) + handlerRef.current = handler useEffect(() => { const handleClickOutside = (e: MouseEvent | TouchEvent) => { - if (node.current?.contains(e.target as Node) ?? false) { + let nodes: RefObject[] + if (Array.isArray(node)) nodes = node + else nodes = [node] + if (nodes.some(node => node.current?.contains(e.target as Node) ?? false)) { return } if (handlerRef.current) handlerRef.current() diff --git a/src/pages/CrossChain/SwapForm/index.tsx b/src/pages/CrossChain/SwapForm/index.tsx index 7bc0ff3724..46d5049f0e 100644 --- a/src/pages/CrossChain/SwapForm/index.tsx +++ b/src/pages/CrossChain/SwapForm/index.tsx @@ -368,7 +368,7 @@ export default function SwapForm() { {!!priceImpact && } - {inputError?.state && ( + {inputError?.state && !inputError?.insufficientFund && ( )} @@ -383,7 +383,7 @@ export default function SwapForm() { route={route} minimal={false} showNoteGetRoute={priceImpactResult.isHigh || priceImpactResult.isVeryHigh || priceImpactResult.isInvalid} - disabledText={t`Swap`} + disabledText={(inputError?.insufficientFund ? inputError?.tip : '') || t`Swap`} showTooltipPriceImpact={false} /> ) : ( diff --git a/src/pages/CrossChain/useValidateInput.ts b/src/pages/CrossChain/useValidateInput.ts index c88fcd49c7..18981dad81 100644 --- a/src/pages/CrossChain/useValidateInput.ts +++ b/src/pages/CrossChain/useValidateInput.ts @@ -20,6 +20,7 @@ export const useIsTokensSupport = () => { ) } +type InputError = undefined | { state: 'warn' | 'error'; tip: string; desc?: ReactNode; insufficientFund?: boolean } export default function useValidateInput({ inputAmount, route, @@ -37,7 +38,7 @@ export default function useValidateInput({ const showErrorGas = !isEnoughEth && route const isTokenSupport = useIsTokensSupport() - const inputError: undefined | { state: 'warn' | 'error'; tip: string; desc?: ReactNode } = useMemo(() => { + const inputError: InputError = useMemo(() => { if (!listTokenOut.length && !listTokenIn.length && !loadingToken) { return { state: 'error', tip: t`Cannot get token info. Please try again later.` } } @@ -65,7 +66,8 @@ export default function useValidateInput({ desc: t`Please decrease the size of your transaction and try again.`, } - if (balance?.lessThan(parseAmount)) return { state: 'warn', tip: t`Insufficient ${currencyIn?.symbol} balance` } + if (balance?.lessThan(parseAmount)) + return { state: 'warn', tip: t`Insufficient ${currencyIn?.symbol} balance`, insufficientFund: true } if (showErrorGas && account) { return { diff --git a/src/pages/SwapV2/index.tsx b/src/pages/SwapV2/index.tsx index 2d5596b157..953b827e33 100644 --- a/src/pages/SwapV2/index.tsx +++ b/src/pages/SwapV2/index.tsx @@ -416,6 +416,7 @@ export default function Swap() { const tradeRouteComposition = useMemo(() => { return getTradeComposition(chainId, trade?.inputAmount, trade?.tokens, trade?.swaps, defaultTokens) }, [chainId, defaultTokens, trade]) + const swapActionsRef = useRef(null) return ( <> @@ -429,7 +430,7 @@ export default function Swap() { -
+
{activeTab === TAB.SWAP && ( @@ -735,6 +736,7 @@ export default function Swap() { {activeTab === TAB.INFO && } {activeTab === TAB.SETTINGS && ( setActiveTab(TAB.LIQUIDITY_SOURCES)} diff --git a/src/pages/SwapV3/Header.tsx b/src/pages/SwapV3/Header.tsx index 209f1aa908..6cf5e23fe8 100644 --- a/src/pages/SwapV3/Header.tsx +++ b/src/pages/SwapV3/Header.tsx @@ -1,6 +1,6 @@ import { Trans, t } from '@lingui/macro' import { rgba } from 'polished' -import { Dispatch, SetStateAction, useState } from 'react' +import { Dispatch, RefObject, SetStateAction, useState } from 'react' import { useLocation } from 'react-router-dom' import { Text } from 'rebass' import styled from 'styled-components' @@ -24,9 +24,11 @@ const DegenBanner = styled(RowBetween)` export default function Header({ activeTab, setActiveTab, + swapActionsRef, }: { activeTab: TAB setActiveTab: Dispatch> + swapActionsRef: RefObject }) { const theme = useTheme() const [isDegenMode] = useDegenModeManager() @@ -41,7 +43,7 @@ export default function Header({ - + diff --git a/src/pages/SwapV3/HeaderRightMenu.tsx b/src/pages/SwapV3/HeaderRightMenu.tsx index f3b9919690..f1f7fef228 100644 --- a/src/pages/SwapV3/HeaderRightMenu.tsx +++ b/src/pages/SwapV3/HeaderRightMenu.tsx @@ -1,5 +1,5 @@ import { Trans, t } from '@lingui/macro' -import { Dispatch, SetStateAction, useState } from 'react' +import { Dispatch, RefObject, SetStateAction, useState } from 'react' import { isMobile } from 'react-device-detect' import { MoreHorizontal } from 'react-feather' import { useLocation } from 'react-router-dom' @@ -61,9 +61,11 @@ const TransactionSettingsIconWrapper = styled.span` export default function HeaderRightMenu({ activeTab, setActiveTab, + swapActionsRef, }: { activeTab: TAB setActiveTab: Dispatch> + swapActionsRef: RefObject }) { const theme = useTheme() @@ -96,7 +98,12 @@ export default function HeaderRightMenu({ const isShowMenu = Boolean(isShowHeaderMenu || forceShowMenu) return ( - + {isShowMenu && ( <> diff --git a/src/pages/SwapV3/index.tsx b/src/pages/SwapV3/index.tsx index 6d6e456332..030d4b8600 100644 --- a/src/pages/SwapV3/index.tsx +++ b/src/pages/SwapV3/index.tsx @@ -205,6 +205,7 @@ export default function Swap() { const tradeRouteComposition = useMemo(() => { return getTradeComposition(chainId, routeSummary?.parsedAmountIn, undefined, routeSummary?.route, defaultTokens) }, [chainId, defaultTokens, routeSummary]) + const swapActionsRef = useRef(null) return ( <> @@ -218,7 +219,7 @@ export default function Swap() { -
+
{(isLimitPage || isSwapPage) && !TYPE_AND_SWAP_NOT_SUPPORTED_CHAINS.includes(chainId) && ( setActiveTab(TAB.LIQUIDITY_SOURCES)} onClickGasPriceTracker={() => setActiveTab(TAB.GAS_PRICE_TRACKER)} + swapActionsRef={swapActionsRef} /> )} {activeTab === TAB.GAS_PRICE_TRACKER && ( diff --git a/src/utils/string.ts b/src/utils/string.ts index dbfb2b8153..4e56993b2f 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -37,7 +37,8 @@ export const escapeScriptHtml = (str: string) => { return str.replace(/<.*?script.*?>.*?<\/.*?script.*?>/gim, '') } -export const isEmailValid = (value: string | undefined) => value?.match(/^\w+([\.-]?\w)*@\w+([\.-]?\w)*(\.\w{2,10})+$/) +export const isEmailValid = (value: string | undefined) => + (value || '').trim().match(/^\w+([\.-]?\w)*@\w+([\.-]?\w)*(\.\w{2,10})+$/) export const getChainIdFromSlug = (network: string | undefined): ChainId | undefined => { return SUPPORTED_NETWORKS.find(chainId => NETWORKS_INFO[chainId].route === network) diff --git a/yarn.lock b/yarn.lock index 2a94841cc8..28c71e35de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -372,16 +372,16 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-string-parser@^7.19.4", "@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + "@babel/helper-string-parser@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - "@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" @@ -662,13 +662,13 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0-beta.49": - version "7.22.17" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.17.tgz#f753352c4610ffddf9c8bc6823f9ff03e2303eee" - integrity sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg== +"@babel/types@^7.0.0-beta.49", "@babel/types@~7.20.0": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" + integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.15" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" "@babel/types@^7.11.5", "@babel/types@^7.17.0", "@babel/types@^7.18.6", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3":