Skip to content

Commit

Permalink
Network notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
LeonmanRolls committed Dec 13, 2024
1 parent 356293c commit adedc98
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 79 deletions.
17 changes: 17 additions & 0 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -419,5 +419,22 @@
"ownerNotManager": "You must be connected as the Manager of this name to set the verification record. You can view and update the Manager under the Ownership tab.",
"wrongAccount": "You must be connected as <strong>{{ nameOrAddress }}</strong> to set the verification record.",
"default": "We could't verify your account. Please return to Dentity and try again."
},
"networkNotifications": {
"Ethereum": {
"title": "Switch to Ethereum?",
"description": "You've selected Ethereum (mainnet) in your wallet.",
"action": "Go to app.ens.domains"
},
"Sepolia": {
"title": "Switch to Sepolia?",
"description": "You've selected Sepolia (testnet) in your wallet.",
"action": "Go to sepolia.app.ens.domains"
},
"Holesky": {
"title": "Switch to Holesky?",
"description": "You've selected Holesky (testnet) in your wallet.",
"action": "Go to holesky.app.ens.domains"
}
}
}
Empty file.
54 changes: 54 additions & 0 deletions src/components/NetworkNotifications.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled, { css } from 'styled-components'
import { useAccount } from 'wagmi'

import { Button, Toast } from '@ensdomains/thorin'

import { getChainsFromUrl, getSupportedChainById } from '@app/constants/chains'

const appLinks = {
ethereum: 'app.ens.domains',
sepolia: 'sepolia.app.ens.domains',
holesky: 'holesky.app.ens.domains',
}

export const NetworkNotifications = () => {
const { t } = useTranslation()
const account = useAccount()
const [open, setOpen] = useState(false)

const connectedChainName = account?.chain?.name
const connectedChainId = account?.chain?.id

console.log('connectedChainName: ', connectedChainName)

useEffect(() => {
if (!connectedChainName) return
if (!getSupportedChainById(connectedChainId)) return

const currentChain = getChainsFromUrl()?.[0]
if (currentChain?.id !== connectedChainId) {
setOpen(true)
}
}, [connectedChainName, connectedChainId])

return (
<Toast
description={t(`networkNotifications.${connectedChainName}.description`)}
open={open}
title={t(`networkNotifications.${connectedChainName}.title`)}
variant="desktop"
onClose={() => setOpen(false)}
>
<Button
size="small"
onClick={() =>
(window.location.href = `https://${appLinks[connectedChainName?.toLocaleLowerCase()]}`)
}
>
{t(`networkNotifications.${connectedChainName}.action`)}
</Button>
</Toast>
)
}
35 changes: 4 additions & 31 deletions src/components/TestnetWarning.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// import { useSearchParams } from 'next/dist/client/components/navigation'
import { useEffect, useState } from 'react'
import styled, { css } from 'styled-components'

import { getChainFromSubdomain, getChainFromUrl } from '@app/utils/utils'
import { getChainsFromUrl } from '@app/constants/chains'

const Container = styled.div(
({ theme }) => css`
Expand All @@ -15,41 +14,15 @@ const Container = styled.div(
`,
)

/*
const getChainFromQueryString = (searchParams: URLSearchParams) => {
// Query param only possible in test/dev
if (
!(
window.location.hostname.endsWith('localhost') ||
window.location.hostname.endsWith('ens.pages.dev')
)
)
return
// If requested chain id is supported return it, otherwise default to mainnet
searchParams.get('chainId')
}
const getChainFromSubdomain = (subdomain: string) => {
// if (subdomain === 'testnet') return chains[0]
// if (subdomain === 'beta') return chains[1]
// return chains[2]
}
const useGetConifguredChain = () => {
const searchParams = useSearchParams()
}
*/

