Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: redesign Safe List in sidebar #3245

Merged
merged 14 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/e2e/pages/load_safe.pages.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as constants from '../../support/constants'

const addExistingAccountBtnStr = 'Add existing one'
const contactStr = 'Name, address & network'
const contactStr = 'Choose address, network and a name'
export const invalidAddressFormatErrorMsg = 'Invalid address format'
const invalidAddressNameLengthErrorMsg = 'Maximum 50 symbols'

Expand Down
5 changes: 0 additions & 5 deletions cypress/e2e/pages/main.page.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ export function waitForHistoryCallToComplete() {
cy.wait('@History')
}

export function waitForSafeListRequestToComplete() {
cy.intercept('GET', constants.safeListEndpoint).as('Safes')
cy.wait('@Safes')
}

export function acceptCookies(index = 0) {
cy.wait(1000)

Expand Down
1 change: 0 additions & 1 deletion cypress/e2e/regression/create_safe_simple.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as owner from '../pages/owners.pages'
describe('Safe creation tests', () => {
beforeEach(() => {
cy.visit(constants.welcomeUrl + '?chain=sep')
main.waitForSafeListRequestToComplete()
cy.clearLocalStorage()
main.acceptCookies()
})
Expand Down
1 change: 0 additions & 1 deletion cypress/e2e/smoke/create_safe_simple.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as owner from '../pages/owners.pages'
describe('[SMOKE] Safe creation tests', () => {
beforeEach(() => {
cy.visit(constants.welcomeUrl + '?chain=sep')
main.waitForSafeListRequestToComplete()
cy.clearLocalStorage()
main.acceptCookies()
})
Expand Down
14 changes: 10 additions & 4 deletions src/components/common/ChainIndicator/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@
}

@media (max-width: 899.95px) {
.indicator {
min-width: 35px;
}
}

@container my-accounts-container (max-width: 500px) {
.responsive {
min-width: 0;
}
.responsive .name {
display: none;
}
.indicator {
min-width: 35px;
}
}
}
14 changes: 11 additions & 3 deletions src/components/common/ConnectWallet/ConnectWalletButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Button } from '@mui/material'
import useConnectWallet from '@/components/common/ConnectWallet/useConnectWallet'

const ConnectWalletButton = ({ onConnect }: { onConnect?: () => void }): React.ReactElement => {
const ConnectWalletButton = ({
onConnect,
contained = true,
text,
}: {
onConnect?: () => void
contained?: boolean
text?: string
}): React.ReactElement => {
const connectWallet = useConnectWallet()

const handleConnect = () => {
Expand All @@ -12,13 +20,13 @@ const ConnectWalletButton = ({ onConnect }: { onConnect?: () => void }): React.R
return (
<Button
onClick={handleConnect}
variant="contained"
variant={contained ? 'contained' : 'text'}
size="small"
disableElevation
fullWidth
sx={{ fontSize: ['12px', '13px'] }}
>
Connect
{text || 'Connect'}
</Button>
)
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/new-safe/load/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type LoadSafeFormData = NamedAddress & {

export const LoadSafeSteps: TxStepperProps<LoadSafeFormData>['steps'] = [
{
title: 'Name, address & network',
title: 'Choose address, network and a name',
subtitle: 'Paste the address of the Safe Account you want to add, select the network and choose a name.',
render: (data, onSubmit, onBack, setStep) => (
<SetAddressStep onSubmit={onSubmit} onBack={onBack} data={data} setStep={setStep} />
Expand All @@ -33,7 +33,7 @@ export const LoadSafeSteps: TxStepperProps<LoadSafeFormData>['steps'] = [
},
{
title: 'Review',
subtitle: 'Confirm loading Safe Account',
subtitle: 'Confirm adding Safe Account to your Watchlist',
render: (data, onSubmit, onBack, setStep) => (
<SafeReviewStep onSubmit={onSubmit} onBack={onBack} data={data} setStep={setStep} />
),
Expand All @@ -56,7 +56,7 @@ const LoadSafe = ({ initialData }: { initialData?: TxStepperProps<LoadSafeFormDa
<Grid container columnSpacing={3} mt={[2, null, 7]} justifyContent="center">
<Grid item xs={12} md={10} lg={8}>
<Typography variant="h2" pb={2}>
Load Safe Account
Add Safe Account to watchlist
</Typography>
</Grid>
<Grid item xs={12} md={10} lg={8} order={[1, null, 0]}>
Expand Down
9 changes: 2 additions & 7 deletions src/components/sidebar/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import ChevronRight from '@mui/icons-material/ChevronRight'

import ChainIndicator from '@/components/common/ChainIndicator'
import SidebarHeader from '@/components/sidebar/SidebarHeader'
import SafeList from '@/components/sidebar/SafeList'
import SidebarNavigation from '@/components/sidebar/SidebarNavigation'
import SidebarFooter from '@/components/sidebar/SidebarFooter'

import css from './styles.module.css'
import { trackEvent, OVERVIEW_EVENTS } from '@/services/analytics'
import { DataWidget } from '@/components/welcome/SafeListDrawer/DataWidget'
import MyAccounts from '@/components/welcome/MyAccounts'

const Sidebar = (): ReactElement => {
const [isDrawerOpen, setIsDrawerOpen] = useState<boolean>(false)
Expand Down Expand Up @@ -53,11 +52,7 @@ const Sidebar = (): ReactElement => {

<Drawer variant="temporary" anchor="left" open={isDrawerOpen} onClose={onDrawerToggle}>
<div className={css.drawer}>
<SafeList closeDrawer={closeDrawer} />

<div className={css.dataWidget}>
<DataWidget />
</div>
<MyAccounts onLinkClick={closeDrawer}></MyAccounts>
</div>
</Drawer>
</div>
Expand Down
21 changes: 16 additions & 5 deletions src/components/welcome/MyAccounts/AccountItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,29 @@ import css from './styles.module.css'
import { selectAllAddressBooks } from '@/store/addressBookSlice'
import { shortenAddress } from '@/utils/formatters'
import SafeListContextMenu from '@/components/sidebar/SafeListContextMenu'
import useSafeAddress from '@/hooks/useSafeAddress'
import useChainId from '@/hooks/useChainId'
import { sameAddress } from '@/utils/addresses'
import classnames from 'classnames'

type AccountItemProps = {
chainId: string
address: string
threshold?: number
owners?: number
onLinkClick?: () => void
}

const getSafeHref = (prefix: string, address: string) => ({
pathname: AppRoutes.home,
query: { safe: `${prefix}:${address}` },
})

const AccountItem = ({ chainId, address, ...rest }: AccountItemProps) => {
const AccountItem = ({ onLinkClick, chainId, address, ...rest }: AccountItemProps) => {
const chain = useAppSelector((state) => selectChainById(state, chainId))
const safeAddress = useSafeAddress()
const currChainId = useChainId()
const isCurrentSafe = chainId === currChainId && sameAddress(safeAddress, address)

const href = useMemo(() => {
return chain ? getSafeHref(chain.shortName, address) : ''
Expand All @@ -35,9 +43,12 @@ const AccountItem = ({ chainId, address, ...rest }: AccountItemProps) => {
const name = useAppSelector(selectAllAddressBooks)[chainId]?.[address]

return (
<ListItemButton className={css.listItem}>
<ListItemButton
selected={isCurrentSafe}
className={classnames(css.listItem, { [css.currentListItem]: isCurrentSafe })}
>
<Track {...OVERVIEW_EVENTS.OPEN_SAFE} label={OPEN_SAFE_LABELS.login_page}>
<Link href={href} className={css.safeLink}>
<Link onClick={onLinkClick} href={href} className={css.safeLink}>
<SafeIcon address={address} {...rest} />

<Typography variant="body2" component="div" className={css.safeAddress}>
Expand All @@ -46,8 +57,8 @@ const AccountItem = ({ chainId, address, ...rest }: AccountItemProps) => {
{name}
</Typography>
)}
<b>{chain?.shortName}: </b>
<Typography color="text.secondary" fontSize="inherit" component="span">
{chain?.shortName}:
<Typography color="var(--color-primary-light)" fontSize="inherit" component="span">
{shortenAddress(address)}
</Typography>
</Typography>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Button, Grid, SvgIcon, Card, CardHeader, CardContent, Box } from '@mui/material'
import { Button, Grid, SvgIcon, Card, CardHeader, CardContent, Tooltip } from '@mui/material'
import { useState } from 'react'
import type { ReactElement } from 'react'

import { useAppSelector } from '@/store'
import { selectAllAddedSafes } from '@/store/addedSafesSlice'
import { selectAllAddressBooks } from '@/store/addressBookSlice'
import FileIcon from '@/public/images/settings/data/file.svg'
import ExportIcon from '@/public/images/common/export.svg'
import ImportIcon from '@/public/images/common/import.svg'
import { exportAppData } from '@/components/settings/DataManagement'
import { ImportDialog } from '@/components/settings/DataManagement/ImportDialog'
import { OVERVIEW_EVENTS } from '@/services/analytics'
import Track from '@/components/common/Track'
import InfoIcon from '@/public/images/notifications/info.svg'

import css from './styles.module.css'

Expand All @@ -34,22 +34,24 @@ export const DataWidget = (): ReactElement => {
return (
<Card className={css.card}>
<CardHeader
avatar={
<Box
className={css.fileIcon}
sx={{
borderRadius: ({ shape }) => `${shape.borderRadius}px`,
}}
>
<SvgIcon component={FileIcon} inheritViewBox fontSize="small" sx={{ fill: 'none' }} />
</Box>
}
className={css.cardHeader}
title={<b>{hasData ? 'Work with your data' : 'Already have a Safe Account?'}</b>}
subheader={hasData ? 'Export or import your data' : 'Import your data'}
title={
<>
<b>{hasData ? 'Export or import your Safe data' : 'Import your Safe data'}</b>
<Tooltip
title="Download or upload your local data with your added Safe Accounts, address book and settings."
placement="top"
arrow
>
<span>
<InfoIcon className={css.infoIcon} />
</span>
</Tooltip>
</>
}
/>
<CardContent>
<Grid container spacing={2}>
<Grid container spacing={2} sx={{ maxWidth: 240, margin: 'auto', paddingRight: 2 }}>
{hasData && (
<Grid item xs={6}>
<Track {...OVERVIEW_EVENTS.EXPORT_DATA}>
Expand All @@ -58,7 +60,7 @@ export const DataWidget = (): ReactElement => {
size="small"
onClick={exportAppData}
startIcon={<SvgIcon component={ExportIcon} inheritViewBox fontSize="small" />}
sx={{ width: '100%' }}
sx={{ width: '100%', py: 0.5 }}
>
Export
</Button>
Expand All @@ -72,7 +74,7 @@ export const DataWidget = (): ReactElement => {
size="small"
onClick={onImport}
startIcon={<SvgIcon component={ImportIcon} inheritViewBox fontSize="small" />}
sx={{ width: '100%' }}
sx={{ width: '100%', py: 0.5 }}
>
Import
</Button>
Expand Down
66 changes: 66 additions & 0 deletions src/components/welcome/MyAccounts/PaginatedSafeList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { ReactElement, ReactNode } from 'react'
import { useMemo, useState } from 'react'
import { Button, Box, Paper, Typography } from '@mui/material'
import AccountItem from './AccountItem'
import { type SafeItems } from './useAllSafes'
import Track from '@/components/common/Track'
import { OVERVIEW_EVENTS } from '@/services/analytics'
import css from './styles.module.css'

type PaginatedSafeListProps = {
safes: SafeItems
title: ReactNode
noSafesMessage?: ReactNode
action?: ReactElement
onLinkClick?: () => void
}

const DEFAULT_SHOWN = 5
const MAX_DEFAULT_SHOWN = 7
const PAGE_SIZE = 5

const PaginatedSafeList = ({ safes, title, action, noSafesMessage, onLinkClick }: PaginatedSafeListProps) => {
const [maxShownSafes, setMaxShownSafes] = useState<number>(DEFAULT_SHOWN)

const shownSafes = useMemo(() => {
if (safes.length <= MAX_DEFAULT_SHOWN) {
return safes
}
return safes.slice(0, maxShownSafes)
}, [safes, maxShownSafes])

const onShowMoreSafes = () => setMaxShownSafes((prev) => prev + PAGE_SIZE)

return (
<Paper className={css.safeList}>
<div className={css.listHeader}>
<Typography variant="h5" fontWeight={700} mb={2} className={css.listTitle}>
{title}
{safes.length > 0 && (
<Typography component="span" color="var(--color-primary-light)" fontSize="inherit" fontWeight="normal">
{' '}
({safes.length})
</Typography>
)}
</Typography>
{action}
</div>
{shownSafes.length ? (
shownSafes.map((item) => <AccountItem onLinkClick={onLinkClick} {...item} key={item.chainId + item.address} />)
) : (
<Typography variant="body2" color="text.secondary" textAlign="center" my={3} mx="auto" width={250}>
{noSafesMessage}
</Typography>
)}
{safes.length > shownSafes.length && (
<Box display="flex" justifyContent="center">
<Track {...OVERVIEW_EVENTS.SHOW_MORE_SAFES}>
<Button onClick={onShowMoreSafes}>Show more</Button>
</Track>
</Box>
)}
</Paper>
)
}

export default PaginatedSafeList
Loading
Loading