diff --git a/src/components/common/EthHashInfo/index.tsx b/src/components/common/EthHashInfo/index.tsx
index 7d6821f2ba..f5f66a60aa 100644
--- a/src/components/common/EthHashInfo/index.tsx
+++ b/src/components/common/EthHashInfo/index.tsx
@@ -16,7 +16,7 @@ const EthHashInfo = ({
const currentChainId = useChainId()
const chain = useAppSelector((state) => selectChainById(state, props.chainId || currentChainId))
const addressBook = useAddressBook()
- const link = chain ? getBlockExplorerLink(chain, props.address) : undefined
+ const link = chain && getBlockExplorerLink(chain, props.address) : undefined
const name = showName ? addressBook[props.address] || props.name : undefined
return (
diff --git a/src/components/welcome/MyAccounts/AccountItem.tsx b/src/components/welcome/MyAccounts/AccountItem.tsx
new file mode 100644
index 0000000000..21b0ecc271
--- /dev/null
+++ b/src/components/welcome/MyAccounts/AccountItem.tsx
@@ -0,0 +1,63 @@
+import { useMemo } from 'react'
+import { ListItemButton, Box, Typography } from '@mui/material'
+import Link from 'next/link'
+import SafeIcon from '@/components/common/SafeIcon'
+import Track from '@/components/common/Track'
+import { OPEN_SAFE_LABELS, OVERVIEW_EVENTS } from '@/services/analytics'
+import { AppRoutes } from '@/config/routes'
+import { useAppSelector } from '@/store'
+import { selectChainById } from '@/store/chainsSlice'
+import ChainIndicator from '@/components/common/ChainIndicator'
+import css from './styles.module.css'
+import { selectAllAddressBooks } from '@/store/addressBookSlice'
+import { shortenAddress } from '@/utils/formatters'
+
+type AccountItemProps = {
+ chainId: string
+ address: string
+ threshold?: number
+ owners?: number
+}
+
+const getSafeHref = (prefix: string, address: string) => ({
+ pathname: AppRoutes.home,
+ query: { safe: `${prefix}:${address}` },
+})
+
+const AccountItem = ({ chainId, address, ...rest }: AccountItemProps) => {
+ const chain = useAppSelector((state) => selectChainById(state, chainId))
+
+ const href = useMemo(() => {
+ return chain ? getSafeHref(chain.shortName, address) : ''
+ }, [chain, address])
+
+ const name = useAppSelector(selectAllAddressBooks)[chainId]?.[address]
+
+ return (
+
+ )
+}
+
+export default AccountItem
diff --git a/src/components/welcome/MyAccounts/CreateButton.tsx b/src/components/welcome/MyAccounts/CreateButton.tsx
new file mode 100644
index 0000000000..fe7112f511
--- /dev/null
+++ b/src/components/welcome/MyAccounts/CreateButton.tsx
@@ -0,0 +1,18 @@
+import { Button } from '@mui/material'
+import Link from 'next/link'
+import { AppRoutes } from '@/config/routes'
+import { useCurrentChain } from '@/hooks/useChains'
+
+const CreateButton = () => {
+ const currentChain = useCurrentChain()
+
+ return (
+
+
+
+ )
+}
+
+export default CreateButton
diff --git a/src/components/welcome/MyAccounts/index.tsx b/src/components/welcome/MyAccounts/index.tsx
new file mode 100644
index 0000000000..b4fe580e97
--- /dev/null
+++ b/src/components/welcome/MyAccounts/index.tsx
@@ -0,0 +1,65 @@
+import { useMemo, useState } from 'react'
+import { Button, Box, Paper, Typography } from '@mui/material'
+import madProps from '@/utils/mad-props'
+import AccountItem from './AccountItem'
+import CreateButton from './CreateButton'
+import useAllSafes, { type SafeItems } from './useAllSafes'
+
+type AccountsListProps = {
+ safes: SafeItems
+}
+
+const DEFAULT_SHOWN = 5
+const MAX_DEFAULT_SHOWN = 7
+
+const AccountsList = ({ safes }: AccountsListProps) => {
+ const [maxShown, setMaxShown] = useState(DEFAULT_SHOWN)
+
+ const shownSafes = useMemo(() => {
+ if (safes.length <= MAX_DEFAULT_SHOWN) {
+ return safes
+ }
+ return safes.slice(0, maxShown)
+ }, [safes, maxShown])
+
+ const onShowMore = () => {
+ const pageSize = 100 // DEFAULT_SHOWN
+ setMaxShown((prev) => prev + pageSize)
+ }
+
+ return (
+
+
+
+
+ My Safe accounts
+
+ {' '}
+ ({safes.length})
+
+
+
+
+
+
+
+ {shownSafes.map((item) => (
+
+ ))}
+
+ {safes.length > shownSafes.length && (
+
+
+
+ )}
+
+
+
+ )
+}
+
+const MyAccounts = madProps(AccountsList, {
+ safes: useAllSafes,
+})
+
+export default MyAccounts
diff --git a/src/components/welcome/MyAccounts/styles.module.css b/src/components/welcome/MyAccounts/styles.module.css
new file mode 100644
index 0000000000..a391717b4c
--- /dev/null
+++ b/src/components/welcome/MyAccounts/styles.module.css
@@ -0,0 +1,8 @@
+.listItem {
+ gap: var(--space-2);
+ border: 1px solid var(--color-border-light);
+ border-radius: var(--space-1);
+ margin-bottom: 12px;
+ padding-top: var(--space-2);
+ padding-bottom: var(--space-2);
+}
diff --git a/src/components/welcome/MyAccounts/useAllOwnedSafes.ts b/src/components/welcome/MyAccounts/useAllOwnedSafes.ts
new file mode 100644
index 0000000000..4762bad957
--- /dev/null
+++ b/src/components/welcome/MyAccounts/useAllOwnedSafes.ts
@@ -0,0 +1,14 @@
+import { getAllOwnedSafes } from '@safe-global/safe-gateway-typescript-sdk'
+import useAsync from '@/hooks/useAsync'
+import useWallet from '@/hooks/wallets/useWallet'
+
+const useAllOwnedSafes = () => {
+ const { address = '' } = useWallet() || {}
+
+ return useAsync(() => {
+ if (!address) return
+ return getAllOwnedSafes(address)
+ }, [address])
+}
+
+export default useAllOwnedSafes
diff --git a/src/components/welcome/MyAccounts/useAllSafes.ts b/src/components/welcome/MyAccounts/useAllSafes.ts
new file mode 100644
index 0000000000..ad4df13909
--- /dev/null
+++ b/src/components/welcome/MyAccounts/useAllSafes.ts
@@ -0,0 +1,46 @@
+import { useMemo } from 'react'
+import uniq from 'lodash/uniq'
+import { useAppSelector } from '@/store'
+import { selectAllAddedSafes } from '@/store/addedSafesSlice'
+import useAllOwnedSafes from './useAllOwnedSafes'
+import useChains from '@/hooks/useChains'
+import useChainId from '@/hooks/useChainId'
+
+export type SafeItems = Array<{
+ chainId: string
+ address: string
+ threshold?: number
+ owners?: number
+}>
+
+const useAddedSafes = () => {
+ const allAdded = useAppSelector(selectAllAddedSafes)
+ return allAdded
+}
+
+const useAllSafes = (): SafeItems => {
+ const [allOwned = {}] = useAllOwnedSafes()
+ const allAdded = useAddedSafes()
+ const { configs } = useChains()
+ const currentChainId = useChainId()
+
+ return useMemo(() => {
+ const chains = uniq([currentChainId].concat(Object.keys(allAdded)).concat(Object.keys(allOwned)))
+
+ return chains.flatMap((chainId) => {
+ if (!configs.some((item) => item.chainId === chainId)) return []
+ const addedOnChain = Object.keys(allAdded[chainId] || {})
+ const ownedOnChain = allOwned[chainId]
+ const uniqueAddresses = uniq(addedOnChain.concat(ownedOnChain)).filter(Boolean)
+
+ return uniqueAddresses.map((address) => ({
+ address,
+ chainId,
+ threshold: allAdded[chainId]?.[address]?.threshold,
+ owners: allAdded[chainId]?.[address]?.owners.length,
+ }))
+ })
+ }, [configs, allAdded, allOwned, currentChainId])
+}
+
+export default useAllSafes
diff --git a/src/components/welcome/WelcomeLogin/index.tsx b/src/components/welcome/WelcomeLogin/index.tsx
index 7cdb42ae16..9d3bbc7cb4 100644
--- a/src/components/welcome/WelcomeLogin/index.tsx
+++ b/src/components/welcome/WelcomeLogin/index.tsx
@@ -1,15 +1,18 @@
+import isEmpty from 'lodash/isEmpty'
import { AppRoutes } from '@/config/routes'
import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'
-import { Paper, SvgIcon, Typography, Divider, Link, Box, Skeleton } from '@mui/material'
+import { Paper, SvgIcon, Typography, Divider, Box, Skeleton } from '@mui/material'
import SafeLogo from '@/public/images/logo-text.svg'
import dynamic from 'next/dynamic'
import css from './styles.module.css'
import { useRouter } from 'next/router'
import WalletLogin from './WalletLogin'
-import { LOAD_SAFE_EVENTS, CREATE_SAFE_EVENTS } from '@/services/analytics/events/createLoadSafe'
-import Track from '@/components/common/Track'
+import { CREATE_SAFE_EVENTS } from '@/services/analytics/events/createLoadSafe'
import { trackEvent } from '@/services/analytics'
+import { useAppSelector } from '@/store'
+import { selectAllAddedSafes } from '@/store/addedSafesSlice'
+import useWallet from '@/hooks/wallets/useWallet'
const SocialSigner = dynamic(() => import('@/components/common/SocialSigner'), {
loading: () => ,
@@ -17,11 +20,18 @@ const SocialSigner = dynamic(() => import('@/components/common/SocialSigner'), {
const WelcomeLogin = () => {
const router = useRouter()
+ const wallet = useWallet()
const isSocialLoginEnabled = useHasFeature(FEATURES.SOCIAL_LOGIN)
+ const addedSafes = useAppSelector(selectAllAddedSafes)
+ const hasAddedSafes = !isEmpty(addedSafes)
const continueToCreation = () => {
- trackEvent(CREATE_SAFE_EVENTS.OPEN_SAFE_CREATION)
- router.push({ pathname: AppRoutes.newSafe.create, query: router.query })
+ if (hasAddedSafes) {
+ router.push({ pathname: AppRoutes.welcome.accounts, query: router.query })
+ } else {
+ trackEvent(CREATE_SAFE_EVENTS.OPEN_SAFE_CREATION)
+ router.push({ pathname: AppRoutes.newSafe.create, query: router.query })
+ }
}
return (
@@ -30,12 +40,15 @@ const WelcomeLogin = () => {
- Create Account
+ Get started
- Choose how you would like to create your Safe Account
+ {wallet
+ ? 'Open your existing Safe Accounts or create a new one'
+ : 'Connect your wallet to create a new Safe Account or open an existing one'}
+
{isSocialLoginEnabled && (
@@ -49,15 +62,6 @@ const WelcomeLogin = () => {
>
)}
-
-
- Already have a Safe Account?
-
-
)
diff --git a/src/config/routes.ts b/src/config/routes.ts
index 4ea91c3249..fbc4c0f96e 100644
--- a/src/config/routes.ts
+++ b/src/config/routes.ts
@@ -1,6 +1,5 @@
export const AppRoutes = {
'404': '/404',
- _offline: '/_offline',
wc: '/wc',
terms: '/terms',
privacy: '/privacy',
@@ -11,6 +10,7 @@ export const AppRoutes = {
cookie: '/cookie',
addressBook: '/address-book',
addOwner: '/addOwner',
+ _offline: '/_offline',
apps: {
open: '/apps/open',
index: '/apps',
@@ -52,5 +52,6 @@ export const AppRoutes = {
welcome: {
socialLogin: '/welcome/social-login',
index: '/welcome',
+ accounts: '/welcome/accounts',
},
}
diff --git a/src/hooks/useIsSidebarRoute.ts b/src/hooks/useIsSidebarRoute.ts
index 287fc689cd..1b89b2f606 100644
--- a/src/hooks/useIsSidebarRoute.ts
+++ b/src/hooks/useIsSidebarRoute.ts
@@ -9,6 +9,7 @@ const NO_SIDEBAR_ROUTES = [
AppRoutes.index,
AppRoutes.welcome.index,
AppRoutes.welcome.socialLogin,
+ AppRoutes.welcome.accounts,
AppRoutes.imprint,
AppRoutes.privacy,
AppRoutes.cookie,
diff --git a/src/pages/welcome/accounts.tsx b/src/pages/welcome/accounts.tsx
new file mode 100644
index 0000000000..b85f0a906e
--- /dev/null
+++ b/src/pages/welcome/accounts.tsx
@@ -0,0 +1,17 @@
+import type { NextPage } from 'next'
+import Head from 'next/head'
+import MyAccounts from '@/components/welcome/MyAccounts'
+
+const Accounts: NextPage = () => {
+ return (
+ <>
+
+ {'Safe{Wallet} – My accounts'}
+
+
+
+ >
+ )
+}
+
+export default Accounts
diff --git a/src/services/analytics/events/overview.ts b/src/services/analytics/events/overview.ts
index 514a3b7286..4d5ed9030b 100644
--- a/src/services/analytics/events/overview.ts
+++ b/src/services/analytics/events/overview.ts
@@ -114,4 +114,5 @@ export enum OPEN_SAFE_LABELS {
sidebar = 'sidebar',
after_create = 'after_create',
after_add = 'after_add',
+ login_page = 'login_page',
}