diff --git a/src/hooks/useBannerCampaigns.tsx b/src/hooks/useBannerCampaigns.tsx new file mode 100644 index 0000000000..53174ab8f6 --- /dev/null +++ b/src/hooks/useBannerCampaigns.tsx @@ -0,0 +1,152 @@ +import { ChainId } from '@aave/contract-helpers'; +import { Trans } from '@lingui/macro'; +import { useRouter } from 'next/router'; +import { ReactNode } from 'react'; +import { useModalContext } from 'src/hooks/useModal'; +import { useRootStore } from 'src/store/root'; +import { CustomMarket } from 'src/ui-config/marketsConfig'; +import { useShallow } from 'zustand/shallow'; + +export interface CampaignConfig { + notifyText: ReactNode; + buttonText: ReactNode; + buttonAction: ButtonAction; + bannerVersion: string; + icon?: string; +} + +export type ButtonAction = + | { + type: 'route'; + value: string; + } + | { + type: 'function'; + value: () => void; + }; + +type CampaignConfigs = Partial>; + +export type NetworkCampaigns = { [chainId: number]: CampaignConfig }; + +export const useBannerCampaigns = (chainId?: ChainId): NetworkCampaigns => { + const router = useRouter(); + const [setCurrentMarket] = useRootStore(useShallow((store) => [store.setCurrentMarket])); + const { openSwitch } = useModalContext(); + + const openMarket = (market: CustomMarket) => { + setCurrentMarket(market); + router.push(`/markets/?marketName=${market}`); + }; + + const campaignConfigs: CampaignConfigs = { + [ChainId.base]: { + notifyText: A new incentives campaign is live on the Base market, + buttonText: Explore Base, + buttonAction: { + type: 'route' as const, + value: '/markets/?marketName=proto_base_v3', + }, + bannerVersion: 'base-incentives-v1', + icon: '/icons/networks/base.svg', + }, + + // [ChainId.sonic]: { + // notifyText: Swaps are now live on Sonic, + // buttonText: Swap Now, + // buttonAction: { + // type: 'function' as const, + // value: () => openSwitch('', ChainId.sonic), + // }, + // bannerVersion: 'sonic-incentives-v1', + // icon: '/icons/networks/sonic.svg', + // }, + + [ChainId.mainnet]: { + notifyText: The Plasma market is now live., + buttonText: Get Started, + buttonAction: { + type: 'function' as const, + value: () => openMarket(CustomMarket.proto_plasma_v3), + }, + bannerVersion: 'plasma-market-v0', + icon: '/icons/networks/plasma.svg', + }, + + // [ChainId.polygon]: { + // notifyText: Swap tokens directly in the Aave App, + // buttonText: Swap Now, + // buttonAction: { + // type: 'function' as const, + // value: () => openSwitch('', ChainId.polygon), + // }, + // bannerVersion: 'polygon-swap-v1', + // icon: '/icons/networks/polygon.svg', + // }, + + // [ChainId.avalanche]: { + // notifyText: Swap tokens directly in the Aave App, + // buttonText: Swap Now, + // buttonAction: { + // type: 'function' as const, + // value: () => openSwitch('', ChainId.avalanche), + // }, + // bannerVersion: 'avalanche-swap-v1', + // icon: '/icons/networks/avalanche.svg', + // }, + + [ChainId.arbitrum_one]: { + notifyText: Limit orders are now live on Arbitrum, + buttonText: Swap Now, + buttonAction: { + type: 'function' as const, + value: () => openSwitch('', ChainId.arbitrum_one), + }, + bannerVersion: 'arbitrum-swap-v1', + icon: '/icons/networks/arbitrum.svg', + }, + + // [ChainId.optimism]: { + // notifyText: Swap tokens directly in the Aave App, + // buttonText: Swap Now, + // buttonAction: { + // type: 'function' as const, + // value: () => openSwitch('', ChainId.optimism), + // }, + // bannerVersion: 'optimism-swap-v1', + // icon: '/icons/networks/optimism.svg', + // }, + + // [ChainId.xdai]: { + // notifyText: Swap tokens directly in the Aave App, + // buttonText: Swap Now, + // buttonAction: { + // type: 'function' as const, + // value: () => openSwitch('', ChainId.xdai), + // }, + // bannerVersion: 'gnosis-swap-v1', + // icon: '/icons/networks/gnosis.svg', + // }, + + // [ChainId.bnb]: { + // notifyText: Swap tokens directly in the Aave App, + // buttonText: Swap Now, + // buttonAction: { + // type: 'function' as const, + // value: () => openSwitch('', ChainId.bnb), + // }, + // bannerVersion: 'binance-swap-v1', + // icon: '/icons/networks/binance.svg', + // }, + }; + + const isCampaignChainId = (chainId: ChainId): chainId is ChainId => { + return chainId in campaignConfigs; + }; + + if (!chainId || !isCampaignChainId(chainId)) { + return {}; + } + + return { [chainId]: campaignConfigs[chainId]! }; +}; diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx index 6643078cf9..8819ed28b3 100644 --- a/src/layouts/MainLayout.tsx +++ b/src/layouts/MainLayout.tsx @@ -1,12 +1,12 @@ import { ChainId } from '@aave/contract-helpers'; import { Box } from '@mui/material'; -import { useRouter } from 'next/router'; import React, { ReactNode } from 'react'; import AnalyticsConsent from 'src/components/Analytics/AnalyticsConsent'; -// import { useModalContext } from 'src/hooks/useModal'; +import { useBannerCampaigns } from 'src/hooks/useBannerCampaigns'; import { SupportModal } from 'src/layouts/SupportModal'; import { useRootStore } from 'src/store/root'; -import { CustomMarket } from 'src/ui-config/marketsConfig'; +import { getQueryParameter } from 'src/store/utils/queryParams'; +import { CustomMarket, marketsData } from 'src/ui-config/marketsConfig'; import { FORK_ENABLED } from 'src/utils/marketsAndNetworksConfig'; import { useShallow } from 'zustand/shallow'; @@ -14,124 +14,39 @@ import { AppFooter } from './AppFooter'; import { AppHeader } from './AppHeader'; import TopBarNotify from './TopBarNotify'; -const getCampaignConfigs = ( - // openSwitch: (underlyingAsset: string) => void, - openMarket: (market: CustomMarket) => void -) => ({ - [ChainId.base]: { - notifyText: 'A new incentives campaign is live on the Base market', - buttonText: 'Explore Base', - buttonAction: { - type: 'route' as const, - value: '/markets/?marketName=proto_base_v3', - }, - bannerVersion: 'base-incentives-v1', - icon: '/icons/networks/base.svg', - }, - - // [ChainId.sonic]: { - // notifyText: 'Swaps are now live on Sonic', - // buttonText: 'Swap Now', - // buttonAction: { - // type: 'function' as const, - // value: () => openSwitch('', ChainId.sonic), - // }, - // bannerVersion: 'sonic-incentives-v1', - // icon: '/icons/networks/sonic.svg', - // }, - - [ChainId.mainnet]: { - notifyText: 'The Plasma market is now live.', - buttonText: 'Get Started', - buttonAction: { - type: 'function' as const, - value: () => openMarket(CustomMarket.proto_plasma_v3), - }, - bannerVersion: 'plasma-market-v0', - icon: '/icons/networks/plasma.svg', - }, - - // [ChainId.polygon]: { - // notifyText: 'Swap tokens directly in the Aave App', - // buttonText: 'Swap Now', - // buttonAction: { - // type: 'function' as const, - // value: () => openSwitch('', ChainId.polygon), - // }, - // bannerVersion: 'polygon-swap-v1', - // icon: '/icons/networks/polygon.svg', - // }, - - // [ChainId.avalanche]: { - // notifyText: 'Swap tokens directly in the Aave App', - // buttonText: 'Swap Now', - // buttonAction: { - // type: 'function' as const, - // value: () => openSwitch('', ChainId.avalanche), - // }, - // bannerVersion: 'avalanche-swap-v1', - // icon: '/icons/networks/avalanche.svg', - // }, - - // [ChainId.arbitrum_one]: { - // notifyText: 'Swap tokens directly in the Aave App', - // buttonText: 'Swap Now', - // buttonAction: { - // type: 'function' as const, - // value: () => openSwitch('', ChainId.arbitrum_one), - // }, - // bannerVersion: 'arbitrum-swap-v1', - // icon: '/icons/networks/arbitrum.svg', - // }, - - // [ChainId.optimism]: { - // notifyText: 'Swap tokens directly in the Aave App', - // buttonText: 'Swap Now', - // buttonAction: { - // type: 'function' as const, - // value: () => openSwitch('', ChainId.optimism), - // }, - // bannerVersion: 'optimism-swap-v1', - // icon: '/icons/networks/optimism.svg', - // }, - - // [ChainId.xdai]: { - // notifyText: 'Swap tokens directly in the Aave App', - // buttonText: 'Swap Now', - // buttonAction: { - // type: 'function' as const, - // value: () => openSwitch('', ChainId.xdai), - // }, - // bannerVersion: 'gnosis-swap-v1', - // icon: '/icons/networks/gnosis.svg', - // }, - - // [ChainId.bnb]: { - // notifyText: 'Swap tokens directly in the Aave App', - // buttonText: 'Swap Now', - // buttonAction: { - // type: 'function' as const, - // value: () => openSwitch('', ChainId.bnb), - // }, - // bannerVersion: 'binance-swap-v1', - // icon: '/icons/networks/binance.svg', - // }, -}); +const getIntendedChainId = (currentChainId?: ChainId): ChainId => { + // Priority 1: URL params marketName + const urlMarket = getQueryParameter('marketName'); + if (urlMarket && marketsData[urlMarket as CustomMarket]) { + return marketsData[urlMarket as CustomMarket].chainId; + } + + // Priority 2: currentChainId from store + if (currentChainId) { + return currentChainId; + } + + if (typeof window !== 'undefined') { + // Priority 3: localStorage selectedMarket + const selectedMarket = localStorage.getItem('selectedMarket'); + if (selectedMarket && marketsData[selectedMarket as CustomMarket]) { + return marketsData[selectedMarket as CustomMarket].chainId; + } + } + + // Priority 4: Default to mainnet + return ChainId.mainnet; +}; export function MainLayout({ children }: { children: ReactNode }) { - const router = useRouter(); - const setCurrentMarket = useRootStore(useShallow((store) => store.setCurrentMarket)); - - const openMarket = (market: CustomMarket) => { - setCurrentMarket(market); - router.push(`/markets/?marketName=${market}`); - }; + const [currentChainId] = useRootStore(useShallow((store) => [store.currentChainId])); - const campaignConfigs = getCampaignConfigs(openMarket); + const intendedChainId = getIntendedChainId(currentChainId); + const filteredCampaigns = useBannerCampaigns(intendedChainId); return ( <> - + diff --git a/src/layouts/TopBarNotify.tsx b/src/layouts/TopBarNotify.tsx index 8f4ef2673d..e7479fbadd 100644 --- a/src/layouts/TopBarNotify.tsx +++ b/src/layouts/TopBarNotify.tsx @@ -4,6 +4,7 @@ import { useMediaQuery, useTheme } from '@mui/material'; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; +import Slide from '@mui/material/Slide'; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; import { useRouter } from 'next/router'; @@ -51,39 +52,55 @@ export default function TopBarNotify({ campaigns }: TopBarNotifyProps) { const currentCampaign = getCurrentCampaign(); - const [showWarning, setShowWarning] = useState(() => { - if (!currentCampaign) return false; + const [showWarning, setShowWarning] = useState(false); - const storedBannerVersion = localStorage.getItem(`bannerVersion_${currentChainId}`); - const warningBarOpen = localStorage.getItem(`warningBarOpen_${currentChainId}`); - - if (storedBannerVersion !== currentCampaign.bannerVersion) { - return true; - } - - return warningBarOpen !== 'false'; - }); + const [slideIn, setSlideIn] = useState(false); useEffect(() => { - if (!currentCampaign) return; + if (!currentCampaign) { + setShowWarning(false); + setSlideIn(false); + return; + } const storedBannerVersion = localStorage.getItem(`bannerVersion_${currentChainId}`); + const warningBarOpen = localStorage.getItem(`warningBarOpen_${currentChainId}`); + // Check if this is a new banner version for this chain if (storedBannerVersion !== currentCampaign.bannerVersion) { localStorage.setItem(`bannerVersion_${currentChainId}`, currentCampaign.bannerVersion); localStorage.setItem(`warningBarOpen_${currentChainId}`, 'true'); setShowWarning(true); + } else { + // Use stored preference for this chain + setShowWarning(warningBarOpen !== 'false'); } }, [currentCampaign, currentChainId]); + useEffect(() => { + if (showWarning) { + const timer = setTimeout(() => { + setSlideIn(true); + }, 100); + + return () => clearTimeout(timer); + } else { + setSlideIn(false); + } + }, [showWarning]); + // If no campaign is configured for the current network, don't show anything if (!currentCampaign) { return null; } const handleClose = () => { - localStorage.setItem(`warningBarOpen_${currentChainId}`, 'false'); - setShowWarning(false); + setSlideIn(false); + // Wait for animation to complete before hiding + setTimeout(() => { + localStorage.setItem(`warningBarOpen_${currentChainId}`, 'false'); + setShowWarning(false); + }, 300); // Match MUI Slide animation duration }; const handleButtonAction = () => { @@ -118,100 +135,115 @@ export default function TopBarNotify({ campaigns }: TopBarNotifyProps) { if (showWarning) { return ( - theme.palette.gradients.newGradient, - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - borderRadius: 0, - }} - position="static" - > - + theme.palette.gradients.newGradient + : (theme) => theme.palette.background.header, display: 'flex', - paddingRight: md ? 0 : '', - justifyContent: 'center', alignItems: 'center', - width: '100%', + justifyContent: 'space-between', + borderRadius: 0, + transition: 'background 0.3s ease-in-out, color 0.3s ease-in-out', }} - variant="dense" + position="static" > - - - {currentCampaign.notifyText} - - {currentCampaign.customIcon ? currentCampaign.customIcon : null} - - {currentCampaign.icon && !sm ? ( - - ) : ( - '' - )} - - {currentCampaign.learnMoreLink && md ? ( - typeof currentCampaign.learnMoreLink === 'string' ? ( - - - {currentCampaign.buttonText ? currentCampaign.buttonText : `Learn more`} - - + + + + {currentCampaign.notifyText} + + {currentCampaign.customIcon ? currentCampaign.customIcon : null} + + {currentCampaign.icon && !sm ? ( + ) : ( - - ) + '' + )} + + {currentCampaign.learnMoreLink && md ? ( + typeof currentCampaign.learnMoreLink === 'string' ? ( + + + {currentCampaign.buttonText ? currentCampaign.buttonText : `Learn more`} + + + ) : ( + + ) + ) : null} + + + + + {!md && currentCampaign.buttonText && currentCampaign.buttonAction ? ( + ) : null} - - - - - {!md && currentCampaign.buttonText && currentCampaign.buttonAction ? ( - - ) : null} - -