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] 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.UniswapV2]: ICON_UNISWAP, @@ -35,12 +40,28 @@ interface CoWAmmBannerContentProps { isDemo: boolean selectedState: StateKey setSelectedState: (state: StateKey) => void - dummyData: Record - lpTokenConfig: Record + 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, +) => ( + + {content} + +) + 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) => ( + + higher APR available for your {poolName} pool: + +
+ +
+ WBTC-USDC +
+
+ ) + + 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 ( + + + + + + + + + + + + ) } return ( @@ -116,27 +215,22 @@ export function CoWAmmBannerContent({ ) - }, [selectedState, lpTokenConfig]) - - const renderProductLogo = (color: string) => ( - - ) + }, [selectedState, lpTokenConfig, lpTokenIcons]) - const renderStarIcon = (props: any) => ( - - - + const renderProductLogo = useCallback( + (color: string) => ( + + ), + [], ) - const renderTextfit = ( - content: React.ReactNode, - mode: 'single' | 'multi', - minFontSize: number, - maxFontSize: number, - ) => ( - - {content} - + const renderStarIcon = useCallback( + (props: any) => ( + + + + ), + [], ) 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})` })} -

{renderTextfit(aprMessage, 'single', 45, 48)}

- {renderTextfit(comparisonMessage, 'multi', 12, 21)} +

{renderTextfit(aprMessage, 'single', 24, 48, `apr-${selectedState}`)}

+ + {renderTextfit(comparisonMessage, 'multi', 12, isMobile ? 18 : 21, `comparison-${selectedState}`)} + {renderStarIcon({ size: 16, bottom: 3, right: 20, color: `var(${UI.COLOR_COWAMM_LIGHTER_GREEN})` })} ) - const renderGlobalContent = () => ( - - - - {renderProductLogo(UI.COLOR_COWAMM_LIGHT_GREEN)} - {title} - - - {renderStarIcon({ size: 36, top: -17, right: 80 })} -

{renderTextfit(aprMessage, 'single', 80, 80)}

- {renderTextfit(comparisonMessage, 'multi', 10, 28)} - {renderStarIcon({ size: 26, bottom: -10, right: 20 })} -
- - {!isMobile && ( - + const renderGlobalContent = () => { + return ( + + + + {renderProductLogo(UI.COLOR_COWAMM_LIGHT_GREEN)} + {title} + + + {renderStarIcon({ size: 36, top: -17, right: 80 })} +

{renderTextfit(aprMessage, 'single', isMobile ? 40 : 80, isMobile ? 50 : 80, `apr-${selectedState}`)}

- {renderTextfit( - <> - One-click convert, boost yield - , - 'multi', - 10, - 30, - )} + {renderTextfit(comparisonMessage, 'multi', 10, isMobile ? 21 : 28, `comparison-${selectedState}`)} - {lpEmblems} + {renderStarIcon({ size: 26, bottom: -10, right: 20 })}
- )} - - {ctaText} - + {!isMobile && ( + + + {renderTextfit( + <> + One-click convert, boost yield + , + 'multi', + 10, + 30, + `boost-yield-${selectedState}`, + )} + + {lpEmblems} + + )} - Pool analytics ↗ + + {ctaText} + - -
- ) + Pool analytics ↗ + + +
+ ) + } const renderDemoDropdown = () => ( 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 = { - 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) { ` 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