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

Commit

Permalink
[Epic] Dashboard (#3696)
Browse files Browse the repository at this point in the history
* Epic: Dashboard

* feat: Add Safe route for dashboard (#3759)

* feat: Add safe specific dashboard route

* fix: Redirect user to Dashboard when adding/loading/removing a safe

* fix: Remove old dashboard route

* fix: Move Dashboard component inside SafeContainer to access transactions

* fix: Remove null fallback for wrapInSuspense, remove unnecessary React reference

* Dashboard: create/add safe widgets (#3763)

* feat: pending transactions widget (#3757)

* feat: pending transactions widget

* refactor: split PendingTxs component

* fix: code review remarks

* fix: change selectedSafe type

* fix: rename loading state variable in useOwnerSafes

* feat: fetch Redux queued txs in the Dashboard

* fix: revert changes in useOwnerSafes

* fix: use Skeleton component from MUI

* fix: return a spacer component instead of null for some TxInfo

* style: tweak the UI of the Skeleton component and PendingTxListItem

* fix: fetch store from inside the widget

Co-authored-by: Aaron Cook <[email protected]>

* fix: move location of PendingTxs files

* fix: parametrize MAX_TXS_DISPLAY from the Dashboard component

* fix: display one queued transaction per nonce

* Refactor how txns are mapped

Co-authored-by: Aaron Cook <[email protected]>
Co-authored-by: katspaugh <[email protected]>

* fix: move PendingTxs components to Dashboard folder

* feat: Add Home button to sidebar navigation (#3788)

* feat: Dashboard Safe Apps (#3738)

* feat: Add Safe route for dashboard (#3759)

* feat: Add safe specific dashboard route

* fix: Redirect user to Dashboard when adding/loading/removing a safe

* fix: Remove old dashboard route

* fix: Move Dashboard component inside SafeContainer to access transactions

* fix: Remove null fallback for wrapInSuspense, remove unnecessary React reference

* feat: add harcoded WC app

* Add a redirect for Safe Apps + Bookmark handler

* feat: display "official" apps after pinned apps

* Add "Explore" Card

* refactor: extract official app idss to enum

* fix: render after safe apps info response

* fix: import explore icon as module

* fix: remove duplicated safe apps

* fix: memoize safeApps data

* fix: change useMemo dependency

* fix: move related data inside the same function. Use hook isLoading.

* fix: GENERIC_APPS_ROUTE route

* feat: track timestamp when opening safeApp

* feat: track openingCount when opening safeApp

* feat: track txCount when creating a transaction from ReviewConfirm

* fix: keep previous data when tracking on opening

* fix: Adds ranking function for tracked safe apps

* fix: unify rankTrackedSafeApps input types

* fix: change localstorage prefered module

* feat: display top ranked apps

* fix: track opening SafeApp in a separate hook

* fix: move app count tracking to a separate module

* chore: Add comments to the sorting formula

* chore: move app usage related functions to the same file

* fix: improve the setItem in the LS logic

* feat: add Skeleton Cards

* fix: remove breadcrumbs in Dashboard

* feat: always display the "Explore" card

* rename safe app tracking methods. extract card dimensions to constants

* include random apps to fill the gaps in the widget

* prop drill the safeApp number id

* code comments clean up

* fix: make logo height fix to align SafeApp name

Co-authored-by: Usame Algan <[email protected]>
Co-authored-by: katspaugh <[email protected]>
Co-authored-by: Usame Algan <[email protected]>

* feat: Add Overview widget for dashboard (#3786)

* feat: Add Safe route for dashboard (#3759)

* feat: Add safe specific dashboard route

* fix: Redirect user to Dashboard when adding/loading/removing a safe

* fix: Remove old dashboard route

* fix: Move Dashboard component inside SafeContainer to access transactions

* fix: Remove null fallback for wrapInSuspense, remove unnecessary React reference

* feat: Add Overview widget for dashboard

* fix: Remove Load Safe Button

* fix: Add loading state for safe record, show skeleton ui for overview widget

* fix: Reset safe loading state when switching safes

* fix: Reset nft token loaded state when switching safes

* fix: await all dispatches in fetchSafe before setting loaded state, add skeletons

* fix: Split up loading state and dont reset it for nfts anymore

* fix: dispatch array type

* fix: Split nft token actions, reset nft loaded state before they are refetched

* feat: Featured Apps Widget (#3789)

* feat: Add featured apps widget to dashboard

* refactor: Extract getSafeAppUrl

* fix: Filter apps by tags

* fix: Update gateway sdk package and remove old type

* fix: Update gateway sdk package and remove old type

* refactor: Extract featured apps const

* fix: Check for tags if they exist

* feat: Dashboard grid layout (#3795)

* wip layout

* feat: Adjust Dashboard layout

* fix: remove leftover prop

* fix: NFT route, style container spacings

* style: Adjust Overview widget style

* style: Remove row, col from featured apps widget, adjust spacings

* Add total transactions to sign to the PendingTxs widget title

* Add view all Link

* tune spacing in TxPendingListItem

* fix: Remove hardcoded featured app ids, use lodash sampleSize to get random apps, adjust grid layout

* fix: React prop errors

* style: Adjust overview skeleton container size

* style: Adjust pending txs spacing

* tweaks in grid layout

* fix: Update comment

Co-authored-by: Diogo Soares <[email protected]>

* style: Adjust font-size and bookmark icon size for safe apps grid, adjust empty state for pending txs widget

* Chore: rm unused dashboard widgets (#3805)

* fix: show last queued in grouped transactions

* fix: Dashboard styles (#3806)

* fix: exclude featured safe apps from top ranked

* fix overlap in featured apps

* style: Pending txs widget height

* style: switch bookmark icons to src

* style: vertical scroll in transactions view adjusted for new spacings

* fix: Remove pinned apps local state

* style: Hide featured apps widget if none exist

* fix: Set app fallback image

* fix: Adjust widget titles, revert app layout spacing

* style: Adjust app frame margin for larger spaces

Co-authored-by: Usame Algan <[email protected]>

* style: Adjust app frame height for new spacing

* style: Adjust layout padding to 24px instead of 40px

* fix: lint

Co-authored-by: Usame Algan <[email protected]>
Co-authored-by: Diogo Soares <[email protected]>
Co-authored-by: Aaron Cook <[email protected]>
Co-authored-by: Diogo Soares <[email protected]>
Co-authored-by: Usame Algan <[email protected]>
  • Loading branch information
6 people authored Apr 26, 2022
1 parent ee39ef9 commit d1a20f0
Show file tree
Hide file tree
Showing 45 changed files with 1,039 additions and 69 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"@gnosis.pm/safe-deployments": "^1.8.0",
"@gnosis.pm/safe-modules-deployments": "^1.0.0",
"@gnosis.pm/safe-react-components": "^1.1.2",
"@gnosis.pm/safe-react-gateway-sdk": "^2.10.2",
"@gnosis.pm/safe-react-gateway-sdk": "^2.10.3",
"@gnosis.pm/safe-web3-lib": "^1.0.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.0",
Expand Down
5 changes: 5 additions & 0 deletions src/assets/icons/explore.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/components/AppLayout/Sidebar/useSidebarItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ const useSidebarItems = (): ListItemType[] => {
].filter(Boolean)

return [
makeEntryItem({
label: 'Home',
iconType: 'home',
href: currentSafeRoutes.DASHBOARD,
}),
makeEntryItem({
label: 'Assets',
iconType: 'assets',
Expand Down
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: 8px 24px;
> :nth-child(1) {
flex-grow: 1;
Expand Down
69 changes: 69 additions & 0 deletions src/components/Dashboard/FeaturedApps/FeaturedApps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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 { 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 { Card, WidgetBody, WidgetContainer, WidgetTitle } from 'src/components/Dashboard/styled'

export const FEATURED_APPS_TAG = 'dashboard-widgets'

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

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

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

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

if (!featuredApps.length && !isLoading) return null

return (
<Grid item xs={12} md={6}>
<WidgetContainer>
<WidgetTitle>Connect & Transact</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={12} md={3}>
<StyledImage src={app.iconUrl} alt={app.name} />
</Grid>
<Grid item xs={12} md={9}>
<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>
</Grid>
)
}
164 changes: 164 additions & 0 deletions src/components/Dashboard/Overview/Overview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
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 { 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 { 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;
margin-bottom: ${md};
`

const SafeThreshold = styled.div`
position: absolute;
left: -6px;
top: -6px;
background: ${primaryLite};
color: ${primaryActive};
font-size: ${smallFontSize};
font-weight: bold;
border-radius: 100%;
padding: 4px;
z-index: 2;
min-width: 24px;
min-height: 24px;
box-sizing: border-box;
`

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

const NetworkLabelContainer = styled.div`
position: absolute;
top: ${lg};
right: ${lg};
& span {
bottom: auto;
}
`

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 (
<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>
)
}

export default Overview
90 changes: 90 additions & 0 deletions src/components/Dashboard/PendingTxs/PendingTxListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { ReactElement } from 'react'
import { Text } from '@gnosis.pm/safe-react-components'
import { TransactionSummary } from '@gnosis.pm/safe-react-gateway-sdk'
import { Link } from 'react-router-dom'
import ChevronRightIcon from '@material-ui/icons/ChevronRight'
import styled from 'styled-components'

import { useAssetInfo } from 'src/routes/safe/components/Transactions/TxList/hooks/useAssetInfo'
import { useKnownAddress } from 'src/routes/safe/components/Transactions/TxList/hooks/useKnownAddress'
import { useTransactionType } from 'src/routes/safe/components/Transactions/TxList/hooks/useTransactionType'
import { getTxTo } from 'src/routes/safe/components/Transactions/TxList/utils'
import { boldFont, grey400, primary200, smallFontSize } from 'src/theme/variables'
import { isMultisigExecutionInfo } from 'src/logic/safe/store/models/types/gateway.d'
import Spacer from 'src/components/Spacer'
import { CustomIconText } from 'src/components/CustomIconText'
import { TxInfo } from 'src/routes/safe/components/Transactions/TxList/TxCollapsed'
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions'

const TransactionToConfirm = styled(Link)`
width: 100%;
display: grid;
align-items: center;
grid-template-columns: 36px 1fr 1fr auto;
gap: 4px;
margin: 0 auto;
padding: 8px 24px;
text-decoration: none;
background-color: ${({ theme }) => theme.colors.white};
border: 2px solid ${grey400};
color: ${({ theme }) => theme.colors.text};
border-radius: 8px;
box-sizing: border-box;
`

const StyledConfirmationsCount = styled.div`
padding: 8px 12px;
border-radius: 8px;
background-color: ${primary200};
font-weight: ${boldFont};
font-size: ${smallFontSize};
`

const TxConfirmations = styled.div`
display: flex;
align-items: center;
margin-left: auto;
& svg {
margin-left: 8px;
}
`

type PendingTxType = {
transaction: TransactionSummary
url: string
}

const PendingTx = ({ transaction, url }: PendingTxType): ReactElement => {
const info = useAssetInfo(transaction.txInfo)
const type = useTransactionType(transaction)
const toAddress = getTxTo(transaction)
const toInfo = useKnownAddress(toAddress)

return (
<TransactionToConfirm key={transaction.id} to={url}>
<Text color="text" size="lg" as="span">
{isMultisigExecutionInfo(transaction.executionInfo) && transaction.executionInfo.nonce}
</Text>
<CustomIconText
address={toAddress?.value || EMPTY_DATA}
iconUrl={type.icon || toInfo?.logoUri || undefined}
iconUrlFallback={type.fallbackIcon}
text={type.text || toInfo?.name || undefined}
/>
{info ? <TxInfo info={info} /> : <Spacer />}
<TxConfirmations>
{isMultisigExecutionInfo(transaction.executionInfo) ? (
<StyledConfirmationsCount>
{`${transaction.executionInfo.confirmationsSubmitted}/${transaction.executionInfo.confirmationsRequired}`}
</StyledConfirmationsCount>
) : (
<Spacer />
)}
<ChevronRightIcon color="secondary" />
</TxConfirmations>
</TransactionToConfirm>
)
}

export default PendingTx
Loading

0 comments on commit d1a20f0

Please sign in to comment.