Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9ab5717de | ||
|
|
94544de74b | ||
|
|
96f24d5a9b | ||
|
|
8e59a352c0 | ||
|
|
3b765b4f05 | ||
|
|
9f4a1f48a5 | ||
|
|
de9533399a | ||
|
|
a02afd50b5 |
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * 2-4' # every day 12:00 UTC Monday-Thursday
|
||||
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
92
cypress/e2e/token-details.test.ts
Normal file
92
cypress/e2e/token-details.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Token details', () => {
|
||||
before(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('Uniswap token should have all information populated', () => {
|
||||
// Uniswap token
|
||||
cy.visit('/tokens/ethereum/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
||||
|
||||
// Price chart should be filled in
|
||||
cy.get('[data-cy="chart-header"]').should('include.text', '$')
|
||||
cy.get('[data-cy="price-chart"]').should('exist')
|
||||
|
||||
// Stats should have: TVL, 24H Volume, 52W low, 52W high
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-details-stats')).within(() => {
|
||||
cy.get('[data-cy="tvl"]').should('include.text', '$')
|
||||
cy.get('[data-cy="volume-24h"]').should('include.text', '$')
|
||||
cy.get('[data-cy="52w-low"]').should('include.text', '$')
|
||||
cy.get('[data-cy="52w-high"]').should('include.text', '$')
|
||||
})
|
||||
|
||||
// About section should have description of token
|
||||
cy.get(getTestSelector('token-details-about-section')).should('exist')
|
||||
cy.contains('UNI is the governance token for Uniswap').should('exist')
|
||||
|
||||
// Links section should link out to Etherscan, More analytics, Website, Twitter
|
||||
cy.get('[data-cy="resources-container"]').within(() => {
|
||||
cy.contains('Etherscan')
|
||||
.should('have.attr', 'href')
|
||||
.and('include', 'etherscan.io/address/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
||||
cy.contains('More analytics')
|
||||
.should('have.attr', 'href')
|
||||
.and('include', 'info.uniswap.org/#/tokens/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
||||
cy.contains('Website').should('have.attr', 'href').and('include', 'uniswap.org')
|
||||
cy.contains('Twitter').should('have.attr', 'href').and('include', 'twitter.com/Uniswap')
|
||||
})
|
||||
|
||||
// Contract address should be displayed
|
||||
cy.contains('0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984').should('exist')
|
||||
|
||||
// Swap widget should have this token pre-selected as the “destination” token
|
||||
cy.get(getTestSelector('token-select')).should('include.text', 'UNI')
|
||||
})
|
||||
|
||||
it('token with warning and low trading volume should have all information populated', () => {
|
||||
// Shiba predator token, low trading volume and also has warning modal
|
||||
cy.visit('/tokens/ethereum/0xa71d0588EAf47f12B13cF8eC750430d21DF04974')
|
||||
|
||||
// Should have missing price chart when price unavailable (expected for this token)
|
||||
if (cy.get('[data-cy="chart-header"]').contains('Price Unavailable')) {
|
||||
cy.get('[data-cy="missing-chart"]').should('exist')
|
||||
}
|
||||
// Stats should have: TVL, 24H Volume, 52W low, 52W high
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-details-stats')).within(() => {
|
||||
cy.get('[data-cy="tvl"]').should('exist')
|
||||
cy.get('[data-cy="volume-24h"]').should('exist')
|
||||
cy.get('[data-cy="52w-low"]').should('exist')
|
||||
cy.get('[data-cy="52w-high"]').should('exist')
|
||||
})
|
||||
|
||||
// About section should have description of token
|
||||
cy.get(getTestSelector('token-details-about-section')).should('exist')
|
||||
cy.contains('QOM is the Shiba Predator').should('exist')
|
||||
|
||||
// Links section should link out to Etherscan, More analytics, Website, Twitter
|
||||
cy.get('[data-cy="resources-container"]').within(() => {
|
||||
cy.contains('Etherscan')
|
||||
.should('have.attr', 'href')
|
||||
.and('include', 'etherscan.io/address/0xa71d0588EAf47f12B13cF8eC750430d21DF04974')
|
||||
cy.contains('More analytics')
|
||||
.should('have.attr', 'href')
|
||||
.and('include', 'info.uniswap.org/#/tokens/0xa71d0588EAf47f12B13cF8eC750430d21DF04974')
|
||||
cy.contains('Website').should('have.attr', 'href').and('include', 'qom')
|
||||
cy.contains('Twitter').should('have.attr', 'href').and('include', 'twitter.com/ShibaPredator1')
|
||||
})
|
||||
|
||||
// Contract address should be displayed
|
||||
cy.contains('0xa71d0588EAf47f12B13cF8eC750430d21DF04974').should('exist')
|
||||
|
||||
// Swap widget should have this token pre-selected as the “destination” token
|
||||
cy.get(getTestSelector('token-select')).should('include.text', 'QOM')
|
||||
|
||||
// Warning label should show if relevant ([spec](https://www.notion.so/3f7fce6f93694be08a94a6984d50298e))
|
||||
cy.get('[data-cy="token-safety-message"]')
|
||||
.should('include.text', 'Warning')
|
||||
.and('include.text', "This token isn't traded on leading U.S. centralized exchanges")
|
||||
})
|
||||
})
|
||||
@@ -5,7 +5,7 @@ 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 } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useToggleWalletDropdown } from 'state/application/hooks'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
@@ -100,6 +100,7 @@ const MAX_RENDER_COUNT = 3
|
||||
export function FiatOnrampAnnouncement() {
|
||||
const { account } = useWeb3React()
|
||||
const [acks, acknowledge] = useFiatOnrampAck()
|
||||
const [localClose, setLocalClose] = useState(false)
|
||||
useEffect(() => {
|
||||
if (!sessionStorage.getItem(ANNOUNCEMENT_RENDERED)) {
|
||||
acknowledge({ renderCount: acks?.renderCount + 1 })
|
||||
@@ -108,6 +109,7 @@ export function FiatOnrampAnnouncement() {
|
||||
}, [acknowledge, acks])
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setLocalClose(true)
|
||||
localStorage.setItem(ANNOUNCEMENT_DISMISSED, 'true')
|
||||
}, [])
|
||||
|
||||
@@ -128,7 +130,8 @@ export function FiatOnrampAnnouncement() {
|
||||
localStorage.getItem(ANNOUNCEMENT_DISMISSED) ||
|
||||
acks?.renderCount >= MAX_RENDER_COUNT ||
|
||||
isMobile ||
|
||||
openModal !== null
|
||||
openModal !== null ||
|
||||
localClose
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ $scrollOverlay?: boo
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
align-items: flex-end;
|
||||
}
|
||||
overflow-y: ${({ $scrollOverlay }) => $scrollOverlay && 'scroll'};
|
||||
justify-content: center;
|
||||
|
||||
@@ -27,7 +30,6 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ $scrollOverlay?: boo
|
||||
type StyledDialogProps = {
|
||||
$minHeight?: number | false
|
||||
$maxHeight?: number
|
||||
$isBottomSheet?: boolean
|
||||
$scrollOverlay?: boolean
|
||||
$hideBorder?: boolean
|
||||
$maxWidth: number
|
||||
@@ -40,14 +42,12 @@ const StyledDialogContent = styled(AnimatedDialogContent)<StyledDialogProps>`
|
||||
&[data-reach-dialog-content] {
|
||||
margin: auto;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: ${({ theme, $hideBorder }) => !$hideBorder && `1px solid ${theme.deprecated_bg1}`};
|
||||
border: ${({ theme, $hideBorder }) => !$hideBorder && `1px solid ${theme.backgroundOutline}`};
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
padding: 0px;
|
||||
width: 50vw;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
align-self: ${({ $isBottomSheet }) => $isBottomSheet && 'flex-end'};
|
||||
max-width: ${({ $maxWidth }) => $maxWidth}px;
|
||||
${({ $maxHeight }) =>
|
||||
$maxHeight &&
|
||||
@@ -61,22 +61,17 @@ const StyledDialogContent = styled(AnimatedDialogContent)<StyledDialogProps>`
|
||||
`}
|
||||
display: ${({ $scrollOverlay }) => ($scrollOverlay ? 'inline-table' : 'flex')};
|
||||
border-radius: 20px;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.md}px) {
|
||||
width: 65vw;
|
||||
margin: auto;
|
||||
`}
|
||||
${({ theme, $isBottomSheet }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
width: 85vw;
|
||||
${
|
||||
$isBottomSheet &&
|
||||
css`
|
||||
width: 100vw;
|
||||
border-radius: 20px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
`
|
||||
}
|
||||
`}
|
||||
}
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
border-radius: 20px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -91,7 +86,6 @@ interface ModalProps {
|
||||
children?: React.ReactNode
|
||||
$scrollOverlay?: boolean
|
||||
hideBorder?: boolean
|
||||
isBottomSheet?: boolean
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
@@ -104,7 +98,6 @@ export default function Modal({
|
||||
children,
|
||||
onSwipe = onDismiss,
|
||||
$scrollOverlay,
|
||||
isBottomSheet = isMobile,
|
||||
hideBorder = false,
|
||||
}: ModalProps) {
|
||||
const fadeTransition = useTransition(isOpen, {
|
||||
@@ -148,7 +141,6 @@ export default function Modal({
|
||||
aria-label="dialog"
|
||||
$minHeight={minHeight}
|
||||
$maxHeight={maxHeight}
|
||||
$isBottomSheet={isBottomSheet}
|
||||
$scrollOverlay={$scrollOverlay}
|
||||
$hideBorder={hideBorder}
|
||||
$maxWidth={maxWidth}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { UniIcon } from 'nft/components/icons'
|
||||
import { useProfilePageState } from 'nft/hooks'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { ReactNode } from 'react'
|
||||
import { NavLink, NavLinkProps, useLocation, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -79,6 +82,8 @@ export const PageTabs = () => {
|
||||
|
||||
const Navbar = () => {
|
||||
const isNftPage = useIsNftPage()
|
||||
const sellPageState = useProfilePageState((state) => state.state)
|
||||
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
@@ -120,7 +125,7 @@ const Navbar = () => {
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
{isNftPage && <Bag />}
|
||||
{isNftPage && (!isNftListV2 || sellPageState !== ProfilePageStateType.LISTING) && <Bag />}
|
||||
{!isNftPage && (
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<ChainSelector />
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function TokenSafetyMessage({ warning, tokenAddress }: TokenSafet
|
||||
const { heading, description } = getWarningCopy(warning)
|
||||
|
||||
return (
|
||||
<Label color={textColor} backgroundColor={backgroundColor}>
|
||||
<Label data-cy="token-safety-message" color={textColor} backgroundColor={backgroundColor}>
|
||||
<TitleRow>
|
||||
{warning.canProceed ? <AlertTriangle size="16px" /> : <Slash size="16px" />}
|
||||
<Title marginLeft="7px">{warning.message}</Title>
|
||||
|
||||
@@ -103,9 +103,8 @@ export function AboutSection({ address, chainId, description, homepageUrl, twitt
|
||||
<ThemedText.SubHeaderSmall>
|
||||
<Trans>Links</Trans>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ResourcesContainer>
|
||||
<ResourcesContainer data-cy="resources-container">
|
||||
<Resource
|
||||
data-testid="token-details-about-section-explorer-link"
|
||||
name={chainId === SupportedChainId.MAINNET ? 'Etherscan' : 'Block Explorer'}
|
||||
link={`${baseExplorerUrl}${address === 'NATIVE' ? '' : 'address/' + address}`}
|
||||
/>
|
||||
|
||||
@@ -275,7 +275,7 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChartHeader>
|
||||
<ChartHeader data-cy="chart-header">
|
||||
{displayPrice.value ? (
|
||||
<>
|
||||
<TokenPrice>{formatDollar({ num: displayPrice.value, isPrice: true })}</TokenPrice>
|
||||
@@ -294,7 +294,7 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod }
|
||||
{!chartAvailable ? (
|
||||
<MissingPriceChart width={width} height={graphHeight} message={!!displayPrice.value && missingPricesMessage} />
|
||||
) : (
|
||||
<svg width={width} height={graphHeight} style={{ minWidth: '100%' }}>
|
||||
<svg data-cy="price-chart" width={width} height={graphHeight} style={{ minWidth: '100%' }}>
|
||||
<AnimatedInLineChart
|
||||
data={prices}
|
||||
getX={getX}
|
||||
@@ -411,7 +411,7 @@ function MissingPriceChart({ width, height, message }: { width: number; height:
|
||||
const theme = useTheme()
|
||||
const midPoint = height / 2 + 45
|
||||
return (
|
||||
<StyledMissingChart width={width} height={height} style={{ minWidth: '100%' }}>
|
||||
<StyledMissingChart data-cy="missing-chart" width={width} height={height} style={{ minWidth: '100%' }}>
|
||||
<path
|
||||
d={`M 0 ${midPoint} Q 104 ${midPoint - 70}, 208 ${midPoint} T 416 ${midPoint}
|
||||
M 416 ${midPoint} Q 520 ${midPoint - 70}, 624 ${midPoint} T 832 ${midPoint}`}
|
||||
|
||||
@@ -45,9 +45,19 @@ export const StatsWrapper = styled.div`
|
||||
|
||||
type NumericStat = number | undefined | null
|
||||
|
||||
function Stat({ value, title, description }: { value: NumericStat; title: ReactNode; description?: ReactNode }) {
|
||||
function Stat({
|
||||
dataCy,
|
||||
value,
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
dataCy: string
|
||||
value: NumericStat
|
||||
title: ReactNode
|
||||
description?: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<StatWrapper>
|
||||
<StatWrapper data-cy={`${dataCy}`}>
|
||||
<MouseoverTooltip text={description}>{title}</MouseoverTooltip>
|
||||
<StatPrice>{formatNumber(value, NumberType.FiatTokenStats)}</StatPrice>
|
||||
</StatWrapper>
|
||||
@@ -71,11 +81,13 @@ export default function StatsSection(props: StatsSectionProps) {
|
||||
<TokenStatsSection>
|
||||
<StatPair>
|
||||
<Stat
|
||||
dataCy="tvl"
|
||||
value={TVL}
|
||||
description={HEADER_DESCRIPTIONS[TokenSortMethod.TOTAL_VALUE_LOCKED]}
|
||||
title={<Trans>TVL</Trans>}
|
||||
/>
|
||||
<Stat
|
||||
dataCy="volume-24h"
|
||||
value={volume24H}
|
||||
description={
|
||||
<Trans>
|
||||
@@ -86,8 +98,8 @@ export default function StatsSection(props: StatsSectionProps) {
|
||||
/>
|
||||
</StatPair>
|
||||
<StatPair>
|
||||
<Stat value={priceLow52W} title={<Trans>52W low</Trans>} />
|
||||
<Stat value={priceHigh52W} title={<Trans>52W high</Trans>} />
|
||||
<Stat dataCy="52w-low" value={priceLow52W} title={<Trans>52W low</Trans>} />
|
||||
<Stat dataCy="52w-high" value={priceHigh52W} title={<Trans>52W high</Trans>} />
|
||||
</StatPair>
|
||||
</TokenStatsSection>
|
||||
</StatsWrapper>
|
||||
|
||||
@@ -4,7 +4,6 @@ import styled from 'styled-components/macro'
|
||||
|
||||
const Menu = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
overflow: auto;
|
||||
max-height: 450px;
|
||||
@@ -58,8 +57,9 @@ const StyledChevron = styled(ChevronLeft)`
|
||||
const BackSection = styled.div`
|
||||
position: absolute;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
width: 99%;
|
||||
padding: 0 16px 16px 16px;
|
||||
width: -webkit-fill-available;
|
||||
margin: 0px 2vw 0px 0px;
|
||||
padding: 0px 0px 2vh 0px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
cursor: default;
|
||||
display: flex;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function usePermit2Flag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.permit2)
|
||||
return useBaseFlag(FeatureFlag.permit2, BaseVariant.Enabled)
|
||||
}
|
||||
|
||||
export function usePermit2Enabled(): boolean {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'utils/signTypedData'
|
||||
|
||||
import { AllowanceTransfer, MaxAllowanceTransferAmount, PERMIT2_ADDRESS, PermitSingle } from '@uniswap/permit2-sdk'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
|
||||
30
src/nft/components/profile/list/ListModal.tsx
Normal file
30
src/nft/components/profile/list/ListModal.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import ListingModal from 'nft/components/bag/profile/ListingModal'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Overlay } from 'nft/components/modals/Overlay'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
const ListModalWrapper = styled.div`
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 420px;
|
||||
z-index: ${Z_INDEX.modalOverTooltip};
|
||||
background: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 20px;
|
||||
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
padding: 0px 12px 4px;
|
||||
`
|
||||
|
||||
export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
return (
|
||||
<Portal>
|
||||
<ListModalWrapper>
|
||||
<ListingModal />
|
||||
</ListModalWrapper>
|
||||
<Overlay onClick={overlayClick} />
|
||||
</Portal>
|
||||
)
|
||||
}
|
||||
@@ -9,14 +9,16 @@ import { BackArrowIcon } from 'nft/components/icons'
|
||||
import { headlineLarge, headlineSmall } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { useBag, useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks'
|
||||
import { LIST_PAGE_MARGIN } from 'nft/pages/profile/profile'
|
||||
import { ListingStatus, ProfilePageStateType } from 'nft/types'
|
||||
import { fetchPrice, formatEth, formatUsdPrice } from 'nft/utils'
|
||||
import { ListingMarkets } from 'nft/utils/listNfts'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import { ListModal } from './ListModal'
|
||||
import { NFTListingsGrid } from './NFTListingsGrid'
|
||||
import { SelectMarketplacesDropdown } from './SelectMarketplacesDropdown'
|
||||
import { SetDurationModal } from './SetDurationModal'
|
||||
@@ -37,12 +39,16 @@ const ButtonsWrapper = styled(Row)`
|
||||
width: min-content;
|
||||
`
|
||||
|
||||
const MarketWrap = styled.section`
|
||||
const MarketWrap = styled.section<{ isNftListV2: boolean }>`
|
||||
gap: 48px;
|
||||
margin: 0px auto;
|
||||
padding: 0px 16px;
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
${({ isNftListV2 }) => !isNftListV2 && v1Padding}
|
||||
`
|
||||
|
||||
const v1Padding = css`
|
||||
padding: 0px 16px;
|
||||
|
||||
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
padding: 0px 44px;
|
||||
@@ -87,8 +93,10 @@ const FloatingConfirmationBar = styled(Row)`
|
||||
background: ${({ theme }) => theme.backgroundSurface};
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
margin: 0px 156px;
|
||||
width: calc(100vw - 312px);
|
||||
width: calc(100vw - ${LIST_PAGE_MARGIN * 2}px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
max-width: 1200px;
|
||||
z-index: ${Z_INDEX.under_dropdown};
|
||||
`
|
||||
|
||||
@@ -130,6 +138,7 @@ export const ListPage = () => {
|
||||
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
||||
const anyListingsMissingPrice = useMemo(() => !!listings.find((listing) => !listing.price), [listings])
|
||||
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
|
||||
const [showListModal, toggleShowListModal] = useReducer((s) => !s, false)
|
||||
|
||||
useEffect(() => {
|
||||
fetchPrice().then((price) => {
|
||||
@@ -158,7 +167,7 @@ export const ListPage = () => {
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<MarketWrap>
|
||||
<MarketWrap isNftListV2={isNftListV2}>
|
||||
<ListingHeader>
|
||||
<TitleWrapper>
|
||||
<BackArrowIcon
|
||||
@@ -201,7 +210,7 @@ export const ListPage = () => {
|
||||
</ProceedsWrapper>
|
||||
<ListingButtonWrapper>
|
||||
<ListingButton
|
||||
onClick={toggleBag}
|
||||
onClick={isNftListV2 ? toggleShowListModal : toggleBag}
|
||||
buttonText={anyListingsMissingPrice ? t`Set prices to continue` : t`Start listing`}
|
||||
/>
|
||||
</ListingButtonWrapper>
|
||||
@@ -215,6 +224,11 @@ export const ListPage = () => {
|
||||
<ListingButton onClick={toggleBag} buttonText="Continue listing" />
|
||||
</MobileListButtonWrapper>
|
||||
)}
|
||||
{isNftListV2 && showListModal && (
|
||||
<>
|
||||
<ListModal overlayClick={toggleShowListModal} />
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { InterfacePageName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Center, Column } from 'nft/components/Flex'
|
||||
import { ListPage } from 'nft/components/profile/list/ListPage'
|
||||
@@ -14,6 +15,8 @@ import { useToggleWalletModal } from 'state/application/hooks'
|
||||
|
||||
import * as styles from './profile.css'
|
||||
|
||||
export const LIST_PAGE_MARGIN = 156
|
||||
|
||||
const SHOPPING_BAG_WIDTH = 360
|
||||
|
||||
const ProfileContent = () => {
|
||||
@@ -42,6 +45,7 @@ const ProfileContent = () => {
|
||||
}
|
||||
}, [account, resetSellAssets, setSellPageState, clearCollectionFilters])
|
||||
const cartExpanded = useBag((state) => state.bagExpanded)
|
||||
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
||||
|
||||
return (
|
||||
<Trace page={InterfacePageName.NFT_PROFILE_PAGE} shouldLogImpression>
|
||||
@@ -50,7 +54,18 @@ const ProfileContent = () => {
|
||||
<title>Genie | Sell</title>
|
||||
</Head> */}
|
||||
{account ? (
|
||||
<Box style={{ width: `calc(100% - ${cartExpanded ? SHOPPING_BAG_WIDTH : 0}px)` }}>
|
||||
<Box
|
||||
style={{
|
||||
width: `calc(100% - ${
|
||||
cartExpanded && (!isNftListV2 || sellPageState === ProfilePageStateType.VIEWING)
|
||||
? SHOPPING_BAG_WIDTH
|
||||
: isNftListV2
|
||||
? LIST_PAGE_MARGIN * 2
|
||||
: 0
|
||||
}px)`,
|
||||
margin: isNftListV2 ? `0px ${LIST_PAGE_MARGIN}px` : 'unset',
|
||||
}}
|
||||
>
|
||||
{sellPageState === ProfilePageStateType.VIEWING ? <ProfilePage /> : <ListPage />}
|
||||
</Box>
|
||||
) : (
|
||||
|
||||
30
src/utils/signTypedData.ts
Normal file
30
src/utils/signTypedData.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { _TypedDataEncoder } from '@ethersproject/hash'
|
||||
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||
|
||||
/**
|
||||
* Overrides the _signTypedData method to add support for wallets without EIP-712 support (eg Zerion) by adding a fallback to eth_sign.
|
||||
* The implementation is copied from ethers (and linted), except for the catch statement, which removes the logger and adds the fallback.
|
||||
* @see https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/providers/src.ts/json-rpc-provider.ts#L334
|
||||
*/
|
||||
JsonRpcSigner.prototype._signTypedData = async function signTypedDataWithFallbacks(this, domain, types, value) {
|
||||
// Populate any ENS names (in-place)
|
||||
const populated = await _TypedDataEncoder.resolveNames(domain, types, value, (name: string) => {
|
||||
return this.provider.resolveName(name) as Promise<string>
|
||||
})
|
||||
|
||||
const address = await this.getAddress()
|
||||
|
||||
try {
|
||||
return await this.provider.send('eth_signTypedData_v4', [
|
||||
address.toLowerCase(),
|
||||
JSON.stringify(_TypedDataEncoder.getPayload(populated.domain, types, populated.value)),
|
||||
])
|
||||
} catch (error) {
|
||||
if (typeof error.message === 'string' && error.message.match(/not found/i)) {
|
||||
console.warn('eth_signTypedData_v4 failed, falling back to eth_sign:', error)
|
||||
const hash = _TypedDataEncoder.hash(populated.domain, types, populated.value)
|
||||
return await this.provider.send('eth_sign', [address, hash])
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user