export const TestnetWarning = () => {
const chain = getChainFromUrl()
const chains = getChainsFromUrl()
const [isClient, setIsClient] = useState(false)

useEffect(() => {
setIsClient(true)
}, [])

if (isClient && chain && chain.id !== 1)
return <Container>You are viewing the ENS app on {chain.name} testnet.</Container>
if (isClient && chains && chains[0].id !== 1)
return <Container>You are viewing the ENS app on {chains[0].name} testnet.</Container>
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { Transaction } from '@app/hooks/transactions/transactionStore'
import { useBreakpoint } from '@app/utils/BreakpointProvider'
import { UpdateCallback, useCallbackOnTransaction } from '@app/utils/SyncProvider/SyncProvider'

import { Notifications } from './Notifications'
import { TransactionNotifications } from './TransactionNotifications'

vi.mock('@app/hooks/chain/useChainName')
vi.mock('@app/utils/SyncProvider/SyncProvider')
Expand Down Expand Up @@ -45,15 +45,15 @@ describe('Notifications', () => {
})
mockUseChainName.mockReturnValue('mainnet')
it('should not render a toast if there is no transactions', () => {
render(<Notifications />)
render(<TransactionNotifications />)
expect(screen.queryByTestId('toast-desktop')).not.toBeInTheDocument()
})
it('should render a toast when a pending transaction is confirmed', async () => {
const { rerender } = render(<Notifications />)
const { rerender } = render(<TransactionNotifications />)
expect(screen.queryByTestId('toast-desktop')).not.toBeInTheDocument()

cb(makeRecentTransaction('confirmed')(null, 0))
rerender(<Notifications />)
rerender(<TransactionNotifications />)

await waitFor(() => screen.queryByTestId('toast-desktop'), {
timeout: 500,
Expand All @@ -62,13 +62,13 @@ describe('Notifications', () => {
it('should show a new notification on dismiss if there is one queued', async () => {
const mockData = Array.from({ length: 2 }, makeRecentTransaction('pending'))

const { rerender } = render(<Notifications />)
const { rerender } = render(<TransactionNotifications />)
expect(screen.queryByTestId('toast-desktop')).not.toBeInTheDocument()

cb({ ...mockData[0], status: 'confirmed1' as any })
cb({ ...mockData[1], status: 'confirmed2' as any })

rerender(<Notifications />)
rerender(<TransactionNotifications />)

await waitFor(() => screen.queryByText('transaction.status.confirmed1.notifyTitle'), {
timeout: 500,
Expand All @@ -83,12 +83,12 @@ describe('Notifications', () => {
}).then((el) => expect(el).toBeInTheDocument())
})
it('should show the correct title and description for a notification', async () => {
const { rerender } = render(<Notifications />)
const { rerender } = render(<TransactionNotifications />)

const mockData = makeRecentTransaction('confirmed')(null, 0)
cb(mockData)

rerender(<Notifications />)
rerender(<TransactionNotifications />)

await waitFor(() => screen.queryByTestId('toast-desktop'), {
timeout: 500,
Expand All @@ -98,12 +98,12 @@ describe('Notifications', () => {
expect(screen.getByText('transaction.status.confirmed.notifyMessage')).toBeInTheDocument()
})
it('should not show a notification for a repriced transaction', async () => {
const { rerender } = render(<Notifications />)
const { rerender } = render(<TransactionNotifications />)

const mockData = makeRecentTransaction('repriced')(null, 0)
cb(mockData)

rerender(<Notifications />)
rerender(<TransactionNotifications />)

await waitFor(() => screen.queryByTestId('toast-desktop'), {
timeout: 500,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const ButtonContainer = styled.div(
`,
)

export const Notifications = () => {
export const TransactionNotifications = () => {
const { t } = useTranslation()
const breakpoints = useBreakpoint()

Expand Down
46 changes: 46 additions & 0 deletions src/constants/chains.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { match } from 'ts-pattern'
import { holesky } from 'viem/chains'
import { localhost, mainnet, sepolia } from 'wagmi/chains'

Expand All @@ -6,6 +7,8 @@ import { addEnsContracts } from '@ensdomains/ensjs'
import type { Register } from '@app/local-contracts'
import { makeLocalhostChainWithEns } from '@app/utils/chains/makeLocalhostChainWithEns'

const isLocalProvider = !!process.env.NEXT_PUBLIC_PROVIDER

export const deploymentAddresses = JSON.parse(
process.env.NEXT_PUBLIC_DEPLOYMENT_ADDRESSES || '{}',
) as Register['deploymentAddresses']
Expand Down Expand Up @@ -43,3 +46,46 @@ export type SupportedChain =
| typeof sepoliaWithEns
| typeof holeskyWithEns
| typeof localhostWithEns

export const getChainsFromUrl = () => {
if (typeof window === 'undefined')
return [
...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)),
sepoliaWithEns,
mainnetWithEns,
holeskyWithEns,
]

const { hostname, search } = window.location
const params = new URLSearchParams(search)
const chainParam = params.get('chain')
const segments = hostname.split('.')

if (segments.length === 4 && segments.slice(1).join('.') === 'ens-app-v3.pages.dev') {
if (chainParam === 'sepolia') return sepoliaWithEns
if (chainParam === 'holesky') return holeskyWithEns
}

if (!hostname.includes('app.ens.domains')) return mainnetWithEns
if (segments.length !== 4) return mainnetWithEns

return match(segments[0])
.with('sepolia', () => [
...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)),
sepoliaWithEns,
mainnetWithEns,
holeskyWithEns,
])
.with('holesky', () => [
...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)),
holeskyWithEns,
sepoliaWithEns,
mainnetWithEns,
])
.otherwise(() => [
...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)),
mainnetWithEns,
holeskyWithEns,
sepoliaWithEns,
])
}
15 changes: 11 additions & 4 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import '@splidejs/react-splide/css'

import { NextPage } from 'next'
import type { AppProps } from 'next/app'
import { ReactElement, ReactNode } from 'react'
import { ReactElement, ReactNode, useState } from 'react'
import { I18nextProvider } from 'react-i18next'
import { IntercomProvider } from 'react-use-intercom'
import { createGlobalStyle, keyframes, ThemeProvider } from 'styled-components'

import { ThorinGlobalStyles, lightTheme as thorinLightTheme } from '@ensdomains/thorin'
import {
Button,
ThorinGlobalStyles,
lightTheme as thorinLightTheme,
Toast,
} from '@ensdomains/thorin'

import { Notifications } from '@app/components/Notifications'
import { NetworkNotifications } from '@app/components/NetworkNotifications'
import { TestnetWarning } from '@app/components/TestnetWarning'
import { TransactionNotifications } from '@app/components/TransactionNotifications'
import { TransactionStoreProvider } from '@app/hooks/transactions/TransactionStoreContext'
import { Basic } from '@app/layouts/Basic'
import { TransactionFlowProvider } from '@app/transaction-flow/TransactionFlowProvider'
Expand Down Expand Up @@ -145,7 +151,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<SyncProvider>
<TransactionFlowProvider>
<SyncDroppedTransaction>
<Notifications />
<NetworkNotifications />
<TransactionNotifications />
<TestnetWarning />
<Basic>{getLayout(<Component {...pageProps} />)}</Basic>
</SyncDroppedTransaction>
Expand Down
11 changes: 2 additions & 9 deletions src/utils/query/wagmi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { holesky, localhost, mainnet, sepolia } from 'wagmi/chains'

import { ccipRequest } from '@ensdomains/ensjs/utils'

import { localhostWithEns } from '@app/constants/chains'
import { getChainsFromUrl } from '@app/constants/chains'

import { getChainFromUrl } from '../utils'
import { rainbowKitConnectors } from './wallets'

const isLocalProvider = !!process.env.NEXT_PUBLIC_PROVIDER
Expand Down Expand Up @@ -73,12 +72,6 @@ const localStorageWithInvertMiddleware = (): Storage | undefined => {
}
}

const getChain = () => {
if (isLocalProvider) return localhostWithEns
const chain = getChainFromUrl()
return chain
}

const transports = {
...(isLocalProvider
? ({
Expand All @@ -99,7 +92,7 @@ const wagmiConfig_ = createConfig({
ssr: true,
multiInjectedProviderDiscovery: true,
storage: createStorage({ storage: localStorageWithInvertMiddleware(), key: prefix }),
chains: [getChain()],
chains: [...getChainsFromUrl()],
client: ({ chain }) => {
const chainId = chain.id

Expand Down
24 changes: 0 additions & 24 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import type { TFunction } from 'react-i18next'
import { match } from 'ts-pattern'
import { toBytes, type Address } from 'viem'

import { Eth2ldName } from '@ensdomains/ensjs/dist/types/types'
import { GetPriceReturnType } from '@ensdomains/ensjs/public'
import { DecodedFuses } from '@ensdomains/ensjs/utils'

import { holeskyWithEns, mainnetWithEns, sepoliaWithEns } from '@app/constants/chains'
import { KNOWN_RESOLVER_DATA } from '@app/constants/resolverAddressData'

import { CURRENCY_FLUCTUATION_BUFFER_PERCENTAGE } from './constants'
Expand Down Expand Up @@ -222,25 +220,3 @@ export const hslToHex = (hsl: string) => {
}
return `#${f(0)}${f(8)}${f(4)}`
}

export const getChainFromUrl = () => {
if (typeof window === 'undefined') return mainnetWithEns

const { hostname, search } = window.location
const params = new URLSearchParams(search)
const chainParam = params.get('chain')
const segments = hostname.split('.')

if (segments.length === 4 && segments.slice(1).join('.') === 'ens-app-v3.pages.dev') {
if (chainParam === 'sepolia') return sepoliaWithEns
if (chainParam === 'holesky') return holeskyWithEns
}

if (!hostname.includes('app.ens.domains')) return mainnetWithEns
if (segments.length !== 4) return mainnetWithEns

return match(segments[0])
.with('sepolia', () => sepoliaWithEns)
.with('holesky', () => holeskyWithEns)
.otherwise(() => mainnetWithEns)
}

0 comments on commit adedc98

Please sign in to comment.