diff --git a/apps/web/src/components/NavBar/UkBanner.tsx b/apps/web/src/components/NavBar/UkBanner.tsx index 76bcbce2aea..d9e12bc9288 100644 --- a/apps/web/src/components/NavBar/UkBanner.tsx +++ b/apps/web/src/components/NavBar/UkBanner.tsx @@ -1,16 +1,22 @@ import { t, Trans } from '@lingui/macro' +import throttle from 'lodash/throttle' +import { useEffect, useState } from 'react' import { useOpenModal } from 'state/application/hooks' import { ApplicationModal } from 'state/application/reducer' import styled from 'styled-components' -import { ButtonText, ThemedText } from 'theme/components' +import { ButtonText, CloseIcon, ThemedText } from 'theme/components' import { Z_INDEX } from 'theme/zIndex' +import { useUkBannerState } from 'state/application/atoms' +import { useAppSelector } from 'state/hooks' +import { AppState } from 'state/reducer' +import { Flex } from 'ui/src' + export const UK_BANNER_HEIGHT = 65 export const UK_BANNER_HEIGHT_MD = 113 export const UK_BANNER_HEIGHT_SM = 137 const BannerWrapper = styled.div` - position: relative; display: flex; background-color: ${({ theme }) => theme.surface1}; padding: 20px; @@ -19,7 +25,7 @@ const BannerWrapper = styled.div` box-sizing: border-box; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; - + width: 98%; @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { flex-direction: column; } @@ -66,17 +72,63 @@ export const bannerText = t` recommendation, invitation or inducement to deal in cryptoassets. ` +const BannerContainer = styled.div<{ + show: boolean +}>` + display: flex; + max-height: ${({ show }) => (show ? `${UK_BANNER_HEIGHT}px` : '0px')}; + overflow: hidden; + width: 100%; + transition: max-height 0.35s ease-in-out; + position: relative; + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { + max-height: ${({ show }) => (show ? `${UK_BANNER_HEIGHT_MD}px` : '0px')}; + } + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { + max-height: ${({ show }) => (show ? `${UK_BANNER_HEIGHT_SM}px` : '0px')}; + } +` + +export const useRenderUkBanner = () => { + const [show, setShow] = useState(true) + const [notDismissed, dismissBanner] = useUkBannerState() + + const originCountry = useAppSelector((state: AppState) => state.user.originCountry) + + useEffect(() => { + const scrollListener = () => { + if (window.scrollY > 0) setShow(false) + else setShow(true) + } + window.addEventListener('scroll', throttle(scrollListener, 200)) + return () => window.removeEventListener('scroll', throttle(scrollListener, 200)) + }, []) + + return { + renderUkBanner: Boolean(originCountry) && originCountry === 'GB' && show && notDismissed, + dismissBanner, + } +} + export function UkBanner() { + const { renderUkBanner, dismissBanner } = useRenderUkBanner() const openDisclaimer = useOpenModal(ApplicationModal.UK_DISCLAIMER) return ( - - {t`UK disclaimer:` + ' ' + bannerText} - - - Read more - - - + + + {`${t`UK disclaimer:`} ${bannerText}`} + + + Read more + + + + + + + ) } diff --git a/apps/web/src/pages/App.tsx b/apps/web/src/pages/App.tsx index ffc026cd2d5..e0847401d6f 100644 --- a/apps/web/src/pages/App.tsx +++ b/apps/web/src/pages/App.tsx @@ -4,7 +4,13 @@ import { getDeviceId, sendAnalyticsEvent, sendInitializationEvent, Trace, user } import ErrorBoundary from 'components/ErrorBoundary' import Loader from 'components/Icons/LoadingSpinner' import NavBar, { PageTabs } from 'components/NavBar' -import { UK_BANNER_HEIGHT, UK_BANNER_HEIGHT_MD, UK_BANNER_HEIGHT_SM, UkBanner } from 'components/NavBar/UkBanner' +import { + UK_BANNER_HEIGHT, + UK_BANNER_HEIGHT_MD, + UK_BANNER_HEIGHT_SM, + UkBanner, + useRenderUkBanner, +} from 'components/NavBar/UkBanner' import { useFeatureFlagsIsLoaded, useFeatureFlagURLOverrides } from 'featureFlags' import { useAtom } from 'jotai' import { useBag } from 'nft/hooks/useBag' @@ -13,7 +19,6 @@ import { Helmet } from 'react-helmet' import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom' import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' import { useAppSelector } from 'state/hooks' -import { AppState } from 'state/reducer' import { useRouterPreference } from 'state/user/hooks' import { StatsigProvider, StatsigUser } from 'statsig-react' import styled from 'styled-components' @@ -37,6 +42,7 @@ const BodyWrapper = styled.div<{ bannerIsVisible?: boolean }>` flex-direction: column; position: relative; width: 100%; + min-height: calc(100vh - ${({ bannerIsVisible }) => (bannerIsVisible ? UK_BANNER_HEIGHT : 0)}px); padding: ${({ theme }) => theme.navHeight}px 0px 5rem 0px; align-items: center; @@ -49,6 +55,7 @@ const BodyWrapper = styled.div<{ bannerIsVisible?: boolean }>` @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { min-height: calc(100vh - ${({ bannerIsVisible }) => (bannerIsVisible ? UK_BANNER_HEIGHT_SM : 0)}px); } + transition: top 0.35s ease-out; ` const MobileBottomBar = styled.div` @@ -72,7 +79,11 @@ const MobileBottomBar = styled.div` } ` -const HeaderWrapper = styled.div<{ transparent?: boolean; bannerIsVisible?: boolean; scrollY: number }>` +const HeaderWrapper = styled.div<{ + transparent?: boolean + bannerIsVisible?: boolean + scrollY: number +}>` ${flexRowNoWrap}; background-color: ${({ theme, transparent }) => !transparent && theme.surface1}; border-bottom: ${({ theme, transparent }) => !transparent && `1px solid ${theme.surface3}`}; @@ -89,20 +100,15 @@ const HeaderWrapper = styled.div<{ transparent?: boolean; bannerIsVisible?: bool @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { top: ${({ bannerIsVisible }) => (bannerIsVisible ? Math.max(UK_BANNER_HEIGHT_SM - scrollY, 0) : 0)}px; } + transition: top 0.35s ease-out; ` -const useRenderUkBanner = () => { - const originCountry = useAppSelector((state: AppState) => state.user.originCountry) - return Boolean(originCountry) && originCountry === 'GB' -} - export default function App() { const [, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom) const location = useLocation() const { pathname } = location const currentPage = getCurrentPageFromLocation(pathname) - const renderUkBanner = useRenderUkBanner() const [searchParams] = useSearchParams() useEffect(() => { @@ -165,7 +171,7 @@ export default function App() { }} > - {renderUkBanner && } +
@@ -181,7 +187,7 @@ export default function App() { const Body = memo(function Body() { const isLoaded = useFeatureFlagsIsLoaded() const routerConfig = useRouterConfig() - const renderUkBanner = useRenderUkBanner() + const { renderUkBanner } = useRenderUkBanner() return ( @@ -235,18 +241,10 @@ const ResetPageScrollEffect = memo(function ResetPageScrollEffect() { }) const Header = memo(function Header() { - const [isScrolledDown, setIsScrolledDown] = useState(false) const isBagExpanded = useBag((state) => state.bagExpanded) - const isHeaderTransparent = !isScrolledDown && !isBagExpanded - const renderUkBanner = useRenderUkBanner() + const { renderUkBanner } = useRenderUkBanner() - useEffect(() => { - const scrollListener = () => { - setIsScrolledDown(window.scrollY > 0) - } - window.addEventListener('scroll', scrollListener) - return () => window.removeEventListener('scroll', scrollListener) - }, []) + const isHeaderTransparent = useMemo(() => Boolean(window.scrollY > 0 && !isBagExpanded), [isBagExpanded]) return ( diff --git a/apps/web/src/state/application/atoms.ts b/apps/web/src/state/application/atoms.ts index ae271c9cf8b..c1d5a63f29b 100644 --- a/apps/web/src/state/application/atoms.ts +++ b/apps/web/src/state/application/atoms.ts @@ -1,3 +1,5 @@ +import dayjs from 'dayjs' +import { atom, useAtom } from 'jotai' import { atomWithStorage, createJSONStorage } from 'jotai/utils' // Note: @@ -10,3 +12,18 @@ import { atomWithStorage, createJSONStorage } from 'jotai/utils' const storage = createJSONStorage(() => sessionStorage) export const shouldDisableNFTRoutesAtom = atomWithStorage('shouldDisableNFTRoutes', false, storage) + +const UKBannerAtom = atomWithStorage('uni:uk-banner', 0) + +const hideUKBannerAtom = atom( + (get) => { + const now = dayjs() + const last = dayjs(get(UKBannerAtom)) + return now.diff(last, 'days', true) >= 5 // option to not totally disable banner forever + }, + (_, set) => set(UKBannerAtom, Date.now()) +) + +export function useUkBannerState() { + return useAtom(hideUKBannerAtom) +}