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

Release/1.0.6 #1100

Merged
merged 6 commits into from
Nov 8, 2022
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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"homepage": "https://github.com/safe-global/web-core",
"license": "MIT",
"type": "module",
"version": "1.0.5",
"version": "1.0.6",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
6 changes: 4 additions & 2 deletions src/components/address-book/AddressBookTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import PagePlaceholder from '@/components/common/PagePlaceholder'
import AddressBookIcon from '@/public/images/address-book/address-book.svg'
import { useCurrentChain } from '@/hooks/useChains'

import tableCss from '@/components/common/EnhancedTable/styles.module.css'

const headCells = [
{ id: 'name', label: 'Name' },
{ id: 'address', label: 'Address' },
Expand Down Expand Up @@ -91,7 +93,7 @@ const AddressBookTable = () => {
rawValue: '',
sticky: true,
content: (
<>
<div className={tableCss.actions}>
<Track {...ADDRESS_BOOK_EVENTS.EDIT_ENTRY}>
<Tooltip title="Edit entry" placement="top">
<IconButton onClick={() => handleOpenModalWithValues(ModalType.ENTRY, address, name)} size="small">
Expand Down Expand Up @@ -121,7 +123,7 @@ const AddressBookTable = () => {
</Button>
</Track>
)}
</>
</div>
),
},
}))
Expand Down
1 change: 0 additions & 1 deletion src/components/common/EnhancedTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ function EnhancedTable({ rows, headCells, variant }: EnhancedTableProps) {
className={classNames({
sticky: cell.sticky,
[css.hide]: cell.hide,
[css.actions]: key === 'actions',
})}
>
{cell.content}
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/MetaTags/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { ContentSecurityPolicy, StrictTransportSecurity } from '@/config/securit
import palette from '@/styles/colors'
import darkPalette from '@/styles/colors-dark'

const descriptionText = 'Safe (formerly Gnosis Safe) is the most trusted platform to manage digital assets'
const descriptionText =
'Safe (prev. Gnosis Safe) is the most trusted platform to manage digital assets on Ethereum and multiple EVMs. Over $40B secured. Unlock Ownership.'
const titleText = 'Safe'

const MetaTags = ({ prefetchUrl }: { prefetchUrl: string }) => (
Expand Down
40 changes: 40 additions & 0 deletions src/components/common/PsaBanner/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.banner {
position: fixed;
z-index: 10000;
top: 0;
left: 0;
right: 0;
background-color: var(--color-info-dark);
color: var(--color-text-light);
padding: 5px 20px;
font-size: 16px;
}

.banner a {
color: inherit;
font-weight: bold;
text-decoration: underline;
}

.wrapper {
position: relative;
display: flex;
flex-direction: column;
alignitems: center;
height: 70px;
}

.content {
max-width: 960px;
margin: 0 auto;
text-align: center;
padding: 10px;
}

.close {
position: absolute;
right: 10px;
top: 10px;
cursor: pointer;
z-index: 2;
}
68 changes: 68 additions & 0 deletions src/components/common/PsaBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { ReactElement, ReactNode } from 'react'
import { isEmpty } from 'lodash'
import type { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk'
import { IconButton } from '@mui/material'
import CloseIcon from '@mui/icons-material/Close'
import styles from './index.module.css'
import { hasFeature } from '@/utils/chains'
import { useCurrentChain } from '@/hooks/useChains'
import useLocalStorage from '@/services/local-storage/useLocalStorage'
import { useRouter } from 'next/router'
import { selectAllAddressBooks } from '@/store/addressBookSlice'
import { useAppSelector } from '@/store'

const WARNING_BANNER = 'WARNING_BANNER'
const OLD_APP = 'https://gnosis-safe.io/app'
const NO_REDIRECT = '?no-redirect=true'

const ExportLink = ({ children }: { children: ReactNode }): ReactElement => {
const router = useRouter()
const safeAddress = router.query.safe as string
const url = safeAddress ? `${OLD_APP}/${safeAddress}/address-book${NO_REDIRECT}` : `${OLD_APP}${NO_REDIRECT}`

return (
<a href={url} target="_blank" rel="noreferrer">
{children}
</a>
)
}

const BANNERS: Record<string, ReactElement | string> = {
'*': (
<>
<b>app.safe.global</b> is Safe&apos;s new official URL.
<br />
Import your address book via the CSV export from the <ExportLink>old app</ExportLink>.
</>
),
}

const PsaBanner = (): ReactElement | null => {
const chain = useCurrentChain()
const banner = chain ? BANNERS[chain.chainId] || BANNERS['*'] : undefined
const isEnabled = chain && hasFeature(chain, WARNING_BANNER as FEATURES)
const [closed = false, setClosed] = useLocalStorage<boolean>(`${WARNING_BANNER}_closed`)

// Address books on all chains
const ab = useAppSelector(selectAllAddressBooks)

const showBanner = Boolean(isEnabled && banner && !closed && isEmpty(ab))

const onClose = () => {
setClosed(true)
}

return showBanner ? (
<div className={styles.banner}>
<div className={styles.wrapper}>
<div className={styles.content}>{banner}</div>

<IconButton className={styles.close} onClick={onClose} aria-label="dismiss announcement banner">
<CloseIcon />
</IconButton>
</div>
</div>
) : null
}

export default PsaBanner
6 changes: 4 additions & 2 deletions src/components/settings/owner/OwnerList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { RemoveOwnerDialog } from '../RemoveOwnerDialog'
import { ReplaceOwnerDialog } from '../ReplaceOwnerDialog'
import EnhancedTable from '@/components/common/EnhancedTable'

import tableCss from '@/components/common/EnhancedTable/styles.module.css'

const headCells = [
{ id: 'owner', label: 'Name' },
{ id: 'actions', label: '', sticky: true },
Expand All @@ -32,11 +34,11 @@ export const OwnerList = ({ isGranted }: { isGranted: boolean }) => {
rawValue: '',
sticky: true,
content: (
<>
<div className={tableCss.actions}>
{isGranted && <ReplaceOwnerDialog address={address} />}
<EditOwnerDialog address={address} name={name} chainId={safe.chainId} />
{isGranted && <RemoveOwnerDialog owner={{ address, name }} />}
</>
</div>
),
},
}
Expand Down
38 changes: 36 additions & 2 deletions src/hooks/__tests__/useSafeNotifications.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jest.mock('../../hooks/useSafeInfo')
// mock router
jest.mock('next/router', () => ({
useRouter: jest.fn(() => ({
query: { safe: 'rin:0x123' },
query: { safe: 'eth:0x123' },
})),
}))

Expand Down Expand Up @@ -56,13 +56,43 @@ describe('useSafeNotifications', () => {
link: {
href: {
pathname: '/settings/setup',
query: { safe: 'rin:0x123' },
query: { safe: 'eth:0x123' },
},
title: 'Update Safe',
},
})
})

it('should show a notification for legacy Safes', async () => {
// mock useSafeInfo to return a SafeInfo with an outdated version
;(useSafeInfo as jest.Mock).mockReturnValue({
safe: {
implementation: { value: '0x123' },
implementationVersionState: 'OUTDATED',
version: '1.0.0',
},
safeAddress: '0x123',
})

// render the hook
const { result } = renderHook(() => useSafeNotifications())

// await
await act(async () => Promise.resolve())

// check that the notification was shown
expect(result.current).toBeUndefined()
expect(showNotification).toHaveBeenCalledWith({
variant: 'warning',
message: `Safe version 1.0.0 is not supported by this web app anymore. You can update your Safe via the old web app here.`,
groupKey: 'safe-outdated-version',
link: {
href: 'https://gnosis-safe.io/app/eth:0x123/settings/details?no-redirect=true',
title: 'Update Safe',
},
})
})

it('should not show a notification when the Safe version is up to date', async () => {
;(useSafeInfo as jest.Mock).mockReturnValue({
safe: {
Expand All @@ -89,6 +119,8 @@ describe('useSafeNotifications', () => {
;(useSafeInfo as jest.Mock).mockReturnValue({
safe: {
implementation: { value: '0x123' },
implementationVersionState: 'UP_TO_DATE',
version: '1.3.0',
},
})
jest.spyOn(contracts, 'isValidMasterCopy').mockImplementation((...args: any[]) => Promise.resolve(false))
Expand Down Expand Up @@ -117,6 +149,8 @@ describe('useSafeNotifications', () => {
;(useSafeInfo as jest.Mock).mockReturnValue({
safe: {
implementation: { value: '0x456' },
implementationVersionState: 'UP_TO_DATE',
version: '1.3.0',
},
})
jest.spyOn(contracts, 'isValidMasterCopy').mockImplementation((...args: any[]) => Promise.resolve(true))
Expand Down
21 changes: 12 additions & 9 deletions src/hooks/useSafeNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import useSafeInfo from './useSafeInfo'
import { useAppDispatch } from '@/store'
import { AppRoutes } from '@/config/routes'
import useAsync from './useAsync'
import { isOldestVersion, isValidMasterCopy } from '@/services/contracts/safeContracts'
import { isValidMasterCopy } from '@/services/contracts/safeContracts'
import { useRouter } from 'next/router'
import { isValidSafeVersion } from './coreSDK/safeCoreSDK'

const OLD_URL = 'https://gnosis-safe.io/app'

const CLI_LINK = {
href: 'https://github.com/5afe/safe-cli',
Expand All @@ -31,26 +34,26 @@ const useSafeNotifications = (): void => {
return
}

const isOldSafe = isOldestVersion(version)
const isOldSafe = !isValidSafeVersion(version)

const id = dispatch(
showNotification({
variant: 'warning',
groupKey: 'safe-outdated-version',

message: isOldSafe
? `Safe version ${version} is not supported by this web app anymore. We recommend using the command line interface instead.`
? `Safe version ${version} is not supported by this web app anymore. You can update your Safe via the old web app here.`
: `Your Safe version ${version} is out of date. Please update it.`,

link: isOldSafe
? CLI_LINK
: {
href: {
link: {
href: isOldSafe
? `${OLD_URL}/${query.safe}/settings/details?no-redirect=true`
: {
pathname: AppRoutes.settings.setup,
query: { safe: query.safe },
},
title: 'Update Safe',
},
title: 'Update Safe',
},
}),
)

Expand Down
3 changes: 3 additions & 0 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import useBeamer from '@/hooks/useBeamer'
import ErrorBoundary from '@/components/common/ErrorBoundary'
import createEmotionCache from '@/utils/createEmotionCache'
import MetaTags from '@/components/common/MetaTags'
import PsaBanner from '@/components/common/PsaBanner'

const GATEWAY_URL = IS_PRODUCTION || cgwDebugStorage.get() ? GATEWAY_URL_PRODUCTION : GATEWAY_URL_STAGING

Expand Down Expand Up @@ -86,6 +87,8 @@ const WebCoreApp = ({ Component, pageProps, emotionCache = clientSideEmotionCach

<InitApp />

<PsaBanner />

<PageLayout>
<Component {...pageProps} />
</PageLayout>
Expand Down
2 changes: 1 addition & 1 deletion src/services/contracts/safeContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const getSpecificGnosisSafeContractInstance = (safe: SafeInfo) => {
})
}

export const isOldestVersion = (safeVersion: string): boolean => {
const isOldestVersion = (safeVersion: string): boolean => {
return semverSatisfies(safeVersion, '<=1.0.0')
}

Expand Down
10 changes: 10 additions & 0 deletions src/utils/__tests__/formatters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,15 @@ describe('formatters', () => {
it('should shorten an address with custom length', () => {
expect(formatters.shortenAddress('0x1234567890123456789012345678901234567890', 5)).toEqual('0x12345...67890')
})

it('should return an empty string if passed a falsy value', () => {
expect(formatters.shortenAddress('', 5)).toEqual('')

// @ts-ignore - Invalid type
expect(formatters.shortenAddress(undefined, 5)).toEqual('')

// @ts-ignore - Invalid type
expect(formatters.shortenAddress(null, 5)).toEqual('')
})
})
})
4 changes: 4 additions & 0 deletions src/utils/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export const safeParseUnits = (value: string, decimals: number | string = GWEI):
}

export const shortenAddress = (address: string, length = 4): string => {
if (!address) {
return ''
}

return `${address.slice(0, length + 2)}...${address.slice(-length)}`
}

Expand Down