diff --git a/src/components/dashboard/FirstSteps/index.tsx b/src/components/dashboard/FirstSteps/index.tsx new file mode 100644 index 0000000000..f914ffe841 --- /dev/null +++ b/src/components/dashboard/FirstSteps/index.tsx @@ -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 ( + +
+ {badge} +
+
+ {completed ? ( + + ) : ( + + )} +
+ + {title} + + {content} +
+ ) +} + +type StatusProgressItem = { + title: string + content: string + completed?: boolean +} + +type StatusProgressItems = Array + +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() + } + + const progress = calculateProgress(items) + const stepsCompleted = items.filter((item) => item.completed).length + + if (safe.deployed) return null + + return ( + + + + + + + + + + Activate your Safe Account + + + + {stepsCompleted} of {items.length} steps completed. + {' '} + {progress === 100 ? ( + <> + Congratulations! You finished the first steps. Hide this section + + ) : ( + 'Finish the next steps to start using all Safe Account features:' + )} + + + + + {items.map((item) => { + return ( + + + + ) + })} + + + + + Skip first steps + + Pay a network fee to immediately access all Safe Account features. + + + + + + + ) +} + +export default FirstSteps diff --git a/src/components/dashboard/FirstSteps/styles.module.css b/src/components/dashboard/FirstSteps/styles.module.css new file mode 100644 index 0000000000..8a8ed5a421 --- /dev/null +++ b/src/components/dashboard/FirstSteps/styles.module.css @@ -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); +} diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index f72bc36909..5bd0c06b4c 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -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' @@ -32,6 +33,10 @@ const Dashboard = (): ReactElement => { + + + + diff --git a/src/features/counterfactual/ActivateAccount.tsx b/src/features/counterfactual/ActivateAccount.tsx index 860316236f..fbc2326d31 100644 --- a/src/features/counterfactual/ActivateAccount.tsx +++ b/src/features/counterfactual/ActivateAccount.tsx @@ -54,7 +54,7 @@ const useActivateAccount = () => { return { options, totalFee, walletCanPay } } -const ActivateAccountFlow = () => { +export const ActivateAccountFlow = () => { const [isSubmittable, setIsSubmittable] = useState(true) const [submitError, setSubmitError] = useState() const [executionMethod, setExecutionMethod] = useState(ExecutionMethod.RELAY) diff --git a/src/store/txHistorySlice.ts b/src/store/txHistorySlice.ts index d9717d0a3b..bf7d0fb8fe 100644 --- a/src/store/txHistorySlice.ts +++ b/src/store/txHistorySlice.ts @@ -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' @@ -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, diff --git a/src/utils/transaction-guards.ts b/src/utils/transaction-guards.ts index 06b1591131..817dca0591 100644 --- a/src/utils/transaction-guards.ts +++ b/src/utils/transaction-guards.ts @@ -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