Skip to content

Commit

Permalink
[Counterfactual] Add first steps widget (#3218)
Browse files Browse the repository at this point in the history
* feat: Add first steps widget to dashboard

* fix: Display the widget in the dashboard

* fix: Extract last step as completed step, use enum and memoize items

* fix: Revert change to completedName

* fix: Remove StatusProgress and add individual first step cards

* fix: Add counterfactual check to widget

* fix: Remove steps badges
  • Loading branch information
usame-algan authored Feb 9, 2024
1 parent 911d7ea commit d0f3fb5
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 2 deletions.
154 changes: 154 additions & 0 deletions src/components/dashboard/FirstSteps/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { TxModalContext } from '@/components/tx-flow'
import { ActivateAccountFlow } from '@/features/counterfactual/ActivateAccount'
import useBalances from '@/hooks/useBalances'
import useSafeInfo from '@/hooks/useSafeInfo'
import { useAppSelector } from '@/store'
import { selectOutgoingTransactions } from '@/store/txHistorySlice'
import React, { useContext, useMemo } from 'react'
import { Card, WidgetBody, WidgetContainer } from '@/components/dashboard/styled'
import { Button, CircularProgress, Grid, Link, Typography } from '@mui/material'
import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined'
import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded'
import css from './styles.module.css'

const calculateProgress = (items: StatusProgressItems) => {
const totalNumberOfItems = items.length
const completedItems = items.filter((item) => item.completed)
return Math.round((completedItems.length / totalNumberOfItems) * 100)
}

const StatusCard = ({
badge,
title,
content,
completed,
}: {
badge: string
title: string
content: string
completed: boolean
}) => {
return (
<Card className={css.card}>
<div className={css.topBadge}>
<Typography variant="body2">{badge}</Typography>
</div>
<div className={css.status}>
{completed ? (
<CheckCircleRoundedIcon color="primary" fontSize="medium" />
) : (
<CircleOutlinedIcon color="inherit" fontSize="medium" />
)}
</div>
<Typography variant="h4" fontWeight="bold" mb={2}>
{title}
</Typography>
<Typography>{content}</Typography>
</Card>
)
}

type StatusProgressItem = {
title: string
content: string
completed?: boolean
}

type StatusProgressItems = Array<StatusProgressItem>

const FirstStepsContent: StatusProgressItems = [
{
title: 'Add funds',
content: 'Receive assets to start interacting with your account.',
},
{
title: 'Create your first transaction',
content: 'Do a simple transfer or use a safe app to create your first transaction.',
},
]

const FirstSteps = () => {
const { balances } = useBalances()
const { safe } = useSafeInfo()
const outgoingTransactions = useAppSelector(selectOutgoingTransactions)
const { setTxFlow } = useContext(TxModalContext)

const hasNonZeroBalance = balances && (balances.items.length > 1 || BigInt(balances.items[0]?.balance || 0) > 0)
const hasOutgoingTransactions = !!outgoingTransactions && outgoingTransactions.length > 0

const items = useMemo(
() => [
{ ...FirstStepsContent[0], completed: hasNonZeroBalance },
{ ...FirstStepsContent[1], completed: hasOutgoingTransactions },
],
[hasNonZeroBalance, hasOutgoingTransactions],
)

const activateAccount = () => {
setTxFlow(<ActivateAccountFlow />)
}

const progress = calculateProgress(items)
const stepsCompleted = items.filter((item) => item.completed).length

if (safe.deployed) return null

return (
<WidgetContainer>
<WidgetBody>
<Grid container gap={3} mb={2} flexWrap="nowrap">
<Grid item position="relative">
<CircularProgress variant="determinate" value={100} className={css.circleBg} size={60} thickness={5} />
<CircularProgress
variant="determinate"
value={progress}
className={css.circleProgress}
size={60}
thickness={5}
/>
</Grid>
<Grid item>
<Typography component="div" variant="h2" fontWeight={700} mb={1}>
Activate your Safe Account
</Typography>
<Typography variant="body2">
<strong>
{stepsCompleted} of {items.length} steps completed.
</strong>{' '}
{progress === 100 ? (
<>
Congratulations! You finished the first steps. <Link>Hide this section</Link>
</>
) : (
'Finish the next steps to start using all Safe Account features:'
)}
</Typography>
</Grid>
</Grid>
<Grid container spacing={3}>
{items.map((item) => {
return (
<Grid item xs={12} md={4} key={item.title}>
<StatusCard badge="First steps" title={item.title} content={item.content} completed={item.completed} />
</Grid>
)
})}

<Grid item xs={12} md={4}>
<Card className={css.card}>
<Typography variant="h4" fontWeight="bold" mb={2}>
Skip first steps
</Typography>
<Typography mb={2}>Pay a network fee to immediately access all Safe Account features.</Typography>
<Button variant="contained" onClick={activateAccount}>
Activate Safe Account
</Button>
</Card>
</Grid>
</Grid>
</WidgetBody>
</WidgetContainer>
)
}

export default FirstSteps
31 changes: 31 additions & 0 deletions src/components/dashboard/FirstSteps/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.circleProgress {
color: var(--color-secondary-main);
}

.circleBg {
color: var(--color-border-light);
position: absolute;
}

.card {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
min-height: 240px;
}

.topBadge {
position: absolute;
top: 0;
right: 24px;
background-color: var(--color-secondary-light);
border-radius: 0 0 4px 4px;
padding: 4px var(--space-1);
}

.status {
align-self: flex-start;
margin-bottom: auto;
color: var(--color-border-light);
}
5 changes: 5 additions & 0 deletions src/components/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import FirstSteps from '@/components/dashboard/FirstSteps'
import type { ReactElement } from 'react'
import dynamic from 'next/dynamic'
import { Grid } from '@mui/material'
Expand Down Expand Up @@ -32,6 +33,10 @@ const Dashboard = (): ReactElement => {
<Overview />
</Grid>

<Grid item xs={12}>
<FirstSteps />
</Grid>

<Grid item xs={12} lg={6}>
<PendingTxsList />
</Grid>
Expand Down
2 changes: 1 addition & 1 deletion src/features/counterfactual/ActivateAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const useActivateAccount = () => {
return { options, totalFee, walletCanPay }
}

const ActivateAccountFlow = () => {
export const ActivateAccountFlow = () => {
const [isSubmittable, setIsSubmittable] = useState<boolean>(true)
const [submitError, setSubmitError] = useState<Error | undefined>()
const [executionMethod, setExecutionMethod] = useState(ExecutionMethod.RELAY)
Expand Down
9 changes: 8 additions & 1 deletion src/store/txHistorySlice.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { listenerMiddlewareInstance } from '@/store'
import { createSelector } from '@reduxjs/toolkit'
import type { TransactionListPage } from '@safe-global/safe-gateway-typescript-sdk'
import { isTransactionListItem } from '@/utils/transaction-guards'
import { isCreationTxInfo, isIncomingTransfer, isTransactionListItem } from '@/utils/transaction-guards'
import { txDispatch, TxEvent } from '@/services/tx/txEvents'
import { selectPendingTxs } from './pendingTxsSlice'
import { makeLoadableSlice } from './common'
Expand All @@ -10,6 +11,12 @@ const { slice, selector } = makeLoadableSlice('txHistory', undefined as Transact
export const txHistorySlice = slice
export const selectTxHistory = selector

export const selectOutgoingTransactions = createSelector(selectTxHistory, (txHistory) => {
return txHistory.data?.results.filter(isTransactionListItem).filter((tx) => {
return !isIncomingTransfer(tx.transaction.txInfo) && !isCreationTxInfo(tx.transaction.txInfo)
})
})

export const txHistoryListener = (listenerMiddleware: typeof listenerMiddlewareInstance) => {
listenerMiddleware.startListening({
actionCreator: txHistorySlice.actions.set,
Expand Down
4 changes: 4 additions & 0 deletions src/utils/transaction-guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ export const isOutgoingTransfer = (txInfo: TransactionInfo): boolean => {
return isTransferTxInfo(txInfo) && txInfo.direction.toUpperCase() === TransferDirection.OUTGOING
}

export const isIncomingTransfer = (txInfo: TransactionInfo): boolean => {
return isTransferTxInfo(txInfo) && txInfo.direction.toUpperCase() === TransferDirection.INCOMING
}

// TransactionListItem type guards
export const isLabelListItem = (value: TransactionListItem): value is Label => {
return value.type === TransactionListItemType.LABEL
Expand Down

0 comments on commit d0f3fb5

Please sign in to comment.