Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

feat: Dashboard grid layout #3795

Merged
merged 16 commits into from
Apr 20, 2022
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
2 changes: 1 addition & 1 deletion src/components/AppLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const ContentWrapper = styled.div`
flex-direction: column;
overflow-x: auto;

padding: 0 16px;
padding: 24px 40px;

> :nth-child(1) {
flex-grow: 1;
Expand Down
38 changes: 20 additions & 18 deletions src/components/Dashboard/CreateSafe.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ReactElement } from 'react'
import styled from 'styled-components'
import { Button, Title, Text } from '@gnosis.pm/safe-react-components'
import { Button, Text } from '@gnosis.pm/safe-react-components'
import Link from 'src/components/layout/Link'
import Track from 'src/components/Track'
import { OPEN_SAFE_ROUTE } from 'src/routes/routes'
import { CREATE_SAFE_EVENTS } from 'src/utils/events/createLoadSafe'
import { Card, WidgetBody, WidgetContainer, WidgetTitle } from 'src/components/Dashboard/styled'

export const CardContentContainer = styled.div`
display: flex;
Expand All @@ -19,24 +20,25 @@ export const CardDescriptionContainer = styled.div`

const CreateSafeWidget = (): ReactElement => {
return (
<CardContentContainer>
<Title size="sm" strong withoutMargin>
Create Safe
</Title>
<WidgetContainer>
<WidgetTitle>Create Safe</WidgetTitle>
<WidgetBody>
<Card>
<CardDescriptionContainer>
<Text size="xl">Create a new Safe that is controlled by one or multiple owners.</Text>
<Text size="xl">You will be required to pay a network fee for creating your new Safe.</Text>
</CardDescriptionContainer>

<CardDescriptionContainer>
<Text size="xl">Create a new Safe that is controlled by one or multiple owners.</Text>
<Text size="xl">You will be required to pay a network fee for creating your new Safe.</Text>
</CardDescriptionContainer>

<Track {...CREATE_SAFE_EVENTS.CREATE_BUTTON}>
<Button size="lg" color="primary" variant="contained" component={Link} to={OPEN_SAFE_ROUTE}>
<Text size="xl" color="white">
+ Create new Safe
</Text>
</Button>
</Track>
</CardContentContainer>
<Track {...CREATE_SAFE_EVENTS.CREATE_BUTTON}>
<Button size="lg" color="primary" variant="contained" component={Link} to={OPEN_SAFE_ROUTE}>
<Text size="xl" color="white">
+ Create new Safe
</Text>
</Button>
</Track>
</Card>
</WidgetBody>
</WidgetContainer>
)
}

Expand Down
73 changes: 37 additions & 36 deletions src/components/Dashboard/FeaturedApps/FeaturedApps.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,65 @@
import { ReactElement, useMemo } from 'react'
import { useAppList } from 'src/routes/safe/components/Apps/hooks/appList/useAppList'
import { Text } from '@gnosis.pm/safe-react-components'
import { Link } from 'react-router-dom'
import { getSafeAppUrl, SafeRouteParams } from 'src/routes/routes'
import { useSelector } from 'react-redux'
import { Box, Grid } from '@material-ui/core'

import styled from 'styled-components'
import { getSafeAppUrl, SafeRouteParams } from 'src/routes/routes'
import { currentSafe } from 'src/logic/safe/store/selectors'
import { getShortName } from 'src/config'
import { ReactElement, useMemo } from 'react'
import Row from 'src/components/layout/Row'
import Col from 'src/components/layout/Col'
import styled from 'styled-components'
import { Card, WidgetBody, WidgetContainer, WidgetTitle } from 'src/components/Dashboard/styled'

const FEATURED_APPS_TAGS = 'dashboard-widgets'
export const FEATURED_APPS_TAG = 'dashboard-widgets'

const StyledImage = styled.img`
max-width: 64px;
max-height: 64px;
width: 64px;
height: 64px;
`

const StyledLink = styled(Link)`
margin-top: 10px;
text-decoration: none;
`

const StyledRow = styled(Row)`
gap: 24px;
flex-wrap: inherit;
`

export const FeaturedApps = (): ReactElement => {
const { allApps } = useAppList()
const { address } = useSelector(currentSafe) ?? {}
const featuredApps = useMemo(() => allApps.filter((app) => app.tags?.includes(FEATURED_APPS_TAGS)), [allApps])
const featuredApps = useMemo(() => allApps.filter((app) => app.tags?.includes(FEATURED_APPS_TAG)), [allApps])

const routesSlug: SafeRouteParams = {
shortName: getShortName(),
safeAddress: address,
}

return (
<>
{featuredApps.map((app) => {
const appRoute = getSafeAppUrl(app.url, routesSlug)
return (
<StyledRow key={app.id} margin="lg">
<Col xs={2}>
<StyledImage src={app.iconUrl} alt={app.name} />
</Col>
<Col xs={10} layout="column">
<Text size="lg" strong>
{app.description}
</Text>
<StyledLink to={appRoute}>
<Text color="primary" size="lg" strong>
Use {app.name}
</Text>
</StyledLink>
</Col>
</StyledRow>
)
})}
</>
<WidgetContainer>
<WidgetTitle>Safe Apps</WidgetTitle>
<WidgetBody>
{featuredApps.map((app) => {
const appRoute = getSafeAppUrl(app.url, routesSlug)
return (
<Card key={app.id}>
<Grid container alignItems="center" spacing={3}>
<Grid item xs={3} md={2}>
<StyledImage src={app.iconUrl} alt={app.name} />
</Grid>
<Grid item xs={9} md={10}>
<Box mb={1}>
<Text size="xl">{app.description}</Text>
</Box>
<StyledLink to={appRoute}>
<Text color="primary" size="lg" strong>
Use {app.name}
</Text>
</StyledLink>
</Grid>
</Grid>
</Card>
)
})}
</WidgetBody>
</WidgetContainer>
)
}
163 changes: 107 additions & 56 deletions src/components/Dashboard/Overview/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import { ReactElement } from 'react'
import { useSelector } from 'react-redux'
import styled from 'styled-components'
import { Text, Identicon } from '@gnosis.pm/safe-react-components'
import { useHistory } from 'react-router-dom'
import { Box, Grid } from '@material-ui/core'
import { Skeleton } from '@material-ui/lab'

import { currentSafeLoaded, currentSafeWithNames } from 'src/logic/safe/store/selectors'
import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo'
import Row from 'src/components/layout/Row'
import Col from 'src/components/layout/Col'
import { primaryLite, primaryActive, smallFontSize, md } from 'src/theme/variables'
import { primaryLite, primaryActive, smallFontSize, md, lg } from 'src/theme/variables'
import NetworkLabel from 'src/components/NetworkLabel/NetworkLabel'
import { nftLoadedSelector, nftTokensSelector } from 'src/logic/collectibles/store/selectors'
import { Skeleton } from '@material-ui/lab'
import { Card, DashboardTitle } from 'src/components/Dashboard/styled'
import { WidgetBody, WidgetContainer } from 'src/components/Dashboard/styled'
import Button from 'src/components/layout/Button'
import { generateSafeRoute, SAFE_ROUTES } from 'src/routes/routes'
import { currentChainId } from 'src/logic/config/store/selectors'
import { getChainById } from 'src/config'

const IdenticonContainer = styled.div`
position: relative;
Expand All @@ -34,79 +40,124 @@ const SafeThreshold = styled.div`
`

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

