Skip to content

Commit 5e7a523

Browse files
authored
Merge pull request #1082 from ensdomains/feature/fet-1981-profile-address-dropdowndialog-on-mobile-not-functioning
Fix profile dropdown navigation on mobile by using onClick handlers
2 parents 8bb3bdb + 90cc320 commit 5e7a523

File tree

15 files changed

+188
-137
lines changed

15 files changed

+188
-137
lines changed

.claude/commands/dev.md

Lines changed: 0 additions & 6 deletions
This file was deleted.

.claude/settings.local.json

Lines changed: 0 additions & 56 deletions
This file was deleted.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,5 @@ certificates
7878

7979
# Claude
8080
.playwright-mcp
81+
.claude
82+
/thoughts

public/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@
349349
"unknown": "An unknown error occurred"
350350
},
351351
"viewEtherscan": "View on Etherscan",
352+
"viewOnBlockExplorer": "View on {{blockExplorer}}",
352353
"viewMore": "View More"
353354
},
354355
"search": {

src/components/pages/profile/ProfileButton.test.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import userEvent from '@testing-library/user-event'
44
import { describe, expect, it, vi } from 'vitest'
55

66
import { useCoinChain } from '@app/hooks/chain/useCoinChain'
7+
import { useChainName } from '@app/hooks/chain/useChainName'
78
import { usePrimaryName } from '@app/hooks/ensjs/public/usePrimaryName'
9+
import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory'
810
import { useBreakpoint } from '@app/utils/BreakpointProvider'
911
import { formatExpiry, shortenAddress } from '@app/utils/utils'
1012

@@ -34,6 +36,8 @@ vi.mock('next/router', () => ({
3436
vi.mock('@app/utils/BreakpointProvider')
3537
vi.mock('@app/hooks/ensjs/public/usePrimaryName')
3638
vi.mock('@app/hooks/chain/useCoinChain')
39+
vi.mock('@app/hooks/chain/useChainName')
40+
vi.mock('@app/hooks/useRouterWithHistory')
3741

3842
const mockUseBreakpoint = mockFunction(useBreakpoint)
3943
mockUseBreakpoint.mockReturnValue({
@@ -85,6 +89,19 @@ mockUseCoinChain.mockImplementation(({ coinName }) => {
8589
}
8690
})
8791

92+
const mockUseChainName = mockFunction(useChainName)
93+
mockUseChainName.mockReturnValue('mainnet')
94+
95+
const mockUseRouterWithHistory = mockFunction(useRouterWithHistory)
96+
mockUseRouterWithHistory.mockReturnValue({
97+
push: vi.fn(),
98+
replace: vi.fn(),
99+
pushWithHistory: vi.fn(),
100+
asPath: '/',
101+
pathname: '/',
102+
query: {},
103+
} as any)
104+
88105
describe('<OwnerProfileButton/>', () => {
89106
it('renders', () => {
90107
render(<OwnerProfileButton iconKey="name.owner" value="name.eth" />)
@@ -182,7 +199,7 @@ describe('<AddressProfileButton/>', () => {
182199
await user.click(addressProfileBtn)
183200

184201
const viewAddressButton = screen.getAllByText((content, element) => {
185-
return element?.tagName?.toLowerCase() === 'a' && content?.toLowerCase() === 'view address'
202+
return element?.tagName?.toLowerCase() === 'button' && content?.toLowerCase() === 'view address'
186203
})
187204

188205
expect(viewAddressButton.length).toBe(1)
@@ -209,7 +226,7 @@ describe('<AddressProfileButton/>', () => {
209226

210227
const viewBlockExplorerBtn = screen.getAllByText((content, element) => {
211228
return (
212-
element?.tagName?.toLowerCase() === 'a' && content?.toLowerCase() === 'view on etherscan'
229+
element?.tagName?.toLowerCase() === 'button' && content?.toLowerCase() === 'view on etherscan'
213230
)
214231
})
215232

src/components/pages/profile/ProfileButton.tsx

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useRouter } from 'next/router'
21
import { useMemo } from 'react'
32
import { useTranslation } from 'react-i18next'
43
import { useCopyToClipboard } from 'react-use'
@@ -24,15 +23,17 @@ import { DynamicVerificationIcon } from '@app/assets/verification/DynamicVerific
2423
import { VerificationBadgeAccountTooltipContent } from '@app/components/@molecules/VerificationBadge/components/VerificationBadgeAccountTooltipContent'
2524
import { VerificationBadgeVerifierTooltipContent } from '@app/components/@molecules/VerificationBadge/components/VerificationBadgeVerifierTooltipContent'
2625
import { VerificationBadge } from '@app/components/@molecules/VerificationBadge/VerificationBadge'
26+
import { useBlockExplorer } from '@app/hooks/chain/useBlockExplorer'
2727
import { useCoinChain } from '@app/hooks/chain/useCoinChain'
2828
import { usePrimaryName } from '@app/hooks/ensjs/public/usePrimaryName'
29+
import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory'
2930
import { getDestinationAsHref } from '@app/routes'
3031
import { VerificationProtocol } from '@app/transaction-flow/input/VerifyProfile/VerifyProfile-flow'
3132
import { useBreakpoint } from '@app/utils/BreakpointProvider'
3233
import { getContentHashLink } from '@app/utils/contenthash'
3334
import { getSocialData } from '@app/utils/getSocialData'
3435
import { createUrlObject } from '@app/utils/urlObject'
35-
import { makeEtherscanLink, shortenAddress } from '@app/utils/utils'
36+
import { shortenAddress } from '@app/utils/utils'
3637
import { getVerifierData } from '@app/utils/verification/getVerifierData'
3738
import { isVerificationProtocol } from '@app/utils/verification/isVerificationProtocol'
3839

@@ -120,13 +121,16 @@ export const AddressProfileButton = ({
120121
iconKey: string
121122
value: string
122123
}) => {
123-
const router = useRouter()
124+
const router = useRouterWithHistory()
124125
const breakpoints = useBreakpoint()
125126
const iconKey = _iconKey.toLowerCase()
126127
const [, copy] = useCopyToClipboard()
127-
const coinChainResults = useCoinChain({ coinName: iconKey })
128+
const { blockExplorer: currentChainBlockExplorer } = useBlockExplorer()
129+
const coinChainResults = useCoinChain({ coinName: iconKey, enabled: iconKey !== 'eth' })
128130
const { data } = coinChainResults
129-
const defaultBlockExplorer = data?.blockExplorers?.default
131+
// For ETH addresses, use the current chain's block explorer; for other coins, use coin-specific explorer
132+
const defaultBlockExplorer =
133+
iconKey === 'eth' ? currentChainBlockExplorer : data?.blockExplorers?.default
130134
const referrer = router.query.referrer as string | undefined
131135

132136
const IconComponent = useMemo(
@@ -139,7 +143,8 @@ export const AddressProfileButton = ({
139143
? {
140144
icon: UpRightArrowIcon,
141145
label: 'View address',
142-
href: getDestinationAsHref(createUrlObject(`/${address}`, { referrer })),
146+
onClick: () =>
147+
router.push(getDestinationAsHref(createUrlObject(`/${address}`, { referrer }))),
143148
}
144149
: undefined,
145150
{
@@ -151,7 +156,12 @@ export const AddressProfileButton = ({
151156
? {
152157
icon: UpRightArrowIcon,
153158
label: `View on ${defaultBlockExplorer?.name}`,
154-
href: `${defaultBlockExplorer?.url}/address/${address}`,
159+
onClick: () =>
160+
window.open(
161+
`${defaultBlockExplorer?.url}/address/${address}`,
162+
'_blank',
163+
'noopener,noreferrer',
164+
),
155165
}
156166
: undefined,
157167
].filter((item) => item !== undefined) as DropdownItem[]
@@ -278,19 +288,18 @@ export const OwnerProfileButton = ({
278288
value: string
279289
timestamp?: number
280290
}) => {
281-
const router = useRouter()
291+
const router = useRouterWithHistory()
292+
const { blockExplorer, buildAddressUrl } = useBlockExplorer()
282293
const { t } = useTranslation('common')
283294
const breakpoints = useBreakpoint()
284295
const referrer = router.query.referrer as string | undefined
285296

286297
const dataType = useMemo(() => {
287-
if (!addressOrNameOrDate)
288-
// eslint-disable-next-line no-nested-ternary
289-
return label === 'name.expiry'
290-
? 'noExpiry'
291-
: label === 'name.parent'
292-
? 'noParent'
293-
: 'notOwned'
298+
if (!addressOrNameOrDate) {
299+
if (label === 'name.expiry') return 'noExpiry'
300+
if (label === 'name.parent') return 'noParent'
301+
return 'notOwned'
302+
}
294303
if (label === 'name.expiry') return 'expiry'
295304
if (isAddress(addressOrNameOrDate)) return 'address'
296305
const isTLD = addressOrNameOrDate.split('.').length === 1
@@ -362,7 +371,7 @@ export const OwnerProfileButton = ({
362371
? {
363372
icon: UpRightArrowIcon,
364373
label: 'View profile',
365-
href: link,
374+
onClick: () => router.push(link),
366375
}
367376
: undefined,
368377
primary.data?.name
@@ -377,21 +386,30 @@ export const OwnerProfileButton = ({
377386
{
378387
icon: UpRightArrowIcon,
379388
label: 'View address',
380-
href: getDestinationAsHref({
381-
pathname: `/${addressOrNameOrDate}`,
382-
query: { referrer },
383-
}),
389+
onClick: () =>
390+
router.push(
391+
getDestinationAsHref(createUrlObject(`/${addressOrNameOrDate}`, { referrer })),
392+
),
384393
},
385394
{
386395
icon: CopyIcon,
387396
label: 'Copy address',
388397
onClick: () => copy(addressOrNameOrDate),
389398
},
390-
{
391-
icon: UpRightArrowIcon,
392-
label: 'View on Etherscan',
393-
href: makeEtherscanLink(addressOrNameOrDate, 'mainnet', 'address'),
394-
},
399+
...(blockExplorer
400+
? [
401+
{
402+
icon: UpRightArrowIcon,
403+
label: `View on ${blockExplorer.name}`,
404+
onClick: () =>
405+
window.open(
406+
buildAddressUrl(addressOrNameOrDate),
407+
'_blank',
408+
'noopener,noreferrer',
409+
),
410+
},
411+
]
412+
: []),
395413
] as DropdownItem[])
396414
: []),
397415
].filter((item) => item !== undefined) as DropdownItem[]

src/components/pages/profile/ProfileDetails.test.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ vi.mock('@app/utils/BreakpointProvider', () => ({
3939
}),
4040
}))
4141

42+
vi.mock('@app/hooks/chain/useChainName', () => ({
43+
useChainName: () => 'mainnet',
44+
}))
45+
46+
vi.mock('@app/hooks/useRouterWithHistory', () => ({
47+
useRouterWithHistory: () => ({
48+
push: vi.fn(),
49+
replace: vi.fn(),
50+
pushWithHistory: vi.fn(),
51+
asPath: '/',
52+
pathname: '/',
53+
query: {},
54+
}),
55+
}))
56+
4257
describe('onwershipInfoCalc', () => {
4358
it('should return no owner if PCC is expired', () => {
4459
const result = ownershipInfoCalc('', true, [], new Date(), new Date())

src/components/pages/profile/[name]/tabs/MoreTab/Miscellaneous/RegistrationDate.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { useTranslation } from 'react-i18next'
22

33
import { OutlinkSVG, Typography } from '@ensdomains/thorin'
44

5-
import { useChainName } from '@app/hooks/chain/useChainName'
5+
import { useBlockExplorer } from '@app/hooks/chain/useBlockExplorer'
66
import type useRegistrationDate from '@app/hooks/useRegistrationData'
7-
import { formatDateTime, formatExpiry, makeEtherscanLink } from '@app/utils/utils'
7+
import { formatDateTime, formatExpiry } from '@app/utils/utils'
88

99
import { DateLayout } from './components/DateLayout'
1010

@@ -14,22 +14,25 @@ export const RegistrationDate = ({
1414
registrationData: ReturnType<typeof useRegistrationDate>['data']
1515
}) => {
1616
const { t } = useTranslation('common')
17-
const chainName = useChainName()
17+
const { buildTransactionUrl } = useBlockExplorer()
1818
if (!registrationData) return null
19+
const transactionUrl = buildTransactionUrl(registrationData.transactionHash)
1920
return (
2021
<DateLayout>
2122
<Typography>{t('name.registered')}</Typography>
2223
<Typography>{formatExpiry(registrationData.registrationDate)}</Typography>
2324
<Typography>{formatDateTime(registrationData.registrationDate)}</Typography>
24-
<a
25-
target="_blank"
26-
href={makeEtherscanLink(registrationData.transactionHash, chainName)}
27-
rel="noreferrer"
28-
data-testid="etherscan-registration-link"
29-
>
30-
{t('action.view')}
31-
<OutlinkSVG />
32-
</a>
25+
{transactionUrl && (
26+
<a
27+
target="_blank"
28+
href={transactionUrl}
29+
rel="noreferrer"
30+
data-testid="etherscan-registration-link"
31+
>
32+
{t('action.view')}
33+
<OutlinkSVG />
34+
</a>
35+
)}
3336
</DateLayout>
3437
)
3538
}

src/components/pages/profile/[name]/tabs/MoreTab/Token/Token.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import { CacheableComponent } from '@app/components/@atoms/CacheableComponent'
88
import { NFTWithPlaceholder } from '@app/components/NFTWithPlaceholder'
99
import { Outlink } from '@app/components/Outlink'
1010
import RecordItem from '@app/components/RecordItem'
11-
import { useChainName } from '@app/hooks/chain/useChainName'
11+
import { useBlockExplorer } from '@app/hooks/chain/useBlockExplorer'
1212
import { useContractAddress } from '@app/hooks/chain/useContractAddress'
13-
import { checkETH2LDFromName, makeEtherscanLink } from '@app/utils/utils'
13+
import { checkETH2LDFromName } from '@app/utils/utils'
1414

1515
import { TabWrapper } from '../../../../TabWrapper'
1616

@@ -106,7 +106,7 @@ const NftBox = styled(NFTWithPlaceholder)(
106106
const Token = ({ name, isWrapped }: Props) => {
107107
const { t } = useTranslation('profile')
108108

109-
const networkName = useChainName()
109+
const { blockExplorer, buildNftUrl } = useBlockExplorer()
110110
const nameWrapperAddress = useContractAddress({ contract: 'ensNameWrapper' })
111111
const registrarAddress = useContractAddress({ contract: 'ensBaseRegistrarImplementation' })
112112

@@ -123,16 +123,12 @@ const Token = ({ name, isWrapped }: Props) => {
123123
<Container>
124124
<HeaderContainer>
125125
<Typography fontVariant="headingFour">{t('tabs.more.token.label')}</Typography>
126-
{hasToken ? (
127-
<Outlink
128-
data-testid="etherscan-nft-link"
129-
href={makeEtherscanLink(`${contractAddress}/${tokenId}`, networkName, 'nft')}
130-
>
131-
{t('etherscan', { ns: 'common' })}
126+
{hasToken && blockExplorer && (
127+
<Outlink data-testid="etherscan-nft-link" href={buildNftUrl(contractAddress, tokenId)!}>
128+
{blockExplorer.name}
132129
</Outlink>
133-
) : (
134-
<Tag colorStyle="greySecondary">{t('tabs.more.token.noToken')}</Tag>
135130
)}
131+
{!hasToken && <Tag colorStyle="greySecondary">{t('tabs.more.token.noToken')}</Tag>}
136132
</HeaderContainer>
137133
{hasToken && (
138134
<ItemsContainer data-testid="token-ids">

0 commit comments

Comments
 (0)