From 9c56c21d0293dc237f4ae2ff92684ad19e1b9f7b Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 20 Nov 2024 13:40:02 +0800 Subject: [PATCH 1/4] add tests and clean up folder structure --- .../pages/profile/[name]/Profile.tsx | 2 +- .../hooks/pages/profile/useRenew}/useRenew.ts | 0 src/utils/string.test.ts | 28 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) rename { src/hooks/pages/profile => src/hooks/pages/profile/useRenew}/useRenew.ts (100%) create mode 100644 src/utils/string.test.ts diff --git a/src/components/pages/profile/[name]/Profile.tsx b/src/components/pages/profile/[name]/Profile.tsx index ac9940a62..a34786aa3 100644 --- a/src/components/pages/profile/[name]/Profile.tsx +++ b/src/components/pages/profile/[name]/Profile.tsx @@ -1,4 +1,3 @@ -import { useRenew } from '@root/ src/hooks/pages/profile/useRenew' import Head from 'next/head' import { useEffect, useMemo } from 'react' import { Trans, useTranslation } from 'react-i18next' @@ -12,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' diff --git a/ src/hooks/pages/profile/useRenew.ts b/src/hooks/pages/profile/useRenew/useRenew.ts similarity index 100% rename from src/hooks/pages/profile/useRenew.ts rename to src/hooks/pages/profile/useRenew/useRenew.ts diff --git a/src/utils/string.test.ts b/src/utils/string.test.ts new file mode 100644 index 000000000..7c079b315 --- /dev/null +++ b/src/utils/string.test.ts @@ -0,0 +1,28 @@ +import { it, describe, expect } from "vitest"; +import { parseNumericString } from "./string"; + +describe('parseNumericString', () => { + it('should return an integer', () => { + expect(parseNumericString('123')).toBe(123) + }) + + it('should return an integer for a decimal', () => { + expect(parseNumericString('123.123')).toBe(123) + }) + + it('should return null for a string', () => { + expect(parseNumericString('abc')).toBe(null) + }) + + it('should return null for an empty string', () => { + expect(parseNumericString('')).toBe(null) + }) + + it('should return null for a negative number', () => { + expect(parseNumericString('-123')).toBe(null) + }) + + it('should return null for a negative number', () => { + expect(parseNumericString('1a23')).toBe(null) + }) +}) \ No newline at end of file From f74504b388b092af12dadc0e0ac5f03272c94c3e Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 20 Nov 2024 19:41:02 +0800 Subject: [PATCH 2/4] refactor useRew.ts --- src/hooks/pages/profile/useRenew/useRenew.ts | 99 ++++++++++++++------ 1 file changed, 70 insertions(+), 29 deletions(-) diff --git a/src/hooks/pages/profile/useRenew/useRenew.ts b/src/hooks/pages/profile/useRenew/useRenew.ts index 4e19d1449..fe7b34e47 100644 --- a/src/hooks/pages/profile/useRenew/useRenew.ts +++ b/src/hooks/pages/profile/useRenew/useRenew.ts @@ -1,50 +1,91 @@ import { useConnectModal } from '@rainbow-me/rainbowkit' import { useSearchParams } from 'next/navigation' -import { useEffect, useRef } from 'react' +import { useEffect } from 'react' +import { match } from 'ts-pattern' import { useAccount } from 'wagmi' import { useAbilities } from '@app/hooks/abilities/useAbilities' -import { useNameDetails } from '@app/hooks/useNameDetails' +import { useBasicName } from '@app/hooks/useBasicName' +import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' +import { RegistrationStatus } from '@app/utils/registrationStatus' import { parseNumericString } from '@app/utils/string' +type RenewStatus = 'connect-user' | 'display-extend-names' | 'idle' + +export const calculateRenewState = ({ + registrationStatus, + isRegistrationStatusLoading, + renewSeconds, + openConnectModal, + connectModalOpen, + accountStatus, + isAbilitiesLoading, + name, +}: { + registrationStatus?: RegistrationStatus + isRegistrationStatusLoading: boolean + renewSeconds: number | null + connectModalOpen: boolean + openConnectModal: ReturnType['openConnectModal'] + accountStatus: ReturnType['status'] + isAbilitiesLoading: boolean + name?: string +}): RenewStatus => { + const isNameRegisteredOrGracePeriod = + registrationStatus === 'registered' || registrationStatus === 'gracePeriod' + const isRenewActive = + !isRegistrationStatusLoading && + !!name && + isNameRegisteredOrGracePeriod && + !!renewSeconds && + !connectModalOpen + + if (isRenewActive && accountStatus === 'disconnected' && !!openConnectModal) return 'connect-user' + if (isRenewActive && accountStatus === 'connected' && !isAbilitiesLoading) + return 'display-extend-names' + return 'idle' +} + export function useRenew(name: string) { - const { registrationStatus, isLoading } = useNameDetails({ name }) + const router = useRouterWithHistory() + const { registrationStatus, isLoading: isBasicNameLoading } = useBasicName({ name }) const abilities = useAbilities({ name }) const searchParams = useSearchParams() - const { isConnected, isDisconnected } = useAccount() - const { usePreparedDataInput } = useTransactionFlow() + const { status } = useAccount() const { openConnectModal, connectModalOpen } = useConnectModal() + + const { usePreparedDataInput } = useTransactionFlow() const showExtendNamesInput = usePreparedDataInput('ExtendNames') - const { data: { canSelfExtend } = {} } = abilities - const isAvailableName = registrationStatus === 'available' - const renewSeconds = parseNumericString(searchParams.get('renew')) + const { data: { canSelfExtend } = {}, isLoading: isAbilitiesLoading } = abilities - const prevIsConnected = useRef(isConnected) + const renewSeconds = parseNumericString(searchParams.get('renew')) - const isRenewActive = - (!isConnected || !connectModalOpen) && !!renewSeconds && !isLoading && !isAvailableName + const renewState = calculateRenewState({ + registrationStatus, + isRegistrationStatusLoading: isBasicNameLoading, + renewSeconds, + connectModalOpen, + accountStatus: status, + isAbilitiesLoading, + name, + openConnectModal, + }) - // http://localhost:3000/anyname.eth?renew=63072000 useEffect(() => { - if (!isRenewActive) return - - if (isDisconnected && prevIsConnected.current) { - openConnectModal?.() - return - } - - if (isConnected) { - showExtendNamesInput(`extend-names-${name}`, { - names: [name], - isSelf: canSelfExtend, - seconds: renewSeconds, + match(renewState) + .with('connect-user', () => openConnectModal?.()) + .with('display-extend-names', () => { + showExtendNamesInput(`extend-names-${name}`, { + names: [name], + isSelf: canSelfExtend, + seconds: renewSeconds!, + }) + router.replace(`/${name}`) }) - } - - prevIsConnected.current = !isConnected - + .with('idle', () => {}) + .exhaustive() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isRenewActive, isConnected, isDisconnected, name, canSelfExtend]) + }, [renewState]) } From c0ae83941324f56489a6bbb0b13c39fc9b9fa238 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 20 Nov 2024 21:06:48 +0800 Subject: [PATCH 3/4] added additional condition for router ready and function to remove renew parameter from query --- src/hooks/pages/profile/useRenew/useRenew.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/hooks/pages/profile/useRenew/useRenew.ts b/src/hooks/pages/profile/useRenew/useRenew.ts index fe7b34e47..3ce349a8f 100644 --- a/src/hooks/pages/profile/useRenew/useRenew.ts +++ b/src/hooks/pages/profile/useRenew/useRenew.ts @@ -21,6 +21,7 @@ export const calculateRenewState = ({ connectModalOpen, accountStatus, isAbilitiesLoading, + isRouterReady, name, }: { registrationStatus?: RegistrationStatus @@ -30,11 +31,13 @@ export const calculateRenewState = ({ openConnectModal: ReturnType['openConnectModal'] accountStatus: ReturnType['status'] isAbilitiesLoading: boolean + isRouterReady: boolean name?: string }): RenewStatus => { const isNameRegisteredOrGracePeriod = registrationStatus === 'registered' || registrationStatus === 'gracePeriod' const isRenewActive = + isRouterReady && !isRegistrationStatusLoading && !!name && isNameRegisteredOrGracePeriod && @@ -47,6 +50,19 @@ export const calculateRenewState = ({ return 'idle' } +export const removeRenewParam = ({ + query, +}: { + query: ReturnType['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 }) @@ -70,6 +86,7 @@ export function useRenew(name: string) { accountStatus: status, isAbilitiesLoading, name, + isRouterReady: router.isReady, openConnectModal, }) @@ -82,7 +99,8 @@ export function useRenew(name: string) { isSelf: canSelfExtend, seconds: renewSeconds!, }) - router.replace(`/${name}`) + const params = removeRenewParam({ query: router.query }) + router.replace(`/${name}${params}`) }) .with('idle', () => {}) .exhaustive() From 4b32cf594f8124c19be0a3a0ebbcf3531d263665 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Wed, 20 Nov 2024 21:35:06 +0800 Subject: [PATCH 4/4] add flag to track if connect modal has been opened --- src/hooks/pages/profile/useRenew/useRenew.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/hooks/pages/profile/useRenew/useRenew.ts b/src/hooks/pages/profile/useRenew/useRenew.ts index 3ce349a8f..ab15ba377 100644 --- a/src/hooks/pages/profile/useRenew/useRenew.ts +++ b/src/hooks/pages/profile/useRenew/useRenew.ts @@ -1,6 +1,6 @@ import { useConnectModal } from '@rainbow-me/rainbowkit' import { useSearchParams } from 'next/navigation' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { match } from 'ts-pattern' import { useAccount } from 'wagmi' @@ -23,6 +23,7 @@ export const calculateRenewState = ({ isAbilitiesLoading, isRouterReady, name, + openedConnectModal, }: { registrationStatus?: RegistrationStatus isRegistrationStatusLoading: boolean @@ -33,6 +34,7 @@ export const calculateRenewState = ({ isAbilitiesLoading: boolean isRouterReady: boolean name?: string + openedConnectModal: boolean }): RenewStatus => { const isNameRegisteredOrGracePeriod = registrationStatus === 'registered' || registrationStatus === 'gracePeriod' @@ -44,7 +46,13 @@ export const calculateRenewState = ({ !!renewSeconds && !connectModalOpen - if (isRenewActive && accountStatus === 'disconnected' && !!openConnectModal) return 'connect-user' + if ( + isRenewActive && + accountStatus === 'disconnected' && + !!openConnectModal && + !openedConnectModal + ) + return 'connect-user' if (isRenewActive && accountStatus === 'connected' && !isAbilitiesLoading) return 'display-extend-names' return 'idle' @@ -69,7 +77,9 @@ export function useRenew(name: string) { 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') @@ -88,11 +98,15 @@ export function useRenew(name: string) { name, isRouterReady: router.isReady, openConnectModal, + openedConnectModal, }) useEffect(() => { match(renewState) - .with('connect-user', () => openConnectModal?.()) + .with('connect-user', () => { + openConnectModal?.() + setOpenedConnectModal(!!openConnectModal) + }) .with('display-extend-names', () => { showExtendNamesInput(`extend-names-${name}`, { names: [name],