const NetworkLabelContainer = styled.div`
position: absolute;
top: 0;
right: 0;
top: ${lg};
right: ${lg};

& span {
bottom: auto;
}
`

const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
position: relative;
`

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

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

<Box mb={2}>
<Text size="xl" strong>
<Skeleton variant="text" height={28} />
</Text>
<Skeleton variant="text" height={21} />
</Box>
<NetworkLabelContainer>
<Skeleton variant="text" width="80px" />
</NetworkLabelContainer>
</Grid>
</Grid>
<Grid container>
<Grid item xs={3}>
<Text color="inputDefault" size="lg">
Tokens
</Text>
<StyledText size="xl">{ValueSkeleton}</StyledText>
</Grid>
<Grid item xs={3}>
<Text color="inputDefault" size="lg">
NFTs
</Text>
<StyledText size="xl">{ValueSkeleton}</StyledText>
</Grid>
</Grid>
</Card>
)

const Overview = (): ReactElement => {
const { address, name, owners, threshold, balances } = useSelector(currentSafeWithNames)
const chainId = useSelector(currentChainId)
const { shortName } = getChainById(chainId)
const loaded = useSelector(currentSafeLoaded)
const nftTokens = useSelector(nftTokensSelector)
const nftLoaded = useSelector(nftLoadedSelector)
const history = useHistory()

const handleOpenAssets = (): void => {
history.push(generateSafeRoute(SAFE_ROUTES.ASSETS_BALANCES, { safeAddress: address, shortName }))
}

return (
<Container>
<Row margin="md">
<Col layout="column">
<IdenticonContainer>
{loaded ? (
<>
<SafeThreshold>
{threshold}/{owners.length}
</SafeThreshold>
<Identicon address={address} size="lg" />
</>
) : (
<Skeleton variant="circle" width="40px" height="40px" />
)}
</IdenticonContainer>
<Text size="xl" strong>
{loaded ? name : <Skeleton variant="text" />}
</Text>
{loaded ? <PrefixedEthHashInfo hash={address} textSize="lg" /> : <Skeleton variant="text" />}
</Col>
<Col end="xs">
<NetworkLabelContainer>
<NetworkLabel />
</NetworkLabelContainer>
</Col>
</Row>
<Row>
<Col layout="column" xs={3}>
<Text color="inputDefault" size="md">
Tokens
</Text>
<StyledText size="xl">{loaded ? balances.length : ValueSkeleton}</StyledText>
</Col>
<Col layout="column" xs={3}>
<Text color="inputDefault" size="md">
NFTs
</Text>
{nftTokens && <StyledText size="xl">{nftLoaded ? nftTokens.length : ValueSkeleton}</StyledText>}
</Col>
</Row>
</Container>
<WidgetContainer>
<DashboardTitle>Dashboard</DashboardTitle>
<WidgetBody>
{!loaded ? (
SkeletonOverview
) : (
<Card>
<Grid container>
<Grid item xs={12}>
<IdenticonContainer>
<SafeThreshold>
{threshold}/{owners.length}
</SafeThreshold>
<Identicon address={address} size="xl" />
</IdenticonContainer>
<Box mb={2} overflow="hidden">
<Text size="xl" strong>
{name}
</Text>
<PrefixedEthHashInfo hash={address} textSize="xl" textColor="placeHolder" />
</Box>
<NetworkLabelContainer>
<NetworkLabel />
</NetworkLabelContainer>
</Grid>
</Grid>
<Grid container>
<Grid item xs={3}>
<Text color="inputDefault" size="lg">
Tokens
</Text>
<StyledText size="xl">{balances.length}</StyledText>
</Grid>
<Grid item xs={3}>
<Text color="inputDefault" size="lg">
NFTs
</Text>
{nftTokens && <StyledText size="xl">{nftLoaded ? nftTokens.length : ValueSkeleton}</StyledText>}
</Grid>
<Grid item xs={6}>
<Box display="flex" height={1} alignItems="flex-end" justifyContent="flex-end">
<Button size="medium" variant="contained" color="primary" onClick={handleOpenAssets}>
View Assets
</Button>
</Box>
</Grid>
</Grid>
</Card>
)}
</WidgetBody>
</WidgetContainer>
)
}

Expand Down
Loading