Skip to content

Commit

Permalink
Fix: prevent key prop warning in EthHashInfo (#2351)
Browse files Browse the repository at this point in the history
* Fix: prevent key prop warning in EthHashInfo

Fix: prevent key prop warning in EthHashInfo

Fix: prevent key prop warning in EthHashInfo

* Fix tests

* PR comments
  • Loading branch information
katspaugh authored Aug 7, 2023
1 parent bd9118c commit 8826af3
Show file tree
Hide file tree
Showing 17 changed files with 262 additions and 89 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"@emotion/react": "^11.10.0",
"@emotion/server": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@mui/icons-material": "^5.8.4",
"@mui/material": "^5.13.5",
"@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.3",
"@mui/x-date-pickers": "^5.0.12",
"@reduxjs/toolkit": "^1.9.5",
"@safe-global/safe-apps-sdk": "7.11.0",
Expand All @@ -52,7 +52,7 @@
"@safe-global/safe-ethers-lib": "^1.9.4",
"@safe-global/safe-gateway-typescript-sdk": "^3.8.0",
"@safe-global/safe-modules-deployments": "^1.0.0",
"@safe-global/safe-react-components": "^2.0.5",
"@safe-global/safe-react-components": "^2.0.6",
"@sentry/react": "^7.28.1",
"@sentry/tracing": "^7.28.1",
"@truffle/hdwallet-provider": "^2.1.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import CopyButton from '../../CopyButton'

type CopyAddressButtonProps = {
prefix?: string
address: string
copyPrefix?: boolean
}

const CopyAddressButton = ({ prefix, address, copyPrefix }: CopyAddressButtonProps): React.ReactElement => {
const addressText = copyPrefix && prefix ? `${prefix}:${address}` : address
return <CopyButton text={addressText} />
}

export default CopyAddressButton
96 changes: 96 additions & 0 deletions src/components/common/EthHashInfo/SrcEthHashInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { ReactNode, ReactElement } from 'react'
import { isAddress } from 'ethers/lib/utils'
import { useTheme } from '@mui/material/styles'
import Box from '@mui/material/Box'
import useMediaQuery from '@mui/material/useMediaQuery'
import Identicon from '../../Identicon'
import CopyAddressButton from '../../CopyAddressButton'
import ExplorerButton, { type ExplorerButtonProps } from '../../ExplorerButton'
import { shortenAddress } from '@/utils/formatters'
import ImageFallback from '../../ImageFallback'
import css from './styles.module.css'

export type EthHashInfoProps = {
address: string
chainId?: string
name?: string | null
showAvatar?: boolean
showCopyButton?: boolean
prefix?: string
showPrefix?: boolean
copyPrefix?: boolean
shortAddress?: boolean
customAvatar?: string
hasExplorer?: boolean
avatarSize?: number
children?: ReactNode
ExplorerButtonProps?: ExplorerButtonProps
}

const SrcEthHashInfo = ({
address,
customAvatar,
prefix = '',
copyPrefix,
showPrefix,
shortAddress = true,
showAvatar = true,
avatarSize,
name,
showCopyButton,
hasExplorer,
ExplorerButtonProps,
children,
}: EthHashInfoProps): ReactElement => {
const shouldPrefix = isAddress(address)
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))

const identicon = <Identicon address={address} size={avatarSize} />

return (
<div className={css.container}>
{showAvatar && (
<div
className={css.avatarContainer}
style={avatarSize ? { width: `${avatarSize}px`, height: `${avatarSize}px` } : undefined}
>
{customAvatar ? (
<ImageFallback src={customAvatar} fallbackComponent={identicon} width={avatarSize} height={avatarSize} />
) : (
identicon
)}
</div>
)}

<Box overflow="hidden">
{name && (
<Box sx={{ fontSize: 'body2' }} textOverflow="ellipsis" overflow="hidden" title={name}>
{name}
</Box>
)}

<div className={css.addressContainer}>
<Box fontWeight="inherit" fontSize="inherit">
{showPrefix && shouldPrefix && prefix && <b>{prefix}:</b>}
<span>{shortAddress || isMobile ? shortenAddress(address) : address}</span>
</Box>

{showCopyButton && (
<CopyAddressButton prefix={prefix} address={address} copyPrefix={shouldPrefix && copyPrefix} />
)}

{hasExplorer && ExplorerButtonProps && (
<Box color="border.main">
<ExplorerButton {...ExplorerButtonProps} />
</Box>
)}

{children}
</div>
</Box>
</div>
)
}

export default SrcEthHashInfo
22 changes: 22 additions & 0 deletions src/components/common/EthHashInfo/SrcEthHashInfo/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.container {
display: flex;
align-items: center;
gap: 0.5em;
line-height: 1.4;
}

.avatarContainer {
flex-shrink: 0;
}

.avatarContainer > * {
width: 100% !important;
height: 100% !important;
}

