From f3adb29bee2ad28ba7d826b97978d55ed6cfef00 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:30:33 +0100 Subject: [PATCH 01/15] feat: refactor cowamm banner --- .../src/common/pure/CoWAMMBanner/index.tsx | 289 ++++++++++-------- 1 file changed, 158 insertions(+), 131 deletions(-) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index 265c32cc46..dcd66afc04 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -1,30 +1,36 @@ -import { Media } from '@cowprotocol/ui' +import { useState } from 'react' + +import { Media, ProductLogo, ProductVariant } from '@cowprotocol/ui' import { ClosableBanner } from '@cowprotocol/ui' import { X } from 'react-feather' +import { Textfit } from 'react-textfit' import styled from 'styled-components/macro' import { cowAnalytics } from 'modules/analytics' const BannerWrapper = styled.div` --darkGreen: #194d05; + --green: #2b6f0b; --lightGreen: #bcec79; + --lighterGreen: #dcf8a7; + --blue: #3fc4ff; position: fixed; top: 76px; right: 10px; z-index: 3; - width: 400px; - height: 345px; + width: 485px; + height: auto; border-radius: 24px; background-color: var(--darkGreen); - color: var(--lightGreen); - padding: 24px; + color: var(--darkGreen); + padding: 20px; + gap: 20px; display: flex; flex-flow: column wrap; align-items: center; justify-content: center; - gap: 24px; overflow: hidden; ${Media.upToSmall()} { @@ -40,160 +46,141 @@ const BannerWrapper = styled.div` box-shadow: 0 0 0 100vh rgb(0 0 0 / 40%); z-index: 10; } +` - > i { - position: absolute; - top: -30px; - left: -30px; - width: 166px; - height: 42px; - border: 1px solid var(--lightGreen); - border-radius: 16px; - border-left: 0; - animation: bounceLeftRight 7s infinite; - animation-delay: 2s; - } - - &::before { - content: ''; - position: absolute; - top: 100px; - left: -23px; - width: 56px; - height: 190px; - border: 1px solid var(--lightGreen); - border-radius: 16px; - border-left: 0; - animation: bounceUpDown 7s infinite; - } - - &::after { - content: ''; - position: absolute; - bottom: -21px; - right: 32px; - width: 76px; - height: 36px; - border: 1px solid var(--lightGreen); - border-radius: 16px; - border-bottom: 0; - animation: bounceLeftRight 7s infinite; - animation-delay: 1s; - } - - > div { - display: flex; - flex-flow: column wrap; - gap: 24px; - width: 100%; - max-width: 75%; - margin: 0 auto; - } - - @keyframes bounceUpDown { - 0%, - 100% { - transform: translateY(0); - } - 50% { - transform: translateY(-7px); - } - } +const CloseButton = styled(X)` + position: absolute; + top: 16px; + right: 16px; + cursor: pointer; + color: var(--lightGreen); + opacity: 0.6; + transition: opacity 0.2s ease-in-out; - @keyframes bounceLeftRight { - 0%, - 100% { - transform: translateX(0); - } - 50% { - transform: translateX(7px); - } + &:hover { + opacity: 1; } ` const Title = styled.h2` - font-size: 34px; + display: flex; + align-items: center; + gap: 8px; + font-size: 18px; font-weight: bold; - margin: 0; + margin: 0 auto 0 0; + color: var(--lightGreen); ${Media.upToSmall()} { font-size: 26px; } ` -const Description = styled.p` - font-size: 17px; - line-height: 1.5; +const Card = styled.div<{ bgColor?: string; color?: string }>` + display: grid; + grid-template-columns: 1.1fr 0.9fr; + gap: 24px; + align-items: center; + justify-content: center; + font-size: 30px; + line-height: 1.2; + font-weight: 500; margin: 0; + width: 100%; + max-width: 100%; + min-height: 150px; + border-radius: 16px; + padding: 24px; + background: ${({ bgColor }) => bgColor || 'transparent'}; + color: ${({ color }) => color || 'inherit'}; - ${Media.upToSmall()} { - font-size: 15px; + > h3, + > p { + display: flex; + align-items: center; + justify-content: center; + margin: 0; + width: 100%; + height: 100%; + + > div { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + } + + > h3 { + font-weight: bold; + letter-spacing: -2px; + } + + > p { + font-weight: inherit; } ` const CTAButton = styled.button` - background-color: var(--lightGreen); + --size: 58px; + background: var(--lightGreen); color: var(--darkGreen); border: none; - border-radius: 56px; + border-radius: var(--size); + min-height: var(--size); padding: 12px 24px; - font-size: 18px; + font-size: 24px; font-weight: bold; cursor: pointer; width: 100%; - max-width: 75%; + max-width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; - transition: border-radius 0.2s ease-in-out; + transition: background 0.2s ease-in-out; &:hover { - border-radius: 16px; - - > i { - transform: rotate(45deg); - } - } - - > i { - font-size: 22px; - font-weight: bold; - font-style: normal; - line-height: 1; - margin: 3px 0 0; - transition: transform 0.2s ease-in-out; - animation: spin 6s infinite; - } - - @keyframes spin { - 0% { - transform: rotate(0deg); - } - 20% { - transform: rotate(360deg); - } - 100% { - transform: rotate(360deg); - } + background: var(--lighterGreen); } ` -const CloseButton = styled(X)` - position: absolute; - top: 16px; - right: 16px; - cursor: pointer; +const SecondaryLink = styled.a` color: var(--lightGreen); - opacity: 0.6; - transition: opacity 0.2s ease-in-out; + font-size: 14px; + font-weight: 500; + text-decoration: none; &:hover { - opacity: 1; + text-decoration: underline; } ` +const DEMO_DROPDOWN = styled.select` + position: fixed; + bottom: 150px; + right: 10px; + z-index: 999999999; + padding: 5px; + font-size: 16px; +` + +// Dummy data for different states +const dummyData = { + noLp: { apr: 1.5, comparison: 'UNI-V2' }, + uniV2: { apr: 2.1, comparison: 'UNI-V2' }, + sushi: { apr: 1.8, comparison: 'SushiSwap' }, + curve: { apr: 1.3, comparison: 'Curve' }, + pancake: { apr: 2.5, comparison: 'PancakeSwap' }, + multiple: { apr: 2.0, comparison: 'UNI-V2, SushiSwap' }, +} as const + +type StateKey = keyof typeof dummyData + export function CoWAmmBanner() { + const [selectedState, setSelectedState] = useState<StateKey>('noLp') + const handleCTAClick = () => { cowAnalytics.sendEvent({ category: 'CoW Swap', @@ -202,7 +189,7 @@ export function CoWAmmBanner() { window.open( 'https://balancer.fi/pools/cow?utm_source=swap.cow.fi&utm_medium=web&utm_content=cow_amm_banner', - '_blank' + '_blank', ) } @@ -213,9 +200,21 @@ export function CoWAmmBanner() { }) } - return ClosableBanner('cow_amm_banner', (close) => ( + const getAprMessage = () => { + const { apr } = dummyData[selectedState] + return `+${apr.toFixed(1)}%` + } + + const getComparisonMessage = () => { + const { comparison } = dummyData[selectedState] + if (selectedState === 'multiple') { + return `Get higher APR than average ${comparison}` + } + return `Get higher APR than ${comparison}` + } + + return ClosableBanner('cow_amm_banner_2024_va', (close) => ( <BannerWrapper> - <i></i> <CloseButton size={24} onClick={() => { @@ -223,15 +222,43 @@ export function CoWAmmBanner() { close() }} /> - <div> - <Title>Now live: the first MEV-capturing AMM</Title> - <Description> - CoW AMM shields you from LVR, so you can provide liquidity with less risk and more rewards. - </Description> - </div> - <CTAButton onClick={handleCTAClick}> - LP on CoW AMM <i>↗</i> - </CTAButton> + + <DEMO_DROPDOWN value={selectedState} onChange={(e) => setSelectedState(e.target.value as StateKey)}> + <option value="noLp">No LP tokens</option> + <option value="uniV2">UNI-V2 LP</option> + <option value="sushi">SushiSwap LP</option> + <option value="curve">Curve LP</option> + <option value="pancake">PancakeSwap LP</option> + <option value="multiple">Multiple LP tokens</option> + </DEMO_DROPDOWN> + + <Title> + <ProductLogo height={20} overrideColor={'var(--lightGreen)'} variant={ProductVariant.CowAmm} logoIconOnly /> + <span>CoW AMM</span> + </Title> + <Card bgColor={'var(--blue)'}> + <h3> + <Textfit mode="single" forceSingleModeWidth={false} min={21} max={76}> + {getAprMessage()} + </Textfit> + </h3> + <p> + <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={28}> + {getComparisonMessage()} + </Textfit> + </p> + </Card> + + <Card bgColor={'var(--green)'} color={'var(--lighterGreen)'}> + <p> + <Textfit mode="single" forceSingleModeWidth={false} min={10} max={28}> + One-click convert, boost yield + </Textfit> + </p> + </Card> + + <CTAButton onClick={handleCTAClick}>Booooost APR gas-free!</CTAButton> + <SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</SecondaryLink> </BannerWrapper> )) } From 8054f7fc97f8baa386f1de1ff5d52afd3def5aa5 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:53:35 +0100 Subject: [PATCH 02/15] feat: refactor cowamm banner --- .../src/common/pure/CoWAMMBanner/index.tsx | 279 ++++++++++++++++-- libs/assets/src/cow-swap/icon-curve.svg | 1 + libs/assets/src/cow-swap/icon-pancakeswap.svg | 1 + libs/assets/src/cow-swap/icon-sushi.svg | 1 + libs/assets/src/cow-swap/icon-uni.svg | 1 + 5 files changed, 252 insertions(+), 31 deletions(-) create mode 100644 libs/assets/src/cow-swap/icon-curve.svg create mode 100644 libs/assets/src/cow-swap/icon-pancakeswap.svg create mode 100644 libs/assets/src/cow-swap/icon-sushi.svg create mode 100644 libs/assets/src/cow-swap/icon-uni.svg diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index dcd66afc04..59dcebe5ed 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -1,21 +1,40 @@ import { useState } from 'react' +import ICON_ARROW from '@cowprotocol/assets/cow-swap/arrow.svg' +import ICON_CURVE from '@cowprotocol/assets/cow-swap/icon-curve.svg' +import ICON_PANCAKESWAP from '@cowprotocol/assets/cow-swap/icon-pancakeswap.svg' +import ICON_SUSHISWAP from '@cowprotocol/assets/cow-swap/icon-sushi.svg' +import ICON_UNISWAP from '@cowprotocol/assets/cow-swap/icon-uni.svg' +import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' import { Media, ProductLogo, ProductVariant } from '@cowprotocol/ui' import { ClosableBanner } from '@cowprotocol/ui' import { X } from 'react-feather' +import SVG from 'react-inlinesvg' import { Textfit } from 'react-textfit' import styled from 'styled-components/macro' import { cowAnalytics } from 'modules/analytics' -const BannerWrapper = styled.div` - --darkGreen: #194d05; - --green: #2b6f0b; - --lightGreen: #bcec79; - --lighterGreen: #dcf8a7; - --blue: #3fc4ff; +// Add this enum at the top of the file, after imports +enum CoWAMMColors { + DarkGreen = '#194d05', + Green = '#2b6f0b', + LightGreen = '#bcec79', + LighterGreen = '#dcf8a7', + Blue = '#3fc4ff', + LightBlue = '#ccf8ff', +} + +// Add this enum after the CoWAMMColors enum +enum LpToken { + UniswapV2 = 'UniswapV2', + Sushiswap = 'Sushiswap', + PancakeSwap = 'PancakeSwap', + Curve = 'Curve', +} +const BannerWrapper = styled.div` position: fixed; top: 76px; right: 10px; @@ -23,8 +42,8 @@ const BannerWrapper = styled.div` width: 485px; height: auto; border-radius: 24px; - background-color: var(--darkGreen); - color: var(--darkGreen); + background-color: ${CoWAMMColors.DarkGreen}; + color: ${CoWAMMColors.DarkGreen}; padding: 20px; gap: 20px; display: flex; @@ -53,7 +72,7 @@ const CloseButton = styled(X)` top: 16px; right: 16px; cursor: pointer; - color: var(--lightGreen); + color: ${CoWAMMColors.LightGreen}; opacity: 0.6; transition: opacity 0.2s ease-in-out; @@ -69,16 +88,17 @@ const Title = styled.h2` font-size: 18px; font-weight: bold; margin: 0 auto 0 0; - color: var(--lightGreen); + color: ${CoWAMMColors.LightGreen}; ${Media.upToSmall()} { font-size: 26px; } ` -const Card = styled.div<{ bgColor?: string; color?: string }>` - display: grid; - grid-template-columns: 1.1fr 0.9fr; +const Card = styled.div<{ bgColor?: string; color?: string; height?: string }>` + --default-height: 150px; + display: flex; + flex-flow: row nowrap; gap: 24px; align-items: center; justify-content: center; @@ -88,12 +108,13 @@ const Card = styled.div<{ bgColor?: string; color?: string }>` margin: 0; width: 100%; max-width: 100%; - min-height: 150px; + height: ${({ height }) => height || 'var(--default-height)'}; + max-height: ${({ height }) => height || 'var(--default-height)'}; border-radius: 16px; padding: 24px; background: ${({ bgColor }) => bgColor || 'transparent'}; color: ${({ color }) => color || 'inherit'}; - + position: relative; > h3, > p { display: flex; @@ -102,6 +123,7 @@ const Card = styled.div<{ bgColor?: string; color?: string }>` margin: 0; width: 100%; height: 100%; + max-height: 100%; > div { width: 100%; @@ -120,12 +142,17 @@ const Card = styled.div<{ bgColor?: string; color?: string }>` > p { font-weight: inherit; } + + > p b { + font-weight: 900; + color: ${CoWAMMColors.LighterGreen}; + } ` const CTAButton = styled.button` --size: 58px; - background: var(--lightGreen); - color: var(--darkGreen); + background: ${CoWAMMColors.LightGreen}; + color: ${CoWAMMColors.DarkGreen}; border: none; border-radius: var(--size); min-height: var(--size); @@ -142,12 +169,12 @@ const CTAButton = styled.button` transition: background 0.2s ease-in-out; &:hover { - background: var(--lighterGreen); + background: ${CoWAMMColors.LighterGreen}; } ` const SecondaryLink = styled.a` - color: var(--lightGreen); + color: ${CoWAMMColors.LightGreen}; font-size: 14px; font-weight: 500; text-decoration: none; @@ -166,18 +193,164 @@ const DEMO_DROPDOWN = styled.select` font-size: 16px; ` -// Dummy data for different states +const StarIcon = styled.div<{ size?: number; top?: number; left?: number; right?: number; bottom?: number }>` + width: ${({ size }) => size || 16}px; + height: ${({ size }) => size || 16}px; + position: absolute; + top: ${({ top }) => top ?? 'initial'}px; + left: ${({ left }) => left ?? 'initial'}px; + right: ${({ right }) => right ?? 'initial'}px; + bottom: ${({ bottom }) => bottom ?? 'initial'}px; +` + +const LpEmblems = styled.div<{ totalItems: number }>` + display: flex; + gap: 8px; + width: 100%; + justify-content: center; + align-items: center; +` + +const LpEmblemItemsWrapper = styled.div<{ totalItems: number }>` + display: ${({ totalItems }) => (totalItems > 2 ? 'grid' : 'flex')}; + gap: ${({ totalItems }) => (totalItems > 2 ? '0' : '8px')}; + width: 100%; + justify-content: center; + align-items: center; + + ${({ totalItems }) => + totalItems === 3 && + ` + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + justify-items: center; + + > :first-child { + grid-column: 1 / -1; + } + `} + + ${({ totalItems }) => + totalItems === 4 && + ` + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + `} +` + +const LpEmblemItem = styled.div<{ + totalItems: number + index: number +}>` + --size: ${({ totalItems }) => + totalItems === 4 ? '50px' : totalItems === 3 ? '65px' : totalItems === 2 ? '80px' : '104px'}; + width: var(--size); + height: var(--size); + padding: ${({ totalItems }) => (totalItems === 4 ? '10px' : totalItems >= 2 ? '15px' : '20px')}; + border-radius: 50%; + background: ${CoWAMMColors.DarkGreen}; + color: ${CoWAMMColors.LightGreen}; + border: ${({ totalItems }) => + totalItems > 2 ? `2px solid ${CoWAMMColors.Green}` : `4px solid ${CoWAMMColors.Green}`}; + display: flex; + align-items: center; + justify-content: center; + position: relative; + + > svg { + width: 100%; + height: 100%; + } + + ${({ totalItems, index }) => { + const styleMap: Record<number, Record<number, string>> = { + 2: { + 0: 'margin-right: -42px;', + }, + 3: { + 0: 'margin-bottom: -20px; z-index: 10;', + 1: 'margin-top: -20px;', + 2: 'margin-top: -20px;', + }, + 4: { + 0: 'margin-bottom: -5px; z-index: 10; margin-right: -10px;', + 1: 'margin-bottom: -5px; z-index: 10;', + 2: 'margin-top: -5px; margin-right: -10px;', + 3: 'margin-top: -5px;', + }, + } + + return styleMap[totalItems]?.[index] || '' + }} +` + +const CoWAMMEmblemItem = styled.div` + --size: 104px; + width: var(--size); + height: var(--size); + border-radius: var(--size); + padding: 30px 30px 23px 30px; + background: ${CoWAMMColors.LightGreen}; + color: ${CoWAMMColors.DarkGreen}; + border: 4px solid ${CoWAMMColors.Green}; + display: flex; + align-items: center; + justify-content: center; +` + +const EmblemArrow = styled.div` + --size: 32px; + width: var(--size); + height: var(--size); + min-width: var(--size); + border-radius: var(--size); + background: ${CoWAMMColors.DarkGreen}; + border: 3px solid ${CoWAMMColors.Green}; + margin: 0 -24px; + padding: 6px; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + color: ${CoWAMMColors.LightGreen}; + + > svg > path { + fill: ${CoWAMMColors.LightGreen}; + } +` +// Update the dummyData object to include all possible states const dummyData = { noLp: { apr: 1.5, comparison: 'UNI-V2' }, uniV2: { apr: 2.1, comparison: 'UNI-V2' }, sushi: { apr: 1.8, comparison: 'SushiSwap' }, curve: { apr: 1.3, comparison: 'Curve' }, pancake: { apr: 2.5, comparison: 'PancakeSwap' }, - multiple: { apr: 2.0, comparison: 'UNI-V2, SushiSwap' }, + twoLps: { apr: 2.0, comparison: 'UNI-V2 and SushiSwap' }, + threeLps: { apr: 2.2, comparison: 'UNI-V2, SushiSwap, and Curve' }, + fourLps: { apr: 2.4, comparison: 'UNI-V2, Sushiswap, Curve, and Balancer' }, } as const type StateKey = keyof typeof dummyData +// Update the lpTokenConfig mapping to match dummyData keys +const lpTokenConfig: Record<StateKey, LpToken[]> = { + noLp: [LpToken.UniswapV2], + uniV2: [LpToken.UniswapV2], + sushi: [LpToken.Sushiswap], + curve: [LpToken.Curve], + pancake: [LpToken.PancakeSwap], + twoLps: [LpToken.UniswapV2, LpToken.Sushiswap], + threeLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve], + fourLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve, LpToken.Curve], +} + +const lpTokenIcons: Record<LpToken, string> = { + [LpToken.UniswapV2]: ICON_UNISWAP, + [LpToken.Sushiswap]: ICON_SUSHISWAP, + [LpToken.PancakeSwap]: ICON_PANCAKESWAP, + [LpToken.Curve]: ICON_CURVE, +} + export function CoWAmmBanner() { const [selectedState, setSelectedState] = useState<StateKey>('noLp') @@ -207,12 +380,47 @@ export function CoWAmmBanner() { const getComparisonMessage = () => { const { comparison } = dummyData[selectedState] - if (selectedState === 'multiple') { - return `Get higher APR than average ${comparison}` + if (selectedState === 'noLp') { + return `yield over the average UNI-V2 pool` + } + if (selectedState === 'twoLps' || selectedState === 'threeLps') { + return `Get higher average APR than ${comparison}` } return `Get higher APR than ${comparison}` } + const renderLpEmblems = () => { + const tokens = lpTokenConfig[selectedState] + const totalItems = tokens.length + + if (totalItems === 0) { + return null + } + + return ( + <LpEmblems totalItems={totalItems}> + <LpEmblemItemsWrapper totalItems={totalItems}> + {tokens.map((token, index) => ( + <LpEmblemItem key={token} totalItems={totalItems} index={index}> + <SVG src={lpTokenIcons[token]} /> + </LpEmblemItem> + ))} + </LpEmblemItemsWrapper> + <EmblemArrow> + <SVG src={ICON_ARROW} /> + </EmblemArrow> + <CoWAMMEmblemItem> + <ProductLogo + height={'100%'} + overrideColor={CoWAMMColors.DarkGreen} + variant={ProductVariant.CowAmm} + logoIconOnly + /> + </CoWAMMEmblemItem> + </LpEmblems> + ) + } + return ClosableBanner('cow_amm_banner_2024_va', (close) => ( <BannerWrapper> <CloseButton @@ -229,32 +437,41 @@ export function CoWAmmBanner() { <option value="sushi">SushiSwap LP</option> <option value="curve">Curve LP</option> <option value="pancake">PancakeSwap LP</option> - <option value="multiple">Multiple LP tokens</option> + <option value="twoLps">2 LP tokens</option> + <option value="threeLps">3 LP tokens</option> + <option value="fourLps">4 LP tokens</option> </DEMO_DROPDOWN> <Title> - <ProductLogo height={20} overrideColor={'var(--lightGreen)'} variant={ProductVariant.CowAmm} logoIconOnly /> + <ProductLogo height={20} overrideColor={CoWAMMColors.DarkGreen} variant={ProductVariant.CowAmm} logoIconOnly /> <span>CoW AMM</span> </Title> - <Card bgColor={'var(--blue)'}> + <Card bgColor={CoWAMMColors.Blue}> + <StarIcon size={36} top={-17} right={80}> + <SVG src={ICON_STAR} /> + </StarIcon> <h3> - <Textfit mode="single" forceSingleModeWidth={false} min={21} max={76}> + <Textfit mode="single" forceSingleModeWidth={false} min={21} max={80} key={getAprMessage()}> {getAprMessage()} </Textfit> </h3> <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={28}> + <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={28} key={getComparisonMessage()}> {getComparisonMessage()} </Textfit> </p> + <StarIcon size={26} bottom={-10} right={20}> + <SVG src={ICON_STAR} /> + </StarIcon> </Card> - <Card bgColor={'var(--green)'} color={'var(--lighterGreen)'}> + <Card bgColor={CoWAMMColors.Green} color={CoWAMMColors.LightGreen}> <p> - <Textfit mode="single" forceSingleModeWidth={false} min={10} max={28}> - One-click convert, boost yield + <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={30}> + One-click convert, <b>boost yield</b> </Textfit> </p> + {renderLpEmblems()} </Card> <CTAButton onClick={handleCTAClick}>Booooost APR gas-free!</CTAButton> diff --git a/libs/assets/src/cow-swap/icon-curve.svg b/libs/assets/src/cow-swap/icon-curve.svg new file mode 100644 index 0000000000..07688bbd5c --- /dev/null +++ b/libs/assets/src/cow-swap/icon-curve.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 34"><path fill="#BCEC79" d="M26.194 13.864c-.264-2.34-4.42-5.15-11.121-7.516a3.02 3.02 0 0 0-.464-.04c-.28 0-.563.053-.708.237-.216.274-.16.816-.071 1.017 1.01 2.277 2.354 5.621 2.69 9.024 1.526-.064 4.128-.18 6.523-.485 3.348-.45 3.2-1.795 3.151-2.238v.002Z"/><path fill="#BCEC79" d="M30.805 11.095c-.957-1.367-4.086-3.777-5.415-4.564-1.123-.664-6.382-3.48-11.59-5.12-.761-.236-2.164-.593-3.708-.593-1.262 0-2.619.238-3.796.973C3.59 3.481 1.238 8.142.574 13.129c-.74 5.511-.901 15.784 3.188 19.323 1.426 1.235 3.233 1.519 5.523.867 1.907-.545 4.161-2.822 4.894-6.678l.085-.442.037-.013c.698-2.77.913-5.342.735-8.754v-.006c-.177-3.404-1.579-6.904-2.617-9.24-.256-.58-.395-1.748.268-2.593.388-.494 1.174-1.027 2.714-.757l.062.012.06.021c9.649 3.395 11.962 6.671 12.206 8.825.164 1.502-.48 3.402-4.485 3.939-2.45.313-5.076.432-6.631.496.098 2.785-.083 5.12-.587 7.466.564-.197 1.102-.387 1.629-.577 3.234-1.15 5.787-2.059 9.767-2.79l.075-.014c1.162-.193 4.246-.704 5.048-3.803.894-3.36.159-4.597-1.738-7.316h-.002ZM9.588 26.147c-.64 1.282-1.405 1.529-1.802 1.564h-.033l-.033.001c-.4 0-1.18-.189-1.905-1.449-.61-1.03-.968-2.445-1.009-3.973-.04-1.521.236-2.953.776-4.032.64-1.281 1.405-1.528 1.804-1.563h.064c.4-.002 1.181.185 1.905 1.445.592 1.043.95 2.45 1.009 3.964v.01c.04 1.52-.235 2.953-.776 4.031v.002Z"/><path fill="#BCEC79" d="M8.013 18.91c-.233-.406-.432-.584-.536-.647-.106.069-.301.262-.516.687-.422.845-.646 2.047-.613 3.302.033 1.25.324 2.431.8 3.236.238.415.439.594.543.658.106-.07.301-.263.515-.687.423-.843.645-2.046.614-3.296-.05-1.253-.344-2.438-.807-3.252v-.002Zm23.979 10.21a.343.343 0 0 1-.346-.347c0-.194.152-.347.346-.347.2 0 .356.153.356.347a.351.351 0 0 1-.356.346Zm.236 2.72h-.48v-2.384h.48v2.384Zm-.926-2.383v.42h-.614v1.964h-.48v-1.964h-.384v-.42h.383v-.208c0-.462.292-.763.726-.763.152 0 .263.023.351.051v.44a.945.945 0 0 0-.259-.042c-.185 0-.337.074-.337.3v.222h.614Zm-2.198 2.413a.363.363 0 0 1-.365-.366c0-.203.157-.36.365-.36.213 0 .36.162.36.36a.359.359 0 0 1-.36.365Zm-2.56-1.073c.041.356.263.638.67.638a.563.563 0 0 0 .55-.356h.494c-.037.226-.31.79-1.044.79-.689 0-1.165-.527-1.165-1.22 0-.679.485-1.22 1.141-1.22.638 0 1.11.541 1.11 1.193 0 .064-.005.124-.01.175h-1.746Zm.646-.933c-.355 0-.573.25-.633.573h1.253c-.051-.319-.264-.573-.62-.573Zm-1.858-.409h.513l-.947 2.384h-.494l-.952-2.384h.513l.42 1.077c.083.217.185.48.263.72h.005c.083-.24.18-.503.263-.72l.416-1.077Zm-2.327-.025c.111 0 .176.009.231.014v.48a1.48 1.48 0 0 0-.25-.023c-.36 0-.614.203-.614.66v1.28h-.48v-2.384h.48v.314a.706.706 0 0 1 .633-.341Zm-2.959 2.437c-.527 0-.878-.356-.878-.984v-1.428h.48v1.405c0 .314.236.531.536.531a.539.539 0 0 0 .545-.55v-1.386h.48v2.384h-.47v-.332a.817.817 0 0 1-.693.36Zm-2.833.001a1.672 1.672 0 0 1-1.668-1.69c0-.934.735-1.692 1.668-1.692a1.5 1.5 0 0 1 1.465 1.137h-.508c-.125-.384-.555-.656-.957-.656-.67 0-1.187.54-1.187 1.21 0 .67.517 1.21 1.187 1.21.44 0 .823-.249.97-.66h.509c-.153.642-.703 1.141-1.479 1.141Z"/></svg> \ No newline at end of file diff --git a/libs/assets/src/cow-swap/icon-pancakeswap.svg b/libs/assets/src/cow-swap/icon-pancakeswap.svg new file mode 100644 index 0000000000..3367035850 --- /dev/null +++ b/libs/assets/src/cow-swap/icon-pancakeswap.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 192 199"><path fill="currentColor" fill-rule="evenodd" d="M25.708 178.998c16.598 12.582 40.353 19.587 69.844 19.609h.148c29.49-.022 53.245-7.027 69.844-19.609 16.797-12.733 25.708-30.803 25.708-51.25 0-19.701-8.892-33.907-18.952-43.51-7.884-7.525-16.585-12.344-22.643-15.117 1.37-4.162 3.079-9.61 4.608-15.238 2.047-7.53 4.054-16.366 4.054-22.84 0-7.663-1.688-15.36-6.239-21.34C147.272 3.385 140.033 0 131.325 0c-6.806 0-12.584 2.499-17.107 6.81-4.324 4.12-7.202 9.593-9.189 15.298-3.492 10.024-4.851 22.618-5.233 35.187h-8.34c-.382-12.569-1.742-25.163-5.233-35.187-1.987-5.705-4.865-11.177-9.19-15.298C72.51 2.5 66.734 0 59.927 0 51.218 0 43.98 3.385 39.171 9.703c-4.55 5.98-6.239 13.677-6.239 21.34 0 6.474 2.008 15.31 4.054 22.84 1.53 5.628 3.239 11.076 4.608 15.238-6.058 2.773-14.758 7.592-22.642 15.118C8.892 93.84 0 108.047 0 127.748c0 20.447 8.91 38.517 25.708 51.25Zm36.647-46.323c5.666 0 10.258-5.061 10.258-14.779s-4.592-14.779-10.258-14.779c-5.665 0-10.257 5.061-10.257 14.779s4.592 14.779 10.257 14.779Zm66.676 0c5.665 0 10.257-5.061 10.257-14.779s-4.592-14.779-10.257-14.779c-5.666 0-10.258 5.061-10.258 14.779s4.592 14.779 10.258 14.779Z" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/libs/assets/src/cow-swap/icon-sushi.svg b/libs/assets/src/cow-swap/icon-sushi.svg new file mode 100644 index 0000000000..5e214be336 --- /dev/null +++ b/libs/assets/src/cow-swap/icon-sushi.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 22"><path fill="currentColor" d="M12.94 9.462c2.4 1.6 4.8 2.1 5.5 1.1.7-1-.7-3.1-3-4.7-2.4-1.6-4.8-2.1-5.5-1.1-.7 1 .7 3.1 3 4.7Z"/><path fill="currentColor" fill-rule="evenodd" d="M17.04 3.464c5.2 3.4 8 8.2 6.5 10.5l-4.6 6.8-.048-.033c-1.658 2.215-7.002 1.297-12.051-2.267C1.823 14.922-1.065 10.413.366 7.982L.34 7.964l4.6-6.7c1.6-2.3 7-1.4 12.1 2.2Zm-5.2 7.798c4.7 3.3 9.6 4.3 11 2.3 1.3-2-1.5-6.2-6.2-9.5s-9.6-4.3-11-2.3c-1.4 2 1.4 6.2 6.2 9.5Zm-7.3-7.699c0-.1-.1-.2-.2-.1a.559.559 0 0 1-.11.082c-.052.033-.09.056-.09.118.026.08.053.154.078.222.068.185.122.332.122.478 0 .1.1.2.2.1.1 0 .2-.1.1-.2 0-.2 0-.4-.1-.7Zm.501 1.6c0-.1-.1-.2-.2-.1s-.1.1-.1.2c1.1 2.5 3.4 5.2 6.4 7.2.067.067.134.044.2.022.034-.011.067-.022.1-.022.067-.067.045-.133.023-.2a.322.322 0 0 1-.023-.1c-3.1-2-5.3-4.6-6.4-7Zm11.9 9.9c0-.1.1-.1.2-.1.15.05.324.1.5.15.174.05.35.1.5.15.1 0 .1.1.1.2s-.1.1-.2.1c-.15-.05-.325-.1-.5-.15-.175-.05-.35-.1-.5-.15-.1 0-.1-.1-.1-.2Zm1.8.5c0-.1.1-.2.2-.2.8.1 1.6.2 2.4.1.1 0 .2.1.2.2s-.1.2-.2.2c-.7.1-1.6 0-2.4-.1-.1 0-.2-.1-.2-.2Z" clip-rule="evenodd"/></svg> \ No newline at end of file diff --git a/libs/assets/src/cow-swap/icon-uni.svg b/libs/assets/src/cow-swap/icon-uni.svg new file mode 100644 index 0000000000..a88a09ae2e --- /dev/null +++ b/libs/assets/src/cow-swap/icon-uni.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 38 41"><path fill="currentColor" d="M14.702 9.722c-.46-.067-.479-.075-.263-.106.414-.06 1.392.022 2.066.173 1.574.354 3.005 1.26 4.533 2.87l.406.427.581-.088c2.447-.372 4.936-.076 7.018.833.573.25 1.476.749 1.588.877.036.04.102.303.147.584.155.97.077 1.713-.236 2.268-.17.302-.18.398-.066.656.092.206.347.359.6.359.517 0 1.074-.792 1.332-1.893l.103-.437.203.217c1.113 1.194 1.988 2.822 2.138 3.98l.04.302-.188-.274c-.322-.473-.645-.794-1.06-1.054-.747-.468-1.537-.627-3.63-.731-1.89-.094-2.959-.247-4.02-.574-1.804-.557-2.713-1.298-4.857-3.958-.952-1.181-1.54-1.835-2.126-2.362-1.33-1.196-2.636-1.823-4.31-2.069Z"/><path fill="currentColor" d="M31.058 12.36c.047-.791.16-1.313.389-1.79.09-.19.175-.344.188-.344.013 0-.026.14-.087.31-.166.463-.193 1.096-.08 1.832.146.935.228 1.07 1.273 2.08.49.473 1.059 1.07 1.266 1.326l.375.467-.375-.333c-.46-.408-1.516-1.202-1.75-1.316-.155-.076-.179-.075-.275.016-.09.084-.108.21-.12.804-.02.927-.153 1.522-.475 2.116-.174.322-.202.253-.044-.11.118-.27.13-.39.129-1.287-.002-1.803-.228-2.236-1.555-2.979-.336-.188-.89-.459-1.23-.602-.341-.144-.612-.269-.602-.278.037-.035 1.331.322 1.852.511.774.282.902.319.996.285.064-.023.094-.197.125-.707Zm-15.464 3.084c-.933-1.214-1.51-3.076-1.385-4.467l.04-.431.211.037c.399.068 1.086.31 1.408.495.883.507 1.265 1.175 1.654 2.89.114.503.263 1.071.332 1.264.11.31.53 1.033.87 1.503.244.338.082.499-.46.453-.827-.071-1.947-.802-2.67-1.744Zm14.333 9.037c-4.357-1.66-5.892-3.1-5.892-5.53 0-.358.013-.65.029-.65s.184.117.375.262c.883.67 1.873.956 4.612 1.334 1.611.223 2.518.402 3.355.665 2.66.834 4.305 2.527 4.697 4.833.114.67.047 1.927-.138 2.59-.146.523-.59 1.466-.709 1.502-.033.01-.065-.109-.073-.27-.045-.866-.508-1.71-1.284-2.341-.884-.718-2.07-1.29-4.972-2.395Zm-3.059.689c-.055-.307-.15-.7-.21-.872l-.112-.313.207.22c.286.303.512.69.703 1.208.146.395.162.512.161 1.153 0 .63-.02.762-.154 1.117a3.51 3.51 0 0 1-.92 1.384c-.796.766-1.819 1.19-3.295 1.367a34.81 34.81 0 0 1-1.663.114c-1.657.082-2.748.25-3.729.576-.14.047-.266.075-.28.063-.039-.038.629-.414 1.18-.665.777-.354 1.55-.547 3.285-.82.856-.136 1.74-.3 1.965-.364 2.12-.616 3.21-2.203 2.862-4.168Z"/><path fill="currentColor" d="M28.865 28.526c-.579-1.177-.711-2.314-.394-3.374.034-.113.088-.206.12-.206.034 0 .17.07.304.155.267.17.802.457 2.228 1.192 1.778.917 2.792 1.627 3.482 2.439.604.71.978 1.52 1.158 2.507.102.56.042 1.905-.11 2.468-.478 1.775-1.59 3.17-3.175 3.983-.232.12-.44.217-.463.218-.023 0 .062-.203.188-.453.533-1.054.594-2.08.19-3.222-.246-.699-.75-1.552-1.766-2.993-1.181-1.677-1.471-2.123-1.762-2.714ZM12.5 34.878c1.616-1.292 3.628-2.21 5.46-2.49.79-.122 2.106-.074 2.837.103 1.173.284 2.221.92 2.767 1.677.533.74.762 1.385 1 2.82.093.566.196 1.135.227 1.263.178.744.526 1.339.957 1.637.685.474 1.864.504 3.024.076.197-.073.368-.123.38-.112.042.04-.542.41-.954.604-.555.262-.995.364-1.581.364-1.063 0-1.945-.512-2.68-1.554-.145-.205-.471-.82-.724-1.366-.777-1.676-1.16-2.187-2.063-2.746-.785-.486-1.798-.573-2.56-.22-1 .464-1.28 1.674-.563 2.44.285.305.816.568 1.25.619.813.096 1.511-.49 1.511-1.265 0-.504-.204-.792-.72-1.012-.704-.3-1.46.05-1.457.676.002.267.125.435.407.556.182.077.186.084.038.054-.646-.126-.797-.863-.278-1.352.624-.587 1.913-.328 2.356.474.186.336.207 1.006.045 1.411-.363.906-1.421 1.383-2.495 1.123-.73-.176-1.028-.367-1.91-1.226-1.53-1.492-2.125-1.781-4.333-2.107l-.423-.063.481-.384Z"/><path fill="currentColor" fill-rule="evenodd" d="M.952 1.452c5.114 5.871 12.998 15.013 13.39 15.525.323.422.201.802-.352 1.1-.308.165-.941.333-1.258.333-.358 0-.762-.163-1.056-.427-.208-.186-1.047-1.372-2.984-4.214-1.482-2.175-2.722-3.98-2.755-4.01-.079-.07-.077-.067 2.604 4.472 1.684 2.85 2.252 3.857 2.252 3.992 0 .274-.079.418-.436.795-.596.629-.862 1.335-1.055 2.797-.215 1.64-.821 2.797-2.5 4.779-.984 1.16-1.145 1.372-1.393 1.84-.313.589-.399.919-.433 1.662-.037.786.035 1.294.288 2.045.223.658.455 1.093 1.048 1.962.512.75.806 1.307.806 1.525 0 .173.035.173.828.004 1.898-.405 3.44-1.118 4.306-1.992.537-.541.663-.84.667-1.581.002-.485-.016-.586-.154-.865-.226-.454-.636-.832-1.541-1.417-1.186-.767-1.692-1.384-1.832-2.233-.115-.696.018-1.188.674-2.488.68-1.346.848-1.92.961-3.276.074-.877.176-1.223.442-1.5.277-.29.527-.387 1.215-.476 1.12-.145 1.833-.42 2.42-.93.508-.444.721-.87.754-1.514l.025-.488-.285-.313C14.57 15.423.263.559.2.559c-.014 0 .325.402.752.893Zm6.744 29.533a.834.834 0 0 0-.28-1.136c-.368-.231-.94-.122-.94.18 0 .091.054.158.176.218.204.099.219.21.058.439-.163.231-.15.434.037.573.3.222.726.1.95-.274ZM16.59 20.06c-.526.152-1.037.68-1.196 1.233-.096.337-.042.928.103 1.111.234.295.46.373 1.071.369 1.198-.008 2.24-.494 2.36-1.101.1-.498-.358-1.188-.988-1.491-.325-.157-1.016-.219-1.35-.122Zm1.4 1.035c.184-.248.104-.516-.21-.698-.599-.345-1.503-.06-1.503.475 0 .266.471.557.904.557.288 0 .682-.163.809-.334Z" clip-rule="evenodd"/></svg> \ No newline at end of file From b6f7c69372b7d8a4f67a39b6b0d4df3a53dae8bd Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:34:31 +0100 Subject: [PATCH 03/15] feat: add token selector cow amm banner --- .../src/common/pure/CoWAMMBanner/dummyData.ts | 30 ++ .../src/common/pure/CoWAMMBanner/index.tsx | 498 ++++-------------- .../src/common/pure/CoWAMMBanner/styled.ts | 354 +++++++++++++ .../pure/CoWAMMBanner/tokenSelectorBanner.tsx | 143 +++++ .../pure/SelectTokenModal/index.tsx | 2 + .../pure/TokensVirtualList/index.tsx | 4 + libs/ui/src/enum.ts | 9 + libs/ui/src/theme/ThemeColorVars.tsx | 13 +- 8 files changed, 647 insertions(+), 406 deletions(-) create mode 100644 apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts create mode 100644 apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts create mode 100644 apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts new file mode 100644 index 0000000000..0cfb4da319 --- /dev/null +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts @@ -0,0 +1,30 @@ +export enum LpToken { + UniswapV2 = 'UniswapV2', + Sushiswap = 'Sushiswap', + PancakeSwap = 'PancakeSwap', + Curve = 'Curve', +} + +export const dummyData = { + noLp: { apr: 1.5, comparison: 'UNI-V2' }, + uniV2: { apr: 2.1, comparison: 'UNI-V2' }, + sushi: { apr: 1.8, comparison: 'SushiSwap' }, + curve: { apr: 1.3, comparison: 'Curve' }, + pancake: { apr: 2.5, comparison: 'PancakeSwap' }, + twoLps: { apr: 2.0, comparison: 'UNI-V2 and SushiSwap' }, + threeLps: { apr: 2.2, comparison: 'UNI-V2, SushiSwap, and Curve' }, + fourLps: { apr: 2.4, comparison: 'UNI-V2, Sushiswap, Curve, and Balancer' }, +} as const + +export type StateKey = keyof typeof dummyData + +export const lpTokenConfig: Record<StateKey, LpToken[]> = { + noLp: [LpToken.UniswapV2], + uniV2: [LpToken.UniswapV2], + sushi: [LpToken.Sushiswap], + curve: [LpToken.Curve], + pancake: [LpToken.PancakeSwap], + twoLps: [LpToken.UniswapV2, LpToken.Sushiswap], + threeLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve], + fourLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve, LpToken.Curve], +} diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index 59dcebe5ed..d0164987f6 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useCallback, useMemo } from 'react' import ICON_ARROW from '@cowprotocol/assets/cow-swap/arrow.svg' import ICON_CURVE from '@cowprotocol/assets/cow-swap/icon-curve.svg' @@ -6,343 +6,32 @@ import ICON_PANCAKESWAP from '@cowprotocol/assets/cow-swap/icon-pancakeswap.svg' import ICON_SUSHISWAP from '@cowprotocol/assets/cow-swap/icon-sushi.svg' import ICON_UNISWAP from '@cowprotocol/assets/cow-swap/icon-uni.svg' import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' -import { Media, ProductLogo, ProductVariant } from '@cowprotocol/ui' +import { ProductLogo, ProductVariant, UI } from '@cowprotocol/ui' import { ClosableBanner } from '@cowprotocol/ui' -import { X } from 'react-feather' import SVG from 'react-inlinesvg' import { Textfit } from 'react-textfit' -import styled from 'styled-components/macro' -import { cowAnalytics } from 'modules/analytics' - -// Add this enum at the top of the file, after imports -enum CoWAMMColors { - DarkGreen = '#194d05', - Green = '#2b6f0b', - LightGreen = '#bcec79', - LighterGreen = '#dcf8a7', - Blue = '#3fc4ff', - LightBlue = '#ccf8ff', -} - -// Add this enum after the CoWAMMColors enum -enum LpToken { - UniswapV2 = 'UniswapV2', - Sushiswap = 'Sushiswap', - PancakeSwap = 'PancakeSwap', - Curve = 'Curve', -} - -const BannerWrapper = styled.div` - position: fixed; - top: 76px; - right: 10px; - z-index: 3; - width: 485px; - height: auto; - border-radius: 24px; - background-color: ${CoWAMMColors.DarkGreen}; - color: ${CoWAMMColors.DarkGreen}; - padding: 20px; - gap: 20px; - display: flex; - flex-flow: column wrap; - align-items: center; - justify-content: center; - overflow: hidden; - - ${Media.upToSmall()} { - width: 100%; - height: auto; - left: 0; - right: 0; - margin: 0 auto; - bottom: 57px; - top: initial; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - box-shadow: 0 0 0 100vh rgb(0 0 0 / 40%); - z-index: 10; - } -` - -const CloseButton = styled(X)` - position: absolute; - top: 16px; - right: 16px; - cursor: pointer; - color: ${CoWAMMColors.LightGreen}; - opacity: 0.6; - transition: opacity 0.2s ease-in-out; - - &:hover { - opacity: 1; - } -` - -const Title = styled.h2` - display: flex; - align-items: center; - gap: 8px; - font-size: 18px; - font-weight: bold; - margin: 0 auto 0 0; - color: ${CoWAMMColors.LightGreen}; - - ${Media.upToSmall()} { - font-size: 26px; - } -` - -const Card = styled.div<{ bgColor?: string; color?: string; height?: string }>` - --default-height: 150px; - display: flex; - flex-flow: row nowrap; - gap: 24px; - align-items: center; - justify-content: center; - font-size: 30px; - line-height: 1.2; - font-weight: 500; - margin: 0; - width: 100%; - max-width: 100%; - height: ${({ height }) => height || 'var(--default-height)'}; - max-height: ${({ height }) => height || 'var(--default-height)'}; - border-radius: 16px; - padding: 24px; - background: ${({ bgColor }) => bgColor || 'transparent'}; - color: ${({ color }) => color || 'inherit'}; - position: relative; - > h3, - > p { - display: flex; - align-items: center; - justify-content: center; - margin: 0; - width: 100%; - height: 100%; - max-height: 100%; - - > div { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - } - } - - > h3 { - font-weight: bold; - letter-spacing: -2px; - } - - > p { - font-weight: inherit; - } - - > p b { - font-weight: 900; - color: ${CoWAMMColors.LighterGreen}; - } -` - -const CTAButton = styled.button` - --size: 58px; - background: ${CoWAMMColors.LightGreen}; - color: ${CoWAMMColors.DarkGreen}; - border: none; - border-radius: var(--size); - min-height: var(--size); - padding: 12px 24px; - font-size: 24px; - font-weight: bold; - cursor: pointer; - width: 100%; - max-width: 100%; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - transition: background 0.2s ease-in-out; - - &:hover { - background: ${CoWAMMColors.LighterGreen}; - } -` - -const SecondaryLink = styled.a` - color: ${CoWAMMColors.LightGreen}; - font-size: 14px; - font-weight: 500; - text-decoration: none; - - &:hover { - text-decoration: underline; - } -` - -const DEMO_DROPDOWN = styled.select` - position: fixed; - bottom: 150px; - right: 10px; - z-index: 999999999; - padding: 5px; - font-size: 16px; -` +import { upToSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' -const StarIcon = styled.div<{ size?: number; top?: number; left?: number; right?: number; bottom?: number }>` - width: ${({ size }) => size || 16}px; - height: ${({ size }) => size || 16}px; - position: absolute; - top: ${({ top }) => top ?? 'initial'}px; - left: ${({ left }) => left ?? 'initial'}px; - right: ${({ right }) => right ?? 'initial'}px; - bottom: ${({ bottom }) => bottom ?? 'initial'}px; -` - -const LpEmblems = styled.div<{ totalItems: number }>` - display: flex; - gap: 8px; - width: 100%; - justify-content: center; - align-items: center; -` - -const LpEmblemItemsWrapper = styled.div<{ totalItems: number }>` - display: ${({ totalItems }) => (totalItems > 2 ? 'grid' : 'flex')}; - gap: ${({ totalItems }) => (totalItems > 2 ? '0' : '8px')}; - width: 100%; - justify-content: center; - align-items: center; - - ${({ totalItems }) => - totalItems === 3 && - ` - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr; - justify-items: center; - - > :first-child { - grid-column: 1 / -1; - } - `} - - ${({ totalItems }) => - totalItems === 4 && - ` - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr; - `} -` - -const LpEmblemItem = styled.div<{ - totalItems: number - index: number -}>` - --size: ${({ totalItems }) => - totalItems === 4 ? '50px' : totalItems === 3 ? '65px' : totalItems === 2 ? '80px' : '104px'}; - width: var(--size); - height: var(--size); - padding: ${({ totalItems }) => (totalItems === 4 ? '10px' : totalItems >= 2 ? '15px' : '20px')}; - border-radius: 50%; - background: ${CoWAMMColors.DarkGreen}; - color: ${CoWAMMColors.LightGreen}; - border: ${({ totalItems }) => - totalItems > 2 ? `2px solid ${CoWAMMColors.Green}` : `4px solid ${CoWAMMColors.Green}`}; - display: flex; - align-items: center; - justify-content: center; - position: relative; - - > svg { - width: 100%; - height: 100%; - } - - ${({ totalItems, index }) => { - const styleMap: Record<number, Record<number, string>> = { - 2: { - 0: 'margin-right: -42px;', - }, - 3: { - 0: 'margin-bottom: -20px; z-index: 10;', - 1: 'margin-top: -20px;', - 2: 'margin-top: -20px;', - }, - 4: { - 0: 'margin-bottom: -5px; z-index: 10; margin-right: -10px;', - 1: 'margin-bottom: -5px; z-index: 10;', - 2: 'margin-top: -5px; margin-right: -10px;', - 3: 'margin-top: -5px;', - }, - } - - return styleMap[totalItems]?.[index] || '' - }} -` - -const CoWAMMEmblemItem = styled.div` - --size: 104px; - width: var(--size); - height: var(--size); - border-radius: var(--size); - padding: 30px 30px 23px 30px; - background: ${CoWAMMColors.LightGreen}; - color: ${CoWAMMColors.DarkGreen}; - border: 4px solid ${CoWAMMColors.Green}; - display: flex; - align-items: center; - justify-content: center; -` +import { cowAnalytics } from 'modules/analytics' -const EmblemArrow = styled.div` - --size: 32px; - width: var(--size); - height: var(--size); - min-width: var(--size); - border-radius: var(--size); - background: ${CoWAMMColors.DarkGreen}; - border: 3px solid ${CoWAMMColors.Green}; - margin: 0 -24px; - padding: 6px; - z-index: 10; - display: flex; - align-items: center; - justify-content: center; - color: ${CoWAMMColors.LightGreen}; +import { LpToken, dummyData, lpTokenConfig, StateKey } from './dummyData' +import * as styledEl from './styled' - > svg > path { - fill: ${CoWAMMColors.LightGreen}; - } -` -// Update the dummyData object to include all possible states -const dummyData = { - noLp: { apr: 1.5, comparison: 'UNI-V2' }, - uniV2: { apr: 2.1, comparison: 'UNI-V2' }, - sushi: { apr: 1.8, comparison: 'SushiSwap' }, - curve: { apr: 1.3, comparison: 'Curve' }, - pancake: { apr: 2.5, comparison: 'PancakeSwap' }, - twoLps: { apr: 2.0, comparison: 'UNI-V2 and SushiSwap' }, - threeLps: { apr: 2.2, comparison: 'UNI-V2, SushiSwap, and Curve' }, - fourLps: { apr: 2.4, comparison: 'UNI-V2, Sushiswap, Curve, and Balancer' }, -} as const +const ANALYTICS_URL = 'https://cow.fi/pools?utm_source=swap.cow.fi&utm_medium=web&utm_content=cow_amm_banner' +const BANNER_ID = 'cow_amm_banner_2024_va' -type StateKey = keyof typeof dummyData - -// Update the lpTokenConfig mapping to match dummyData keys -const lpTokenConfig: Record<StateKey, LpToken[]> = { - noLp: [LpToken.UniswapV2], - uniV2: [LpToken.UniswapV2], - sushi: [LpToken.Sushiswap], - curve: [LpToken.Curve], - pancake: [LpToken.PancakeSwap], - twoLps: [LpToken.UniswapV2, LpToken.Sushiswap], - threeLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve], - fourLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve, LpToken.Curve], -} +const DEMO_DROPDOWN_OPTIONS = [ + { value: 'noLp', label: 'No LP tokens' }, + { value: 'uniV2', label: 'UNI-V2 LP' }, + { value: 'sushi', label: 'SushiSwap LP' }, + { value: 'curve', label: 'Curve LP' }, + { value: 'pancake', label: 'PancakeSwap LP' }, + { value: 'twoLps', label: '2 LP tokens' }, + { value: 'threeLps', label: '3 LP tokens' }, + { value: 'fourLps', label: '4 LP tokens' }, +] const lpTokenIcons: Record<LpToken, string> = { [LpToken.UniswapV2]: ICON_UNISWAP, @@ -353,32 +42,30 @@ const lpTokenIcons: Record<LpToken, string> = { export function CoWAmmBanner() { const [selectedState, setSelectedState] = useState<StateKey>('noLp') + const isMobile = useMediaQuery(upToSmall) - const handleCTAClick = () => { + const handleCTAClick = useCallback(() => { cowAnalytics.sendEvent({ category: 'CoW Swap', - action: 'CoW AMM Banner CTA Clicked', + action: 'CoW AMM Banner [Global] CTA Clicked', }) - window.open( - 'https://balancer.fi/pools/cow?utm_source=swap.cow.fi&utm_medium=web&utm_content=cow_amm_banner', - '_blank', - ) - } + window.open(ANALYTICS_URL, '_blank') + }, []) - const handleClose = () => { + const handleClose = useCallback(() => { cowAnalytics.sendEvent({ category: 'CoW Swap', - action: 'CoW AMM Banner Closed', + action: 'CoW AMM Banner [Global] Closed', }) - } + }, []) - const getAprMessage = () => { + const aprMessage = useMemo(() => { const { apr } = dummyData[selectedState] return `+${apr.toFixed(1)}%` - } + }, [selectedState]) - const getComparisonMessage = () => { + const comparisonMessage = useMemo(() => { const { comparison } = dummyData[selectedState] if (selectedState === 'noLp') { return `yield over the average UNI-V2 pool` @@ -387,9 +74,9 @@ export function CoWAmmBanner() { return `Get higher average APR than ${comparison}` } return `Get higher APR than ${comparison}` - } + }, [selectedState, dummyData]) - const renderLpEmblems = () => { + const lpEmblems = useMemo(() => { const tokens = lpTokenConfig[selectedState] const totalItems = tokens.length @@ -398,84 +85,87 @@ export function CoWAmmBanner() { } return ( - <LpEmblems totalItems={totalItems}> - <LpEmblemItemsWrapper totalItems={totalItems}> + <styledEl.LpEmblems> + <styledEl.LpEmblemItemsWrapper totalItems={totalItems}> {tokens.map((token, index) => ( - <LpEmblemItem key={token} totalItems={totalItems} index={index}> + <styledEl.LpEmblemItem key={token} totalItems={totalItems} index={index}> <SVG src={lpTokenIcons[token]} /> - </LpEmblemItem> + </styledEl.LpEmblemItem> ))} - </LpEmblemItemsWrapper> - <EmblemArrow> + </styledEl.LpEmblemItemsWrapper> + <styledEl.EmblemArrow> <SVG src={ICON_ARROW} /> - </EmblemArrow> - <CoWAMMEmblemItem> + </styledEl.EmblemArrow> + <styledEl.CoWAMMEmblemItem> <ProductLogo height={'100%'} - overrideColor={CoWAMMColors.DarkGreen} + overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} variant={ProductVariant.CowAmm} logoIconOnly /> - </CoWAMMEmblemItem> - </LpEmblems> + </styledEl.CoWAMMEmblemItem> + </styledEl.LpEmblems> ) - } - - return ClosableBanner('cow_amm_banner_2024_va', (close) => ( - <BannerWrapper> - <CloseButton - size={24} - onClick={() => { - handleClose() - close() - }} - /> - - <DEMO_DROPDOWN value={selectedState} onChange={(e) => setSelectedState(e.target.value as StateKey)}> - <option value="noLp">No LP tokens</option> - <option value="uniV2">UNI-V2 LP</option> - <option value="sushi">SushiSwap LP</option> - <option value="curve">Curve LP</option> - <option value="pancake">PancakeSwap LP</option> - <option value="twoLps">2 LP tokens</option> - <option value="threeLps">3 LP tokens</option> - <option value="fourLps">4 LP tokens</option> - </DEMO_DROPDOWN> - - <Title> - <ProductLogo height={20} overrideColor={CoWAMMColors.DarkGreen} variant={ProductVariant.CowAmm} logoIconOnly /> + }, [selectedState]) + + const handleBannerClose = (close: () => void) => () => { + handleClose() + close() + } + + return ClosableBanner(BANNER_ID, (close) => ( + <styledEl.BannerWrapper> + <styledEl.CloseButton size={24} onClick={handleBannerClose(close)} /> + + <styledEl.DEMO_DROPDOWN value={selectedState} onChange={(e) => setSelectedState(e.target.value as StateKey)}> + {DEMO_DROPDOWN_OPTIONS.map((option) => ( + <option key={option.value} value={option.value}> + {option.label} + </option> + ))} + </styledEl.DEMO_DROPDOWN> + + <styledEl.Title> + <ProductLogo + height={20} + overrideColor={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`} + variant={ProductVariant.CowAmm} + logoIconOnly + /> <span>CoW AMM</span> - </Title> - <Card bgColor={CoWAMMColors.Blue}> - <StarIcon size={36} top={-17} right={80}> + </styledEl.Title> + <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_BLUE})`}> + <styledEl.StarIcon size={36} top={-17} right={80}> <SVG src={ICON_STAR} /> - </StarIcon> + </styledEl.StarIcon> <h3> - <Textfit mode="single" forceSingleModeWidth={false} min={21} max={80} key={getAprMessage()}> - {getAprMessage()} + <Textfit mode="single" forceSingleModeWidth={false} min={80} max={80} key={aprMessage}> + {aprMessage} </Textfit> </h3> <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={28} key={getComparisonMessage()}> - {getComparisonMessage()} + <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={28} key={comparisonMessage}> + {comparisonMessage} </Textfit> </p> - <StarIcon size={26} bottom={-10} right={20}> + <styledEl.StarIcon size={26} bottom={-10} right={20}> <SVG src={ICON_STAR} /> - </StarIcon> - </Card> - - <Card bgColor={CoWAMMColors.Green} color={CoWAMMColors.LightGreen}> - <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={30}> - One-click convert, <b>boost yield</b> - </Textfit> - </p> - {renderLpEmblems()} - </Card> - - <CTAButton onClick={handleCTAClick}>Booooost APR gas-free!</CTAButton> - <SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</SecondaryLink> - </BannerWrapper> + </styledEl.StarIcon> + </styledEl.Card> + + {!isMobile && ( + <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_GREEN})`} color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}> + <p> + <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={30}> + One-click convert, <b>boost yield</b> + </Textfit> + </p> + {lpEmblems} + </styledEl.Card> + )} + + <styledEl.CTAButton onClick={handleCTAClick}>Booooost APR gas-free!</styledEl.CTAButton> + <styledEl.SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</styledEl.SecondaryLink> + </styledEl.BannerWrapper> )) } diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts new file mode 100644 index 0000000000..44c54083fd --- /dev/null +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts @@ -0,0 +1,354 @@ +import { UI, Media } from '@cowprotocol/ui' + +import { X } from 'react-feather' +import styled from 'styled-components/macro' + +export const BannerWrapper = styled.div` + position: fixed; + top: 76px; + right: 10px; + z-index: 3; + width: 485px; + height: auto; + border-radius: 24px; + background-color: var(${UI.COLOR_COWAMM_DARK_GREEN}); + color: var(${UI.COLOR_COWAMM_DARK_GREEN}); + padding: 20px; + display: flex; + flex-flow: column wrap; + align-items: center; + justify-content: center; + gap: 20px; + overflow: hidden; + + ${Media.upToSmall()} { + width: 100%; + height: auto; + left: 0; + right: 0; + margin: 0 auto; + bottom: 57px; + top: initial; + border-radius: 24px 24px 0 0; + box-shadow: 0 0 0 100vh rgb(0 0 0 / 40%); + z-index: 10; + } +` + +export const CloseButton = styled(X)<{ color?: string; top?: number }>` + position: absolute; + top: ${({ top = 16 }) => top}px; + right: 16px; + cursor: pointer; + color: ${({ color }) => color || `var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}; + opacity: 0.6; + transition: opacity 0.2s ease-in-out; + + &:hover { + opacity: 1; + } +` + +export const Title = styled.h2<{ color?: string }>` + display: flex; + align-items: center; + gap: 8px; + font-size: 18px; + font-weight: bold; + margin: 0 auto 0 0; + color: ${({ color }) => color || `var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}; + + ${Media.upToSmall()} { + font-size: 26px; + } +` + +export const Card = styled.div<{ + bgColor?: string + color?: string + height?: string + borderColor?: string + borderWidth?: number + padding?: string + gap?: string +}>` + --default-height: 150px; + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: center; + gap: ${({ gap }) => gap || '24px'}; + font-size: 30px; + line-height: 1.2; + font-weight: 500; + margin: 0; + width: 100%; + max-width: 100%; + height: ${({ height }) => height || 'var(--default-height)'}; + max-height: ${({ height }) => height || 'var(--default-height)'}; + border-radius: 16px; + padding: ${({ padding }) => padding || '24px'}; + background: ${({ bgColor }) => bgColor || 'transparent'}; + color: ${({ color }) => color || 'inherit'}; + border: ${({ borderWidth, borderColor }) => borderWidth && borderColor && `${borderWidth}px solid ${borderColor}`}; + position: relative; + + > h3, + > p { + display: flex; + align-items: center; + justify-content: center; + margin: 0; + width: max-content; + height: 100%; + max-height: 100%; + + > div { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + } + + > h3 { + font-weight: bold; + letter-spacing: -2px; + } + + > p { + font-weight: inherit; + } + + > p b { + font-weight: 900; + color: var(${UI.COLOR_COWAMM_LIGHTER_GREEN}); + } +` + +export const CTAButton = styled.button<{ + bgColor?: string + bgHoverColor?: string + color?: string + size?: number + fontSize?: number +}>` + --size: ${({ size = 58 }) => size}px; + background: ${({ bgColor }) => bgColor || `var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}; + color: ${({ color }) => color || `var(${UI.COLOR_COWAMM_DARK_GREEN})`}; + border: none; + border-radius: var(--size); + min-height: var(--size); + padding: 12px 24px; + font-size: ${({ fontSize = 24 }) => fontSize}px; + font-weight: bold; + cursor: pointer; + width: 100%; + max-width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: background 0.2s ease-in-out; + + @keyframes shake { + 0% { + transform: translate(1px, 1px) rotate(0deg); + } + 10% { + transform: translate(-1px, -2px) rotate(-1deg); + } + 20% { + transform: translate(-3px, 0px) rotate(1deg); + } + 30% { + transform: translate(3px, 2px) rotate(0deg); + } + 40% { + transform: translate(1px, -1px) rotate(1deg); + } + 50% { + transform: translate(-1px, 2px) rotate(-1deg); + } + 60% { + transform: translate(-3px, 1px) rotate(0deg); + } + 70% { + transform: translate(3px, 1px) rotate(-1deg); + } + 80% { + transform: translate(-1px, -1px) rotate(1deg); + } + 90% { + transform: translate(1px, 2px) rotate(0deg); + } + 100% { + transform: translate(1px, -2px) rotate(-1deg); + } + } + + &:hover { + background: ${({ bgHoverColor }) => bgHoverColor || `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}; + animation: shake 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; + transform: translate3d(0, 0, 0); + backface-visibility: hidden; + perspective: 1000px; + } +` + +export const SecondaryLink = styled.a` + color: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); + font-size: 14px; + font-weight: 500; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +` + +export const DEMO_DROPDOWN = styled.select` + position: fixed; + bottom: 150px; + right: 10px; + z-index: 999999999; + padding: 5px; + font-size: 16px; +` + +export const StarIcon = styled.div<{ + color?: string + size?: number + top?: number | 'initial' + left?: number | 'initial' + right?: number | 'initial' + bottom?: number | 'initial' +}>` + width: ${({ size = 16 }) => size}px; + height: ${({ size = 16 }) => size}px; + position: absolute; + top: ${({ top }) => (top === 'initial' ? 'initial' : top != null ? `${top}px` : 'initial')}; + left: ${({ left }) => (left === 'initial' ? 'initial' : left != null ? `${left}px` : 'initial')}; + right: ${({ right }) => (right === 'initial' ? 'initial' : right != null ? `${right}px` : 'initial')}; + bottom: ${({ bottom }) => (bottom === 'initial' ? 'initial' : bottom != null ? `${bottom}px` : 'initial')}; + color: ${({ color }) => color ?? `var(${UI.COLOR_WHITE})`}; + + > svg > path { + fill: ${({ color }) => color ?? 'currentColor'}; + } +` + +export const LpEmblems = styled.div` + display: flex; + gap: 8px; + width: 100%; + justify-content: center; + align-items: center; +` + +export const LpEmblemItemsWrapper = styled.div<{ totalItems: number }>` + display: ${({ totalItems }) => (totalItems > 2 ? 'grid' : 'flex')}; + gap: ${({ totalItems }) => (totalItems > 2 ? '0' : '8px')}; + width: 100%; + justify-content: center; + align-items: center; + + ${({ totalItems }) => + totalItems === 3 && + ` + grid-template: 1fr 1fr / 1fr 1fr; + justify-items: center; + + > :first-child { + grid-column: 1 / -1; + } + `} + + ${({ totalItems }) => + totalItems === 4 && + ` + grid-template: 1fr 1fr / 1fr 1fr; + `} +` + +export const LpEmblemItem = styled.div<{ + totalItems: number + index: number +}>` + --size: ${({ totalItems }) => + totalItems === 4 ? '50px' : totalItems === 3 ? '65px' : totalItems === 2 ? '80px' : '104px'}; + width: var(--size); + height: var(--size); + padding: ${({ totalItems }) => (totalItems === 4 ? '10px' : totalItems >= 2 ? '15px' : '20px')}; + border-radius: 50%; + background: var(${UI.COLOR_COWAMM_DARK_GREEN}); + color: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); + border: ${({ totalItems }) => + totalItems > 2 ? `2px solid var(${UI.COLOR_COWAMM_GREEN})` : `4px solid var(${UI.COLOR_COWAMM_GREEN})`}; + display: flex; + align-items: center; + justify-content: center; + position: relative; + + > svg { + width: 100%; + height: 100%; + } + + ${({ totalItems, index }) => { + const styleMap: Record<number, Record<number, string>> = { + 2: { + 0: 'margin-right: -42px;', + }, + 3: { + 0: 'margin-bottom: -20px; z-index: 10;', + 1: 'margin-top: -20px;', + 2: 'margin-top: -20px;', + }, + 4: { + 0: 'margin: -5px -10px -5px 0; z-index: 10;', + 1: 'margin-bottom: -5px; z-index: 10;', + 2: 'margin: -5px -10px 0 0;', + 3: 'margin-top: -5px;', + }, + } + + return styleMap[totalItems]?.[index] || '' + }} +` + +export const CoWAMMEmblemItem = styled.div` + --size: 104px; + width: var(--size); + height: var(--size); + border-radius: var(--size); + padding: 30px 30px 23px; + background: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); + color: var(${UI.COLOR_COWAMM_DARK_GREEN}); + border: 4px solid var(${UI.COLOR_COWAMM_GREEN}); + display: flex; + align-items: center; + justify-content: center; +` + +export const EmblemArrow = styled.div` + --size: 32px; + width: var(--size); + height: var(--size); + min-width: var(--size); + border-radius: var(--size); + background: var(${UI.COLOR_COWAMM_DARK_GREEN}); + border: 3px solid var(${UI.COLOR_COWAMM_GREEN}); + margin: 0 -24px; + padding: 6px; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + color: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); + + > svg > path { + fill: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); + } +` diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx new file mode 100644 index 0000000000..1002ee845c --- /dev/null +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx @@ -0,0 +1,143 @@ +import { useState, useCallback, useMemo } from 'react' + +import styled from 'styled-components/macro' +import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' +import { ProductLogo, ProductVariant, UI } from '@cowprotocol/ui' +import { ClosableBanner } from '@cowprotocol/ui' + +import SVG from 'react-inlinesvg' +import { Textfit } from 'react-textfit' + +import { upToSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' + +import { cowAnalytics } from 'modules/analytics' + +import { dummyData, StateKey } from './dummyData' +import * as styledEl from './styled' + +export const Wrapper = styled.div` + z-index: 3; + width: 100%; + padding: 20px; + position: relative; +` + +const WrapperInner = styled.div` + position: relative; + width: 100%; + height: auto; + border-radius: 24px; + background-color: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); + color: var(${UI.COLOR_COWAMM_DARK_GREEN}); + padding: 14px; + margin: 0 auto; + display: flex; + flex-flow: column wrap; + align-items: center; + justify-content: center; + gap: 14px; + overflow: hidden; +` + +const ANALYTICS_URL = 'https://cow.fi/pools?utm_source=swap.cow.fi&utm_medium=web&utm_content=cow_amm_banner' +const BANNER_ID = 'cow_amm_banner_tokenselector_2024_va' + +export function CoWAmmTokenSelectorBanner() { + const [selectedState, setSelectedState] = useState<StateKey>('noLp') + + const handleCTAClick = useCallback(() => { + cowAnalytics.sendEvent({ + category: 'CoW Swap', + action: 'CoW AMM Banner [Token selector] CTA Clicked', + }) + + window.open(ANALYTICS_URL, '_blank') + }, []) + + const handleClose = useCallback(() => { + cowAnalytics.sendEvent({ + category: 'CoW Swap', + action: 'CoW AMM Banner [Token selector] Closed', + }) + }, []) + + const aprMessage = useMemo(() => { + const { apr } = dummyData[selectedState] + return `+${apr.toFixed(1)}%` + }, [selectedState]) + + const comparisonMessage = useMemo(() => { + const { comparison } = dummyData[selectedState] + if (selectedState === 'noLp') { + return `yield over the average UNI-V2 pool` + } + if (selectedState === 'twoLps' || selectedState === 'threeLps') { + return `Get higher average APR than ${comparison}` + } + return `Get higher APR than ${comparison}` + }, [selectedState, dummyData]) + + const handleBannerClose = (close: () => void) => () => { + handleClose() + close() + } + + return ClosableBanner(BANNER_ID, (close) => ( + <Wrapper> + <WrapperInner> + <styledEl.CloseButton + top={14} + size={24} + onClick={handleBannerClose(close)} + color={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} + /> + + <styledEl.Title color={`var(${UI.COLOR_COWAMM_DARK_GREEN})`}> + <ProductLogo + height={20} + overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} + variant={ProductVariant.CowAmm} + logoIconOnly + /> + <span>CoW AMM</span> + </styledEl.Title> + <styledEl.Card + bgColor={'transparent'} + borderColor={`var(${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30})`} + borderWidth={2} + padding={'14px'} + gap={'14px'} + height={'78px'} + > + <styledEl.StarIcon size={26} top={-16} right={80} color={`var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}> + <SVG src={ICON_STAR} /> + </styledEl.StarIcon> + <h3> + <Textfit mode="single" forceSingleModeWidth={false} min={45} max={48} key={aprMessage}> + {aprMessage} + </Textfit> + </h3> + <p> + <Textfit mode="multi" forceSingleModeWidth={false} min={12} max={21} key={comparisonMessage}> + {comparisonMessage} + </Textfit> + </p> + <styledEl.StarIcon size={16} bottom={3} right={20} color={`var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}> + <SVG src={ICON_STAR} /> + </styledEl.StarIcon> + </styledEl.Card> + + <styledEl.CTAButton + onClick={handleCTAClick} + size={38} + fontSize={18} + bgColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} + bgHoverColor={`var(${UI.COLOR_COWAMM_GREEN})`} + color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`} + > + Boooooost APR gas-free! + </styledEl.CTAButton> + </WrapperInner> + </Wrapper> + )) +} diff --git a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx index 1dba5f772b..36b6ba37b2 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/SelectTokenModal/index.tsx @@ -88,7 +88,9 @@ export function SelectTokenModal(props: SelectTokenModalProps) { hideTooltip={hideFavoriteTokensTooltip} /> </styledEl.Row> + <styledEl.Separator /> + {inputValue.trim() ? ( <TokenSearchResults searchInput={inputValue.trim()} {...selectTokenContext} /> ) : ( 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 16e91c70a8..186d8545ed 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx @@ -5,6 +5,8 @@ import { TokenWithLogo } from '@cowprotocol/common-const' import { useVirtualizer } from '@tanstack/react-virtual' import ms from 'ms.macro' +import { CoWAmmTokenSelectorBanner } from 'common/pure/CoWAMMBanner/tokenSelectorBanner' + import * as styledEl from './styled' import { SelectTokenContext } from '../../types' @@ -65,6 +67,8 @@ export function TokensVirtualList(props: TokensVirtualListProps) { return ( <CommonListContainer id="tokens-list" ref={parentRef} onScroll={onScroll}> + <CoWAmmTokenSelectorBanner /> + <styledEl.TokensInner ref={wrapperRef} style={{ height: virtualizer.getTotalSize() }}> <styledEl.TokensScroller style={{ transform: `translateY(${items[0]?.start ?? 0}px)` }}> {items.map((virtualRow) => { diff --git a/libs/ui/src/enum.ts b/libs/ui/src/enum.ts index 8a2986e83b..608ce17b35 100644 --- a/libs/ui/src/enum.ts +++ b/libs/ui/src/enum.ts @@ -60,6 +60,15 @@ export enum UI { COLOR_DANGER_BG = '--cow-color-danger-bg', COLOR_DANGER_TEXT = '--cow-color-danger-text', + // CoW AMM Colors + COLOR_COWAMM_DARK_GREEN = '--cow-color-cowamm-dark-green', + COLOR_COWAMM_DARK_GREEN_OPACITY_30 = '--cow-color-cowamm-dark-green-opacity-30', + COLOR_COWAMM_GREEN = '--cow-color-cowamm-green', + COLOR_COWAMM_LIGHT_GREEN = '--cow-color-cowamm-light-green', + COLOR_COWAMM_LIGHTER_GREEN = '--cow-color-cowamm-lighter-green', + COLOR_COWAMM_BLUE = '--cow-color-cowamm-blue', + COLOR_COWAMM_LIGHT_BLUE = '--cow-color-cowamm-light-blue', + // ================================================================================ // Badge diff --git a/libs/ui/src/theme/ThemeColorVars.tsx b/libs/ui/src/theme/ThemeColorVars.tsx index 6f2860c49a..55efc51732 100644 --- a/libs/ui/src/theme/ThemeColorVars.tsx +++ b/libs/ui/src/theme/ThemeColorVars.tsx @@ -97,8 +97,17 @@ export const ThemeColorVars = css` ${UI.COLOR_GREEN}: ${({ theme }) => theme.success}; ${UI.COLOR_RED}: ${({ theme }) => theme.danger}; + // CoW AMM Colors + ${UI.COLOR_COWAMM_DARK_GREEN}: #194d05; + ${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30}: ${() => transparentize('#194d05', 0.7)}; + ${UI.COLOR_COWAMM_GREEN}: #2b6f0b; + ${UI.COLOR_COWAMM_LIGHT_GREEN}: #bcec79; + ${UI.COLOR_COWAMM_LIGHTER_GREEN}: #dcf8a7; + ${UI.COLOR_COWAMM_BLUE}: #3fc4ff; + ${UI.COLOR_COWAMM_LIGHT_BLUE}: #ccf8ff; + // Base - ${UI.COLOR_CONTAINER_BG_02}: ${UI.COLOR_PAPER}; + ${UI.COLOR_CONTAINER_BG_02}: var(${UI.COLOR_PAPER}); ${UI.MODAL_BACKDROP}: var(${UI.COLOR_TEXT}); ${UI.BORDER_RADIUS_NORMAL}: 24px; ${UI.PADDING_NORMAL}: 24px; @@ -115,7 +124,7 @@ export const ThemeColorVars = css` ${UI.COLOR_TEXT_OPACITY_25}: ${({ theme }) => transparentize(theme.text, 0.75)}; ${UI.COLOR_TEXT_OPACITY_10}: ${({ theme }) => transparentize(theme.text, 0.9)}; ${UI.COLOR_TEXT2}: ${({ theme }) => transparentize(theme.text, 0.3)}; - ${UI.COLOR_LINK}: ${`var(${UI.COLOR_PRIMARY})`}; + ${UI.COLOR_LINK}: var(${UI.COLOR_PRIMARY}); ${UI.COLOR_LINK_OPACITY_10}: ${({ theme }) => transparentize(theme.info, 0.9)}; // Font Weights & Sizes From 7f10daaf29059b7ef688d0622481c77c9f0f1291 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:42:27 +0100 Subject: [PATCH 04/15] feat: add shared atom state --- .../src/common/pure/CoWAMMBanner/cowAmmBannerState.ts | 5 +++++ .../src/common/pure/CoWAMMBanner/index.tsx | 7 +++++-- .../src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx | 7 +++++-- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 apps/cowswap-frontend/src/common/pure/CoWAMMBanner/cowAmmBannerState.ts diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/cowAmmBannerState.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/cowAmmBannerState.ts new file mode 100644 index 0000000000..bc0c9b3054 --- /dev/null +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/cowAmmBannerState.ts @@ -0,0 +1,5 @@ +import { atom } from 'jotai' + +import { StateKey } from './dummyData' + +export const cowAmmBannerStateAtom = atom<StateKey>('noLp') diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index d0164987f6..1fce3ee502 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -1,4 +1,7 @@ -import { useState, useCallback, useMemo } from 'react' +import { useAtom } from 'jotai' +import { cowAmmBannerStateAtom } from './cowAmmBannerState' + +import { useCallback, useMemo } from 'react' import ICON_ARROW from '@cowprotocol/assets/cow-swap/arrow.svg' import ICON_CURVE from '@cowprotocol/assets/cow-swap/icon-curve.svg' @@ -41,7 +44,7 @@ const lpTokenIcons: Record<LpToken, string> = { } export function CoWAmmBanner() { - const [selectedState, setSelectedState] = useState<StateKey>('noLp') + const [selectedState, setSelectedState] = useAtom(cowAmmBannerStateAtom) const isMobile = useMediaQuery(upToSmall) const handleCTAClick = useCallback(() => { diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx index 1002ee845c..243e1eaa4e 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx @@ -1,4 +1,7 @@ -import { useState, useCallback, useMemo } from 'react' +import { useAtomValue } from 'jotai' +import { cowAmmBannerStateAtom } from './cowAmmBannerState' + +import { useCallback, useMemo } from 'react' import styled from 'styled-components/macro' import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' @@ -43,7 +46,7 @@ const ANALYTICS_URL = 'https://cow.fi/pools?utm_source=swap.cow.fi&utm_medium=we const BANNER_ID = 'cow_amm_banner_tokenselector_2024_va' export function CoWAmmTokenSelectorBanner() { - const [selectedState, setSelectedState] = useState<StateKey>('noLp') + const selectedState = useAtomValue(cowAmmBannerStateAtom) const handleCTAClick = useCallback(() => { cowAnalytics.sendEvent({ From f765bdd415c1789e35d4b01628103338b5854ea4 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:05:09 +0100 Subject: [PATCH 05/15] feat: add animation and refactoring --- .../pure/CoWAMMBanner/arrowBackground.tsx | 88 +++++++++++++++ .../src/common/pure/CoWAMMBanner/index.tsx | 30 ++++- .../src/common/pure/CoWAMMBanner/styled.ts | 104 +++++++++++------- .../pure/CoWAMMBanner/tokenSelectorBanner.tsx | 25 ++++- 4 files changed, 200 insertions(+), 47 deletions(-) create mode 100644 apps/cowswap-frontend/src/common/pure/CoWAMMBanner/arrowBackground.tsx diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/arrowBackground.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/arrowBackground.tsx new file mode 100644 index 0000000000..184afe0e7d --- /dev/null +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/arrowBackground.tsx @@ -0,0 +1,88 @@ +import React, { forwardRef, useMemo } from 'react' +import { memo } from 'react' + +import { UI } from '@cowprotocol/ui' + +import styled from 'styled-components/macro' + +const ArrowBackgroundWrapper = styled.div` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + visibility: hidden; + opacity: 0; + transition: opacity 0.3s ease-in-out; +` + +const MIN_FONT_SIZE = 18 +const MAX_FONT_SIZE = 42 + +const Arrow = styled.div<{ delay: number; color: string; fontSize: number }>` + position: absolute; + font-size: ${({ fontSize }) => fontSize}px; + color: ${({ color }) => color}; + animation: float 2s infinite linear; + animation-delay: ${({ delay }) => delay}s; + + @keyframes float { + 0% { + transform: translateY(100%); + opacity: 0; + } + 10% { + opacity: 0.3; + } + 50% { + opacity: 0.3; + } + 90% { + opacity: 0.2; + } + 100% { + transform: translateY(-100%); + opacity: 0; + } + } +` + +export interface ArrowBackgroundProps { + count?: number + color?: string +} + +export const ArrowBackground = memo( + forwardRef<HTMLDivElement, ArrowBackgroundProps>( + ({ count = 36, color = `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` }, ref) => { + const arrows = useMemo(() => { + return Array.from({ length: count }, (_, index) => ({ + delay: (index / count) * 4, + left: `${Math.random() * 100}%`, + top: `${Math.random() * 100}%`, + fontSize: Math.floor(Math.random() * (MAX_FONT_SIZE - MIN_FONT_SIZE + 1)) + MIN_FONT_SIZE, + })) + }, [count]) + + return ( + <ArrowBackgroundWrapper ref={ref}> + {arrows.map((arrow, index) => ( + <Arrow + key={index} + delay={arrow.delay} + color={color} + fontSize={arrow.fontSize} + style={{ + left: arrow.left, + top: arrow.top, + }} + > + ↑ + </Arrow> + ))} + </ArrowBackgroundWrapper> + ) + }, + ), +) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index 1fce3ee502..f2e7853715 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -1,7 +1,7 @@ import { useAtom } from 'jotai' import { cowAmmBannerStateAtom } from './cowAmmBannerState' -import { useCallback, useMemo } from 'react' +import { useCallback, useMemo, useRef } from 'react' import ICON_ARROW from '@cowprotocol/assets/cow-swap/arrow.svg' import ICON_CURVE from '@cowprotocol/assets/cow-swap/icon-curve.svg' @@ -12,6 +12,8 @@ import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' import { ProductLogo, ProductVariant, UI } from '@cowprotocol/ui' import { ClosableBanner } from '@cowprotocol/ui' +import { ArrowBackground } from './arrowBackground' + import SVG from 'react-inlinesvg' import { Textfit } from 'react-textfit' @@ -46,6 +48,7 @@ const lpTokenIcons: Record<LpToken, string> = { export function CoWAmmBanner() { const [selectedState, setSelectedState] = useAtom(cowAmmBannerStateAtom) const isMobile = useMediaQuery(upToSmall) + const arrowBackgroundRef = useRef<HTMLDivElement>(null) const handleCTAClick = useCallback(() => { cowAnalytics.sendEvent({ @@ -63,6 +66,20 @@ export function CoWAmmBanner() { }) }, []) + const handleCTAMouseEnter = useCallback(() => { + if (arrowBackgroundRef.current) { + arrowBackgroundRef.current.style.visibility = 'visible' + arrowBackgroundRef.current.style.opacity = '1' + } + }, []) + + const handleCTAMouseLeave = useCallback(() => { + if (arrowBackgroundRef.current) { + arrowBackgroundRef.current.style.visibility = 'hidden' + arrowBackgroundRef.current.style.opacity = '0' + } + }, []) + const aprMessage = useMemo(() => { const { apr } = dummyData[selectedState] return `+${apr.toFixed(1)}%` @@ -167,7 +184,16 @@ export function CoWAmmBanner() { </styledEl.Card> )} - <styledEl.CTAButton onClick={handleCTAClick}>Booooost APR gas-free!</styledEl.CTAButton> + <styledEl.CTAButton + onClick={handleCTAClick} + onMouseEnter={handleCTAMouseEnter} + onMouseLeave={handleCTAMouseLeave} + > + Booooost APR gas-free! + </styledEl.CTAButton> + + <ArrowBackground ref={arrowBackgroundRef} /> + <styledEl.SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</styledEl.SecondaryLink> </styledEl.BannerWrapper> )) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts index 44c54083fd..be0d1f8be3 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts @@ -1,7 +1,21 @@ import { UI, Media } from '@cowprotocol/ui' import { X } from 'react-feather' -import styled from 'styled-components/macro' +import styled, { keyframes } from 'styled-components/macro' + +const arrowUpAnimation = keyframes` + 0% { + transform: translateY(100%); + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + transform: translateY(-100%); + opacity: 0; + } +` export const BannerWrapper = styled.div` position: fixed; @@ -20,6 +34,8 @@ export const BannerWrapper = styled.div` justify-content: center; gap: 20px; overflow: hidden; + overflow: hidden; + transition: transform 0.2s ease; ${Media.upToSmall()} { width: 100%; @@ -150,50 +166,30 @@ export const CTAButton = styled.button<{ align-items: center; justify-content: center; gap: 8px; - transition: background 0.2s ease-in-out; + position: relative; + overflow: hidden; + z-index: 1; - @keyframes shake { - 0% { - transform: translate(1px, 1px) rotate(0deg); - } - 10% { - transform: translate(-1px, -2px) rotate(-1deg); - } - 20% { - transform: translate(-3px, 0px) rotate(1deg); - } - 30% { - transform: translate(3px, 2px) rotate(0deg); - } - 40% { - transform: translate(1px, -1px) rotate(1deg); - } - 50% { - transform: translate(-1px, 2px) rotate(-1deg); - } - 60% { - transform: translate(-3px, 1px) rotate(0deg); - } - 70% { - transform: translate(3px, 1px) rotate(-1deg); - } - 80% { - transform: translate(-1px, -1px) rotate(1deg); - } - 90% { - transform: translate(1px, 2px) rotate(0deg); - } - 100% { - transform: translate(1px, -2px) rotate(-1deg); - } + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: ${({ bgHoverColor }) => bgHoverColor || `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}; + z-index: -1; + transform: scaleX(0); + transform-origin: left; + transition: transform 2s ease-out; } - &:hover { - background: ${({ bgHoverColor }) => bgHoverColor || `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}; - animation: shake 0.5s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; - transform: translate3d(0, 0, 0); - backface-visibility: hidden; - perspective: 1000px; + &:hover::before { + transform: scaleX(1); + } + + > * { + z-index: 2; } ` @@ -352,3 +348,27 @@ export const EmblemArrow = styled.div` fill: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); } ` + +export const ArrowBackground = styled.div` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + height: 100%; + width: 100%; + visibility: hidden; + opacity: 0; + transition: opacity 0.2s ease-in-out; +` + +export const Arrow = styled.span<{ delay: number }>` + position: absolute; + font-size: 28px; + color: rgba(255, 255, 255, 0.3); + animation: ${arrowUpAnimation} 1s linear infinite; + animation-delay: ${({ delay }) => delay}s; + left: ${() => Math.random() * 100}%; + font-weight: 500; +` diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx index 243e1eaa4e..3cf6a04c27 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx @@ -1,7 +1,7 @@ import { useAtomValue } from 'jotai' import { cowAmmBannerStateAtom } from './cowAmmBannerState' -import { useCallback, useMemo } from 'react' +import { useCallback, useMemo, useRef } from 'react' import styled from 'styled-components/macro' import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' @@ -11,13 +11,13 @@ import { ClosableBanner } from '@cowprotocol/ui' import SVG from 'react-inlinesvg' import { Textfit } from 'react-textfit' -import { upToSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' - import { cowAnalytics } from 'modules/analytics' import { dummyData, StateKey } from './dummyData' import * as styledEl from './styled' +import { ArrowBackground } from './arrowBackground' + export const Wrapper = styled.div` z-index: 3; width: 100%; @@ -47,6 +47,7 @@ const BANNER_ID = 'cow_amm_banner_tokenselector_2024_va' export function CoWAmmTokenSelectorBanner() { const selectedState = useAtomValue(cowAmmBannerStateAtom) + const arrowBackgroundRef = useRef<HTMLDivElement>(null) const handleCTAClick = useCallback(() => { cowAnalytics.sendEvent({ @@ -64,6 +65,20 @@ export function CoWAmmTokenSelectorBanner() { }) }, []) + const handleCTAMouseEnter = useCallback(() => { + if (arrowBackgroundRef.current) { + arrowBackgroundRef.current.style.visibility = 'visible' + arrowBackgroundRef.current.style.opacity = '1' + } + }, []) + + const handleCTAMouseLeave = useCallback(() => { + if (arrowBackgroundRef.current) { + arrowBackgroundRef.current.style.visibility = 'hidden' + arrowBackgroundRef.current.style.opacity = '0' + } + }, []) + const aprMessage = useMemo(() => { const { apr } = dummyData[selectedState] return `+${apr.toFixed(1)}%` @@ -132,6 +147,8 @@ export function CoWAmmTokenSelectorBanner() { <styledEl.CTAButton onClick={handleCTAClick} + onMouseEnter={handleCTAMouseEnter} + onMouseLeave={handleCTAMouseLeave} size={38} fontSize={18} bgColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} @@ -140,6 +157,8 @@ export function CoWAmmTokenSelectorBanner() { > Boooooost APR gas-free! </styledEl.CTAButton> + + <ArrowBackground ref={arrowBackgroundRef} count={14} color={`var(${UI.COLOR_COWAMM_GREEN})`} /> </WrapperInner> </Wrapper> )) From bdbcb238d348f83b25d1bce9c57a84abc7ec0ad4 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:49:27 +0100 Subject: [PATCH 06/15] feat: consolidate banners into single component --- .../pure/CoWAMMBanner/CoWAmmBannerContent.tsx | 241 ++++++++++++++++++ .../pure/CoWAMMBanner/arrowBackground.tsx | 2 +- .../src/common/pure/CoWAMMBanner/index.tsx | 184 ++----------- .../src/common/pure/CoWAMMBanner/styled.ts | 24 ++ .../pure/CoWAMMBanner/tokenSelectorBanner.tsx | 165 ------------ .../pure/TokensVirtualList/index.tsx | 4 +- 6 files changed, 294 insertions(+), 326 deletions(-) create mode 100644 apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx delete mode 100644 apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx new file mode 100644 index 0000000000..c2ce0fc717 --- /dev/null +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx @@ -0,0 +1,241 @@ +import { useCallback, useMemo, useRef } from 'react' + +import ICON_ARROW from '@cowprotocol/assets/cow-swap/arrow.svg' +import ICON_CURVE from '@cowprotocol/assets/cow-swap/icon-curve.svg' +import ICON_PANCAKESWAP from '@cowprotocol/assets/cow-swap/icon-pancakeswap.svg' +import ICON_SUSHISWAP from '@cowprotocol/assets/cow-swap/icon-sushi.svg' +import ICON_UNISWAP from '@cowprotocol/assets/cow-swap/icon-uni.svg' +import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' +import { ProductLogo, ProductVariant, UI } from '@cowprotocol/ui' + +import SVG from 'react-inlinesvg' +import { Textfit } from 'react-textfit' + +import { upToSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' + +import { ArrowBackground } from './arrowBackground' +import { LpToken, StateKey } from './dummyData' +import * as styledEl from './styled' + +import { DEMO_DROPDOWN_OPTIONS } from './index' + +const lpTokenIcons: Record<LpToken, string> = { + [LpToken.UniswapV2]: ICON_UNISWAP, + [LpToken.Sushiswap]: ICON_SUSHISWAP, + [LpToken.PancakeSwap]: ICON_PANCAKESWAP, + [LpToken.Curve]: ICON_CURVE, +} + +interface CoWAmmBannerContentProps { + location: 'global' | 'tokenSelector' + selectedState: StateKey + setSelectedState: (state: StateKey) => void + dummyData: Record<StateKey, { apr: number; comparison: string }> + lpTokenConfig: Record<StateKey, LpToken[]> + handleCTAClick: () => void + handleBannerClose: () => void +} + +export function CoWAmmBannerContent({ + location, + selectedState, + setSelectedState, + dummyData, + lpTokenConfig, + handleCTAClick, + handleBannerClose, +}: CoWAmmBannerContentProps) { + const isMobile = useMediaQuery(upToSmall) + const arrowBackgroundRef = useRef<HTMLDivElement>(null) + + const handleCTAMouseEnter = useCallback(() => { + if (arrowBackgroundRef.current) { + arrowBackgroundRef.current.style.visibility = 'visible' + arrowBackgroundRef.current.style.opacity = '1' + } + }, []) + + const handleCTAMouseLeave = useCallback(() => { + if (arrowBackgroundRef.current) { + arrowBackgroundRef.current.style.visibility = 'hidden' + arrowBackgroundRef.current.style.opacity = '0' + } + }, []) + + const aprMessage = useMemo(() => { + const { apr } = dummyData[selectedState] + return `+${apr.toFixed(1)}%` + }, [selectedState, dummyData]) + + const comparisonMessage = useMemo(() => { + const { comparison } = dummyData[selectedState] + if (selectedState === 'noLp') { + return `yield over the average UNI-V2 pool` + } + if (selectedState === 'twoLps' || selectedState === 'threeLps') { + return `Get higher average APR than ${comparison}` + } + return `Get higher APR than ${comparison}` + }, [selectedState, dummyData]) + + const lpEmblems = useMemo(() => { + const tokens = lpTokenConfig[selectedState] + const totalItems = tokens.length + + if (totalItems === 0) { + return null + } + + return ( + <styledEl.LpEmblems> + <styledEl.LpEmblemItemsWrapper totalItems={totalItems}> + {tokens.map((token, index) => ( + <styledEl.LpEmblemItem key={token} totalItems={totalItems} index={index}> + <SVG src={lpTokenIcons[token]} /> + </styledEl.LpEmblemItem> + ))} + </styledEl.LpEmblemItemsWrapper> + <styledEl.EmblemArrow> + <SVG src={ICON_ARROW} /> + </styledEl.EmblemArrow> + <styledEl.CoWAMMEmblemItem> + <ProductLogo + height={'100%'} + overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} + variant={ProductVariant.CowAmm} + logoIconOnly + /> + </styledEl.CoWAMMEmblemItem> + </styledEl.LpEmblems> + ) + }, [selectedState, lpTokenConfig]) + + const isTokenSelector = (loc: typeof location): loc is 'tokenSelector' => loc === 'tokenSelector' + + const renderBannerContent = () => { + if (isTokenSelector(location)) { + return ( + <styledEl.TokenSelectorWrapper> + <styledEl.TokenSelectorWrapperInner> + <styledEl.Title> + <ProductLogo + height={20} + overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} + variant={ProductVariant.CowAmm} + logoIconOnly + /> + <span>CoW AMM</span> + </styledEl.Title> + <styledEl.Card + bgColor={'transparent'} + borderColor={`var(${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30})`} + borderWidth={2} + padding={'14px'} + gap={'14px'} + height={'78px'} + > + <styledEl.StarIcon size={26} top={-16} right={80} color={`var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}> + <SVG src={ICON_STAR} /> + </styledEl.StarIcon> + <h3> + <Textfit mode="single" forceSingleModeWidth={false} min={45} max={48} key={aprMessage}> + {aprMessage} + </Textfit> + </h3> + <p> + <Textfit mode="multi" forceSingleModeWidth={false} min={12} max={21} key={comparisonMessage}> + {comparisonMessage} + </Textfit> + </p> + <styledEl.StarIcon size={16} bottom={3} right={20} color={`var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}> + <SVG src={ICON_STAR} /> + </styledEl.StarIcon> + </styledEl.Card> + </styledEl.TokenSelectorWrapperInner> + </styledEl.TokenSelectorWrapper> + ) + } + + return ( + <> + <styledEl.Title> + <ProductLogo + height={20} + overrideColor={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`} + variant={ProductVariant.CowAmm} + logoIconOnly + /> + <span>CoW AMM</span> + </styledEl.Title> + <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_BLUE})`}> + <styledEl.StarIcon size={36} top={-17} right={80}> + <SVG src={ICON_STAR} /> + </styledEl.StarIcon> + <h3> + <Textfit mode="single" forceSingleModeWidth={false} min={80} max={80} key={aprMessage}> + {aprMessage} + </Textfit> + </h3> + <p> + <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={28} key={comparisonMessage}> + {comparisonMessage} + </Textfit> + </p> + <styledEl.StarIcon size={26} bottom={-10} right={20}> + <SVG src={ICON_STAR} /> + </styledEl.StarIcon> + </styledEl.Card> + + {!isMobile && ( + <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_GREEN})`} color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}> + <p> + <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={30}> + One-click convert, <b>boost yield</b> + </Textfit> + </p> + {lpEmblems} + </styledEl.Card> + )} + </> + ) + } + + return ( + <> + {isTokenSelector(location) ? ( + renderBannerContent() + ) : ( + <styledEl.BannerWrapper> + <styledEl.CloseButton size={24} onClick={handleBannerClose} /> + + <styledEl.DEMO_DROPDOWN value={selectedState} onChange={(e) => setSelectedState(e.target.value as StateKey)}> + {DEMO_DROPDOWN_OPTIONS.map((option) => ( + <option key={option.value} value={option.value}> + {option.label} + </option> + ))} + </styledEl.DEMO_DROPDOWN> + + {renderBannerContent()} + + <styledEl.CTAButton + onClick={handleCTAClick} + onMouseEnter={handleCTAMouseEnter} + onMouseLeave={handleCTAMouseLeave} + size={isTokenSelector(location) ? 38 : undefined} + fontSize={isTokenSelector(location) ? 18 : undefined} + bgColor={isTokenSelector(location) ? `var(${UI.COLOR_COWAMM_DARK_GREEN})` : undefined} + bgHoverColor={isTokenSelector(location) ? `var(${UI.COLOR_COWAMM_GREEN})` : undefined} + color={isTokenSelector(location) ? `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` : undefined} + > + Booooost APR gas-free! + </styledEl.CTAButton> + + <ArrowBackground ref={arrowBackgroundRef} /> + + <styledEl.SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</styledEl.SecondaryLink> + </styledEl.BannerWrapper> + )} + </> + ) +} diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/arrowBackground.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/arrowBackground.tsx index 184afe0e7d..a4677c4674 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/arrowBackground.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/arrowBackground.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useMemo } from 'react' +import { forwardRef, useMemo } from 'react' import { memo } from 'react' import { UI } from '@cowprotocol/ui' diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index f2e7853715..40f0ad3edf 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -1,33 +1,17 @@ import { useAtom } from 'jotai' -import { cowAmmBannerStateAtom } from './cowAmmBannerState' - -import { useCallback, useMemo, useRef } from 'react' +import { useCallback } from 'react' -import ICON_ARROW from '@cowprotocol/assets/cow-swap/arrow.svg' -import ICON_CURVE from '@cowprotocol/assets/cow-swap/icon-curve.svg' -import ICON_PANCAKESWAP from '@cowprotocol/assets/cow-swap/icon-pancakeswap.svg' -import ICON_SUSHISWAP from '@cowprotocol/assets/cow-swap/icon-sushi.svg' -import ICON_UNISWAP from '@cowprotocol/assets/cow-swap/icon-uni.svg' -import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' -import { ProductLogo, ProductVariant, UI } from '@cowprotocol/ui' import { ClosableBanner } from '@cowprotocol/ui' -import { ArrowBackground } from './arrowBackground' - -import SVG from 'react-inlinesvg' -import { Textfit } from 'react-textfit' - -import { upToSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' - import { cowAnalytics } from 'modules/analytics' -import { LpToken, dummyData, lpTokenConfig, StateKey } from './dummyData' -import * as styledEl from './styled' +import { CoWAmmBannerContent } from './CoWAmmBannerContent' +import { cowAmmBannerStateAtom } from './cowAmmBannerState' +import { dummyData, lpTokenConfig } from './dummyData' const ANALYTICS_URL = 'https://cow.fi/pools?utm_source=swap.cow.fi&utm_medium=web&utm_content=cow_amm_banner' -const BANNER_ID = 'cow_amm_banner_2024_va' -const DEMO_DROPDOWN_OPTIONS = [ +export const DEMO_DROPDOWN_OPTIONS = [ { value: 'noLp', label: 'No LP tokens' }, { value: 'uniV2', label: 'UNI-V2 LP' }, { value: 'sushi', label: 'SushiSwap LP' }, @@ -38,163 +22,47 @@ const DEMO_DROPDOWN_OPTIONS = [ { value: 'fourLps', label: '4 LP tokens' }, ] -const lpTokenIcons: Record<LpToken, string> = { - [LpToken.UniswapV2]: ICON_UNISWAP, - [LpToken.Sushiswap]: ICON_SUSHISWAP, - [LpToken.PancakeSwap]: ICON_PANCAKESWAP, - [LpToken.Curve]: ICON_CURVE, +type BannerLocation = 'global' | 'tokenSelector' + +interface BannerProps { + location: BannerLocation } -export function CoWAmmBanner() { +export function CoWAmmBanner({ location }: BannerProps) { const [selectedState, setSelectedState] = useAtom(cowAmmBannerStateAtom) - const isMobile = useMediaQuery(upToSmall) - const arrowBackgroundRef = useRef<HTMLDivElement>(null) const handleCTAClick = useCallback(() => { cowAnalytics.sendEvent({ category: 'CoW Swap', - action: 'CoW AMM Banner [Global] CTA Clicked', + action: `CoW AMM Banner [${location}] CTA Clicked`, }) window.open(ANALYTICS_URL, '_blank') - }, []) + }, [location]) const handleClose = useCallback(() => { cowAnalytics.sendEvent({ category: 'CoW Swap', - action: 'CoW AMM Banner [Global] Closed', + action: `CoW AMM Banner [${location}] Closed`, }) - }, []) - - const handleCTAMouseEnter = useCallback(() => { - if (arrowBackgroundRef.current) { - arrowBackgroundRef.current.style.visibility = 'visible' - arrowBackgroundRef.current.style.opacity = '1' - } - }, []) - - const handleCTAMouseLeave = useCallback(() => { - if (arrowBackgroundRef.current) { - arrowBackgroundRef.current.style.visibility = 'hidden' - arrowBackgroundRef.current.style.opacity = '0' - } - }, []) - - const aprMessage = useMemo(() => { - const { apr } = dummyData[selectedState] - return `+${apr.toFixed(1)}%` - }, [selectedState]) - - const comparisonMessage = useMemo(() => { - const { comparison } = dummyData[selectedState] - if (selectedState === 'noLp') { - return `yield over the average UNI-V2 pool` - } - if (selectedState === 'twoLps' || selectedState === 'threeLps') { - return `Get higher average APR than ${comparison}` - } - return `Get higher APR than ${comparison}` - }, [selectedState, dummyData]) - - const lpEmblems = useMemo(() => { - const tokens = lpTokenConfig[selectedState] - const totalItems = tokens.length - - if (totalItems === 0) { - return null - } - - return ( - <styledEl.LpEmblems> - <styledEl.LpEmblemItemsWrapper totalItems={totalItems}> - {tokens.map((token, index) => ( - <styledEl.LpEmblemItem key={token} totalItems={totalItems} index={index}> - <SVG src={lpTokenIcons[token]} /> - </styledEl.LpEmblemItem> - ))} - </styledEl.LpEmblemItemsWrapper> - <styledEl.EmblemArrow> - <SVG src={ICON_ARROW} /> - </styledEl.EmblemArrow> - <styledEl.CoWAMMEmblemItem> - <ProductLogo - height={'100%'} - overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} - variant={ProductVariant.CowAmm} - logoIconOnly - /> - </styledEl.CoWAMMEmblemItem> - </styledEl.LpEmblems> - ) - }, [selectedState]) + }, [location]) const handleBannerClose = (close: () => void) => () => { handleClose() close() } - return ClosableBanner(BANNER_ID, (close) => ( - <styledEl.BannerWrapper> - <styledEl.CloseButton size={24} onClick={handleBannerClose(close)} /> - - <styledEl.DEMO_DROPDOWN value={selectedState} onChange={(e) => setSelectedState(e.target.value as StateKey)}> - {DEMO_DROPDOWN_OPTIONS.map((option) => ( - <option key={option.value} value={option.value}> - {option.label} - </option> - ))} - </styledEl.DEMO_DROPDOWN> - - <styledEl.Title> - <ProductLogo - height={20} - overrideColor={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`} - variant={ProductVariant.CowAmm} - logoIconOnly - /> - <span>CoW AMM</span> - </styledEl.Title> - <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_BLUE})`}> - <styledEl.StarIcon size={36} top={-17} right={80}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> - <h3> - <Textfit mode="single" forceSingleModeWidth={false} min={80} max={80} key={aprMessage}> - {aprMessage} - </Textfit> - </h3> - <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={28} key={comparisonMessage}> - {comparisonMessage} - </Textfit> - </p> - <styledEl.StarIcon size={26} bottom={-10} right={20}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> - </styledEl.Card> - - {!isMobile && ( - <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_GREEN})`} color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}> - <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={30}> - One-click convert, <b>boost yield</b> - </Textfit> - </p> - {lpEmblems} - </styledEl.Card> - )} - - <styledEl.CTAButton - onClick={handleCTAClick} - onMouseEnter={handleCTAMouseEnter} - onMouseLeave={handleCTAMouseLeave} - > - Booooost APR gas-free! - </styledEl.CTAButton> - - <ArrowBackground ref={arrowBackgroundRef} /> - - <styledEl.SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</styledEl.SecondaryLink> - </styledEl.BannerWrapper> + const bannerId = `cow_amm_banner_2024_va_${location}` + + return ClosableBanner(bannerId, (close) => ( + <CoWAmmBannerContent + location={location} + selectedState={selectedState} + setSelectedState={setSelectedState} + dummyData={dummyData} + lpTokenConfig={lpTokenConfig} + handleCTAClick={handleCTAClick} + handleBannerClose={handleBannerClose(close)} + /> )) } diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts index be0d1f8be3..2485823659 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts @@ -372,3 +372,27 @@ export const Arrow = styled.span<{ delay: number }>` left: ${() => Math.random() * 100}%; font-weight: 500; ` + +export const TokenSelectorWrapper = styled.div` + z-index: 3; + width: 100%; + padding: 20px; + position: relative; +` + +export const TokenSelectorWrapperInner = styled.div` + position: relative; + width: 100%; + height: auto; + border-radius: 24px; + background-color: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); + color: var(${UI.COLOR_COWAMM_DARK_GREEN}); + padding: 14px; + margin: 0 auto; + display: flex; + flex-flow: column wrap; + align-items: center; + justify-content: center; + gap: 14px; + overflow: hidden; +` diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx deleted file mode 100644 index 3cf6a04c27..0000000000 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/tokenSelectorBanner.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { useAtomValue } from 'jotai' -import { cowAmmBannerStateAtom } from './cowAmmBannerState' - -import { useCallback, useMemo, useRef } from 'react' - -import styled from 'styled-components/macro' -import ICON_STAR from '@cowprotocol/assets/cow-swap/star-shine.svg' -import { ProductLogo, ProductVariant, UI } from '@cowprotocol/ui' -import { ClosableBanner } from '@cowprotocol/ui' - -import SVG from 'react-inlinesvg' -import { Textfit } from 'react-textfit' - -import { cowAnalytics } from 'modules/analytics' - -import { dummyData, StateKey } from './dummyData' -import * as styledEl from './styled' - -import { ArrowBackground } from './arrowBackground' - -export const Wrapper = styled.div` - z-index: 3; - width: 100%; - padding: 20px; - position: relative; -` - -const WrapperInner = styled.div` - position: relative; - width: 100%; - height: auto; - border-radius: 24px; - background-color: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); - color: var(${UI.COLOR_COWAMM_DARK_GREEN}); - padding: 14px; - margin: 0 auto; - display: flex; - flex-flow: column wrap; - align-items: center; - justify-content: center; - gap: 14px; - overflow: hidden; -` - -const ANALYTICS_URL = 'https://cow.fi/pools?utm_source=swap.cow.fi&utm_medium=web&utm_content=cow_amm_banner' -const BANNER_ID = 'cow_amm_banner_tokenselector_2024_va' - -export function CoWAmmTokenSelectorBanner() { - const selectedState = useAtomValue(cowAmmBannerStateAtom) - const arrowBackgroundRef = useRef<HTMLDivElement>(null) - - const handleCTAClick = useCallback(() => { - cowAnalytics.sendEvent({ - category: 'CoW Swap', - action: 'CoW AMM Banner [Token selector] CTA Clicked', - }) - - window.open(ANALYTICS_URL, '_blank') - }, []) - - const handleClose = useCallback(() => { - cowAnalytics.sendEvent({ - category: 'CoW Swap', - action: 'CoW AMM Banner [Token selector] Closed', - }) - }, []) - - const handleCTAMouseEnter = useCallback(() => { - if (arrowBackgroundRef.current) { - arrowBackgroundRef.current.style.visibility = 'visible' - arrowBackgroundRef.current.style.opacity = '1' - } - }, []) - - const handleCTAMouseLeave = useCallback(() => { - if (arrowBackgroundRef.current) { - arrowBackgroundRef.current.style.visibility = 'hidden' - arrowBackgroundRef.current.style.opacity = '0' - } - }, []) - - const aprMessage = useMemo(() => { - const { apr } = dummyData[selectedState] - return `+${apr.toFixed(1)}%` - }, [selectedState]) - - const comparisonMessage = useMemo(() => { - const { comparison } = dummyData[selectedState] - if (selectedState === 'noLp') { - return `yield over the average UNI-V2 pool` - } - if (selectedState === 'twoLps' || selectedState === 'threeLps') { - return `Get higher average APR than ${comparison}` - } - return `Get higher APR than ${comparison}` - }, [selectedState, dummyData]) - - const handleBannerClose = (close: () => void) => () => { - handleClose() - close() - } - - return ClosableBanner(BANNER_ID, (close) => ( - <Wrapper> - <WrapperInner> - <styledEl.CloseButton - top={14} - size={24} - onClick={handleBannerClose(close)} - color={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} - /> - - <styledEl.Title color={`var(${UI.COLOR_COWAMM_DARK_GREEN})`}> - <ProductLogo - height={20} - overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} - variant={ProductVariant.CowAmm} - logoIconOnly - /> - <span>CoW AMM</span> - </styledEl.Title> - <styledEl.Card - bgColor={'transparent'} - borderColor={`var(${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30})`} - borderWidth={2} - padding={'14px'} - gap={'14px'} - height={'78px'} - > - <styledEl.StarIcon size={26} top={-16} right={80} color={`var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> - <h3> - <Textfit mode="single" forceSingleModeWidth={false} min={45} max={48} key={aprMessage}> - {aprMessage} - </Textfit> - </h3> - <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={12} max={21} key={comparisonMessage}> - {comparisonMessage} - </Textfit> - </p> - <styledEl.StarIcon size={16} bottom={3} right={20} color={`var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> - </styledEl.Card> - - <styledEl.CTAButton - onClick={handleCTAClick} - onMouseEnter={handleCTAMouseEnter} - onMouseLeave={handleCTAMouseLeave} - size={38} - fontSize={18} - bgColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} - bgHoverColor={`var(${UI.COLOR_COWAMM_GREEN})`} - color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`} - > - Boooooost APR gas-free! - </styledEl.CTAButton> - - <ArrowBackground ref={arrowBackgroundRef} count={14} color={`var(${UI.COLOR_COWAMM_GREEN})`} /> - </WrapperInner> - </Wrapper> - )) -} 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 186d8545ed..93f4623b30 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx @@ -5,7 +5,7 @@ import { TokenWithLogo } from '@cowprotocol/common-const' import { useVirtualizer } from '@tanstack/react-virtual' import ms from 'ms.macro' -import { CoWAmmTokenSelectorBanner } from 'common/pure/CoWAMMBanner/tokenSelectorBanner' +import { CoWAmmBanner } from 'common/pure/CoWAMMBanner' import * as styledEl from './styled' @@ -67,7 +67,7 @@ export function TokensVirtualList(props: TokensVirtualListProps) { return ( <CommonListContainer id="tokens-list" ref={parentRef} onScroll={onScroll}> - <CoWAmmTokenSelectorBanner /> + <CoWAmmBanner location="tokenSelector" /> <styledEl.TokensInner ref={wrapperRef} style={{ height: virtualizer.getTotalSize() }}> <styledEl.TokensScroller style={{ transform: `translateY(${items[0]?.start ?? 0}px)` }}> From 33918c48be344b04fe5e6bef5596101d2a5cd928 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:46:58 +0100 Subject: [PATCH 07/15] feat: add darkmode styling for cow amm banners --- .../pure/CoWAMMBanner/CoWAmmBannerContent.tsx | 289 +++++++++--------- .../src/common/pure/CoWAMMBanner/index.tsx | 22 +- .../src/common/pure/CoWAMMBanner/styled.ts | 12 +- .../application/containers/App/index.tsx | 6 +- .../pure/TokensVirtualList/index.tsx | 5 +- libs/ui/src/enum.ts | 1 + libs/ui/src/theme/ThemeColorVars.tsx | 1 + 7 files changed, 182 insertions(+), 154 deletions(-) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx index c2ce0fc717..75b615b287 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx @@ -12,12 +12,13 @@ import SVG from 'react-inlinesvg' import { Textfit } from 'react-textfit' import { upToSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' +import { useIsDarkMode } from 'legacy/state/user/hooks' import { ArrowBackground } from './arrowBackground' import { LpToken, StateKey } from './dummyData' import * as styledEl from './styled' -import { DEMO_DROPDOWN_OPTIONS } from './index' +import { BannerLocation, DEMO_DROPDOWN_OPTIONS } from './index' const lpTokenIcons: Record<LpToken, string> = { [LpToken.UniswapV2]: ICON_UNISWAP, @@ -27,25 +28,34 @@ const lpTokenIcons: Record<LpToken, string> = { } interface CoWAmmBannerContentProps { - location: 'global' | 'tokenSelector' + id: string + title: string + ctaText: string + location: BannerLocation + isDemo: boolean selectedState: StateKey setSelectedState: (state: StateKey) => void dummyData: Record<StateKey, { apr: number; comparison: string }> lpTokenConfig: Record<StateKey, LpToken[]> - handleCTAClick: () => void - handleBannerClose: () => void + onCtaClick: () => void + onClose: () => void } export function CoWAmmBannerContent({ + id, + title, + ctaText, location, + isDemo, selectedState, setSelectedState, dummyData, lpTokenConfig, - handleCTAClick, - handleBannerClose, + onCtaClick, + onClose, }: CoWAmmBannerContentProps) { const isMobile = useMediaQuery(upToSmall) + const isDarkMode = useIsDarkMode() const arrowBackgroundRef = useRef<HTMLDivElement>(null) const handleCTAMouseEnter = useCallback(() => { @@ -62,21 +72,19 @@ export function CoWAmmBannerContent({ } }, []) - const aprMessage = useMemo(() => { - const { apr } = dummyData[selectedState] - return `+${apr.toFixed(1)}%` - }, [selectedState, dummyData]) + const { apr, comparison } = dummyData[selectedState] + + const aprMessage = useMemo(() => `+${apr.toFixed(1)}%`, [apr]) const comparisonMessage = useMemo(() => { - const { comparison } = dummyData[selectedState] if (selectedState === 'noLp') { return `yield over the average UNI-V2 pool` } - if (selectedState === 'twoLps' || selectedState === 'threeLps') { - return `Get higher average APR than ${comparison}` - } - return `Get higher APR than ${comparison}` - }, [selectedState, dummyData]) + const prefix = ['twoLps', 'threeLps'].includes(selectedState) + ? 'Get higher average APR than' + : 'Get higher APR than' + return `${prefix} ${comparison}` + }, [selectedState, comparison]) const lpEmblems = useMemo(() => { const tokens = lpTokenConfig[selectedState] @@ -110,132 +118,141 @@ export function CoWAmmBannerContent({ ) }, [selectedState, lpTokenConfig]) - const isTokenSelector = (loc: typeof location): loc is 'tokenSelector' => loc === 'tokenSelector' - - const renderBannerContent = () => { - if (isTokenSelector(location)) { - return ( - <styledEl.TokenSelectorWrapper> - <styledEl.TokenSelectorWrapperInner> - <styledEl.Title> - <ProductLogo - height={20} - overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} - variant={ProductVariant.CowAmm} - logoIconOnly - /> - <span>CoW AMM</span> - </styledEl.Title> - <styledEl.Card - bgColor={'transparent'} - borderColor={`var(${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30})`} - borderWidth={2} - padding={'14px'} - gap={'14px'} - height={'78px'} - > - <styledEl.StarIcon size={26} top={-16} right={80} color={`var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> - <h3> - <Textfit mode="single" forceSingleModeWidth={false} min={45} max={48} key={aprMessage}> - {aprMessage} - </Textfit> - </h3> - <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={12} max={21} key={comparisonMessage}> - {comparisonMessage} - </Textfit> - </p> - <styledEl.StarIcon size={16} bottom={3} right={20} color={`var(${UI.COLOR_COWAMM_LIGHTER_GREEN})`}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> - </styledEl.Card> - </styledEl.TokenSelectorWrapperInner> - </styledEl.TokenSelectorWrapper> - ) - } + const renderProductLogo = (color: string) => ( + <ProductLogo height={20} overrideColor={`var(${color})`} variant={ProductVariant.CowAmm} logoIconOnly /> + ) - return ( - <> - <styledEl.Title> - <ProductLogo - height={20} - overrideColor={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`} - variant={ProductVariant.CowAmm} - logoIconOnly - /> - <span>CoW AMM</span> + const renderStarIcon = (props: any) => ( + <styledEl.StarIcon {...props}> + <SVG src={ICON_STAR} /> + </styledEl.StarIcon> + ) + + const renderTextfit = ( + content: React.ReactNode, + mode: 'single' | 'multi', + minFontSize: number, + maxFontSize: number, + ) => ( + <Textfit mode={mode} forceSingleModeWidth={false} min={minFontSize} max={maxFontSize}> + {content} + </Textfit> + ) + + const renderTokenSelectorContent = () => ( + <styledEl.TokenSelectorWrapper> + <styledEl.TokenSelectorWrapperInner + bgColor={isDarkMode ? `var(${UI.COLOR_COWAMM_DARK_GREEN})` : undefined} + color={isDarkMode ? `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` : undefined} + > + <styledEl.CloseButton + size={24} + top={14} + onClick={onClose} + color={`var(${isDarkMode ? UI.COLOR_COWAMM_LIGHT_GREEN : UI.COLOR_COWAMM_DARK_GREEN})`} + /> + <styledEl.Title color={`var(${isDarkMode ? UI.COLOR_COWAMM_LIGHT_GREEN : UI.COLOR_COWAMM_DARK_GREEN})`}> + {renderProductLogo(isDarkMode ? UI.COLOR_COWAMM_LIGHT_GREEN : UI.COLOR_COWAMM_DARK_GREEN)} + <span>{title}</span> </styledEl.Title> - <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_BLUE})`}> - <styledEl.StarIcon size={36} top={-17} right={80}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> - <h3> - <Textfit mode="single" forceSingleModeWidth={false} min={80} max={80} key={aprMessage}> - {aprMessage} - </Textfit> - </h3> - <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={28} key={comparisonMessage}> - {comparisonMessage} - </Textfit> - </p> - <styledEl.StarIcon size={26} bottom={-10} right={20}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> + <styledEl.Card + bgColor={'transparent'} + borderColor={`var(${isDarkMode ? UI.COLOR_COWAMM_LIGHT_GREEN_OPACITY_30 : UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30})`} + borderWidth={2} + padding={'14px'} + gap={'14px'} + height={'78px'} + > + {renderStarIcon({ size: 26, top: -16, right: 80, color: `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` })} + <h3>{renderTextfit(aprMessage, 'single', 45, 48)}</h3> + <span>{renderTextfit(comparisonMessage, 'multi', 12, 21)}</span> + {renderStarIcon({ size: 16, bottom: 3, right: 20, color: `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` })} + </styledEl.Card> + <styledEl.CTAButton + onClick={onCtaClick} + onMouseEnter={handleCTAMouseEnter} + onMouseLeave={handleCTAMouseLeave} + size={38} + fontSize={18} + bgColor={isDarkMode ? `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` : `var(${UI.COLOR_COWAMM_DARK_GREEN})`} + bgHoverColor={isDarkMode ? `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` : `var(${UI.COLOR_COWAMM_GREEN})`} + color={isDarkMode ? `var(${UI.COLOR_COWAMM_DARK_GREEN})` : `var(${UI.COLOR_COWAMM_LIGHT_GREEN})`} + > + {ctaText} + </styledEl.CTAButton> + </styledEl.TokenSelectorWrapperInner> + </styledEl.TokenSelectorWrapper> + ) + + const renderGlobalContent = () => ( + <styledEl.BannerWrapper> + <styledEl.CloseButton size={24} onClick={onClose} /> + <styledEl.Title> + {renderProductLogo(UI.COLOR_COWAMM_LIGHT_GREEN)} + <span>{title}</span> + </styledEl.Title> + <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_BLUE})`}> + {renderStarIcon({ size: 36, top: -17, right: 80 })} + <h3>{renderTextfit(aprMessage, 'single', 80, 80)}</h3> + <span>{renderTextfit(comparisonMessage, 'multi', 10, 28)}</span> + {renderStarIcon({ size: 26, bottom: -10, right: 20 })} + </styledEl.Card> + + {!isMobile && ( + <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_GREEN})`} color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}> + <span> + {renderTextfit( + <> + One-click convert, <strong>boost yield</strong> + </>, + 'multi', + 10, + 30, + )} + </span> + {lpEmblems} </styledEl.Card> + )} - {!isMobile && ( - <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_GREEN})`} color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}> - <p> - <Textfit mode="multi" forceSingleModeWidth={false} min={10} max={30}> - One-click convert, <b>boost yield</b> - </Textfit> - </p> - {lpEmblems} - </styledEl.Card> - )} - </> - ) + <styledEl.CTAButton onClick={onCtaClick} onMouseEnter={handleCTAMouseEnter} onMouseLeave={handleCTAMouseLeave}> + {ctaText} + </styledEl.CTAButton> + + <styledEl.SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</styledEl.SecondaryLink> + + <ArrowBackground ref={arrowBackgroundRef} /> + </styledEl.BannerWrapper> + ) + + const renderDemoDropdown = () => ( + <styledEl.DEMO_DROPDOWN value={selectedState} onChange={(e) => setSelectedState(e.target.value as StateKey)}> + {DEMO_DROPDOWN_OPTIONS.map((option) => ( + <option key={option.value} value={option.value}> + {option.label} + </option> + ))} + </styledEl.DEMO_DROPDOWN> + ) + + const content = (() => { + switch (location) { + case BannerLocation.TokenSelector: + return renderTokenSelectorContent() + case BannerLocation.Global: + return renderGlobalContent() + default: + return null + } + })() + + if (!content) { + return null } return ( - <> - {isTokenSelector(location) ? ( - renderBannerContent() - ) : ( - <styledEl.BannerWrapper> - <styledEl.CloseButton size={24} onClick={handleBannerClose} /> - - <styledEl.DEMO_DROPDOWN value={selectedState} onChange={(e) => setSelectedState(e.target.value as StateKey)}> - {DEMO_DROPDOWN_OPTIONS.map((option) => ( - <option key={option.value} value={option.value}> - {option.label} - </option> - ))} - </styledEl.DEMO_DROPDOWN> - - {renderBannerContent()} - - <styledEl.CTAButton - onClick={handleCTAClick} - onMouseEnter={handleCTAMouseEnter} - onMouseLeave={handleCTAMouseLeave} - size={isTokenSelector(location) ? 38 : undefined} - fontSize={isTokenSelector(location) ? 18 : undefined} - bgColor={isTokenSelector(location) ? `var(${UI.COLOR_COWAMM_DARK_GREEN})` : undefined} - bgHoverColor={isTokenSelector(location) ? `var(${UI.COLOR_COWAMM_GREEN})` : undefined} - color={isTokenSelector(location) ? `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` : undefined} - > - Booooost APR gas-free! - </styledEl.CTAButton> - - <ArrowBackground ref={arrowBackgroundRef} /> - - <styledEl.SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</styledEl.SecondaryLink> - </styledEl.BannerWrapper> - )} - </> + <div data-banner-id={id}> + {content} + {isDemo && renderDemoDropdown()} + </div> ) } diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index 40f0ad3edf..987d3e35d8 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -9,6 +9,7 @@ import { CoWAmmBannerContent } from './CoWAmmBannerContent' import { cowAmmBannerStateAtom } from './cowAmmBannerState' import { dummyData, lpTokenConfig } from './dummyData' +const IS_DEMO_MODE = true const ANALYTICS_URL = 'https://cow.fi/pools?utm_source=swap.cow.fi&utm_medium=web&utm_content=cow_amm_banner' export const DEMO_DROPDOWN_OPTIONS = [ @@ -22,7 +23,10 @@ export const DEMO_DROPDOWN_OPTIONS = [ { value: 'fourLps', label: '4 LP tokens' }, ] -type BannerLocation = 'global' | 'tokenSelector' +export enum BannerLocation { + Global = 'global', + TokenSelector = 'tokenSelector', +} interface BannerProps { location: BannerLocation @@ -47,22 +51,28 @@ export function CoWAmmBanner({ location }: BannerProps) { }) }, [location]) - const handleBannerClose = (close: () => void) => () => { + const handleBannerClose = useCallback(() => { handleClose() - close() - } + }, [handleClose]) const bannerId = `cow_amm_banner_2024_va_${location}` return ClosableBanner(bannerId, (close) => ( <CoWAmmBannerContent + id={bannerId} + title="CoW AMM" + ctaText="Explore CoW AMM" location={location} + isDemo={IS_DEMO_MODE} selectedState={selectedState} setSelectedState={setSelectedState} dummyData={dummyData} lpTokenConfig={lpTokenConfig} - handleCTAClick={handleCTAClick} - handleBannerClose={handleBannerClose(close)} + onCtaClick={handleCTAClick} + onClose={() => { + handleBannerClose() + close() + }} /> )) } diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts index 2485823659..18769e6472 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts @@ -110,7 +110,7 @@ export const Card = styled.div<{ position: relative; > h3, - > p { + > span { display: flex; align-items: center; justify-content: center; @@ -133,11 +133,11 @@ export const Card = styled.div<{ letter-spacing: -2px; } - > p { + > span { font-weight: inherit; } - > p b { + > span b { font-weight: 900; color: var(${UI.COLOR_COWAMM_LIGHTER_GREEN}); } @@ -380,13 +380,13 @@ export const TokenSelectorWrapper = styled.div` position: relative; ` -export const TokenSelectorWrapperInner = styled.div` +export const TokenSelectorWrapperInner = styled.div<{ bgColor?: string; color?: string }>` position: relative; width: 100%; height: auto; border-radius: 24px; - background-color: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); - color: var(${UI.COLOR_COWAMM_DARK_GREEN}); + background: ${({ bgColor }) => bgColor || `var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}; + color: ${({ color }) => color || `var(${UI.COLOR_COWAMM_DARK_GREEN})`}; padding: 14px; margin: 0 auto; display: flex; 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 c08a02680e..8cdbbbbc57 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx @@ -24,7 +24,7 @@ import { useInitializeUtm } from 'modules/utm' import { InvalidLocalTimeWarning } from 'common/containers/InvalidLocalTimeWarning' import { useCategorizeRecentActivity } from 'common/hooks/useCategorizeRecentActivity' import { useMenuItems } from 'common/hooks/useMenuItems' -import { CoWAmmBanner } from 'common/pure/CoWAMMBanner' +import { BannerLocation, CoWAmmBanner } from 'common/pure/CoWAMMBanner' import { LoadingApp } from 'common/pure/LoadingApp' import { CoWDAOFonts } from 'common/styles/CoWDAOFonts' import RedirectAnySwapAffectedUsers from 'pages/error/AnySwapAffectedUsers/RedirectAnySwapAffectedUsers' @@ -62,7 +62,7 @@ export function App() { onClick: toggleDarkMode, }, ], - [darkMode, toggleDarkMode] + [darkMode, toggleDarkMode], ) const tradeContext = useTradeRouteContext() @@ -129,7 +129,7 @@ export function App() { )} {/* CoW AMM banner */} - {!isInjectedWidgetMode && <CoWAmmBanner />} + {!isInjectedWidgetMode && <CoWAmmBanner location={BannerLocation.Global} />} <styledEl.BodyWrapper> <TopLevelModals /> 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 93f4623b30..33377421db 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx @@ -5,7 +5,7 @@ import { TokenWithLogo } from '@cowprotocol/common-const' import { useVirtualizer } from '@tanstack/react-virtual' import ms from 'ms.macro' -import { CoWAmmBanner } from 'common/pure/CoWAMMBanner' +import { CoWAmmBanner, BannerLocation } from 'common/pure/CoWAMMBanner' import * as styledEl from './styled' @@ -67,8 +67,7 @@ export function TokensVirtualList(props: TokensVirtualListProps) { return ( <CommonListContainer id="tokens-list" ref={parentRef} onScroll={onScroll}> - <CoWAmmBanner location="tokenSelector" /> - + <CoWAmmBanner location={BannerLocation.TokenSelector} /> <styledEl.TokensInner ref={wrapperRef} style={{ height: virtualizer.getTotalSize() }}> <styledEl.TokensScroller style={{ transform: `translateY(${items[0]?.start ?? 0}px)` }}> {items.map((virtualRow) => { diff --git a/libs/ui/src/enum.ts b/libs/ui/src/enum.ts index 608ce17b35..fda3e8dbc5 100644 --- a/libs/ui/src/enum.ts +++ b/libs/ui/src/enum.ts @@ -65,6 +65,7 @@ export enum UI { COLOR_COWAMM_DARK_GREEN_OPACITY_30 = '--cow-color-cowamm-dark-green-opacity-30', COLOR_COWAMM_GREEN = '--cow-color-cowamm-green', COLOR_COWAMM_LIGHT_GREEN = '--cow-color-cowamm-light-green', + COLOR_COWAMM_LIGHT_GREEN_OPACITY_30 = '--cow-color-cowamm-light-green-opacity-30', COLOR_COWAMM_LIGHTER_GREEN = '--cow-color-cowamm-lighter-green', COLOR_COWAMM_BLUE = '--cow-color-cowamm-blue', COLOR_COWAMM_LIGHT_BLUE = '--cow-color-cowamm-light-blue', diff --git a/libs/ui/src/theme/ThemeColorVars.tsx b/libs/ui/src/theme/ThemeColorVars.tsx index 55efc51732..81a27134de 100644 --- a/libs/ui/src/theme/ThemeColorVars.tsx +++ b/libs/ui/src/theme/ThemeColorVars.tsx @@ -102,6 +102,7 @@ export const ThemeColorVars = css` ${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30}: ${() => transparentize('#194d05', 0.7)}; ${UI.COLOR_COWAMM_GREEN}: #2b6f0b; ${UI.COLOR_COWAMM_LIGHT_GREEN}: #bcec79; + ${UI.COLOR_COWAMM_LIGHT_GREEN_OPACITY_30}: ${() => transparentize('#bcec79', 0.7)}; ${UI.COLOR_COWAMM_LIGHTER_GREEN}: #dcf8a7; ${UI.COLOR_COWAMM_BLUE}: #3fc4ff; ${UI.COLOR_COWAMM_LIGHT_BLUE}: #ccf8ff; From b0bb42650116805c98ae8129706cc41371a7cc8b Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Tue, 15 Oct 2024 13:07:15 +0100 Subject: [PATCH 08/15] feat: add poolinfo element --- .../pure/CoWAMMBanner/CoWAmmBannerContent.tsx | 236 +++++++++++++----- .../src/common/pure/CoWAMMBanner/dummyData.ts | 85 ++++++- .../src/common/pure/CoWAMMBanner/index.tsx | 20 +- .../src/common/pure/CoWAMMBanner/styled.ts | 87 ++++++- libs/ui/src/enum.ts | 2 + libs/ui/src/theme/ThemeColorVars.tsx | 2 + 6 files changed, 336 insertions(+), 96 deletions(-) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx index 75b615b287..8103c4baa8 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx @@ -1,3 +1,4 @@ +import React from 'react' import { useCallback, useMemo, useRef } from 'react' import ICON_ARROW from '@cowprotocol/assets/cow-swap/arrow.svg' @@ -15,10 +16,14 @@ import { upToSmall, useMediaQuery } from 'legacy/hooks/useMediaQuery' import { useIsDarkMode } from 'legacy/state/user/hooks' import { ArrowBackground } from './arrowBackground' -import { LpToken, StateKey } from './dummyData' +import { LpToken, StateKey, dummyData, lpTokenConfig } from './dummyData' import * as styledEl from './styled' import { BannerLocation, DEMO_DROPDOWN_OPTIONS } from './index' +import { TokenLogo } from '../../../../../../libs/tokens/src/pure/TokenLogo' +import { USDC, WBTC } from '@cowprotocol/common-const' +import { SupportedChainId } from '@cowprotocol/cow-sdk' +import { DummyDataType, TwoLpScenario } from './dummyData' const lpTokenIcons: Record<LpToken, string> = { [LpToken.UniswapV2]: ICON_UNISWAP, @@ -35,12 +40,28 @@ interface CoWAmmBannerContentProps { isDemo: boolean selectedState: StateKey setSelectedState: (state: StateKey) => void - dummyData: Record<StateKey, { apr: number; comparison: string }> - lpTokenConfig: Record<StateKey, LpToken[]> + dummyData: typeof dummyData + lpTokenConfig: typeof lpTokenConfig onCtaClick: () => void onClose: () => void } +function isTwoLpScenario(scenario: DummyDataType[keyof DummyDataType]): scenario is TwoLpScenario { + return 'uniV2Apr' in scenario && 'sushiApr' in scenario +} + +const renderTextfit = ( + content: React.ReactNode, + mode: 'single' | 'multi', + minFontSize: number, + maxFontSize: number, + key: string, +) => ( + <Textfit key={key} mode={mode} forceSingleModeWidth={false} min={minFontSize} max={maxFontSize}> + {content} + </Textfit> +) + export function CoWAmmBannerContent({ id, title, @@ -72,26 +93,104 @@ export function CoWAmmBannerContent({ } }, []) - const { apr, comparison } = dummyData[selectedState] + const { apr } = dummyData[selectedState] const aprMessage = useMemo(() => `+${apr.toFixed(1)}%`, [apr]) const comparisonMessage = useMemo(() => { - if (selectedState === 'noLp') { - return `yield over the average UNI-V2 pool` + const currentData = dummyData[selectedState] + + if (!currentData) { + return 'Invalid state selected' } - const prefix = ['twoLps', 'threeLps'].includes(selectedState) - ? 'Get higher average APR than' - : 'Get higher APR than' - return `${prefix} ${comparison}` - }, [selectedState, comparison]) + + const renderPoolInfo = (poolName: string) => ( + <styledEl.PoolInfo + flow={location === BannerLocation.TokenSelector ? 'row' : 'column'} + align={location === BannerLocation.TokenSelector ? 'center' : 'flex-start'} + bgColor={ + location === BannerLocation.TokenSelector ? `var(${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_15})` : undefined + } + color={location === BannerLocation.TokenSelector ? `var(${UI.COLOR_COWAMM_DARK_GREEN})` : undefined} + tokenBorderColor={location === BannerLocation.TokenSelector ? `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` : undefined} + > + higher APR available for your {poolName} pool: + <i> + <div> + <TokenLogo token={WBTC} /> <TokenLogo token={USDC[SupportedChainId.MAINNET]} /> + </div> + <span>WBTC-USDC</span> + </i> + </styledEl.PoolInfo> + ) + + if (isTwoLpScenario(currentData)) { + if (selectedState === 'twoLpsMixed') { + return renderPoolInfo('UNI-V2') + } else if (selectedState === 'twoLpsBothSuperior') { + const { uniV2Apr, sushiApr } = currentData + const higherAprPool = uniV2Apr > sushiApr ? 'UNI-V2' : 'SushiSwap' + return renderPoolInfo(higherAprPool) + } + } + + if (selectedState === 'uniV2Superior') { + return renderPoolInfo('UNI-V2') + } + + if (currentData.hasCoWAmmPool) { + return `yield over average ${currentData.comparison} pool` + } else { + const tokens = lpTokenConfig[selectedState] + if (tokens.length > 1) { + const tokenNames = tokens + .map((token) => { + switch (token) { + case LpToken.UniswapV2: + return 'UNI-V2' + case LpToken.Sushiswap: + return 'Sushi' + case LpToken.PancakeSwap: + return 'PancakeSwap' + case LpToken.Curve: + return 'Curve' + default: + return '' + } + }) + .filter(Boolean) + + return `yield over average ${tokenNames.join(', ')} pool${tokenNames.length > 1 ? 's' : ''}` + } else { + return `yield over average UNI-V2 pool` + } + } + }, [selectedState, lpTokenConfig]) const lpEmblems = useMemo(() => { const tokens = lpTokenConfig[selectedState] const totalItems = tokens.length if (totalItems === 0) { - return null + // Fallback to UniswapV2 emblem if no LP tokens + return ( + <styledEl.LpEmblems> + <styledEl.LpEmblemItem key={LpToken.UniswapV2} totalItems={1} index={0}> + <SVG src={lpTokenIcons[LpToken.UniswapV2]} /> + </styledEl.LpEmblemItem> + <styledEl.EmblemArrow> + <SVG src={ICON_ARROW} /> + </styledEl.EmblemArrow> + <styledEl.CoWAMMEmblemItem> + <ProductLogo + height={'100%'} + overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} + variant={ProductVariant.CowAmm} + logoIconOnly + /> + </styledEl.CoWAMMEmblemItem> + </styledEl.LpEmblems> + ) } return ( @@ -116,27 +215,22 @@ export function CoWAmmBannerContent({ </styledEl.CoWAMMEmblemItem> </styledEl.LpEmblems> ) - }, [selectedState, lpTokenConfig]) - - const renderProductLogo = (color: string) => ( - <ProductLogo height={20} overrideColor={`var(${color})`} variant={ProductVariant.CowAmm} logoIconOnly /> - ) + }, [selectedState, lpTokenConfig, lpTokenIcons]) - const renderStarIcon = (props: any) => ( - <styledEl.StarIcon {...props}> - <SVG src={ICON_STAR} /> - </styledEl.StarIcon> + const renderProductLogo = useCallback( + (color: string) => ( + <ProductLogo height={20} overrideColor={`var(${color})`} variant={ProductVariant.CowAmm} logoIconOnly /> + ), + [], ) - const renderTextfit = ( - content: React.ReactNode, - mode: 'single' | 'multi', - minFontSize: number, - maxFontSize: number, - ) => ( - <Textfit mode={mode} forceSingleModeWidth={false} min={minFontSize} max={maxFontSize}> - {content} - </Textfit> + const renderStarIcon = useCallback( + (props: any) => ( + <styledEl.StarIcon {...props}> + <SVG src={ICON_STAR} /> + </styledEl.StarIcon> + ), + [], ) const renderTokenSelectorContent = () => ( @@ -159,13 +253,15 @@ export function CoWAmmBannerContent({ bgColor={'transparent'} borderColor={`var(${isDarkMode ? UI.COLOR_COWAMM_LIGHT_GREEN_OPACITY_30 : UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30})`} borderWidth={2} - padding={'14px'} + padding={'10px'} gap={'14px'} - height={'78px'} + height={'max-content'} > {renderStarIcon({ size: 26, top: -16, right: 80, color: `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` })} - <h3>{renderTextfit(aprMessage, 'single', 45, 48)}</h3> - <span>{renderTextfit(comparisonMessage, 'multi', 12, 21)}</span> + <h3>{renderTextfit(aprMessage, 'single', 24, 48, `apr-${selectedState}`)}</h3> + <span> + {renderTextfit(comparisonMessage, 'multi', 12, isMobile ? 18 : 21, `comparison-${selectedState}`)} + </span> {renderStarIcon({ size: 16, bottom: 3, right: 20, color: `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` })} </styledEl.Card> <styledEl.CTAButton @@ -173,6 +269,7 @@ export function CoWAmmBannerContent({ onMouseEnter={handleCTAMouseEnter} onMouseLeave={handleCTAMouseLeave} size={38} + fontSizeMobile={18} fontSize={18} bgColor={isDarkMode ? `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` : `var(${UI.COLOR_COWAMM_DARK_GREEN})`} bgHoverColor={isDarkMode ? `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` : `var(${UI.COLOR_COWAMM_GREEN})`} @@ -184,45 +281,50 @@ export function CoWAmmBannerContent({ </styledEl.TokenSelectorWrapper> ) - const renderGlobalContent = () => ( - <styledEl.BannerWrapper> - <styledEl.CloseButton size={24} onClick={onClose} /> - <styledEl.Title> - {renderProductLogo(UI.COLOR_COWAMM_LIGHT_GREEN)} - <span>{title}</span> - </styledEl.Title> - <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_BLUE})`}> - {renderStarIcon({ size: 36, top: -17, right: 80 })} - <h3>{renderTextfit(aprMessage, 'single', 80, 80)}</h3> - <span>{renderTextfit(comparisonMessage, 'multi', 10, 28)}</span> - {renderStarIcon({ size: 26, bottom: -10, right: 20 })} - </styledEl.Card> - - {!isMobile && ( - <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_GREEN})`} color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}> + const renderGlobalContent = () => { + return ( + <styledEl.BannerWrapper> + <styledEl.CloseButton size={24} onClick={onClose} /> + <styledEl.Title> + {renderProductLogo(UI.COLOR_COWAMM_LIGHT_GREEN)} + <span>{title}</span> + </styledEl.Title> + <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_BLUE})`} color={`var(${UI.COLOR_COWAMM_DARK_BLUE})`}> + {renderStarIcon({ size: 36, top: -17, right: 80 })} + <h3>{renderTextfit(aprMessage, 'single', isMobile ? 40 : 80, isMobile ? 50 : 80, `apr-${selectedState}`)}</h3> <span> - {renderTextfit( - <> - One-click convert, <strong>boost yield</strong> - </>, - 'multi', - 10, - 30, - )} + {renderTextfit(comparisonMessage, 'multi', 10, isMobile ? 21 : 28, `comparison-${selectedState}`)} </span> - {lpEmblems} + {renderStarIcon({ size: 26, bottom: -10, right: 20 })} </styledEl.Card> - )} - <styledEl.CTAButton onClick={onCtaClick} onMouseEnter={handleCTAMouseEnter} onMouseLeave={handleCTAMouseLeave}> - {ctaText} - </styledEl.CTAButton> + {!isMobile && ( + <styledEl.Card bgColor={`var(${UI.COLOR_COWAMM_GREEN})`} color={`var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}> + <span> + {renderTextfit( + <> + One-click convert, <strong>boost yield</strong> + </>, + 'multi', + 10, + 30, + `boost-yield-${selectedState}`, + )} + </span> + {lpEmblems} + </styledEl.Card> + )} - <styledEl.SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</styledEl.SecondaryLink> + <styledEl.CTAButton onClick={onCtaClick} onMouseEnter={handleCTAMouseEnter} onMouseLeave={handleCTAMouseLeave}> + {ctaText} + </styledEl.CTAButton> - <ArrowBackground ref={arrowBackgroundRef} /> - </styledEl.BannerWrapper> - ) + <styledEl.SecondaryLink href={'https://cow.fi/'}>Pool analytics ↗</styledEl.SecondaryLink> + + <ArrowBackground ref={arrowBackgroundRef} /> + </styledEl.BannerWrapper> + ) + } const renderDemoDropdown = () => ( <styledEl.DEMO_DROPDOWN value={selectedState} onChange={(e) => setSelectedState(e.target.value as StateKey)}> diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts index 0cfb4da319..1a40e2de18 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts @@ -5,26 +5,87 @@ export enum LpToken { Curve = 'Curve', } +type BaseScenario = { + readonly apr: number + readonly comparison: string + readonly hasCoWAmmPool: boolean +} + +export type TwoLpScenario = BaseScenario & { + readonly uniV2Apr: number + readonly sushiApr: number +} + export const dummyData = { - noLp: { apr: 1.5, comparison: 'UNI-V2' }, - uniV2: { apr: 2.1, comparison: 'UNI-V2' }, - sushi: { apr: 1.8, comparison: 'SushiSwap' }, - curve: { apr: 1.3, comparison: 'Curve' }, - pancake: { apr: 2.5, comparison: 'PancakeSwap' }, - twoLps: { apr: 2.0, comparison: 'UNI-V2 and SushiSwap' }, - threeLps: { apr: 2.2, comparison: 'UNI-V2, SushiSwap, and Curve' }, - fourLps: { apr: 2.4, comparison: 'UNI-V2, Sushiswap, Curve, and Balancer' }, + noLp: { + apr: 1.5, + comparison: 'average UNI-V2 pool', + hasCoWAmmPool: false, + }, + uniV2Superior: { + apr: 2.1, + comparison: 'UNI-V2', + hasCoWAmmPool: true, + }, + uniV2Inferior: { + apr: 1.2, + comparison: 'UNI-V2', + hasCoWAmmPool: true, + }, + sushi: { + apr: 1.8, + comparison: 'SushiSwap', + hasCoWAmmPool: true, + }, + curve: { + apr: 1.3, + comparison: 'Curve', + hasCoWAmmPool: true, + }, + pancake: { + apr: 2.5, + comparison: 'PancakeSwap', + hasCoWAmmPool: true, + isYieldSuperior: true, + }, + twoLpsMixed: { + apr: 2.5, + comparison: 'UNI-V2 and SushiSwap', + hasCoWAmmPool: true, + uniV2Apr: 3.0, + sushiApr: 1.8, + } as TwoLpScenario, + twoLpsBothSuperior: { + apr: 3.2, + comparison: 'UNI-V2 and SushiSwap', + hasCoWAmmPool: true, + uniV2Apr: 3.5, + sushiApr: 2.9, + } as TwoLpScenario, + threeLps: { + apr: 2.2, + comparison: 'UNI-V2, SushiSwap, and Curve', + hasCoWAmmPool: false, + }, + fourLps: { + apr: 2.4, + comparison: 'UNI-V2, SushiSwap, Curve, and PancakeSwap', + hasCoWAmmPool: false, + }, } as const export type StateKey = keyof typeof dummyData +export type DummyDataType = typeof dummyData export const lpTokenConfig: Record<StateKey, LpToken[]> = { - noLp: [LpToken.UniswapV2], - uniV2: [LpToken.UniswapV2], + noLp: [], + uniV2Superior: [LpToken.UniswapV2], + uniV2Inferior: [LpToken.UniswapV2], sushi: [LpToken.Sushiswap], curve: [LpToken.Curve], pancake: [LpToken.PancakeSwap], - twoLps: [LpToken.UniswapV2, LpToken.Sushiswap], + twoLpsMixed: [LpToken.UniswapV2, LpToken.Sushiswap], + twoLpsBothSuperior: [LpToken.UniswapV2, LpToken.Sushiswap], threeLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve], - fourLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve, LpToken.Curve], + fourLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve, LpToken.PancakeSwap], } diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index 987d3e35d8..fb65b7df8e 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -13,14 +13,16 @@ const IS_DEMO_MODE = true const ANALYTICS_URL = 'https://cow.fi/pools?utm_source=swap.cow.fi&utm_medium=web&utm_content=cow_amm_banner' export const DEMO_DROPDOWN_OPTIONS = [ - { value: 'noLp', label: 'No LP tokens' }, - { value: 'uniV2', label: 'UNI-V2 LP' }, - { value: 'sushi', label: 'SushiSwap LP' }, - { value: 'curve', label: 'Curve LP' }, - { value: 'pancake', label: 'PancakeSwap LP' }, - { value: 'twoLps', label: '2 LP tokens' }, - { value: 'threeLps', label: '3 LP tokens' }, - { value: 'fourLps', label: '4 LP tokens' }, + { value: 'noLp', label: '🚫 No LP Tokens' }, + { value: 'uniV2Superior', label: '⬆️ 🐴 UNI-V2 LP (Superior Yield)' }, + { value: 'uniV2Inferior', label: '⬇️ 🐴 UNI-V2 LP (Inferior Yield)' }, + { value: 'sushi', label: '⬇️ 🍣 SushiSwap LP (Inferior Yield)' }, + { value: 'curve', label: '⬇️ 🌈 Curve LP (Inferior Yield)' }, + { value: 'pancake', label: '⬇️ 🥞 PancakeSwap LP (Inferior Yield)' }, + { value: 'twoLpsMixed', label: '⬆️ 🐴 UNI-V2 (Superior) & ⬇️ 🍣 SushiSwap (Inferior) LPs' }, + { value: 'twoLpsBothSuperior', label: '⬆️ 🐴 UNI-V2 & ⬆️ 🍣 SushiSwap LPs (Both Superior, but UNI-V2 is higher)' }, + { value: 'threeLps', label: '⬇️ 🐴 UNI-V2, 🍣 SushiSwap & 🌈 Curve LPs (Inferior Yield)' }, + { value: 'fourLps', label: '⬇️ 🐴 UNI-V2, 🍣 SushiSwap, 🌈 Curve & 🥞 PancakeSwap LPs (Inferior Yield)' }, ] export enum BannerLocation { @@ -61,7 +63,7 @@ export function CoWAmmBanner({ location }: BannerProps) { <CoWAmmBannerContent id={bannerId} title="CoW AMM" - ctaText="Explore CoW AMM" + ctaText="Booooost APR gas-free!" location={location} isDemo={IS_DEMO_MODE} selectedState={selectedState} diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts index 18769e6472..d8896bfe2b 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts @@ -2,6 +2,7 @@ import { UI, Media } from '@cowprotocol/ui' import { X } from 'react-feather' import styled, { keyframes } from 'styled-components/macro' +import { TokenLogoWrapper } from '../../../../../../libs/tokens/src/pure/TokenLogo' const arrowUpAnimation = keyframes` 0% { @@ -82,7 +83,7 @@ export const Title = styled.h2<{ color?: string }>` export const Card = styled.div<{ bgColor?: string color?: string - height?: string + height?: number | 'max-content' borderColor?: string borderWidth?: number padding?: string @@ -100,8 +101,10 @@ export const Card = styled.div<{ margin: 0; width: 100%; max-width: 100%; - height: ${({ height }) => height || 'var(--default-height)'}; - max-height: ${({ height }) => height || 'var(--default-height)'}; + height: ${({ height }) => + height === 'max-content' ? 'max-content' : height ? `${height}px` : 'var(--default-height)'}; + max-height: ${({ height }) => + height === 'max-content' ? 'max-content' : height ? `${height}px` : 'var(--default-height)'}; border-radius: 16px; padding: ${({ padding }) => padding || '24px'}; background: ${({ bgColor }) => bgColor || 'transparent'}; @@ -109,6 +112,13 @@ export const Card = styled.div<{ border: ${({ borderWidth, borderColor }) => borderWidth && borderColor && `${borderWidth}px solid ${borderColor}`}; position: relative; + ${Media.upToSmall()} { + flex-flow: column wrap; + height: auto; + max-height: initial; + gap: 8px; + } + > h3, > span { display: flex; @@ -118,6 +128,12 @@ export const Card = styled.div<{ width: max-content; height: 100%; max-height: 100%; + color: inherit; + + ${Media.upToSmall()} { + width: 100%; + text-align: center; + } > div { width: 100%; @@ -143,21 +159,63 @@ export const Card = styled.div<{ } ` +export const PoolInfo = styled.div<{ + flow?: 'column' | 'row' + align?: 'flex-start' | 'center' + color?: string + bgColor?: string + tokenBorderColor?: string +}>` + display: flex; + align-items: ${({ align = 'flex-start' }) => align}; + flex-flow: ${({ flow = 'column' }) => flow}; + font-size: 16px; + gap: 10px; + + > i { + font-style: normal; + background: ${({ bgColor }) => bgColor || `var(${UI.COLOR_COWAMM_LIGHT_BLUE})`}; + color: ${({ color }) => color || `var(${UI.COLOR_COWAMM_DARK_BLUE})`}; + display: flex; + flex-flow: row; + gap: 6px; + padding: 6px 12px 6px 6px; + height: min-content; + border-radius: 62px; + width: min-content; + box-shadow: var(${UI.BOX_SHADOW_2}); + } + + > i > div { + display: flex; + } + + ${TokenLogoWrapper} { + border: 2px solid ${({ tokenBorderColor }) => tokenBorderColor || `var(${UI.COLOR_COWAMM_LIGHT_BLUE})`}; + + :last-child { + margin-left: -18px; + } + } +` + export const CTAButton = styled.button<{ bgColor?: string bgHoverColor?: string color?: string size?: number fontSize?: number + fontSizeMobile?: number }>` --size: ${({ size = 58 }) => size}px; + --font-size: ${({ fontSize = 24 }) => fontSize}px; background: ${({ bgColor }) => bgColor || `var(${UI.COLOR_COWAMM_LIGHT_GREEN})`}; color: ${({ color }) => color || `var(${UI.COLOR_COWAMM_DARK_GREEN})`}; border: none; border-radius: var(--size); min-height: var(--size); padding: 12px 24px; - font-size: ${({ fontSize = 24 }) => fontSize}px; + font-size: var(--font-size); font-weight: bold; cursor: pointer; width: 100%; @@ -170,6 +228,11 @@ export const CTAButton = styled.button<{ overflow: hidden; z-index: 1; + ${Media.upToSmall()} { + --font-size: ${({ fontSizeMobile = 21 }) => fontSizeMobile}px; + min-height: initial; + } + &::before { content: ''; position: absolute; @@ -206,11 +269,19 @@ export const SecondaryLink = styled.a` export const DEMO_DROPDOWN = styled.select` position: fixed; - bottom: 150px; - right: 10px; + bottom: 20px; + right: 20px; z-index: 999999999; padding: 5px; - font-size: 16px; + font-size: 14px; + + ${Media.upToSmall()} { + bottom: initial; + top: 0; + width: 100%; + right: 0; + left: 0; + } ` export const StarIcon = styled.div<{ @@ -228,7 +299,7 @@ export const StarIcon = styled.div<{ left: ${({ left }) => (left === 'initial' ? 'initial' : left != null ? `${left}px` : 'initial')}; right: ${({ right }) => (right === 'initial' ? 'initial' : right != null ? `${right}px` : 'initial')}; bottom: ${({ bottom }) => (bottom === 'initial' ? 'initial' : bottom != null ? `${bottom}px` : 'initial')}; - color: ${({ color }) => color ?? `var(${UI.COLOR_WHITE})`}; + color: ${({ color }) => color ?? `var(${UI.COLOR_COWAMM_LIGHT_BLUE})`}; > svg > path { fill: ${({ color }) => color ?? 'currentColor'}; diff --git a/libs/ui/src/enum.ts b/libs/ui/src/enum.ts index fda3e8dbc5..7b083a0990 100644 --- a/libs/ui/src/enum.ts +++ b/libs/ui/src/enum.ts @@ -63,11 +63,13 @@ export enum UI { // CoW AMM Colors COLOR_COWAMM_DARK_GREEN = '--cow-color-cowamm-dark-green', COLOR_COWAMM_DARK_GREEN_OPACITY_30 = '--cow-color-cowamm-dark-green-opacity-30', + COLOR_COWAMM_DARK_GREEN_OPACITY_15 = '--cow-color-cowamm-dark-green-opacity-15', COLOR_COWAMM_GREEN = '--cow-color-cowamm-green', COLOR_COWAMM_LIGHT_GREEN = '--cow-color-cowamm-light-green', COLOR_COWAMM_LIGHT_GREEN_OPACITY_30 = '--cow-color-cowamm-light-green-opacity-30', COLOR_COWAMM_LIGHTER_GREEN = '--cow-color-cowamm-lighter-green', COLOR_COWAMM_BLUE = '--cow-color-cowamm-blue', + COLOR_COWAMM_DARK_BLUE = '--cow-color-cowamm-dark-blue', COLOR_COWAMM_LIGHT_BLUE = '--cow-color-cowamm-light-blue', // ================================================================================ diff --git a/libs/ui/src/theme/ThemeColorVars.tsx b/libs/ui/src/theme/ThemeColorVars.tsx index 81a27134de..03acc39dcf 100644 --- a/libs/ui/src/theme/ThemeColorVars.tsx +++ b/libs/ui/src/theme/ThemeColorVars.tsx @@ -100,11 +100,13 @@ export const ThemeColorVars = css` // CoW AMM Colors ${UI.COLOR_COWAMM_DARK_GREEN}: #194d05; ${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_30}: ${() => transparentize('#194d05', 0.7)}; + ${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_15}: ${() => transparentize('#194d05', 0.85)}; ${UI.COLOR_COWAMM_GREEN}: #2b6f0b; ${UI.COLOR_COWAMM_LIGHT_GREEN}: #bcec79; ${UI.COLOR_COWAMM_LIGHT_GREEN_OPACITY_30}: ${() => transparentize('#bcec79', 0.7)}; ${UI.COLOR_COWAMM_LIGHTER_GREEN}: #dcf8a7; ${UI.COLOR_COWAMM_BLUE}: #3fc4ff; + ${UI.COLOR_COWAMM_DARK_BLUE}: #012F7A; ${UI.COLOR_COWAMM_LIGHT_BLUE}: #ccf8ff; // Base From 8b2dde26e0b87296e71f86712e55503d87f6d5f6 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:43:58 +0100 Subject: [PATCH 09/15] feat: text fit optimisation --- .../common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx | 6 +++--- .../src/common/pure/CoWAMMBanner/styled.ts | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx index 8103c4baa8..018ceb9b31 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx @@ -255,12 +255,12 @@ export function CoWAmmBannerContent({ borderWidth={2} padding={'10px'} gap={'14px'} - height={'max-content'} + height={90} > {renderStarIcon({ size: 26, top: -16, right: 80, color: `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` })} - <h3>{renderTextfit(aprMessage, 'single', 24, 48, `apr-${selectedState}`)}</h3> + <h3>{renderTextfit(aprMessage, 'single', 35, 65, `apr-${selectedState}`)}</h3> <span> - {renderTextfit(comparisonMessage, 'multi', 12, isMobile ? 18 : 21, `comparison-${selectedState}`)} + {renderTextfit(comparisonMessage, 'multi', 15, isMobile ? 15 : 21, `comparison-${selectedState}`)} </span> {renderStarIcon({ size: 16, bottom: 3, right: 20, color: `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` })} </styledEl.Card> diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts index d8896bfe2b..165e249b33 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts @@ -169,9 +169,13 @@ export const PoolInfo = styled.div<{ display: flex; align-items: ${({ align = 'flex-start' }) => align}; flex-flow: ${({ flow = 'column' }) => flow}; - font-size: 16px; + font-size: inherit; gap: 10px; + ${Media.upToSmall()} { + flex-flow: column wrap; + } + > i { font-style: normal; background: ${({ bgColor }) => bgColor || `var(${UI.COLOR_COWAMM_LIGHT_BLUE})`}; @@ -184,6 +188,10 @@ export const PoolInfo = styled.div<{ border-radius: 62px; width: min-content; box-shadow: var(${UI.BOX_SHADOW_2}); + + ${Media.upToSmall()} { + margin: 0 auto; + } } > i > div { From ea742c87d916e5719c02213b04743965534170f4 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:37:34 +0100 Subject: [PATCH 10/15] feat: do not show banners without connect wallet --- .../src/modules/application/containers/App/index.tsx | 5 ++++- .../src/modules/tokensList/pure/TokensVirtualList/index.tsx | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) 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 8cdbbbbc57..83a1a3810a 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx @@ -3,6 +3,7 @@ import { lazy, PropsWithChildren, Suspense, useMemo } from 'react' import { useMediaQuery } from '@cowprotocol/common-hooks' import { isInjectedWidget } from '@cowprotocol/common-utils' import { Color, Footer, GlobalCoWDAOStyles, Media, MenuBar } from '@cowprotocol/ui' +import { useWalletInfo } from '@cowprotocol/wallet' import { NavLink } from 'react-router-dom' import { ThemeProvider } from 'theme' @@ -94,6 +95,8 @@ export function App() { </HeaderControls> ) + const { account } = useWalletInfo() + return ( <ErrorBoundary> <Suspense fallback={<LoadingApp />}> @@ -129,7 +132,7 @@ export function App() { )} {/* CoW AMM banner */} - {!isInjectedWidgetMode && <CoWAmmBanner location={BannerLocation.Global} />} + {!isInjectedWidgetMode && account && <CoWAmmBanner location={BannerLocation.Global} />} <styledEl.BodyWrapper> <TopLevelModals /> 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 33377421db..c1313c0e5d 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 { useWalletInfo } from '@cowprotocol/wallet' import { useVirtualizer } from '@tanstack/react-virtual' import ms from 'ms.macro' @@ -35,6 +36,8 @@ export function TokensVirtualList(props: TokensVirtualListProps) { props const { values: balances, isLoading: balancesLoading } = balancesState + const { account: connectedAccount } = useWalletInfo() + const isWalletConnected = !!account const scrollTimeoutRef = useRef<NodeJS.Timeout>() @@ -67,7 +70,7 @@ export function TokensVirtualList(props: TokensVirtualListProps) { return ( <CommonListContainer id="tokens-list" ref={parentRef} onScroll={onScroll}> - <CoWAmmBanner location={BannerLocation.TokenSelector} /> + {connectedAccount && <CoWAmmBanner location={BannerLocation.TokenSelector} />} <styledEl.TokensInner ref={wrapperRef} style={{ height: virtualizer.getTotalSize() }}> <styledEl.TokensScroller style={{ transform: `translateY(${items[0]?.start ?? 0}px)` }}> {items.map((virtualRow) => { From 608f3c0fb6a65451e2500369eb99e6345af391f3 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:05:00 +0100 Subject: [PATCH 11/15] feat: add usdc scenario --- .../pure/CoWAMMBanner/CoWAmmBannerContent.tsx | 78 ++++++++++++------- .../src/common/pure/CoWAMMBanner/dummyData.ts | 30 ++++++- .../src/common/pure/CoWAMMBanner/index.tsx | 1 + .../src/common/pure/CoWAMMBanner/styled.ts | 9 ++- 4 files changed, 85 insertions(+), 33 deletions(-) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx index 018ceb9b31..0e23150f2d 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx @@ -23,7 +23,7 @@ import { BannerLocation, DEMO_DROPDOWN_OPTIONS } from './index' import { TokenLogo } from '../../../../../../libs/tokens/src/pure/TokenLogo' import { USDC, WBTC } from '@cowprotocol/common-const' import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { DummyDataType, TwoLpScenario } from './dummyData' +import { DummyDataType, TwoLpScenario, InferiorYieldScenario } from './dummyData' const lpTokenIcons: Record<LpToken, string> = { [LpToken.UniswapV2]: ICON_UNISWAP, @@ -50,6 +50,10 @@ function isTwoLpScenario(scenario: DummyDataType[keyof DummyDataType]): scenario return 'uniV2Apr' in scenario && 'sushiApr' in scenario } +function isInferiorYieldScenario(scenario: DummyDataType[keyof DummyDataType]): scenario is InferiorYieldScenario { + return 'poolsCount' in scenario +} + const renderTextfit = ( content: React.ReactNode, mode: 'single' | 'multi', @@ -95,7 +99,15 @@ export function CoWAmmBannerContent({ const { apr } = dummyData[selectedState] - const aprMessage = useMemo(() => `+${apr.toFixed(1)}%`, [apr]) + const aprMessage = useMemo(() => { + if (selectedState === 'uniV2InferiorWithLowAverageYield') { + const currentData = dummyData[selectedState] + if (isInferiorYieldScenario(currentData)) { + return `${currentData.poolsCount}+` + } + } + return `+${apr.toFixed(1)}%` + }, [selectedState, apr, dummyData]) const comparisonMessage = useMemo(() => { const currentData = dummyData[selectedState] @@ -138,6 +150,10 @@ export function CoWAmmBannerContent({ return renderPoolInfo('UNI-V2') } + if (selectedState === 'uniV2InferiorWithLowAverageYield' && isInferiorYieldScenario(currentData)) { + return 'pools available to get yield on your assets!' + } + if (currentData.hasCoWAmmPool) { return `yield over average ${currentData.comparison} pool` } else { @@ -165,30 +181,44 @@ export function CoWAmmBannerContent({ return `yield over average UNI-V2 pool` } } - }, [selectedState, lpTokenConfig]) + }, [selectedState, location, lpTokenConfig]) const lpEmblems = useMemo(() => { const tokens = lpTokenConfig[selectedState] const totalItems = tokens.length - if (totalItems === 0) { - // Fallback to UniswapV2 emblem if no LP tokens + const renderEmblemContent = () => ( + <> + <styledEl.EmblemArrow> + <SVG src={ICON_ARROW} /> + </styledEl.EmblemArrow> + <styledEl.CoWAMMEmblemItem> + <ProductLogo + height={'100%'} + overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} + variant={ProductVariant.CowAmm} + logoIconOnly + /> + </styledEl.CoWAMMEmblemItem> + </> + ) + + if (totalItems === 0 || selectedState === 'uniV2InferiorWithLowAverageYield') { return ( <styledEl.LpEmblems> - <styledEl.LpEmblemItem key={LpToken.UniswapV2} totalItems={1} index={0}> - <SVG src={lpTokenIcons[LpToken.UniswapV2]} /> + <styledEl.LpEmblemItem + key="USDC" + totalItems={1} + index={0} + isUSDC={selectedState === 'uniV2InferiorWithLowAverageYield'} + > + {selectedState === 'uniV2InferiorWithLowAverageYield' ? ( + <TokenLogo token={USDC[SupportedChainId.MAINNET]} size={40} /> + ) : ( + <SVG src={lpTokenIcons[LpToken.UniswapV2]} /> + )} </styledEl.LpEmblemItem> - <styledEl.EmblemArrow> - <SVG src={ICON_ARROW} /> - </styledEl.EmblemArrow> - <styledEl.CoWAMMEmblemItem> - <ProductLogo - height={'100%'} - overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} - variant={ProductVariant.CowAmm} - logoIconOnly - /> - </styledEl.CoWAMMEmblemItem> + {renderEmblemContent()} </styledEl.LpEmblems> ) } @@ -202,17 +232,7 @@ export function CoWAmmBannerContent({ </styledEl.LpEmblemItem> ))} </styledEl.LpEmblemItemsWrapper> - <styledEl.EmblemArrow> - <SVG src={ICON_ARROW} /> - </styledEl.EmblemArrow> - <styledEl.CoWAMMEmblemItem> - <ProductLogo - height={'100%'} - overrideColor={`var(${UI.COLOR_COWAMM_DARK_GREEN})`} - variant={ProductVariant.CowAmm} - logoIconOnly - /> - </styledEl.CoWAMMEmblemItem> + {renderEmblemContent()} </styledEl.LpEmblems> ) }, [selectedState, lpTokenConfig, lpTokenIcons]) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts index 1a40e2de18..26492684f6 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/dummyData.ts @@ -16,7 +16,25 @@ export type TwoLpScenario = BaseScenario & { readonly sushiApr: number } -export const dummyData = { +export type InferiorYieldScenario = BaseScenario & { + readonly poolsCount: number +} + +export type DummyDataType = { + noLp: BaseScenario + uniV2Superior: BaseScenario + uniV2Inferior: BaseScenario + sushi: BaseScenario + curve: BaseScenario + pancake: BaseScenario & { readonly isYieldSuperior: boolean } + twoLpsMixed: TwoLpScenario + twoLpsBothSuperior: TwoLpScenario + threeLps: BaseScenario + fourLps: BaseScenario + uniV2InferiorWithLowAverageYield: InferiorYieldScenario +} + +export const dummyData: DummyDataType = { noLp: { apr: 1.5, comparison: 'average UNI-V2 pool', @@ -72,10 +90,15 @@ export const dummyData = { comparison: 'UNI-V2, SushiSwap, Curve, and PancakeSwap', hasCoWAmmPool: false, }, -} as const + uniV2InferiorWithLowAverageYield: { + apr: 1.2, + comparison: 'UNI-V2', + hasCoWAmmPool: true, + poolsCount: 195, + }, +} export type StateKey = keyof typeof dummyData -export type DummyDataType = typeof dummyData export const lpTokenConfig: Record<StateKey, LpToken[]> = { noLp: [], @@ -88,4 +111,5 @@ export const lpTokenConfig: Record<StateKey, LpToken[]> = { twoLpsBothSuperior: [LpToken.UniswapV2, LpToken.Sushiswap], threeLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve], fourLps: [LpToken.UniswapV2, LpToken.Sushiswap, LpToken.Curve, LpToken.PancakeSwap], + uniV2InferiorWithLowAverageYield: [LpToken.UniswapV2], } diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index fb65b7df8e..f100a2baf3 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -16,6 +16,7 @@ export const DEMO_DROPDOWN_OPTIONS = [ { value: 'noLp', label: '🚫 No LP Tokens' }, { value: 'uniV2Superior', label: '⬆️ 🐴 UNI-V2 LP (Superior Yield)' }, { value: 'uniV2Inferior', label: '⬇️ 🐴 UNI-V2 LP (Inferior Yield)' }, + { value: 'uniV2InferiorWithLowAverageYield', label: '⬇️ 🐴 UNI-V2 LP (Inferior Yield, Lower Average)' }, { value: 'sushi', label: '⬇️ 🍣 SushiSwap LP (Inferior Yield)' }, { value: 'curve', label: '⬇️ 🌈 Curve LP (Inferior Yield)' }, { value: 'pancake', label: '⬇️ 🥞 PancakeSwap LP (Inferior Yield)' }, diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts index 165e249b33..ba9093db41 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/styled.ts @@ -350,12 +350,14 @@ export const LpEmblemItemsWrapper = styled.div<{ totalItems: number }>` export const LpEmblemItem = styled.div<{ totalItems: number index: number + isUSDC?: boolean }>` --size: ${({ totalItems }) => totalItems === 4 ? '50px' : totalItems === 3 ? '65px' : totalItems === 2 ? '80px' : '104px'}; width: var(--size); height: var(--size); - padding: ${({ totalItems }) => (totalItems === 4 ? '10px' : totalItems >= 2 ? '15px' : '20px')}; + padding: ${({ totalItems, isUSDC }) => + isUSDC ? '9px' : totalItems === 4 ? '10px' : totalItems >= 2 ? '15px' : '20px'}; border-radius: 50%; background: var(${UI.COLOR_COWAMM_DARK_GREEN}); color: var(${UI.COLOR_COWAMM_LIGHT_GREEN}); @@ -391,6 +393,11 @@ export const LpEmblemItem = styled.div<{ return styleMap[totalItems]?.[index] || '' }} + + ${TokenLogoWrapper} { + width: 100%; + height: 100%; + } ` export const CoWAMMEmblemItem = styled.div` From 9087316b0d3da0713a58df877e69d82ea38b00a5 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:36:00 +0100 Subject: [PATCH 12/15] feat: modify CTA button text for SC wallets --- apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx index f100a2baf3..5f831b0ece 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/index.tsx @@ -2,6 +2,7 @@ import { useAtom } from 'jotai' import { useCallback } from 'react' import { ClosableBanner } from '@cowprotocol/ui' +import { useIsSmartContractWallet } from '@cowprotocol/wallet' import { cowAnalytics } from 'modules/analytics' @@ -60,11 +61,13 @@ export function CoWAmmBanner({ location }: BannerProps) { const bannerId = `cow_amm_banner_2024_va_${location}` + const isSmartContractWallet = useIsSmartContractWallet() + return ClosableBanner(bannerId, (close) => ( <CoWAmmBannerContent id={bannerId} title="CoW AMM" - ctaText="Booooost APR gas-free!" + ctaText={isSmartContractWallet ? 'Booooost APR!' : 'Booooost APR gas-free!'} location={location} isDemo={IS_DEMO_MODE} selectedState={selectedState} From cb108ccbf674509a3ef4471af537db8cd9639021 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:02:55 +0100 Subject: [PATCH 13/15] feat: add dark mode styles for token selector banner --- .../pure/CoWAMMBanner/CoWAmmBannerContent.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx index 0e23150f2d..2a2fd6d5dd 100644 --- a/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx +++ b/apps/cowswap-frontend/src/common/pure/CoWAMMBanner/CoWAmmBannerContent.tsx @@ -121,10 +121,26 @@ export function CoWAmmBannerContent({ flow={location === BannerLocation.TokenSelector ? 'row' : 'column'} align={location === BannerLocation.TokenSelector ? 'center' : 'flex-start'} bgColor={ - location === BannerLocation.TokenSelector ? `var(${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_15})` : undefined + location === BannerLocation.TokenSelector + ? isDarkMode + ? `var(${UI.COLOR_COWAMM_LIGHT_BLUE})` + : `var(${UI.COLOR_COWAMM_DARK_GREEN_OPACITY_15})` + : undefined + } + color={ + location === BannerLocation.TokenSelector + ? isDarkMode + ? `var(${UI.COLOR_COWAMM_DARK_BLUE})` + : `var(${UI.COLOR_COWAMM_DARK_GREEN})` + : undefined + } + tokenBorderColor={ + location === BannerLocation.TokenSelector + ? isDarkMode + ? `var(${UI.COLOR_COWAMM_LIGHT_BLUE})` + : `var(${UI.COLOR_COWAMM_DARK_GREEN})` + : undefined } - color={location === BannerLocation.TokenSelector ? `var(${UI.COLOR_COWAMM_DARK_GREEN})` : undefined} - tokenBorderColor={location === BannerLocation.TokenSelector ? `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` : undefined} > higher APR available for your {poolName} pool: <i> @@ -181,7 +197,7 @@ export function CoWAmmBannerContent({ return `yield over average UNI-V2 pool` } } - }, [selectedState, location, lpTokenConfig]) + }, [selectedState, location, lpTokenConfig, isDarkMode]) const lpEmblems = useMemo(() => { const tokens = lpTokenConfig[selectedState] From 077667a04f946d84330e5ec68a9bf0a7a5cb8be7 Mon Sep 17 00:00:00 2001 From: fairlighteth <31534717+fairlighteth@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:20:40 +0100 Subject: [PATCH 14/15] feat: only show banners on supported networks --- .../src/modules/application/containers/App/index.tsx | 6 +++++- .../modules/tokensList/pure/TokensVirtualList/index.tsx | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) 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 83a1a3810a..3f157a846f 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx @@ -24,6 +24,7 @@ import { useInitializeUtm } from 'modules/utm' import { InvalidLocalTimeWarning } from 'common/containers/InvalidLocalTimeWarning' import { useCategorizeRecentActivity } from 'common/hooks/useCategorizeRecentActivity' +import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' import { useMenuItems } from 'common/hooks/useMenuItems' import { BannerLocation, CoWAmmBanner } from 'common/pure/CoWAMMBanner' import { LoadingApp } from 'common/pure/LoadingApp' @@ -96,6 +97,7 @@ export function App() { ) const { account } = useWalletInfo() + const isChainIdUnsupported = useIsProviderNetworkUnsupported() return ( <ErrorBoundary> @@ -132,7 +134,9 @@ export function App() { )} {/* CoW AMM banner */} - {!isInjectedWidgetMode && account && <CoWAmmBanner location={BannerLocation.Global} />} + {!isInjectedWidgetMode && account && !isChainIdUnsupported && ( + <CoWAmmBanner location={BannerLocation.Global} /> + )} <styledEl.BodyWrapper> <TopLevelModals /> 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 c1313c0e5d..1cb591f269 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx @@ -1,11 +1,13 @@ import { useCallback, useMemo, useRef } from 'react' import { TokenWithLogo } from '@cowprotocol/common-const' +import { isInjectedWidget } from '@cowprotocol/common-utils' import { useWalletInfo } from '@cowprotocol/wallet' import { useVirtualizer } from '@tanstack/react-virtual' import ms from 'ms.macro' +import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' import { CoWAmmBanner, BannerLocation } from 'common/pure/CoWAMMBanner' import * as styledEl from './styled' @@ -38,7 +40,9 @@ export function TokensVirtualList(props: TokensVirtualListProps) { const { account: connectedAccount } = useWalletInfo() + const isInjectedWidgetMode = isInjectedWidget() const isWalletConnected = !!account + const isChainIdUnsupported = useIsProviderNetworkUnsupported() const scrollTimeoutRef = useRef<NodeJS.Timeout>() const parentRef = useRef<HTMLDivElement>(null) @@ -70,7 +74,9 @@ export function TokensVirtualList(props: TokensVirtualListProps) { return ( <CommonListContainer id="tokens-list" ref={parentRef} onScroll={onScroll}> - {connectedAccount && <CoWAmmBanner location={BannerLocation.TokenSelector} />} + {!isInjectedWidgetMode && connectedAccount && !isChainIdUnsupported && ( + <CoWAmmBanner location={BannerLocation.TokenSelector} /> + )} <styledEl.TokensInner ref={wrapperRef} style={{ height: virtualizer.getTotalSize() }}> <styledEl.TokensScroller style={{ transform: `translateY(${items[0]?.start ?? 0}px)` }}> {items.map((virtualRow) => { From ec81c0651dabbd7290998804487bb323673f4fe7 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko <shoom3301@gmail.com> Date: Wed, 23 Oct 2024 12:26:22 +0500 Subject: [PATCH 15/15] chore: temporarily hide cow amm banner --- .../application/containers/App/index.tsx | 13 +++++-------- .../tokensList/pure/TokensVirtualList/index.tsx | 17 +++++------------ 2 files changed, 10 insertions(+), 20 deletions(-) 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 a68e699e33..b8f9b4db77 100644 --- a/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx +++ b/apps/cowswap-frontend/src/modules/application/containers/App/index.tsx @@ -3,7 +3,6 @@ import { lazy, PropsWithChildren, Suspense, useMemo } from 'react' import { useMediaQuery } from '@cowprotocol/common-hooks' import { isInjectedWidget } from '@cowprotocol/common-utils' import { Color, Footer, GlobalCoWDAOStyles, Media, MenuBar } from '@cowprotocol/ui' -import { useWalletInfo } from '@cowprotocol/wallet' import { NavLink } from 'react-router-dom' import { ThemeProvider } from 'theme' @@ -24,9 +23,7 @@ import { useInitializeUtm } from 'modules/utm' import { InvalidLocalTimeWarning } from 'common/containers/InvalidLocalTimeWarning' import { useCategorizeRecentActivity } from 'common/hooks/useCategorizeRecentActivity' -import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' import { useMenuItems } from 'common/hooks/useMenuItems' -import { BannerLocation, CoWAmmBanner } from 'common/pure/CoWAMMBanner' import { LoadingApp } from 'common/pure/LoadingApp' import { CoWDAOFonts } from 'common/styles/CoWDAOFonts' import RedirectAnySwapAffectedUsers from 'pages/error/AnySwapAffectedUsers/RedirectAnySwapAffectedUsers' @@ -96,8 +93,8 @@ export function App() { </HeaderControls> ) - const { account } = useWalletInfo() - const isChainIdUnsupported = useIsProviderNetworkUnsupported() + // const { account } = useWalletInfo() + // const isChainIdUnsupported = useIsProviderNetworkUnsupported() return ( <ErrorBoundary> @@ -134,9 +131,9 @@ export function App() { )} {/* CoW AMM banner */} - {!isInjectedWidgetMode && account && !isChainIdUnsupported && ( - <CoWAmmBanner location={BannerLocation.Global} /> - )} + {/*{!isInjectedWidgetMode && account && !isChainIdUnsupported && (*/} + {/* <CoWAmmBanner location={BannerLocation.Global} />*/} + {/*)}*/} <styledEl.BodyWrapper> <TopLevelModals /> 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 1cb591f269..3637167df1 100644 --- a/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx +++ b/apps/cowswap-frontend/src/modules/tokensList/pure/TokensVirtualList/index.tsx @@ -1,15 +1,10 @@ import { useCallback, useMemo, useRef } from 'react' import { TokenWithLogo } from '@cowprotocol/common-const' -import { isInjectedWidget } from '@cowprotocol/common-utils' -import { useWalletInfo } from '@cowprotocol/wallet' import { useVirtualizer } from '@tanstack/react-virtual' import ms from 'ms.macro' -import { useIsProviderNetworkUnsupported } from 'common/hooks/useIsProviderNetworkUnsupported' -import { CoWAmmBanner, BannerLocation } from 'common/pure/CoWAMMBanner' - import * as styledEl from './styled' import { SelectTokenContext } from '../../types' @@ -38,11 +33,9 @@ export function TokensVirtualList(props: TokensVirtualListProps) { props const { values: balances, isLoading: balancesLoading } = balancesState - const { account: connectedAccount } = useWalletInfo() - - const isInjectedWidgetMode = isInjectedWidget() const isWalletConnected = !!account - const isChainIdUnsupported = useIsProviderNetworkUnsupported() + // const isInjectedWidgetMode = isInjectedWidget() + // const isChainIdUnsupported = useIsProviderNetworkUnsupported() const scrollTimeoutRef = useRef<NodeJS.Timeout>() const parentRef = useRef<HTMLDivElement>(null) @@ -74,9 +67,9 @@ export function TokensVirtualList(props: TokensVirtualListProps) { return ( <CommonListContainer id="tokens-list" ref={parentRef} onScroll={onScroll}> - {!isInjectedWidgetMode && connectedAccount && !isChainIdUnsupported && ( - <CoWAmmBanner location={BannerLocation.TokenSelector} /> - )} + {/*{!isInjectedWidgetMode && account && !isChainIdUnsupported && (*/} + {/* <CoWAmmBanner location={BannerLocation.TokenSelector} />*/} + {/*)}*/} <styledEl.TokensInner ref={wrapperRef} style={{ height: virtualizer.getTotalSize() }}> <styledEl.TokensScroller style={{ transform: `translateY(${items[0]?.start ?? 0}px)` }}> {items.map((virtualRow) => {