From 7b6b949a1f68af82f2f2e6e808b68fb8f7ec583c Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Mon, 23 Sep 2024 09:50:35 +0700 Subject: [PATCH 01/37] Track DNS import flow events --- .../@molecules/SearchInput/SearchInput.tsx | 28 +++++++---- .../stage/TransactionStageModal.tsx | 30 ++++++++++-- .../import/[name]/steps/SelectImportType.tsx | 7 +++ .../steps/onchain/ImportTransaction.tsx | 11 ++++- src/hooks/useEventTracker.ts | 48 +++++++++++++++++-- 5 files changed, 107 insertions(+), 17 deletions(-) diff --git a/src/components/@molecules/SearchInput/SearchInput.tsx b/src/components/@molecules/SearchInput/SearchInput.tsx index 770dd55d1..534f7eb4c 100644 --- a/src/components/@molecules/SearchInput/SearchInput.tsx +++ b/src/components/@molecules/SearchInput/SearchInput.tsx @@ -13,6 +13,7 @@ import { import { TFunction, useTranslation } from 'react-i18next' import useTransition, { TransitionState } from 'react-transition-state' import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' import { Address, isAddress } from 'viem' import { useAccount, useChainId } from 'wagmi' @@ -343,17 +344,26 @@ const createSearchHandler = const path = getRouteForSearchItem({ address, chainId, queryClient, selectedItem }) - if (path === `/register/${text}`) { - trackEvent({ - eventName: 'search_selected_eth', - customProperties: { name: text }, + match(path) + .with(`/register/${text}`, () => { + trackEvent({ + eventName: 'search_selected_eth', + customProperties: { name: text }, + }) }) - } else if (path === `/dotbox/${text}`) { - trackEvent({ - eventName: 'search_selected_box', - customProperties: { name: text }, + .with(`/dotbox/${text}`, () => { + trackEvent({ + eventName: 'search_selected_box', + customProperties: { name: text }, + }) }) - } + .with(`/import/${text}`, () => { + trackEvent({ + eventName: 'search_selected_dns', + customProperties: { name: text }, + }) + }) + .otherwise(() => {}) setInputVal('') searchInputRef.current?.blur() diff --git a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx index b76d86d68..b1730ad24 100644 --- a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx +++ b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx @@ -411,6 +411,15 @@ export const TransactionStageModal = ({ }, [helper]) const ActionButton = useMemo(() => { + const handleClickActionButton = () => { + dispatch({ name: 'incrementTransaction' }) + + if (actionName === 'approveDnsRegistrar') { + trackEvent({ + eventName: 'register_started_dns', + }) + } + } if (stage === 'complete') { const final = currentStep + 1 === stepCount @@ -428,7 +437,7 @@ export const TransactionStageModal = ({ return ( @@ -480,10 +489,25 @@ export const TransactionStageModal = ({ onClick={() => { sendTransaction(request!) + console.log(actionName) + if (['commitName', 'registerName'].includes(actionName)) { trackEvent({ - eventName: - actionName === 'commitName' ? 'commit_wallet_opened' : 'register_wallet_opened', + eventName: ['commitName'].includes(actionName) + ? 'commit_wallet_opened' + : 'register_wallet_opened', + }) + } + + if (actionName === 'approveDnsRegistrar') { + trackEvent({ + eventName: 'commit_wallet_opened_dns', + }) + } + + if (actionName === 'claimDnsName') { + trackEvent({ + eventName: 'register_wallet_opened_dns', }) } }} diff --git a/src/components/pages/import/[name]/steps/SelectImportType.tsx b/src/components/pages/import/[name]/steps/SelectImportType.tsx index 97ddbee25..55b44e0c7 100644 --- a/src/components/pages/import/[name]/steps/SelectImportType.tsx +++ b/src/components/pages/import/[name]/steps/SelectImportType.tsx @@ -12,6 +12,7 @@ import { useDnsOffchainStatus } from '@app/hooks/dns/useDnsOffchainStatus' import { useDnsSecEnabled } from '@app/hooks/dns/useDnsSecEnabled' import { useDnsOwner } from '@app/hooks/ensjs/dns/useDnsOwner' import { useResolver } from '@app/hooks/ensjs/public/useResolver' +import { useEventTracker } from '@app/hooks/useEventTracker' import { CenteredTypography } from '@app/transaction-flow/input/ProfileEditor/components/CenteredTypography' import { getSupportLink } from '@app/utils/supportLinks' @@ -155,6 +156,7 @@ export const SelectImportType = ({ }) => { const { t } = useTranslation('dnssec', { keyPrefix: 'steps.selectType' }) const { t: tc } = useTranslation('common') + const { trackEvent } = useEventTracker() const { address } = useAccount() const chainId = useChainId() @@ -191,6 +193,11 @@ export const SelectImportType = ({ }) dispatch({ name: 'setSteps', selected, payload: steps }) dispatch({ name: 'increaseStep', selected }) + + trackEvent({ + eventName: 'import_type_selected_dns', + customProperties: { name: selected.name, importType: item.type }, + }) } return ( diff --git a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx index d27f5715a..b9a20c671 100644 --- a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx +++ b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx @@ -15,6 +15,7 @@ import { useDnsImportData } from '@app/hooks/ensjs/dns/useDnsImportData' import { useDnsOwner } from '@app/hooks/ensjs/dns/useDnsOwner' import { usePrimaryName } from '@app/hooks/ensjs/public/usePrimaryName' import { useApprovedForAll } from '@app/hooks/useApprovedForAll' +import { useEventTracker } from '@app/hooks/useEventTracker' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' import { UpdateCallback, useCallbackOnTransaction } from '@app/utils/SyncProvider/SyncProvider' import useUserConfig from '@app/utils/useUserConfig' @@ -105,6 +106,7 @@ export const ImportTransaction = ({ const { t } = useTranslation('dnssec', { keyPrefix: 'steps.transaction' }) const { t: tc } = useTranslation('common') + const { trackEvent } = useEventTracker() const { data: gasPrice } = useGasPrice() const { userConfig, setCurrency } = useUserConfig() const currencyDisplay = userConfig.currency === 'fiat' ? userConfig.fiat : 'eth' @@ -180,7 +182,13 @@ export const ImportTransaction = ({ const startOrResumeFlow = () => { if (!item.started) dispatch({ name: 'setStarted', selected }) - if (resumable) return resumeTransactionFlow(key) + if (resumable) { + trackEvent({ + eventName: 'claim_domain_started_dns', + }) + return resumeTransactionFlow(key) + } + return createTransactionFlow(key, { transactions, resumable: true, @@ -189,7 +197,6 @@ export const ImportTransaction = ({ resumeLink: `/import/${selected.name}`, }) } - const txCallback: UpdateCallback = useCallback( ({ action, status, key: cbKey }) => { if (action !== 'claimDnsName' && action !== 'importDnsName') return diff --git a/src/hooks/useEventTracker.ts b/src/hooks/useEventTracker.ts index 0759ee144..6e73a8784 100644 --- a/src/hooks/useEventTracker.ts +++ b/src/hooks/useEventTracker.ts @@ -8,7 +8,7 @@ import useUserConfig from '@app/utils/useUserConfig' import { useChainName } from './chain/useChainName' type SearchSelectEvent = { - eventName: 'search_selected_eth' | 'search_selected_box' + eventName: 'search_selected_eth' | 'search_selected_box' | 'search_selected_dns' customProperties: { name: string } } @@ -22,6 +22,14 @@ type PaymentEvent = { } } +type DNSImportTypeEvent = { + eventName: 'import_type_selected_dns' + customProperties: { + importType: 'onchain' | 'offchain' | null + name: string + } +} + type DefaultEvent = { eventName: | 'commit_started' @@ -29,10 +37,19 @@ type DefaultEvent = { | 'register_started' | 'register_started_box' | 'register_wallet_opened' + | 'verify_ownership_started_dns' + | 'claim_domain_started_dns' + | 'commit_wallet_opened_dns' + | 'register_started_dns' + | 'register_wallet_opened_dns' customProperties?: never } -export type TrackEventParameters = SearchSelectEvent | PaymentEvent | DefaultEvent +export type TrackEventParameters = + | SearchSelectEvent + | PaymentEvent + | DefaultEvent + | DNSImportTypeEvent export const useEventTracker = () => { const chain = useChainName() @@ -41,7 +58,9 @@ export const useEventTracker = () => { const trackEvent = (props: TrackEventParameters) => { match(props) .with( - { eventName: P.union('search_selected_eth', 'search_selected_box') }, + { + eventName: P.union('search_selected_eth', 'search_selected_box', 'search_selected_dns'), + }, ({ eventName, customProperties }) => { const { name } = customProperties sendTrackEvent(eventName, chain, { name }) @@ -72,6 +91,29 @@ export const useEventTracker = () => { paymentAmount, }) }) + .with( + { + eventName: P.union('import_type_selected_dns'), + }, + ({ eventName, customProperties }) => { + const { importType, name } = customProperties + sendTrackEvent(eventName, chain, { name, importType }) + }, + ) + .with( + { + eventName: P.union( + 'verify_ownership_started_dns', + 'claim_domain_started_dns', + 'commit_wallet_opened_dns', + 'register_started_dns', + 'register_wallet_opened_dns', + ), + }, + ({ eventName }) => { + sendTrackEvent(eventName, chain) + }, + ) .exhaustive() } From 3be33542c013b987b47fd72393f02d6026c457b9 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Tue, 24 Sep 2024 19:22:49 +0700 Subject: [PATCH 02/37] Add e2e test for DNS import flow --- e2e/specs/stateless/_importName.spec.ts | 118 ++++++++++++++++++ .../stage/TransactionStageModal.tsx | 2 - .../steps/onchain/ImportTransaction.tsx | 8 +- .../steps/onchain/VerifyOnchainOwnership.tsx | 14 ++- 4 files changed, 137 insertions(+), 5 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index 55e9d7172..109710bf1 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -1,9 +1,24 @@ import { expect } from '@playwright/test' import { test } from '../../../playwright' +import { trackConsoleEvents } from '../../../playwright/fixtures/consoleListener' + +const chain = 'localhost' +const validEventTypes = [ + 'search_selected_dns', + 'import_type_selected_dns', + 'verify_ownership_started_dns', + 'claim_domain_started_dns', + 'commit_wallet_opened_dns', + 'register_started_dns', + 'register_wallet_opened_dns', +] + +const validEthRegistrationEventRegex = new RegExp(`"type":"(${validEventTypes.join('|')})"`) test('should allow claim (owned by user)', async ({ page, login, accounts, makePageObject }) => { const name = 'swagabc.xyz' + const consoleEvents = trackConsoleEvents(page, validEthRegistrationEventRegex) const homePage = makePageObject('HomePage') const importPage = makePageObject('ImportPage') @@ -14,7 +29,19 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP // should redirect to registration page await homePage.searchInput.fill(name) + await page.locator(`[data-testid="search-result-name"]`, { hasText: name }).waitFor() + await page.locator(`[data-testid="search-result-name"]`, { hasText: 'Not Imported' }).waitFor() await homePage.searchInput.press('Enter') + + await test.step('should fire DNS tracking event: search_selected_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ type: 'search_selected_dns', chain, props: { name, referrer: null } }), + ) + consoleEvents.length = 0 + }) + await expect(importPage.heading).toHaveText(`Claim ${name}`) // no type should be checked yet @@ -31,6 +58,19 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP // should jump straight to transaction step await expect(importPage.heading).toHaveText('Claim your domain') + await test.step('should fire DNS tracking event: import_type_selected_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ + type: 'import_type_selected_dns', + chain, + props: { name, importType: 'onchain', referrer: null }, + }), + ) + consoleEvents.length = 0 + }) + // should show cost value above 0 await expect(importPage.getCost()).resolves.toBeGreaterThan(0) @@ -39,10 +79,29 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await importPage.nextButton.click() + await test.step('should fire DNS tracking event: claim_domain_started_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ type: 'claim_domain_started_dns', chain, props: { referrer: null } }), + ) + consoleEvents.length = 0 + }) + // should be two steps: approve and claim await expect(transactionModal.getStepCount()).resolves.toEqual(2) await transactionModal.confirm() + + await test.step('should fire DNS tracking event: commit_wallet_opened_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ type: 'commit_wallet_opened_dns', chain, props: { referrer: null } }), + ) + consoleEvents.length = 0 + }) + await transactionModal.complete() // should save transaction status on refresh @@ -50,15 +109,46 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await expect(importPage.heading).toHaveText('Claim your domain') await expect(importPage.nextButton).toBeEnabled({ timeout: 15000 }) + await test.step('should fire DNS tracking event: import_type_selected_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ + type: 'import_type_selected_dns', + chain, + props: { name, importType: 'onchain', referrer: null }, + }), + ) + consoleEvents.length = 0 + }) + // should allow finalising await importPage.nextButton.click() + await test.step('should fire DNS tracking event: register_started_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ type: 'register_started_dns', chain, props: { referrer: null } }), + ) + consoleEvents.length = 0 + }) + // transaction modal should still have 2 steps await expect(transactionModal.getStepCount()).resolves.toEqual(2) await expect(page.getByText('Open Wallet')).toBeVisible() await transactionModal.confirm() + await test.step('should fire DNS tracking event: register_wallet_opened_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ type: 'register_wallet_opened_dns', chain, props: { referrer: null } }), + ) + consoleEvents.length = 0 + }) + // should show complete step await expect(page.getByText('Congratulations!')).toBeVisible() await expect(page.getByText(`You are now the owner of ${name}`)).toBeVisible() @@ -71,6 +161,7 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP test('should allow import (not owned by user)', async ({ page, login, makePageObject }) => { const name = 'taytems.xyz' + const consoleEvents = trackConsoleEvents(page, validEthRegistrationEventRegex) const homePage = makePageObject('HomePage') const importPage = makePageObject('ImportPage') @@ -81,7 +172,21 @@ test('should allow import (not owned by user)', async ({ page, login, makePageOb // should redirect to registration page await homePage.searchInput.fill(name) + await page.locator(`[data-testid="search-result-name"]`, { hasText: name }).waitFor() + await page.locator(`[data-testid="search-result-name"]`, { hasText: 'Not Imported' }).waitFor() await homePage.searchInput.press('Enter') + + await page.pause() + + await test.step('should fire DNS tracking event: search_selected_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ type: 'search_selected_dns', chain, props: { name, referrer: null } }), + ) + consoleEvents.length = 0 + }) + await expect(importPage.heading).toHaveText(`Claim ${name}`) // no type should be checked yet @@ -95,6 +200,19 @@ test('should allow import (not owned by user)', async ({ page, login, makePageOb await expect(importPage.nextButton).toBeEnabled({ timeout: 15000 }) await importPage.nextButton.click() + await test.step('should fire DNS tracking event: import_type_selected_dns', async () => { + await expect(consoleEvents).toHaveLength(1) + + await expect(consoleEvents[0]).toContain( + JSON.stringify({ + type: 'import_type_selected_dns', + chain, + props: { name, importType: 'onchain', referrer: null }, + }), + ) + consoleEvents.length = 0 + }) + // should show verify ownership step with error message await expect(importPage.heading).toHaveText('Verify Ownership') await expect( diff --git a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx index b1730ad24..1d2082a18 100644 --- a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx +++ b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx @@ -489,8 +489,6 @@ export const TransactionStageModal = ({ onClick={() => { sendTransaction(request!) - console.log(actionName) - if (['commitName', 'registerName'].includes(actionName)) { trackEvent({ eventName: ['commitName'].includes(actionName) diff --git a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx index b9a20c671..4e8b424ad 100644 --- a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx +++ b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx @@ -181,11 +181,15 @@ export const ImportTransaction = ({ const resumable = getResumable(key) const startOrResumeFlow = () => { - if (!item.started) dispatch({ name: 'setStarted', selected }) - if (resumable) { + if (!item.started) { trackEvent({ eventName: 'claim_domain_started_dns', }) + + dispatch({ name: 'setStarted', selected }) + } + + if (resumable) { return resumeTransactionFlow(key) } diff --git a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx index 17bfba790..50950e021 100644 --- a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx @@ -8,6 +8,7 @@ import { CheckCircleSVG, Helper, Typography } from '@ensdomains/thorin' import RecordItem from '@app/components/RecordItem' import { DNS_TXT_RECORD_HELPER_LINKS } from '@app/constants/dnsLinks' import { useDnsOwner } from '@app/hooks/ensjs/dns/useDnsOwner' +import { useEventTracker } from '@app/hooks/useEventTracker' import { shortenAddress } from '@app/utils/utils' import { @@ -68,6 +69,7 @@ export const VerifyOnchainOwnership = ({ }) => { const { t } = useTranslation('dnssec', { keyPrefix: 'steps.verifyOwnership' }) const { t: tc } = useTranslation('common') + const { trackEvent } = useEventTracker() const { data: dnsOwner, @@ -96,6 +98,16 @@ export const VerifyOnchainOwnership = ({ return tc(`error.${errorKey}`, { ns: 'dnssec' }) }, [tc, error, isLoading]) + const handleDNSButtonClick = () => { + dispatch({ name: 'increaseStep', selected }) + + if (dnsOwnerStatus === 'mismatching') { + trackEvent({ + eventName: 'verify_ownership_started_dns', + }) + } + } + return ( {t('title')} @@ -160,7 +172,7 @@ export const VerifyOnchainOwnership = ({ {isConnected ? ( dispatch({ name: 'increaseStep', selected })} + onClick={handleDNSButtonClick} data-testid="import-next-button" {...(dnsOwnerStatus === 'mismatching' ? { From 1dd642e2e74dba0d3ff906beb529e5765b30b91d Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Wed, 25 Sep 2024 09:23:34 +0700 Subject: [PATCH 03/37] Add tracker DNS follow to event open wallet and verify offchain owner ship --- .../stage/TransactionStageModal.tsx | 2 +- .../import/[name]/steps/VerifyOffchainOwnership.tsx | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx index 1d2082a18..3ba31a94f 100644 --- a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx +++ b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx @@ -497,7 +497,7 @@ export const TransactionStageModal = ({ }) } - if (actionName === 'approveDnsRegistrar') { + if (['approveDnsRegistrar', 'importDnsName'].includes(actionName)) { trackEvent({ eventName: 'commit_wallet_opened_dns', }) diff --git a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx index e72dbc4ad..0a2a7b19e 100644 --- a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx @@ -9,6 +9,7 @@ import RecordItem from '@app/components/RecordItem' import { DNS_TXT_RECORD_HELPER_LINKS } from '@app/constants/dnsLinks' import { EXTENDED_DNS_RESOLVER_MAP } from '@app/constants/resolverAddressData' import { useDnsOffchainStatus } from '@app/hooks/dns/useDnsOffchainStatus' +import { useEventTracker } from '@app/hooks/useEventTracker' import { shortenAddress } from '@app/utils/utils' import { @@ -73,6 +74,7 @@ export const VerifyOffchainOwnership = ({ }) => { const { t } = useTranslation('dnssec', { keyPrefix: 'steps.verifyOwnership' }) const { t: tc } = useTranslation('common') + const { trackEvent } = useEventTracker() const { address, chainId } = selected const isConnected = !!address @@ -97,6 +99,13 @@ export const VerifyOffchainOwnership = ({ return null }, [tc, error]) + const handleCompleteOffChainProcess = () => { + dispatch({ name: 'increaseStep', selected }) + if (dnsOffchainStatus?.address?.status === 'mismatching') { + trackEvent({ eventName: 'register_started_dns' }) + } + } + return ( {t('title')} @@ -165,7 +174,7 @@ export const VerifyOffchainOwnership = ({ {isConnected ? ( dispatch({ name: 'increaseStep', selected })} + onClick={handleCompleteOffChainProcess} {...(dnsOffchainStatus?.address?.status === 'mismatching' ? { colorStyle: 'redPrimary', From 87b55b3c8aefde2511225fb793abba8e12c02082 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Wed, 25 Sep 2024 10:25:33 +0700 Subject: [PATCH 04/37] Fix e2e test failed on import_type_selected_dns event --- e2e/specs/stateless/_importName.spec.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index 109710bf1..b34b9c501 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -109,19 +109,6 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await expect(importPage.heading).toHaveText('Claim your domain') await expect(importPage.nextButton).toBeEnabled({ timeout: 15000 }) - await test.step('should fire DNS tracking event: import_type_selected_dns', async () => { - await expect(consoleEvents).toHaveLength(1) - - await expect(consoleEvents[0]).toContain( - JSON.stringify({ - type: 'import_type_selected_dns', - chain, - props: { name, importType: 'onchain', referrer: null }, - }), - ) - consoleEvents.length = 0 - }) - // should allow finalising await importPage.nextButton.click() @@ -176,8 +163,6 @@ test('should allow import (not owned by user)', async ({ page, login, makePageOb await page.locator(`[data-testid="search-result-name"]`, { hasText: 'Not Imported' }).waitFor() await homePage.searchInput.press('Enter') - await page.pause() - await test.step('should fire DNS tracking event: search_selected_dns', async () => { await expect(consoleEvents).toHaveLength(1) From d3d68ffb201ee2215a3fb8610290bf847e32fa24 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Wed, 25 Sep 2024 15:54:56 +0700 Subject: [PATCH 05/37] Change logic function complete off chain process and function verify owner ship --- .../pages/import/[name]/steps/VerifyOffchainOwnership.tsx | 6 +++--- .../[name]/steps/onchain/VerifyOnchainOwnership.tsx | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx index 0a2a7b19e..7b65f1156 100644 --- a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx @@ -99,9 +99,9 @@ export const VerifyOffchainOwnership = ({ return null }, [tc, error]) - const handleCompleteOffChainProcess = () => { + const handleCompleteOffchainProcess = () => { dispatch({ name: 'increaseStep', selected }) - if (dnsOffchainStatus?.address?.status === 'mismatching') { + if (dnsOffchainStatus?.address?.status !== 'mismatching') { trackEvent({ eventName: 'register_started_dns' }) } } @@ -174,7 +174,7 @@ export const VerifyOffchainOwnership = ({ {isConnected ? ( { dispatch({ name: 'increaseStep', selected }) - if (dnsOwnerStatus === 'mismatching') { - trackEvent({ - eventName: 'verify_ownership_started_dns', - }) - } + trackEvent({ + eventName: 'verify_ownership_started_dns', + }) } return ( From 112a529fb856a5338b9d204c8c280500e4779ec5 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Wed, 25 Sep 2024 16:42:20 +0700 Subject: [PATCH 06/37] Update message test, change name function start dns import and reduce case tracker event in useEventTracker --- e2e/specs/stateless/_importName.spec.ts | 16 ++++++++-------- .../steps/onchain/VerifyOnchainOwnership.tsx | 4 ++-- src/hooks/useEventTracker.ts | 19 +++++-------------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index b34b9c501..2f0832138 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -33,7 +33,7 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await page.locator(`[data-testid="search-result-name"]`, { hasText: 'Not Imported' }).waitFor() await homePage.searchInput.press('Enter') - await test.step('should fire DNS tracking event: search_selected_dns', async () => { + await test.step('should fire DNS import tracking event: search_selected_dns', async () => { await expect(consoleEvents).toHaveLength(1) await expect(consoleEvents[0]).toContain( @@ -58,7 +58,7 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP // should jump straight to transaction step await expect(importPage.heading).toHaveText('Claim your domain') - await test.step('should fire DNS tracking event: import_type_selected_dns', async () => { + await test.step('should fire DNS import tracking event: import_type_selected_dns', async () => { await expect(consoleEvents).toHaveLength(1) await expect(consoleEvents[0]).toContain( @@ -79,7 +79,7 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await importPage.nextButton.click() - await test.step('should fire DNS tracking event: claim_domain_started_dns', async () => { + await test.step('should fire DNS import tracking event: claim_domain_started_dns', async () => { await expect(consoleEvents).toHaveLength(1) await expect(consoleEvents[0]).toContain( @@ -93,7 +93,7 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await transactionModal.confirm() - await test.step('should fire DNS tracking event: commit_wallet_opened_dns', async () => { + await test.step('should fire DNS import tracking event: commit_wallet_opened_dns', async () => { await expect(consoleEvents).toHaveLength(1) await expect(consoleEvents[0]).toContain( @@ -112,7 +112,7 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP // should allow finalising await importPage.nextButton.click() - await test.step('should fire DNS tracking event: register_started_dns', async () => { + await test.step('should fire DNS import tracking event: register_started_dns', async () => { await expect(consoleEvents).toHaveLength(1) await expect(consoleEvents[0]).toContain( @@ -127,7 +127,7 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await expect(page.getByText('Open Wallet')).toBeVisible() await transactionModal.confirm() - await test.step('should fire DNS tracking event: register_wallet_opened_dns', async () => { + await test.step('should fire DNS import tracking event: register_wallet_opened_dns', async () => { await expect(consoleEvents).toHaveLength(1) await expect(consoleEvents[0]).toContain( @@ -163,7 +163,7 @@ test('should allow import (not owned by user)', async ({ page, login, makePageOb await page.locator(`[data-testid="search-result-name"]`, { hasText: 'Not Imported' }).waitFor() await homePage.searchInput.press('Enter') - await test.step('should fire DNS tracking event: search_selected_dns', async () => { + await test.step('should fire DNS import tracking event: search_selected_dns', async () => { await expect(consoleEvents).toHaveLength(1) await expect(consoleEvents[0]).toContain( @@ -185,7 +185,7 @@ test('should allow import (not owned by user)', async ({ page, login, makePageOb await expect(importPage.nextButton).toBeEnabled({ timeout: 15000 }) await importPage.nextButton.click() - await test.step('should fire DNS tracking event: import_type_selected_dns', async () => { + await test.step('should fire DNS import tracking event: import_type_selected_dns', async () => { await expect(consoleEvents).toHaveLength(1) await expect(consoleEvents[0]).toContain( diff --git a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx index a768acb56..149ede542 100644 --- a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx @@ -98,7 +98,7 @@ export const VerifyOnchainOwnership = ({ return tc(`error.${errorKey}`, { ns: 'dnssec' }) }, [tc, error, isLoading]) - const handleDNSButtonClick = () => { + const handleStartDNSImport = () => { dispatch({ name: 'increaseStep', selected }) trackEvent({ @@ -170,7 +170,7 @@ export const VerifyOnchainOwnership = ({ {isConnected ? ( { 'register_started', 'register_started_box', 'register_wallet_opened', + 'verify_ownership_started_dns', + 'claim_domain_started_dns', + 'commit_wallet_opened_dns', + 'register_started_dns', + 'register_wallet_opened_dns', ), }, ({ eventName }) => sendTrackEvent(eventName, chain), @@ -100,20 +105,6 @@ export const useEventTracker = () => { sendTrackEvent(eventName, chain, { name, importType }) }, ) - .with( - { - eventName: P.union( - 'verify_ownership_started_dns', - 'claim_domain_started_dns', - 'commit_wallet_opened_dns', - 'register_started_dns', - 'register_wallet_opened_dns', - ), - }, - ({ eventName }) => { - sendTrackEvent(eventName, chain) - }, - ) .exhaustive() } From 98fc8437a73566f6ddd814a3a88189d5bca006b9 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Wed, 25 Sep 2024 17:04:59 +0700 Subject: [PATCH 07/37] Update function name complete transaction DNS import and update type DNS import select event --- .../TransactionDialogManager/stage/TransactionStageModal.tsx | 4 ++-- src/hooks/useEventTracker.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx index 3ba31a94f..ddaa5b7c4 100644 --- a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx +++ b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx @@ -411,7 +411,7 @@ export const TransactionStageModal = ({ }, [helper]) const ActionButton = useMemo(() => { - const handleClickActionButton = () => { + const handleCompleteTransaction = () => { dispatch({ name: 'incrementTransaction' }) if (actionName === 'approveDnsRegistrar') { @@ -437,7 +437,7 @@ export const TransactionStageModal = ({ return ( diff --git a/src/hooks/useEventTracker.ts b/src/hooks/useEventTracker.ts index dc26c4f7e..859b2fc4f 100644 --- a/src/hooks/useEventTracker.ts +++ b/src/hooks/useEventTracker.ts @@ -22,7 +22,7 @@ type PaymentEvent = { } } -type DNSImportTypeEvent = { +type DNSImportTypeSelectedEvent = { eventName: 'import_type_selected_dns' customProperties: { importType: 'onchain' | 'offchain' | null @@ -49,7 +49,7 @@ export type TrackEventParameters = | SearchSelectEvent | PaymentEvent | DefaultEvent - | DNSImportTypeEvent + | DNSImportTypeSelectedEvent export const useEventTracker = () => { const chain = useChainName() From 91267adc9edb47498db454319c651b534c674383 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Thu, 26 Sep 2024 17:53:51 +0700 Subject: [PATCH 08/37] Update console listener event DNS tracker follow --- e2e/specs/stateless/_importName.spec.ts | 118 ++++++++++++++++-------- 1 file changed, 80 insertions(+), 38 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index 2f0832138..d3f10294c 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -1,7 +1,6 @@ import { expect } from '@playwright/test' import { test } from '../../../playwright' -import { trackConsoleEvents } from '../../../playwright/fixtures/consoleListener' const chain = 'localhost' const validEventTypes = [ @@ -16,9 +15,27 @@ const validEventTypes = [ const validEthRegistrationEventRegex = new RegExp(`"type":"(${validEventTypes.join('|')})"`) -test('should allow claim (owned by user)', async ({ page, login, accounts, makePageObject }) => { +test('should allow claim (owned by user)', async ({ + page, + login, + accounts, + makePageObject, + consoleListener, +}) => { const name = 'swagabc.xyz' - const consoleEvents = trackConsoleEvents(page, validEthRegistrationEventRegex) + await consoleListener.initialize({ + regex: new RegExp( + `Event triggered on local development.*?(${[ + 'search_selected_dns', + 'import_type_selected_dns', + 'verify_ownership_started_dns', + 'claim_domain_started_dns', + 'commit_wallet_opened_dns', + 'register_started_dns', + 'register_wallet_opened_dns', + ].join('|')})`, + ), + }) const homePage = makePageObject('HomePage') const importPage = makePageObject('ImportPage') @@ -34,13 +51,14 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await homePage.searchInput.press('Enter') await test.step('should fire DNS import tracking event: search_selected_dns', async () => { - await expect(consoleEvents).toHaveLength(1) + await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleEvents[0]).toContain( - JSON.stringify({ type: 'search_selected_dns', chain, props: { name, referrer: null } }), + await expect(consoleListener.getMessages().toString()).toMatch( + new RegExp(`search_selected_dns`), ) - consoleEvents.length = 0 + consoleListener.clearMessages() }) + await page.pause() await expect(importPage.heading).toHaveText(`Claim ${name}`) @@ -59,16 +77,18 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await expect(importPage.heading).toHaveText('Claim your domain') await test.step('should fire DNS import tracking event: import_type_selected_dns', async () => { - await expect(consoleEvents).toHaveLength(1) - - await expect(consoleEvents[0]).toContain( - JSON.stringify({ - type: 'import_type_selected_dns', - chain, - props: { name, importType: 'onchain', referrer: null }, - }), + await expect(consoleListener.getMessages()).toHaveLength(1) + + await expect(consoleListener.getMessages().toString()).toMatch( + new RegExp( + JSON.stringify({ + type: 'import_type_selected_dns', + chain, + props: { name, importType: 'onchain', referrer: null }, + }), + ), ) - consoleEvents.length = 0 + consoleListener.clearMessages() }) // should show cost value above 0 @@ -80,12 +100,14 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await importPage.nextButton.click() await test.step('should fire DNS import tracking event: claim_domain_started_dns', async () => { - await expect(consoleEvents).toHaveLength(1) + await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleEvents[0]).toContain( - JSON.stringify({ type: 'claim_domain_started_dns', chain, props: { referrer: null } }), + await expect(consoleListener.getMessages().toString()).toMatch( + new RegExp( + JSON.stringify({ type: 'claim_domain_started_dns', chain, props: { referrer: null } }), + ), ) - consoleEvents.length = 0 + consoleListener.clearMessages() }) // should be two steps: approve and claim @@ -94,12 +116,14 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await transactionModal.confirm() await test.step('should fire DNS import tracking event: commit_wallet_opened_dns', async () => { - await expect(consoleEvents).toHaveLength(1) + await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleEvents[0]).toContain( - JSON.stringify({ type: 'commit_wallet_opened_dns', chain, props: { referrer: null } }), + await expect(consoleListener.getMessages().toString()).toMatch( + new RegExp( + JSON.stringify({ type: 'commit_wallet_opened_dns', chain, props: { referrer: null } }), + ), ) - consoleEvents.length = 0 + consoleListener.clearMessages() }) await transactionModal.complete() @@ -113,12 +137,12 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await importPage.nextButton.click() await test.step('should fire DNS import tracking event: register_started_dns', async () => { - await expect(consoleEvents).toHaveLength(1) + await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleEvents[0]).toContain( + await expect(consoleListener.getMessages().toString()).toMatch( JSON.stringify({ type: 'register_started_dns', chain, props: { referrer: null } }), ) - consoleEvents.length = 0 + consoleListener.clearMessages() }) // transaction modal should still have 2 steps @@ -128,12 +152,12 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP await transactionModal.confirm() await test.step('should fire DNS import tracking event: register_wallet_opened_dns', async () => { - await expect(consoleEvents).toHaveLength(1) + await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleEvents[0]).toContain( + await expect(consoleListener.getMessages().toString()).toMatch( JSON.stringify({ type: 'register_wallet_opened_dns', chain, props: { referrer: null } }), ) - consoleEvents.length = 0 + consoleListener.clearMessages() }) // should show complete step @@ -146,9 +170,27 @@ test('should allow claim (owned by user)', async ({ page, login, accounts, makeP ) }) -test('should allow import (not owned by user)', async ({ page, login, makePageObject }) => { +test('should allow import (not owned by user)', async ({ + page, + login, + makePageObject, + consoleListener, +}) => { const name = 'taytems.xyz' - const consoleEvents = trackConsoleEvents(page, validEthRegistrationEventRegex) + + await consoleListener.initialize({ + regex: new RegExp( + `Event triggered on local development.*?(${[ + 'search_selected_dns', + 'import_type_selected_dns', + 'verify_ownership_started_dns', + 'claim_domain_started_dns', + 'commit_wallet_opened_dns', + 'register_started_dns', + 'register_wallet_opened_dns', + ].join('|')})`, + ), + }) const homePage = makePageObject('HomePage') const importPage = makePageObject('ImportPage') @@ -164,12 +206,12 @@ test('should allow import (not owned by user)', async ({ page, login, makePageOb await homePage.searchInput.press('Enter') await test.step('should fire DNS import tracking event: search_selected_dns', async () => { - await expect(consoleEvents).toHaveLength(1) + await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleEvents[0]).toContain( + await expect(consoleListener.getMessages().toString()).toMatch( JSON.stringify({ type: 'search_selected_dns', chain, props: { name, referrer: null } }), ) - consoleEvents.length = 0 + consoleListener.clearMessages() }) await expect(importPage.heading).toHaveText(`Claim ${name}`) @@ -186,16 +228,16 @@ test('should allow import (not owned by user)', async ({ page, login, makePageOb await importPage.nextButton.click() await test.step('should fire DNS import tracking event: import_type_selected_dns', async () => { - await expect(consoleEvents).toHaveLength(1) + await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleEvents[0]).toContain( + await expect(consoleListener.getMessages().toString()).toMatch( JSON.stringify({ type: 'import_type_selected_dns', chain, props: { name, importType: 'onchain', referrer: null }, }), ) - consoleEvents.length = 0 + consoleListener.clearMessages() }) // should show verify ownership step with error message From e197f27345da15b42352d0273e6a4db66f030789 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Thu, 26 Sep 2024 18:24:24 +0700 Subject: [PATCH 09/37] Remove variable unused and change method match to contain --- e2e/specs/stateless/_importName.spec.ts | 61 ++++++------------------- 1 file changed, 14 insertions(+), 47 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index d3f10294c..5cf18fdb3 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -2,19 +2,6 @@ import { expect } from '@playwright/test' import { test } from '../../../playwright' -const chain = 'localhost' -const validEventTypes = [ - 'search_selected_dns', - 'import_type_selected_dns', - 'verify_ownership_started_dns', - 'claim_domain_started_dns', - 'commit_wallet_opened_dns', - 'register_started_dns', - 'register_wallet_opened_dns', -] - -const validEthRegistrationEventRegex = new RegExp(`"type":"(${validEventTypes.join('|')})"`) - test('should allow claim (owned by user)', async ({ page, login, @@ -58,7 +45,6 @@ test('should allow claim (owned by user)', async ({ ) consoleListener.clearMessages() }) - await page.pause() await expect(importPage.heading).toHaveText(`Claim ${name}`) @@ -80,13 +66,7 @@ test('should allow claim (owned by user)', async ({ await expect(consoleListener.getMessages()).toHaveLength(1) await expect(consoleListener.getMessages().toString()).toMatch( - new RegExp( - JSON.stringify({ - type: 'import_type_selected_dns', - chain, - props: { name, importType: 'onchain', referrer: null }, - }), - ), + new RegExp(`import_type_selected_dns.*?${name}`), ) consoleListener.clearMessages() }) @@ -102,11 +82,7 @@ test('should allow claim (owned by user)', async ({ await test.step('should fire DNS import tracking event: claim_domain_started_dns', async () => { await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleListener.getMessages().toString()).toMatch( - new RegExp( - JSON.stringify({ type: 'claim_domain_started_dns', chain, props: { referrer: null } }), - ), - ) + await expect(consoleListener.getMessages().toString()).toContain('claim_domain_started_dns') consoleListener.clearMessages() }) @@ -118,11 +94,7 @@ test('should allow claim (owned by user)', async ({ await test.step('should fire DNS import tracking event: commit_wallet_opened_dns', async () => { await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleListener.getMessages().toString()).toMatch( - new RegExp( - JSON.stringify({ type: 'commit_wallet_opened_dns', chain, props: { referrer: null } }), - ), - ) + await expect(consoleListener.getMessages().toString()).toContain('commit_wallet_opened_dns') consoleListener.clearMessages() }) @@ -139,9 +111,7 @@ test('should allow claim (owned by user)', async ({ await test.step('should fire DNS import tracking event: register_started_dns', async () => { await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleListener.getMessages().toString()).toMatch( - JSON.stringify({ type: 'register_started_dns', chain, props: { referrer: null } }), - ) + await expect(consoleListener.getMessages().toString()).toContain('register_started_dns') consoleListener.clearMessages() }) @@ -154,9 +124,7 @@ test('should allow claim (owned by user)', async ({ await test.step('should fire DNS import tracking event: register_wallet_opened_dns', async () => { await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleListener.getMessages().toString()).toMatch( - JSON.stringify({ type: 'register_wallet_opened_dns', chain, props: { referrer: null } }), - ) + await expect(consoleListener.getMessages().toString()).toContain('register_wallet_opened_dns') consoleListener.clearMessages() }) @@ -184,10 +152,6 @@ test('should allow import (not owned by user)', async ({ 'search_selected_dns', 'import_type_selected_dns', 'verify_ownership_started_dns', - 'claim_domain_started_dns', - 'commit_wallet_opened_dns', - 'register_started_dns', - 'register_wallet_opened_dns', ].join('|')})`, ), }) @@ -209,7 +173,7 @@ test('should allow import (not owned by user)', async ({ await expect(consoleListener.getMessages()).toHaveLength(1) await expect(consoleListener.getMessages().toString()).toMatch( - JSON.stringify({ type: 'search_selected_dns', chain, props: { name, referrer: null } }), + new RegExp(`search_selected_dns.*?${name}`), ) consoleListener.clearMessages() }) @@ -231,11 +195,7 @@ test('should allow import (not owned by user)', async ({ await expect(consoleListener.getMessages()).toHaveLength(1) await expect(consoleListener.getMessages().toString()).toMatch( - JSON.stringify({ - type: 'import_type_selected_dns', - chain, - props: { name, importType: 'onchain', referrer: null }, - }), + new RegExp(`import_type_selected_dns.*?${name}`), ) consoleListener.clearMessages() }) @@ -252,6 +212,13 @@ test('should allow import (not owned by user)', async ({ await importPage.nextButton.click() + await test.step('should fire DNS import tracking event: verify_ownership_started_dns', async () => { + await expect(consoleListener.getMessages()).toHaveLength(1) + + await expect(consoleListener.getMessages().toString()).toContain('verify_ownership_started_dns') + consoleListener.clearMessages() + }) + // should go to transaction step await expect(importPage.heading).toHaveText('Import this domain') From ee21c1d9d114a5ef6527e6f8c491087567e92c42 Mon Sep 17 00:00:00 2001 From: Nho Huynh Date: Thu, 26 Sep 2024 18:30:41 +0700 Subject: [PATCH 10/37] Use a regular expression literal instead of the 'RegExp' for search_selected_dns event --- e2e/specs/stateless/_importName.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index 5cf18fdb3..c029836f3 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -40,9 +40,7 @@ test('should allow claim (owned by user)', async ({ await test.step('should fire DNS import tracking event: search_selected_dns', async () => { await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleListener.getMessages().toString()).toMatch( - new RegExp(`search_selected_dns`), - ) + await expect(consoleListener.getMessages().toString()).toContain('search_selected_dns') consoleListener.clearMessages() }) From 767b9c59e382cbf3717ee28c87262d22bacf9450 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 29 Oct 2024 17:35:39 +0800 Subject: [PATCH 11/37] refactor dns claim event tracking --- .../@molecules/SearchInput/SearchInput.tsx | 26 +--- .../stage/TransactionStageModal.tsx | 27 ++-- .../import/[name]/steps/SelectImportType.tsx | 7 -- .../[name]/steps/VerifyOffchainOwnership.tsx | 11 +- .../steps/onchain/ImportTransaction.tsx | 4 +- .../steps/onchain/VerifyOnchainOwnership.tsx | 6 - .../import/[name]/useDnsImportReducer.ts | 116 +++++++++++------- src/hooks/useEventTracker.ts | 21 +++- 8 files changed, 105 insertions(+), 113 deletions(-) diff --git a/src/components/@molecules/SearchInput/SearchInput.tsx b/src/components/@molecules/SearchInput/SearchInput.tsx index 534f7eb4c..0d091ee4c 100644 --- a/src/components/@molecules/SearchInput/SearchInput.tsx +++ b/src/components/@molecules/SearchInput/SearchInput.tsx @@ -344,26 +344,12 @@ const createSearchHandler = const path = getRouteForSearchItem({ address, chainId, queryClient, selectedItem }) - match(path) - .with(`/register/${text}`, () => { - trackEvent({ - eventName: 'search_selected_eth', - customProperties: { name: text }, - }) - }) - .with(`/dotbox/${text}`, () => { - trackEvent({ - eventName: 'search_selected_box', - customProperties: { name: text }, - }) - }) - .with(`/import/${text}`, () => { - trackEvent({ - eventName: 'search_selected_dns', - customProperties: { name: text }, - }) - }) - .otherwise(() => {}) + const eventName = match(path) + .with(`/register/${text}`, () => 'search_selected_eth' as const) + .with(`/dotbox/${text}`, () => 'search_selected_box' as const) + .with(`/import/${text}`, () => 'search_selected_dns' as const) + .otherwise(() => undefined) + if (eventName) trackEvent({ eventName, customProperties: { name: text } }) setInputVal('') searchInputRef.current?.blur() diff --git a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx index 8ad38f22a..435f3b759 100644 --- a/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx +++ b/src/components/@molecules/TransactionDialogManager/stage/TransactionStageModal.tsx @@ -551,25 +551,14 @@ export const TransactionStageModal = ({ onClick={() => { sendTransaction(request!) - if (['commitName', 'registerName'].includes(actionName)) { - trackEvent({ - eventName: ['commitName'].includes(actionName) - ? 'commit_wallet_opened' - : 'register_wallet_opened', - }) - } - - if (['approveDnsRegistrar', 'importDnsName'].includes(actionName)) { - trackEvent({ - eventName: 'commit_wallet_opened_dns', - }) - } - - if (actionName === 'claimDnsName') { - trackEvent({ - eventName: 'register_wallet_opened_dns', - }) - } + const eventName = match(actionName) + .with('commitName', () => 'commit_wallet_opened' as const) + .with('registerName', () => 'register_wallet_opened' as const) + .with('approveDnsRegistrar', () => 'dns_approve_registrar_wallet_opened' as const) + .with('importDnsName', () => 'dns_import_wallet_opened' as const) + .with('claimDnsName', () => 'dns_claim_wallet_opened' as const) + .otherwise(() => undefined) + if (eventName) trackEvent({ eventName }) }} data-testid="transaction-modal-confirm-button" > diff --git a/src/components/pages/import/[name]/steps/SelectImportType.tsx b/src/components/pages/import/[name]/steps/SelectImportType.tsx index 55b44e0c7..97ddbee25 100644 --- a/src/components/pages/import/[name]/steps/SelectImportType.tsx +++ b/src/components/pages/import/[name]/steps/SelectImportType.tsx @@ -12,7 +12,6 @@ import { useDnsOffchainStatus } from '@app/hooks/dns/useDnsOffchainStatus' import { useDnsSecEnabled } from '@app/hooks/dns/useDnsSecEnabled' import { useDnsOwner } from '@app/hooks/ensjs/dns/useDnsOwner' import { useResolver } from '@app/hooks/ensjs/public/useResolver' -import { useEventTracker } from '@app/hooks/useEventTracker' import { CenteredTypography } from '@app/transaction-flow/input/ProfileEditor/components/CenteredTypography' import { getSupportLink } from '@app/utils/supportLinks' @@ -156,7 +155,6 @@ export const SelectImportType = ({ }) => { const { t } = useTranslation('dnssec', { keyPrefix: 'steps.selectType' }) const { t: tc } = useTranslation('common') - const { trackEvent } = useEventTracker() const { address } = useAccount() const chainId = useChainId() @@ -193,11 +191,6 @@ export const SelectImportType = ({ }) dispatch({ name: 'setSteps', selected, payload: steps }) dispatch({ name: 'increaseStep', selected }) - - trackEvent({ - eventName: 'import_type_selected_dns', - customProperties: { name: selected.name, importType: item.type }, - }) } return ( diff --git a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx index 7b65f1156..e72dbc4ad 100644 --- a/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/VerifyOffchainOwnership.tsx @@ -9,7 +9,6 @@ import RecordItem from '@app/components/RecordItem' import { DNS_TXT_RECORD_HELPER_LINKS } from '@app/constants/dnsLinks' import { EXTENDED_DNS_RESOLVER_MAP } from '@app/constants/resolverAddressData' import { useDnsOffchainStatus } from '@app/hooks/dns/useDnsOffchainStatus' -import { useEventTracker } from '@app/hooks/useEventTracker' import { shortenAddress } from '@app/utils/utils' import { @@ -74,7 +73,6 @@ export const VerifyOffchainOwnership = ({ }) => { const { t } = useTranslation('dnssec', { keyPrefix: 'steps.verifyOwnership' }) const { t: tc } = useTranslation('common') - const { trackEvent } = useEventTracker() const { address, chainId } = selected const isConnected = !!address @@ -99,13 +97,6 @@ export const VerifyOffchainOwnership = ({ return null }, [tc, error]) - const handleCompleteOffchainProcess = () => { - dispatch({ name: 'increaseStep', selected }) - if (dnsOffchainStatus?.address?.status !== 'mismatching') { - trackEvent({ eventName: 'register_started_dns' }) - } - } - return ( {t('title')} @@ -174,7 +165,7 @@ export const VerifyOffchainOwnership = ({ {isConnected ? ( dispatch({ name: 'increaseStep', selected })} {...(dnsOffchainStatus?.address?.status === 'mismatching' ? { colorStyle: 'redPrimary', diff --git a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx index 4e8b424ad..1e9cc13d2 100644 --- a/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx +++ b/src/components/pages/import/[name]/steps/onchain/ImportTransaction.tsx @@ -183,9 +183,9 @@ export const ImportTransaction = ({ const startOrResumeFlow = () => { if (!item.started) { trackEvent({ - eventName: 'claim_domain_started_dns', + eventName: 'dns_claim_started', + customProperties: { name: selected.name, importType: 'onchain' }, }) - dispatch({ name: 'setStarted', selected }) } diff --git a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx index 149ede542..eea61570d 100644 --- a/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx +++ b/src/components/pages/import/[name]/steps/onchain/VerifyOnchainOwnership.tsx @@ -8,7 +8,6 @@ import { CheckCircleSVG, Helper, Typography } from '@ensdomains/thorin' import RecordItem from '@app/components/RecordItem' import { DNS_TXT_RECORD_HELPER_LINKS } from '@app/constants/dnsLinks' import { useDnsOwner } from '@app/hooks/ensjs/dns/useDnsOwner' -import { useEventTracker } from '@app/hooks/useEventTracker' import { shortenAddress } from '@app/utils/utils' import { @@ -69,7 +68,6 @@ export const VerifyOnchainOwnership = ({ }) => { const { t } = useTranslation('dnssec', { keyPrefix: 'steps.verifyOwnership' }) const { t: tc } = useTranslation('common') - const { trackEvent } = useEventTracker() const { data: dnsOwner, @@ -100,10 +98,6 @@ export const VerifyOnchainOwnership = ({ const handleStartDNSImport = () => { dispatch({ name: 'increaseStep', selected }) - - trackEvent({ - eventName: 'verify_ownership_started_dns', - }) } return ( diff --git a/src/components/pages/import/[name]/useDnsImportReducer.ts b/src/components/pages/import/[name]/useDnsImportReducer.ts index 53406876b..300178e50 100644 --- a/src/components/pages/import/[name]/useDnsImportReducer.ts +++ b/src/components/pages/import/[name]/useDnsImportReducer.ts @@ -1,7 +1,9 @@ import { useEffect } from 'react' +import { match } from 'ts-pattern' import { Address } from 'viem' import { useChainId } from 'wagmi' +import { TrackEventParameters, useEventTracker } from '@app/hooks/useEventTracker' import { useLocalStorageReducer } from '@app/hooks/useLocalStorage' import { isBrowser } from '@app/utils/utils' @@ -105,57 +107,75 @@ export const getSelectedIndex = (state: DnsImportReducerData, selected: Selected } /* eslint-disable no-param-reassign */ -const reducer = (state: DnsImportReducerData, action: DnsImportReducerAction) => { - let selectedItemInx = getSelectedIndex(state, action.selected) +const reducer = + ({ trackEvent }: { trackEvent: (event: TrackEventParameters) => void }) => + (state: DnsImportReducerData, action: DnsImportReducerAction) => { + let selectedItemInx = getSelectedIndex(state, action.selected) - if (!isBrowser) return + if (!isBrowser) return - if (selectedItemInx === -1) { - selectedItemInx = state.items.push(createDefaultData(action.selected)) - 1 - } + if (selectedItemInx === -1) { + selectedItemInx = state.items.push(createDefaultData(action.selected)) - 1 + } - const selectedItem = state.items[selectedItemInx] - - switch (action.name) { - case 'increaseStep': - selectedItem.stepIndex += 1 - break - case 'decreaseStep': - selectedItem.stepIndex -= 1 - break - case 'setSteps': - selectedItem.steps = action.payload - break - case 'setType': - selectedItem.type = action.payload - break - case 'setStarted': - selectedItem.started = true - break - case 'setAddress': - selectedItem.address = action.payload - break - case 'resetItem': - state.items[selectedItemInx] = createDefaultData(action.selected) - break - case 'clearItem': - state.items.splice(selectedItemInx, 1) - break - case 'cleanupNonMatching': - for (let i = 0; i < state.items.length; i += 1) { - const item = state.items[i] - if (item !== selectedItem && state.items[i].started === false) { - state.items.splice(i, 1) - i -= 1 - } + const selectedItem = state.items[selectedItemInx] + + switch (action.name) { + case 'increaseStep': { + const currentStep = selectedItem.steps[selectedItem.stepIndex] + + console.log('selectedItem', JSON.stringify(selectedItem)) + const eventName = match(currentStep) + .with('selectType', () => 'dns_selected_import_type' as const) + .with('enableDnssec', () => 'dns_sec_enabled' as const) + .with('verifyOnchainOwnership', () => 'dns_verified_ownership' as const) + .with('transaction', () => 'dns_claimed' as const) + .with('verifyOffchainOwnership', () => 'dns_claimed' as const) + .otherwise(() => undefined) + if (eventName) + trackEvent({ + eventName, + customProperties: { name: selectedItem.name, importType: selectedItem.type }, + }) + selectedItem.stepIndex += 1 + return state } - break - default: - break - } + case 'decreaseStep': + selectedItem.stepIndex -= 1 + break + case 'setSteps': + selectedItem.steps = action.payload + break + case 'setType': + selectedItem.type = action.payload + break + case 'setStarted': + selectedItem.started = true + break + case 'setAddress': + selectedItem.address = action.payload + break + case 'resetItem': + state.items[selectedItemInx] = createDefaultData(action.selected) + break + case 'clearItem': + state.items.splice(selectedItemInx, 1) + break + case 'cleanupNonMatching': + for (let i = 0; i < state.items.length; i += 1) { + const item = state.items[i] + if (item !== selectedItem && state.items[i].started === false) { + state.items.splice(i, 1) + i -= 1 + } + } + break + default: + break + } - return state -} + return state + } /* eslint-enable no-param-reassign */ export const useDnsImportReducer = ({ @@ -166,10 +186,11 @@ export const useDnsImportReducer = ({ name: string }) => { const chainId = useChainId() + const { trackEvent } = useEventTracker() const selected = { address: address || null, name, chainId } as SelectedItemProperties const [state, dispatch] = useLocalStorageReducer( 'dns-import', - reducer, + reducer({ trackEvent }), { items: [] }, ) @@ -180,6 +201,7 @@ export const useDnsImportReducer = ({ } useEffect(() => { + console.log('cleanupNonMatching') dispatch({ name: 'cleanupNonMatching', selected }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [address, name, chainId]) diff --git a/src/hooks/useEventTracker.ts b/src/hooks/useEventTracker.ts index 0156ae362..2bc960368 100644 --- a/src/hooks/useEventTracker.ts +++ b/src/hooks/useEventTracker.ts @@ -24,7 +24,12 @@ type PaymentEvent = { } type DNSImportTypeSelectedEvent = { - eventName: 'import_type_selected_dns' + eventName: + | 'dns_selected_import_type' + | 'dns_sec_enabled' + | 'dns_verified_ownership' + | 'dns_claim_started' + | 'dns_claimed' customProperties: { importType: 'onchain' | 'offchain' | null name: string @@ -44,6 +49,9 @@ type DefaultEvent = { | 'register_started_dns' | 'register_wallet_opened_dns' | 'register_override_triggered' + | 'dns_approve_registrar_wallet_opened' + | 'dns_import_wallet_opened' + | 'dns_claim_wallet_opened' customProperties?: never } @@ -82,6 +90,9 @@ export const useEventTracker = () => { 'register_started_dns', 'register_wallet_opened_dns', 'register_override_triggered', + 'dns_approve_registrar_wallet_opened', + 'dns_import_wallet_opened', + 'dns_claim_wallet_opened', ), }, ({ eventName }) => sendTrackEvent(eventName, chain), @@ -101,7 +112,13 @@ export const useEventTracker = () => { }) .with( { - eventName: P.union('import_type_selected_dns'), + eventName: P.union( + 'dns_selected_import_type', + 'dns_sec_enabled', + 'dns_verified_ownership', + 'dns_claim_started', + 'dns_claimed', + ), }, ({ eventName, customProperties }) => { const { importType, name } = customProperties From 83af07f04df9c950a84405414ad00f773a4f785f Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 29 Oct 2024 22:21:54 +0800 Subject: [PATCH 12/37] update onchain import e2e test --- e2e/specs/stateless/_importName.spec.ts | 51 +++++++++++++------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index c029836f3..6b9c554cf 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -2,24 +2,30 @@ import { expect } from '@playwright/test' import { test } from '../../../playwright' -test('should allow claim (owned by user)', async ({ +// When testing locally, reducers will be run twice +const reducerEventCount = process.env.CI ? 1 : 2 + +test.only('should allow claim (owned by user)', async ({ page, login, accounts, makePageObject, consoleListener, }) => { + console.log(reducerEventCount) const name = 'swagabc.xyz' await consoleListener.initialize({ regex: new RegExp( `Event triggered on local development.*?(${[ 'search_selected_dns', - 'import_type_selected_dns', - 'verify_ownership_started_dns', - 'claim_domain_started_dns', - 'commit_wallet_opened_dns', - 'register_started_dns', - 'register_wallet_opened_dns', + 'dns_selected_import_type', + 'dns_sec_enabled', + 'dns_verified_ownership', + 'dns_claim_started', + 'dns_claimed', + 'dns_approve_registrar_wallet_opened', + 'dns_import_wallet_opened', + 'dns_claim_wallet_opened', ].join('|')})`, ), }) @@ -57,14 +63,16 @@ test('should allow claim (owned by user)', async ({ await expect(importPage.nextButton).toBeEnabled({ timeout: 15000 }) await importPage.nextButton.click() + await page.pause() + // should jump straight to transaction step await expect(importPage.heading).toHaveText('Claim your domain') - await test.step('should fire DNS import tracking event: import_type_selected_dns', async () => { - await expect(consoleListener.getMessages()).toHaveLength(1) + await test.step('should fire DNS import tracking event: dns_selected_import_type', async () => { + await expect(consoleListener.getMessages()).toHaveLength(reducerEventCount) await expect(consoleListener.getMessages().toString()).toMatch( - new RegExp(`import_type_selected_dns.*?${name}`), + new RegExp(`dns_selected_import_type.*?${name}`), ) consoleListener.clearMessages() }) @@ -77,10 +85,10 @@ test('should allow claim (owned by user)', async ({ await importPage.nextButton.click() - await test.step('should fire DNS import tracking event: claim_domain_started_dns', async () => { + await test.step('should fire DNS import tracking event: dns_claim_started', async () => { await expect(consoleListener.getMessages()).toHaveLength(1) - await expect(consoleListener.getMessages().toString()).toContain('claim_domain_started_dns') + await expect(consoleListener.getMessages().toString()).toContain('dns_claim_started') consoleListener.clearMessages() }) @@ -89,10 +97,11 @@ test('should allow claim (owned by user)', async ({ await transactionModal.confirm() - await test.step('should fire DNS import tracking event: commit_wallet_opened_dns', async () => { + await test.step('should fire DNS import tracking event: dns_approve_registrar_wallet_opened', async () => { await expect(consoleListener.getMessages()).toHaveLength(1) - - await expect(consoleListener.getMessages().toString()).toContain('commit_wallet_opened_dns') + await expect(consoleListener.getMessages().toString()).toContain( + 'dns_approve_registrar_wallet_opened', + ) consoleListener.clearMessages() }) @@ -106,23 +115,15 @@ test('should allow claim (owned by user)', async ({ // should allow finalising await importPage.nextButton.click() - await test.step('should fire DNS import tracking event: register_started_dns', async () => { - await expect(consoleListener.getMessages()).toHaveLength(1) - - await expect(consoleListener.getMessages().toString()).toContain('register_started_dns') - consoleListener.clearMessages() - }) - // transaction modal should still have 2 steps await expect(transactionModal.getStepCount()).resolves.toEqual(2) await expect(page.getByText('Open Wallet')).toBeVisible() await transactionModal.confirm() - await test.step('should fire DNS import tracking event: register_wallet_opened_dns', async () => { + await test.step('should fire DNS import tracking event: dns_claim_wallet_opened', async () => { await expect(consoleListener.getMessages()).toHaveLength(1) - - await expect(consoleListener.getMessages().toString()).toContain('register_wallet_opened_dns') + await expect(consoleListener.getMessages().toString()).toContain('dns_claim_wallet_opened') consoleListener.clearMessages() }) From f5a2bcf46b2f84aa995af6c0a9f4789b653289dd Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 29 Oct 2024 22:36:36 +0800 Subject: [PATCH 13/37] fix onchain not owner e2e test --- e2e/specs/stateless/_importName.spec.ts | 26 ++++++++++++------------- src/hooks/useEventTracker.ts | 2 -- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index 6b9c554cf..f60566b6c 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -2,17 +2,17 @@ import { expect } from '@playwright/test' import { test } from '../../../playwright' -// When testing locally, reducers will be run twice -const reducerEventCount = process.env.CI ? 1 : 2 +// When testing locally, reducers will be run twice because of strict mode +const strictModeEventCount = process.env.CI ? 1 : 2 -test.only('should allow claim (owned by user)', async ({ +test('should allow claim (owned by user)', async ({ page, login, accounts, makePageObject, consoleListener, }) => { - console.log(reducerEventCount) + console.log(strictModeEventCount) const name = 'swagabc.xyz' await consoleListener.initialize({ regex: new RegExp( @@ -69,7 +69,7 @@ test.only('should allow claim (owned by user)', async ({ await expect(importPage.heading).toHaveText('Claim your domain') await test.step('should fire DNS import tracking event: dns_selected_import_type', async () => { - await expect(consoleListener.getMessages()).toHaveLength(reducerEventCount) + await expect(consoleListener.getMessages()).toHaveLength(strictModeEventCount) await expect(consoleListener.getMessages().toString()).toMatch( new RegExp(`dns_selected_import_type.*?${name}`), @@ -149,8 +149,8 @@ test('should allow import (not owned by user)', async ({ regex: new RegExp( `Event triggered on local development.*?(${[ 'search_selected_dns', - 'import_type_selected_dns', - 'verify_ownership_started_dns', + 'dns_selected_import_type', + 'dns_verified_ownership', ].join('|')})`, ), }) @@ -190,11 +190,11 @@ test('should allow import (not owned by user)', async ({ await expect(importPage.nextButton).toBeEnabled({ timeout: 15000 }) await importPage.nextButton.click() - await test.step('should fire DNS import tracking event: import_type_selected_dns', async () => { - await expect(consoleListener.getMessages()).toHaveLength(1) + await test.step('should fire DNS import tracking event: dns_selected_import_type', async () => { + await expect(consoleListener.getMessages()).toHaveLength(strictModeEventCount) await expect(consoleListener.getMessages().toString()).toMatch( - new RegExp(`import_type_selected_dns.*?${name}`), + new RegExp(`dns_selected_import_type.*?${name}`), ) consoleListener.clearMessages() }) @@ -211,10 +211,10 @@ test('should allow import (not owned by user)', async ({ await importPage.nextButton.click() - await test.step('should fire DNS import tracking event: verify_ownership_started_dns', async () => { - await expect(consoleListener.getMessages()).toHaveLength(1) + await test.step('should fire DNS import tracking event: dns_verified_ownership', async () => { + await expect(consoleListener.getMessages()).toHaveLength(strictModeEventCount) - await expect(consoleListener.getMessages().toString()).toContain('verify_ownership_started_dns') + await expect(consoleListener.getMessages().toString()).toContain('dns_verified_ownership') consoleListener.clearMessages() }) diff --git a/src/hooks/useEventTracker.ts b/src/hooks/useEventTracker.ts index 2bc960368..b7257638d 100644 --- a/src/hooks/useEventTracker.ts +++ b/src/hooks/useEventTracker.ts @@ -43,7 +43,6 @@ type DefaultEvent = { | 'register_started' | 'register_started_box' | 'register_wallet_opened' - | 'verify_ownership_started_dns' | 'claim_domain_started_dns' | 'commit_wallet_opened_dns' | 'register_started_dns' @@ -84,7 +83,6 @@ export const useEventTracker = () => { 'register_started', 'register_started_box', 'register_wallet_opened', - 'verify_ownership_started_dns', 'claim_domain_started_dns', 'commit_wallet_opened_dns', 'register_started_dns', From 96746a204b8f54a57373156e116436effd11651c Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 29 Oct 2024 23:22:50 +0800 Subject: [PATCH 14/37] clean up console logs and page pauses --- e2e/specs/stateless/_importName.spec.ts | 2 -- e2e/specs/stateless/advancedEditor.spec.ts | 5 ++- e2e/specs/stateless/deleteSubname.spec.ts | 1 - e2e/specs/stateless/extendNames.spec.ts | 5 --- e2e/specs/stateless/myNames.spec.ts | 3 -- e2e/specs/stateless/ownership.2LD.spec.ts | 8 ++--- e2e/specs/stateless/ownership.4LD.spec.ts | 3 -- e2e/specs/stateless/profileEditor.spec.ts | 32 +++---------------- e2e/specs/stateless/setPrimary.spec.ts | 8 ++--- e2e/specs/stateless/verifications.spec.ts | 21 ------------ e2e/specs/stateless/wrapName.spec.ts | 3 -- .../import/[name]/useDnsImportReducer.ts | 2 -- .../input/ProfileReclaim-flow.tsx | 1 - 13 files changed, 11 insertions(+), 83 deletions(-) diff --git a/e2e/specs/stateless/_importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts index f60566b6c..1c9c9de6f 100644 --- a/e2e/specs/stateless/_importName.spec.ts +++ b/e2e/specs/stateless/_importName.spec.ts @@ -63,8 +63,6 @@ test('should allow claim (owned by user)', async ({ await expect(importPage.nextButton).toBeEnabled({ timeout: 15000 }) await importPage.nextButton.click() - await page.pause() - // should jump straight to transaction step await expect(importPage.heading).toHaveText('Claim your domain') diff --git a/e2e/specs/stateless/advancedEditor.spec.ts b/e2e/specs/stateless/advancedEditor.spec.ts index 1f4e770d1..ec83ed3a3 100644 --- a/e2e/specs/stateless/advancedEditor.spec.ts +++ b/e2e/specs/stateless/advancedEditor.spec.ts @@ -152,7 +152,6 @@ test('should maintain state and when returning from transaction modal when addin await recordsPage.goto(name) await login.connect() - await page.pause() // Validate records await expect(recordsPage.getRecordValue('text', 'name')).toHaveText('Bob') @@ -257,7 +256,7 @@ test('should maintain state and when returning from transaction modal when addin await transactionModal.autoComplete() // Validate change in records - await page.pause() + await expect(recordsPage.getRecordValue('text', 'name')).toHaveText('Nick') await expect(recordsPage.getRecordValue('text', 'another text')).toHaveText('another record') await expect(recordsPage.getRecordValue('address', 'bnb')).toHaveText( @@ -266,7 +265,7 @@ test('should maintain state and when returning from transaction modal when addin await expect(recordsPage.getRecordValue('address', 'eth')).toHaveText( '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', ) - await page.pause() + await expect(recordsPage.getRecordValue('contentHash')).toHaveText( 'bzz://e40101fa011b20d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162'.toLowerCase(), ) diff --git a/e2e/specs/stateless/deleteSubname.spec.ts b/e2e/specs/stateless/deleteSubname.spec.ts index 094c294ed..25b4b61a8 100644 --- a/e2e/specs/stateless/deleteSubname.spec.ts +++ b/e2e/specs/stateless/deleteSubname.spec.ts @@ -26,7 +26,6 @@ test.describe('unwrapped', () => { await profilePage.goto(subname) await login.connect() - await page.pause() // Manager button should exist await expect(page.getByTestId('owner-profile-button-name.manager')).toBeVisible() diff --git a/e2e/specs/stateless/extendNames.spec.ts b/e2e/specs/stateless/extendNames.spec.ts index e0dbea69b..67515f294 100644 --- a/e2e/specs/stateless/extendNames.spec.ts +++ b/e2e/specs/stateless/extendNames.spec.ts @@ -196,7 +196,6 @@ test('should be able to extend a single unwrapped name in grace period from prof const timestamp = await profilePage.getExpiryTimestamp() - await page.pause() await profilePage.getExtendButton.click() const extendNamesModal = makePageObject('ExtendNamesModal') @@ -343,7 +342,6 @@ test('should be able to extend a name by a month', async ({ await profilePage.goto(name) await login.connect() - await page.pause() const timestamp = await profilePage.getExpiryTimestamp() await profilePage.getExtendButton.click() @@ -408,7 +406,6 @@ test('should be able to extend a name by a day', async ({ await profilePage.goto(name) await login.connect() - await page.pause() const timestamp = await profilePage.getExpiryTimestamp() await profilePage.getExtendButton.click() @@ -475,7 +472,6 @@ test('should be able to extend a name in grace period by a month', async ({ const timestamp = await profilePage.getExpiryTimestamp() - await page.pause() await profilePage.getExtendButton.click() const extendNamesModal = makePageObject('ExtendNamesModal') @@ -552,7 +548,6 @@ test('should be able to extend a name in grace period by 1 day', async ({ const timestamp = await profilePage.getExpiryTimestamp() - await page.pause() await profilePage.getExtendButton.click() const extendNamesModal = makePageObject('ExtendNamesModal') diff --git a/e2e/specs/stateless/myNames.spec.ts b/e2e/specs/stateless/myNames.spec.ts index 0c3ae97c5..506770b62 100644 --- a/e2e/specs/stateless/myNames.spec.ts +++ b/e2e/specs/stateless/myNames.spec.ts @@ -18,7 +18,6 @@ test('myNames', async ({ page, login, makeName }) => { await page.goto('/') await login.connect('user2') - await page.pause() await page.goto('/my/names') @@ -32,6 +31,4 @@ test('myNames', async ({ page, login, makeName }) => { ) expect(timestamps.every((timestamp) => timestamp === timestamps[0])).toBe(true) - - await page.pause() }) diff --git a/e2e/specs/stateless/ownership.2LD.spec.ts b/e2e/specs/stateless/ownership.2LD.spec.ts index f4aa055c4..8fa590a0d 100644 --- a/e2e/specs/stateless/ownership.2LD.spec.ts +++ b/e2e/specs/stateless/ownership.2LD.spec.ts @@ -48,7 +48,6 @@ test.describe('Unwrapped 2LD - Owner and Manager', () => { await transactionModal.autoComplete() - await page.pause() await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('owner', { timeout: 15000, }) @@ -368,7 +367,6 @@ test.describe('Unwrapped 2LD - Manager Only', () => { await ownershipPage.goto(name) await login.connect() - await page.pause() expect(await ownershipPage.getExpiryTimestamp()).toBeGreaterThan(0) await expect(ownershipPage.expiryPanelRegistrationDate).toHaveCount(1) await expect(ownershipPage.expiryPanelGracePeriod).toHaveCount(1) @@ -538,7 +536,6 @@ test.describe('Grace Period Unwrapped 2LD', () => { await ownershipPage.goto(name) await login.connect() - await page.pause() await expect(ownershipPage.sendNameButton).toHaveCount(0) }) @@ -580,7 +577,7 @@ test.describe('Grace Period Unwrapped 2LD', () => { await ownershipPage.goto(name) await login.connect() - await page.pause() + await ownershipPage.editRolesButton.click() await expect(editRolesModal.roleCardChangeButton('owner')).toHaveCount(0) @@ -650,7 +647,6 @@ test.describe('Grace Period Wrapped 2LD', () => { await ownershipPage.goto(name) await login.connect() - await page.pause() await expect(ownershipPage.sendNameButton).toHaveCount(0) }) @@ -691,7 +687,7 @@ test.describe('Grace Period Wrapped 2LD', () => { await ownershipPage.goto(name) await login.connect() - await page.pause() + await ownershipPage.editRolesButton.click() await expect(editRolesModal.roleCardChangeButton('owner')).toHaveCount(0) diff --git a/e2e/specs/stateless/ownership.4LD.spec.ts b/e2e/specs/stateless/ownership.4LD.spec.ts index 19ffef7c6..58c7ed289 100644 --- a/e2e/specs/stateless/ownership.4LD.spec.ts +++ b/e2e/specs/stateless/ownership.4LD.spec.ts @@ -424,7 +424,6 @@ test.describe('Unwrapped 4LD, Unwrapped 3LD,2LD - Manager Only', () => { await ownershipPage.goto(subname) await login.connect() - await page.pause() await expect(ownershipPage.expiryPanelExpiry).toHaveCount(0) await expect(ownershipPage.expiryPanelGracePeriod).toHaveCount(0) @@ -683,7 +682,6 @@ test.describe('Unwrapped 4LD - Wrapped 3LD,2LD - Parent Owner only', () => { await ownershipPage.goto(subname) await login.connect() - await page.pause() await page.waitForTimeout(2000) await expect(ownershipPage.sendNameButton).toHaveCount(0) await expect(ownershipPage.editRolesButton).toHaveCount(0) @@ -776,7 +774,6 @@ test.describe('Unwrapped 4LD, Wrapped 3LD,2LD - Manager Only', () => { await ownershipPage.goto(subname) await login.connect() - await page.pause() await ownershipPage.sendNameButton.click() await sendNameModal.searchInput.fill(accounts.getAddress('user3')) await sendNameModal.searchResult(accounts.getAddress('user3')).click() diff --git a/e2e/specs/stateless/profileEditor.spec.ts b/e2e/specs/stateless/profileEditor.spec.ts index 77839e758..f0733b6ca 100644 --- a/e2e/specs/stateless/profileEditor.spec.ts +++ b/e2e/specs/stateless/profileEditor.spec.ts @@ -96,7 +96,6 @@ test.describe('profile', () => { await profilePage.goto(name) await login.connect() - await page.pause() await expect(profilePage.record('text', 'description')).toHaveText('Hello2') await expect(profilePage.record('text', 'url')).toHaveText('twitter.com') await expect(profilePage.record('address', 'btc')).toHaveText('bc1qj...pwa6n') @@ -126,7 +125,6 @@ test.describe('profile', () => { await expect(page).toHaveURL(`/${name}`) - await page.pause() await expect(profilePage.record('text', 'description')).toHaveText('Hello2') await expect(profilePage.record('text', 'url')).toHaveText('twitter.com') await expect(profilePage.record('address', 'btc')).toHaveText('bc1qj...pwa6n') @@ -215,7 +213,6 @@ test.describe('migrations', () => { await morePage.goto(name) await login.connect('user2') - await page.pause() await expect(morePage.resolver.getByText(ownedResolverAddress)).toBeVisible() @@ -250,7 +247,6 @@ test.describe('migrations', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver.getByText(invalidResolverAddress)).toBeVisible() @@ -283,7 +279,6 @@ test.describe('migrations', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver).toHaveText(oldResolver) @@ -329,7 +324,6 @@ test.describe('migrations', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver.getByText(oldResolver)).toBeVisible() @@ -350,7 +344,7 @@ test.describe('migrations', () => { await expect(morePage.resolver.getByText(newResolver)).toBeVisible() await recordsPage.goto(name) - await page.pause() + await expect(recordsPage.getRecordValue('text', 'email')).toHaveText('fakeemail@fake.com') await expect(recordsPage.getRecordValue('text', 'description')).toHaveText('Hello2') await expect(recordsPage.getRecordValue('text', 'url')).toHaveText('https://twitter.com') @@ -399,7 +393,6 @@ test.describe('migrations', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver.getByText(oldResolver)).toBeVisible() @@ -424,7 +417,7 @@ test.describe('migrations', () => { await expect(morePage.resolver.getByText(newResolver)).toBeVisible() await recordsPage.goto(name) - await page.pause() + await expect(page.getByTestId('text-amount')).toHaveText('0 Records') await expect(page.getByTestId('address-amount')).toHaveText('0 Records') await expect(page.getByText('No Content Hash')).toBeVisible() @@ -466,7 +459,6 @@ test.describe('migrations', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver.getByText(oldResolver)).toBeVisible() @@ -484,7 +476,7 @@ test.describe('migrations', () => { await expect(morePage.resolver.getByText(newResolver)).toBeVisible() await recordsPage.goto(name) - await page.pause() + await expect(recordsPage.getRecordValue('text', 'email')).toHaveText('fakeemail@fake.com') await expect(recordsPage.getRecordValue('text', 'description')).toHaveText('New profile') await expect(recordsPage.getRecordValue('text', 'url')).toHaveText('https://twitter.com') @@ -538,7 +530,6 @@ test.describe('migrations', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver.getByText(oldResolver)).toBeVisible() @@ -547,7 +538,6 @@ test.describe('migrations', () => { await expect(page.getByText('Resolver out of sync')).toBeVisible() await profilePage.profileEditor.getByTestId('warning-overlay-next-button').click() - await page.pause() await page.getByTestId('migrate-profile-selector-current').check() await expect(page.getByTestId('migrate-profile-selector-current')).toBeChecked() await profilePage.profileEditor.getByTestId('warning-overlay-next-button').click() @@ -561,7 +551,7 @@ test.describe('migrations', () => { await expect(morePage.resolver.getByText(newResolver)).toBeVisible() await recordsPage.goto(name) - await page.pause() + await expect(page.getByTestId('text-amount')).toHaveText('3 Records') await expect(recordsPage.getRecordValue('text', 'email')).toHaveText('fakeemail@fake.com') await expect(recordsPage.getRecordValue('text', 'description')).toHaveText('Hello2') @@ -615,7 +605,6 @@ test.describe('migrations', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver.getByText(oldResolver)).toBeVisible() @@ -624,13 +613,10 @@ test.describe('migrations', () => { await expect(page.getByText('Resolver out of sync')).toBeVisible() await profilePage.profileEditor.getByTestId('warning-overlay-next-button').click() - await page.pause() await page.getByTestId('migrate-profile-selector-reset').check() await expect(page.getByTestId('migrate-profile-selector-reset')).toBeChecked() await profilePage.profileEditor.getByTestId('warning-overlay-next-button').click() - await page.pause() - await expect(page.getByText('Reset profile')).toBeVisible() await profilePage.profileEditor.getByTestId('warning-overlay-next-button').click() @@ -640,7 +626,7 @@ test.describe('migrations', () => { await expect(morePage.resolver.getByText(newResolver)).toBeVisible() await recordsPage.goto(name) - await page.pause() + await expect(page.getByTestId('text-amount')).toHaveText('0 Records') await expect(page.getByTestId('address-amount')).toHaveText('0 Records') await expect(page.getByText('No Content Hash')).toBeVisible() @@ -670,7 +656,6 @@ test.describe('unwrapped', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver.getByText(oldResolver)).toBeVisible() @@ -702,7 +687,6 @@ test.describe('unwrapped', () => { await morePage.goto(name) await expect(morePage.resolver).toHaveText(oldResolver) - await page.pause() await recordsPage.goto(name) await expect(recordsPage.getRecordValue('text', 'location')).toHaveText('L1 chain') await expect(recordsPage.getRecordValue('text', 'description')).toHaveText('new name') @@ -737,7 +721,6 @@ test.describe('unwrapped', () => { await morePage.goto(name) await login.connect() - await page.pause() await expect(morePage.resolver.getByText(oldResolver)).toBeVisible() @@ -785,7 +768,6 @@ test.describe('unwrapped', () => { await morePage.goto(name) await expect(morePage.resolver).toHaveText(oldResolver) - await page.pause() await recordsPage.goto(name) await expect(page.getByTestId('text-amount')).toHaveText('0 Records') await expect(page.getByTestId('address-amount')).toHaveText('0 Records') @@ -814,7 +796,6 @@ test.describe('wrapped', () => { await profilePage.goto(name) await login.connect() - await page.pause() await expect(profilePage.record('text', 'description')).toHaveText('Hello2') await expect(profilePage.record('text', 'url')).toHaveText('twitter.com') @@ -838,7 +819,6 @@ test.describe('wrapped', () => { await transactionModal.autoComplete() - await page.pause() await recordsPage.goto(name) await expect(recordsPage.getRecordValue('text', 'location')).toHaveText('L1 chain') await expect(recordsPage.getRecordValue('text', 'description')).toHaveText('new name') @@ -871,7 +851,6 @@ test.describe('wrapped', () => { await profilePage.goto(name) await login.connect() - await page.pause() await expect(profilePage.record('text', 'description')).toHaveText('Hello2') await expect(profilePage.record('text', 'url')).toHaveText('twitter.com') @@ -911,7 +890,6 @@ test.describe('wrapped', () => { await transactionModal.autoComplete() - await page.pause() await recordsPage.goto(name) await expect(page.getByTestId('text-amount')).toHaveText('0 Records') await expect(page.getByTestId('address-amount')).toHaveText('0 Records') diff --git a/e2e/specs/stateless/setPrimary.spec.ts b/e2e/specs/stateless/setPrimary.spec.ts index df0bf6040..8237984eb 100644 --- a/e2e/specs/stateless/setPrimary.spec.ts +++ b/e2e/specs/stateless/setPrimary.spec.ts @@ -186,7 +186,7 @@ test.describe('profile', () => { await morePage.goto(subname) await login.connect() - await page.pause() + await expect(morePage.resolver).toContainText(UNAUTHORISED_RESOLVER) await profilePage.goto(subname) @@ -317,7 +317,7 @@ test.describe('profile', () => { await profilePage.goto(subname) await login.connect() - await page.pause() + // Assert state await expect(page.getByTestId('profile-title')).not.toContainText(subname) @@ -426,8 +426,6 @@ test.describe('profile', () => { await profilePage.goto(subname) await login.connect() - await page.pause() - // Assert state await expect(page.getByTestId('owner-profile-button-name.manager')).toContainText( accounts.getAddress('user', 5), @@ -482,8 +480,6 @@ test.describe('profile', () => { await profilePage.goto(name) await login.connect() - await page.pause() - await expect(page.getByTestId('profile-action-Set as primary name')).toHaveCount(0) // Assert state diff --git a/e2e/specs/stateless/verifications.spec.ts b/e2e/specs/stateless/verifications.spec.ts index 3f23e22e4..3145b4ac6 100644 --- a/e2e/specs/stateless/verifications.spec.ts +++ b/e2e/specs/stateless/verifications.spec.ts @@ -108,8 +108,6 @@ test.describe('Verified records', () => { await page.goto(`/${name}`) // await login.connect() - await page.pause() - await expect(page.getByTestId('profile-section-verifications')).toBeVisible() await profilePage.isRecordVerified('text', 'com.twitter') @@ -180,8 +178,6 @@ test.describe('Verified records', () => { await page.goto(`/${name}`) - await page.pause() - await expect(page.getByTestId('profile-section-verifications')).toBeVisible() await profilePage.isRecordVerified('text', 'com.twitter', false) @@ -345,8 +341,6 @@ test.describe('Verify profile', () => { await profilePage.goto(name) await login.connect() - await page.pause() - await expect(page.getByTestId('profile-section-verifications')).toBeVisible() await profilePage.isRecordVerified('text', 'com.twitter') @@ -374,7 +368,6 @@ test.describe('Verify profile', () => { await profilePage.isRecordVerified('text', 'com.discord', false) await profilePage.isRecordVerified('verification', 'dentity', false) await profilePage.isPersonhoodVerified(false) - await page.pause() }) }) @@ -488,8 +481,6 @@ test.describe('OAuth flow', () => { await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`) await login.connect() - await page.pause() - await expect(page.getByText('Verification failed')).toBeVisible() await expect( page.getByText( @@ -497,13 +488,10 @@ test.describe('OAuth flow', () => { ), ).toBeVisible() - await page.pause() await login.switchTo('user2') // Page should redirect to the profile page await expect(page).toHaveURL(`/${name}`) - - await page.pause() }) test('Should show an error message if user is not logged in', async ({ @@ -531,8 +519,6 @@ test.describe('OAuth flow', () => { await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`) - await page.pause() - await expect(page.getByText('Verification failed')).toBeVisible() await expect( page.getByText('You must be connected as 0x709...c79C8 to set the verification record.'), @@ -540,13 +526,10 @@ test.describe('OAuth flow', () => { await page.locator('.modal').getByRole('button', { name: 'Done' }).click() - await page.pause() await login.connect('user2') // Page should redirect to the profile page await expect(page).toHaveURL(`/${name}`) - - await page.pause() }) test('Should redirect to profile page without showing set verification record if it already set', async ({ @@ -594,8 +577,6 @@ test.describe('OAuth flow', () => { await expect(page).toHaveURL(`/${name}`) await expect(transactionModal.transactionModal).not.toBeVisible() - - await page.pause() }) test('Should show general error message if other problems occur', async ({ @@ -624,8 +605,6 @@ test.describe('OAuth flow', () => { await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`) await login.connect('user') - await page.pause() - await expect(page.getByText('Verification failed')).toBeVisible() await expect( page.getByText("We could't verify your account. Please return to Dentity and try again."), diff --git a/e2e/specs/stateless/wrapName.spec.ts b/e2e/specs/stateless/wrapName.spec.ts index a1d104e0d..8b370f822 100644 --- a/e2e/specs/stateless/wrapName.spec.ts +++ b/e2e/specs/stateless/wrapName.spec.ts @@ -158,8 +158,6 @@ test('should allow wrapping a subdomain', async ({ page, makeName, login, makePa await morePage.goto(subname) await login.connect() - await page.pause() - // should approve name wrapper for address await morePage.wrapButton.click() @@ -198,7 +196,6 @@ test('should allow wrapping a name with an unknown label', async ({ await morePage.goto(subname) await login.connect() - await page.pause() await morePage.wrapButton.click() const input = page.getByTestId(`unknown-label-input-${unknownLabelhash}`) diff --git a/src/components/pages/import/[name]/useDnsImportReducer.ts b/src/components/pages/import/[name]/useDnsImportReducer.ts index 300178e50..24a5af4a2 100644 --- a/src/components/pages/import/[name]/useDnsImportReducer.ts +++ b/src/components/pages/import/[name]/useDnsImportReducer.ts @@ -124,7 +124,6 @@ const reducer = case 'increaseStep': { const currentStep = selectedItem.steps[selectedItem.stepIndex] - console.log('selectedItem', JSON.stringify(selectedItem)) const eventName = match(currentStep) .with('selectType', () => 'dns_selected_import_type' as const) .with('enableDnssec', () => 'dns_sec_enabled' as const) @@ -201,7 +200,6 @@ export const useDnsImportReducer = ({ } useEffect(() => { - console.log('cleanupNonMatching') dispatch({ name: 'cleanupNonMatching', selected }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [address, name, chainId]) diff --git a/src/transaction-flow/input/ProfileReclaim-flow.tsx b/src/transaction-flow/input/ProfileReclaim-flow.tsx index a71448ecb..407a0727e 100644 --- a/src/transaction-flow/input/ProfileReclaim-flow.tsx +++ b/src/transaction-flow/input/ProfileReclaim-flow.tsx @@ -85,7 +85,6 @@ const ProfileReclaim = ({ data: { name, label, parent }, dispatch, onDismiss }: errorForRecordAtIndex, isDirtyForRecordAtIndex, } = useProfileEditorForm(existingRecords) - console.log(existingRecords) const handleSubmit = () => { const payload = [ createTransactionItem('createSubname', { From 0006c4a13a17798a74e3394f75ecd5ac4a229a2e Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 29 Oct 2024 23:23:16 +0800 Subject: [PATCH 15/37] remove page pause --- e2e/specs/stateful/address.spec.ts | 1 - e2e/specs/stateless/ownership.3LD.spec.ts | 3 --- e2e/specs/stateless/ownership.spec.ts | 13 ++++--------- e2e/specs/stateless/registerName.spec.ts | 2 -- 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/e2e/specs/stateful/address.spec.ts b/e2e/specs/stateful/address.spec.ts index 3997ab815..4fce98112 100644 --- a/e2e/specs/stateful/address.spec.ts +++ b/e2e/specs/stateful/address.spec.ts @@ -18,7 +18,6 @@ test('should go to the address page', async ({ page, login }) => { await page.goto('/') await login.connect() - await page.pause() await page .getByPlaceholder('Search for a name') .fill('0xFc5958B4B6F9a06D21E06429c8833f865577acf0') diff --git a/e2e/specs/stateless/ownership.3LD.spec.ts b/e2e/specs/stateless/ownership.3LD.spec.ts index 000cbef68..4c42a7288 100644 --- a/e2e/specs/stateless/ownership.3LD.spec.ts +++ b/e2e/specs/stateless/ownership.3LD.spec.ts @@ -34,7 +34,6 @@ test.describe('Unwrapped 3LD, Unwrapped 2LD - Parent Owner and Manager', () => { await ownershipPage.goto(subname) await login.connect() - await page.pause() await ownershipPage.sendNameButton.click() await sendNameModal.searchInput.fill(accounts.getAddress('user3')) await sendNameModal.searchResult(accounts.getAddress('user3')).click() @@ -1193,7 +1192,6 @@ test.describe('Wrapped 3LD, Unwrapped 2LD - Manager only', () => { await ownershipPage.goto(subname) await login.connect() - await page.pause() await ownershipPage.sendNameButton.click() await sendNameModal.searchInput.fill(accounts.getAddress('user3')) await sendNameModal.searchResult(accounts.getAddress('user3')).click() @@ -1390,7 +1388,6 @@ test.describe('Wrapped 3LD, Unwrapped 2LD - Parent only', () => { await ownershipPage.goto(subname) await login.connect() - await page.pause() await page.waitForTimeout(2000) await expect(ownershipPage.sendNameButton).toHaveCount(0) await expect(ownershipPage.editRolesButton).toHaveCount(0) diff --git a/e2e/specs/stateless/ownership.spec.ts b/e2e/specs/stateless/ownership.spec.ts index 332de2f35..890f57477 100644 --- a/e2e/specs/stateless/ownership.spec.ts +++ b/e2e/specs/stateless/ownership.spec.ts @@ -389,7 +389,7 @@ test.describe('Send name', () => { await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager', { timeout: 15000, }) - await page.pause() + await expect(ownershipPage.roleRow(accounts.getAddress('user2'))).toContainText('Parent owner') await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('ETH record') }) @@ -436,7 +436,6 @@ test.describe('Send name', () => { await profilePage.goto(subname) await login.connect() await expect(profilePage.record('text', 'nickname')).toContainText('test') - await page.pause() await ownershipPage.goto(subname) await ownershipPage.sendNameButton.click() @@ -460,7 +459,7 @@ test.describe('Send name', () => { await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager', { timeout: 15000, }) - await page.pause() + await expect(ownershipPage.roleRow(accounts.getAddress('user'))).toContainText('Parent owner') await expect(ownershipPage.roleRow('0x42D63ae25990889E35F215bC95884039Ba354115')).toContainText( 'ETH record', @@ -535,7 +534,7 @@ test.describe('Send name', () => { await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('manager', { timeout: 15000, }) - await page.pause() + await expect(ownershipPage.roleRow(accounts.getAddress('user2'))).toContainText('Parent owner') await expect(ownershipPage.roleRow(accounts.getAddress('user3'))).toContainText('ETH record') @@ -601,7 +600,6 @@ test.describe('Send name', () => { await sendNameModal.searchResult(accounts.getAddress('user3')).click() await sendNameModal.resetProfileSwitch.check() - await page.pause() // Should not be able to set owner because name is unwrapped // Should not be able to set eth record because user is not the manager // Should not be able to reset profile since old resolver does not support VersionableResolver @@ -682,7 +680,7 @@ test.describe('Edit roles: Happy ', () => { // Should not allow the manager to change the owner await expect(editRolesModal.roleCard('owner')).toHaveCount(0) await editRolesModal.roleCardChangeButton('manager').click() - await page.pause() + await editRolesModal.searchInput.fill(accounts.getAddress('user3')) await editRolesModal.searchResult(accounts.getAddress('user3')).click() await editRolesModal.saveButton.click() @@ -889,7 +887,6 @@ test.describe('Edit roles: Unwrapped subnames', () => { await ownershipPage.goto(subname) await login.connect() - await page.pause() await page.waitForTimeout(2000) await expect(ownershipPage.sendNameButton).toHaveCount(0) @@ -959,7 +956,6 @@ test.describe('Edit roles: Wrapped subnames', () => { await ownershipPage.goto(subname) await login.connect() - await page.pause() await ownershipPage.editRolesButton.click() await editRolesModal.roleCardChangeButton('manager').click() @@ -1143,7 +1139,6 @@ test.describe('Extend name', () => { await ownershipPage.goto(name) await login.connect() - await page.pause() const timestamp = await ownershipPage.getExpiryTimestamp() diff --git a/e2e/specs/stateless/registerName.spec.ts b/e2e/specs/stateless/registerName.spec.ts index 1b2209a93..d4ccb4326 100644 --- a/e2e/specs/stateless/registerName.spec.ts +++ b/e2e/specs/stateless/registerName.spec.ts @@ -388,7 +388,6 @@ test('should allow registering a name and resuming from the commit toast', async await page.goto(`/${name}/register`) await login.connect() - await page.pause() await page.getByTestId('payment-choice-ethereum').click() await page.getByTestId('primary-name-toggle').uncheck() await page.getByTestId('next-button').click() @@ -449,7 +448,6 @@ test('should allow registering with a specific date', async ({ page, login, make await calendar.fill(dateToDateInput(twoYearsAndHalfLater)) - await page.pause() await expect(page.getByTestId('calendar-date')).toHaveValue( dateToDateInput(twoYearsAndHalfLater), ) From 6ef12d787833e545fb1a15a72a58d9ab27b8d405 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Tue, 29 Oct 2024 23:40:13 +0800 Subject: [PATCH 16/37] fix build errors --- e2e/specs/stateless/ownership.2LD.spec.ts | 10 +++++----- e2e/specs/stateless/ownership.3LD.spec.ts | 4 ++-- e2e/specs/stateless/ownership.4LD.spec.ts | 9 ++------- e2e/specs/stateless/ownership.spec.ts | 3 --- e2e/specs/stateless/profileEditor.spec.ts | 7 +------ e2e/specs/stateless/wrapName.spec.ts | 2 +- 6 files changed, 11 insertions(+), 24 deletions(-) diff --git a/e2e/specs/stateless/ownership.2LD.spec.ts b/e2e/specs/stateless/ownership.2LD.spec.ts index 8fa590a0d..3910a0738 100644 --- a/e2e/specs/stateless/ownership.2LD.spec.ts +++ b/e2e/specs/stateless/ownership.2LD.spec.ts @@ -3,7 +3,7 @@ import { expect } from '@playwright/test' import { test } from '../../../playwright' test.describe('Unwrapped 2LD - Owner and Manager', () => { - test('Send feature', async ({ page, login, accounts, makeName, makePageObject }) => { + test('Send feature', async ({ login, accounts, makeName, makePageObject }) => { const name = await makeName({ label: 'owner-manager', type: 'legacy', @@ -516,7 +516,7 @@ test.describe('Wrapped and Emancipated 2LD - Owner', () => { }) test.describe('Grace Period Unwrapped 2LD', () => { - test('Send feature', async ({ page, login, makeName, makePageObject }) => { + test('Send feature', async ({ login, makeName, makePageObject }) => { const name = await makeName({ label: 'grace-period-unwrapped', type: 'legacy', @@ -564,7 +564,7 @@ test.describe('Grace Period Unwrapped 2LD', () => { await expect(ownershipPage.syncManagerButton).toHaveCount(0) }) - test('Edit Roles', async ({ page, login, accounts, makeName, makePageObject }) => { + test('Edit Roles', async ({ login, accounts, makeName, makePageObject }) => { const name = await makeName({ label: 'grace-period-unwrapped', type: 'legacy', @@ -627,7 +627,7 @@ test.describe('Grace Period Unwrapped 2LD', () => { }) test.describe('Grace Period Wrapped 2LD', () => { - test('Send feature', async ({ page, login, makeName, makePageObject }) => { + test('Send feature', async ({ login, makeName, makePageObject }) => { const name = await makeName({ label: 'grace-period-wrapped', type: 'wrapped', @@ -674,7 +674,7 @@ test.describe('Grace Period Wrapped 2LD', () => { await expect(ownershipPage.syncManagerButton).toHaveCount(0) }) - test('Edit Roles', async ({ page, login, accounts, makeName, makePageObject }) => { + test('Edit Roles', async ({ login, accounts, makeName, makePageObject }) => { const name = await makeName({ label: 'grace-period-wrapped', type: 'wrapped', diff --git a/e2e/specs/stateless/ownership.3LD.spec.ts b/e2e/specs/stateless/ownership.3LD.spec.ts index 4c42a7288..0d574db81 100644 --- a/e2e/specs/stateless/ownership.3LD.spec.ts +++ b/e2e/specs/stateless/ownership.3LD.spec.ts @@ -4,7 +4,7 @@ import { test } from '../../../playwright' import { testClient } from '../../../playwright/fixtures/contracts/utils/addTestContracts' test.describe('Unwrapped 3LD, Unwrapped 2LD - Parent Owner and Manager', () => { - test('Send feature', async ({ page, login, accounts, makeName, makePageObject }) => { + test('Send feature', async ({ login, accounts, makeName, makePageObject }) => { const name = await makeName({ label: 'unwrapped', type: 'legacy', @@ -1160,7 +1160,7 @@ test.describe('Wrapped 3LD - Wrapped 2LD - Parent Owner only', () => { }) test.describe('Wrapped 3LD, Unwrapped 2LD - Manager only', () => { - test('Send feature', async ({ page, login, accounts, makeName, makePageObject }) => { + test('Send feature', async ({ login, accounts, makeName, makePageObject }) => { const name = await makeName({ label: 'unwrapped', type: 'legacy', diff --git a/e2e/specs/stateless/ownership.4LD.spec.ts b/e2e/specs/stateless/ownership.4LD.spec.ts index 58c7ed289..702de55a2 100644 --- a/e2e/specs/stateless/ownership.4LD.spec.ts +++ b/e2e/specs/stateless/ownership.4LD.spec.ts @@ -386,12 +386,7 @@ test.describe('Unwrapped 4LD, Unwrapped 3LD,2LD - Manager Only', () => { }) }) - test('Expiry Section, Extend & Set Reminder', async ({ - login, - makeName, - makePageObject, - page, - }) => { + test('Expiry Section, Extend & Set Reminder', async ({ login, makeName, makePageObject }) => { const name = await makeName({ label: 'unwrapped', type: 'legacy', @@ -736,7 +731,7 @@ test.describe('Unwrapped 4LD - Wrapped 3LD,2LD - Parent Owner only', () => { }) test.describe('Unwrapped 4LD, Wrapped 3LD,2LD - Manager Only', () => { - test('Send feature', async ({ page, login, accounts, makeName, makePageObject }) => { + test('Send feature', async ({ login, accounts, makeName, makePageObject }) => { const name = await makeName({ label: 'unwrapped', type: 'wrapped', diff --git a/e2e/specs/stateless/ownership.spec.ts b/e2e/specs/stateless/ownership.spec.ts index 890f57477..50aa0cd55 100644 --- a/e2e/specs/stateless/ownership.spec.ts +++ b/e2e/specs/stateless/ownership.spec.ts @@ -328,7 +328,6 @@ test.describe('Send name', () => { }) test('should be able to send manager and eth-record if user is manager and not parent owner of unwrapped subname', async ({ - page, login, accounts, makeName, @@ -655,7 +654,6 @@ test.describe('Edit roles: Happy ', () => { }) test('Should allow manager to change manager when they are not the owner', async ({ - page, login, accounts, makeName, @@ -929,7 +927,6 @@ test.describe('Edit roles: Unwrapped name', () => { test.describe('Edit roles: Wrapped subnames', () => { test('should allow namewrapper subname owner to send name', async ({ - page, login, accounts, makeName, diff --git a/e2e/specs/stateless/profileEditor.spec.ts b/e2e/specs/stateless/profileEditor.spec.ts index f0733b6ca..823c9cdb6 100644 --- a/e2e/specs/stateless/profileEditor.spec.ts +++ b/e2e/specs/stateless/profileEditor.spec.ts @@ -84,7 +84,7 @@ const makeRecords = async (overwrites: RecordOptions = {}): Promise { - test('should display profile records', async ({ page, login, makeName, makePageObject }) => { + test('should display profile records', async ({ login, makeName, makePageObject }) => { const name = await makeName({ label: 'profile', type: 'legacy', @@ -194,7 +194,6 @@ test.describe('migrations', () => { }) test('should force a name with an unauthorised resolver to update their resolver', async ({ - page, login, makeName, makePageObject, @@ -228,7 +227,6 @@ test.describe('migrations', () => { }) test('should force a name with an invalid resolver to update their resolver', async ({ - page, login, makeName, makePageObject, @@ -262,7 +260,6 @@ test.describe('migrations', () => { }) test('should force a wrapped name with a resolver that is not name wrapper aware to migrate update their resolver', async ({ - page, login, makeName, makePageObject, @@ -636,7 +633,6 @@ test.describe('migrations', () => { test.describe('unwrapped', () => { test('should be able to add/update profile records without migration', async ({ - page, login, makeName, makePageObject, @@ -778,7 +774,6 @@ test.describe('unwrapped', () => { test.describe('wrapped', () => { test('should be able to add/update profile records without migration', async ({ - page, login, makeName, makePageObject, diff --git a/e2e/specs/stateless/wrapName.spec.ts b/e2e/specs/stateless/wrapName.spec.ts index 8b370f822..607839564 100644 --- a/e2e/specs/stateless/wrapName.spec.ts +++ b/e2e/specs/stateless/wrapName.spec.ts @@ -131,7 +131,7 @@ test('should wrap name', async ({ makeName, login, makePageObject }) => { await expect(morePage.wrapButton).toHaveCount(0) }) -test('should allow wrapping a subdomain', async ({ page, makeName, login, makePageObject }) => { +test('should allow wrapping a subdomain', async ({ makeName, login, makePageObject }) => { const name = await makeName({ label: 'unwrapped-with-wrapped-subnames', type: 'legacy', From f0b74a28d8e27f89d9e3670631725db9c00ba6a6 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Thu, 31 Oct 2024 15:41:20 +0200 Subject: [PATCH 17/37] wip migrate --- public/confetti.png | Bin 0 -> 107983 bytes public/locales/en/ensvs2.json | 4 + src/components/pages/migrate/Carousel.tsx | 21 +++ src/pages/migrate.tsx | 218 ++++++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 public/confetti.png create mode 100644 public/locales/en/ensvs2.json create mode 100644 src/components/pages/migrate/Carousel.tsx create mode 100644 src/pages/migrate.tsx diff --git a/public/confetti.png b/public/confetti.png new file mode 100644 index 0000000000000000000000000000000000000000..eab8f092f45b92920f6a2a432db23466c7268900 GIT binary patch literal 107983 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 SY0N0flgUacN&XNw4*Gu~l@Rg( literal 0 HcmV?d00001 diff --git a/public/locales/en/ensvs2.json b/public/locales/en/ensvs2.json new file mode 100644 index 000000000..ac4b3e432 --- /dev/null +++ b/public/locales/en/ensvs2.json @@ -0,0 +1,4 @@ +{ + "title": "ENSv2: The Next Generation of ENS", + "caption": "Approve your ENS names for ENSv2 now and enjoy automatic migration, paid for by ENS DAO." +} \ No newline at end of file diff --git a/src/components/pages/migrate/Carousel.tsx b/src/components/pages/migrate/Carousel.tsx new file mode 100644 index 000000000..9d25dca59 --- /dev/null +++ b/src/components/pages/migrate/Carousel.tsx @@ -0,0 +1,21 @@ +import { Splide, SplideSlide } from '@splidejs/react-splide' +import { ReactNode } from 'react' + +export const Carousel = ({ children }: { children: ReactNode[] }) => { + return ( + + {children.map((child, i) => ( + // eslint-disable-next-line react/no-array-index-key + {child} + ))} + + ) +} \ No newline at end of file diff --git a/src/pages/migrate.tsx b/src/pages/migrate.tsx new file mode 100644 index 000000000..948c5a7b3 --- /dev/null +++ b/src/pages/migrate.tsx @@ -0,0 +1,218 @@ +import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' + +import { + Banner, + Button, + Card, + GasPumpSVG, + KeySVG, + RightArrowSVG, + Typography, + WalletSVG, +} from '@ensdomains/thorin' + +import { Carousel } from '@app/components/pages/migrate/Carousel' + +const Title = styled.h1` + font-weight: 830; + text-align: center; + + font-size: 52px; + line-height: 104%; + + @media (min-width: 640px) { + font-size: 60px; + } + + @media (min-width: 1024px) { + font-size: 76px; + } +` + +const Header = styled.header( + ({ theme }) => css` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: ${theme.space[4]}; + padding: ${theme.space[4]}; + min-height: 530px; + `, +) + +const Main = styled.main( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['16']}; + `, +) + +const PartnershipAnnouncement = styled.div( + ({ theme }) => css` + width: ${theme.space.full}; + padding: ${theme.space['4']}; + background-color: ${theme.colors.backgroundPrimary}; + border-radius: ${theme.radii['4xLarge']}; + font-size: ${theme.fontSizes.body}; + font-weight: ${theme.fontWeights.bold}; + display: flex; + justify-content: space-between; + & > a { + color: ${theme.colors.greenDim}; + cursor: pointer; + display: flex; + align-items: center; + gap: ${theme.space['2']}; + } + & > a:hover { + color: ${theme.colors.green}; + } + @media (min-width: 640px) { + border-radius: ${theme.radii['3xLarge']}; + } + `, +) + +const TopNav = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['6']}; + align-items: center; + position: sticky; + top: 0; + left: 0; + `, +) + +const CardWithEmoji = styled(Card)` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding-top: 83px; + position: relative; + grid-column: 1 / -1; + & > img { + position: absolute; + top: -72px; + } +` + +const GridOneToThree = styled.div( + ({ theme }) => css` + display: grid; + grid-template-rows: auto; + gap: ${theme.space['4']}; + text-align: center; + grid-template-columns: 1fr; + @media (min-width: 640px) { + grid-template-columns: repeat(3, 1fr); + } + `, +) + +const CardHeader = styled.h3( + ({ theme }) => css` + display: flex; + flex-direction: column; + font-size: ${theme.fontSizes.extraLarge}; + color: ${theme.colors.greenDim}; + font-weight: ${theme.fontWeights.bold}; + gap: ${theme.space['2']}; + align-items: center; + `, +) + +const AnnouncementSlide = ({ title, text }: { title: string; text: string }) => ( + + {text} + +) + +const SlideshowContainer = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['6']}; + h3 { + text-align: center; + } + `, +) + +export default function ENSv2() { + const { t } = useTranslation('ensvs2') + return ( + + ) +} From eefb82b5173387f7a0b1fec55801de85830ad924 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Thu, 31 Oct 2024 18:08:04 +0200 Subject: [PATCH 18/37] landing page --- public/locales/en/ensvs2.json | 49 +++++++++- public/{ => migrate}/confetti.png | Bin public/migrate/preview.svg | 77 +++++++++++++++ src/assets/DAO.svg | 10 ++ src/assets/social/SocialX.svg | 2 +- src/components/pages/migrate/Carousel.tsx | 4 +- src/pages/_app.tsx | 1 + src/pages/migrate.tsx | 110 ++++++++++++++++++++-- 8 files changed, 243 insertions(+), 10 deletions(-) rename public/{ => migrate}/confetti.png (100%) create mode 100644 public/migrate/preview.svg create mode 100644 src/assets/DAO.svg diff --git a/public/locales/en/ensvs2.json b/public/locales/en/ensvs2.json index ac4b3e432..14a15d823 100644 --- a/public/locales/en/ensvs2.json +++ b/public/locales/en/ensvs2.json @@ -1,4 +1,51 @@ { "title": "ENSv2: The Next Generation of ENS", - "caption": "Approve your ENS names for ENSv2 now and enjoy automatic migration, paid for by ENS DAO." + "caption": "Approve your ENS names for ENSv2 now and enjoy automatic migration, paid for by ENS DAO.", + "accessible": { + "title": "Making ENS accessible to more people", + "caption": "We're taking our knowledge from the last 7 years at the frontier of web3 naming to re-envision the architecture from the ground up on L2 By utilizing L2s, we're excited to make ENS more accessible to a wider range of users.", + "link": "ENSv2 Project Plan", + "gas": { + "title": "Lower Gas Costs", + "text": "Layer 2 reduces gas fees, making .eth registrations and renewals cheaper and faster." + }, + "control": { + "title": "Enhanced Control", + "text": "ENSv2 gives each .eth name its own registry, offering more flexibility and control." + }, + "multichain": { + "title": "Improved Multi-Chain", + "text": "Layer 2 enables seamless .eth name use across blockchains with trustless connections." + } + }, + "announcement": { + "title": "Announcements", + "l2": { + "title": "L2 partner announcement", + "caption": "We’re announced that we’re partnering with _____! Watch Nick’s ___ presentation to see the full announcement." + }, + "ensv2": { + "title": "ENSv2: An Update on our Progress", + "caption": "This update aims to be informative and slightly technical, allowing you, the ENS community, to stay connected with our progress." + }, + "nextgen": { + "title": "ENSv2: The Next Generation of ENS", + "caption": "Our vision for the next iterations of the ENS protocol, on L2." + } + }, + "footer": { + "title": "Got questions?", + "learn": { + "title": "Learn", + "faq": "ENSv2 FAQs", + "plan": "ENSv2 Project Plan", + "base": "Knowledge base" + }, + "support": { + "title": "Support", + "ticket": "Open a ticket", + "twitter": "X (Twitter)", + "dao": "DAO forums" + } + } } \ No newline at end of file diff --git a/public/confetti.png b/public/migrate/confetti.png similarity index 100% rename from public/confetti.png rename to public/migrate/confetti.png diff --git a/public/migrate/preview.svg b/public/migrate/preview.svg new file mode 100644 index 000000000..5c5546d10 --- /dev/null +++ b/public/migrate/preview.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/DAO.svg b/src/assets/DAO.svg new file mode 100644 index 000000000..2d33444de --- /dev/null +++ b/src/assets/DAO.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/social/SocialX.svg b/src/assets/social/SocialX.svg index 6a7b7dfe7..134d9024f 100644 --- a/src/assets/social/SocialX.svg +++ b/src/assets/social/SocialX.svg @@ -1,3 +1,3 @@ - + diff --git a/src/components/pages/migrate/Carousel.tsx b/src/components/pages/migrate/Carousel.tsx index 9d25dca59..1f7f254a5 100644 --- a/src/components/pages/migrate/Carousel.tsx +++ b/src/components/pages/migrate/Carousel.tsx @@ -8,7 +8,7 @@ export const Carousel = ({ children }: { children: ReactNode[] }) => { arrows: false, pagination: false, gap: '16px', - perPage: 2, + perPage: 1, fixedWidth: 312, }} > @@ -18,4 +18,4 @@ export const Carousel = ({ children }: { children: ReactNode[] }) => { ))} ) -} \ No newline at end of file +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 3c5d9dfb9..294ed4587 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,6 +1,7 @@ import { lightTheme, RainbowKitProvider, Theme } from '@rainbow-me/rainbowkit' import '@rainbow-me/rainbowkit/styles.css' +import '@splidejs/react-splide/css' import { NextPage } from 'next' import type { AppProps } from 'next/app' diff --git a/src/pages/migrate.tsx b/src/pages/migrate.tsx index 948c5a7b3..b6be85326 100644 --- a/src/pages/migrate.tsx +++ b/src/pages/migrate.tsx @@ -6,14 +6,21 @@ import { Button, Card, GasPumpSVG, + InfoCircleSVG, KeySVG, + QuestionBubbleSVG, + QuestionCircleSVG, RightArrowSVG, + SpannerAltSVG, Typography, WalletSVG, } from '@ensdomains/thorin' import { Carousel } from '@app/components/pages/migrate/Carousel' +import DAOSVG from '../assets/DAO.svg' +import SocialX from '../assets/social/SocialX.svg' + const Title = styled.h1` font-weight: 830; text-align: center; @@ -50,6 +57,54 @@ const Main = styled.main( `, ) +const Footer = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['6']}; + h3 { + text-align: center; + } + span { + display: flex; + flex-direction: row; + align-items: center; + gap: ${theme.space['2']}; + color: ${theme.colors.green}; + } + & > div { + display: grid; + grid-template-columns: repeat(1, 1fr); + gap: ${theme.space['4']}; + } + & > div > div { + width: 100%; + display: flex; + align-items: center; + } + @media (min-width: 480px) { + & > div { + grid-template-columns: repeat(2, 1fr); + } + } + `, +) + +const AnnouncementBanner = styled.div( + ({ theme }) => css` + height: ${theme.space.full}; + text-align: center; + & > a { + height: ${theme.space.full}; + justify-content: flex-start; + & > div { + height: ${theme.space.full}; + justify-content: flex-start; + } + } + `, +) + const PartnershipAnnouncement = styled.div( ({ theme }) => css` width: ${theme.space.full}; @@ -83,8 +138,9 @@ const TopNav = styled.div( gap: ${theme.space['6']}; align-items: center; position: sticky; - top: 0; + top: ${theme.space['4']}; left: 0; + z-index: 1; `, ) @@ -128,9 +184,11 @@ const CardHeader = styled.h3( ) const AnnouncementSlide = ({ title, text }: { title: string; text: string }) => ( - - {text} - + + + {text} + + ) const SlideshowContainer = styled.div( @@ -144,6 +202,15 @@ const SlideshowContainer = styled.div( `, ) +const Video = styled.video( + ({ theme }) => css` + height: ${theme.space.full}; + width: ${theme.space.full}; + border-radius: ${theme.radii.card}; + margin-bottom: ${theme.space[18]}; + `, +) + export default function ENSv2() { const { t } = useTranslation('ensvs2') return ( @@ -160,10 +227,12 @@ export default function ENSv2() { {t('title')} {t('caption')} -
video
+ - 🎉 + 🎉 {t('accessible.title')} @@ -213,6 +282,35 @@ export default function ENSv2() { /> +
+ + {t('footer.title')} + +
+ + + {t('footer.learn.faq')} + + + {t('footer.learn.plan')} + + + {t('footer.learn.base')} + + + + + {t('footer.support.ticket')} + + + {t('footer.support.twitter')} + + + {t('footer.support.dao')} + + +
+
) } From 148f7d452a52f2e70b0b169da5d379debcafb1c9 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Thu, 31 Oct 2024 18:10:05 +0200 Subject: [PATCH 19/37] fix --- package.json | 1 + pnpm-lock.yaml | 41 ++++++++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index d223d48ec..1742cc73d 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@noble/hashes": "^1.3.2", "@rainbow-me/rainbowkit": "2.1.2", "@sentry/nextjs": "7.43.x", + "@splidejs/react-splide": "^0.7.12", "@svgr/webpack": "^8.1.0", "@tanstack/query-persist-client-core": "5.22.2", "@tanstack/query-sync-storage-persister": "5.22.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bd38f822..e774a7f8e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@sentry/nextjs': specifier: 7.43.x version: 7.43.0(encoding@0.1.13)(next@13.5.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.91.0(esbuild@0.17.19)) + '@splidejs/react-splide': + specifier: ^0.7.12 + version: 0.7.12 '@svgr/webpack': specifier: ^8.1.0 version: 8.1.0(typescript@5.4.5) @@ -296,7 +299,7 @@ importers: version: 0.3.9 eslint-plugin-import: specifier: ^2.28.1 - version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: specifier: ^6.7.1 version: 6.8.0(eslint@8.50.0) @@ -2950,6 +2953,12 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@splidejs/react-splide@0.7.12': + resolution: {integrity: sha512-UfXH+j47jsMc4x5HA/aOwuuHPqn6y9+ZTNYPWDRD8iLKvIVMZlzq2unjUEvyDAU+TTVPZOXkG2Ojeoz0P4AkZw==} + + '@splidejs/splide@4.1.4': + resolution: {integrity: sha512-5I30evTJcAJQXt6vJ26g2xEkG+l1nXcpEw4xpKh0/FWQ8ozmAeTbtniVtVmz2sH1Es3vgfC4SS8B2X4o5JMptA==} + '@stablelib/aead@1.0.1': resolution: {integrity: sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg==} @@ -13641,6 +13650,12 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@splidejs/react-splide@0.7.12': + dependencies: + '@splidejs/splide': 4.1.4 + + '@splidejs/splide@4.1.4': {} + '@stablelib/aead@1.0.1': {} '@stablelib/binary@1.0.1': @@ -16657,7 +16672,7 @@ snapshots: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.50.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 @@ -16668,13 +16683,13 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint-plugin-jsx-a11y@6.8.0(eslint@8.50.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.50.0))(eslint-plugin-react@7.34.1(eslint@8.50.0))(eslint@8.50.0): dependencies: eslint: 8.50.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.50.0) eslint-plugin-react: 7.34.1(eslint@8.50.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.50.0) @@ -16688,8 +16703,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.50.0) eslint-plugin-react: 7.34.1(eslint@8.50.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.50.0) @@ -16711,13 +16726,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0): dependencies: debug: 4.3.4(supports-color@5.5.0) enhanced-resolve: 5.16.1 eslint: 8.50.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -16728,18 +16743,18 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -16749,7 +16764,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 From e471abb9deef9382fc1c39599286a8b73834b28b Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 4 Nov 2024 23:34:08 +0800 Subject: [PATCH 20/37] bump ensjs to 4.0.2-alpha.5 --- e2e/specs/stateless/myNames.spec.ts | 235 +++++++++++++++++- package.json | 5 +- playwright/fixtures/accounts.ts | 5 +- .../generators/legacyNameGenerator.ts | 17 +- .../legacyWithConfigNameGenerator.ts | 18 +- .../generators/wrappedNameGenerator.ts | 10 +- playwright/fixtures/makeName/index.ts | 19 +- playwright/fixtures/subgraph.ts | 17 +- pnpm-lock.yaml | 218 ++++++++++++++-- .../NameTableHeader/NameTableHeader.tsx | 2 + 10 files changed, 473 insertions(+), 73 deletions(-) diff --git a/e2e/specs/stateless/myNames.spec.ts b/e2e/specs/stateless/myNames.spec.ts index 0c3ae97c5..ac85f255e 100644 --- a/e2e/specs/stateless/myNames.spec.ts +++ b/e2e/specs/stateless/myNames.spec.ts @@ -1,5 +1,12 @@ import { expect } from '@playwright/test' -import { testClient } from '@root/playwright/fixtures/contracts/utils/addTestContracts' +import { createAccounts } from '@root/playwright/fixtures/accounts' +import { + testClient, + walletClient, +} from '@root/playwright/fixtures/contracts/utils/addTestContracts' +import { Address, labelhash } from 'viem' + +import { deleteSubname } from '@ensdomains/ensjs/wallet' import { test } from '../../../playwright' import { Name } from '../../../playwright/fixtures/makeName' @@ -18,7 +25,6 @@ test('myNames', async ({ page, login, makeName }) => { await page.goto('/') await login.connect('user2') - await page.pause() await page.goto('/my/names') @@ -32,6 +38,229 @@ test('myNames', async ({ page, login, makeName }) => { ) expect(timestamps.every((timestamp) => timestamp === timestamps[0])).toBe(true) +}) + +test.describe.serial('myNames', () => { + test.beforeAll(async ({ subgraph }) => { + // Move time to the future to force previous names to expire + await testClient.increaseTime({ seconds: 2 * 365 * 24 * 60 * 60 }) + await testClient.mine({ blocks: 1 }) + await subgraph.sync() + }) + + let subnamesToDelete: string[] = [] + let allNames: string[] = [] + + test.afterAll(async () => { + console.log('cleaning up subnames') + const account = createAccounts().getAddress('user4') as Address + for (const subname of subnamesToDelete) { + const contract = subname.includes('wrapped') ? 'nameWrapper' : 'registry' + console.log('deleting subname:', subname, 'on', contract) + // eslint-disable-next-line no-await-in-loop + await deleteSubname(walletClient, { + name: subname, + account, + contract, + }) + } + }) + + const makeSubnamesConfig = (type: 'legacy' | 'wrapped') => + Array.from( + { length: 10 }, + (_, i) => + ({ + label: `sub${i}`, + owner: 'user4', + type, + ...(type === 'wrapped' + ? { + fuses: { + parent: { + named: ['PARENT_CANNOT_CONTROL'], + }, + }, + } + : {}), + }) as any, + ) + + test('should display all names for expiry date ASC', async ({ page, login, makeName }) => { + const earlierName = await makeName({ + label: 'earlier-wrapped', + type: 'wrapped', + owner: 'user4', + fuses: { + named: ['CANNOT_UNWRAP'], + }, + subnames: makeSubnamesConfig('wrapped'), + }) + const concurrentNames = await makeName([ + { + label: `concurrent-legacy`, + type: 'legacy', + owner: 'user4', + subnames: makeSubnamesConfig('legacy'), + } as Name, + { + label: `concurrent-wrapped`, + type: 'wrapped', + owner: 'user4', + fuses: { + named: ['CANNOT_UNWRAP'], + }, + subnames: makeSubnamesConfig('wrapped'), + }, + ]) + const laterName = await makeName({ + label: 'later-legacy-name', + type: 'legacy', + owner: 'user4', + subnames: makeSubnamesConfig('legacy'), + }) + + subnamesToDelete = [earlierName, ...concurrentNames, laterName].flatMap((name) => + Array.from({ length: 10 }, (_, i) => `sub${i}.${name}`), + ) + allNames = [earlierName, ...concurrentNames, laterName, ...subnamesToDelete] + + await page.goto('/') + await login.connect('user4') + await page.goto('/my/names') + + await expect(page.getByTestId('names-list')).toBeVisible({ timeout: 10000 }) + + await page.evaluate(async () => { + let previousScrollHeight = 0 + let { scrollHeight } = document.body + do { + window.scrollTo(0, scrollHeight) + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(resolve, 1000) + }) + previousScrollHeight = scrollHeight + scrollHeight = document.body.scrollHeight + } while (previousScrollHeight !== scrollHeight) + }) + + for (const name of allNames) { + const decryptedLocator = page.getByTestId(`name-item-${name}`) + const nameParts = name.split('.') + const label = nameParts.shift()! + const labelHash = `[${labelhash(label).replace('0x', '')}]` + const encryptedLocator = page.getByTestId(`name-item-${[labelHash, ...nameParts].join('.')}`) + // eslint-disable-next-line no-await-in-loop + await expect(decryptedLocator.or(encryptedLocator)).toBeVisible() + } + }) + + test('should display all names for expiry date DESC', async ({ page, login }) => { + await page.goto('/') + await login.connect('user4') + await page.goto('/my/names') + + await expect(page.getByTestId('names-list')).toBeVisible({ timeout: 10000 }) + + await page.getByTestId('sort-desc').click() + await page.waitForTimeout(1000) + + await page.evaluate(async () => { + let previousScrollHeight = 0 + let { scrollHeight } = document.body + do { + window.scrollTo(0, scrollHeight) + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(resolve, 1000) + }) + previousScrollHeight = scrollHeight + scrollHeight = document.body.scrollHeight + } while (previousScrollHeight !== scrollHeight) + }) + + for (const name of allNames) { + const decryptedLocator = page.getByTestId(`name-item-${name}`) + const nameParts = name.split('.') + const label = nameParts.shift()! + const labelHash = `[${labelhash(label).replace('0x', '')}]` + const encryptedLocator = page.getByTestId(`name-item-${[labelHash, ...nameParts].join('.')}`) + // eslint-disable-next-line no-await-in-loop + await expect(decryptedLocator.or(encryptedLocator)).toBeVisible() + } + }) + + test('should display all names for createdAt ASC', async ({ page, login }) => { + await page.goto('/') + await login.connect('user4') + await page.goto('/my/names') + + await expect(page.getByTestId('names-list')).toBeVisible({ timeout: 10000 }) + + await page.getByTestId('select-container').getByRole('button').click() + await page.getByTestId('select-option-createdAt').click() + await page.waitForTimeout(1000) + + await page.evaluate(async () => { + let previousScrollHeight = 0 + let { scrollHeight } = document.body + do { + window.scrollTo(0, scrollHeight) + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(resolve, 1000) + }) + previousScrollHeight = scrollHeight + scrollHeight = document.body.scrollHeight + } while (previousScrollHeight !== scrollHeight) + }) + + for (const name of allNames) { + const decryptedLocator = page.getByTestId(`name-item-${name}`) + const nameParts = name.split('.') + const label = nameParts.shift()! + const labelHash = `[${labelhash(label).replace('0x', '')}]` + const encryptedLocator = page.getByTestId(`name-item-${[labelHash, ...nameParts].join('.')}`) + // eslint-disable-next-line no-await-in-loop + await expect(decryptedLocator.or(encryptedLocator)).toBeVisible() + } + }) + + test('should display all names for createdAt DESC', async ({ page, login }) => { + await page.goto('/') + await login.connect('user4') + await page.goto('/my/names') + + await expect(page.getByTestId('names-list')).toBeVisible({ timeout: 10000 }) + + await page.getByTestId('select-container').getByRole('button').click() + await page.getByTestId('select-option-createdAt').click() + await page.getByTestId('sort-desc').click() + await page.waitForTimeout(1000) + + await page.evaluate(async () => { + let previousScrollHeight = 0 + let { scrollHeight } = document.body + do { + window.scrollTo(0, scrollHeight) + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(resolve, 1000) + }) + previousScrollHeight = scrollHeight + scrollHeight = document.body.scrollHeight + } while (previousScrollHeight !== scrollHeight) + }) - await page.pause() + for (const name of allNames) { + const decryptedLocator = page.getByTestId(`name-item-${name}`) + const nameParts = name.split('.') + const label = nameParts.shift()! + const labelHash = `[${labelhash(label).replace('0x', '')}]` + const encryptedLocator = page.getByTestId(`name-item-${[labelHash, ...nameParts].join('.')}`) + // eslint-disable-next-line no-await-in-loop + await expect(decryptedLocator.or(encryptedLocator)).toBeVisible() + } + }) }) diff --git a/package.json b/package.json index 17cb30b03..75aaaac08 100644 --- a/package.json +++ b/package.json @@ -51,10 +51,11 @@ "knip:fix": "knip --fix --allow-remove-files" }, "dependencies": { + "@adraffy/ens-normalize": "1.10.1", "@ensdomains/address-encoder": "1.1.1", "@ensdomains/content-hash": "^3.0.0-beta.5", "@ensdomains/ens-contracts": "1.2.0-beta.0", - "@ensdomains/ensjs": "4.0.0", + "@ensdomains/ensjs": "4.0.2-alpha.5", "@ensdomains/thorin": "0.6.50", "@metamask/post-message-stream": "^6.1.2", "@metamask/providers": "^14.0.2", @@ -206,4 +207,4 @@ } }, "packageManager": "pnpm@9.3.0" -} \ No newline at end of file +} diff --git a/playwright/fixtures/accounts.ts b/playwright/fixtures/accounts.ts index ff1ea21ea..5aa446225 100644 --- a/playwright/fixtures/accounts.ts +++ b/playwright/fixtures/accounts.ts @@ -17,13 +17,12 @@ const shortenAddress = (address = '', maxLength = 10, leftSlice = 5, rightSlice export type Accounts = ReturnType -export type User = 'user' | 'user2' | 'user3' +const users = ['user', 'user2', 'user3', 'user4'] as const +export type User = typeof users[number] export const createAccounts = (stateful = false) => { const mnemonic = stateful ? process.env.SECRET_WORDS || DEFAULT_MNEMONIC : DEFAULT_MNEMONIC - const users: User[] = ['user', 'user2', 'user3'] - const { accounts, privateKeys } = users.reduce<{ accounts: Account[]; privateKeys: Hash[] }>( (acc, _, index) => { const { getHdKey } = mnemonicToAccount(mnemonic, { addressIndex: index }) diff --git a/playwright/fixtures/makeName/generators/legacyNameGenerator.ts b/playwright/fixtures/makeName/generators/legacyNameGenerator.ts index 9aaa527b8..4d8c65172 100644 --- a/playwright/fixtures/makeName/generators/legacyNameGenerator.ts +++ b/playwright/fixtures/makeName/generators/legacyNameGenerator.ts @@ -101,16 +101,15 @@ export const makeLegacyNameGenerator = ({ accounts }: Dependencies) => ({ configure: async (nameConfig: LegacyName) => { const { label, owner, manager, subnames = [], secret } = nameWithDefaults(nameConfig) const name = `${label}.eth` + // Create subnames - await Promise.all( - subnames.map((subname) => { - return generateLegacySubname({ accounts })({ - ...subname, - name: `${label}.eth`, - nameOwner: owner, - }) - }), - ) + for (const subname of subnames) { + await generateLegacySubname({ accounts })({ + ...subname, + name: `${label}.eth`, + nameOwner: owner, + }) + } if (!!manager && manager !== owner) { console.log('setting manager:', name, manager) diff --git a/playwright/fixtures/makeName/generators/legacyWithConfigNameGenerator.ts b/playwright/fixtures/makeName/generators/legacyWithConfigNameGenerator.ts index 0d58f8c49..65158fd2e 100644 --- a/playwright/fixtures/makeName/generators/legacyWithConfigNameGenerator.ts +++ b/playwright/fixtures/makeName/generators/legacyWithConfigNameGenerator.ts @@ -130,16 +130,14 @@ export const makeLegacyWithConfigNameGenerator = ({ accounts }: Dependencies) => await generateRecords({ accounts })({ name: `${label}.eth`, owner, resolver, records }) // Create subnames - await Promise.all( - subnames.map((subname) => - generateLegacySubname({ accounts })({ - ...subname, - name, - nameOwner: owner, - resolver: subname.resolver ?? _resolver, - }), - ), - ) + for (const subname of subnames) { + await generateLegacySubname({ accounts })({ + ...subname, + name, + nameOwner: owner, + resolver: subname.resolver ?? _resolver, + }) + } // Set resolver if not valid if (!hasValidResolver && resolver) { diff --git a/playwright/fixtures/makeName/generators/wrappedNameGenerator.ts b/playwright/fixtures/makeName/generators/wrappedNameGenerator.ts index 0d3dbd865..0d9ff76ab 100644 --- a/playwright/fixtures/makeName/generators/wrappedNameGenerator.ts +++ b/playwright/fixtures/makeName/generators/wrappedNameGenerator.ts @@ -159,16 +159,14 @@ export const makeWrappedNameGenerator = ({ accounts }: Dependencies) => ({ }) } - await Promise.all( - subnames.map((subname) => - generateWrappedSubname({ accounts })({ + for (const subname of subnames) { + await generateWrappedSubname({ accounts })({ ...subname, name: `${label}.eth`, nameOwner: owner, resolver: subname.resolver ?? _resolver, - }), - ), - ) + }) + } if (!hasValidResolver && resolver) { console.log('setting resolver: ', name, resolver) diff --git a/playwright/fixtures/makeName/index.ts b/playwright/fixtures/makeName/index.ts index 3bcd6dbcd..9e3030b75 100644 --- a/playwright/fixtures/makeName/index.ts +++ b/playwright/fixtures/makeName/index.ts @@ -91,18 +91,21 @@ export function createMakeNames({ accounts, time, subgraph }: Dependencies) { await testClient.setAutomine(true) - // Finish setting up names - await Promise.all( - adjustedNames.map((name) => { + // Make sure that registration and subnames are on different block or it might cause the subgraph to crash due to + // RegisterName and TransferName event having the same event ids. + await testClient.mine({ blocks: 1 }) + + // Finish setting up names + for (const name of adjustedNames) { if (isWrappendName(name)) { - return wrappedNameGenerator.configure(name) + await wrappedNameGenerator.configure(name) } else if (isLegacyName(name)) { - return legacyRegisterNameGenerator.configure(name) + console.log('registering legacy name:', name) + await legacyRegisterNameGenerator.configure(name) } else { - return legacyNameGenerator.configure(name) + await legacyNameGenerator.configure(name) } - }), - ) + } if (offset > 0) { console.warn('You are increasing the block timestamp. Do not run this test in parallel mode.') diff --git a/playwright/fixtures/subgraph.ts b/playwright/fixtures/subgraph.ts index 0fda0a1ff..78867ab69 100644 --- a/playwright/fixtures/subgraph.ts +++ b/playwright/fixtures/subgraph.ts @@ -23,17 +23,20 @@ const query = gql` export const waitForSubgraph = () => async () => { const blockNumber = await getBlockNumber(publicClient) - - let wait = true - let count = 0 + const anvilBlockNumbers: number[] = [] do { await new Promise((resolve) => setTimeout(resolve, 500)) const client = new GraphQLClient('http://localhost:8000/subgraphs/name/graphprotocol/ens') const res = await client.request(query) - wait = blockNumber > res._meta.block.number - count += 1 - console.log(`subgraph: ${res._meta.block.number} -> ${blockNumber} ${!wait ? '[IN SYNC]' : ''}`) - } while (wait && count < 10) + + anvilBlockNumbers.push(res._meta.block.number) + if (anvilBlockNumbers.length > 10) anvilBlockNumbers.shift() + + const finished = res._meta.block.number >= blockNumber + console.log(`subgraph: ${res._meta.block.number} -> ${blockNumber} ${finished ? '[IN SYNC]' : ''}`) + + if (anvilBlockNumbers.length >= 10 && anvilBlockNumbers.every((blockNumb) => blockNumb === anvilBlockNumbers[0])) throw new Error('Subgraph not in sync') + } while (anvilBlockNumbers[anvilBlockNumbers.length - 1] < blockNumber) } export const createSubgraph = () => ({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96fc7eb4e..13a68c7a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: .: dependencies: + '@adraffy/ens-normalize': + specifier: 1.10.1 + version: 1.10.1 '@ensdomains/address-encoder': specifier: 1.1.1 version: 1.1.1 @@ -36,8 +39,8 @@ importers: specifier: 1.2.0-beta.0 version: 1.2.0-beta.0 '@ensdomains/ensjs': - specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + specifier: 4.0.2-alpha.5 + version: 4.0.2-alpha.5(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) '@ensdomains/thorin': specifier: 0.6.50 version: 0.6.50(react-dom@18.3.1(react@18.3.1))(react-transition-state@1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)) @@ -421,6 +424,48 @@ importers: specifier: ^1.0.0-pre.53 version: 1.0.0-pre.53 + .yalc/@ensdomains/ens-test-env: + dependencies: + '@ethersproject/wallet': + specifier: ^5.6.0 + version: 5.7.0 + ansi-colors: + specifier: ^4.1.1 + version: 4.1.3 + cli-progress: + specifier: ^3.10.0 + version: 3.12.0 + commander: + specifier: ^9.3.0 + version: 9.5.0 + concurrently: + specifier: ^7.1.0 + version: 7.6.0 + docker-compose: + specifier: ^0.24.7 + version: 0.24.8 + dotenv: + specifier: ^16.0.1 + version: 16.4.5 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 + lz4: + specifier: ^0.6.5 + version: 0.6.5 + progress-stream: + specifier: ^2.0.0 + version: 2.0.0 + tar-fs: + specifier: ^2.1.1 + version: 2.1.1 + viem: + specifier: ^2.21.37 + version: 2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + wait-on: + specifier: ^6.0.1 + version: 6.0.1 + packages: '@adobe/css-tools@4.3.3': @@ -432,6 +477,9 @@ packages: '@adraffy/ens-normalize@1.10.1': resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} + '@adraffy/ens-normalize@1.11.0': + resolution: {integrity: sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -1556,8 +1604,8 @@ packages: '@ensdomains/ensjs@2.1.0': resolution: {integrity: sha512-GRbGPT8Z/OJMDuxs75U/jUNEC0tbL0aj7/L/QQznGYKm/tiasp+ndLOaoULy9kKJFC0TBByqfFliEHDgoLhyog==} - '@ensdomains/ensjs@4.0.0': - resolution: {integrity: sha512-iI6ieuP0TeSK46JCP21EGxyup5rPE5rMmDMTrpRs+u3iwk42Bx3e4oG5sEtTRmxnXFO9uaSqk+WSXEMcHyPKxQ==} + '@ensdomains/ensjs@4.0.2-alpha.5': + resolution: {integrity: sha512-xDlnEg+JbZMs+vV5C3v0ks8nBrJciwVD9gYeFH5TzDpSM1zcGxb4KO4vvv0F+f8UI0aZFLLldZ7xyWfn4egjAw==} peerDependencies: viem: ^2.9.2 @@ -2324,6 +2372,10 @@ packages: '@noble/curves@1.4.0': resolution: {integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==} + '@noble/curves@1.6.0': + resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} + engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.2.0': resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} @@ -2335,6 +2387,10 @@ packages: resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} + '@noble/hashes@1.5.0': + resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + engines: {node: ^14.21.3 || >=16} + '@noble/secp256k1@1.7.1': resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} @@ -2813,6 +2869,9 @@ packages: '@scure/base@1.1.6': resolution: {integrity: sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==} + '@scure/base@1.1.9': + resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + '@scure/bip32@1.1.5': resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} @@ -2822,6 +2881,9 @@ packages: '@scure/bip32@1.4.0': resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + '@scure/bip32@1.5.0': + resolution: {integrity: sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==} + '@scure/bip39@1.1.1': resolution: {integrity: sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==} @@ -2831,6 +2893,9 @@ packages: '@scure/bip39@1.3.0': resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + '@scure/bip39@1.4.0': + resolution: {integrity: sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==} + '@sentry/browser@7.43.0': resolution: {integrity: sha512-NlRkBYKb9o5IQdGY8Ktps19Hz9RdSuqS1tlLC7Sjr+MqZqSHmhKq8MWJKciRynxBeMbeGt0smExi9BqpVQdCEg==} engines: {node: '>=8'} @@ -3756,6 +3821,17 @@ packages: zod: optional: true + abitype@1.0.6: + resolution: {integrity: sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==} + peerDependencies: + typescript: '>=5.0.4' + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -6535,6 +6611,11 @@ packages: peerDependencies: ws: '*' + isows@1.0.6: + resolution: {integrity: sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==} + peerDependencies: + ws: '*' + isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} @@ -9329,6 +9410,9 @@ packages: ts-pattern@4.3.0: resolution: {integrity: sha512-pefrkcd4lmIVR0LA49Imjf9DYLK8vtWhqBPA3Ya1ir8xCW0O2yjL9dsCVvI7pCodLC5q7smNpEtDR2yVulQxOg==} + ts-pattern@5.5.0: + resolution: {integrity: sha512-jqbIpTsa/KKTJYWgPNsFNbLVpwCgzXfFJ1ukNn4I8hMwyQzHMJnk/BqWzggB0xpkILuKzaO/aMYhS0SkaJyKXg==} + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -9694,6 +9778,14 @@ packages: typescript: optional: true + viem@2.21.40: + resolution: {integrity: sha512-no/mE3l7B0mdUTtvO7z/cTLENttQ/M7+ombqFGXJqsQrxv9wrYsTIGpS3za+FA5a447hY+x9D8Wxny84q1zAaA==} + peerDependencies: + typescript: '>=5.0.4' + peerDependenciesMeta: + typescript: + optional: true + vite-node@2.0.5: resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} engines: {node: ^18.0.0 || >=20.0.0} @@ -10000,6 +10092,9 @@ packages: resolution: {integrity: sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA==} engines: {node: '>=8.0.0'} + webauthn-p256@0.0.10: + resolution: {integrity: sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA==} + webauthn-p256@0.0.5: resolution: {integrity: sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==} @@ -10243,6 +10338,18 @@ packages: utf-8-validate: optional: true + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xhr-request-promise@0.1.3: resolution: {integrity: sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==} @@ -10411,6 +10518,8 @@ snapshots: '@adraffy/ens-normalize@1.10.1': {} + '@adraffy/ens-normalize@1.11.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -11791,7 +11900,7 @@ snapshots: '@ensdomains/ensjs@2.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.25.0 '@ensdomains/address-encoder': 0.1.9 '@ensdomains/ens': 0.4.5 '@ensdomains/resolver': 0.2.4 @@ -11803,7 +11912,7 @@ snapshots: - bufferutil - utf-8-validate - '@ensdomains/ensjs@4.0.0(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)': + '@ensdomains/ensjs@4.0.2-alpha.5(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)': dependencies: '@adraffy/ens-normalize': 1.10.1 '@ensdomains/address-encoder': 1.1.1 @@ -11814,6 +11923,7 @@ snapshots: graphql: 16.8.1 graphql-request: 6.1.0(encoding@0.1.13)(graphql@16.8.1) pako: 2.1.0 + ts-pattern: 5.5.0 viem: 2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - encoding @@ -12674,7 +12784,7 @@ snapshots: '@motionone/easing': 10.17.0 '@motionone/types': 10.17.0 '@motionone/utils': 10.17.0 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/dom@10.17.0': dependencies: @@ -12683,12 +12793,12 @@ snapshots: '@motionone/types': 10.17.0 '@motionone/utils': 10.17.0 hey-listen: 1.0.8 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/easing@10.17.0': dependencies: '@motionone/utils': 10.17.0 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/generators@10.17.0': dependencies: @@ -12699,7 +12809,7 @@ snapshots: '@motionone/svelte@10.16.4': dependencies: '@motionone/dom': 10.17.0 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/types@10.17.0': {} @@ -12707,12 +12817,12 @@ snapshots: dependencies: '@motionone/types': 10.17.0 hey-listen: 1.0.8 - tslib: 2.6.2 + tslib: 2.6.3 '@motionone/vue@10.16.4': dependencies: '@motionone/dom': 10.17.0 - tslib: 2.6.2 + tslib: 2.6.3 '@mswjs/cookies@0.2.2': dependencies: @@ -12785,12 +12895,18 @@ snapshots: dependencies: '@noble/hashes': 1.4.0 + '@noble/curves@1.6.0': + dependencies: + '@noble/hashes': 1.5.0 + '@noble/hashes@1.2.0': {} '@noble/hashes@1.3.3': {} '@noble/hashes@1.4.0': {} + '@noble/hashes@1.5.0': {} + '@noble/secp256k1@1.7.1': {} '@nodelib/fs.scandir@2.1.5': @@ -13403,7 +13519,7 @@ snapshots: '@safe-global/safe-apps-sdk@9.1.0(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8)': dependencies: '@safe-global/safe-gateway-typescript-sdk': 3.21.1 - viem: 2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + viem: 2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) transitivePeerDependencies: - bufferutil - typescript @@ -13414,6 +13530,8 @@ snapshots: '@scure/base@1.1.6': {} + '@scure/base@1.1.9': {} + '@scure/bip32@1.1.5': dependencies: '@noble/hashes': 1.2.0 @@ -13432,6 +13550,12 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.6 + '@scure/bip32@1.5.0': + dependencies: + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/base': 1.1.9 + '@scure/bip39@1.1.1': dependencies: '@noble/hashes': 1.2.0 @@ -13447,6 +13571,11 @@ snapshots: '@noble/hashes': 1.4.0 '@scure/base': 1.1.6 + '@scure/bip39@1.4.0': + dependencies: + '@noble/hashes': 1.5.0 + '@scure/base': 1.1.9 + '@sentry/browser@7.43.0': dependencies: '@sentry/core': 7.43.0 @@ -13804,7 +13933,7 @@ snapshots: '@swc/helpers@0.5.2': dependencies: - tslib: 2.6.2 + tslib: 2.6.3 '@szmarczak/http-timer@4.0.6': dependencies: @@ -14935,6 +15064,11 @@ snapshots: typescript: 5.4.5 zod: 3.23.8 + abitype@1.0.6(typescript@5.4.5)(zod@3.23.8): + optionalDependencies: + typescript: 5.4.5 + zod: 3.23.8 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -15542,7 +15676,7 @@ snapshots: capnp-ts@0.7.0: dependencies: debug: 4.3.6 - tslib: 2.6.2 + tslib: 2.6.3 transitivePeerDependencies: - supports-color @@ -16306,7 +16440,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.6.3 dotenv@16.4.5: {} @@ -18317,6 +18451,10 @@ snapshots: dependencies: ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + isows@1.0.6(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)): + dependencies: + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + isstream@0.1.2: {} istanbul-lib-coverage@3.2.2: {} @@ -18896,7 +19034,7 @@ snapshots: media-query-parser@2.0.2: dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.25.0 media-typer@0.3.0: {} @@ -19449,7 +19587,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.2 + tslib: 2.6.3 nocache@3.0.4: {} @@ -20294,7 +20432,7 @@ snapshots: dependencies: react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.2.21)(react@18.3.1) - tslib: 2.6.2 + tslib: 2.6.3 optionalDependencies: '@types/react': 18.2.21 @@ -20320,7 +20458,7 @@ snapshots: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 - tslib: 2.6.2 + tslib: 2.6.3 optionalDependencies: '@types/react': 18.2.21 @@ -20466,7 +20604,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.25.0 regexp.prototype.flags@1.5.2: dependencies: @@ -20642,7 +20780,7 @@ snapshots: rtl-css-js@1.16.1: dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.25.0 run-async@2.4.1: {} @@ -20907,7 +21045,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.6.3 socket.io-client@4.7.5(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: @@ -21608,6 +21746,8 @@ snapshots: ts-pattern@4.3.0: {} + ts-pattern@5.5.0: {} + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -21837,7 +21977,7 @@ snapshots: use-callback-ref@1.3.2(@types/react@18.2.21)(react@18.3.1): dependencies: react: 18.3.1 - tslib: 2.6.2 + tslib: 2.6.3 optionalDependencies: '@types/react': 18.2.21 @@ -21850,7 +21990,7 @@ snapshots: dependencies: detect-node-es: 1.1.0 react: 18.3.1 - tslib: 2.6.2 + tslib: 2.6.3 optionalDependencies: '@types/react': 18.2.21 @@ -21929,6 +22069,24 @@ snapshots: - utf-8-validate - zod + viem@2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8): + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/bip32': 1.5.0 + '@scure/bip39': 1.4.0 + abitype: 1.0.6(typescript@5.4.5)(zod@3.23.8) + isows: 1.0.6(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + webauthn-p256: 0.0.10 + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + vite-node@2.0.5(@types/node@18.19.33)(terser@5.31.5): dependencies: cac: 6.7.14 @@ -22521,6 +22679,11 @@ snapshots: - supports-color - utf-8-validate + webauthn-p256@0.0.10: + dependencies: + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + webauthn-p256@0.0.5: dependencies: '@noble/curves': 1.4.0 @@ -22792,6 +22955,11 @@ snapshots: bufferutil: 4.0.8 utf-8-validate: 5.0.10 + ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 5.0.10 + xhr-request-promise@0.1.3: dependencies: xhr-request: 1.1.0 diff --git a/src/components/@molecules/NameTableHeader/NameTableHeader.tsx b/src/components/@molecules/NameTableHeader/NameTableHeader.tsx index 1c4479a40..141416219 100644 --- a/src/components/@molecules/NameTableHeader/NameTableHeader.tsx +++ b/src/components/@molecules/NameTableHeader/NameTableHeader.tsx @@ -189,12 +189,14 @@ export const NameTableHeader = ({ /> onSortDirectionChange?.('asc')} > onSortDirectionChange?.('desc')} > From 270c253e7fc7af7889d6393239a8c0e9d61e06e6 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 6 Nov 2024 00:11:11 +0200 Subject: [PATCH 21/37] page rename --- src/pages/{migrate.tsx => ens-v2.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/pages/{migrate.tsx => ens-v2.tsx} (100%) diff --git a/src/pages/migrate.tsx b/src/pages/ens-v2.tsx similarity index 100% rename from src/pages/migrate.tsx rename to src/pages/ens-v2.tsx From 3a86d2081a60f8e981eb8885992d6a85c41bcd2e Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 6 Nov 2024 00:39:03 +0200 Subject: [PATCH 22/37] update --- public/locales/en/ensvs2.json | 2 +- src/pages/ens-v2.tsx | 75 +++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/public/locales/en/ensvs2.json b/public/locales/en/ensvs2.json index 14a15d823..a9df3484c 100644 --- a/public/locales/en/ensvs2.json +++ b/public/locales/en/ensvs2.json @@ -1,6 +1,6 @@ { "title": "ENSv2: The Next Generation of ENS", - "caption": "Approve your ENS names for ENSv2 now and enjoy automatic migration, paid for by ENS DAO.", + "caption": "After seven years at the frontier of web3 naming, we're re-envisioning ENS on L2.", "accessible": { "title": "Making ENS accessible to more people", "caption": "We're taking our knowledge from the last 7 years at the frontier of web3 naming to re-envision the architecture from the ground up on L2 By utilizing L2s, we're excited to make ENS more accessible to a wider range of users.", diff --git a/src/pages/ens-v2.tsx b/src/pages/ens-v2.tsx index b6be85326..7cb839202 100644 --- a/src/pages/ens-v2.tsx +++ b/src/pages/ens-v2.tsx @@ -1,3 +1,4 @@ +/* stylelint-disable no-descending-specificity */ import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' @@ -57,6 +58,32 @@ const Main = styled.main( `, ) +const PartnershipAnnouncement = styled.div( + ({ theme }) => css` + width: ${theme.space.full}; + padding: ${theme.space['4']}; + background-color: ${theme.colors.backgroundPrimary}; + border-radius: ${theme.radii['4xLarge']}; + font-size: ${theme.fontSizes.body}; + font-weight: ${theme.fontWeights.bold}; + display: flex; + justify-content: space-between; + & > a { + color: ${theme.colors.greenDim}; + cursor: pointer; + display: flex; + align-items: center; + gap: ${theme.space['2']}; + } + & > a:hover { + color: ${theme.colors.green}; + } + @media (min-width: 640px) { + border-radius: ${theme.radii['3xLarge']}; + } + `, +) + const Footer = styled.div( ({ theme }) => css` display: flex; @@ -65,17 +92,21 @@ const Footer = styled.div( h3 { text-align: center; } - span { + + & > div { + display: grid; + grid-template-columns: repeat(1, 1fr); + gap: ${theme.space['4']}; + } + & > div a { display: flex; flex-direction: row; align-items: center; gap: ${theme.space['2']}; color: ${theme.colors.green}; } - & > div { - display: grid; - grid-template-columns: repeat(1, 1fr); - gap: ${theme.space['4']}; + & > div a:hover { + color: ${theme.colors.greenDim}; } & > div > div { width: 100%; @@ -105,32 +136,6 @@ const AnnouncementBanner = styled.div( `, ) -const PartnershipAnnouncement = styled.div( - ({ theme }) => css` - width: ${theme.space.full}; - padding: ${theme.space['4']}; - background-color: ${theme.colors.backgroundPrimary}; - border-radius: ${theme.radii['4xLarge']}; - font-size: ${theme.fontSizes.body}; - font-weight: ${theme.fontWeights.bold}; - display: flex; - justify-content: space-between; - & > a { - color: ${theme.colors.greenDim}; - cursor: pointer; - display: flex; - align-items: center; - gap: ${theme.space['2']}; - } - & > a:hover { - color: ${theme.colors.green}; - } - @media (min-width: 640px) { - border-radius: ${theme.radii['3xLarge']}; - } - `, -) - const TopNav = styled.div( ({ theme }) => css` display: flex; @@ -302,12 +307,12 @@ export default function ENSv2() { {t('footer.support.ticket')} - + {t('footer.support.twitter')} - - + + {t('footer.support.dao')} - + From 114a262e6e417ddd3b5749f0ada54fbadd08ae05 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Thu, 7 Nov 2024 17:59:08 +0200 Subject: [PATCH 23/37] add banner --- public/locales/en/{ensvs2.json => ensv2.json} | 5 +++ src/components/pages/AnnouncementBanner.tsx | 45 +++++++++++++++++++ src/pages/ens-v2.tsx | 2 +- src/pages/index.tsx | 3 ++ 4 files changed, 54 insertions(+), 1 deletion(-) rename public/locales/en/{ensvs2.json => ensv2.json} (93%) create mode 100644 src/components/pages/AnnouncementBanner.tsx diff --git a/public/locales/en/ensvs2.json b/public/locales/en/ensv2.json similarity index 93% rename from public/locales/en/ensvs2.json rename to public/locales/en/ensv2.json index a9df3484c..f0ec9019e 100644 --- a/public/locales/en/ensvs2.json +++ b/public/locales/en/ensv2.json @@ -47,5 +47,10 @@ "twitter": "X (Twitter)", "dao": "DAO forums" } + }, + "banner": { + "title": "Namechain is coming!", + "caption": "Keep up with ENSv2 development", + "cta": "ENSv2 hub" } } \ No newline at end of file diff --git a/src/components/pages/AnnouncementBanner.tsx b/src/components/pages/AnnouncementBanner.tsx new file mode 100644 index 000000000..f36fd18c9 --- /dev/null +++ b/src/components/pages/AnnouncementBanner.tsx @@ -0,0 +1,45 @@ +import Link from 'next/link' +import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' + +import { Button, Typography } from '@ensdomains/thorin' + +const Container = styled(Link)( + ({ theme }) => css` + display: flex; + flex-direction: row; + width: ${theme.space.full}; + max-width: 466px; + padding: ${theme.space['4']}; + align-items: center; + gap: ${theme.space['4']}; + align-self: center; + border-radius: ${theme.radii['2xLarge']}; + background-color: ${theme.colors.backgroundPrimary}; + `, +) + +const Text = styled.span( + ({ theme }) => css` + width: ${theme.space.full}; + `, +) + +export const AnnouncementBanner = () => { + const { t } = useTranslation('ensv2') + + return ( + + + + {t('banner.title')} + + {t('banner.caption')} + + + + + ) +} diff --git a/src/pages/ens-v2.tsx b/src/pages/ens-v2.tsx index 7cb839202..c5390aead 100644 --- a/src/pages/ens-v2.tsx +++ b/src/pages/ens-v2.tsx @@ -217,7 +217,7 @@ const Video = styled.video( ) export default function ENSv2() { - const { t } = useTranslation('ensvs2') + const { t } = useTranslation('ensv2') return (
diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 77fccae3c..2df5b1ac1 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -8,6 +8,7 @@ import FaucetBanner from '@app/components/@molecules/FaucetBanner' import Hamburger from '@app/components/@molecules/Hamburger/Hamburger' import { SearchInput } from '@app/components/@molecules/SearchInput/SearchInput' import { LeadingHeading } from '@app/components/LeadingHeading' +import { AnnouncementBanner } from '@app/components/pages/AnnouncementBanner' import { VerificationErrorDialog } from '@app/components/pages/VerificationErrorDialog' import { useVerificationOAuthHandler } from '@app/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler' @@ -114,6 +115,8 @@ export default function Page() { + + From bd2685fed961129b6225ad17364980dc06ccf3bf Mon Sep 17 00:00:00 2001 From: v1rtl Date: Fri, 8 Nov 2024 17:06:37 +0200 Subject: [PATCH 24/37] qa --- public/locales/en/ensv2.json | 9 ++++++-- src/pages/ens-v2.tsx | 42 +++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/public/locales/en/ensv2.json b/public/locales/en/ensv2.json index f0ec9019e..6064ec0a5 100644 --- a/public/locales/en/ensv2.json +++ b/public/locales/en/ensv2.json @@ -1,6 +1,6 @@ { - "title": "ENSv2: The Next Generation of ENS", - "caption": "After seven years at the frontier of web3 naming, we're re-envisioning ENS on L2.", + "title": "Namechain is a Layer 2 designed for onchain identity", + "caption": "Built for everyone tired of addresses, numbers, and complexity. Namechain lets users and developers create onchain identities through the power of names, not numbers.", "accessible": { "title": "Making ENS accessible to more people", "caption": "We're taking our knowledge from the last 7 years at the frontier of web3 naming to re-envision the architecture from the ground up on L2 By utilizing L2s, we're excited to make ENS more accessible to a wider range of users.", @@ -18,6 +18,11 @@ "text": "Layer 2 enables seamless .eth name use across blockchains with trustless connections." } }, + "learn-more": { + "title": "Want to learn more?", + "caption": "If you’re interested in learning more and building on Namechain, join the ENS Developer Telegram.", + "button": "Developer Telegram" + }, "announcement": { "title": "Announcements", "l2": { diff --git a/src/pages/ens-v2.tsx b/src/pages/ens-v2.tsx index c5390aead..184fecce8 100644 --- a/src/pages/ens-v2.tsx +++ b/src/pages/ens-v2.tsx @@ -26,15 +26,11 @@ const Title = styled.h1` font-weight: 830; text-align: center; - font-size: 52px; + font-size: 36px; line-height: 104%; @media (min-width: 640px) { - font-size: 60px; - } - - @media (min-width: 1024px) { - font-size: 76px; + font-size: 52px; } ` @@ -46,7 +42,8 @@ const Header = styled.header( justify-content: center; gap: ${theme.space[4]}; padding: ${theme.space[4]}; - min-height: 530px; + min-height: 330px; + text-align: center; `, ) @@ -149,18 +146,15 @@ const TopNav = styled.div( `, ) -const CardWithEmoji = styled(Card)` +const CenteredCard = styled(Card)` display: flex; flex-direction: column; align-items: center; justify-content: center; - padding-top: 83px; - position: relative; +` + +const CardWithEmoji = styled(CenteredCard)` grid-column: 1 / -1; - & > img { - position: absolute; - top: -72px; - } ` const GridOneToThree = styled.div( @@ -212,7 +206,6 @@ const Video = styled.video( height: ${theme.space.full}; width: ${theme.space.full}; border-radius: ${theme.radii.card}; - margin-bottom: ${theme.space[18]}; `, ) @@ -230,14 +223,29 @@ export default function ENSv2() {
{t('title')} - {t('caption')} + {t('caption')}
+ + + {t('learn-more.title')} + + {t('learn-more.caption')} + + - 🎉 {t('accessible.title')} From a3ef7371db668fbc2555a50255dab0caa570f5f2 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sat, 9 Nov 2024 21:44:09 +0200 Subject: [PATCH 25/37] responsive banner --- pnpm-lock.yaml | 43 +-------------------- src/components/pages/AnnouncementBanner.tsx | 42 +++++++++++++++----- 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 193771135..feadf869c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -427,48 +427,6 @@ importers: specifier: ^1.0.0-pre.53 version: 1.0.0-pre.53 - .yalc/@ensdomains/ens-test-env: - dependencies: - '@ethersproject/wallet': - specifier: ^5.6.0 - version: 5.7.0 - ansi-colors: - specifier: ^4.1.1 - version: 4.1.3 - cli-progress: - specifier: ^3.10.0 - version: 3.12.0 - commander: - specifier: ^9.3.0 - version: 9.5.0 - concurrently: - specifier: ^7.1.0 - version: 7.6.0 - docker-compose: - specifier: ^0.24.7 - version: 0.24.8 - dotenv: - specifier: ^16.0.1 - version: 16.4.5 - js-yaml: - specifier: ^4.1.0 - version: 4.1.0 - lz4: - specifier: ^0.6.5 - version: 0.6.5 - progress-stream: - specifier: ^2.0.0 - version: 2.0.0 - tar-fs: - specifier: ^2.1.1 - version: 2.1.1 - viem: - specifier: ^2.21.37 - version: 2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) - wait-on: - specifier: ^6.0.1 - version: 6.0.1 - packages: '@adobe/css-tools@4.3.3': @@ -3863,6 +3821,7 @@ packages: acorn-import-assertions@1.9.0: resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes peerDependencies: acorn: ^8 diff --git a/src/components/pages/AnnouncementBanner.tsx b/src/components/pages/AnnouncementBanner.tsx index f36fd18c9..42815bf0e 100644 --- a/src/components/pages/AnnouncementBanner.tsx +++ b/src/components/pages/AnnouncementBanner.tsx @@ -7,15 +7,37 @@ import { Button, Typography } from '@ensdomains/thorin' const Container = styled(Link)( ({ theme }) => css` display: flex; - flex-direction: row; + flex-direction: column; width: ${theme.space.full}; max-width: 466px; padding: ${theme.space['4']}; align-items: center; + justify-content: space-between; gap: ${theme.space['4']}; align-self: center; border-radius: ${theme.radii['2xLarge']}; background-color: ${theme.colors.backgroundPrimary}; + + @media (max-width: 640px) { + a { + width: 100%; + } + } + + @media (min-width: 640px) { + flex-direction: row; + } + `, +) + +const TextContainer = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: row; + align-items: center; + gap: ${theme.space['4']}; + + width: ${theme.space.full}; `, ) @@ -30,14 +52,16 @@ export const AnnouncementBanner = () => { return ( - - - {t('banner.title')} - - {t('banner.caption')} - - - From c6d94c2a57839f573bd4e67e3bb19a989454cba7 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sat, 9 Nov 2024 21:53:01 +0200 Subject: [PATCH 26/37] qa --- src/pages/ens-v2.tsx | 57 ++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/pages/ens-v2.tsx b/src/pages/ens-v2.tsx index 184fecce8..8edcefeda 100644 --- a/src/pages/ens-v2.tsx +++ b/src/pages/ens-v2.tsx @@ -7,20 +7,20 @@ import { Button, Card, GasPumpSVG, - InfoCircleSVG, + // InfoCircleSVG, KeySVG, - QuestionBubbleSVG, - QuestionCircleSVG, - RightArrowSVG, - SpannerAltSVG, + // QuestionBubbleSVG, + // QuestionCircleSVG, + // RightArrowSVG, + // SpannerAltSVG, Typography, WalletSVG, } from '@ensdomains/thorin' import { Carousel } from '@app/components/pages/migrate/Carousel' -import DAOSVG from '../assets/DAO.svg' -import SocialX from '../assets/social/SocialX.svg' +// import DAOSVG from '../assets/DAO.svg' +// import SocialX from '../assets/social/SocialX.svg' const Title = styled.h1` font-weight: 830; @@ -55,7 +55,7 @@ const Main = styled.main( `, ) -const PartnershipAnnouncement = styled.div( +/* const PartnershipAnnouncement = styled.div( ({ theme }) => css` width: ${theme.space.full}; padding: ${theme.space['4']}; @@ -79,9 +79,9 @@ const PartnershipAnnouncement = styled.div( border-radius: ${theme.radii['3xLarge']}; } `, -) +) */ -const Footer = styled.div( +/* const Footer = styled.div( ({ theme }) => css` display: flex; flex-direction: column; @@ -116,7 +116,7 @@ const Footer = styled.div( } } `, -) +) */ const AnnouncementBanner = styled.div( ({ theme }) => css` @@ -133,7 +133,7 @@ const AnnouncementBanner = styled.div( `, ) -const TopNav = styled.div( +/* const TopNav = styled.div( ({ theme }) => css` display: flex; flex-direction: column; @@ -144,7 +144,7 @@ const TopNav = styled.div( left: 0; z-index: 1; `, -) +) */ const CenteredCard = styled(Card)` display: flex; @@ -182,9 +182,17 @@ const CardHeader = styled.h3( `, ) -const AnnouncementSlide = ({ title, text }: { title: string; text: string }) => ( +const AnnouncementSlide = ({ + title, + text, + href = '#', +}: { + title: string + text: string + href?: string +}) => ( - + {text} @@ -213,14 +221,14 @@ export default function ENSv2() { const { t } = useTranslation('ensv2') return (
- + {/* {t('partnership.text')} {t('partnership.watch')} - + */}
{t('title')} {t('caption')} @@ -250,7 +258,14 @@ export default function ENSv2() { {t('accessible.title')} {t('accessible.caption')} - @@ -288,14 +303,16 @@ export default function ENSv2() { -
+ {/*
{t('footer.title')} @@ -323,7 +340,7 @@ export default function ENSv2() { -
+
*/}
) } From 1f4358926c792783b325beee2e25ed4ca121ff1c Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Sun, 10 Nov 2024 23:17:05 +0700 Subject: [PATCH 27/37] Fix hydration and pre-load english translation --- src/components/pages/AnnouncementBanner.tsx | 32 +++++++++++---------- src/i18n.ts | 3 ++ src/pages/ens-v2.tsx | 4 +-- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/components/pages/AnnouncementBanner.tsx b/src/components/pages/AnnouncementBanner.tsx index 42815bf0e..d9e0c2a4d 100644 --- a/src/components/pages/AnnouncementBanner.tsx +++ b/src/components/pages/AnnouncementBanner.tsx @@ -4,7 +4,7 @@ import styled, { css } from 'styled-components' import { Button, Typography } from '@ensdomains/thorin' -const Container = styled(Link)( +const Container = styled.div( ({ theme }) => css` display: flex; flex-direction: column; @@ -51,19 +51,21 @@ export const AnnouncementBanner = () => { const { t } = useTranslation('ensv2') return ( - - - - - {t('banner.title')} - - {t('banner.caption')} - - - - - + + + + + + {t('banner.title')} + + {t('banner.caption')} + + + + + + ) } diff --git a/src/i18n.ts b/src/i18n.ts index 97889272a..ad7d9f964 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -6,6 +6,7 @@ import { initReactI18next } from 'react-i18next' import address from '../public/locales/en/address.json' import common from '../public/locales/en/common.json' import dnssec from '../public/locales/en/dnssec.json' +import ensv2 from '../public/locales/en/ensv2.json' import names from '../public/locales/en/names.json' import profile from '../public/locales/en/profile.json' import register from '../public/locales/en/register.json' @@ -33,6 +34,7 @@ i18n 'register', 'settings', 'transactionFlow', + 'ensv2', ], react: { useSuspense: false, @@ -48,5 +50,6 @@ i18n.addResourceBundle('en', 'profile', profile) i18n.addResourceBundle('en', 'register', register) i18n.addResourceBundle('en', 'settings', settings) i18n.addResourceBundle('en', 'transactionFlow', transactionFlow) +i18n.addResourceBundle('en', 'ensv2', ensv2) export default i18n diff --git a/src/pages/ens-v2.tsx b/src/pages/ens-v2.tsx index 8edcefeda..8914398ca 100644 --- a/src/pages/ens-v2.tsx +++ b/src/pages/ens-v2.tsx @@ -296,10 +296,10 @@ export default function ENSv2() { {t('announcement.title')} - + /> */} Date: Sun, 10 Nov 2024 23:59:02 +0700 Subject: [PATCH 28/37] Remove carousel for now --- src/pages/ens-v2.tsx | 58 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/pages/ens-v2.tsx b/src/pages/ens-v2.tsx index 8914398ca..3e1a7c50f 100644 --- a/src/pages/ens-v2.tsx +++ b/src/pages/ens-v2.tsx @@ -9,6 +9,7 @@ import { GasPumpSVG, // InfoCircleSVG, KeySVG, + mq, // QuestionBubbleSVG, // QuestionCircleSVG, // RightArrowSVG, @@ -17,8 +18,6 @@ import { WalletSVG, } from '@ensdomains/thorin' -import { Carousel } from '@app/components/pages/migrate/Carousel' - // import DAOSVG from '../assets/DAO.svg' // import SocialX from '../assets/social/SocialX.svg' @@ -120,7 +119,8 @@ const Main = styled.main( const AnnouncementBanner = styled.div( ({ theme }) => css` - height: ${theme.space.full}; + width: 312px; + height: 182px; text-align: center; & > a { height: ${theme.space.full}; @@ -182,7 +182,23 @@ const CardHeader = styled.h3( `, ) -const AnnouncementSlide = ({ +// const AnnouncementSlide = ({ +// title, +// text, +// href = '#', +// }: { +// title: string +// text: string +// href?: string +// }) => ( +// +// +// {text} +// +// +// ) + +const AnnouncementSlideTemp = ({ title, text, href = '#', @@ -198,6 +214,20 @@ const AnnouncementSlide = ({ ) +const AnnouncementContainer = styled.div( + ({ theme }) => css` + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: ${theme.space['4']}; + + ${mq.sm.min(css` + flex-direction: row; + `)} + `, +) + const SlideshowContainer = styled.div( ({ theme }) => css` display: flex; @@ -295,11 +325,11 @@ export default function ENSv2() { {t('announcement.title')} - - {/* + */} + /> - + */} + + + + {/*
From a260c4ba69137dae60c158348ad2452096a44ef9 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 11 Nov 2024 00:20:52 +0700 Subject: [PATCH 29/37] bump ensjs to 4.0.2 --- package.json | 2 +- pnpm-lock.yaml | 93 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index f724b4154..b11a69774 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@ensdomains/address-encoder": "1.1.1", "@ensdomains/content-hash": "^3.0.0-beta.5", "@ensdomains/ens-contracts": "1.2.0-beta.0", - "@ensdomains/ensjs": "4.0.2-alpha.5", + "@ensdomains/ensjs": "4.0.2", "@ensdomains/thorin": "0.6.50", "@metamask/post-message-stream": "^6.1.2", "@metamask/providers": "^14.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed228aad9..a62913ea3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,8 +39,8 @@ importers: specifier: 1.2.0-beta.0 version: 1.2.0-beta.0 '@ensdomains/ensjs': - specifier: 4.0.2-alpha.5 - version: 4.0.2-alpha.5(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + specifier: 4.0.2 + version: 4.0.2(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) '@ensdomains/thorin': specifier: 0.6.50 version: 0.6.50(react-dom@18.3.1(react@18.3.1))(react-transition-state@1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)) @@ -299,7 +299,7 @@ importers: version: 0.3.9 eslint-plugin-import: specifier: ^2.28.1 - version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + version: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: specifier: ^6.7.1 version: 6.8.0(eslint@8.50.0) @@ -469,6 +469,42 @@ importers: specifier: ^6.0.1 version: 6.0.1 + .yalc/@ensdomains/ensjs: + dependencies: + '@adraffy/ens-normalize': + specifier: 1.10.1 + version: 1.10.1 + '@ensdomains/address-encoder': + specifier: 1.1.1 + version: 1.1.1 + '@ensdomains/content-hash': + specifier: 3.1.0-rc.1 + version: 3.1.0-rc.1 + '@ensdomains/dnsprovejs': + specifier: ^0.5.1 + version: 0.5.1 + abitype: + specifier: ^1.0.0 + version: 1.0.6(typescript@5.4.5)(zod@3.23.8) + dns-packet: + specifier: ^5.3.1 + version: 5.6.1 + graphql: + specifier: ^16.3.0 + version: 16.8.1 + graphql-request: + specifier: 6.1.0 + version: 6.1.0(encoding@0.1.13)(graphql@16.8.1) + pako: + specifier: ^2.1.0 + version: 2.1.0 + ts-pattern: + specifier: ^5.4.0 + version: 5.5.0 + viem: + specifier: ^2.9.2 + version: 2.21.40(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + packages: '@adobe/css-tools@4.3.3': @@ -1607,8 +1643,8 @@ packages: '@ensdomains/ensjs@2.1.0': resolution: {integrity: sha512-GRbGPT8Z/OJMDuxs75U/jUNEC0tbL0aj7/L/QQznGYKm/tiasp+ndLOaoULy9kKJFC0TBByqfFliEHDgoLhyog==} - '@ensdomains/ensjs@4.0.2-alpha.5': - resolution: {integrity: sha512-xDlnEg+JbZMs+vV5C3v0ks8nBrJciwVD9gYeFH5TzDpSM1zcGxb4KO4vvv0F+f8UI0aZFLLldZ7xyWfn4egjAw==} + '@ensdomains/ensjs@4.0.2': + resolution: {integrity: sha512-4vDIZEFAa1doNA3H9MppUHxflSDYYPVNyaDbdHLksTa4taq3y4dGpletX67Xea8nxI+cMfjEi4nOzLJmPzRE/g==} peerDependencies: viem: ^2.9.2 @@ -3857,6 +3893,7 @@ packages: acorn-import-assertions@1.9.0: resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes peerDependencies: acorn: ^8 @@ -11849,15 +11886,15 @@ snapshots: '@ensdomains/address-encoder@1.0.0-rc.3': dependencies: - '@noble/curves': 1.4.0 - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.6 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/base': 1.1.9 '@ensdomains/address-encoder@1.1.1': dependencies: - '@noble/curves': 1.4.0 - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.6 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/base': 1.1.9 '@ensdomains/buffer@0.1.1': {} @@ -11869,12 +11906,12 @@ snapshots: '@ensdomains/content-hash@3.1.0-rc.1': dependencies: '@ensdomains/address-encoder': 1.0.0-rc.3 - '@noble/curves': 1.4.0 - '@scure/base': 1.1.6 + '@noble/curves': 1.6.0 + '@scure/base': 1.1.9 '@ensdomains/dnsprovejs@0.5.1': dependencies: - '@noble/hashes': 1.4.0 + '@noble/hashes': 1.5.0 dns-packet: 5.6.1 typescript-logging: 1.0.1 @@ -11924,13 +11961,13 @@ snapshots: - bufferutil - utf-8-validate - '@ensdomains/ensjs@4.0.2-alpha.5(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)': + '@ensdomains/ensjs@4.0.2(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.4(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)': dependencies: '@adraffy/ens-normalize': 1.10.1 '@ensdomains/address-encoder': 1.1.1 '@ensdomains/content-hash': 3.1.0-rc.1 '@ensdomains/dnsprovejs': 0.5.1 - abitype: 1.0.5(typescript@5.4.5)(zod@3.23.8) + abitype: 1.0.6(typescript@5.4.5)(zod@3.23.8) dns-packet: 5.6.1 graphql: 16.8.1 graphql-request: 6.1.0(encoding@0.1.13)(graphql@16.8.1) @@ -16791,7 +16828,7 @@ snapshots: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.50.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) object.assign: 4.1.5 object.entries: 1.1.8 semver: 6.3.1 @@ -16802,13 +16839,13 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-config-airbnb@19.0.4(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint-plugin-jsx-a11y@6.8.0(eslint@8.50.0))(eslint-plugin-react-hooks@4.6.2(eslint@8.50.0))(eslint-plugin-react@7.34.1(eslint@8.50.0))(eslint@8.50.0): dependencies: eslint: 8.50.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.50.0) eslint-plugin-react: 7.34.1(eslint@8.50.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.50.0) @@ -16822,8 +16859,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.50.0) eslint-plugin-react: 7.34.1(eslint@8.50.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.50.0) @@ -16845,13 +16882,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0): dependencies: debug: 4.3.4(supports-color@5.5.0) enhanced-resolve: 5.16.1 eslint: 8.50.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -16862,18 +16899,18 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.50.0)(typescript@5.4.5) eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.50.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -16883,7 +16920,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.50.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint@8.50.0))(eslint@8.50.0))(eslint@8.50.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.50.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.50.0))(eslint@8.50.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 From 83f469eb9e05000be4a0ab25a66b6208054036dd Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 11 Nov 2024 00:30:28 +0700 Subject: [PATCH 30/37] Update resolverAddressData.ts --- src/constants/resolverAddressData.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/constants/resolverAddressData.ts b/src/constants/resolverAddressData.ts index 76545ef89..6b8d6da62 100644 --- a/src/constants/resolverAddressData.ts +++ b/src/constants/resolverAddressData.ts @@ -318,6 +318,23 @@ export const KNOWN_RESOLVER_DATA: KnownResolverData = { RESOLVER_INTERFACE_IDS.VersionableResolver, ], }, + { + address: '0x8FADE66B79cC9f707aB26799354482EB93a5B7dD', + deployer: 'ENS Labs', + tag: null, + isNameWrapperAware: true, + supportedInterfaces: [ + RESOLVER_INTERFACE_IDS.AddressResolver, + RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, + RESOLVER_INTERFACE_IDS.NameResolver, + RESOLVER_INTERFACE_IDS.AbiResolver, + RESOLVER_INTERFACE_IDS.TextResolver, + RESOLVER_INTERFACE_IDS.ContentHashResolver, + RESOLVER_INTERFACE_IDS.DnsRecordResolver, + RESOLVER_INTERFACE_IDS.InterfaceResolver, + RESOLVER_INTERFACE_IDS.VersionableResolver, + ], + }, { address: '0x0CeEC524b2807841739D3B5E161F5bf1430FFA48', deployer: 'ENS Labs', From 634cd294efcd4688a058a71155a33176a4e012d0 Mon Sep 17 00:00:00 2001 From: storywithoutend Date: Mon, 11 Nov 2024 00:39:45 +0700 Subject: [PATCH 31/37] Update resolverAddressData.ts --- src/constants/resolverAddressData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/resolverAddressData.ts b/src/constants/resolverAddressData.ts index 6b8d6da62..70b710b82 100644 --- a/src/constants/resolverAddressData.ts +++ b/src/constants/resolverAddressData.ts @@ -302,7 +302,7 @@ export const KNOWN_RESOLVER_DATA: KnownResolverData = { ], '11155111': [ { - address: '0x8FADE66B79cC9f707aB26799354482EB93a5B7dD', + address: '0x8948458626811dd0c23EB25Cc74291247077cC51', deployer: 'ENS Labs', tag: 'latest', isNameWrapperAware: true, From 9de73df715903f7a3dd34b97e3ed43080159cd20 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Thu, 21 Nov 2024 17:00:38 +0800 Subject: [PATCH 32/37] Add namechain announcement --- src/pages/ens-v2.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/pages/ens-v2.tsx b/src/pages/ens-v2.tsx index 3e1a7c50f..420686028 100644 --- a/src/pages/ens-v2.tsx +++ b/src/pages/ens-v2.tsx @@ -41,7 +41,7 @@ const Header = styled.header( justify-content: center; gap: ${theme.space[4]}; padding: ${theme.space[4]}; - min-height: 330px; + padding-top: ${theme.space[16]}; text-align: center; `, ) @@ -239,11 +239,10 @@ const SlideshowContainer = styled.div( `, ) -const Video = styled.video( +const YoutubeEmbed = styled.iframe( ({ theme }) => css` - height: ${theme.space.full}; + aspect-ratio: 16 / 9; width: ${theme.space.full}; - border-radius: ${theme.radii.card}; `, ) @@ -263,9 +262,14 @@ export default function ENSv2() { {t('title')} {t('caption')} - + {t('learn-more.title')} From 1df6db6154eda22b61be2890a0e7b0d9b7da7574 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Thu, 21 Nov 2024 17:22:02 +0800 Subject: [PATCH 33/37] build fix --- knip.config.ts | 2 ++ src/pages/ens-v2.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/knip.config.ts b/knip.config.ts index edc38490a..ece2f9479 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -24,6 +24,8 @@ const config: KnipConfig = { 'src/utils/query/match/matchExactOrNullParamItem.test-d.ts', 'src/utils/query/match/matchQueryKeyMeta.test-d.ts', 'src/utils/query/match/queryKeyToInternalParams.test-d.ts', + // Will need later + 'src/components/pages/migrate/Carousel.tsx', ], } diff --git a/src/pages/ens-v2.tsx b/src/pages/ens-v2.tsx index 420686028..19cbde148 100644 --- a/src/pages/ens-v2.tsx +++ b/src/pages/ens-v2.tsx @@ -268,7 +268,7 @@ export default function ENSv2() { frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" - allowfullscreen + allowFullScreen /> From df0b00765f78814b30df269438286478354a07ed Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Thu, 28 Nov 2024 18:05:25 +0800 Subject: [PATCH 34/37] Add dRPC endpoint --- .github/workflows/pages-deployment.yaml | 1 + src/utils/query/wagmi.ts | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pages-deployment.yaml b/.github/workflows/pages-deployment.yaml index 6aea45954..2a957ae8a 100644 --- a/.github/workflows/pages-deployment.yaml +++ b/.github/workflows/pages-deployment.yaml @@ -3,6 +3,7 @@ name: Cloudflare Pages env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} NEXT_PUBLIC_INTERCOM_ID: re9q5yti + NEXT_PUBLIC_DRPC_KEY: AnmpasF2C0JBqeAEzxVO8aTteiMlrW4R75hpDonbV6cR on: [push] jobs: diff --git a/src/utils/query/wagmi.ts b/src/utils/query/wagmi.ts index 4799fefbc..0794e847b 100644 --- a/src/utils/query/wagmi.ts +++ b/src/utils/query/wagmi.ts @@ -1,6 +1,6 @@ import { createClient, type FallbackTransport, type HttpTransport, type Transport } from 'viem' import { createConfig, createStorage, fallback, http } from 'wagmi' -import { goerli, holesky, localhost, mainnet, sepolia } from 'wagmi/chains' +import { holesky, localhost, mainnet, sepolia } from 'wagmi/chains' import { ccipRequest } from '@ensdomains/ensjs/utils' @@ -24,12 +24,16 @@ const connectors = getDefaultWallets({ const infuraKey = process.env.NEXT_PUBLIC_INFURA_KEY || 'cfa6ae2501cc4354a74e20432507317c' const tenderlyKey = process.env.NEXT_PUBLIC_TENDERLY_KEY || '4imxc4hQfRjxrVB2kWKvTo' +const drpcKey = process.env.NEXT_PUBLIC_DRPC_KEY || 'AnmpasF2C0JBqeAEzxVO8aRuvzLTrWcR75hmDonbV6cR' export const infuraUrl = (chainName: string) => `https://${chainName}.infura.io/v3/${infuraKey}` -const cloudflareUrl = (chainName: string) => `https://web3.ens.domains/v1/${chainName}` const tenderlyUrl = (chainName: string) => `https://${chainName}.gateway.tenderly.co/${tenderlyKey}` +const drpcUrl = (chainName: string) => + `https://lb.drpc.org/ogrpc?network=${ + chainName === 'mainnet' ? 'ethereum' : chainName + }&dkey=${drpcKey}` -type SupportedUrlFunc = typeof infuraUrl | typeof cloudflareUrl | typeof tenderlyUrl +type SupportedUrlFunc = typeof infuraUrl | typeof dRPCUrl | typeof tenderlyUrl const initialiseTransports = ( chainName: string, @@ -97,10 +101,9 @@ const transports = { // this is a hack to make the types happy, dont remove pls [localhost.id]: HttpTransport })), - [mainnet.id]: initialiseTransports('mainnet', [infuraUrl, cloudflareUrl, tenderlyUrl]), - [sepolia.id]: initialiseTransports('sepolia', [infuraUrl, cloudflareUrl, tenderlyUrl]), - [goerli.id]: initialiseTransports('goerli', [infuraUrl, cloudflareUrl, tenderlyUrl]), - [holesky.id]: initialiseTransports('holesky', [tenderlyUrl]), + [mainnet.id]: initialiseTransports('mainnet', [drpcUrl, infuraUrl, tenderlyUrl]), + [sepolia.id]: initialiseTransports('sepolia', [drpcUrl, infuraUrl, tenderlyUrl]), + [holesky.id]: initialiseTransports('holesky', [drpcUrl, tenderlyUrl]), } as const const wagmiConfig_ = createConfig({ From 4601acb9c8a66c9444407429cc157a50287e2993 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Thu, 28 Nov 2024 18:17:20 +0800 Subject: [PATCH 35/37] Remove goerli properly --- .../pages/profile/[name]/registration/Registration.tsx | 5 ----- src/constants/chains.ts | 5 +---- src/hooks/useEthPrice.ts | 9 ++------- src/utils/query/wagmi.ts | 4 +--- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/components/pages/profile/[name]/registration/Registration.tsx b/src/components/pages/profile/[name]/registration/Registration.tsx index 48ce3df24..3294b044d 100644 --- a/src/components/pages/profile/[name]/registration/Registration.tsx +++ b/src/components/pages/profile/[name]/registration/Registration.tsx @@ -359,11 +359,6 @@ const Registration = ({ nameDetails, isLoading }: Props) => { {t('steps.info.moonpayModalHeader')} - {chainId === 5 && ( - - {`${t('steps.info.moonpayTestCard')}: 4000 0209 5159 5032, 12/2030, 123`} - - )} export type SupportedChain = | typeof mainnetWithEns - | typeof goerliWithEns | typeof sepoliaWithEns | typeof holeskyWithEns | typeof localhostWithEns diff --git a/src/hooks/useEthPrice.ts b/src/hooks/useEthPrice.ts index 4fd8cef70..1861ed2a2 100644 --- a/src/hooks/useEthPrice.ts +++ b/src/hooks/useEthPrice.ts @@ -1,21 +1,16 @@ import { Address } from 'viem' -import { useChainId, useReadContract } from 'wagmi' -import { goerli } from 'wagmi/chains' +import { useReadContract } from 'wagmi' import { useAddressRecord } from './ensjs/public/useAddressRecord' const ORACLE_ENS = 'eth-usd.data.eth' -const ORACLE_GOERLI = '0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e' as const export const useEthPrice = () => { - const chainId = useChainId() - const { data: address_ } = useAddressRecord({ name: ORACLE_ENS, - enabled: chainId !== goerli.id, }) - const address = chainId === 5 ? ORACLE_GOERLI : (address_?.value as Address) || undefined + const address = (address_?.value as Address) || undefined return useReadContract({ abi: [ diff --git a/src/utils/query/wagmi.ts b/src/utils/query/wagmi.ts index 0794e847b..31e44228f 100644 --- a/src/utils/query/wagmi.ts +++ b/src/utils/query/wagmi.ts @@ -5,7 +5,6 @@ import { holesky, localhost, mainnet, sepolia } from 'wagmi/chains' import { ccipRequest } from '@ensdomains/ensjs/utils' import { - goerliWithEns, holeskyWithEns, localhostWithEns, mainnetWithEns, @@ -33,7 +32,7 @@ const drpcUrl = (chainName: string) => chainName === 'mainnet' ? 'ethereum' : chainName }&dkey=${drpcKey}` -type SupportedUrlFunc = typeof infuraUrl | typeof dRPCUrl | typeof tenderlyUrl +type SupportedUrlFunc = typeof infuraUrl | typeof drpcUrl | typeof tenderlyUrl const initialiseTransports = ( chainName: string, @@ -87,7 +86,6 @@ const localStorageWithInvertMiddleware = (): Storage | undefined => { const chains = [ ...(isLocalProvider ? ([localhostWithEns] as const) : ([] as const)), mainnetWithEns, - goerliWithEns, sepoliaWithEns, holeskyWithEns, ] as const From ccb7c17676a061fbb547257dcc51b70eb8472b32 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Thu, 28 Nov 2024 18:33:06 +0800 Subject: [PATCH 36/37] Fix test --- src/constants/resolverAddressData.test.ts | 7 +- src/constants/resolverAddressData.ts | 177 ---------------------- 2 files changed, 2 insertions(+), 182 deletions(-) diff --git a/src/constants/resolverAddressData.test.ts b/src/constants/resolverAddressData.test.ts index 4e9b21134..085912d78 100644 --- a/src/constants/resolverAddressData.test.ts +++ b/src/constants/resolverAddressData.test.ts @@ -1,7 +1,7 @@ import { getChainContractAddress } from 'viem/utils' -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' -import { goerliWithEns, localhostWithEns, mainnetWithEns, sepoliaWithEns } from './chains' +import { mainnetWithEns, sepoliaWithEns } from './chains' ;(process.env as any).NODE_ENV = 'development' @@ -12,9 +12,6 @@ it('should have the most recent resolver as the first address', async () => { expect(KNOWN_RESOLVER_DATA['1']![0].address).toEqual( getChainContractAddress({ chain: mainnetWithEns, contract: 'ensPublicResolver' }), ) - expect(KNOWN_RESOLVER_DATA['5']![0].address).toEqual( - getChainContractAddress({ chain: goerliWithEns, contract: 'ensPublicResolver' }), - ) expect(KNOWN_RESOLVER_DATA['11155111']![0].address).toEqual( getChainContractAddress({ chain: sepoliaWithEns, contract: 'ensPublicResolver' }), ) diff --git a/src/constants/resolverAddressData.ts b/src/constants/resolverAddressData.ts index 70b710b82..eecb73d9d 100644 --- a/src/constants/resolverAddressData.ts +++ b/src/constants/resolverAddressData.ts @@ -123,183 +123,6 @@ export const KNOWN_RESOLVER_DATA: KnownResolverData = { ], }, ], - '5': [ - { - address: '0xd7a4F6473f32aC2Af804B3686AE8F1932bC35750', - deployer: 'ENS Labs', - tag: 'latest', - isNameWrapperAware: true, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.DnsRecordResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - RESOLVER_INTERFACE_IDS.VersionableResolver, - ], - }, - { - address: '0x342cf18D3e41DE491aa1a3067574C849AdA6a2Ad', - deployer: 'ENS Labs', - tag: null, - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.DnsRecordResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - RESOLVER_INTERFACE_IDS.VersionableResolver, - ], - }, - { - address: '0x19c2d5D0f035563344dBB7bE5fD09c8dad62b001', - deployer: 'ENS Labs', - tag: null, - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.DnsRecordResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - RESOLVER_INTERFACE_IDS.VersionableResolver, - ], - }, - { - address: '0x2800Ec5BAB9CE9226d19E0ad5BC607e3cfC4347E', - deployer: 'ENS Labs', - tag: null, - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.DnsRecordResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - RESOLVER_INTERFACE_IDS.VersionableResolver, - ], - }, - { - address: '0x121304143ea8101E69335F309e2062d299A234B5', - deployer: 'ENS Labs', - tag: null, - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.DnsRecordResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - ], - }, - { - address: '0x4B1488B7a6B320d2D721406204aBc3eeAa9AD329', - deployer: 'ENS Labs', - tag: null, - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.DnsRecordResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - ], - }, - { - address: '0xfF77b96d6bafCec0D684bB528b22e0Ab09C70663', - deployer: 'ENS Labs', - tag: null, - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.DnsRecordResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - ], - }, - { - address: '0x6e1b40ed2d626b97A43d2c12e48a6De49A03c7A4', - deployer: 'ENS Labs', - tag: null, - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - ], - }, - { - address: '0xc1EA41786094D1fBE5aded033B5370d51F7a3F96', - deployer: 'ENS Labs', - tag: 'outdated', - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - ], - }, - { - address: '0xBbe3fD189D18C8b73BA54e9dD01F89E6b3Ee71f0', - deployer: 'ENS Labs', - tag: 'outdated', - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - ], - }, - { - address: '0x4B1488B7a6B320d2D721406204aBc3eeAa9AD329', - deployer: 'ENS Labs', - tag: 'outdated', - isNameWrapperAware: false, - supportedInterfaces: [ - RESOLVER_INTERFACE_IDS.AddressResolver, - RESOLVER_INTERFACE_IDS.NameResolver, - RESOLVER_INTERFACE_IDS.AbiResolver, - RESOLVER_INTERFACE_IDS.TextResolver, - RESOLVER_INTERFACE_IDS.ContentHashResolver, - RESOLVER_INTERFACE_IDS.DnsRecordResolver, - RESOLVER_INTERFACE_IDS.InterfaceResolver, - RESOLVER_INTERFACE_IDS.MultiCoinAddressResolver, - ], - }, - ], '11155111': [ { address: '0x8948458626811dd0c23EB25Cc74291247077cC51', From 97c347129f7faac23d5a7aa04a2d4b3b998d65da Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Thu, 28 Nov 2024 19:01:08 +0800 Subject: [PATCH 37/37] Only use paid endpoint in prod --- .github/workflows/pages-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages-deployment.yaml b/.github/workflows/pages-deployment.yaml index 2a957ae8a..f92265db5 100644 --- a/.github/workflows/pages-deployment.yaml +++ b/.github/workflows/pages-deployment.yaml @@ -3,7 +3,7 @@ name: Cloudflare Pages env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} NEXT_PUBLIC_INTERCOM_ID: re9q5yti - NEXT_PUBLIC_DRPC_KEY: AnmpasF2C0JBqeAEzxVO8aTteiMlrW4R75hpDonbV6cR + NEXT_PUBLIC_DRPC_KEY: ${{ github.ref == 'refs/heads/main' && AnmpasF2C0JBqeAEzxVO8aTteiMlrW4R75hpDonbV6cR || '' }} on: [push] jobs:
+ + + {t('partnership.text')} + + {t('partnership.watch')} + + + +
+ {t('title')} + {t('caption')} +
+
video
+ + + 🎉 + + {t('accessible.title')} + + {t('accessible.caption')} + + + + + + {t('accessible.gas.title')} + + {t('accessible.gas.text')} + + + + + {t('accessible.control.title')} + + {t('accessible.control.text')} + + + + + {t('accessible.multichain.title')} + + {t('accessible.multichain.text')} + + + + + {t('announcement.title')} + + + + + + + +