.addressContainer {
display: flex;
align-items: center;
gap: 0.25em;
white-space: nowrap;
}
11 changes: 5 additions & 6 deletions src/components/common/EthHashInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { type ReactElement } from 'react'
import { EthHashInfo } from '@safe-global/safe-react-components'
import useAddressBook from '@/hooks/useAddressBook'
import useChainId from '@/hooks/useChainId'
import { useAppSelector } from '@/store'
import { selectSettings } from '@/store/settingsSlice'
import { selectChainById } from '@/store/chainsSlice'
import { getBlockExplorerLink } from '@/utils/chains'
import type { EthHashInfoProps } from '@safe-global/safe-react-components'
import css from './styles.module.css'
import { Emoji } from './AddressEmoji'
import SrcEthHashInfo, { type EthHashInfoProps } from './SrcEthHashInfo'

const PrefixedEthHashInfo = ({
const EthHashInfo = ({
showName = true,
avatarSize = 44,
...props
Expand All @@ -25,7 +24,7 @@ const PrefixedEthHashInfo = ({

return (
<div className={css.container}>
<EthHashInfo
<SrcEthHashInfo
prefix={chain?.shortName}
showPrefix={settings.shortName.show}
copyPrefix={settings.shortName.copy}
Expand All @@ -36,10 +35,10 @@ const PrefixedEthHashInfo = ({
avatarSize={avatarSize}
>
{props.children}
</EthHashInfo>
</SrcEthHashInfo>
{showEmoji && <Emoji address={props.address} size={avatarSize} />}
</div>
)
}

export default PrefixedEthHashInfo
export default EthHashInfo
27 changes: 27 additions & 0 deletions src/components/common/ExplorerButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ReactElement, ComponentType } from 'react'
import { IconButton, SvgIcon, Tooltip } from '@mui/material'
import LinkIcon from '@/public/images/common/link.svg'

export type ExplorerButtonProps = {
title?: string
href?: string
className?: string
icon?: ComponentType
}

const ExplorerButton = ({ title = '', href = '', icon = LinkIcon, className }: ExplorerButtonProps): ReactElement => (
<Tooltip title={title} placement="top">
<IconButton
className={className}
target="_blank"
rel="noreferrer"
href={href}
size="small"
sx={{ color: 'inherit' }}
>
<SvgIcon component={icon} inheritViewBox fontSize="small" />
</IconButton>
</Tooltip>
)

export default ExplorerButton
19 changes: 14 additions & 5 deletions src/components/common/ImageFallback/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import type { ReactElement } from 'react'
import { useState } from 'react'

type ImageFallbackProps = React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement> & {
fallbackSrc: string
fallbackComponent?: ReactElement
}
type ImageAttributes = React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>

type ImageFallbackProps = ImageAttributes &
(
| {
fallbackSrc: string
fallbackComponent?: ReactElement
}
| {
fallbackSrc?: string
fallbackComponent: ReactElement
}
)

const ImageFallback = ({ src, fallbackSrc, fallbackComponent, ...props }: ImageFallbackProps): React.ReactElement => {
const [isError, setIsError] = useState<boolean>(false)
Expand All @@ -14,7 +23,7 @@ const ImageFallback = ({ src, fallbackSrc, fallbackComponent, ...props }: ImageF
return (
<img
{...props}
alt={props.alt}
alt={props.alt || ''}
src={isError || src === undefined ? fallbackSrc : src}
onError={() => setIsError(true)}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/components/settings/DelegatesList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getDelegates } from '@safe-global/safe-gateway-typescript-sdk'
import useAsync from '@/hooks/useAsync'
import useSafeInfo from '@/hooks/useSafeInfo'
import { Box, Grid, Paper, SvgIcon, Tooltip, Typography } from '@mui/material'
import PrefixedEthHashInfo from '@/components/common/EthHashInfo'
import EthHashInfo from '@/components/common/EthHashInfo'
import InfoIcon from '@/public/images/notifications/info.svg'
import ExternalLink from '@/components/common/ExternalLink'
import { HelpCenterArticle } from '@/config/constants'
Expand Down Expand Up @@ -61,7 +61,7 @@ const DelegatesList = () => {
style={{ listStyleType: 'none', marginBottom: '1em' }}
title={`Delegated by ${item.delegator}`}
>
<PrefixedEthHashInfo
<EthHashInfo
address={item.delegate}
showCopyButton
hasExplorer
Expand Down
12 changes: 2 additions & 10 deletions src/components/sidebar/SidebarHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { SvgIcon } from '@mui/material'
import { useVisibleBalances } from '@/hooks/useVisibleBalances'
import EnvHintButton from '@/components/settings/EnvironmentVariables/EnvHintButton'
import useSafeAddress from '@/hooks/useSafeAddress'
import ExplorerButton from '@/components/common/ExplorerButton'

const SafeHeader = (): ReactElement => {
const currency = useAppSelector(selectCurrency)
Expand Down Expand Up @@ -93,16 +94,7 @@ const SafeHeader = (): ReactElement => {
</Track>

<Track {...OVERVIEW_EVENTS.OPEN_EXPLORER}>
<Tooltip title={blockExplorerLink?.title || ''} placement="top">
<IconButton
className={css.iconButton}
target="_blank"
rel="noreferrer"
href={blockExplorerLink?.href || ''}
>
<SvgIcon component={LinkIconBold} inheritViewBox fontSize="small" color="primary" />
</IconButton>
</Tooltip>
<ExplorerButton {...blockExplorerLink} className={css.iconButton} icon={LinkIconBold} />
</Track>

<EnvHintButton />
Expand Down
1 change: 1 addition & 0 deletions src/components/sidebar/SidebarHeader/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
.iconButton {
border-radius: 4px;
padding: 6px;
color: var(--color-primary-main);
background-color: var(--color-background-main);
width: 32px;
height: 32px;
Expand Down
2 changes: 1 addition & 1 deletion src/components/tx-flow/flows/AddOwner/ChooseOwner.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { EthHashInfo } from '@safe-global/safe-react-components'
import {
Box,
Typography,
Expand Down Expand Up @@ -27,6 +26,7 @@ import TxCard from '../../common/TxCard'
import InfoIcon from '@/public/images/notifications/info.svg'
import commonCss from '@/components/tx-flow/common/styles.module.css'
import { TOOLTIP_TITLES } from '@/components/tx-flow/common/constants'
import EthHashInfo from '@/components/common/EthHashInfo'

type FormData = Pick<AddOwnerFlowProps | ReplaceOwnerFlowProps, 'newOwner' | 'threshold'>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useContext, useEffect } from 'react'
import { Typography, Divider, Box, Paper, SvgIcon } from '@mui/material'
import { EthHashInfo } from '@safe-global/safe-react-components'
import type { ReactElement } from 'react'

import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm'
Expand All @@ -11,6 +10,7 @@ import { createRemoveOwnerTx } from '@/services/tx/tx-sender'
import MinusIcon from '@/public/images/common/minus.svg'
import { SafeTxContext } from '../../SafeTxProvider'
import type { RemoveOwnerFlowProps } from '.'
import EthHashInfo from '@/components/common/EthHashInfo'

import commonCss from '@/components/tx-flow/common/styles.module.css'

Expand Down
8 changes: 4 additions & 4 deletions src/components/tx/ApprovalEditor/ApprovalEditorForm.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { fireEvent, getAllByRole, render, waitFor } from '@/tests/test-utils'
import { fireEvent, render, waitFor } from '@/tests/test-utils'
import { hexZeroPad } from 'ethers/lib/utils'
import { TokenType } from '@safe-global/safe-gateway-typescript-sdk'
import { ApprovalEditorForm } from '@/components/tx/ApprovalEditor/ApprovalEditorForm'
import { getAllByTestId } from '@testing-library/dom'
import { getAllByTestId, getAllByTitle } from '@testing-library/dom'

describe('ApprovalEditorForm', () => {
beforeEach(() => {
Expand Down Expand Up @@ -39,7 +39,7 @@ describe('ApprovalEditorForm', () => {
expect(approvalItems).toHaveLength(2)

// One button for each approval
const buttons = getAllByRole(result.container, 'button')
const buttons = getAllByTitle(result.container, 'Save')
expect(buttons).toHaveLength(2)

// First approval value is rendered
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('ApprovalEditorForm', () => {

// Change value and save
const amountInput = result.container.querySelector('input[name="approvals.0"]') as HTMLInputElement
const saveButton = result.getByRole('button')
const saveButton = result.getByTitle('Save')

fireEvent.change(amountInput!, { target: { value: '100' } })
fireEvent.click(saveButton)
Expand Down
4 changes: 2 additions & 2 deletions src/components/tx/ApprovalEditor/ApprovalItem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type ReactElement } from 'react'
import { Alert, Grid, Typography } from '@mui/material'
import css from '@/components/tx/ApprovalEditor/styles.module.css'
import PrefixedEthHashInfo from '@/components/common/EthHashInfo'
import EthHashInfo from '@/components/common/EthHashInfo'

const ApprovalItem = ({ spender, children }: { spender: string; children: ReactElement }) => {
return (
Expand All @@ -20,7 +20,7 @@ const ApprovalItem = ({ spender, children }: { spender: string; children: ReactE

<Grid item>
<Typography fontSize="14px">
<PrefixedEthHashInfo address={spender} hasExplorer showAvatar={false} shortAddress={false} />
<EthHashInfo address={spender} hasExplorer showAvatar={false} shortAddress={false} />
</Typography>
</Grid>
</Grid>
Expand Down
Loading

0 comments on commit 8826af3

Please sign in to comment.