packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx
import { Fragment } from 'react'
import { useRouter } from 'next/router'
import { Popover, Transition } from '@headlessui/react'
import { MenuIcon, XIcon } from '@heroicons/react/outline'
import { useTranslations } from 'next-intl'
import Grid from '@tw/Grid'
import ForumIcon from '@icons/ForumIcon'
import TwitterIcon from '@icons/TwitterIcon'
import DiscordIcon from '@icons/DiscordIcon'
import TelegramIcon from '@icons/TelegramIcon'
import DocumentTextIcon from '@icons/DocsIcon'
import { Wallet } from '@components/Wallet'
import { SynapseLogoSvg, SynapseLogoWithTitleSvg } from './SynapseLogoSvg'
import { TopBarNavLink, checkIsRouteMatched } from './TopBarNavLink'
import {
DISCORD_URL,
SYNAPSE_DOCS_URL,
FORUM_URL,
LANDING_PATH,
TELEGRAM_URL,
TWITTER_URL,
} from '@/constants/urls'
import { NAVIGATION } from '@/constants/routes'
import { MoreButton } from './MoreButton'
import { PageFooter } from './PageFooter'
import { joinClassNames } from '@/utils/joinClassNames'
import {
MaintenanceBanners,
useMaintenance,
} from '@/components/Maintenance/Maintenance'
import { AnnouncementBanner } from '@/components/Maintenance/components/AnnouncementBanner'
import { LanguageSelector } from '@/components/LanguageSelector'
const wrapperClassNames = {
textColor: 'text-zinc-800 dark:text-zinc-200',
font: 'tracking-wide',
bgColor: 'bg-gradient-to-b',
bgGradient: 'from-white to-[hsl(235deg_75%_96%)]',
bgGradientDark: 'dark:from-black dark:to-[hsl(265deg_25%_7.5%)]',
// bgFrame: 'w-screen h-screen overflow-scroll', // TODO: Enable once wrapperStyle is removed
}
const TODO_REMOVE_wrapperStyle = {
background:
'radial-gradient(23.86% 33.62% at 50.97% 47.88%, rgba(255, 0, 255, 0.04) 0%, rgba(172, 143, 255, 0.04) 100%), #111111',
backgroundImage: `url('landingBg.svg')`,
backgroundSize: '800px',
backgroundPosition: 'center 150px',
backgroundRepeat: 'no-repeat',
}
export function LandingPageWrapper({ children }: { children: any }) {
return (
<div className="dark">
<div
className={joinClassNames(wrapperClassNames)}
style={TODO_REMOVE_wrapperStyle}
>
<AnnouncementBanner
bannerId="2024-12-11-hyperliquid"
bannerContent="Synapse now supports Hyperliquid. Bridge and deposit to your Hyperliquid account now!"
startDate={new Date('2024-12-11T18:45:09+00:00')}
endDate={new Date('2025-01-10T18:45:09+00:00')}
/>
<MaintenanceBanners />
<LandingNav />
{children}
<PageFooter />
</div>
</div>
)
}
export function LandingNav() {
const t = useTranslations('Nav')
return (
<Popover>
<div className="flex gap-4 place-content-between p-8 max-w-[1440px] m-auto">
<SynapseTitleLogo showText={true} />
<div className="lg:hidden">
<Popover.Button
data-test-id="mobile-navbar-button"
className="p-2 text-gray-400 rounded-md hover:bg-gray-800 focus:outline-none"
>
<span className="sr-only">{t('Open menu')}</span>
<MenuIcon className="w-8 h-8" aria-hidden="true" />
</Popover.Button>
</div>
<Popover.Group
as="nav"
className="flex-wrap justify-center hidden lg:flex"
data-test-id="desktop-nav"
>
<TopBarButtons />
</Popover.Group>
<div className="hidden lg:flex h-fit">
<div className="flex items-center space-x-2">
<LanguageSelector />
<Wallet />
<Popover className="relative">
{({ open }) => (
<>
<Popover.Button as="div" onMouseEnter={() => {}}>
<MoreButton open={open} />
</Popover.Button>
<PopoverPanelContainer className="-translate-x-full left-full">
<MoreInfoButtons />
<SocialButtons />
</PopoverPanelContainer>
</>
)}
</Popover>
</div>
</div>
</div>
<Transition
as={Fragment as any}
enter="duration-100 ease-out"
enterFrom=" opacity-0"
enterTo=" opacity-100"
leave="duration-75 ease-in"
leaveFrom=" opacity-100"
leaveTo=" opacity-0"
>
<Popover.Panel focus className="absolute top-0 z-10 w-screen">
<div
className="bg-bgLight"
// data-test-id="mobile-nav"
>
<div className="flex items-center px-4 pt-4 place-content-between">
<SynapseTitleLogo showText={true} />
<Popover.Button className="p-2 text-gray-400 rounded-md hover:bg-gray-900 focus:outline-none">
<span className="sr-only">{t('Close menu')}</span>
<XIcon className="w-8 h-8" aria-hidden="true" />
</Popover.Button>
</div>
<div className="flex flex-col gap-2 py-4" data-test-id="mobile-nav">
<MobileBarButtons />
</div>
<div className="flex items-center px-2 py-4 space-x-2 bg-white/10">
<LanguageSelector />
<Wallet />
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
)
}
export function PopoverPanelContainer({
children,
className,
}: {
children: any
className?: string
}) {
return (
<Transition
as={Fragment as any}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel
className={`
absolute z-10 left-1/2 transform-gpu
${className ?? '-translate-x-1/2'}
mt-3 w-screen max-w-xs sm:px-0
`}
>
<div className="overflow-hidden rounded-md shadow-xl">
<div className="relative grid gap-3 bg-bgLight px-2.5 py-3 sm:p-2">
{children}
</div>
</div>
</Popover.Panel>
</Transition>
)
}
function TopBarButtons() {
const t = useTranslations('Nav')
const topBarNavLinks = Object.entries(NAVIGATION).map(([key, value]) => (
<TopBarNavLink
key={key}
to={value.path}
labelText={t(value.text)}
match={value.match}
/>
))
return <>{topBarNavLinks}</>
}
function MoreInfoButtons() {
return (
<>
<MoreInfoItem
className="mdl:hidden"
to={NAVIGATION.Analytics.path}
labelText={NAVIGATION.Analytics.text}
description="See preliminary analytics of the bridge"
/>
<MoreInfoItem
to={NAVIGATION.SYN.path}
labelText={NAVIGATION.SYN.text}
description="View $SYN related information such as contract addresses"
/>
</>
)
}
function SocialButtons() {
return (
<Grid cols={{ xs: 2, sm: 1 }} gapY={'1'}>
<MiniInfoItem
href={SYNAPSE_DOCS_URL}
labelText="Docs"
icon={<DocumentTextIcon className="inline w-5 mr-2 -ml-1 " />}
/>
<MiniInfoItem
href={DISCORD_URL}
labelText="Discord"
icon={<DiscordIcon className="inline w-5 mr-2 -ml-1" />}
/>
<MiniInfoItem
href={TELEGRAM_URL}
labelText="Telegram"
icon={<TelegramIcon className="inline w-5 mr-2 -ml-1 " />}
/>
<MiniInfoItem
href={TWITTER_URL}
labelText="Twitter"
icon={<TwitterIcon className="inline w-5 mr-2 -ml-1 " />}
/>
<MiniInfoItem
href={FORUM_URL}
labelText="Forum"
icon={<ForumIcon className="inline w-5 mr-2 -ml-1" />}
/>
</Grid>
)
}
function MobileBarButtons() {
const mobileBarItems = Object.entries(NAVIGATION).map(([key, value]) => (
<MobileBarItem
key={key}
to={value.path}
labelText={value.text}
match={value.match}
/>
))
return <>{mobileBarItems}</>
}
function MobileBarItem({
to,
labelText,
match,
}: {
to: string
labelText: string
match?: string | { startsWith: string }
}) {
const router = useRouter()
const isRouteMatched = checkIsRouteMatched(router, match)
const isInternal = to[0] === '/' || to[0] === '#'
return (
<a
key={labelText}
href={to}
target={isInternal ? undefined : '_blank'}
className={`
px-4 py-2 text-2xl font-medium text-white
${isRouteMatched ? 'text-opacity-100' : 'text-opacity-30'}
`}
>
{labelText}
</a>
)
}
function MoreInfoItem({
to,
labelText,
description,
className,
}: {
to: string
labelText: string
description: string
className?: string
}) {
return (
<a
key={labelText}
href={to}
target={to[0] === '/' ? undefined : '_blank'}
className={`block px-3 pt-2 pb-2 rounded-md hover:bg-white hover:bg-opacity-10 ${className}`}
>
<p className="text-base font-medium text-white">{labelText}</p>
<p className="hidden mt-1 text-sm text-white text-opacity-60 md:block">
{description}
</p>
</a>
)
}
function MiniInfoItem({
href,
icon,
labelText,
}: {
href: string
icon: JSX.Element
labelText: string
}) {
return (
<a
key={labelText}
href={href}
className="block px-3 pt-1 pb-2 text-sm rounded-md group"
target="_blank"
>
<div>
<p className="text-base text-white text-opacity-40 group-hover:text-white">
{icon}
<span className="mt-1">{labelText}</span>
</p>
</div>
</a>
)
}
export function SynapseTitleLogo({ showText }: { showText: boolean }) {
return (
<a href={LANDING_PATH}>
{showText ? <SynapseLogoWithTitleSvg /> : <SynapseLogoSvg />}
</a>
)
}