diff --git a/e2e/specs/stateless/extendNames.spec.ts b/e2e/specs/stateless/extendNames.spec.ts index fb2e7f9d8..459ca0336 100644 --- a/e2e/specs/stateless/extendNames.spec.ts +++ b/e2e/specs/stateless/extendNames.spec.ts @@ -92,7 +92,7 @@ test('should be able to extend multiple names on the address page', async ({ await page.waitForLoadState('networkidle') await expect(page.getByText('Your "Extend names" transaction was successful')).toBeVisible({ - timeout: 10000, + timeout: 15000, }) await subgraph.sync() @@ -624,7 +624,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() @@ -670,7 +670,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() }) @@ -681,6 +681,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() }) diff --git a/src/components/ProfileSnippet.tsx b/src/components/ProfileSnippet.tsx index 148dc7b1c..503b7d28a 100644 --- a/src/components/ProfileSnippet.tsx +++ b/src/components/ProfileSnippet.tsx @@ -1,8 +1,6 @@ -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' @@ -10,7 +8,6 @@ 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' @@ -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) @@ -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 ( diff --git a/src/components/pages/profile/[name]/Profile.tsx b/src/components/pages/profile/[name]/Profile.tsx index 68847c87f..e42be984c 100644 --- a/src/components/pages/profile/[name]/Profile.tsx +++ b/src/components/pages/profile/[name]/Profile.tsx @@ -1,5 +1,7 @@ +import { useConnectModal } from '@rainbow-me/rainbowkit' import Head from 'next/head' -import { useEffect, useMemo } from 'react' +import { useSearchParams } from 'next/navigation' +import { useEffect, useMemo, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' import { match } from 'ts-pattern' @@ -16,6 +18,7 @@ import { useProtectedRoute } from '@app/hooks/useProtectedRoute' import { useQueryParameterState } from '@app/hooks/useQueryParameterState' import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory' import { Content, ContentWarning } from '@app/layouts/Content' +import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' import { OG_IMAGE_URL } from '@app/utils/constants' import { shouldRedirect } from '@app/utils/shouldRedirect' import { formatFullExpiry, makeEtherscanLink } from '@app/utils/utils' @@ -104,6 +107,67 @@ export const NameAvailableBanner = ({ ) } +function useRenew(name: string) { + const parseNumericString = (time: string | null) => { + if (!time) return + + if (typeof +time === 'number' && !Number.isNaN(+time)) { + return +time + } + } + + const [opened, setOpened] = useState(false) + const router = useRouterWithHistory() + + const { registrationStatus, isLoading } = useNameDetails({ name }) + const abilities = useAbilities({ name }) + const searchParams = useSearchParams() + const { isConnected, isDisconnected } = useAccount() + const { usePreparedDataInput } = useTransactionFlow() + const { openConnectModal } = useConnectModal() + const showExtendNamesInput = usePreparedDataInput('ExtendNames') + + const { data: { canSelfExtend } = {} } = abilities + const isAvailableName = registrationStatus === 'available' + const renewSeconds = parseNumericString(searchParams.get('renew')) + + useEffect(() => { + if (opened || !renewSeconds || isLoading) return + + if (isAvailableName) { + setOpened(true) + router.push(`/${name}/register`) + return + } + + if (!isAvailableName && isDisconnected) { + setOpened(true) + openConnectModal?.() + return + } + + if (!isAvailableName && isConnected) { + setOpened(true) + showExtendNamesInput(`extend-names-${name}`, { + names: [name], + isSelf: canSelfExtend, + seconds: renewSeconds, + }) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + isAvailableName, + isLoading, + isConnected, + isDisconnected, + name, + canSelfExtend, + renewSeconds, + opened, + ]) +} + const ProfileContent = ({ isSelf, isLoading: parentIsLoading, name }: Props) => { const router = useRouterWithHistory() const { t } = useTranslation('profile') @@ -181,6 +245,7 @@ const ProfileContent = ({ isSelf, isLoading: parentIsLoading, name }: Props) => const abilities = useAbilities({ name: normalisedName }) + useRenew(normalisedName) // hook for redirecting to the correct profile url // profile.decryptedName fetches labels from NW/subgraph // normalisedName fetches labels from localStorage diff --git a/src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx b/src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx index 50343fdf2..ac308fec1 100644 --- a/src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx +++ b/src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx @@ -160,6 +160,7 @@ const NamesList = ({ names }: NamesListProps) => { type Data = { names: string[] + seconds?: number isSelf?: boolean } @@ -169,7 +170,11 @@ export type Props = { const minSeconds = ONE_DAY -const ExtendNames = ({ data: { names, isSelf }, dispatch, onDismiss }: Props) => { +const ExtendNames = ({ + data: { seconds: defaultSeconds = ONE_YEAR, names, isSelf }, + dispatch, + onDismiss, +}: Props) => { const { t } = useTranslation(['transactionFlow', 'common']) const { data: ethPrice } = useEthPrice() @@ -195,7 +200,7 @@ const ExtendNames = ({ data: { names, isSelf }, dispatch, onDismiss }: Props) => const decrementView = () => (viewIdx <= 0 ? onDismiss() : setViewIdx(viewIdx - 1)) const view = flow[viewIdx] - const [seconds, setSeconds] = useState(ONE_YEAR) + const [seconds, setSeconds] = useState(Math.max(defaultSeconds, ONE_YEAR)) const [durationType, setDurationType] = useState<'years' | 'date'>('years') const years = secondsToYears(seconds)