Commit 8cbd111e authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

Merge pull request #8 from Uniswap/merge-upstream

chore: Merge upstream
parents 404775e8 55ffcbd4
......@@ -3,8 +3,8 @@ describe('Redirect', () => {
cy.visit('/create-proposal')
cy.url().should('match', /\/vote\/create-proposal/)
})
it('should redirect to /swap when visiting nonexist url', () => {
it('should redirect to /not-found when visiting nonexist url', () => {
cy.visit('/none-exist-url')
cy.url().should('match', /\/swap/)
cy.url().should('match', /\/not-found/)
})
})
......@@ -9,14 +9,27 @@ export { default as LoadingButtonSpinner } from './LoadingButtonSpinner'
type ButtonProps = Omit<ButtonPropsOriginal, 'css'>
export const BaseButton = styled(RebassButton)<
{
padding?: string
width?: string
$borderRadius?: string
altDisabledStyle?: boolean
} & ButtonProps
>`
const ButtonOverlay = styled.div`
background-color: transparent;
bottom: 0;
border-radius: inherit;
height: 100%;
left: 0;
position: absolute;
right: 0;
top: 0;
transition: 150ms ease background-color;
width: 100%;
`
type BaseButtonProps = {
padding?: string
width?: string
$borderRadius?: string
altDisabledStyle?: boolean
} & ButtonProps
export const BaseButton = styled(RebassButton)<BaseButtonProps>`
padding: ${({ padding }) => padding ?? '16px'};
width: ${({ width }) => width ?? '100%'};
font-weight: 500;
......@@ -81,7 +94,14 @@ export const ButtonPrimary = styled(BaseButton)`
}
`
export const ButtonLight = styled(BaseButton)`
export const SmallButtonPrimary = styled(ButtonPrimary)`
width: auto;
font-size: 16px;
padding: 10px 16px;
border-radius: 12px;
`
const BaseButtonLight = styled(BaseButton)`
background-color: ${({ theme }) => theme.accentActionSoft};
color: ${({ theme }) => theme.accentAction};
font-size: 20px;
......@@ -98,6 +118,19 @@ export const ButtonLight = styled(BaseButton)`
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
}
:hover {
${ButtonOverlay} {
background-color: ${({ theme }) => theme.stateOverlayHover};
}
}
:active {
${ButtonOverlay} {
background-color: ${({ theme }) => theme.stateOverlayPressed};
}
}
:disabled {
opacity: 0.4;
:hover {
......@@ -378,7 +411,7 @@ export enum ButtonEmphasis {
warning,
destructive,
}
interface BaseButtonProps {
interface BaseThemeButtonProps {
size: ButtonSize
emphasis: ButtonEmphasis
}
......@@ -456,19 +489,8 @@ function pickThemeButtonTextColor({ theme, emphasis }: { theme: DefaultTheme; em
return theme.textPrimary
}
}
const ButtonOverlay = styled.div`
background-color: transparent;
bottom: 0;
border-radius: inherit;
height: 100%;
left: 0;
position: absolute;
right: 0;
top: 0;
transition: 150ms ease background-color;
width: 100%;
`
const BaseThemeButton = styled.button<BaseButtonProps>`
const BaseThemeButton = styled.button<BaseThemeButtonProps>`
align-items: center;
background-color: ${pickThemeButtonBackgroundColor};
border-radius: 16px;
......@@ -515,7 +537,7 @@ const BaseThemeButton = styled.button<BaseButtonProps>`
}
`
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseButtonProps {}
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseThemeButtonProps {}
export const ThemeButton = ({ children, ...rest }: ThemeButtonProps) => {
return (
......@@ -525,3 +547,12 @@ export const ThemeButton = ({ children, ...rest }: ThemeButtonProps) => {
</BaseThemeButton>
)
}
export const ButtonLight = ({ children, ...rest }: BaseButtonProps) => {
return (
<BaseButtonLight {...rest}>
<ButtonOverlay />
{children}
</BaseButtonLight>
)
}
import { Trans } from '@lingui/macro'
import * as Sentry from '@sentry/react'
import { ButtonLight, ButtonPrimary } from 'components/Button'
import { ButtonLight, SmallButtonPrimary } from 'components/Button'
import { ChevronUpIcon } from 'nft/components/icons'
import { useIsMobile } from 'nft/hooks'
import React, { PropsWithChildren, useState } from 'react'
......@@ -23,13 +23,6 @@ const BodyWrapper = styled.div<{ margin?: string }>`
padding: 1rem;
`
const SmallButtonPrimary = styled(ButtonPrimary)`
width: auto;
font-size: 16px;
padding: 10px 16px;
border-radius: 12px;
`
const SmallButtonLight = styled(ButtonLight)`
font-size: 16px;
padding: 10px 16px;
......
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
import { LandingRedirectVariant, useLandingRedirectFlag } from 'featureFlags/flags/landingRedirect'
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
......@@ -210,10 +210,10 @@ export default function FeatureFlagModal() {
label="Permit 2 / Universal Router"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useFiatOnrampFlag()}
featureFlag={FeatureFlag.fiatOnramp}
label="Fiat on-ramp"
variant={LandingRedirectVariant}
value={useLandingRedirectFlag()}
featureFlag={FeatureFlag.landingRedirect}
label="Landing Page Redirect"
/>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption
......
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import fiatMaskUrl from 'assets/svg/fiat_mask.svg'
import { BaseVariant } from 'featureFlags'
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
import { useCallback, useEffect, useState } from 'react'
import { X } from 'react-feather'
import { useToggleWalletDropdown } from 'state/application/hooks'
......@@ -116,13 +114,12 @@ export function FiatOnrampAnnouncement() {
toggleWalletDropdown()
acknowledge({ user: true })
}, [acknowledge, toggleWalletDropdown])
const fiatOnrampFlag = useFiatOnrampFlag()
const openModal = useAppSelector((state) => state.application.openModal)
if (
!account ||
acks?.user ||
fiatOnrampFlag === BaseVariant.Control ||
locallyDismissed ||
sessionStorage.getItem(ANNOUNCEMENT_DISMISSED) ||
acks.renderCount >= MAX_RENDER_COUNT ||
......
// eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro'
import { t, Trans } from '@lingui/macro'
import { sendAnalyticsEvent, Trace, TraceEvent, useTrace } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName, SectionName } from '@uniswap/analytics-events'
import clsx from 'clsx'
......@@ -156,9 +156,9 @@ export const SearchBar = () => {
>
<Row
className={clsx(
` ${styles.nftSearchBar} ${!isOpen && !isMobile && magicalGradientOnHover} ${
isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)
} `
styles.nftSearchBar,
!isOpen && !isMobile && magicalGradientOnHover,
isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)
)}
borderRadius={isOpen || isMobileOrTablet ? undefined : '12'}
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
......@@ -182,18 +182,23 @@ export const SearchBar = () => {
element={ElementName.NAVBAR_SEARCH_INPUT}
properties={{ ...trace }}
>
<Box
as="input"
placeholder={placeholderText}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
!isOpen && toggleOpen()
setSearchValue(event.target.value)
}}
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
className={`${styles.searchBarInput} ${styles.searchContentLeftAlign}`}
value={searchValue}
ref={inputRef}
width="full"
<Trans
id={placeholderText}
render={({ translation }) => (
<Box
as="input"
placeholder={translation as string}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
!isOpen && toggleOpen()
setSearchValue(event.target.value)
}}
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
className={`${styles.searchBarInput} ${styles.searchContentLeftAlign}`}
value={searchValue}
ref={inputRef}
width="full"
/>
)}
/>
</TraceEvent>
{!isOpen && <KeyShortCut>/</KeyShortCut>}
......
......@@ -9,14 +9,12 @@ import { AutoColumn } from '../Column'
import ClaimPopup from './ClaimPopup'
import PopupItem from './PopupItem'
const MobilePopupWrapper = styled.div<{ height: string | number }>`
const MobilePopupWrapper = styled.div`
position: relative;
max-width: 100%;
height: ${({ height }) => height};
margin: ${({ height }) => (height ? '0 auto;' : 0)};
margin-bottom: ${({ height }) => (height ? '20px' : 0)};
margin: 0 auto;
display: none;
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
display: block;
padding-top: 20px;
......@@ -74,16 +72,18 @@ export default function Popups() {
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
))}
</FixedPopupColumn>
<MobilePopupWrapper height={activePopups?.length > 0 ? 'fit-content' : 0}>
<MobilePopupInner>
{activePopups // reverse so new items up front
.slice(0)
.reverse()
.map((item) => (
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
))}
</MobilePopupInner>
</MobilePopupWrapper>
{activePopups?.length > 0 && (
<MobilePopupWrapper>
<MobilePopupInner>
{activePopups // reverse so new items up front
.slice(0)
.reverse()
.map((item) => (
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
))}
</MobilePopupInner>
</MobilePopupWrapper>
)}
</>
)
}
......@@ -4,4 +4,4 @@ export const LARGE_MEDIA_BREAKPOINT = '840px'
export const MEDIUM_MEDIA_BREAKPOINT = '720px'
export const SMALL_MEDIA_BREAKPOINT = '540px'
export const MOBILE_MEDIA_BREAKPOINT = '420px'
export const SMALL_MOBILE_MEDIA_BREAKPOINT = '390px'
// export const SMALL_MOBILE_MEDIA_BREAKPOINT = '390px'
......@@ -2,12 +2,8 @@ import { useWeb3React } from '@web3-react/core'
import AddressClaimModal from 'components/claim/AddressClaimModal'
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
import FiatOnrampModal from 'components/FiatOnrampModal'
import { BaseVariant } from 'featureFlags'
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
import NftExploreBanner from 'nft/components/nftExploreBanner/NftExploreBanner'
import { lazy } from 'react'
import { useLocation } from 'react-router-dom'
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
......@@ -20,13 +16,6 @@ export default function TopLevelModals() {
const addressClaimToggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
const blockedAccountModalOpen = useModalIsOpen(ApplicationModal.BLOCKED_ACCOUNT)
const { account } = useWeb3React()
const location = useLocation()
const fiatOnrampFlagEnabled = useFiatOnrampFlag() === BaseVariant.Enabled
const pageShowsNftPromoBanner =
!fiatOnrampFlagEnabled &&
(location.pathname.startsWith('/swap') ||
location.pathname.startsWith('/tokens') ||
location.pathname.startsWith('/pool'))
useAccountRiskCheck(account)
const accountBlocked = Boolean(blockedAccountModalOpen && account)
return (
......@@ -36,8 +25,7 @@ export default function TopLevelModals() {
<Bag />
<TransactionCompleteModal />
<AirdropModal />
{pageShowsNftPromoBanner && <NftExploreBanner />}
{fiatOnrampFlagEnabled && <FiatOnrampModal />}
<FiatOnrampModal />
</>
)
}
......@@ -7,8 +7,6 @@ import Tooltip from 'components/Tooltip'
import { getConnection } from 'connection/utils'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { BaseVariant } from 'featureFlags'
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
import useCopyClipboard from 'hooks/useCopyClipboard'
import useStablecoinPrice from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
......@@ -228,8 +226,6 @@ const AuthenticatedHeader = () => {
closeModal()
}, [clearCollectionFilters, closeModal, navigate, resetSellAssets, setSellPageState])
const fiatOnrampFlag = useFiatOnrampFlag()
// animate the border of the buy crypto button when a user navigates here from the feature announcement
// can be removed when components/FiatOnrampAnnouncment.tsx is no longer used
const [acknowledgements, acknowledge] = useFiatOnrampAck()
......@@ -316,44 +312,42 @@ const AuthenticatedHeader = () => {
>
<Trans>View and sell NFTs</Trans>
</ProfileButton>
{fiatOnrampFlag === BaseVariant.Enabled && (
<>
<BuyCryptoButton
$animateBorder={animateBuyCryptoButtonBorder}
size={ButtonSize.medium}
emphasis={ButtonEmphasis.medium}
onClick={handleBuyCryptoClick}
disabled={disableBuyCryptoButton}
>
{error ? (
<ThemedText.BodyPrimary>{error}</ThemedText.BodyPrimary>
) : (
<>
{fiatOnrampAvailabilityLoading ? <StyledLoadingButtonSpinner /> : <CreditCard />}{' '}
<Trans>Buy crypto</Trans>
</>
)}
</BuyCryptoButton>
{Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && (
<FiatOnrampNotAvailableText marginTop="8px">
<Trans>Not available in your region</Trans>
<Tooltip
show={showFiatOnrampUnavailableTooltip}
text={<Trans>Moonpay is not available in some regions. Click to learn more.</Trans>}
>
<FiatOnrampAvailabilityExternalLink
onMouseEnter={openFiatOnrampUnavailableTooltip}
onMouseLeave={closeFiatOnrampUnavailableTooltip}
style={{ color: 'inherit' }}
href="https://support.uniswap.org/hc/en-us/articles/10966551707533-Why-is-MoonPay-not-supported-in-my-region-"
>
<StyledInfoIcon />
</FiatOnrampAvailabilityExternalLink>
</Tooltip>
</FiatOnrampNotAvailableText>
<>
<BuyCryptoButton
$animateBorder={animateBuyCryptoButtonBorder}
size={ButtonSize.medium}
emphasis={ButtonEmphasis.medium}
onClick={handleBuyCryptoClick}
disabled={disableBuyCryptoButton}
>
{error ? (
<ThemedText.BodyPrimary>{error}</ThemedText.BodyPrimary>
) : (
<>
{fiatOnrampAvailabilityLoading ? <StyledLoadingButtonSpinner /> : <CreditCard />}{' '}
<Trans>Buy crypto</Trans>
</>
)}
</>
)}
</BuyCryptoButton>
{Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && (
<FiatOnrampNotAvailableText marginTop="8px">
<Trans>Not available in your region</Trans>
<Tooltip
show={showFiatOnrampUnavailableTooltip}
text={<Trans>Moonpay is not available in some regions. Click to learn more.</Trans>}
>
<FiatOnrampAvailabilityExternalLink
onMouseEnter={openFiatOnrampUnavailableTooltip}
onMouseLeave={closeFiatOnrampUnavailableTooltip}
style={{ color: 'inherit' }}
href="https://support.uniswap.org/hc/en-us/articles/10966551707533-Why-is-MoonPay-not-supported-in-my-region-"
>
<StyledInfoIcon />
</FiatOnrampAvailabilityExternalLink>
</Tooltip>
</FiatOnrampNotAvailableText>
)}
</>
{isUnclaimed && (
<UNIButton onClick={openClaimModal} size={ButtonSize.medium} emphasis={ButtonEmphasis.medium}>
<Trans>Claim</Trans> {unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} <Trans>reward</Trans>
......
......@@ -110,19 +110,10 @@ interface SwapDetailsInlineProps {
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
syncing: boolean
loading: boolean
showInverted: boolean
setShowInverted: React.Dispatch<React.SetStateAction<boolean>>
allowedSlippage: Percent
}
export default function SwapDetailsDropdown({
trade,
syncing,
loading,
showInverted,
setShowInverted,
allowedSlippage,
}: SwapDetailsInlineProps) {
export default function SwapDetailsDropdown({ trade, syncing, loading, allowedSlippage }: SwapDetailsInlineProps) {
const theme = useTheme()
const { chainId } = useWeb3React()
const [showDetails, setShowDetails] = useState(false)
......@@ -169,11 +160,7 @@ export default function SwapDetailsDropdown({
)}
{trade ? (
<LoadingOpacityContainer $loading={syncing}>
<TradePrice
price={trade.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
<TradePrice price={trade.executionPrice} />
</LoadingOpacityContainer>
) : loading || syncing ? (
<ThemedText.DeprecatedMain fontSize={14}>
......
......@@ -75,7 +75,6 @@ export default function SwapModalHeader({
}) {
const theme = useTheme()
const [showInverted, setShowInverted] = useState<boolean>(false)
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade.executionPrice)
const [priceUpdate, setPriceUpdate] = useState<number | undefined>()
......@@ -153,7 +152,7 @@ export default function SwapModalHeader({
</AutoColumn>
</LightCard>
<RowBetween style={{ marginTop: '0.25rem', padding: '0 1rem' }}>
<TradePrice price={trade.executionPrice} showInverted={showInverted} setShowInverted={setShowInverted} />
<TradePrice price={trade.executionPrice} />
</RowBetween>
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />
......
import { Trans } from '@lingui/macro'
import { Currency, Price } from '@uniswap/sdk-core'
import useStablecoinPrice from 'hooks/useStablecoinPrice'
import { useCallback } from 'react'
import { Text } from 'rebass'
import styled, { useTheme } from 'styled-components/macro'
import { useCallback, useState } from 'react'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { formatDollar, formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
interface TradePriceProps {
price: Price<Currency, Currency>
showInverted: boolean
setShowInverted: (showInverted: boolean) => void
}
const StyledPriceContainer = styled.button`
......@@ -30,8 +27,8 @@ const StyledPriceContainer = styled.button`
user-select: text;
`
export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) {
const theme = useTheme()
export default function TradePrice({ price }: TradePriceProps) {
const [showInverted, setShowInverted] = useState<boolean>(false)
const usdcPrice = useStablecoinPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
......@@ -58,9 +55,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
}}
title={text}
>
<Text fontWeight={500} color={theme.textPrimary}>
{text}
</Text>{' '}
<ThemedText.BodySmall>{text}</ThemedText.BodySmall>{' '}
{usdcPrice && (
<ThemedText.DeprecatedDarkGray>
<Trans>({formatDollar({ num: priceToPreciseFloat(usdcPrice), isPrice: true })})</Trans>
......
......@@ -12,7 +12,6 @@ export const PageWrapper = styled.div`
padding: 68px 8px 0px;
max-width: 480px;
width: 100%;
height: 100vh;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
padding-top: 48px;
......
......@@ -2,4 +2,5 @@ export enum FeatureFlag {
fiatOnramp = 'fiatOnramp',
traceJsonRpc = 'traceJsonRpc',
permit2 = 'permit2',
landingRedirect = 'landingRedirect',
}
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useFiatOnrampFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.fiatOnramp)
export function useLandingRedirectFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.landingRedirect)
}
export { BaseVariant as LandingRedirectVariant }
......@@ -20,13 +20,13 @@ enum SyncState {
export enum PermitState {
INVALID,
LOADING,
PERMIT_NEEDED,
PERMITTED,
APPROVAL_OR_PERMIT_NEEDED,
APPROVAL_LOADING,
APPROVED_AND_PERMITTED,
}
export interface Permit {
state: PermitState
isSyncing?: boolean
signature?: PermitSignature
callback?: () => Promise<{
response: ContractTransaction
......@@ -81,9 +81,8 @@ export default function usePermit(amount?: CurrencyAmount<Token>, spender?: stri
// Permit2 should be marked syncing from the time approval is submitted (pending) until it is
// synced in tokenAllowance, to avoid re-prompting the user for an already-submitted approval.
// It should *not* be marked syncing if not permitted, because the user must still take action.
const [syncState, setSyncState] = useState(SyncState.SYNCED)
const isSyncing = isPermitted || isSigned ? false : syncState !== SyncState.SYNCED
const isApprovalLoading = syncState !== SyncState.SYNCED
const hasPendingApproval = useHasPendingApproval(amount?.currency, PERMIT2_ADDRESS)
useEffect(() => {
if (hasPendingApproval) {
......@@ -117,13 +116,25 @@ export default function usePermit(amount?: CurrencyAmount<Token>, spender?: stri
return { state: PermitState.INVALID }
} else if (!tokenAllowance || !permitAllowance) {
return { state: PermitState.LOADING }
} else if (isAllowed) {
if (isPermitted) {
return { state: PermitState.PERMITTED }
} else if (isSigned) {
return { state: PermitState.PERMITTED, signature }
} else if (!(isPermitted || isSigned)) {
return { state: PermitState.APPROVAL_OR_PERMIT_NEEDED, callback }
} else if (!isAllowed) {
return {
state: isApprovalLoading ? PermitState.APPROVAL_LOADING : PermitState.APPROVAL_OR_PERMIT_NEEDED,
callback,
}
} else {
return { state: PermitState.APPROVED_AND_PERMITTED, signature: isPermitted ? undefined : signature }
}
return { state: PermitState.PERMIT_NEEDED, isSyncing, callback }
}, [amount, callback, isAllowed, isPermitted, isSigned, isSyncing, permitAllowance, signature, tokenAllowance])
}, [
amount,
callback,
isAllowed,
isApprovalLoading,
isPermitted,
isSigned,
permitAllowance,
signature,
tokenAllowance,
])
}
/* eslint-disable @typescript-eslint/no-var-requires */
import Modal from 'components/Modal'
import { useState } from 'react'
import { X } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { ExternalLink } from 'theme/components'
import { ThemedText } from 'theme/components/text'
const Container = styled.div`
position: relative;
display: flex;
padding: 30% 24px 24px;
overflow: hidden;
height: fit-content;
user-select: none;
`
const CloseButton = styled(X)`
position: absolute;
top: 20px;
right: 24px;
cursor: pointer;
`
const Background = styled.img`
position: absolute;
top: 0;
left: 0;
width: 100%;
object-fit: contain;
`
const Content = styled.div`
display: flex;
flex-direction: column;
z-index: 1;
gap: 16px;
`
const Link = styled(ExternalLink)`
color: ${({ theme }) => theme.accentActive};
stroke: ${({ theme }) => theme.accentActive};
`
const Title = styled(ThemedText.LargeHeader)`
@media (max-width: ${({ theme }) => theme.breakpoint.xl}px) {
font-size: 20px !important;
}
`
const Paragraph = styled(ThemedText.BodySecondary)`
line-height: 24px;
@media (max-width: ${({ theme }) => theme.breakpoint.xl}px) {
font-size: 14px !important;
line-height: 20px;
}
`
const BACKGROUND_IMAGE = {
dark: {
src: require('../../../assets/images/welcomeModal-dark.jpg').default,
srcSet: `
${require('../../../assets/images/welcomeModal-dark@2x.jpg').default} 2x,
${require('../../../assets/images/welcomeModal-dark@3x.jpg').default} 3x,
`,
},
light: {
src: require('../../../assets/images/welcomeModal-light.jpg').default,
srcSet: `
${require('../../../assets/images/welcomeModal-light@2x.jpg').default} 2x,
${require('../../../assets/images/welcomeModal-light@3x.jpg').default} 3x,
`,
},
}
export function WelcomeModal({ onDismissed }: { onDismissed: () => void }) {
const [isOpen, setIsOpen] = useState(true)
const dismiss = () => {
setIsOpen(false)
setTimeout(() => onDismissed())
}
const theme = useTheme()
return (
<Modal isOpen={isOpen} onSwipe={dismiss} maxWidth={720} isBottomSheet={false}>
<Container data-testid="nft-welcome-modal">
<Background
{...(theme.darkMode ? BACKGROUND_IMAGE.dark : BACKGROUND_IMAGE.light)}
alt="Welcome modal background"
draggable={false}
/>
<Content>
<Title>Introducing NFTs on Uniswap</Title>
<Paragraph>
You can now buy and sell NFTs on Uniswap across marketplaces. Trade here to find more listings and better
prices. <br />
<br />
NFTs on Uniswap replaces Genie, which was{' '}
<Link href="https://uniswap.org/blog/genie" title="Uniswap Labs has acquired Genie">
acquired{' '}
</Link>{' '}
by Uniswap Labs earlier this year. If you have used Genie in the past, you may be eligible for a USDC
airdrop.{' '}
<Link
href="https://uniswap.org/blog/uniswap-nft-aggregator-announcement"
title="Uniswap NFT aggregator announcement"
>
Learn more.
</Link>
</Paragraph>
<CloseButton data-testid="nft-intro-modal" size={24} onClick={dismiss} />
</Content>
</Container>
</Modal>
)
}
import { Trans } from '@lingui/macro'
import { LARGE_MEDIA_BREAKPOINT, SMALL_MOBILE_MEDIA_BREAKPOINT } from 'components/Tokens/constants'
import { Box } from 'nft/components/Box'
import { bodySmall, subhead } from 'nft/css/common.css'
import { X } from 'react-feather'
import { useNavigate } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { useHideNftPromoBanner } from 'state/user/hooks'
import styled, { css } from 'styled-components/macro'
import { ClickableStyle } from 'theme'
import { Z_INDEX } from 'theme/zIndex'
import nftPromoImage1 from '../nftExploreBanner/nftArt1.png'
import nftPromoImage2 from '../nftExploreBanner/nftArt2.png'
import nftPromoImage3 from '../nftExploreBanner/nftArt3.png'
function getRandom(list: any[]) {
return list[Math.floor(Math.random() * list.length)]
}
const randomizedNftImage = getRandom([nftPromoImage1, nftPromoImage2, nftPromoImage3])
const PopupContainer = styled.div<{ show: boolean }>`
background-color: ${({ theme }) => theme.backgroundSurface};
box-shadow: ${({ theme }) => theme.deepShadow};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 12px;
cursor: pointer;
color: ${({ theme }) => theme.textPrimary};
display: ${({ show }) => (show ? 'flex' : 'none')};
flex-direction: column;
position: fixed;
right: clamp(0px, 1vw, 16px);
z-index: ${Z_INDEX.sticky};
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.slow} opacity ${timing.in}`};
width: 98vw;
bottom: 55px;
@media screen and (min-width: ${LARGE_MEDIA_BREAKPOINT}) {
bottom: 48px;
}
@media screen and (min-width: ${SMALL_MOBILE_MEDIA_BREAKPOINT}) {
width: 391px;
}
:hover {
border: double 1px transparent;
border-radius: 12px;
background-image: ${({ theme }) =>
`linear-gradient(${theme.backgroundSurface}, ${theme.backgroundSurface}),
radial-gradient(circle at top left, hsla(299, 100%, 87%, 1), hsla(299, 100%, 61%, 1))`};
background-origin: border-box;
background-clip: padding-box, border-box;
}
`
const InnerContainer = styled.div`
overflow: hidden;
display: flex;
position: relative;
gap: 8px;
padding: 12px;
`
const TextContainer = styled.div`
display: flex;
flex-direction: column;
flex: 1;
justify-content: flex-start;
`
const StyledXButton = styled(X)`
color: ${({ theme }) => theme.textSecondary};
&:hover {
opacity: ${({ theme }) => theme.opacity.hover};
}
&:active {
opacity: ${({ theme }) => theme.opacity.click};
}
`
const StyledImageContainer = styled(Box)`
width: 20%;
cursor: pointer;
aspectratio: 1;
transition: transform 0.25s ease 0s;
object-fit: contain;
`
const LinkStyle = css`
color: ${({ theme }) => theme.accentActive};
stroke: ${({ theme }) => theme.accentActive};
`
const StyledLink = styled(Link)`
${ClickableStyle}
${LinkStyle}
`
export default function NftExploreBanner() {
const [hideNftPromoBanner, toggleHideNftPromoBanner] = useHideNftPromoBanner()
const navigate = useNavigate()
const navigateToNfts = () => {
navigate('/nfts')
toggleHideNftPromoBanner()
}
return (
<PopupContainer show={!hideNftPromoBanner} onClick={navigateToNfts}>
<InnerContainer>
<StyledImageContainer as="img" src={randomizedNftImage} draggable={false} />
<TextContainer>
{/* <HeaderText> */}
<div className={subhead}>
<Trans>Introducing NFTs on Uniswap</Trans>
</div>
{/* </HeaderText> */}
{/* <Description> */}
<div className={bodySmall}>
<Trans>Buy and sell NFTs across more listings at better prices.</Trans>{' '}
<StyledLink to="/nfts">
<Trans>Explore NFTs</Trans>
</StyledLink>{' '}
</div>
</TextContainer>
{/* </Description> */}
<StyledXButton
size={20}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
toggleHideNftPromoBanner()
}}
/>
</InnerContainer>
</PopupContainer>
)
}
......@@ -34,7 +34,7 @@ const ListingHeader = styled(Row)`
margin-top: 18px;
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
margin-top: 40px;
margin-top: 16px;
}
`
......
......@@ -10,9 +10,21 @@ import { LOOKS_RARE_CREATOR_BASIS_POINTS } from 'nft/utils'
import { formatEth, formatUsdPrice } from 'nft/utils/currency'
import { fetchPrice } from 'nft/utils/fetchPrice'
import { Dispatch, FormEvent, useEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components/macro'
import * as styles from './ListPage.css'
const TableHeader = styled.div`
display: flex;
position: sticky;
align-items: center;
top: 72px;
padding-top: 24px;
padding-bottom: 24px;
z-index: 1;
background-color: ${({ theme }) => theme.backgroundBackdrop};
`
enum SetPriceMethod {
SAME_PRICE,
FLOOR_PRICE,
......@@ -44,7 +56,7 @@ export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingM
return (
<Column>
<Row marginTop="20">
<TableHeader>
<Column
marginLeft={selectedMarkets.length > 1 ? '36' : '0'}
transition="500"
......@@ -86,7 +98,7 @@ export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingM
You receive
</Column>
</Row>
</Row>
</TableHeader>
{sellAssets.map((asset) => {
return (
<>
......
......@@ -2,10 +2,8 @@ import { Trace } from '@uniswap/analytics'
import { PageName } from '@uniswap/analytics-events'
import Banner from 'nft/components/explore/Banner'
import TrendingCollections from 'nft/components/explore/TrendingCollections'
import { WelcomeModal } from 'nft/components/explore/WelcomeModal'
import { useBag } from 'nft/hooks'
import { useEffect } from 'react'
import { useHideNFTWelcomeModal } from 'state/user/hooks'
import styled from 'styled-components/macro'
const ExploreContainer = styled.div`
......@@ -25,7 +23,6 @@ const ExploreContainer = styled.div`
const NftExplore = () => {
const setBagExpanded = useBag((state) => state.setBagExpanded)
const [isModalHidden, hideModal] = useHideNFTWelcomeModal()
useEffect(() => {
setBagExpanded({ bagExpanded: false, manualClose: false })
......@@ -38,7 +35,6 @@ const NftExplore = () => {
<Banner />
<TrendingCollections />
</ExploreContainer>
{!isModalHidden && <WelcomeModal onDismissed={hideModal} />}
</Trace>
</>
)
......
......@@ -36,6 +36,7 @@ import Manage from './Earn/Manage'
import Landing from './Landing'
import MigrateV2 from './MigrateV2'
import MigrateV2Pair from './MigrateV2/MigrateV2Pair'
import NotFound from './NotFound'
import Pool from './Pool'
import { PositionPage } from './Pool/PositionPage'
import PoolV2 from './Pool/v2'
......@@ -64,29 +65,19 @@ initializeAnalytics(ANALYTICS_DUMMY_KEY, OriginApplication.INTERFACE, {
isProductionEnv: isProductionEnv(),
})
const AppWrapper = styled.div`
display: flex;
flex-flow: column;
align-items: flex-start;
height: 100%;
`
const BodyWrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 72px 0px 0px 0px;
min-height: 100vh;
padding: ${({ theme }) => theme.navHeight}px 0px 5rem 0px;
align-items: center;
flex: 1;
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
padding: 52px 0px 16px 0px;
`};
`
const MobileBottomBar = styled.div`
z-index: ${Z_INDEX.sticky};
position: sticky;
position: fixed;
display: flex;
bottom: 0;
right: 0;
......@@ -114,10 +105,6 @@ const HeaderWrapper = styled.div<{ transparent?: boolean }>`
z-index: ${Z_INDEX.dropdown};
`
const Marginer = styled.div`
margin-top: 5rem;
`
function getCurrentPageFromLocation(locationPathname: string): PageName | undefined {
switch (true) {
case locationPathname.startsWith('/swap'):
......@@ -205,128 +192,127 @@ export default function App() {
<ErrorBoundary>
<DarkModeQueryParamReader />
<ApeModeQueryParamReader />
<AppWrapper>
<Trace page={currentPage}>
<HeaderWrapper transparent={isHeaderTransparent}>
<NavBar />
</HeaderWrapper>
<BodyWrapper>
<Popups />
<Polling />
<TopLevelModals />
<Suspense fallback={<Loader />}>
{isLoaded ? (
<Routes>
<Route path="/" element={<Landing />} />
<Route path="tokens" element={<Tokens />}>
<Route path=":chainName" />
</Route>
<Route path="tokens/:chainName/:tokenAddress" element={<TokenDetails />} />
<Route
path="vote/*"
element={
<Suspense fallback={<LazyLoadSpinner />}>
<Vote />
</Suspense>
}
/>
<Route path="create-proposal" element={<Navigate to="/vote/create-proposal" replace />} />
<Route path="claim" element={<OpenClaimAddressModalAndRedirectToSwap />} />
<Route path="uni" element={<Earn />} />
<Route path="uni/:currencyIdA/:currencyIdB" element={<Manage />} />
<Trace page={currentPage}>
<HeaderWrapper transparent={isHeaderTransparent}>
<NavBar />
</HeaderWrapper>
<BodyWrapper>
<Popups />
<Polling />
<TopLevelModals />
<Suspense fallback={<Loader />}>
{isLoaded ? (
<Routes>
<Route path="/" element={<Landing />} />
<Route path="tokens" element={<Tokens />}>
<Route path=":chainName" />
</Route>
<Route path="tokens/:chainName/:tokenAddress" element={<TokenDetails />} />
<Route
path="vote/*"
element={
<Suspense fallback={<LazyLoadSpinner />}>
<Vote />
</Suspense>
}
/>
<Route path="create-proposal" element={<Navigate to="/vote/create-proposal" replace />} />
<Route path="claim" element={<OpenClaimAddressModalAndRedirectToSwap />} />
<Route path="uni" element={<Earn />} />
<Route path="uni/:currencyIdA/:currencyIdB" element={<Manage />} />
<Route path="send" element={<RedirectPathToSwapOnly />} />
<Route path="swap" element={<Swap />} />
<Route path="send" element={<RedirectPathToSwapOnly />} />
<Route path="swap" element={<Swap />} />
<Route path="pool/v2/find" element={<PoolFinder />} />
<Route path="pool/v2" element={<PoolV2 />} />
<Route path="pool" element={<Pool />} />
<Route path="pool/:tokenId" element={<PositionPage />} />
<Route path="pool/v2/find" element={<PoolFinder />} />
<Route path="pool/v2" element={<PoolV2 />} />
<Route path="pool" element={<Pool />} />
<Route path="pool/:tokenId" element={<PositionPage />} />
<Route path="add/v2" element={<RedirectDuplicateTokenIdsV2 />}>
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
</Route>
<Route path="add" element={<RedirectDuplicateTokenIds />}>
{/* this is workaround since react-router-dom v6 doesn't support optional parameters any more */}
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
</Route>
<Route path="add/v2" element={<RedirectDuplicateTokenIdsV2 />}>
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
</Route>
<Route path="add" element={<RedirectDuplicateTokenIds />}>
{/* this is workaround since react-router-dom v6 doesn't support optional parameters any more */}
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
</Route>
<Route path="increase" element={<AddLiquidity />}>
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount/:tokenId" />
</Route>
<Route path="increase" element={<AddLiquidity />}>
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount/:tokenId" />
</Route>
<Route path="remove/v2/:currencyIdA/:currencyIdB" element={<RemoveLiquidity />} />
<Route path="remove/:tokenId" element={<RemoveLiquidityV3 />} />
<Route path="remove/v2/:currencyIdA/:currencyIdB" element={<RemoveLiquidity />} />
<Route path="remove/:tokenId" element={<RemoveLiquidityV3 />} />
<Route path="migrate/v2" element={<MigrateV2 />} />
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
<Route path="migrate/v2" element={<MigrateV2 />} />
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
<Route path="about" element={<About />} />
<Route path="about" element={<About />} />
<Route path="*" element={<RedirectPathToSwapOnly />} />
<Route
path="/nfts"
element={
// TODO: replace loading state during Apollo migration
<Suspense fallback={null}>
<NftExplore />
</Suspense>
}
/>
<Route
path="/nfts/asset/:contractAddress/:tokenId"
element={
<Suspense fallback={<AssetDetailsLoading />}>
<Asset />
</Suspense>
}
/>
<Route
path="/nfts/profile"
element={
<Suspense fallback={<ProfilePageLoadingSkeleton />}>
<Profile />
</Suspense>
}
/>
<Route
path="/nfts/collection/:contractAddress"
element={
<Suspense fallback={<CollectionPageSkeleton />}>
<Collection />
</Suspense>
}
/>
<Route
path="/nfts/collection/:contractAddress/activity"
element={
<Suspense fallback={<CollectionPageSkeleton />}>
<Collection />
</Suspense>
}
/>
<Route
path="/nfts"
element={
// TODO: replace loading state during Apollo migration
<Suspense fallback={null}>
<NftExplore />
</Suspense>
}
/>
<Route
path="/nfts/asset/:contractAddress/:tokenId"
element={
<Suspense fallback={<AssetDetailsLoading />}>
<Asset />
</Suspense>
}
/>
<Route
path="/nfts/profile"
element={
<Suspense fallback={<ProfilePageLoadingSkeleton />}>
<Profile />
</Suspense>
}
/>
<Route
path="/nfts/collection/:contractAddress"
element={
<Suspense fallback={<CollectionPageSkeleton />}>
<Collection />
</Suspense>
}
/>
<Route
path="/nfts/collection/:contractAddress/activity"
element={
<Suspense fallback={<CollectionPageSkeleton />}>
<Collection />
</Suspense>
}
/>
</Routes>
) : (
<Loader />
)}
</Suspense>
<Marginer />
</BodyWrapper>
<MobileBottomBar>
<PageTabs />
<Box marginY="4">
<MenuDropdown />
</Box>
</MobileBottomBar>
</Trace>
</AppWrapper>
<Route path="*" element={<Navigate to="/not-found" replace />} />
<Route path="/not-found" element={<NotFound />} />
</Routes>
) : (
<Loader />
)}
</Suspense>
</BodyWrapper>
<MobileBottomBar>
<PageTabs />
<Box marginY="4">
<MenuDropdown />
</Box>
</MobileBottomBar>
</Trace>
</ErrorBoundary>
)
}
import { Trace, TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName, PageName } from '@uniswap/analytics-events'
import { BaseButton } from 'components/Button'
import { LandingRedirectVariant, useLandingRedirectFlag } from 'featureFlags/flags/landingRedirect'
import Swap from 'pages/Swap'
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { Link as NativeLink } from 'react-router-dom'
import { useAppSelector } from 'state/hooks'
import { useIsDarkMode } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
......@@ -176,40 +178,58 @@ export default function Landing() {
}
}, [])
const [showContent, setShowContent] = useState(false)
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
const landingRedirectFlag = useLandingRedirectFlag()
const navigate = useNavigate()
useEffect(() => {
if (selectedWallet) {
if (landingRedirectFlag === LandingRedirectVariant.Enabled) {
navigate('/swap')
} else {
setShowContent(true)
}
} else {
setShowContent(true)
}
}, [navigate, selectedWallet, landingRedirectFlag])
if (!isOpen) return null
return (
<Trace page={PageName.LANDING_PAGE} shouldLogImpression>
<PageContainer>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.LANDING_PAGE_SWAP_ELEMENT}
>
<Link to="/swap">
<LandingSwap />
</Link>
</TraceEvent>
<Glow />
<Gradient isDarkMode={isDarkMode} />
<ContentContainer isDarkMode={isDarkMode}>
<TitleText isDarkMode={isDarkMode}>Trade crypto & NFTs with confidence</TitleText>
<SubTextContainer>
<SubText>Buy, sell, and explore tokens and NFTs</SubText>
</SubTextContainer>
<ActionsContainer>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.CONTINUE_BUTTON}
>
<ButtonCTA as={Link} to="/swap">
<ButtonCTAText>Get started</ButtonCTAText>
</ButtonCTA>
</TraceEvent>
</ActionsContainer>
</ContentContainer>
</PageContainer>
{showContent && (
<PageContainer>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.LANDING_PAGE_SWAP_ELEMENT}
>
<Link to="/swap">
<LandingSwap />
</Link>
</TraceEvent>
<Glow />
<Gradient isDarkMode={isDarkMode} />
<ContentContainer isDarkMode={isDarkMode}>
<TitleText isDarkMode={isDarkMode}>Trade crypto & NFTs with confidence</TitleText>
<SubTextContainer>
<SubText>Buy, sell, and explore tokens and NFTs</SubText>
</SubTextContainer>
<ActionsContainer>
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.ELEMENT_CLICKED}
element={ElementName.CONTINUE_BUTTON}
>
<ButtonCTA as={Link} to="/swap">
<ButtonCTAText>Get started</ButtonCTAText>
</ButtonCTA>
</TraceEvent>
</ActionsContainer>
</ContentContainer>
</PageContainer>
)}
</Trace>
)
}
import { Trans } from '@lingui/macro'
import { Trace } from '@uniswap/analytics'
import { SmallButtonPrimary } from 'components/Button'
import { useIsMobile } from 'nft/hooks'
import { Link } from 'react-router-dom'
import { useIsDarkMode } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import darkImage from '../../assets/images/404-page-dark.png'
import lightImage from '../../assets/images/404-page-light.png'
const Image = styled.img`
max-width: 510px;
width: 100%;
padding: 0 75px;
`
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`
const Header = styled(Container)`
gap: 30px;
`
const PageWrapper = styled(Container)`
flex: 1;
justify-content: center;
gap: 50px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
justify-content: space-between;
padding-top: 64px;
}
`
export default function NotFound() {
const isDarkMode = useIsDarkMode()
const isMobile = useIsMobile()
const Title = isMobile ? ThemedText.LargeHeader : ThemedText.Hero
const Paragraph = isMobile ? ThemedText.HeadlineMedium : ThemedText.HeadlineLarge
return (
<PageWrapper>
<Trace page="404-page" shouldLogImpression>
<Header>
<Container>
<Title>404</Title>
<Paragraph color="textSecondary">
<Trans>Page not found!</Trans>
</Paragraph>
</Container>
<Image src={isDarkMode ? darkImage : lightImage} alt="Liluni" />
</Header>
<SmallButtonPrimary as={Link} to="/">
<Trans>Oops, take me back to Swap</Trans>
</SmallButtonPrimary>
</Trace>
</PageWrapper>
)
}
......@@ -165,7 +165,7 @@ function WrongNetworkCard() {
const theme = useTheme()
return (
<div style={{ height: '100vh' }}>
<>
<PageWrapper>
<AutoColumn gap="lg" justify="center">
<AutoColumn gap="lg" style={{ width: '100%' }}>
......@@ -189,7 +189,7 @@ function WrongNetworkCard() {
</AutoColumn>
</PageWrapper>
<SwitchLocaleLink />
</div>
</>
)
}
......@@ -263,86 +263,84 @@ export default function Pool() {
return (
<Trace page={PageName.POOL_PAGE} shouldLogImpression>
<div style={{ height: '100vh' }}>
<PageWrapper>
<AutoColumn gap="lg" justify="center">
<AutoColumn gap="lg" style={{ width: '100%' }}>
<TitleRow padding="0">
<ThemedText.LargeHeader>
<Trans>Pools</Trans>
</ThemedText.LargeHeader>
<ButtonRow>
{showV2Features && (
<PoolMenu
menuItems={menuItems}
flyoutAlignment={FlyoutAlignment.LEFT}
ToggleUI={(props: any) => (
<MoreOptionsButton {...props}>
<MoreOptionsText>
<Trans>More</Trans>
<ChevronDown size={15} />
</MoreOptionsText>
</MoreOptionsButton>
)}
/>
)}
<ResponsiveButtonPrimary data-cy="join-pool-button" id="join-pool-button" as={Link} to="/add/ETH">
+ <Trans>New Position</Trans>
</ResponsiveButtonPrimary>
</ButtonRow>
</TitleRow>
<MainContentWrapper>
{positionsLoading ? (
<PositionsLoadingPlaceholder />
) : filteredPositions && closedPositions && filteredPositions.length > 0 ? (
<PositionList
positions={filteredPositions}
setUserHideClosedPositions={setUserHideClosedPositions}
userHideClosedPositions={userHideClosedPositions}
/>
) : (
<ErrorContainer>
<ThemedText.DeprecatedBody color={theme.textTertiary} textAlign="center">
<InboxIcon strokeWidth={1} style={{ marginTop: '2em' }} />
<div>
<Trans>Your active V3 liquidity positions will appear here.</Trans>
</div>
</ThemedText.DeprecatedBody>
{!showConnectAWallet && closedPositions.length > 0 && (
<ButtonText
style={{ marginTop: '.5rem' }}
onClick={() => setUserHideClosedPositions(!userHideClosedPositions)}
>
<Trans>Show closed positions</Trans>
</ButtonText>
)}
{showConnectAWallet && (
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: false }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<ButtonPrimary
style={{ marginTop: '2em', marginBottom: '2em', padding: '8px 16px' }}
onClick={toggleWalletModal}
>
<Trans>Connect a wallet</Trans>
</ButtonPrimary>
</TraceEvent>
<PageWrapper>
<AutoColumn gap="lg" justify="center">
<AutoColumn gap="lg" style={{ width: '100%' }}>
<TitleRow padding="0">
<ThemedText.LargeHeader>
<Trans>Pools</Trans>
</ThemedText.LargeHeader>
<ButtonRow>
{showV2Features && (
<PoolMenu
menuItems={menuItems}
flyoutAlignment={FlyoutAlignment.LEFT}
ToggleUI={(props: any) => (
<MoreOptionsButton {...props}>
<MoreOptionsText>
<Trans>More</Trans>
<ChevronDown size={15} />
</MoreOptionsText>
</MoreOptionsButton>
)}
</ErrorContainer>
/>
)}
</MainContentWrapper>
<HideSmall>
<CTACards />
</HideSmall>
</AutoColumn>
<ResponsiveButtonPrimary data-cy="join-pool-button" id="join-pool-button" as={Link} to="/add/ETH">
+ <Trans>New Position</Trans>
</ResponsiveButtonPrimary>
</ButtonRow>
</TitleRow>
<MainContentWrapper>
{positionsLoading ? (
<PositionsLoadingPlaceholder />
) : filteredPositions && closedPositions && filteredPositions.length > 0 ? (
<PositionList
positions={filteredPositions}
setUserHideClosedPositions={setUserHideClosedPositions}
userHideClosedPositions={userHideClosedPositions}
/>
) : (
<ErrorContainer>
<ThemedText.DeprecatedBody color={theme.textTertiary} textAlign="center">
<InboxIcon strokeWidth={1} style={{ marginTop: '2em' }} />
<div>
<Trans>Your active V3 liquidity positions will appear here.</Trans>
</div>
</ThemedText.DeprecatedBody>
{!showConnectAWallet && closedPositions.length > 0 && (
<ButtonText
style={{ marginTop: '.5rem' }}
onClick={() => setUserHideClosedPositions(!userHideClosedPositions)}
>
<Trans>Show closed positions</Trans>
</ButtonText>
)}
{showConnectAWallet && (
<TraceEvent
events={[BrowserEvent.onClick]}
name={EventName.CONNECT_WALLET_BUTTON_CLICKED}
properties={{ received_swap_quote: false }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<ButtonPrimary
style={{ marginTop: '2em', marginBottom: '2em', padding: '8px 16px' }}
onClick={toggleWalletModal}
>
<Trans>Connect a wallet</Trans>
</ButtonPrimary>
</TraceEvent>
)}
</ErrorContainer>
)}
</MainContentWrapper>
<HideSmall>
<CTACards />
</HideSmall>
</AutoColumn>
</PageWrapper>
<SwitchLocaleLink />
</div>
</AutoColumn>
</PageWrapper>
<SwitchLocaleLink />
</Trace>
)
}
......@@ -299,7 +299,7 @@ export default function Swap({ className }: { className?: string }) {
permit2Enabled ? maximumAmountIn : undefined,
permit2Enabled && chainId ? UNIVERSAL_ROUTER_ADDRESS(chainId) : undefined
)
const isApprovalPending = permit.isSyncing
const isApprovalLoading = permit.state === PermitState.APPROVAL_LOADING
const [isPermitPending, setIsPermitPending] = useState(false)
const [isPermitFailed, setIsPermitFailed] = useState(false)
const addTransaction = useTransactionAdder()
......@@ -441,7 +441,6 @@ export default function Swap({ className }: { className?: string }) {
])
// errors
const [showInverted, setShowInverted] = useState<boolean>(false)
const [swapQuoteReceivedDate, setSwapQuoteReceivedDate] = useState<Date | undefined>()
// warnings on the greater of fiat value price impact and execution price impact
......@@ -663,8 +662,6 @@ export default function Swap({ className }: { className?: string }) {
trade={trade}
syncing={routeIsSyncing}
loading={routeIsLoading}
showInverted={showInverted}
setShowInverted={setShowInverted}
allowedSlippage={allowedSlippage}
/>
</DetailsSwapSection>
......@@ -786,10 +783,12 @@ export default function Swap({ className }: { className?: string }) {
</ButtonError>
</AutoColumn>
</AutoRow>
) : isValid && permit.state === PermitState.PERMIT_NEEDED ? (
) : isValid &&
(permit.state === PermitState.APPROVAL_OR_PERMIT_NEEDED ||
permit.state === PermitState.APPROVAL_LOADING) ? (
<ButtonYellow
onClick={updatePermit}
disabled={isPermitPending || isApprovalPending}
disabled={isPermitPending || isApprovalLoading}
style={{ gap: 14 }}
>
{isPermitPending ? (
......@@ -806,7 +805,7 @@ export default function Swap({ className }: { className?: string }) {
<Trans>Approval failed. Try again.</Trans>
</ThemedText.SubHeader>
</>
) : isApprovalPending ? (
) : isApprovalLoading ? (
<>
<Loader size="20px" stroke={theme.accentWarning} />
<ThemedText.SubHeader color="accentWarning">
......
......@@ -4,8 +4,6 @@ import { useWeb3React } from '@web3-react/core'
import { L2_CHAIN_IDS } from 'constants/chains'
import { SupportedLocale } from 'constants/locales'
import { L2_DEADLINE_FROM_NOW } from 'constants/misc'
import { BaseVariant } from 'featureFlags'
import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
import JSBI from 'jsbi'
import { useCallback, useMemo } from 'react'
import { shallowEqual } from 'react-redux'
......@@ -21,8 +19,6 @@ import {
addSerializedToken,
updateFiatOnrampAcknowledgments,
updateHideClosedPositions,
updateHideNFTWelcomeModal,
updateShowNftPromoBanner,
updateUserClientSideRouter,
updateUserDarkMode,
updateUserDeadline,
......@@ -127,15 +123,6 @@ export function useFiatOnrampAck(): [
)
return [fiatOnrampAcknowledgments, setAcknowledgements]
}
export function useHideNFTWelcomeModal(): [boolean | undefined, () => void] {
const dispatch = useAppDispatch()
const fiatOnrampFlagEnabled = useFiatOnrampFlag() === BaseVariant.Enabled
const hideNFTWelcomeModal = useAppSelector((state) => state.user.hideNFTWelcomeModal) || fiatOnrampFlagEnabled
const hideModal = useCallback(() => {
dispatch(updateHideNFTWelcomeModal({ hideNFTWelcomeModal: true }))
}, [dispatch])
return [hideNFTWelcomeModal, hideModal]
}
export function useClientSideRouter(): [boolean, (userClientSideRouter: boolean) => void] {
const dispatch = useAppDispatch()
......@@ -281,17 +268,6 @@ export function useURLWarningVisible(): boolean {
return useAppSelector((state: AppState) => state.user.URLWarningVisible)
}
export function useHideNftPromoBanner(): [boolean, () => void] {
const dispatch = useAppDispatch()
const hideNftPromoBanner = useAppSelector((state) => state.user.hideNFTPromoBanner)
const toggleHideNftPromoBanner = useCallback(() => {
dispatch(updateShowNftPromoBanner({ hideNFTPromoBanner: true }))
}, [dispatch])
return [hideNftPromoBanner, toggleHideNftPromoBanner]
}
/**
* Given two tokens return the liquidity token that represents its liquidity shares
* @param tokenA one of the two tokens
......
......@@ -126,12 +126,6 @@ const userSlice = createSlice({
updateHideClosedPositions(state, action) {
state.userHideClosedPositions = action.payload.userHideClosedPositions
},
updateHideNFTWelcomeModal(state, action) {
state.hideNFTWelcomeModal = action.payload.hideNFTWelcomeModal
},
updateShowNftPromoBanner(state, action) {
state.hideNFTPromoBanner = action.payload.hideNFTPromoBanner
},
addSerializedToken(state, { payload: { serializedToken } }) {
if (!state.tokens) {
state.tokens = {}
......@@ -197,12 +191,10 @@ export const {
updateHideClosedPositions,
updateMatchesDarkMode,
updateUserClientSideRouter,
updateHideNFTWelcomeModal,
updateUserDarkMode,
updateUserDeadline,
updateUserExpertMode,
updateUserLocale,
updateUserSlippageTolerance,
updateShowNftPromoBanner,
} = userSlice.actions
export default userSlice.reducer
......@@ -31,12 +31,18 @@ export const ThemedText = {
HeadlineSmall(props: TextProps) {
return <TextWrapper fontWeight={600} fontSize={20} lineHeight="28px" color="textPrimary" {...props} />
},
HeadlineMedium(props: TextProps) {
return <TextWrapper fontWeight={500} fontSize={28} color="textPrimary" {...props} />
},
HeadlineLarge(props: TextProps) {
return <TextWrapper fontWeight={600} fontSize={36} lineHeight="44px" color="textPrimary" {...props} />
return <TextWrapper fontWeight={600} fontSize={36} lineHeight="36px" color="textPrimary" {...props} />
},
LargeHeader(props: TextProps) {
return <TextWrapper fontWeight={400} fontSize={36} color="textPrimary" {...props} />
},
Hero(props: TextProps) {
return <TextWrapper fontWeight={500} fontSize={48} color="textPrimary" {...props} />
},
Link(props: TextProps) {
return <TextWrapper fontWeight={600} fontSize={14} color="accentAction" {...props} />
},
......
......@@ -91,7 +91,8 @@ function getSettings(darkMode: boolean) {
}
}
function getTheme(darkMode: boolean) {
// eslint-disable-next-line import/no-unused-modules -- used in styled.d.ts
export function getTheme(darkMode: boolean) {
return {
darkMode,
...(darkMode ? darkTheme : lightTheme),
......
......@@ -4195,10 +4195,10 @@
"@typescript-eslint/types" "4.33.0"
eslint-visitor-keys "^2.0.0"
"@uniswap/analytics-events@1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-1.3.1.tgz#e1ad912001646268ea16f64622c683ca2a63002d"
integrity sha512-EzYLBU123TpTCNPfa8cN7cI3Ap8D5Rn0771/bAtjSElZROR6YnGFNj0cjnaHwo8SIUr5Ema7JLoXbMVUyreFXQ==
"@uniswap/analytics-events@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-1.4.0.tgz#63b0fa55d9d258e3b29d07f38da194f9fa841d1d"
integrity sha512-KdY/8iRTbVirJN/ms8f68MfGM+zuCuz4E35Lw4m7xcQ03YAlE9j3Oa3UhsFSCialn8F6brmrAPsx8a8Ps6uoKw==
"@uniswap/analytics@1.2.0":
version "1.2.0"
......@@ -4209,10 +4209,10 @@
react "^18.2.0"
react-dom "^18.2.0"
"@uniswap/conedison@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@uniswap/conedison/-/conedison-1.1.0.tgz#462a1e7dcfc70a653e765c145655faa207fd53f7"
integrity sha512-mHnYkvy+xKfWDzWsqzgWKFl/V8C/KmSrj/2PCgT1R2ASxPzMCU/wzTW29HtIxf1cJ+A6sPwH2HqDZsbnNhKqNQ==
"@uniswap/conedison@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@uniswap/conedison/-/conedison-1.1.1.tgz#affec246613d1f52da3cdd0571ef8195b7b54d17"
integrity sha512-xFHAcWRrU+/+/BInXy6SRiiNwUG0vxLWsoYgod66wWifUvnjfpItzlvJHUer1OOpLDsz0CL5Fb70vFJOGAGi8w==
"@uniswap/default-token-list@^2.0.0":
version "2.2.0"
......
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