feat: implement new designs for tx notifs (#6232)
* feat: re-add transaction activity popups * feat: implement new designs for tx notifs * fix: address comments * fix: remove color from alert icon * fix: nits * fix: remove null check * fix: fix
This commit is contained in:
parent
da79abbc0d
commit
a0f20c54d8
@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
@ -12,26 +12,31 @@ const RowNoFlex = styled(AutoRow)`
|
||||
flex-wrap: nowrap;
|
||||
`
|
||||
|
||||
const ColumnContainer = styled(AutoColumn)`
|
||||
margin: 0 12px;
|
||||
`
|
||||
|
||||
export const PopupAlertTriangle = styled(AlertTriangleFilled)`
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
`
|
||||
|
||||
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) {
|
||||
const chainInfo = getChainInfo(chainId)
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<RowNoFlex>
|
||||
<AutoColumn gap="sm">
|
||||
<RowNoFlex style={{ alignItems: 'center' }}>
|
||||
<div style={{ paddingRight: 13 }}>
|
||||
<AlertTriangle color={theme.accentWarning} size={24} display="flex" />
|
||||
</div>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>Failed to switch networks</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</RowNoFlex>
|
||||
<RowNoFlex gap="12px">
|
||||
<PopupAlertTriangle />
|
||||
<ColumnContainer gap="sm">
|
||||
<ThemedText.SubHeader color="textSecondary">
|
||||
<Trans>Failed to switch networks</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
|
||||
<ThemedText.BodySmall>
|
||||
<ThemedText.BodySmall color="textSecondary">
|
||||
<Trans>To use Uniswap on {chainInfo.label}, switch the network in your wallet’s settings.</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</AutoColumn>
|
||||
</ColumnContainer>
|
||||
</RowNoFlex>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { animated } from 'react-spring'
|
||||
import { useSpring } from 'react-spring'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
@ -24,7 +22,7 @@ const Popup = styled.div`
|
||||
padding: 1em;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
padding-right: 35px;
|
||||
overflow: hidden;
|
||||
@ -36,16 +34,6 @@ const Popup = styled.div`
|
||||
}
|
||||
`}
|
||||
`
|
||||
const Fader = styled.div`
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
`
|
||||
|
||||
const AnimatedFader = animated(Fader)
|
||||
|
||||
export default function PopupItem({
|
||||
removeAfterMs,
|
||||
@ -71,11 +59,6 @@ export default function PopupItem({
|
||||
}, [removeAfterMs, removeThisPopup])
|
||||
|
||||
const theme = useTheme()
|
||||
const faderStyle = useSpring({
|
||||
from: { width: '100%' },
|
||||
to: { width: '0%' },
|
||||
config: { duration: removeAfterMs ?? undefined },
|
||||
})
|
||||
|
||||
let popupContent
|
||||
if ('txn' in content) {
|
||||
@ -88,7 +71,6 @@ export default function PopupItem({
|
||||
<Popup>
|
||||
<StyledClose color={theme.textSecondary} onClick={removeThisPopup} />
|
||||
{popupContent}
|
||||
{removeAfterMs !== null ? <AnimatedFader style={faderStyle} /> : null}
|
||||
</Popup>
|
||||
) : null
|
||||
}
|
||||
|
@ -1,46 +1,66 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { TransactionSummary } from 'components/AccountDetails/TransactionSummary'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { useContext } from 'react'
|
||||
import { AlertCircle, CheckCircle } from 'react-feather'
|
||||
import Column from 'components/Column'
|
||||
import { parseLocalActivity } from 'components/WalletDropdown/MiniPortfolio/Activity/parseLocal'
|
||||
import { PortfolioLogo } from 'components/WalletDropdown/MiniPortfolio/PortfolioLogo'
|
||||
import PortfolioRow from 'components/WalletDropdown/MiniPortfolio/PortfolioRow'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import { useCombinedActiveList } from 'state/lists/hooks'
|
||||
import { useTransaction } from 'state/transactions/hooks'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { TransactionDetails } from 'state/transactions/types'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
const RowNoFlex = styled(AutoRow)`
|
||||
flex-wrap: nowrap;
|
||||
import { PopupAlertTriangle } from './FailedNetworkSwitchPopup'
|
||||
|
||||
const Descriptor = styled(ThemedText.BodySmall)`
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
function TransactionPopupContent({ tx, chainId }: { tx: TransactionDetails; chainId: number }) {
|
||||
const success = tx.receipt?.status === 1
|
||||
const tokens = useCombinedActiveList()
|
||||
const activity = parseLocalActivity(tx, chainId, tokens)
|
||||
const { ENSName } = useENSName(activity?.otherAccount)
|
||||
|
||||
if (!activity) return null
|
||||
|
||||
const explorerUrl = getExplorerLink(chainId, tx.hash, ExplorerDataType.TRANSACTION)
|
||||
|
||||
return (
|
||||
<PortfolioRow
|
||||
left={
|
||||
success ? (
|
||||
<Column>
|
||||
<PortfolioLogo
|
||||
chainId={chainId}
|
||||
currencies={activity.currencies}
|
||||
images={activity.logos}
|
||||
accountAddress={activity.otherAccount}
|
||||
/>
|
||||
</Column>
|
||||
) : (
|
||||
<PopupAlertTriangle />
|
||||
)
|
||||
}
|
||||
title={<ThemedText.SubHeader fontWeight={500}>{activity.title}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<Descriptor color="textSecondary">
|
||||
{activity.descriptor}
|
||||
{ENSName ?? activity.otherAccount}
|
||||
</Descriptor>
|
||||
}
|
||||
onClick={() => window.open(explorerUrl, '_blank')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TransactionPopup({ hash }: { hash: string }) {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const tx = useTransaction(hash)
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
if (!tx) return null
|
||||
const success = Boolean(tx.receipt && tx.receipt.status === 1)
|
||||
if (!chainId || !tx) return null
|
||||
|
||||
return (
|
||||
<RowNoFlex>
|
||||
<div style={{ paddingRight: 16 }}>
|
||||
{success ? (
|
||||
<CheckCircle color={theme.accentSuccess} size={24} />
|
||||
) : (
|
||||
<AlertCircle color={theme.accentFailure} size={24} />
|
||||
)}
|
||||
</div>
|
||||
<AutoColumn gap="8px">
|
||||
<ThemedText.BodyPrimary fontWeight={500}>
|
||||
<TransactionSummary info={tx.info} />
|
||||
</ThemedText.BodyPrimary>
|
||||
{chainId && (
|
||||
<ExternalLink href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}>
|
||||
View on Explorer
|
||||
</ExternalLink>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</RowNoFlex>
|
||||
)
|
||||
return <TransactionPopupContent tx={tx} chainId={chainId} />
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding:
|
||||
position: fixed;
|
||||
top: ${({ extraPadding }) => (extraPadding ? '72px' : '64px')};
|
||||
right: 1rem;
|
||||
max-width: 355px !important;
|
||||
max-width: 376px !important;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import Column from 'components/Column'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { LoaderV2 } from 'components/Icons/LoadingSpinner'
|
||||
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import { PortfolioLogo } from '../PortfolioLogo'
|
||||
import PortfolioRow from '../PortfolioRow'
|
||||
import { useTimeSince } from './parseRemote'
|
||||
import { Activity } from './types'
|
||||
|
||||
const ActivityRowDescriptor = styled(ThemedText.BodySmall)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
const StyledTimestamp = styled(ThemedText.Caption)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-variant: small;
|
||||
font-feature-settings: 'tnum' on, 'lnum' on, 'ss02' on;
|
||||
`
|
||||
|
||||
export function ActivityRow({
|
||||
activity: { chainId, status, title, descriptor, logos, otherAccount, currencies, timestamp, hash },
|
||||
}: {
|
||||
activity: Activity
|
||||
}) {
|
||||
const { ENSName } = useENSName(otherAccount)
|
||||
const timeSince = useTimeSince(timestamp)
|
||||
|
||||
const explorerUrl = getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)
|
||||
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.MINI_PORTFOLIO_ACTIVITY_ROW}
|
||||
properties={{ hash, chain_id: chainId, explorer_url: explorerUrl }}
|
||||
>
|
||||
<PortfolioRow
|
||||
left={
|
||||
<Column>
|
||||
<PortfolioLogo chainId={chainId} currencies={currencies} images={logos} accountAddress={otherAccount} />
|
||||
</Column>
|
||||
}
|
||||
title={<ThemedText.SubHeader fontWeight={500}>{title}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<ActivityRowDescriptor color="textSecondary">
|
||||
{descriptor}
|
||||
{ENSName ?? otherAccount}
|
||||
</ActivityRowDescriptor>
|
||||
}
|
||||
right={
|
||||
status === TransactionStatus.Pending ? (
|
||||
<LoaderV2 />
|
||||
) : status === TransactionStatus.Confirmed ? (
|
||||
<StyledTimestamp>{timeSince}</StyledTimestamp>
|
||||
) : (
|
||||
<AlertTriangleFilled />
|
||||
)
|
||||
}
|
||||
onClick={() => window.open(explorerUrl, '_blank')}
|
||||
/>
|
||||
</TraceEvent>
|
||||
)
|
||||
}
|
@ -1,26 +1,20 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import Column from 'components/Column'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { LoaderV2 } from 'components/Icons/LoadingSpinner'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { useWalletDrawer } from 'components/WalletDropdown'
|
||||
import { getYear, isSameDay, isSameMonth, isSameWeek, isSameYear } from 'date-fns'
|
||||
import { TransactionStatus, useTransactionListQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { PollingInterval } from 'graphql/data/util'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { PortfolioLogo } from '../PortfolioLogo'
|
||||
import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
|
||||
import { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
|
||||
import { ActivityRow } from './ActivityRow'
|
||||
import { useLocalActivities } from './parseLocal'
|
||||
import { parseRemoteActivities, useTimeSince } from './parseRemote'
|
||||
import { parseRemoteActivities } from './parseRemote'
|
||||
import { Activity, ActivityMap } from './types'
|
||||
|
||||
interface ActivityGroup {
|
||||
@ -103,7 +97,7 @@ function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap =
|
||||
|
||||
const lastFetchedAtom = atom<number | undefined>(0)
|
||||
|
||||
export default function ActivityTab({ account }: { account: string }) {
|
||||
export function ActivityTab({ account }: { account: string }) {
|
||||
const [drawerOpen, toggleWalletDrawer] = useWalletDrawer()
|
||||
const [lastFetched, setLastFetched] = useAtom(lastFetchedAtom)
|
||||
|
||||
@ -160,56 +154,3 @@ export default function ActivityTab({ account }: { account: string }) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const StyledDescriptor = styled(ThemedText.BodySmall)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
const StyledTimestamp = styled(ThemedText.Caption)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-variant: small;
|
||||
font-feature-settings: 'tnum' on, 'lnum' on, 'ss02' on;
|
||||
`
|
||||
|
||||
function ActivityRow({ activity }: { activity: Activity }) {
|
||||
const { chainId, status, title, descriptor, logos, otherAccount, currencies } = activity
|
||||
const { ENSName } = useENSName(otherAccount)
|
||||
|
||||
const explorerUrl = getExplorerLink(activity.chainId, activity.hash, ExplorerDataType.TRANSACTION)
|
||||
const timeSince = useTimeSince(activity.timestamp)
|
||||
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.MINI_PORTFOLIO_ACTIVITY_ROW}
|
||||
properties={{ hash: activity.hash, chain_id: chainId, explorer_url: explorerUrl }}
|
||||
>
|
||||
<PortfolioRow
|
||||
left={
|
||||
<Column>
|
||||
<PortfolioLogo chainId={chainId} currencies={currencies} images={logos} accountAddress={otherAccount} />
|
||||
</Column>
|
||||
}
|
||||
title={<ThemedText.SubHeader fontWeight={500}>{title}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<StyledDescriptor color="textSecondary">
|
||||
{descriptor}
|
||||
{ENSName ?? otherAccount}
|
||||
</StyledDescriptor>
|
||||
}
|
||||
right={
|
||||
status === TransactionStatus.Pending ? (
|
||||
<LoaderV2 />
|
||||
) : status === TransactionStatus.Confirmed ? (
|
||||
<StyledTimestamp>{timeSince}</StyledTimestamp>
|
||||
) : (
|
||||
<AlertTriangleFilled />
|
||||
)
|
||||
}
|
||||
onClick={() => window.open(explorerUrl, '_blank')}
|
||||
/>
|
||||
</TraceEvent>
|
||||
)
|
||||
}
|
@ -126,7 +126,7 @@ function parseMigrateCreateV3(
|
||||
return { descriptor, currencies: [baseCurrency, quoteCurrency] }
|
||||
}
|
||||
|
||||
function parseLocalActivity(
|
||||
export function parseLocalActivity(
|
||||
details: TransactionDetails,
|
||||
chainId: SupportedChainId,
|
||||
tokens: TokenAddressMap
|
||||
|
@ -1,10 +1,9 @@
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { useMemo } from 'react'
|
||||
import styled, { css, keyframes } from 'styled-components/macro'
|
||||
|
||||
const RowWrapper = styled(Row)<{ onClick?: any }>`
|
||||
export const PortfolioRowWrapper = styled(Row)<{ onClick?: any }>`
|
||||
gap: 12px;
|
||||
height: 68px;
|
||||
padding: 0 16px;
|
||||
@ -14,7 +13,6 @@ const RowWrapper = styled(Row)<{ onClick?: any }>`
|
||||
${({ onClick }) => onClick && 'cursor: pointer'};
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.hoverDefault};
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
@ -28,39 +26,30 @@ export default function PortfolioRow({
|
||||
title,
|
||||
descriptor,
|
||||
right,
|
||||
setIsHover,
|
||||
onClick,
|
||||
}: {
|
||||
left: React.ReactNode
|
||||
title: React.ReactNode
|
||||
descriptor?: React.ReactNode
|
||||
right: React.ReactNode
|
||||
right?: React.ReactNode
|
||||
setIsHover?: (b: boolean) => void
|
||||
onClick?: () => void
|
||||
}) {
|
||||
const onHover = useMemo(
|
||||
() =>
|
||||
setIsHover && {
|
||||
onMouseEnter: () => setIsHover?.(true),
|
||||
onMouseLeave: () => setIsHover?.(false),
|
||||
},
|
||||
[setIsHover]
|
||||
)
|
||||
return (
|
||||
<RowWrapper {...onHover} onClick={onClick}>
|
||||
<PortfolioRowWrapper onClick={onClick}>
|
||||
{left}
|
||||
<AutoColumn grow>
|
||||
{title}
|
||||
{descriptor}
|
||||
</AutoColumn>
|
||||
<EndColumn>{right}</EndColumn>
|
||||
</RowWrapper>
|
||||
{right && <EndColumn>{right}</EndColumn>}
|
||||
</PortfolioRowWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
function PortfolioSkeletonRow({ shrinkRight }: { shrinkRight?: boolean }) {
|
||||
return (
|
||||
<RowWrapper>
|
||||
<PortfolioRowWrapper>
|
||||
<LoadingBubble height="40px" width="40px" round />
|
||||
<AutoColumn grow gap="4px">
|
||||
<LoadingBubble height="16px" width="60px" delay="300ms" />
|
||||
@ -76,7 +65,7 @@ function PortfolioSkeletonRow({ shrinkRight }: { shrinkRight?: boolean }) {
|
||||
</>
|
||||
)}
|
||||
</EndColumn>
|
||||
</RowWrapper>
|
||||
</PortfolioRowWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -9,9 +9,10 @@ import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import Activity from './Activity'
|
||||
import { ActivityTab } from './Activity/ActivityTab'
|
||||
import NFTs from './NFTs'
|
||||
import Pools from './Pools'
|
||||
import { PortfolioRowWrapper } from './PortfolioRow'
|
||||
import Tokens from './Tokens'
|
||||
|
||||
const Wrapper = styled(Column)`
|
||||
@ -20,6 +21,12 @@ const Wrapper = styled(Column)`
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
gap: 12px;
|
||||
|
||||
${PortfolioRowWrapper} {
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.hoverDefault};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Nav = styled(AutoRow)`
|
||||
@ -60,7 +67,7 @@ const Pages: Array<Page> = [
|
||||
{ title: <Trans>Pools</Trans>, component: Pools, loggingElementName: InterfaceElementName.MINI_PORTFOLIO_POOLS_TAB },
|
||||
{
|
||||
title: <Trans>Activity</Trans>,
|
||||
component: Activity,
|
||||
component: ActivityTab,
|
||||
loggingElementName: InterfaceElementName.MINI_PORTFOLIO_ACTIVITY_TAB,
|
||||
},
|
||||
]
|
||||
|
@ -25,7 +25,7 @@ export default function useSelectChain() {
|
||||
console.error('Failed to switch networks', error)
|
||||
|
||||
dispatch(updateConnectionError({ connectionType, error: error.message }))
|
||||
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
|
||||
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: 'failed-network-switch' }))
|
||||
}
|
||||
},
|
||||
[connector, dispatch, getConnection]
|
||||
|
Loading…
Reference in New Issue
Block a user