Commit a0f20c54 authored by eddie's avatar eddie Committed by GitHub

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