Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Adjust overview widget on dashboard for new design #2782

Merged
merged 6 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions cypress/e2e/pages/dashboard.pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const transactionQueueStr = 'Pending transactions'
const noTransactionStr = 'This Safe has no queued transactions'
const overviewStr = 'Overview'
const viewAssetsStr = 'View assets'
const sendStr = 'Send'
const receiveStr = 'Receive'
const tokensStr = 'Tokens'
const nftStr = 'NFTs'
const viewAllStr = 'View all'
Expand All @@ -27,11 +29,10 @@ export function verifyOverviewWidgetData() {

cy.get('@overviewSection').within(() => {
// Prefix is separated across elements in EthHashInfo
cy.contains(constants.SEPOLIA_TEST_SAFE_5).should('exist')
cy.get('h2').contains('Overview')
cy.get(`a[href="${constants.BALANCE_URL}${encodeURIComponent(constants.SEPOLIA_TEST_SAFE_5)}"]`).contains(
viewAssetsStr,
)
cy.get(`a[href="${constants.BALANCE_URL}${encodeURIComponent(constants.SEPOLIA_TEST_SAFE_5)}"]`).contains('Tokens')
cy.get('button').contains(sendStr)
cy.get('button').contains(receiveStr)
})
}

Expand Down
3 changes: 3 additions & 0 deletions public/images/common/arrow-se.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/common/arrow-top-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
241 changes: 154 additions & 87 deletions src/components/dashboard/Overview/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,92 +1,94 @@
import QrCodeButton from '@/components/sidebar/QrCodeButton'
import { TxModalContext } from '@/components/tx-flow'
import NewTxMenu from '@/components/tx-flow/flows/NewTx'
import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps'
import { OVERVIEW_EVENTS, trackEvent } from '@/services/analytics'
import { useAppSelector } from '@/store'
import { selectCurrency } from '@/store/settingsSlice'
import { formatCurrency } from '@/utils/formatNumber'
import type { ReactElement } from 'react'
import { useMemo } from 'react'
import { useContext, useMemo } from 'react'
import { useRouter } from 'next/router'
import Link from 'next/link'
import styled from '@emotion/styled'
import { Box, Button, Grid, Skeleton, Typography } from '@mui/material'
import { Box, Button, Divider, Grid, Skeleton, Typography } from '@mui/material'
import { Card, WidgetBody, WidgetContainer } from '../styled'
import useSafeInfo from '@/hooks/useSafeInfo'
import { useCurrentChain } from '@/hooks/useChains'
import SafeIcon from '@/components/common/SafeIcon'
import ChainIndicator from '@/components/common/ChainIndicator'
import EthHashInfo from '@/components/common/EthHashInfo'
import { AppRoutes } from '@/config/routes'
import useSafeAddress from '@/hooks/useSafeAddress'
import useCollectibles from '@/hooks/useCollectibles'
import type { UrlObject } from 'url'
import { useVisibleBalances } from '@/hooks/useVisibleBalances'
import AddIcon from '@mui/icons-material/Add'
import ArrowIconNW from '@/public/images/common/arrow-top-right.svg'
import ArrowIconSE from '@/public/images/common/arrow-se.svg'

const IdenticonContainer = styled.div`
position: relative;
margin-bottom: var(--space-2);
`

const StyledText = styled(Typography)`
margin-top: 8px;
font-size: 24px;
font-weight: bold;
`

const NetworkLabelContainer = styled.div`
position: absolute;
top: var(--space-3);
right: var(--space-3);

& span {
bottom: auto;
}
`

const ValueSkeleton = () => <Skeleton variant="text" width={30} />
const ValueSkeleton = () => <Skeleton variant="text" width={20} />

const SkeletonOverview = (
<Card>
<Grid container>
<Grid item xs={12}>
<IdenticonContainer>
<Skeleton variant="circular" width="48px" height="48px" />
</IdenticonContainer>

<Box my={4}>
<Typography fontSize="lg">
<Skeleton variant="text" height={28} />
</Typography>
<Skeleton variant="text" height={21} />
<Box mb={2}>
<Skeleton variant="text" width={100} height={21} />
<Skeleton variant="text" width={120} height={40} />
</Box>
<NetworkLabelContainer>

<Box position="absolute" right="24px" top="24px">
<Skeleton variant="text" width="80px" />
</NetworkLabelContainer>
</Box>

<Divider sx={{ mb: '12px' }} />
</Grid>
</Grid>
<Grid container>
<Grid item xs={3}>
<Typography color="border.main" variant="body2">
Tokens
<Grid item container xs={6} gap={2}>
<Typography
color="border.main"
variant="caption"
fontWeight="bold"
display="flex"
gap={1}
textTransform="uppercase"
>
<ValueSkeleton /> Tokens
</Typography>
<StyledText fontSize="lg">
<ValueSkeleton />
</StyledText>
</Grid>
<Grid item xs={3}>
<Typography color="border.main" variant="body2">
NFTs
<Typography
color="border.main"
variant="caption"
fontWeight="bold"
display="flex"
gap={1}
textTransform="uppercase"
>
<ValueSkeleton /> NFTs
</Typography>
<StyledText fontSize="lg">
<ValueSkeleton />
</StyledText>
</Grid>
</Grid>
<Grid container mt={3} gap={1}>
<Skeleton variant="rounded" width="115px" height="40px" />
<Skeleton variant="rounded" width="115px" height="40px" />
</Grid>
</Card>
)

