diff --git a/src/assets/images/earn_background_large.png b/src/assets/images/earn_background_large.png new file mode 100644 index 0000000000..0fb42c55eb Binary files /dev/null and b/src/assets/images/earn_background_large.png differ diff --git a/src/assets/images/earn_background_small.png b/src/assets/images/earn_background_small.png new file mode 100644 index 0000000000..8f8d62233d Binary files /dev/null and b/src/assets/images/earn_background_small.png differ diff --git a/src/components/EarnBanner/index.tsx b/src/components/EarnBanner/index.tsx new file mode 100644 index 0000000000..3be254ecdf --- /dev/null +++ b/src/components/EarnBanner/index.tsx @@ -0,0 +1,237 @@ +import { useEffect, useMemo, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { Flex, Text } from 'rebass' +import { useExplorerLandingQuery } from 'services/zapEarn' +import styled, { css, keyframes } from 'styled-components' + +import earnLargeBg from 'assets/images/earn_background_large.png' +import earnSmallBg from 'assets/images/earn_background_small.png' +import { APP_PATHS } from 'constants/index' +import { useActiveWeb3React } from 'hooks' +import { formatAprNumber } from 'pages/Earns/utils' + +const EarnBannerContainer = styled.div` + padding: 1px; + position: relative; + background-clip: padding-box; + overflow: hidden; + margin-bottom: 20px; + border-radius: 12px; + border: 1px solid transparent; + cursor: pointer; + + ::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: 1px; + background: linear-gradient(306.9deg, #262525 38.35%, rgba(148, 117, 203, 0.2) 104.02%), + radial-gradient(58.61% 54.58% at 30.56% 0%, rgba(130, 71, 229, 0.6) 0%, rgba(130, 71, 229, 0) 100%); + mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); + -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); + z-index: -1; + } +` + +const EarnBannerWrapper = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 22px 18px 22px 68px; + + background-image: url(${earnLargeBg}); + background-position: center; + background-size: cover; + + ${({ theme }) => theme.mediaWidth.upToXL` + padding: 20px 18px; + `} + + ${({ theme }) => theme.mediaWidth.upToLarge` + flex-direction: column; + background-image: url(${earnSmallBg}); + padding: 20px 24px; + `} + + ${({ theme }) => theme.mediaWidth.upToMedium` + flex-direction: row; + background-image: url(${earnLargeBg}); + padding: 20px 18px 20px 60px; + `} + + @media screen and (max-width: 900px) { + padding: 20px 18px; + } + + ${({ theme }) => theme.mediaWidth.upToSmall` + flex-direction: column; + background-image: url(${earnSmallBg}); + padding: 20px 24px; + `} +` + +const Description = styled(Text)` + width: 400px; + + ${({ theme }) => theme.mediaWidth.upToXL` + width: unset; + `} +` + +const PrimaryText = styled.span` + color: ${({ theme }) => theme.primary}; +` + +const pulse = keyframes` + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +` + +const PoolButton = styled.div<{ animate: boolean }>` + border-radius: 40px; + padding: 10px 20px; + background: #1d5b49cc; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + border: 1px solid rgba(25, 103, 80, 1); + + ${({ animate }) => + animate && + css` + animation: ${pulse} 0.6s; + `} + + ${({ theme }) => theme.mediaWidth.upToLarge` + width: 100%; + `} + + ${({ theme }) => theme.mediaWidth.upToMedium` + width: unset; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + width: 100%; + padding: 10px 16px; + `} + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + padding: 10px 12px; + `} +` + +const TokenImage = styled.img` + width: 24px; + height: 24px; + border-radius: 50%; + box-shadow: 0 4px 8px 0 rgba(11, 46, 36, 1); + + &:nth-child(1) { + margin-right: -8px; + } +` + +const PoolAprWrapper = styled.div` + border-radius: 20px; + box-shadow: 0 8px 8px 0 rgba(0, 0, 0, 0.3); + padding-bottom: 1px; + width: auto; + overflow: hidden; + background-image: linear-gradient( + to right, + rgba(102, 102, 102, 0), + rgba(102, 102, 102, 0), + rgba(162, 233, 212, 1), + rgba(102, 102, 102, 0), + rgba(102, 102, 102, 0) + ); +` + +const PoolApr = styled.div` + display: flex; + font-weight: 600; + background-color: #000; + color: ${({ theme }) => theme.primary}; + padding: 4px 16px; + width: max-content; +` + +const AprText = styled.span` + margin-left: 6px; + ${({ theme }) => theme.mediaWidth.upToXXSmall` + display: none; + `} +` + +let indexInterval: NodeJS.Timeout + +export default function EarnBanner() { + const navigate = useNavigate() + const { account } = useActiveWeb3React() + const { data } = useExplorerLandingQuery({ userAddress: account }) + + const [index, setIndex] = useState(0) + const [animate, setAnimate] = useState(false) + + const pool = useMemo(() => data?.data.highlightedPools[index] || null, [data, index]) + + useEffect(() => { + const handleIndexChange = () => { + setAnimate(true) + setTimeout(() => setIndex(prev => (prev >= 9 ? 0 : prev + 1)), 200) + setTimeout(() => setAnimate(false), 1000) + } + indexInterval = setInterval(handleIndexChange, 4000) + + return () => clearInterval(indexInterval) + }, []) + + return ( + + navigate({ pathname: APP_PATHS.EARN })}> + + Explore and Add Liquidity to High-APR Pools Instantly with{' '} + Any Token(s) or Position you choose! + + { + if (!pool) return + e.stopPropagation() + navigate({ pathname: APP_PATHS.EARN, search: `?openPool=${index}` }) + }} + > + {!!pool && ( + <> + + + + + {pool.tokens[0].symbol}/{pool.tokens[1].symbol} + + + + + {formatAprNumber(pool.apr)}% APR + + + + )} + + + + ) +} diff --git a/src/constants/index.ts b/src/constants/index.ts index d93e90868e..33fbe7e3b2 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -170,10 +170,13 @@ export const APP_PATHS = { REFFERAL_CAMPAIGN: '/campaigns/referrals', MY_DASHBOARD: '/campaigns/dashboard', - EARN: '/earns', - EARN_POOLS: '/earns/pools', - EARN_POSITIONS: '/earns/positions', - EARN_POSITION_DETAIL: '/earns/position/:chainId/:id', + EARN: '/earn', + EARN_POOLS: '/earn/pools', + EARN_POSITIONS: '/earn/positions', + EARN_POSITION_DETAIL: '/earn/position/:chainId/:id', + EARNS: '/earns', + EARNS_POOLS: '/earns/pools', + EARNS_POSITIONS: '/earns/positions', } as const export const TERM_FILES_PATH = { diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 3bd8dd277a..0e29d5ea27 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -326,6 +326,10 @@ export default function App() { } /> } /> + } /> + } /> + } /> + } /> diff --git a/src/pages/Earns/PoolExplorer/index.tsx b/src/pages/Earns/PoolExplorer/index.tsx index 9fac72e63f..434058cd63 100644 --- a/src/pages/Earns/PoolExplorer/index.tsx +++ b/src/pages/Earns/PoolExplorer/index.tsx @@ -1,7 +1,7 @@ import { t } from '@lingui/macro' import { useEffect, useState } from 'react' import { Info, Star } from 'react-feather' -import { useSearchParams } from 'react-router-dom' +import { useNavigate, useSearchParams } from 'react-router-dom' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import { usePoolsExplorerQuery } from 'services/zapEarn' @@ -20,6 +20,7 @@ import useTheme from 'hooks/useTheme' import SortIcon, { Direction } from 'pages/MarketOverview/SortIcon' import { MEDIA_WIDTHS } from 'theme' +import { IconArrowLeft } from '../PositionDetail/styles' import useLiquidityWidget from '../useLiquidityWidget' import useSupportedDexesAndChains from '../useSupportedDexesAndChains' import DropdownMenu, { MenuOption } from './DropdownMenu' @@ -88,6 +89,7 @@ const Earn = () => { const [search, setSearch] = useState('') const deboundedSearch = useDebounce(search, 300) const [searchParams] = useSearchParams() + const navigate = useNavigate() const theme = useTheme() const { filters, updateFilters } = useFilter(setSearch) const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() @@ -139,9 +141,12 @@ const Earn = () => { {liquidityWidget}
- - {t`Earning with Smart Liquidity Providing`} - + + navigate(-1)} /> + + {t`Earning with Smart Liquidity Providing`} + + {t`KyberSwap Zap: Instantly add liquidity to high-APY pools using any token(s) or your existing liquidity position with KyberZap`} diff --git a/src/pages/Earns/PositionDetail/styles.tsx b/src/pages/Earns/PositionDetail/styles.tsx index 1ee758effe..b31050b6ef 100644 --- a/src/pages/Earns/PositionDetail/styles.tsx +++ b/src/pages/Earns/PositionDetail/styles.tsx @@ -6,7 +6,7 @@ export const IconArrowLeft = styled(IconArrowLeftSvg)` cursor: pointer; position: relative; top: 5px; - color: ${({ theme }) => theme.subText}; + color: rgba(250, 250, 250, 1); :hover { filter: brightness(1.5); diff --git a/src/pages/Earns/UserPositions/index.tsx b/src/pages/Earns/UserPositions/index.tsx index 1dc7462d31..1d819a83c6 100644 --- a/src/pages/Earns/UserPositions/index.tsx +++ b/src/pages/Earns/UserPositions/index.tsx @@ -19,7 +19,7 @@ import { shortenAddress } from 'utils' import { formatDisplayNumber } from 'utils/numbers' import { CurrencyRoundedImage, CurrencySecondImage, Disclaimer } from '../PoolExplorer/styles' -import { PositionAction as PositionActionBtn } from '../PositionDetail/styles' +import { IconArrowLeft, PositionAction as PositionActionBtn } from '../PositionDetail/styles' import useLiquidityWidget from '../useLiquidityWidget' import useSupportedDexesAndChains from '../useSupportedDexesAndChains' import Filter from './Filter' @@ -87,9 +87,12 @@ const MyPositions = () => { {liquidityWidget}
- - {t`My Liquidity`} - + + navigate(-1)} /> + + {t`My Liquidity`} + + {t`KyberSwap Zap: Instantly and easily add liquidity to high-APY pools using any token or a combination of tokens.`} diff --git a/src/pages/Earns/index.tsx b/src/pages/Earns/index.tsx index 5da15181c8..0fa142af5c 100644 --- a/src/pages/Earns/index.tsx +++ b/src/pages/Earns/index.tsx @@ -1,6 +1,7 @@ import { ChainId } from '@kyberswap/ks-sdk-core' import { rgba } from 'polished' -import { useNavigate } from 'react-router-dom' +import { useEffect } from 'react' +import { useNavigate, useSearchParams } from 'react-router-dom' import { useMedia } from 'react-use' import { Box, Flex, Text } from 'rebass' import { EarnPool, useExplorerLandingQuery } from 'services/zapEarn' @@ -285,9 +286,11 @@ const Card = ({ export default function Earns() { const navigate = useNavigate() + const [searchParams, setSearchParams] = useSearchParams() const theme = useTheme() const { account } = useActiveWeb3React() const { isLoading, data } = useExplorerLandingQuery({ userAddress: account }) + const { liquidityWidget, handleOpenZapInWidget } = useLiquidityWidget() const title = (title: string, tooltip: string, icon: string) => ( <> @@ -316,8 +319,25 @@ export default function Earns() { const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) const upToXXSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToXXSmall}px)`) + useEffect(() => { + const poolsToOpen = data?.data?.highlightedPools || [] + const openPool = searchParams.get('openPool') + const openPoolIndex = parseInt(openPool || '', 10) + + if (!isNaN(openPoolIndex) && poolsToOpen.length && poolsToOpen[openPoolIndex]) { + searchParams.delete('openPool') + setSearchParams(searchParams) + handleOpenZapInWidget({ + exchange: poolsToOpen[openPoolIndex].exchange, + chainId: poolsToOpen[openPoolIndex].chainId, + address: poolsToOpen[openPoolIndex].address, + }) + } + }, [handleOpenZapInWidget, data, searchParams, setSearchParams]) + return ( + {liquidityWidget} Maximize Your Earnings in DeFi diff --git a/src/pages/SwapV3/index.tsx b/src/pages/SwapV3/index.tsx index b02b3f72db..879f87b3ed 100644 --- a/src/pages/SwapV3/index.tsx +++ b/src/pages/SwapV3/index.tsx @@ -7,6 +7,7 @@ import styled from 'styled-components' import { ReactComponent as RoutingIcon } from 'assets/svg/routing-icon.svg' import Banner from 'components/Banner' +import EarnBanner from 'components/EarnBanner' import { SwitchLocaleLink } from 'components/SwitchLocaleLink' import { TutorialIds } from 'components/Tutorial/TutorialSwap/constant' import GasTokenSetting from 'components/swapv2/GasTokenSetting' @@ -117,6 +118,7 @@ export default function Swap() { const isSwapPage = pathname.startsWith(APP_PATHS.SWAP) const isLimitPage = pathname.startsWith(APP_PATHS.LIMIT) const isCrossChainPage = pathname.startsWith(APP_PATHS.CROSS_CHAIN) + const isPartnerSwap = pathname.startsWith(APP_PATHS.PARTNER_SWAP) const enableDegenMode = searchParams.get('enableDegenMode') === 'true' @@ -188,6 +190,7 @@ export default function Swap() { + {(isSwapPage || isLimitPage) && !isPartnerSwap && } {isShowTradeRoutes && isSwapPage && (