From ab87fbd78e9f14412dfd37c2f7ac600708c5df9d Mon Sep 17 00:00:00 2001 From: Usame Algan Date: Mon, 16 Dec 2024 15:43:55 +0100 Subject: [PATCH] refactor: Split up MyAccounts component --- .../components/AccountsFilter/index.tsx | 60 ++++ .../components/AccountsHeader/index.tsx | 55 ++++ .../components/AccountsList/index.tsx | 29 ++ .../myAccounts/components/AllSafes/index.tsx | 83 ++++++ .../components/FilteredSafes/index.tsx | 30 ++ .../components/PinnedSafes/index.tsx | 43 +++ .../myAccounts/hooks/useAllSafesGrouped.ts | 6 +- .../myAccounts/hooks/useTrackedSafesCount.ts | 17 +- src/features/myAccounts/index.tsx | 260 ++---------------- 9 files changed, 339 insertions(+), 244 deletions(-) create mode 100644 src/features/myAccounts/components/AccountsFilter/index.tsx create mode 100644 src/features/myAccounts/components/AccountsHeader/index.tsx create mode 100644 src/features/myAccounts/components/AccountsList/index.tsx create mode 100644 src/features/myAccounts/components/AllSafes/index.tsx create mode 100644 src/features/myAccounts/components/FilteredSafes/index.tsx create mode 100644 src/features/myAccounts/components/PinnedSafes/index.tsx diff --git a/src/features/myAccounts/components/AccountsFilter/index.tsx b/src/features/myAccounts/components/AccountsFilter/index.tsx new file mode 100644 index 0000000000..dcfd01bf2c --- /dev/null +++ b/src/features/myAccounts/components/AccountsFilter/index.tsx @@ -0,0 +1,60 @@ +import { useAppDispatch, useAppSelector } from '@/store' +import { type OrderByOption, selectOrderByPreference, setOrderByPreference } from '@/store/orderByPreferenceSlice' +import debounce from 'lodash/debounce' +import { type Dispatch, type SetStateAction, useCallback } from 'react' +import OrderByButton from '@/features/myAccounts/components/OrderByButton' +import css from '@/features/myAccounts/styles.module.css' +import SearchIcon from '@/public/images/common/search.svg' +import { Box, InputAdornment, Paper, SvgIcon, TextField } from '@mui/material' + +const AccountsFilter = ({ setSearchQuery }: { setSearchQuery: Dispatch> }) => { + const dispatch = useAppDispatch() + const { orderBy } = useAppSelector(selectOrderByPreference) + + // eslint-disable-next-line react-hooks/exhaustive-deps + const handleSearch = useCallback(debounce(setSearchQuery, 300), []) + + const handleOrderByChange = (orderBy: OrderByOption) => { + dispatch(setOrderByPreference({ orderBy })) + } + + return ( + + + { + handleSearch(e.target.value) + }} + className={css.search} + InputProps={{ + startAdornment: ( + + + + ), + disableUnderline: true, + }} + fullWidth + size="small" + /> + + + + ) +} + +export default AccountsFilter diff --git a/src/features/myAccounts/components/AccountsHeader/index.tsx b/src/features/myAccounts/components/AccountsHeader/index.tsx new file mode 100644 index 0000000000..90d8cdde19 --- /dev/null +++ b/src/features/myAccounts/components/AccountsHeader/index.tsx @@ -0,0 +1,55 @@ +import ConnectWalletButton from '@/components/common/ConnectWallet/ConnectWalletButton' +import Track from '@/components/common/Track' +import { AppRoutes } from '@/config/routes' +import CreateButton from '@/features/myAccounts/components/CreateButton' +import css from '@/features/myAccounts/styles.module.css' +import useWallet from '@/hooks/wallets/useWallet' +import AddIcon from '@/public/images/common/add.svg' +import { OVERVIEW_EVENTS, OVERVIEW_LABELS } from '@/services/analytics' +import { Box, Button, Link, SvgIcon, Typography } from '@mui/material' +import classNames from 'classnames' +import { useRouter } from 'next/router' + +const AccountsHeader = ({ isSidebar, onLinkClick }: { isSidebar: boolean; onLinkClick?: () => void }) => { + const router = useRouter() + const wallet = useWallet() + + const isLoginPage = router.pathname === AppRoutes.welcome.accounts + const trackingLabel = isLoginPage ? OVERVIEW_LABELS.login_page : OVERVIEW_LABELS.sidebar + + return ( + + + My accounts + + + + + + + + + {wallet ? ( + + + + ) : ( + + + + )} + + + ) +} + +export default AccountsHeader diff --git a/src/features/myAccounts/components/AccountsList/index.tsx b/src/features/myAccounts/components/AccountsList/index.tsx new file mode 100644 index 0000000000..6e1c9f915e --- /dev/null +++ b/src/features/myAccounts/components/AccountsList/index.tsx @@ -0,0 +1,29 @@ +import FilteredSafes from '@/features/myAccounts/components/FilteredSafes' +import PinnedSafes from '@/features/myAccounts/components/PinnedSafes' +import type { AllSafeItems } from '@/features/myAccounts/hooks/useAllSafesGrouped' +import AllSafes from '@/features/myAccounts/components/AllSafes' + +const AccountsList = ({ + searchQuery, + allSafes, + onLinkClick, + isSidebar, +}: { + searchQuery: string + allSafes: AllSafeItems + onLinkClick?: () => void + isSidebar: boolean +}) => { + if (searchQuery) { + return + } + + return ( + <> + + + + ) +} + +export default AccountsList diff --git a/src/features/myAccounts/components/AllSafes/index.tsx b/src/features/myAccounts/components/AllSafes/index.tsx new file mode 100644 index 0000000000..d3a6862b3b --- /dev/null +++ b/src/features/myAccounts/components/AllSafes/index.tsx @@ -0,0 +1,83 @@ +import ConnectWalletButton from '@/components/common/ConnectWallet/ConnectWalletButton' +import Track from '@/components/common/Track' +import { AppRoutes } from '@/config/routes' +import SafesList from '@/features/myAccounts/components/SafesList' +import type { AllSafeItems } from '@/features/myAccounts/hooks/useAllSafesGrouped' +import css from '@/features/myAccounts/styles.module.css' +import useWallet from '@/hooks/wallets/useWallet' +import { OVERVIEW_EVENTS, OVERVIEW_LABELS } from '@/services/analytics' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import { Accordion, AccordionDetails, AccordionSummary, Box, Typography } from '@mui/material' +import { useRouter } from 'next/router' + +const AllSafes = ({ + allSafes, + onLinkClick, + isSidebar, +}: { + allSafes: AllSafeItems + onLinkClick?: () => void + isSidebar: boolean +}) => { + const wallet = useWallet() + const router = useRouter() + + const isLoginPage = router.pathname === AppRoutes.welcome.accounts + const trackingLabel = isLoginPage ? OVERVIEW_LABELS.login_page : OVERVIEW_LABELS.sidebar + + return ( + + } + sx={{ + padding: 0, + '& .MuiAccordionSummary-content': { margin: '0 !important', mb: 1, flexGrow: 0 }, + }} + > +
+ + Accounts + {allSafes && allSafes.length > 0 && ( + + {' '} + ({allSafes.length}) + + )} + +
+
+ + {allSafes.length > 0 ? ( + + + + ) : ( + + {!wallet ? ( + <> + Connect a wallet to view your Safe Accounts or to create a new one + + + + + ) : ( + "You don't have any safes yet" + )} + + )} + +
+ ) +} + +export default AllSafes diff --git a/src/features/myAccounts/components/FilteredSafes/index.tsx b/src/features/myAccounts/components/FilteredSafes/index.tsx new file mode 100644 index 0000000000..13579cbca6 --- /dev/null +++ b/src/features/myAccounts/components/FilteredSafes/index.tsx @@ -0,0 +1,30 @@ +import SafesList from '@/features/myAccounts/components/SafesList' +import type { AllSafeItems } from '@/features/myAccounts/hooks/useAllSafesGrouped' +import { useSafesSearch } from '@/features/myAccounts/hooks/useSafesSearch' +import { maybePlural } from '@/utils/formatters' +import { Box, Typography } from '@mui/material' + +const FilteredSafes = ({ + searchQuery, + allSafes, + onLinkClick, +}: { + searchQuery: string + allSafes: AllSafeItems + onLinkClick?: () => void +}) => { + const filteredSafes = useSafesSearch(allSafes ?? [], searchQuery) + + return ( + <> + + Found {filteredSafes.length} result{maybePlural(filteredSafes)} + + + + + + ) +} + +export default FilteredSafes diff --git a/src/features/myAccounts/components/PinnedSafes/index.tsx b/src/features/myAccounts/components/PinnedSafes/index.tsx new file mode 100644 index 0000000000..0fd15f43b5 --- /dev/null +++ b/src/features/myAccounts/components/PinnedSafes/index.tsx @@ -0,0 +1,43 @@ +import SafesList from '@/features/myAccounts/components/SafesList' +import type { SafeItem } from '@/features/myAccounts/hooks/useAllSafes' +import type { AllSafeItems, MultiChainSafeItem } from '@/features/myAccounts/hooks/useAllSafesGrouped' +import css from '@/features/myAccounts/styles.module.css' +import BookmarkIcon from '@/public/images/apps/bookmark.svg' +import { Box, SvgIcon, Typography } from '@mui/material' +import { useMemo } from 'react' + +const PinnedSafes = ({ allSafes, onLinkClick }: { allSafes: AllSafeItems; onLinkClick?: () => void }) => { + const pinnedSafes = useMemo<(MultiChainSafeItem | SafeItem)[]>( + () => [...(allSafes?.filter(({ isPinned }) => isPinned) ?? [])], + [allSafes], + ) + + return ( + +
+ + + Pinned + +
+ {pinnedSafes.length > 0 ? ( + + ) : ( + + + Personalize your account list by clicking the + + icon on the accounts most important to you. + + + )} +
+ ) +} + +export default PinnedSafes diff --git a/src/features/myAccounts/hooks/useAllSafesGrouped.ts b/src/features/myAccounts/hooks/useAllSafesGrouped.ts index 6751d36f7a..021593a3dd 100644 --- a/src/features/myAccounts/hooks/useAllSafesGrouped.ts +++ b/src/features/myAccounts/hooks/useAllSafesGrouped.ts @@ -11,11 +11,13 @@ export type MultiChainSafeItem = { name: string | undefined } -export type AllSafesGrouped = { +export type AllSafeItemsGrouped = { allSingleSafes: SafeItems | undefined allMultiChainSafes: MultiChainSafeItem[] | undefined } +export type AllSafeItems = Array + export const _buildMultiChainSafeItem = (address: string, safes: SafeItems): MultiChainSafeItem => { const isPinned = safes.some((safe) => safe.isPinned) const lastVisited = safes.reduce((acc, safe) => Math.max(acc, safe.lastVisited || 0), 0) @@ -43,7 +45,7 @@ export const _getSingleChainAccounts = (safes: SafeItems, allMultiChainSafes: Mu export const useAllSafesGrouped = () => { const allSafes = useAllSafes() - return useMemo(() => { + return useMemo(() => { if (!allSafes) { return { allMultiChainSafes: undefined, allSingleSafes: undefined } } diff --git a/src/features/myAccounts/hooks/useTrackedSafesCount.ts b/src/features/myAccounts/hooks/useTrackedSafesCount.ts index 9c90115828..e8d09933c2 100644 --- a/src/features/myAccounts/hooks/useTrackedSafesCount.ts +++ b/src/features/myAccounts/hooks/useTrackedSafesCount.ts @@ -4,7 +4,7 @@ import { useRouter } from 'next/router' import { useEffect, useMemo } from 'react' import type { ConnectedWallet } from '@/hooks/wallets/useOnboard' import { type SafeItem } from './useAllSafes' -import type { AllSafesGrouped } from './useAllSafesGrouped' +import type { AllSafeItemsGrouped } from './useAllSafesGrouped' import { type MultiChainSafeItem } from './useAllSafesGrouped' import { isMultiChainSafeItem } from '@/features/multichain/utils/utils' @@ -12,11 +12,7 @@ let isOwnedSafesTracked = false let isPinnedSafesTracked = false let isWatchlistTracked = false -const useTrackSafesCount = ( - safes: AllSafesGrouped, - pinnedSafes: (MultiChainSafeItem | SafeItem)[], - wallet: ConnectedWallet | null, -) => { +const useTrackSafesCount = (safes: AllSafeItemsGrouped, wallet: ConnectedWallet | null) => { const router = useRouter() const isLoginPage = router.pathname === AppRoutes.welcome.accounts @@ -46,6 +42,15 @@ const useTrackSafesCount = ( [safes, watchlistMultiChainSafes], ) + // TODO: This is computed here and inside PinnedSafes now. Find a way to optimize it + const pinnedSafes = useMemo<(MultiChainSafeItem | SafeItem)[]>( + () => [ + ...(safes.allSingleSafes?.filter(({ isPinned }) => isPinned) ?? []), + ...(safes.allMultiChainSafes?.filter(({ isPinned }) => isPinned) ?? []), + ], + [safes], + ) + // Reset tracking for new wallet useEffect(() => { isOwnedSafesTracked = false diff --git a/src/features/myAccounts/index.tsx b/src/features/myAccounts/index.tsx index c14ecaca50..fae0727b57 100644 --- a/src/features/myAccounts/index.tsx +++ b/src/features/myAccounts/index.tsx @@ -1,271 +1,61 @@ -import { useCallback, useMemo, useState } from 'react' -import { - Accordion, - AccordionDetails, - AccordionSummary, - Box, - Button, - Divider, - InputAdornment, - Link, - Paper, - SvgIcon, - TextField, - Typography, -} from '@mui/material' -import debounce from 'lodash/debounce' +import AccountsFilter from '@/features/myAccounts/components/AccountsFilter' +import AccountsHeader from '@/features/myAccounts/components/AccountsHeader' +import AccountsList from '@/features/myAccounts/components/AccountsList' +import { useMemo, useState } from 'react' +import { Box, Divider, Paper } from '@mui/material' import madProps from '@/utils/mad-props' -import CreateButton from '@/features/myAccounts/components/CreateButton' -import AddIcon from '@/public/images/common/add.svg' -import Track from '@/components/common/Track' -import { OVERVIEW_EVENTS, OVERVIEW_LABELS } from '@/services/analytics' import css from '@/features/myAccounts/styles.module.css' -import SafesList from '@/features/myAccounts/components/SafesList' -import { AppRoutes } from '@/config/routes' import useWallet from '@/hooks/wallets/useWallet' -import { useRouter } from 'next/router' import { - type AllSafesGrouped, + type AllSafeItemsGrouped, useAllSafesGrouped, - type MultiChainSafeItem, + type AllSafeItems, } from '@/features/myAccounts/hooks/useAllSafesGrouped' -import { type SafeItem } from '@/features/myAccounts/hooks/useAllSafes' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import BookmarkIcon from '@/public/images/apps/bookmark.svg' import classNames from 'classnames' import { getComparator } from '@/features/myAccounts/utils/utils' -import SearchIcon from '@/public/images/common/search.svg' -import type { OrderByOption } from '@/store/orderByPreferenceSlice' -import { selectOrderByPreference, setOrderByPreference } from '@/store/orderByPreferenceSlice' -import { useAppDispatch, useAppSelector } from '@/store' -import { useSafesSearch } from '@/features/myAccounts/hooks/useSafesSearch' +import { selectOrderByPreference } from '@/store/orderByPreferenceSlice' +import { useAppSelector } from '@/store' import useTrackSafesCount from '@/features/myAccounts/hooks/useTrackedSafesCount' import { DataWidget } from '@/features/myAccounts/components/DataWidget' -import OrderByButton from '@/features/myAccounts/components/OrderByButton' -import ConnectWalletButton from '@/components/common/ConnectWallet/ConnectWalletButton' -import { maybePlural } from '@/utils/formatters' -type AccountsListProps = { - safes: AllSafesGrouped +type MyAccountsProps = { + safes: AllSafeItemsGrouped isSidebar?: boolean onLinkClick?: () => void } -const AccountsList = ({ safes, onLinkClick, isSidebar = false }: AccountsListProps) => { +const MyAccounts = ({ safes, onLinkClick, isSidebar = false }: MyAccountsProps) => { const wallet = useWallet() - const router = useRouter() const { orderBy } = useAppSelector(selectOrderByPreference) - const dispatch = useAppDispatch() const sortComparator = getComparator(orderBy) const [searchQuery, setSearchQuery] = useState('') + useTrackSafesCount(safes, wallet) - const allSafes = useMemo( + const allSafes = useMemo( () => [...(safes.allMultiChainSafes ?? []), ...(safes.allSingleSafes ?? [])].sort(sortComparator), [safes.allMultiChainSafes, safes.allSingleSafes, sortComparator], ) - const filteredSafes = useSafesSearch(allSafes ?? [], searchQuery).sort(sortComparator) - - const pinnedSafes = useMemo<(MultiChainSafeItem | SafeItem)[]>( - () => [...(allSafes?.filter(({ isPinned }) => isPinned) ?? [])], - [allSafes], - ) - - const handleOrderByChange = (orderBy: OrderByOption) => { - dispatch(setOrderByPreference({ orderBy })) - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - const handleSearch = useCallback(debounce(setSearchQuery, 300), []) - - useTrackSafesCount(safes, pinnedSafes, wallet) - - const isLoginPage = router.pathname === AppRoutes.welcome.accounts - const trackingLabel = isLoginPage ? OVERVIEW_LABELS.login_page : OVERVIEW_LABELS.sidebar return ( - - - My accounts - - - - - - - - - {wallet ? ( - - - - ) : ( - - - - )} - - + - - - { - handleSearch(e.target.value) - }} - className={css.search} - InputProps={{ - startAdornment: ( - - - - ), - disableUnderline: true, - }} - fullWidth - size="small" - /> - - - + {isSidebar && } - {searchQuery ? ( - <> - {/* Search results */} - - Found {filteredSafes.length} result{maybePlural(filteredSafes)} - - - - - - ) : ( - <> - {/* Pinned Accounts */} - -
- - - Pinned - -
- {pinnedSafes.length > 0 ? ( - - ) : ( - - - Personalize your account list by clicking the - - icon on the accounts most important to you. - - - )} -
- - {/* All Accounts */} - - } - sx={{ - padding: 0, - '& .MuiAccordionSummary-content': { margin: '0 !important', mb: 1, flexGrow: 0 }, - }} - > -
- - Accounts - {allSafes && allSafes.length > 0 && ( - - {' '} - ({allSafes.length}) - - )} - -
-
- - {allSafes.length > 0 ? ( - - - - ) : ( - - {!wallet ? ( - <> - Connect a wallet to view your Safe Accounts or to create a new one - - - - - ) : ( - "You don't have any safes yet" - )} - - )} - -
- - )} +
+ {isSidebar && }
@@ -273,8 +63,6 @@ const AccountsList = ({ safes, onLinkClick, isSidebar = false }: AccountsListPro ) } -const MyAccounts = madProps(AccountsList, { +export default madProps(MyAccounts, { safes: useAllSafesGrouped, }) - -export default MyAccounts