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

FET-1689: Name Renew Deeplink Params #907

Merged
merged 28 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0df5107
FET-1689: Name Renew Deeplink Params
Nov 1, 2024
30d96fa
test patch
Nov 1, 2024
f7eaf50
extend modal patch
Nov 4, 2024
b971961
prettier format
Nov 4, 2024
42ddd5c
increase timeout
Nov 4, 2024
8581536
add useRenew hook placeholder
storywithoutend Nov 19, 2024
f55ac85
Merge branch 'main' of https://github.com/ensdomains/ens-app-v3 into …
Nov 19, 2024
3ec15ac
Merge branch 'FET-1689' of https://github.com/ensdomains/ens-app-v3 i…
Nov 19, 2024
6bdf235
.
Nov 19, 2024
e6df0a9
review updates
Nov 19, 2024
36c7d89
.
Nov 19, 2024
83df106
remove opened flag
Nov 19, 2024
9c56c21
add tests and clean up folder structure
storywithoutend Nov 20, 2024
f74504b
refactor useRew.ts
storywithoutend Nov 20, 2024
c0ae839
added additional condition for router ready and function to remove re…
storywithoutend Nov 20, 2024
4b32cf5
add flag to track if connect modal has been opened
storywithoutend Nov 20, 2024
ba7a65e
Merge pull request #911 from ensdomains/fet-1689-alternate
staslysak Dec 2, 2024
ef50c58
Merge branch 'main' into FET-1689
storywithoutend Dec 3, 2024
6812fd3
Merge branch 'main' of https://github.com/ensdomains/ens-app-v3 into …
Dec 3, 2024
af0baf3
Merge branch 'FET-1689' of https://github.com/ensdomains/ens-app-v3 i…
Dec 3, 2024
e130f04
string test patch
Dec 3, 2024
6086c21
Merge branch 'FET-1689' of https://github.com/ensdomains/ens-app-v3 i…
storywithoutend Dec 3, 2024
d8d2ee1
add a validation function to handle seconds parameter in ExtendNames
storywithoutend Dec 3, 2024
0ff1c8b
remove unneeded files which have been transferred to new funciton
storywithoutend Dec 3, 2024
4f0bdef
fix unit test title
storywithoutend Dec 4, 2024
02a5773
Merge branch 'main' into FET-1689
sugh01 Dec 4, 2024
61b920d
add more unit tests
sugh01 Dec 6, 2024
6a6da55
playwright tests
sugh01 Dec 8, 2024
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
8 changes: 4 additions & 4 deletions e2e/specs/stateless/extendNames.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ test('should be able to extend multiple names (including names in grace preiod)
await transactionModal.autoComplete()

await expect(page.getByText('Your "Extend names" transaction was successful')).toBeVisible({
timeout: 10000,
timeout: 15000,
})
await subgraph.sync()

Expand Down Expand Up @@ -618,7 +618,7 @@ test('should be able to extend a single wrapped name using deep link', async ({
const homePage = makePageObject('HomePage')
await homePage.goto()
await login.connect()
await page.goto(`/${name}?renew`)
await page.goto(`/${name}?renew=123`)

const timestamp = await profilePage.getExpiryTimestamp()

Expand Down Expand Up @@ -664,7 +664,7 @@ test('should not be able to extend a name which is not registered', async ({
const homePage = makePageObject('HomePage')
await homePage.goto()
await login.connect()
await page.goto(`/${name}?renew`)
await page.goto(`/${name}?renew=123`)
await expect(page.getByRole('heading', { name: `Register ${name}` })).toBeVisible()
})

Expand All @@ -675,6 +675,6 @@ test('renew deep link should redirect to registration when not logged in', async
const name = 'this-name-does-not-exist.eth'
const homePage = makePageObject('HomePage')
await homePage.goto()
await page.goto(`/${name}?renew`)
await page.goto(`/${name}?renew=123`)
await expect(page.getByRole('heading', { name: `Register ${name}` })).toBeVisible()
})
26 changes: 1 addition & 25 deletions src/components/ProfileSnippet.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { useSearchParams } from 'next/navigation'
import { useEffect, useMemo } from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled, { css } from 'styled-components'
import { useAccount } from 'wagmi'

import { Button, mq, NametagSVG, Tag, Typography } from '@ensdomains/thorin'

import FastForwardSVG from '@app/assets/FastForward.svg'
import VerifiedPersonSVG from '@app/assets/VerifiedPerson.svg'
import { useAbilities } from '@app/hooks/abilities/useAbilities'
import { useBeautifiedName } from '@app/hooks/useBeautifiedName'
import { useNameDetails } from '@app/hooks/useNameDetails'
import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory'

import { useTransactionFlow } from '../transaction-flow/TransactionFlowProvider'
Expand Down Expand Up @@ -195,8 +192,6 @@ export const ProfileSnippet = ({
const { usePreparedDataInput } = useTransactionFlow()
const showExtendNamesInput = usePreparedDataInput('ExtendNames')
const abilities = useAbilities({ name })
const details = useNameDetails({ name })
const { isConnected } = useAccount()

const beautifiedName = useBeautifiedName(name)

Expand All @@ -206,27 +201,8 @@ export const ProfileSnippet = ({
const location = getTextRecord?.('location')?.value
const recordName = getTextRecord?.('name')?.value

const searchParams = useSearchParams()

const renew = (searchParams.get('renew') ?? null) !== null
const available = details.registrationStatus === 'available'

const { canSelfExtend, canEdit } = abilities.data ?? {}

useEffect(() => {
if (renew && !isConnected) {
return router.push(`/${name}/register`)
}

if (renew && !available) {
showExtendNamesInput(`extend-names-${name}`, {
names: [name],
isSelf: canSelfExtend,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isConnected, available, renew, name, canSelfExtend])

const ActionButton = useMemo(() => {
if (button === 'extend')
return (
Expand Down
3 changes: 3 additions & 0 deletions src/components/pages/profile/[name]/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import BaseLink from '@app/components/@atoms/BaseLink'
import { Outlink } from '@app/components/Outlink'
import { useAbilities } from '@app/hooks/abilities/useAbilities'
import { useChainName } from '@app/hooks/chain/useChainName'
import { useRenew } from '@app/hooks/pages/profile/useRenew/useRenew'
import { useNameDetails } from '@app/hooks/useNameDetails'
import { useProtectedRoute } from '@app/hooks/useProtectedRoute'
import { useQueryParameterState } from '@app/hooks/useQueryParameterState'
Expand Down Expand Up @@ -201,6 +202,8 @@ const ProfileContent = ({ isSelf, isLoading: parentIsLoading, name }: Props) =>
// }
// }, [name, router, transactions])

useRenew(normalisedName)

const infoBanner = useMemo(() => {
if (
registrationStatus !== 'gracePeriod' &&
Expand Down
170 changes: 170 additions & 0 deletions src/hooks/pages/profile/useRenew/useRenew.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { it, expect, describe} from "vitest";

import { calculateRenewState } from "./useRenew";

describe('calculateRenewState', () => {
it('should return connect-user if accountStatus is disconnected', () => {
expect(calculateRenewState({
registrationStatus: 'gracePeriod',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'disconnected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('connect-user')
})

it('should return connect-user if accountStatus is connected', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('display-extend-names')
})

it('should return idle if registration status is available', () => {
expect(calculateRenewState({
registrationStatus: 'available',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if registration status is loading', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: true,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if renewSeconds is null', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: null,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if connectModalOpen is true', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: true,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if abilities is loading', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: true,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if isRouterReady is false', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: false,
name: 'name',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if name is empty', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: '',
openedConnectModal: false,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if openedConnectModal is true', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'connected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: true,
openConnectModal: () => {}
})).toBe('idle')
})

it('should return idle if openConnectModal is undefined and accountStatus is disconnected', () => {
expect(calculateRenewState({
registrationStatus: 'registered',
isRegistrationStatusLoading: false,
renewSeconds: 123,
connectModalOpen: false,
accountStatus: 'disconnected',
isAbilitiesLoading: false,
isRouterReady: true,
name: 'name',
openedConnectModal: false,
openConnectModal: undefined
})).toBe('idle')
})
})
118 changes: 118 additions & 0 deletions src/hooks/pages/profile/useRenew/useRenew.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useConnectModal } from '@rainbow-me/rainbowkit'
import { useSearchParams } from 'next/navigation'
import { useEffect, useState } from 'react'
import { match } from 'ts-pattern'
import { useAccount } from 'wagmi'

import { useAbilities } from '@app/hooks/abilities/useAbilities'
import { useBasicName } from '@app/hooks/useBasicName'
import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory'
import { validateExtendNamesDuration } from '@app/transaction-flow/input/ExtendNames/utils/validateExtendNamesDuration'
import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider'
import { RegistrationStatus } from '@app/utils/registrationStatus'

type RenewStatus = 'connect-user' | 'display-extend-names' | 'idle'

export const calculateRenewState = ({
registrationStatus,
isRegistrationStatusLoading,
renewSeconds,
openConnectModal,
connectModalOpen,
accountStatus,
isAbilitiesLoading,
isRouterReady,
name,
openedConnectModal,
}: {
registrationStatus?: RegistrationStatus
isRegistrationStatusLoading: boolean
renewSeconds: number | null
connectModalOpen: boolean
openConnectModal: ReturnType<typeof useConnectModal>['openConnectModal']
accountStatus: ReturnType<typeof useAccount>['status']
isAbilitiesLoading: boolean
isRouterReady: boolean
name?: string
openedConnectModal: boolean
}): RenewStatus => {
const isNameRegisteredOrGracePeriod =
registrationStatus === 'registered' || registrationStatus === 'gracePeriod'
const isRenewActive =
isRouterReady &&
!isRegistrationStatusLoading &&
!!name &&
isNameRegisteredOrGracePeriod &&
!!renewSeconds &&
!connectModalOpen &&
!openedConnectModal

if (isRenewActive && accountStatus === 'disconnected' && !!openConnectModal) return 'connect-user'
if (isRenewActive && accountStatus === 'connected' && !isAbilitiesLoading)
return 'display-extend-names'
return 'idle'
}

export const removeRenewParam = ({
query,
}: {
query: ReturnType<typeof useRouterWithHistory>['query']
}): string => {
const searchParams = new URLSearchParams(query as any)
// remove the name param in case the page is a redirect from /profile page
searchParams.delete('name')
searchParams.delete('renew')
const newParams = searchParams.toString()
return newParams ? `?${newParams}` : ''
}

export function useRenew(name: string) {
const router = useRouterWithHistory()
const { registrationStatus, isLoading: isBasicNameLoading } = useBasicName({ name })
const abilities = useAbilities({ name })
const searchParams = useSearchParams()
const { status } = useAccount()

const { openConnectModal, connectModalOpen } = useConnectModal()
const [openedConnectModal, setOpenedConnectModal] = useState(connectModalOpen)

const { usePreparedDataInput } = useTransactionFlow()
const showExtendNamesInput = usePreparedDataInput('ExtendNames')

const { data: { canSelfExtend } = {}, isLoading: isAbilitiesLoading } = abilities

const renewSeconds = validateExtendNamesDuration({ duration: searchParams.get('renew') })

const renewState = calculateRenewState({
registrationStatus,
isRegistrationStatusLoading: isBasicNameLoading,
renewSeconds,
connectModalOpen,
accountStatus: status,
isAbilitiesLoading,
name,
isRouterReady: router.isReady,
openConnectModal,
openedConnectModal,
})

useEffect(() => {
match(renewState)
.with('connect-user', () => {
openConnectModal?.()
setOpenedConnectModal(!!openConnectModal)
})
.with('display-extend-names', () => {
showExtendNamesInput(`extend-names-${name}`, {
names: [name],
isSelf: canSelfExtend,
seconds: renewSeconds!,
})
const params = removeRenewParam({ query: router.query })
router.replace(`/${name}${params}`)
})
.with('idle', () => {})
.exhaustive()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [renewState])
}
Loading
Loading