const Overview = (): ReactElement => {
const currency = useAppSelector(selectCurrency)
const router = useRouter()
const safeAddress = useSafeAddress()
const { safe, safeLoading, safeLoaded } = useSafeInfo()
const { safeLoading, safeLoaded } = useSafeInfo()
const { balances, loading: balancesLoading } = useVisibleBalances()
const [nfts] = useCollectibles()
const [nfts, , nftsLoading] = useCollectibles()
const chain = useCurrentChain()
const { chainId } = chain || {}
const { setTxFlow } = useContext(TxModalContext)
const [apps] = useRemoteSafeApps()

const fiatTotal = useMemo(
() => (balances.fiatTotal ? formatCurrency(balances.fiatTotal, currency) : ''),
[currency, balances.fiatTotal],
)

const rampSafeApp = apps?.find((app) => app.name === 'Ramp Network')

const assetsLink: UrlObject = {
pathname: AppRoutes.balances.index,
Expand All @@ -102,6 +104,12 @@ const Overview = (): ReactElement => {
const nftsCount = useMemo(() => (nfts ? `${nfts.next ? '>' : ''}${nfts.results.length}` : ''), [nfts])

const isInitialState = !safeLoaded && !safeLoading
const isLoading = safeLoading || balancesLoading || nftsLoading || isInitialState

const handleOnSend = () => {
setTxFlow(<NewTxMenu />, undefined, false)
trackEvent(OVERVIEW_EVENTS.NEW_TRANSACTION)
}

return (
<WidgetContainer>
Expand All @@ -110,56 +118,115 @@ const Overview = (): ReactElement => {
</Typography>

<WidgetBody>
{safeLoading || isInitialState ? (
{isLoading ? (
SkeletonOverview
) : (
<Card>
<Card style={{ display: 'flex', flexDirection: 'column' }}>
<Grid container pb={2}>
<SafeIcon address={safeAddress} threshold={safe.threshold} owners={safe.owners.length} size={48} />

<Grid item>
<Typography variant="body2" color="primary.light">
Total asset value
</Typography>
<Typography variant="h2">{fiatTotal}</Typography>
</Grid>
<Grid item xs />

<Grid item>
<ChainIndicator chainId={chainId} inline />
</Grid>
</Grid>

<Box mt={2} mb={4}>
{safeAddress ? (
<EthHashInfo showAvatar={false} address={safeAddress} shortAddress={false} showCopyButton hasExplorer />
) : (
<Skeleton />
)}
</Box>
<Divider />

<Grid container>
<Grid item xs={3}>
<Grid container pt="12px">
<Grid item container xs={6} gap={1}>
<Link href={assetsLink} passHref>
<Typography color="border.main" variant="body2">
Tokens
</Typography>
<StyledText fontSize="lg">{balancesLoading ? <ValueSkeleton /> : tokenCount}</StyledText>
<Box
sx={{
display: 'flex',
borderRight: '1px solid',
borderColor: ({ palette }) => palette.border.light,
gap: 0.5,
pr: 1,
}}
>
<Typography variant="caption" component="span">
{balancesLoading ? <ValueSkeleton /> : tokenCount}
</Typography>{' '}
<Typography
variant="caption"
component="span"
color="border.main"
textTransform="uppercase"
fontWeight="bold"
>
Tokens
</Typography>
</Box>
</Link>
</Grid>

<Grid item xs={3}>
<Link href={nftsLink} passHref>
<Typography color="border.main" variant="body2">
NFTs
</Typography>
<StyledText fontSize="lg">{nftsCount || <ValueSkeleton />}</StyledText>
<Box display="flex" pr={1} gap={0.5}>
<Typography variant="caption" component="span">
{nftsCount || <ValueSkeleton />}
</Typography>{' '}
<Typography
variant="caption"
component="span"
color="border.main"
textTransform="uppercase"
fontWeight="bold"
>
NFTs
</Typography>
</Box>
</Link>
</Grid>
<Grid item xs />

<Grid item>
<Box display="flex" height={1} alignItems="flex-end" justifyContent="flex-end">
<Link href={assetsLink} passHref legacyBehavior>
<Button size="medium" variant="contained" color="primary">
View assets
</Grid>
<Grid item mt="auto">
<Grid container mt={3} spacing={1} flexWrap="wrap">
{rampSafeApp && (
<Grid item xs={12} sm="auto">
<Link
href={{
pathname: AppRoutes.apps.open,
query: { safe: router.query.safe, appUrl: rampSafeApp.url },
}}
passHref
legacyBehavior
>
<Button
size="small"
variant="contained"
color="primary"
startIcon={<AddIcon />}
sx={{ height: 1 }}
fullWidth
>
Buy crypto
</Button>
</Link>
</Grid>
)}
<Grid item xs={6} sm="auto">
<Button
onClick={handleOnSend}
size="small"
variant="outlined"
color="primary"
startIcon={<ArrowIconNW />}
fullWidth
>
Send
</Button>
</Grid>
<Grid item xs={6} sm="auto">
<QrCodeButton>
<Button size="small" variant="outlined" color="primary" startIcon={<ArrowIconSE />} fullWidth>
Receive
</Button>
</Link>
</Box>
</QrCodeButton>
</Grid>
</Grid>
</Grid>
</Card>
Expand Down
Loading