Skip to content

Commit

Permalink
refactor: Split up MyAccounts component
Browse files Browse the repository at this point in the history
  • Loading branch information
usame-algan committed Dec 16, 2024
1 parent 9e44ad4 commit ab87fbd
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 244 deletions.
60 changes: 60 additions & 0 deletions src/features/myAccounts/components/AccountsFilter/index.tsx
Original file line number Diff line number Diff line change
@@ -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<SetStateAction<string>> }) => {
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 (
<Paper sx={{ px: 2, py: 1 }}>
<Box display="flex" justifyContent="space-between" width="100%" gap={1}>
<TextField
id="search-by-name"
placeholder="Search"
aria-label="Search Safe list by name"
variant="filled"
hiddenLabel
onChange={(e) => {
handleSearch(e.target.value)
}}
className={css.search}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SvgIcon
component={SearchIcon}
inheritViewBox
fontWeight="bold"
fontSize="small"
sx={{
color: 'var(--color-border-main)',
'.MuiInputBase-root.Mui-focused &': { color: 'var(--color-text-primary)' },
}}
/>
</InputAdornment>
),
disableUnderline: true,
}}
fullWidth
size="small"
/>
<OrderByButton orderBy={orderBy} onOrderByChange={handleOrderByChange} />
</Box>
</Paper>
)
}

export default AccountsFilter
55 changes: 55 additions & 0 deletions src/features/myAccounts/components/AccountsHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Box className={classNames(css.header, { [css.sidebarHeader]: isSidebar })}>
<Typography variant="h1" fontWeight={700} className={css.title}>
My accounts
</Typography>
<Box className={css.headerButtons}>
<Track {...OVERVIEW_EVENTS.ADD_TO_WATCHLIST} label={trackingLabel}>
<Link href={AppRoutes.newSafe.load}>
<Button
disableElevation
variant="outlined"
size="small"
onClick={onLinkClick}
startIcon={<SvgIcon component={AddIcon} inheritViewBox fontSize="small" />}
sx={{ height: '36px', width: '100%', px: 2 }}
>
Add
</Button>
</Link>
</Track>

{wallet ? (
<Track {...OVERVIEW_EVENTS.CREATE_NEW_SAFE} label={trackingLabel}>
<CreateButton isPrimary />
</Track>
) : (
<Box sx={{ '& button': { height: '36px' } }}>
<ConnectWalletButton small={true} />
</Box>
)}
</Box>
</Box>
)
}

export default AccountsHeader
29 changes: 29 additions & 0 deletions src/features/myAccounts/components/AccountsList/index.tsx
Original file line number Diff line number Diff line change
@@ -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 <FilteredSafes searchQuery={searchQuery} allSafes={allSafes} onLinkClick={onLinkClick} />
}

return (
<>
<PinnedSafes allSafes={allSafes} onLinkClick={onLinkClick} />
<AllSafes allSafes={allSafes} onLinkClick={onLinkClick} isSidebar={isSidebar} />
</>
)
}

export default AccountsList
83 changes: 83 additions & 0 deletions src/features/myAccounts/components/AllSafes/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Accordion sx={{ border: 'none' }} defaultExpanded={!isSidebar} slotProps={{ transition: { unmountOnExit: true } }}>
<AccordionSummary
data-testid="expand-safes-list"
expandIcon={<ExpandMoreIcon sx={{ '& path': { fill: 'var(--color-text-secondary)' } }} />}
sx={{
padding: 0,
'& .MuiAccordionSummary-content': { margin: '0 !important', mb: 1, flexGrow: 0 },
}}
>
<div className={css.listHeader}>
<Typography variant="h5" fontWeight={700}>
Accounts
{allSafes && allSafes.length > 0 && (
<Typography component="span" color="text.secondary" fontSize="inherit" fontWeight="normal" mr={1}>
{' '}
({allSafes.length})
</Typography>
)}
</Typography>
</div>
</AccordionSummary>
<AccordionDetails data-testid="accounts-list" sx={{ padding: 0 }}>
{allSafes.length > 0 ? (
<Box mt={1}>
<SafesList safes={allSafes} onLinkClick={onLinkClick} />
</Box>
) : (
<Typography
data-testid="empty-account-list"
component="div"
variant="body2"
color="text.secondary"
textAlign="center"
py={3}
mx="auto"
width={250}
>
{!wallet ? (
<>
<Box mb={2}>Connect a wallet to view your Safe Accounts or to create a new one</Box>
<Track {...OVERVIEW_EVENTS.OPEN_ONBOARD} label={trackingLabel}>
<ConnectWalletButton text="Connect a wallet" contained />
</Track>
</>
) : (
"You don't have any safes yet"
)}
</Typography>
)}
</AccordionDetails>
</Accordion>
)
}

export default AllSafes
30 changes: 30 additions & 0 deletions src/features/myAccounts/components/FilteredSafes/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Typography variant="h5" fontWeight="normal" mb={2} color="primary.light">
Found {filteredSafes.length} result{maybePlural(filteredSafes)}
</Typography>
<Box mt={1}>
<SafesList safes={filteredSafes} onLinkClick={onLinkClick} useTransitions={false} />
</Box>
</>
)
}

export default FilteredSafes
43 changes: 43 additions & 0 deletions src/features/myAccounts/components/PinnedSafes/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Box data-testid="pinned-accounts" mb={2} minHeight="170px">
<div className={css.listHeader}>
<SvgIcon component={BookmarkIcon} inheritViewBox fontSize="small" sx={{ mt: '2px', mr: 1, strokeWidth: 2 }} />
<Typography variant="h5" fontWeight={700} mb={2}>
Pinned
</Typography>
</div>
{pinnedSafes.length > 0 ? (
<SafesList safes={pinnedSafes} onLinkClick={onLinkClick} />
) : (
<Box data-testid="empty-pinned-list" className={css.noPinnedSafesMessage}>
<Typography color="text.secondary" variant="body2" maxWidth="350px" textAlign="center">
Personalize your account list by clicking the
<SvgIcon
component={BookmarkIcon}
inheritViewBox
fontSize="small"
sx={{ mx: '4px', color: 'text.secondary', position: 'relative', top: '2px' }}
/>
icon on the accounts most important to you.
</Typography>
</Box>
)}
</Box>
)
}

export default PinnedSafes
6 changes: 4 additions & 2 deletions src/features/myAccounts/hooks/useAllSafesGrouped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SafeItem | MultiChainSafeItem>

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)
Expand Down Expand Up @@ -43,7 +45,7 @@ export const _getSingleChainAccounts = (safes: SafeItems, allMultiChainSafes: Mu
export const useAllSafesGrouped = () => {
const allSafes = useAllSafes()

return useMemo<AllSafesGrouped>(() => {
return useMemo<AllSafeItemsGrouped>(() => {
if (!allSafes) {
return { allMultiChainSafes: undefined, allSingleSafes: undefined }
}
Expand Down
17 changes: 11 additions & 6 deletions src/features/myAccounts/hooks/useTrackedSafesCount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@ 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'

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

Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit ab87fbd

Please sign in to comment.