Commit 0bf7b920 authored by eddie's avatar eddie Committed by GitHub

feat: move about content into landing page (#5734)

* feat: move about content into landing page

* fix: delete unused image files

* fix: remove unused components

* fix: pointer events on title and subtitle

* feat: add about footer to landing page

* fix: simplify css in Landing Page

* feat(moonpay): moonpay ip checks to determine if the user can access the fiat onramp (#10)

* feat(moonpay): useFiatOnrampAvailable

* feat(moonpay): ip check with moonpay for buy crypto availability

* add error state and clear up some of the sequence of logic

* add button-specific spinner, put the ... menu button behind the feature flag

* hide ... menu option if onramp is unavailable

* add live publishable moonpay key

* add initial FoR hype border flash to announcement acknowledgment

* remove ... menu access to FoR feature

* add tooltip and external link to info icon

* nicer error display

* add stale market to ack

* pr feedback from zzmp

* fix really weird react bug

* ts fix and clear timeout

* pairing staleness handler w/ zzmp

* add back feature flag
Co-authored-by: default avatarJordan Frankfurt <jordanwfrankfurt@gmail.com>
parent 283479f7
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
import { BookOpen, Globe, Heart, Twitter } from 'react-feather'
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
const Footer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: 48px;
max-width: 1440px;
`
const FooterLinks = styled.div`
display: grid;
grid-template-columns: 1fr;
gap: 12px;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media screen and (min-width: ${BREAKPOINTS.lg}px) {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
`
const FooterLink = styled.a`
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
color: ${({ theme }) => theme.textPrimary};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
font-size: 16px;
line-height: 20px;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
text-decoration: none;
svg {
color: ${({ theme }) => theme.textSecondary};
stroke-width: 1.5;
}
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} border`};
&:hover {
border: 1px solid ${({ theme }) => theme.textTertiary};
}
@media screen and (min-width: ${BREAKPOINTS.md}px) {
font-size: 20px;
line-height: 24px;
}
`
const Copyright = styled.span`
font-size: 16px;
line-height: 20px;
color: ${({ theme }) => theme.textTertiary};
`
export const AboutFooter = () => {
return (
<Footer>
<FooterLinks>
<TraceEvent events={[BrowserEvent.onClick]} name={EventName.ELEMENT_CLICKED} element={ElementName.SUPPORT_LINK}>
<FooterLink rel="noopener noreferrer" target="_blank" href="https://support.uniswap.org">
<Globe /> Support
</FooterLink>
</TraceEvent>
<TraceEvent events={[BrowserEvent.onClick]} name={EventName.ELEMENT_CLICKED} element={ElementName.TWITTER_LINK}>
<FooterLink rel="noopener noreferrer" target="_blank" href="https://twitter.com/uniswap">
<Twitter /> Twitter
</FooterLink>
</TraceEvent>
<TraceEvent events={[BrowserEvent.onClick]} name={EventName.ELEMENT_CLICKED} element={ElementName.BLOG_LINK}>
<FooterLink rel="noopener noreferrer" target="_blank" href="https://uniswap.org/blog">
<BookOpen /> Blog
</FooterLink>
</TraceEvent>
<TraceEvent events={[BrowserEvent.onClick]} name={EventName.ELEMENT_CLICKED} element={ElementName.CAREERS_LINK}>
<FooterLink rel="noopener noreferrer" target="_blank" href="https://boards.greenhouse.io/uniswaplabs">
<Heart /> Careers
</FooterLink>
</TraceEvent>
</FooterLinks>
<Copyright>© {new Date().getFullYear()} Uniswap Labs</Copyright>
</Footer>
)
}
......@@ -2,47 +2,59 @@ import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, EventName } from '@uniswap/analytics-events'
import { Link } from 'react-router-dom'
import { useIsDarkMode } from 'state/user/hooks'
import styled from 'styled-components/macro'
import styled, { DefaultTheme } from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
const DARK_MODE_GRADIENT = 'linear-gradient(180deg, rgba(19, 22, 27, 0.54) 0%, #13161b 100%)'
export enum CardType {
Primary = 'Primary',
Secondary = 'Secondary',
}
const StyledCard = styled.div<{ isDarkMode: boolean; backgroundImgSrc?: string }>`
const StyledCard = styled.div<{ isDarkMode: boolean; backgroundImgSrc?: string; type: CardType }>`
display: flex;
background: ${({ isDarkMode, backgroundImgSrc }) =>
background: ${({ isDarkMode, backgroundImgSrc, type, theme }) =>
isDarkMode
? `${DARK_MODE_GRADIENT} ${backgroundImgSrc ? `, url(${backgroundImgSrc})` : ''}`
: `url(${backgroundImgSrc})`};
? `${type === CardType.Primary ? theme.backgroundModule : theme.backgroundSurface} ${
backgroundImgSrc ? ` url(${backgroundImgSrc})` : ''
}`
: `${type === CardType.Primary ? 'white' : theme.backgroundModule} url(${backgroundImgSrc})`};
background-size: auto 100%;
background-position: right;
background-repeat: no-repeat;
background-origin: border-box;
flex-direction: column;
justify-content: space-between;
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
padding: 24px;
height: 200px;
height: 212px;
border-radius: 24px;
border: 1px solid ${({ theme, isDarkMode }) => (isDarkMode ? 'transparent' : theme.backgroundOutline)};
border: 1px solid ${({ theme, type }) => (type === CardType.Primary ? 'transparent' : theme.backgroundOutline)};
box-shadow: 0px 10px 24px 0px rgba(51, 53, 72, 0.04);
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} border`};
&:hover {
border: 1px solid ${({ theme, isDarkMode }) => (isDarkMode ? theme.backgroundOutline : theme.textTertiary)};
border: 1px solid ${({ theme, isDarkMode }) => (isDarkMode ? theme.backgroundInteractive : theme.textTertiary)};
}
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
height: ${({ backgroundImgSrc }) => (backgroundImgSrc ? 360 : 200)}px;
padding: 40px;
height: ${({ backgroundImgSrc }) => (backgroundImgSrc ? 360 : 260)}px;
}
@media screen and (min-width: ${BREAKPOINTS.xl}px) {
padding: 32px;
}
`
const TitleRow = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`
const CardTitle = styled.div`
font-size: 20px;
line-height: 28px;
font-weight: 500;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
font-size: 20px;
line-height: 28px;
}
font-weight: 600;
@media screen and (min-width: ${BREAKPOINTS.lg}px) {
font-size: 28px;
......@@ -50,40 +62,70 @@ const CardTitle = styled.div`
}
`
const CardDescription = styled.div`
font-size: 14px;
const getCardDescriptionColor = (type: CardType, theme: DefaultTheme) => {
switch (type) {
case CardType.Secondary:
return theme.textSecondary
default:
return theme.textPrimary
}
}
const CardDescription = styled.div<{ type: CardType }>`
display: flex;
flex-direction: column;
font-size: 16px;
line-height: 20px;
color: ${({ theme, type }) => getCardDescriptionColor(type, theme)};
padding: 0 40px 0 0;
max-width: 480px;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
@media screen and (min-width: ${BREAKPOINTS.xl}px) {
font-size: 20px;
line-height: 28px;
max-width: 480px;
}
`
@media screen and (min-width: ${BREAKPOINTS.lg}px) {
font-size: 20px;
line-height: 28px;
const CardCTA = styled(CardDescription)`
color: ${({ theme }) => theme.accentAction};
font-weight: 500;
margin: 24px 0 0;
cursor: pointer;
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} opacity`};
&:hover {
opacity: 0.6;
}
`
const Card = ({
type = CardType.Primary,
title,
description,
cta,
to,
external,
backgroundImgSrc,
icon,
elementName,
}: {
type?: CardType
title: string
description: string
cta?: string
to: string
external?: boolean
backgroundImgSrc?: string
elementName: string
icon?: React.ReactNode
elementName?: string
}) => {
const isDarkMode = useIsDarkMode()
return (
<TraceEvent events={[BrowserEvent.onClick]} name={EventName.ELEMENT_CLICKED} element={elementName}>
<StyledCard
type={type}
as={external ? 'a' : Link}
to={external ? undefined : to}
href={external ? to : undefined}
......@@ -92,8 +134,14 @@ const Card = ({
isDarkMode={isDarkMode}
backgroundImgSrc={backgroundImgSrc}
>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
<TitleRow>
<CardTitle>{title}</CardTitle>
{icon}
</TitleRow>
<CardDescription type={type}>
{description}
<CardCTA type={type}>{cta}</CardCTA>
</CardDescription>
</StyledCard>
</TraceEvent>
)
......
import { ButtonEmpty } from 'components/Button'
import { useIsDarkMode } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import meshSrc from './images/Mesh.png'
const DARK_MODE_GRADIENT = 'radial-gradient(101.8% 4091.31% at 0% 0%, #4673FA 0%, #9646FA 100%)'
const Banner = styled.div<{ isDarkMode: boolean }>`
height: 340px;
width: 100%;
border-radius: 32px;
max-width: 1440px;
margin: 80px 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 32px 48px;
box-shadow: 0px 10px 24px rgba(51, 53, 72, 0.04);
background: ${({ isDarkMode }) =>
isDarkMode
? `url(${meshSrc}), ${DARK_MODE_GRADIENT}`
: `url(${meshSrc}), linear-gradient(93.06deg, #FF00C7 2.66%, #FF9FFB 98.99%);`};
@media screen and (min-width: ${BREAKPOINTS.lg}px) {
height: 140px;
flex-direction: row;
}
`
const TextContainer = styled.div`
color: white;
display: flex;
flex: 1;
flex-direction: column;
`
const HeaderText = styled.div`
font-weight: 700;
font-size: 28px;
line-height: 36px;
@media screen and (min-width: ${BREAKPOINTS.xl}px) {
font-size: 28px;
line-height: 36px;
}
`
const DescriptionText = styled.div`
margin: 10px 10px 0 0;
font-weight: 500;
font-size: 16px;
line-height: 20px;
@media screen and (min-width: ${BREAKPOINTS.xl}px) {
font-size: 20px;
line-height: 28px;
}
`
const BannerButtonContainer = styled.div`
width: 100%;
display: flex;
align-items: center;
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} opacity`};
&:hover {
opacity: 0.6;
}
@media screen and (min-width: ${BREAKPOINTS.lg}px) {
width: auto;
}
`
const BannerButton = styled(ButtonEmpty)`
color: white;
border: 1px solid white;
`
const ProtocolBanner = () => {
const isDarkMode = useIsDarkMode()
return (
<Banner isDarkMode={isDarkMode}>
<TextContainer>
<HeaderText>Powered by the Uniswap Protocol</HeaderText>
<DescriptionText>
The leading decentralized crypto trading protocol, governed by a global community.
</DescriptionText>
</TextContainer>
<BannerButtonContainer>
<BannerButton width="200px" as="a" href="https://uniswap.org" rel="noopener noreferrer" target="_blank">
Learn more
</BannerButton>
</BannerButtonContainer>
</Banner>
)
}
export default ProtocolBanner
import { ElementName } from '@uniswap/analytics-events'
import { DollarSign, Terminal } from 'react-feather'
import styled from 'styled-components/macro'
import { lightTheme } from 'theme/colors'
import darkArrowImgSrc from './images/aboutArrowDark.png'
import lightArrowImgSrc from './images/aboutArrowLight.png'
import darkDollarImgSrc from './images/aboutDollarDark.png'
import darkTerminalImgSrc from './images/aboutTerminalDark.png'
import nftCardImgSrc from './images/nftCard.png'
import swapCardImgSrc from './images/swapCard.png'
export const MAIN_CARDS = [
{
to: '/swap',
title: 'Swap tokens',
description: 'Buy, sell, and explore tokens on Ethereum, Polygon, Optimism, and more.',
cta: 'Trade Tokens',
darkBackgroundImgSrc: swapCardImgSrc,
lightBackgroundImgSrc: swapCardImgSrc,
elementName: ElementName.ABOUT_PAGE_SWAP_CARD,
},
{
to: '/nfts',
title: 'Trade NFTs',
description: 'Buy and sell NFTs across marketplaces to find more listings at better prices.',
cta: 'Explore NFTs',
darkBackgroundImgSrc: nftCardImgSrc,
lightBackgroundImgSrc: nftCardImgSrc,
elementName: ElementName.ABOUT_PAGE_NFTS_CARD,
},
]
const StyledCardLogo = styled.img`
min-width: 20px;
min-height: 20px;
max-height: 48px;
max-width: 48px;
`
export const MORE_CARDS = [
{
to: 'https://info.uniswap.org',
external: true,
title: 'Analytics',
description: 'View, track and analyze Uniswap Protocol analytics.',
lightIcon: <StyledCardLogo src={lightArrowImgSrc} alt="Analytics" />,
darkIcon: <StyledCardLogo src={darkArrowImgSrc} alt="Analytics" />,
cta: 'Explore data',
elementName: ElementName.ABOUT_PAGE_ANALYTICS_CARD,
},
{
to: '/pool',
title: 'Earn',
description: 'Provide liquidity to pools on Uniswap and earn fees on swaps.',
lightIcon: <DollarSign color={lightTheme.textTertiary} size={48} />,
darkIcon: <StyledCardLogo src={darkDollarImgSrc} alt="Earn" />,
cta: 'Provide liquidity',
elementName: ElementName.ABOUT_PAGE_EARN_CARD,
},
{
to: 'https://docs.uniswap.org',
external: true,
title: 'Build dApps',
description: 'Build apps and tools on the largest DeFi protocol on Ethereum.',
lightIcon: <Terminal color={lightTheme.textTertiary} size={48} />,
darkIcon: <StyledCardLogo src={darkTerminalImgSrc} alt="Developers" />,
cta: 'Developer docs',
elementName: ElementName.ABOUT_PAGE_DEV_DOCS_CARD,
},
]
......@@ -4,6 +4,7 @@ import { RowFixed } from 'components/Row'
import { getChainInfo } from 'constants/chainInfo'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import useGasPrice from 'hooks/useGasPrice'
import { useIsLandingPage } from 'hooks/useIsLandingPage'
import { useIsNftPage } from 'hooks/useIsNftPage'
import useMachineTimeMs from 'hooks/useMachineTime'
import JSBI from 'jsbi'
......@@ -120,6 +121,7 @@ export default function Polling() {
const machineTime = useMachineTimeMs(NETWORK_HEALTH_CHECK_MS)
const blockTime = useCurrentBlockTimestamp()
const isNftPage = useIsNftPage()
const isLandingPage = useIsLandingPage()
const ethGasPrice = useGasPrice()
const priceGwei = ethGasPrice ? JSBI.divide(ethGasPrice, JSBI.BigInt(1000000000)) : undefined
......@@ -154,7 +156,7 @@ export default function Polling() {
return getExplorerLink(chainId, blockNumber.toString(), ExplorerDataType.BLOCK)
}, [blockNumber, chainId])
if (isNftPage) {
if (isNftPage || isLandingPage) {
return null
}
......
import { useLocation } from 'react-router-dom'
export function useIsLandingPage() {
const { pathname } = useLocation()
return pathname.endsWith('/')
}
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
const StyledStep = styled.div<{ selected: boolean }>`
cursor: pointer;
display: flex;
padding: 24px 0;
color: ${({ theme, selected }) => (selected ? theme.textPrimary : theme.textSecondary)};
font-size: 20px;
font-weight: 500;
line-height: 28px;
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} color`};
&:not(:last-of-type) {
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
}
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
font-size: 28px;
line-height: 36px;
}
`
const StepIndex = styled.span`
margin-right: 24px;
margin-left: 8px;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
margin-right: 36px;
}
`
const Step = ({
index,
title,
onSelect,
selected,
}: {
index: number
title: string
onSelect: () => void
selected: boolean
}) => {
return (
<StyledStep onClick={onSelect} onMouseEnter={onSelect} selected={selected}>
<StepIndex>{index + 1}</StepIndex>
{title}
</StyledStep>
)
}
export default Step
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
export const Title = styled.h1<{ isDarkMode: boolean }>`
color: transparent;
font-size: 48px;
font-weight: 500;
margin-bottom: 0px;
max-width: 800px;
background: ${({ isDarkMode }) =>
isDarkMode
? 'conic-gradient(from 180deg at 50% 50%, #FFF4CF 0deg, #EBFFBF 95.62deg, #E3CDFF 175.81deg, #FFCDF4 269.07deg, #FFFBEF 360deg);'
: 'linear-gradient(230.12deg, #8A80FF 37.26%, #FF7DE2 52.98%, #FF3998 68.06%)'};
background-clip: text;
-webkit-background-clip: text;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
font-size: 64px;
line-height: 72px;
}
`
export const SubTitle = styled.h2<{ isDarkMode?: boolean }>`
margin: 0;
font-weight: 500;
max-width: 340px;
color: transparent;
font-size: 20px;
line-height: 28px;
background: ${({ isDarkMode }) =>
isDarkMode
? 'conic-gradient(from 180deg at 50% 50%, #FFF4CF 0deg, #EBFFBF 95.62deg, #E3CDFF 175.81deg, #FFCDF4 269.07deg, #FFFBEF 360deg);'
: 'linear-gradient(230.12deg, #8A80FF 37.26%, #FF7DE2 52.98%, #FF3998 68.06%)'};
background-clip: text;
-webkit-background-clip: text;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
font-size: 36px;
line-height: 44px;
}
`
import { ElementName } from '@uniswap/analytics-events'
import darkNftCardImgSrc from './images/darkNftCard.png'
import darkSwapSrc from './images/darkSwap.png'
import darkSwapCardImgSrc from './images/darkSwapCard.png'
import darkWalletsSrc from './images/darkWallets.png'
import lightNftCardImgSrc from './images/lightNftCard.png'
import lightSwapSrc from './images/lightSwap.png'
import lightSwapCardImgSrc from './images/lightSwapCard.png'
import lightWalletsSrc from './images/lightWallets.png'
import tokens from './images/tokens.png'
export const CARDS = [
{
to: '/swap',
title: 'Swap tokens',
description: 'Buy, sell, and explore tokens on Ethereum, Polygon, Optimism, and more.',
darkBackgroundImgSrc: darkSwapCardImgSrc,
lightBackgroundImgSrc: lightSwapCardImgSrc,
elementName: ElementName.ABOUT_PAGE_SWAP_CARD,
},
{
to: '/nfts',
title: 'Trade NFTs',
description: 'Buy and sell NFTs across marketplaces to find more listings at better prices.',
darkBackgroundImgSrc: darkNftCardImgSrc,
lightBackgroundImgSrc: lightNftCardImgSrc,
elementName: ElementName.ABOUT_PAGE_NFTS_CARD,
},
]
export const STEPS = [
{
title: 'Connect a wallet',
description: 'Connect your preferred crypto wallet to the Uniswap Interface.',
lightImgSrc: lightWalletsSrc,
darkImgSrc: darkWalletsSrc,
},
{
title: 'Transfer crypto',
description: 'Trade crypto and NFTs through Uniswap’s platform',
lightImgSrc: tokens,
darkImgSrc: tokens,
},
{
title: 'Trade tokens and NFTs',
description: 'Trade crypto and NFTs through Uniswap’s platform',
lightImgSrc: lightSwapSrc,
darkImgSrc: darkSwapSrc,
},
]
import { Trace, TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName, PageName } from '@uniswap/analytics-events'
import { ButtonOutlined } from 'components/Button'
import { useLayoutEffect, useRef, useState } from 'react'
import { BookOpen, Globe, Heart, Twitter } from 'react-feather'
import { useIsDarkMode } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import Card from './Card'
import { CARDS, STEPS } from './constants'
import backgroundImgSrcDark from './images/About_BG_Dark.jpg'
import backgroundImgSrcLight from './images/About_BG_Light.jpg'
import Step from './Step'
import { SubTitle, Title } from './Title'
const Page = styled.div<{ isDarkMode: boolean; titleHeight: number }>`
position: relative;
width: 100%;
align-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding-top: calc(100vh - ${({ titleHeight }) => titleHeight + 200}px);
`
const PageBackground = styled.div<{ isDarkMode: boolean }>`
position: absolute;
width: 100%;
height: 100vh;
top: -${({ theme }) => theme.navHeight}px;
left: 0;
opacity: ${({ isDarkMode }) => (isDarkMode ? 0.4 : 0.2)};
background: ${({ isDarkMode }) => (isDarkMode ? `url(${backgroundImgSrcDark})` : `url(${backgroundImgSrcLight})`)};
-webkit-mask-image: linear-gradient(to bottom, black 40%, transparent 100%);
mask-image: linear-gradient(to bottom, black 40%, transparent 100%);
background-size: cover;
background-repeat: no-repeat;
`
const Panels = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
color: ${({ theme }) => theme.textPrimary};
font-size: 24px;
line-height: 36px;
gap: 24px;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
gap: 120px;
flex-direction: row;
align-items: center;
}
& > * {
flex: 1;
}
`
const Content = styled.div`
max-width: 1280px;
pointer-events: all;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
padding: 0px 16px 16px 16px;
gap: 48px;
z-index: 1;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
padding: 0px 80px 80px 80px;
gap: 96px;
}
`
const CardGrid = styled.div`
display: grid;
gap: 12px;
width: 100%;
grid-template-columns: 1fr;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
grid-template-columns: 1fr 1fr;
gap: 32px;
}
`
const InfoButton = styled(ButtonOutlined)`
font-size: 16px;
line-height: 20px;
padding: 12px;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
font-size: 20px;
line-height: 24px;
}
`
const ActionsContainer = styled.span`
display: flex;
gap: 16px;
width: 100%;
& > * {
flex: 1;
}
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
gap: 24px;
}
@media screen and (min-width: ${BREAKPOINTS.md}px) {
flex-direction: column;
gap: 24px;
}
@media screen and (min-width: ${BREAKPOINTS.lg}px) {
flex-direction: row;
}
`
const StepList = styled.div`
display: flex;
flex-direction: column;
`
const Intro = styled.div`
display: flex;
flex-direction: column;
gap: 32px;
`
const IntroCopy = styled.p`
font-size: 16px;
line-height: 24px;
margin: 0;
`
const ThumbnailContainer = styled.div`
align-self: center;
`
const Thumbnail = styled.img`
width: 100%;
`
const FooterLinks = styled.div`
display: grid;
grid-template-columns: 1fr;
gap: 12px;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media screen and (min-width: ${BREAKPOINTS.lg}px) {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
`
const FooterLink = styled.a`
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
color: ${({ theme }) => theme.textPrimary};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
font-size: 16px;
line-height: 20px;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
text-decoration: none;
svg {
color: ${({ theme }) => theme.textSecondary};
stroke-width: 1.5;
}
&:hover {
border: 1px solid ${({ theme }) => theme.textTertiary};
}
@media screen and (min-width: ${BREAKPOINTS.md}px) {
font-size: 20px;
line-height: 24px;
}
`
const Footer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: 48px;
`
const Copyright = styled.span`
font-size: 16px;
line-height: 20px;
color: ${({ theme }) => theme.textTertiary};
`
const WrappedExternalArrow = styled.span`
color: ${({ theme }) => theme.textTertiary};
margin-left: 4px;
`
export default function About() {
const isDarkMode = useIsDarkMode()
const titleRef = useRef<HTMLDivElement>(null)
const [titleHeight, setTitleHeight] = useState(0)
useLayoutEffect(() => {
if (titleRef.current) {
setTitleHeight(titleRef.current.scrollHeight)
}
}, [])
const [selectedStepIndex, setSelectedStepIndex] = useState(0)
const selectedStep = STEPS[selectedStepIndex]
const thumbnailImgSrc = isDarkMode ? selectedStep?.darkImgSrc : selectedStep?.lightImgSrc
return (
<Trace page={PageName.ABOUT_PAGE} shouldLogImpression>
<Page isDarkMode={isDarkMode} titleHeight={titleHeight}>
<Content>
<Title ref={titleRef} isDarkMode={isDarkMode}>
Uniswap is the leading on-chain marketplace for tokens and NFTs
</Title>
<Panels>
<div>
<SubTitle isDarkMode={isDarkMode}>Powered by the Uniswap Protocol</SubTitle>
</div>
<Intro>
<IntroCopy>The leading decentralized crypto trading protocol, governed by a global community</IntroCopy>
<ActionsContainer>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.LEGACY_LANDING_PAGE_LINK}
>
<InfoButton as="a" rel="noopener noreferrer" href="https://uniswap.org" target="_blank">
Learn more<WrappedExternalArrow></WrappedExternalArrow>
</InfoButton>
</TraceEvent>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.DOCS_LINK}
>
<InfoButton as="a" rel="noopener noreferrer" href="https://docs.uniswap.org" target="_blank">
Read docs<WrappedExternalArrow></WrappedExternalArrow>
</InfoButton>
</TraceEvent>
</ActionsContainer>
</Intro>
</Panels>
<CardGrid>
{CARDS.map(({ darkBackgroundImgSrc, lightBackgroundImgSrc, ...card }) => (
<Card
{...card}
backgroundImgSrc={isDarkMode ? darkBackgroundImgSrc : lightBackgroundImgSrc}
key={card.title}
/>
))}
</CardGrid>
<div>
<SubTitle isDarkMode={isDarkMode}>Get Started</SubTitle>
<Panels>
<ThumbnailContainer>
<Thumbnail alt="Thumbnail" src={thumbnailImgSrc} />
</ThumbnailContainer>
<StepList>
{STEPS.map((step, index) => (
<Step
selected={selectedStepIndex === index}
onSelect={() => setSelectedStepIndex(index)}
index={index}
key={step.title}
title={step.title}
/>
))}
</StepList>
</Panels>
</div>
<Footer>
<FooterLinks>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.SUPPORT_LINK}
>
<FooterLink rel="noopener noreferrer" target="_blank" href="https://support.uniswap.org">
<Globe /> Support
</FooterLink>
</TraceEvent>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.TWITTER_LINK}
>
<FooterLink rel="noopener noreferrer" target="_blank" href="https://twitter.com/uniswap">
<Twitter /> Twitter
</FooterLink>
</TraceEvent>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.BLOG_LINK}
>
<FooterLink rel="noopener noreferrer" target="_blank" href="https://uniswap.org/blog">
<BookOpen /> Blog
</FooterLink>
</TraceEvent>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.CAREERS_LINK}
>
<FooterLink rel="noopener noreferrer" target="_blank" href="https://boards.greenhouse.io/uniswaplabs">
<Heart /> Careers
</FooterLink>
</TraceEvent>
</FooterLinks>
<Copyright>© {new Date().getFullYear()} Uniswap Labs</Copyright>
</Footer>
</Content>
<PageBackground isDarkMode={isDarkMode} />
</Page>
</Trace>
)
}
......@@ -27,7 +27,6 @@ import Polling from '../components/Polling'
import Popups from '../components/Popups'
import { useIsExpertMode } from '../state/user/hooks'
import DarkModeQueryParamReader from '../theme/components/DarkModeQueryParamReader'
import About from './About'
import AddLiquidity from './AddLiquidity'
import { RedirectDuplicateTokenIds } from './AddLiquidity/redirects'
import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects'
......@@ -249,8 +248,6 @@ export default function App() {
<Route path="migrate/v2" element={<MigrateV2 />} />
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
<Route path="about" element={<About />} />
<Route
path="/nfts"
element={
......
import { Trans } from '@lingui/macro'
import { Trace, TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName, PageName } from '@uniswap/analytics-events'
import { AboutFooter } from 'components/About/AboutFooter'
import Card, { CardType } from 'components/About/Card'
import { MAIN_CARDS, MORE_CARDS } from 'components/About/constants'
import ProtocolBanner from 'components/About/ProtocolBanner'
import { BaseButton } from 'components/Button'
import { LandingRedirectVariant, useLandingRedirectFlag } from 'featureFlags/flags/landingRedirect'
import Swap from 'pages/Swap'
import { parse } from 'qs'
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { ArrowDownCircle } from 'react-feather'
import { useLocation, useNavigate } from 'react-router-dom'
import { Link as NativeLink } from 'react-router-dom'
import { useAppSelector } from 'state/hooks'
......@@ -13,30 +19,57 @@ import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import { Z_INDEX } from 'theme/zIndex'
const PageContainer = styled.div`
const PageContainer = styled.div<{ isDarkMode: boolean }>`
position: absolute;
top: 0;
padding: ${({ theme }) => theme.navHeight}px 0px 0px 0px;
width: 100%;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
scroll-behavior: smooth;
overflow-x: hidden;
height: ${({ theme }) => `calc(100vh - ${theme.navHeight + theme.mobileBottomBarHeight}px)`};
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
height: ${({ theme }) => `calc(100vh - ${theme.navHeight}px)`};
}
background: ${({ isDarkMode }) =>
isDarkMode
? 'linear-gradient(rgba(8, 10, 24, 0) 0%, rgb(8 10 24 / 100%) 45%)'
: 'linear-gradient(rgba(255, 255, 255, 0) 0%, rgb(255 255 255 /100%) 45%)'};
`
const Gradient = styled.div<{ isDarkMode: boolean }>`
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 0;
bottom: 0;
width: 100%;
min-height: 550px;
background: ${({ isDarkMode }) =>
isDarkMode
? 'linear-gradient(rgba(8, 10, 24, 0) 0%, rgb(8 10 24 / 100%) 45%)'
: 'linear-gradient(rgba(255, 255, 255, 0) 0%, rgb(255 255 255 /100%) 45%)'};
z-index: ${Z_INDEX.under_dropdown};
pointer-events: none;
height: ${({ theme }) => `calc(100vh - ${theme.mobileBottomBarHeight}px)`};
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
height: 100vh;
}
`
const GlowContainer = styled.div`
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 0;
bottom: 0;
width: 100%;
overflow-y: hidden;
height: ${({ theme }) => `calc(100vh - ${theme.mobileBottomBarHeight}px)`};
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
height: 100vh;
}
`
const Glow = styled.div`
......@@ -48,41 +81,39 @@ const Glow = styled.div`
border-radius: 24px;
max-width: 480px;
width: 100%;
height: 100%;
`
const ContentContainer = styled.div<{ isDarkMode: boolean }>`
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
justify-content: end;
width: 100%;
padding: 0 0 40px;
max-width: min(720px, 90%);
position: sticky;
bottom: 0;
min-height: 500px;
z-index: ${Z_INDEX.under_dropdown};
padding: 32px 0 80px;
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} opacity`};
height: ${({ theme }) => `calc(100vh - ${theme.navHeight + theme.mobileBottomBarHeight}px)`};
pointer-events: none;
* {
pointer-events: auto;
}
@media screen and (min-width: ${BREAKPOINTS.md}px) {
padding: 64px 0;
}
`
const TitleText = styled.h1<{ isDarkMode: boolean }>`
color: transparent;
font-size: 36px;
line-height: 44px;
font-weight: 500;
font-weight: 700;
text-align: center;
margin: 0 0 24px;
background: ${({ isDarkMode }) =>
isDarkMode
? 'linear-gradient(20deg, rgba(255, 244, 207, 1) 10%, rgba(255, 87, 218, 1) 100%)'
: 'linear-gradient(10deg, rgba(255,79,184,1) 0%, rgba(255,159,251,1) 100%)'};
background-clip: text;
-webkit-background-clip: text;
......@@ -123,7 +154,7 @@ const LandingButton = styled(BaseButton)`
`
const ButtonCTA = styled(LandingButton)`
background: linear-gradient(10deg, rgba(255, 0, 199, 1) 0%, rgba(255, 159, 251, 1) 100%);
background: linear-gradient(93.06deg, #ff00c7 2.66%, #ff9ffb 98.99%);
border: none;
color: ${({ theme }) => theme.white};
transition: ${({ theme }) => `all ${theme.transition.duration.medium} ${theme.transition.timing.ease}`};
......@@ -147,6 +178,84 @@ const ButtonCTAText = styled.p`
const ActionsContainer = styled.span`
max-width: 300px;
width: 100%;
pointer-events: auto;
`
const LearnMoreContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.textTertiary};
cursor: pointer;
font-size: 20px;
font-weight: 600;
margin: 36px 0 0;
display: flex;
visibility: hidden;
pointer-events: auto;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
visibility: visible;
}
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} opacity`};
&:hover {
opacity: 0.6;
}
`
const LearnMoreArrow = styled(ArrowDownCircle)`
margin-left: 14px;
size: 20px;
`
const AboutContentContainer = styled.div<{ isDarkMode: boolean }>`
display: flex;
flex-direction: column;
align-items: center;
padding: 0 24px 5rem;
width: 100%;
background: ${({ isDarkMode }) =>
isDarkMode
? 'linear-gradient(179.82deg, rgba(0, 0, 0, 0) 0.16%, #050026 99.85%)'
: 'linear-gradient(179.82deg, rgba(255, 255, 255, 0) 0.16%, #eaeaea 99.85%)'};
@media screen and (min-width: ${BREAKPOINTS.md}px) {
padding: 0 96px 5rem;
}
`
const CardGrid = styled.div<{ cols: number }>`
display: grid;
gap: 12px;
width: 100%;
padding: 24px 0 0;
max-width: 1440px;
scroll-margin: ${({ theme }) => `${theme.navHeight}px 0 0`};
grid-template-columns: 1fr;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
// At this screen size, we show up to 2 columns.
grid-template-columns: ${({ cols }) =>
Array.from(Array(cols === 2 ? 2 : 1))
.map(() => '1fr')
.join(' ')};
gap: 32px;
}
@media screen and (min-width: ${BREAKPOINTS.lg}px) {
// at this screen size, always show the max number of columns
grid-template-columns: ${({ cols }) =>
Array.from(Array(cols))
.map(() => '1fr')
.join(' ')};
gap: 32px;
}
`
const LandingSwapContainer = styled.div`
height: ${({ theme }) => `calc(100vh - ${theme.mobileBottomBarHeight}px)`};
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
`
const LandingSwap = styled(Swap)`
......@@ -169,16 +278,11 @@ const Link = styled(NativeLink)`
export default function Landing() {
const isDarkMode = useIsDarkMode()
const cardsRef = useRef<HTMLDivElement>(null)
const location = useLocation()
const isOpen = location.pathname === '/'
useEffect(() => {
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = 'auto'
}
}, [])
const [showContent, setShowContent] = useState(false)
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
const landingRedirectFlag = useLandingRedirectFlag()
......@@ -207,18 +311,22 @@ export default function Landing() {
return (
<Trace page={PageName.LANDING_PAGE} shouldLogImpression>
{showContent && (
<PageContainer>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.LANDING_PAGE_SWAP_ELEMENT}
>
<Link to="/swap">
<LandingSwap />
</Link>
</TraceEvent>
<Glow />
<PageContainer isDarkMode={isDarkMode}>
<LandingSwapContainer>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.LANDING_PAGE_SWAP_ELEMENT}
>
<Link to="/swap">
<LandingSwap />
</Link>
</TraceEvent>
</LandingSwapContainer>
<Gradient isDarkMode={isDarkMode} />
<GlowContainer>
<Glow />
</GlowContainer>
<ContentContainer isDarkMode={isDarkMode}>
<TitleText isDarkMode={isDarkMode}>Trade crypto & NFTs with confidence</TitleText>
<SubTextContainer>
......@@ -235,7 +343,33 @@ export default function Landing() {
</ButtonCTA>
</TraceEvent>
</ActionsContainer>
<LearnMoreContainer
onClick={() => {
cardsRef?.current?.scrollIntoView({ behavior: 'smooth' })
}}
>
<Trans>Learn more</Trans>
<LearnMoreArrow />
</LearnMoreContainer>
</ContentContainer>
<AboutContentContainer isDarkMode={isDarkMode}>
<CardGrid cols={2} ref={cardsRef}>
{MAIN_CARDS.map(({ darkBackgroundImgSrc, lightBackgroundImgSrc, ...card }) => (
<Card
{...card}
backgroundImgSrc={isDarkMode ? darkBackgroundImgSrc : lightBackgroundImgSrc}
key={card.title}
/>
))}
</CardGrid>
<CardGrid cols={3}>
{MORE_CARDS.map(({ darkIcon, lightIcon, ...card }) => (
<Card {...card} icon={isDarkMode ? darkIcon : lightIcon} key={card.title} type={CardType.Secondary} />
))}
</CardGrid>
<ProtocolBanner />
<AboutFooter />
</AboutContentContainer>
</PageContainer>
)}
</Trace>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment