From c67d000684e838743d13c4a62ba4d5ebd8c693d4 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Fri, 25 Oct 2024 16:10:09 +0300 Subject: [PATCH 01/24] fet-1670 --- package.json | 1 + pnpm-lock.yaml | 41 +- public/confetti.png | Bin 0 -> 107983 bytes src/assets/DAO.svg | 10 + src/assets/social/SocialX.svg | 2 +- src/components/pages/migrate/Carousel.tsx | 20 + .../pages/migrate/MigrationNamesList.tsx | 170 ++++++ .../migration/useApprovedNamesForMigration.ts | 28 + src/pages/_app.tsx | 1 + src/pages/migrate.tsx | 560 ++++++++++++++++++ .../approveNameWrapperForMigration.ts | 52 ++ .../approveRegistrarForMigration.ts | 53 ++ src/transaction-flow/transaction/index.ts | 4 + 13 files changed, 928 insertions(+), 14 deletions(-) create mode 100644 public/confetti.png create mode 100644 src/assets/DAO.svg create mode 100644 src/components/pages/migrate/Carousel.tsx create mode 100644 src/components/pages/migrate/MigrationNamesList.tsx create mode 100644 src/hooks/migration/useApprovedNamesForMigration.ts create mode 100644 src/pages/migrate.tsx create mode 100644 src/transaction-flow/transaction/approveNameWrapperForMigration.ts create mode 100644 src/transaction-flow/transaction/approveRegistrarForMigration.ts diff --git a/package.json b/package.json index 5da3d7e20..897904740 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 1e8c72c86..094aaeed1 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) @@ -293,7 +296,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) @@ -2947,6 +2950,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==} @@ -13629,6 +13638,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': @@ -16637,7 +16652,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 @@ -16648,13 +16663,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) @@ -16668,8 +16683,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) @@ -16691,13 +16706,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 @@ -16708,18 +16723,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 @@ -16729,7 +16744,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 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/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 new file mode 100644 index 000000000..4c56f4bcd --- /dev/null +++ b/src/components/pages/migrate/Carousel.tsx @@ -0,0 +1,20 @@ +import { Splide, SplideSlide } from '@splidejs/react-splide' +import { ReactNode } from 'react' + +export const Carousel = ({ children }: { children: ReactNode[] }) => { + return ( + + {children.map((child) => ( + {child} + ))} + + ) +} diff --git a/src/components/pages/migrate/MigrationNamesList.tsx b/src/components/pages/migrate/MigrationNamesList.tsx new file mode 100644 index 000000000..9b7f3c6e0 --- /dev/null +++ b/src/components/pages/migrate/MigrationNamesList.tsx @@ -0,0 +1,170 @@ +import { TFunction, useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' +import { useEnsAvatar } from 'wagmi' + +import { NameWithRelation } from '@ensdomains/ensjs/subgraph' +import { CheckCircleSVG, Colors, DisabledSVG, PlusCircleSVG } from '@ensdomains/thorin' + +import { ensAvatarConfig } from '@app/utils/query/ipfsGateway' +import { formatDurationOfDates } from '@app/utils/utils' + +const tabs = ['eligible', 'ineligible', 'approved'] as const + +type Tab = (typeof tabs)[number] + +const icons: Record = { + eligible: , + ineligible: , + approved: , +} + +const colors: Record = { + eligible: { + fg: 'bluePrimary', + bg: 'blueSurface', + hover: 'blueLight', + }, + ineligible: { + fg: 'redPrimary', + bg: 'redSurface', + hover: 'redLight', + }, + approved: { + fg: 'greenPrimary', + bg: 'greenSurface', + hover: 'greenLight', + }, +} + +const Container = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['2']}; + `, +) + +const TabsContainer = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: row; + gap: ${theme.space['4']}; + padding: ${theme.space['0.5']}; + border-radius: ${theme.radii['2xLarge']}; + background-color: ${theme.colors.background}; + border: 4px solid ${theme.colors.background}; + `, +) + +const TabButton = styled.button<{ $isActive?: boolean; tab: Tab }>( + ({ theme, $isActive, tab }) => css` + width: ${theme.space.full}; + padding: 0 ${theme.space['4']}; + height: ${theme.space['12']}; + border-radius: ${theme.radii.large}; + color: ${$isActive ? theme.colors[colors[tab].fg] : theme.colors.textTertiary}; + background-color: ${$isActive ? theme.colors[colors[tab].bg] : theme.colors.background}; + cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + font-weight: ${theme.fontWeights.bold}; + gap: ${theme.space['2']}; + + transition-property: background-color; + transition-duration: ${theme.transitionDuration['150']}; + transition-timing-function: ${theme.transitionTimingFunction.inOut}; + + &:hover { + background-color: ${$isActive + ? theme.colors[colors[tab].hover] + : theme.colors[colors[tab].bg]}; + } + `, +) + +const NamesGrid = styled.div( + ({ theme }) => css` + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: ${theme.space['2']}; + `, +) + +const NameCard = styled.div( + ({ theme }) => css` + background: ${theme.colors.background}; + border-radius: ${theme.radii['2xLarge']}; + padding: ${theme.space['8']}; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + + & > span:nth-of-type(1) { + font-weight: ${theme.fontWeights.bold}; + } + & > span:nth-of-type(2) { + color: ${theme.colors.textTertiary}; + font-size: ${theme.fontSizes.small}; + } + + & > img { + border-radius: ${theme.radii.full}; + } + `, +) + +const nameListTabs = ['eligible', 'ineligible', 'approved'] as const + +export type NameListTab = (typeof nameListTabs)[number] + +const MigrationName = ({ name, t }: { name: NameWithRelation; t: TFunction }) => { + const now = new Date() + const { data: avatar } = useEnsAvatar({ ...ensAvatarConfig, name: name.name! }) + + const expiresIn = formatDurationOfDates({ + startDate: now, + endDate: new Date(name.expiryDate?.date!), + t, + }).split(', ')[0] + + return ( + + {avatar && } + {name.name} + Expires in {expiresIn} + + ) +} + +export const MigrationNamesList = ({ + activeTab, + setTab, + names, +}: { + activeTab: NameListTab + names: NameWithRelation[] + setTab: (tab: NameListTab) => void +}) => { + const { t } = useTranslation('migrate') + + return ( + + + {tabs.map((tab) => ( + setTab(tab)}> + {icons[tab]} {t(`migration-list.${tab}`)} + + ))} + + + {names.map((name) => ( + + ))} + + + ) +} diff --git a/src/hooks/migration/useApprovedNamesForMigration.ts b/src/hooks/migration/useApprovedNamesForMigration.ts new file mode 100644 index 000000000..d9a6bfe8c --- /dev/null +++ b/src/hooks/migration/useApprovedNamesForMigration.ts @@ -0,0 +1,28 @@ +import { erc721Abi } from 'viem' +import { usePublicClient, useReadContracts } from 'wagmi' + +import { getChainContractAddress } from '@ensdomains/ensjs/contracts' +import { NameWithRelation } from '@ensdomains/ensjs/subgraph' + +export const useApprovedNamesForMigration = ({ + names, +}: { + names: NameWithRelation[] +}): NameWithRelation[] => { + const client = usePublicClient() + + const { data } = useReadContracts({ + contracts: names.map((name) => ({ + address: getChainContractAddress({ + client, + contract: name.wrappedOwner ? 'ensNameWrapper' : 'ensBaseRegistrarImplementation', + }), + abi: erc721Abi, + functionName: 'isApprovedForAll', + args: ['contract-go-here', true], + })), + multicallAddress: getChainContractAddress({ client, contract: 'multicall3' }), + }) + + return names.filter((d, i) => Boolean(data?.[i].result)) +} 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 new file mode 100644 index 000000000..38956a0ee --- /dev/null +++ b/src/pages/migrate.tsx @@ -0,0 +1,560 @@ +/* eslint-disable @next/next/no-img-element */ +import { useConnectModal } from '@rainbow-me/rainbowkit' +import Head from 'next/head' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' +import { useAccount } from 'wagmi' + +import { GetNamesForAddressParameters } from '@ensdomains/ensjs/subgraph' +import { + Banner, + Button, + Card, + GasPumpSVG, + InfoCircleSVG, + KeySVG, + QuestionBubbleSVG, + QuestionCircleSVG, + RightArrowSVG, + SpannerAltSVG, + Typography, + UpCircleSVG, + WalletSVG, +} from '@ensdomains/thorin' + +import { Carousel } from '@app/components/pages/migrate/Carousel' +import { MigrationNamesList, NameListTab } from '@app/components/pages/migrate/MigrationNamesList' +import { useNamesForAddress } from '@app/hooks/ensjs/subgraph/useNamesForAddress' +import { useApprovedNamesForMigration } from '@app/hooks/migration/useApprovedNamesForMigration' +import { useQueryParameterState } from '@app/hooks/useQueryParameterState' +import { makeIntroItem } from '@app/transaction-flow/intro' +import { createTransactionItem, TransactionData } from '@app/transaction-flow/transaction' +import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' + +import DAOSVG from '../assets/DAO.svg' +import SocialX from '../assets/social/SocialX.svg' + +const Main = styled.main( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['16']}; + `, +) + +const Header = styled.header<{ $isLanding: boolean }>( + ({ theme, $isLanding }) => css` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: ${theme.space['4']}; + gap: ${theme.space['4']}; + min-height: ${$isLanding ? '530px' : 'unset'}; + `, +) + +const Heading = styled.h1( + ({ theme }) => css` + font-size: 52px; + font-size: 850; + color: ${theme.colors.textPrimary}; + text-align: center; + line-height: 104%; + & > span { + font-weight: inherit; + font-size: inherit; + line-height: inherit; + background: linear-gradient(90deg, #199c75 2.87%, #9b9ba7 97.95%); + background-clip: text; + -webkit-text-fill-color: transparent; + } + @media (min-width: 360px) { + font-size: 60px; + } + @media (min-width: 640px) { + font-size: 76px; + } + `, +) + +const ButtonContainer = styled.div( + ({ theme }) => css` + display: flex; + gap: ${theme.space['2']}; + flex-direction: column; + + @media (min-width: 360px) { + flex-direction: row; + } + `, +) + +const Caption = styled(Typography)` + text-align: center; + max-width: 538px; +` + +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; + } + & > a:hover { + color: ${theme.colors.green}; + } + @media (min-width: 640px) { + border-radius: ${theme.radii['3xLarge']}; + } + `, +) + +const CenteredCard = styled(Card)` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +` + +const CardWithEmoji = styled(CenteredCard)` + padding-top: 83px; + position: relative; + grid-column: 1 / -1; + + img { + position: absolute; + top: -72px; + } +` + +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 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 Section = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['6']}; + h3 { + text-align: center; + } + & > div { + display: flex; + flex-direction: column; + gap: ${theme.space['4']}; + } + & > div > div { + width: 100%; + display: flex; + align-items: center; + } + @media (min-width: 360px) { + & > div { + flex-direction: row; + } + } + `, +) +const Footer = styled(Section)( + ({ theme }) => css` + span { + display: flex; + flex-direction: row; + align-items: center; + gap: ${theme.space['2']}; + color: ${theme.colors.green}; + } + `, +) + +const GetStarted = styled(Section)( + ({ theme }) => css` + & > div button span { + display: flex; + flex-direction: row; + align-items: center; + gap: ${theme.space['2']}; + } + `, +) + +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; + } + `, +) + +const TopNav = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['6']}; + align-items: center; + `, +) + +const TabManager = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: row; + gap: ${theme.space['4']}; + `, +) + +const tabs = ['ensv2', 'migrations', 'extension'] as const + +const filter: Record = { + eligible: { owner: false, wrappedOwner: true, registrant: true, resolvedAddress: false }, + ineligible: { owner: true, wrappedOwner: false, registrant: false, resolvedAddress: false }, + approved: { owner: false, wrappedOwner: true, registrant: true, resolvedAddress: false }, +} + +type Tab = (typeof tabs)[number] + +export default function Page() { + const { t } = useTranslation('migrate') + + const { isConnected, address } = useAccount() + + const { openConnectModal } = useConnectModal() + + const [currentTab, setTab] = useQueryParameterState('tab', 'ensv2') + + const { createTransactionFlow } = useTransactionFlow() + + const [activeNameListTab, setNameListTab] = useState('eligible') + + const { infiniteData } = useNamesForAddress({ + address, + pageSize: 20, + filter: filter[activeNameListTab], + }) + + const names = infiniteData.filter( + (name) => + name.parentName === 'eth' && + (activeNameListTab === 'ineligible' ? name.registrant !== name.owner : true), + ) + + const approvedNames = useApprovedNamesForMigration({ names }) + + return ( + <> + + + ) +} diff --git a/src/transaction-flow/transaction/approveNameWrapperForMigration.ts b/src/transaction-flow/transaction/approveNameWrapperForMigration.ts new file mode 100644 index 000000000..5e3e85bd9 --- /dev/null +++ b/src/transaction-flow/transaction/approveNameWrapperForMigration.ts @@ -0,0 +1,52 @@ +import type { TFunction } from 'react-i18next' +import { Address, encodeFunctionData } from 'viem' + +import { + getChainContractAddress, + registrySetApprovalForAllSnippet, +} from '@ensdomains/ensjs/contracts' + +import { Transaction, TransactionDisplayItem, TransactionFunctionParameters } from '@app/types' + +type Data = { address: Address } + +const displayItems = ( + { address }: Data, + t: TFunction<'translation', undefined>, +): TransactionDisplayItem[] => [ + { + label: 'address', + value: address, + type: 'address', + }, + { + label: 'action', + value: t('transaction.description.approveNameWrapper'), + }, + { + label: 'info', + value: t('transaction.info.approveNameWrapper'), + }, +] + +const transaction = async ({ client }: TransactionFunctionParameters) => { + return { + to: getChainContractAddress({ + client, + contract: 'ensNameWrapper', + }), + data: encodeFunctionData({ + abi: registrySetApprovalForAllSnippet, + functionName: 'setApprovalForAll', + args: [ + getChainContractAddress({ + client, + contract: 'contract-address-will-go-here', + }), + true, + ], + }), + } +} + +export default { displayItems, transaction } satisfies Transaction diff --git a/src/transaction-flow/transaction/approveRegistrarForMigration.ts b/src/transaction-flow/transaction/approveRegistrarForMigration.ts new file mode 100644 index 000000000..9ffee6155 --- /dev/null +++ b/src/transaction-flow/transaction/approveRegistrarForMigration.ts @@ -0,0 +1,53 @@ +import { TFunction } from 'react-i18next' +import { Address } from 'viem/accounts' +import { encodeFunctionData } from 'viem/utils' + +import { + getChainContractAddress, + registrySetApprovalForAllSnippet, +} from '@ensdomains/ensjs/contracts' + +import type { Transaction, TransactionDisplayItem, TransactionFunctionParameters } from '@app/types' + +type Data = { address: Address } + +const displayItems = ( + { address }: Data, + t: TFunction<'translation', undefined>, +): TransactionDisplayItem[] => [ + { + label: 'address', + value: address, + type: 'address', + }, + { + label: 'action', + value: t('transaction.description.approveEthRegistrar'), + }, + { + label: 'info', + value: t('transaction.info.approveEthRegistrar'), + }, +] + +const transaction = async ({ client }: TransactionFunctionParameters) => { + return { + to: getChainContractAddress({ + client, + contract: 'ensBaseRegistrarImplementation', + }), + data: encodeFunctionData({ + abi: registrySetApprovalForAllSnippet, + functionName: 'setApprovalForAll', + args: [ + getChainContractAddress({ + client, + contract: 'address-will-go-here', + }), + true, + ], + }), + } +} + +export default { displayItems, transaction } satisfies Transaction diff --git a/src/transaction-flow/transaction/index.ts b/src/transaction-flow/transaction/index.ts index 55211a5e5..96a04cc5d 100644 --- a/src/transaction-flow/transaction/index.ts +++ b/src/transaction-flow/transaction/index.ts @@ -1,5 +1,7 @@ import approveDnsRegistrar from './approveDnsRegistrar' import approveNameWrapper from './approveNameWrapper' +import approveNameWrapperForMigration from './approveNameWrapperForMigration' +import approveRegistrarForMigration from './approveRegistrarForMigration' import burnFuses from './burnFuses' import changePermissions from './changePermissions' import claimDnsName from './claimDnsName' @@ -31,6 +33,8 @@ import wrapName from './wrapName' export const transactions = { approveDnsRegistrar, + approveNameWrapperForMigration, + approveRegistrarForMigration, approveNameWrapper, burnFuses, changePermissions, From 39c5e7ffd275179d37ec3e284e2d7c11c7102af0 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Fri, 25 Oct 2024 17:40:16 +0300 Subject: [PATCH 02/24] refactor --- .../pages/migrate/MigrationSection.tsx | 27 ++ src/components/pages/migrate/MigrationTab.tsx | 439 ++++++++++++++++++ src/pages/migrate.tsx | 439 +----------------- 3 files changed, 481 insertions(+), 424 deletions(-) create mode 100644 src/components/pages/migrate/MigrationSection.tsx create mode 100644 src/components/pages/migrate/MigrationTab.tsx diff --git a/src/components/pages/migrate/MigrationSection.tsx b/src/components/pages/migrate/MigrationSection.tsx new file mode 100644 index 000000000..2a3b3937e --- /dev/null +++ b/src/components/pages/migrate/MigrationSection.tsx @@ -0,0 +1,27 @@ +import styled, { css } from 'styled-components' + +export const MigrationSection = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['6']}; + h3 { + text-align: center; + } + & > div { + display: flex; + flex-direction: column; + gap: ${theme.space['4']}; + } + & > div > div { + width: 100%; + display: flex; + align-items: center; + } + @media (min-width: 360px) { + & > div { + flex-direction: row; + } + } + `, +) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx new file mode 100644 index 000000000..766af96e7 --- /dev/null +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -0,0 +1,439 @@ +import { useConnectModal } from '@rainbow-me/rainbowkit' +import Head from 'next/head' +import { useState } from 'react' +import { TFunction } from 'react-i18next' +import styled, { css } from 'styled-components' +import { match } from 'ts-pattern' +import { Address } from 'viem' +import { useAccount } from 'wagmi' + +import { GetNamesForAddressParameters } from '@ensdomains/ensjs/subgraph' +import { + Banner, + Button, + Card, + GasPumpSVG, + KeySVG, + RightArrowSVG, + Typography, + UpCircleSVG, + WalletSVG, +} from '@ensdomains/thorin' + +import { Carousel } from '@app/components/pages/migrate/Carousel' +import { useNamesForAddress } from '@app/hooks/ensjs/subgraph/useNamesForAddress' +import { useApprovedNamesForMigration } from '@app/hooks/migration/useApprovedNamesForMigration' +import { makeIntroItem } from '@app/transaction-flow/intro' +import { createTransactionItem, TransactionData } from '@app/transaction-flow/transaction' +import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' + +import { MigrationNamesList, NameListTab } from './MigrationNamesList' +import { MigrationSection } from './MigrationSection' + +const Header = styled.header<{ $isLanding?: boolean }>( + ({ theme, $isLanding }) => css` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: ${theme.space['4']}; + gap: ${theme.space['4']}; + min-height: ${$isLanding ? '530px' : 'unset'}; + `, +) + +const ButtonContainer = styled.div( + ({ theme }) => css` + display: flex; + gap: ${theme.space['2']}; + flex-direction: column; + + @media (min-width: 360px) { + flex-direction: row; + } + `, +) + +const Caption = styled(Typography)` + text-align: center; + max-width: 538px; +` + +const GetStarted = styled(MigrationSection)( + ({ theme }) => css` + & > div button span { + display: flex; + flex-direction: row; + align-items: center; + gap: ${theme.space['2']}; + } + `, +) + +const CenteredCard = styled(Card)` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +` + +const CardWithEmoji = styled(CenteredCard)` + 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 Heading = styled.h1( + ({ theme }) => css` + font-size: 52px; + font-size: 850; + color: ${theme.colors.textPrimary}; + text-align: center; + line-height: 104%; + + @media (min-width: 360px) { + font-size: 60px; + } + @media (min-width: 640px) { + font-size: 76px; + } + `, +) + +const GradientText = styled.span` + font-weight: inherit; + font-size: inherit; + line-height: inherit; + background: linear-gradient(90deg, #199c75 2.87%, #9b9ba7 97.95%); + background-clip: text; + -webkit-text-fill-color: transparent; +` + +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 const migrationTabs = ['ensv2', 'migrations', 'extension'] as const + +export type MigrationTabType = (typeof migrationTabs)[number] + +const LandingTab = ({ + t, + isConnected, + openConnectModal, + setTab, +}: { + t: TFunction + isConnected: boolean + openConnectModal?: () => void + setTab: (tab: MigrationTabType) => void +}) => { + return ( + <> +
+ + {t('title.landing')} + + {t('title.landing')} + {/** @ts-expect-error styled-components don't know how to inherit types */} + {t('caption.ensv2')} + + + + +
+ + + 🎉 + + {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('get-started.title')} + + + + + + {t('get-started.upgrade.title')} + + {t('get-started.upgrade.caption')} + + + + + + + {t('announcement.title')} + + + + + + + + + ) +} + +const filter: Record = { + eligible: { owner: false, wrappedOwner: true, registrant: true, resolvedAddress: false }, + ineligible: { owner: true, wrappedOwner: false, registrant: false, resolvedAddress: false }, + approved: { owner: false, wrappedOwner: true, registrant: true, resolvedAddress: false }, +} + +const MigrationsTab = ({ + t, + isConnected, + openConnectModal, + address, +}: { + t: TFunction + isConnected: boolean + openConnectModal?: () => void + address?: Address +}) => { + const [activeNameListTab, setNameListTab] = useState('eligible') + + const { infiniteData } = useNamesForAddress({ + address, + pageSize: 20, + filter: filter[activeNameListTab], + }) + + const names = infiniteData.filter( + (name) => + name.parentName === 'eth' && + (activeNameListTab === 'ineligible' ? name.registrant !== name.owner : true), + ) + + const approvedNames = useApprovedNamesForMigration({ names }) + + const { createTransactionFlow } = useTransactionFlow() + + return ( + <> +
+ + {t('title.migration')} + + {t('title.migration')} + {/** @ts-expect-error styled-components don't know how to inherit types */} + {t('caption.migration')} + + + + +
+ {isConnected ? ( + + ) : null} + + + {t('approval-benefits.title')} + +
+ + + + {t('approval-benefits.migration.title')} + + {t('approval-benefits.migration.caption')} + + + + + {t('approval-benefits.no-gas-cost.title')} + + {t('approval-benefits.no-gas-cost.caption')} + +
+
+ + ) +} + +const ExtensionTab = ({ t, isConnected }: { t: TFunction; isConnected: boolean }) => { + return ( + <> +
+ + + {t('title.extension.part1')} {t('title.extension.part2')} + + + + {t('title.extension.part1')} {t('title.extension.part2')} + + bloop + + + + +
+ + ) +} + +export const MigrationTab = ({ + tab, + setTab, + t, +}: { + tab: MigrationTabType + setTab: (tab: MigrationTabType) => void + t: TFunction +}) => { + const { isConnected, address } = useAccount() + const { openConnectModal } = useConnectModal() + + return match(tab) + .with('ensv2', () => ) + .with('migrations', () => ) + .with('extension', () => ) + .exhaustive() +} diff --git a/src/pages/migrate.tsx b/src/pages/migrate.tsx index 38956a0ee..a3b38daaf 100644 --- a/src/pages/migrate.tsx +++ b/src/pages/migrate.tsx @@ -1,37 +1,25 @@ /* eslint-disable @next/next/no-img-element */ -import { useConnectModal } from '@rainbow-me/rainbowkit' -import Head from 'next/head' -import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' -import { match } from 'ts-pattern' -import { useAccount } from 'wagmi' -import { GetNamesForAddressParameters } from '@ensdomains/ensjs/subgraph' import { - Banner, Button, Card, - GasPumpSVG, InfoCircleSVG, - KeySVG, QuestionBubbleSVG, QuestionCircleSVG, RightArrowSVG, SpannerAltSVG, Typography, - UpCircleSVG, - WalletSVG, } from '@ensdomains/thorin' -import { Carousel } from '@app/components/pages/migrate/Carousel' -import { MigrationNamesList, NameListTab } from '@app/components/pages/migrate/MigrationNamesList' -import { useNamesForAddress } from '@app/hooks/ensjs/subgraph/useNamesForAddress' -import { useApprovedNamesForMigration } from '@app/hooks/migration/useApprovedNamesForMigration' +import { MigrationSection } from '@app/components/pages/migrate/MigrationSection' +import { + MigrationTab, + migrationTabs, + MigrationTabType, +} from '@app/components/pages/migrate/MigrationTab' import { useQueryParameterState } from '@app/hooks/useQueryParameterState' -import { makeIntroItem } from '@app/transaction-flow/intro' -import { createTransactionItem, TransactionData } from '@app/transaction-flow/transaction' -import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' import DAOSVG from '../assets/DAO.svg' import SocialX from '../assets/social/SocialX.svg' @@ -44,59 +32,6 @@ const Main = styled.main( `, ) -const Header = styled.header<{ $isLanding: boolean }>( - ({ theme, $isLanding }) => css` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: ${theme.space['4']}; - gap: ${theme.space['4']}; - min-height: ${$isLanding ? '530px' : 'unset'}; - `, -) - -const Heading = styled.h1( - ({ theme }) => css` - font-size: 52px; - font-size: 850; - color: ${theme.colors.textPrimary}; - text-align: center; - line-height: 104%; - & > span { - font-weight: inherit; - font-size: inherit; - line-height: inherit; - background: linear-gradient(90deg, #199c75 2.87%, #9b9ba7 97.95%); - background-clip: text; - -webkit-text-fill-color: transparent; - } - @media (min-width: 360px) { - font-size: 60px; - } - @media (min-width: 640px) { - font-size: 76px; - } - `, -) - -const ButtonContainer = styled.div( - ({ theme }) => css` - display: flex; - gap: ${theme.space['2']}; - flex-direction: column; - - @media (min-width: 360px) { - flex-direction: row; - } - `, -) - -const Caption = styled(Typography)` - text-align: center; - max-width: 538px; -` - const PartnershipAnnouncement = styled.div( ({ theme }) => css` width: ${theme.space.full}; @@ -120,76 +55,24 @@ const PartnershipAnnouncement = styled.div( `, ) -const CenteredCard = styled(Card)` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -` - -const CardWithEmoji = styled(CenteredCard)` - padding-top: 83px; - position: relative; - grid-column: 1 / -1; - - img { - position: absolute; - top: -72px; - } -` - -const CardHeader = styled.h3( +const TopNav = styled.div( ({ 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']}; + gap: ${theme.space['6']}; align-items: center; `, ) -const GridOneToThree = styled.div( +const TabManager = styled.div( ({ theme }) => css` - display: grid; - grid-template-rows: auto; + display: flex; + flex-direction: row; gap: ${theme.space['4']}; - text-align: center; - grid-template-columns: 1fr; - - @media (min-width: 640px) { - grid-template-columns: repeat(3, 1fr); - } `, ) -const Section = styled.div( - ({ theme }) => css` - display: flex; - flex-direction: column; - gap: ${theme.space['6']}; - h3 { - text-align: center; - } - & > div { - display: flex; - flex-direction: column; - gap: ${theme.space['4']}; - } - & > div > div { - width: 100%; - display: flex; - align-items: center; - } - @media (min-width: 360px) { - & > div { - flex-direction: row; - } - } - `, -) -const Footer = styled(Section)( +const Footer = styled(MigrationSection)( ({ theme }) => css` span { display: flex; @@ -201,87 +84,10 @@ const Footer = styled(Section)( `, ) -const GetStarted = styled(Section)( - ({ theme }) => css` - & > div button span { - display: flex; - flex-direction: row; - align-items: center; - gap: ${theme.space['2']}; - } - `, -) - -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; - } - `, -) - -const TopNav = styled.div( - ({ theme }) => css` - display: flex; - flex-direction: column; - gap: ${theme.space['6']}; - align-items: center; - `, -) - -const TabManager = styled.div( - ({ theme }) => css` - display: flex; - flex-direction: row; - gap: ${theme.space['4']}; - `, -) - -const tabs = ['ensv2', 'migrations', 'extension'] as const - -const filter: Record = { - eligible: { owner: false, wrappedOwner: true, registrant: true, resolvedAddress: false }, - ineligible: { owner: true, wrappedOwner: false, registrant: false, resolvedAddress: false }, - approved: { owner: false, wrappedOwner: true, registrant: true, resolvedAddress: false }, -} - -type Tab = (typeof tabs)[number] - export default function Page() { const { t } = useTranslation('migrate') - const { isConnected, address } = useAccount() - - const { openConnectModal } = useConnectModal() - - const [currentTab, setTab] = useQueryParameterState('tab', 'ensv2') - - const { createTransactionFlow } = useTransactionFlow() - - const [activeNameListTab, setNameListTab] = useState('eligible') - - const { infiniteData } = useNamesForAddress({ - address, - pageSize: 20, - filter: filter[activeNameListTab], - }) - - const names = infiniteData.filter( - (name) => - name.parentName === 'eth' && - (activeNameListTab === 'ineligible' ? name.registrant !== name.owner : true), - ) - - const approvedNames = useApprovedNamesForMigration({ names }) + const [currentTab, setTab] = useQueryParameterState('tab', 'ensv2') return ( <> @@ -294,7 +100,7 @@ export default function Page() { - {tabs.map((tab) => ( + {migrationTabs.map((tab) => ( - )) - .with('migrations', () => ( - - )) - .with('extension', () => ( - - )) - .exhaustive()} - - - - {match(currentTab) - .with('ensv2', () => ( - <> - - - 🎉 - - {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('get-started.title')} - -
- - - - {t('get-started.upgrade.title')} - - {t('get-started.upgrade.caption')} - - -
-
- - - {t('announcement.title')} - - - - - - - - - )) - .with('migrations', () => ( - <> - {isConnected ? ( - - ) : null} -
- - {t('approval-benefits.title')} - -
- - - - {t('approval-benefits.migration.title')} - - {t('approval-benefits.migration.caption')} - - - - - {t('approval-benefits.no-gas-cost.title')} - - {t('approval-benefits.no-gas-cost.caption')} - -
-
- - )) - .otherwise(() => ( -

bloop

- ))} +
From 3c590e13fb1a091ea8fcedf17a19356a454f645c Mon Sep 17 00:00:00 2001 From: v1rtl Date: Fri, 25 Oct 2024 17:48:08 +0300 Subject: [PATCH 03/24] fix styles --- src/components/pages/migrate/MigrationTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 766af96e7..9e881b8d3 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -59,7 +59,7 @@ const Caption = styled(Typography)` max-width: 538px; ` -const GetStarted = styled(MigrationSection)( +const GetStarted = styled.div( ({ theme }) => css` & > div button span { display: flex; From 0f3058fb4c9b66aeda7346473f523a3c2865af48 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sat, 26 Oct 2024 17:01:31 +0300 Subject: [PATCH 04/24] fet-1670 --- src/components/pages/migrate/MigrationTab.tsx | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 9e881b8d3..eabd1ac93 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -395,7 +395,27 @@ const MigrationsTab = ({ ) } -const ExtensionTab = ({ t, isConnected }: { t: TFunction; isConnected: boolean }) => { +const ExtensionTab = ({ + t, + isConnected, + address, +}: { + t: TFunction + isConnected: boolean + address?: Address +}) => { + const { infiniteData } = useNamesForAddress({ + address, + pageSize: 20, + filter: filter.eligible, + }) + + const names = infiniteData.filter((name) => name.parentName === 'eth') + + const approvedNames = useApprovedNamesForMigration({ names }) + + const allNamesAreApproved = approvedNames.length === names.length + return ( <>
@@ -410,7 +430,11 @@ const ExtensionTab = ({ t, isConnected }: { t: TFunction; isConnected: boolean } bloop @@ -434,6 +458,6 @@ export const MigrationTab = ({ return match(tab) .with('ensv2', () => ) .with('migrations', () => ) - .with('extension', () => ) + .with('extension', () => ) .exhaustive() } From 9f9100734578038b67546bf132b9bf5cf2915bff Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sat, 26 Oct 2024 17:07:37 +0300 Subject: [PATCH 05/24] add banner --- src/components/pages/migrate/MigrationTab.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index eabd1ac93..15d31cea4 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -157,6 +157,20 @@ const SlideshowContainer = styled.div( `, ) +const AllNamesAreApprovedBanner = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + flex: 1 0 0; + align-items: center; + border-radius: ${theme.radii['4xLarge']}; + gap: ${theme.space['2']}; + padding: ${theme.space['4']} ${theme.space['6']}; + background: ${theme.colors.greenSurface}; + border: 1px solid ${theme.colors.greenPrimary}; + `, +) + export const migrationTabs = ['ensv2', 'migrations', 'extension'] as const export type MigrationTabType = (typeof migrationTabs)[number] @@ -305,6 +319,8 @@ const MigrationsTab = ({ const approvedNames = useApprovedNamesForMigration({ names }) + const allNamesAreApproved = approvedNames.length === names.length + const { createTransactionFlow } = useTransactionFlow() return ( @@ -363,6 +379,9 @@ const MigrationsTab = ({
+ {allNamesAreApproved ? ( + {t('banner.all-approved')} + ) : null} {isConnected ? ( Date: Sat, 26 Oct 2024 17:42:44 +0300 Subject: [PATCH 06/24] make more responsive --- .../pages/migrate/MigrationSection.tsx | 8 ++-- src/components/pages/migrate/MigrationTab.tsx | 46 ++++++++++++++++--- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/components/pages/migrate/MigrationSection.tsx b/src/components/pages/migrate/MigrationSection.tsx index 2a3b3937e..3762c9bdb 100644 --- a/src/components/pages/migrate/MigrationSection.tsx +++ b/src/components/pages/migrate/MigrationSection.tsx @@ -9,8 +9,8 @@ export const MigrationSection = styled.div( text-align: center; } & > div { - display: flex; - flex-direction: column; + display: grid; + grid-template-columns: repeat(1, 1fr); gap: ${theme.space['4']}; } & > div > div { @@ -18,9 +18,9 @@ export const MigrationSection = styled.div( display: flex; align-items: center; } - @media (min-width: 360px) { + @media (min-width: 480px) { & > div { - flex-direction: row; + grid-template-columns: repeat(2, 1fr); } } `, diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 15d31cea4..1fab0f54f 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -12,6 +12,7 @@ import { Banner, Button, Card, + FastForwardSVG, GasPumpSVG, KeySVG, RightArrowSVG, @@ -59,9 +60,9 @@ const Caption = styled(Typography)` max-width: 538px; ` -const GetStarted = styled.div( +const ContainerWithCenteredButton = styled.div( ({ theme }) => css` - & > div button span { + button span { display: flex; flex-direction: row; align-items: center; @@ -171,6 +172,13 @@ const AllNamesAreApprovedBanner = styled.div( `, ) +const RebatesMigrationSection = styled(MigrationSection)` + & > div > div:nth-child(3) { + grid-column: 1 / -1; + grid-row: 2; + } +` + export const migrationTabs = ['ensv2', 'migrations', 'extension'] as const export type MigrationTabType = (typeof migrationTabs)[number] @@ -248,7 +256,7 @@ const LandingTab = ({ {t('get-started.title')} - + @@ -261,7 +269,19 @@ const LandingTab = ({ - + + + + {t('get-started.extend.title')} + + {t('get-started.extend.caption')} + + + @@ -389,7 +409,7 @@ const MigrationsTab = ({ setTab={setNameListTab} /> ) : null} - + {t('approval-benefits.title')} @@ -408,8 +428,22 @@ const MigrationsTab = ({ {t('approval-benefits.no-gas-cost.caption')} + + + + {t('approval-benefits.claim-rebates.title')} + + {t('approval-benefits.claim-rebates.caption')} + + + + - + ) } From d0afa2be36dda88c028c73409cc317a985a993ad Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sat, 26 Oct 2024 19:03:09 +0300 Subject: [PATCH 07/24] wip --- .../pages/migrate/MigrationNamesList.tsx | 14 +- src/components/pages/migrate/MigrationTab.tsx | 140 +++++++++++++----- 2 files changed, 113 insertions(+), 41 deletions(-) diff --git a/src/components/pages/migrate/MigrationNamesList.tsx b/src/components/pages/migrate/MigrationNamesList.tsx index 9b7f3c6e0..174d15d49 100644 --- a/src/components/pages/migrate/MigrationNamesList.tsx +++ b/src/components/pages/migrate/MigrationNamesList.tsx @@ -8,17 +8,13 @@ import { CheckCircleSVG, Colors, DisabledSVG, PlusCircleSVG } from '@ensdomains/ import { ensAvatarConfig } from '@app/utils/query/ipfsGateway' import { formatDurationOfDates } from '@app/utils/utils' -const tabs = ['eligible', 'ineligible', 'approved'] as const - -type Tab = (typeof tabs)[number] - -const icons: Record = { +const icons: Record = { eligible: , ineligible: , approved: , } -const colors: Record = { +const colors: Record = { eligible: { fg: 'bluePrimary', bg: 'blueSurface', @@ -56,7 +52,7 @@ const TabsContainer = styled.div( `, ) -const TabButton = styled.button<{ $isActive?: boolean; tab: Tab }>( +const TabButton = styled.button<{ $isActive?: boolean; tab: NameListTab }>( ({ theme, $isActive, tab }) => css` width: ${theme.space.full}; padding: 0 ${theme.space['4']}; @@ -134,7 +130,7 @@ const MigrationName = ({ name, t }: { name: NameWithRelation; t: TFunction }) => return ( {avatar && } - {name.name} + {name.truncatedName} Expires in {expiresIn} ) @@ -144,10 +140,12 @@ export const MigrationNamesList = ({ activeTab, setTab, names, + tabs, }: { activeTab: NameListTab names: NameWithRelation[] setTab: (tab: NameListTab) => void + tabs: NameListTab[] }) => { const { t } = useTranslation('migrate') diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 1fab0f54f..395c9d279 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -7,7 +7,7 @@ import { match } from 'ts-pattern' import { Address } from 'viem' import { useAccount } from 'wagmi' -import { GetNamesForAddressParameters } from '@ensdomains/ensjs/subgraph' +import { NameWithRelation } from '@ensdomains/ensjs/subgraph' import { Banner, Button, @@ -188,11 +188,15 @@ const LandingTab = ({ isConnected, openConnectModal, setTab, + eligibleNames, + approvedNames, }: { t: TFunction isConnected: boolean openConnectModal?: () => void setTab: (tab: MigrationTabType) => void + eligibleNames: NameWithRelation[] + approvedNames: NameWithRelation[] }) => { return ( <> @@ -214,7 +218,18 @@ const LandingTab = ({ }} colorStyle="greenPrimary" > - {isConnected ? t('cta.approve') : t('cta.unconnected')} + {match({ + isConnected, + eligible: eligibleNames.length !== 0, + approved: approvedNames.length !== 0, + }) + .with({ isConnected: true, eligible: true, approved: false }, () => t('cta.approve')) + .with({ isConnected: true, eligible: false, approved: false }, () => t('cta.begin')) + .with({ isConnected: true, eligible: true, approved: true }, () => + t('cta.extend-names'), + ) + .with({ isConnected: false }, () => t('cta.unconnected')) + .otherwise(() => null)} @@ -275,7 +290,7 @@ const LandingTab = ({ {t('get-started.extend.title')} {t('get-started.extend.caption')} - - {allNamesAreApproved ? ( + {approvedNames.length === eligibleNames.length ? ( {t('banner.all-approved')} ) : null} {isConnected ? ( ) : null} @@ -435,7 +478,7 @@ const MigrationsTab = ({ {t('approval-benefits.claim-rebates.caption')} - + {/* {match({ isConnected, allNamesAreApproved }) + .with({ isConnected: true, allNamesAreApproved: true }, () => ( + + )) + .with({ isConnected: false }, () => null) + .exhaustive()} */} ) From 1f2bbbe3d742bffd60828c34f12a3f2f89d5028b Mon Sep 17 00:00:00 2001 From: v1rtl Date: Mon, 28 Oct 2024 13:37:25 +0200 Subject: [PATCH 09/24] wire up MigrationHelper --- .../pages/migrate/MigrationNamesList.tsx | 8 ++--- src/components/pages/migrate/MigrationTab.tsx | 35 +++++++++++++------ .../migration/useApprovedNamesForMigration.ts | 8 +++-- src/migrationHelper/migrationHelper.ts | 9 +++++ .../approveNameWrapperForMigration.ts | 9 ++--- .../approveRegistrarForMigration.ts | 9 ++--- 6 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 src/migrationHelper/migrationHelper.ts diff --git a/src/components/pages/migrate/MigrationNamesList.tsx b/src/components/pages/migrate/MigrationNamesList.tsx index fa34762d4..a09c2f7f9 100644 --- a/src/components/pages/migrate/MigrationNamesList.tsx +++ b/src/components/pages/migrate/MigrationNamesList.tsx @@ -144,16 +144,16 @@ const MigrationName = ({ name, t }: { name: NameWithRelation; t: TFunction }) => ) } -export const MigrationNamesList = ({ +export const MigrationNamesList = ({ activeTab, setTab, names, tabs, }: { - activeTab: NameListTab + activeTab: T names: NameWithRelation[] - setTab: (tab: NameListTab) => void - tabs: NameListTab[] + setTab: (tab: T) => void + tabs: T[] }) => { const { t } = useTranslation('migrate') diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 45ce7991f..31545462c 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -321,11 +321,15 @@ const LandingTab = ({ ) } +type MigrationHelperTab = Exclude + const filterNames = (names: NameWithRelation[]) => { const eligibleNames: NameWithRelation[] = [] const inelegibleNames: NameWithRelation[] = [] - for (const name of names) { + for (const name of names.filter( + ({ expiryDate }) => expiryDate?.date && expiryDate?.date > new Date(), + )) { if (name.parentName === 'eth' && (name.relation.wrappedOwner || name.relation.registrant)) { eligibleNames.push(name) } else { @@ -344,7 +348,7 @@ const filterTabs = ({ eligibleNames: NameWithRelation[] inelegibleNames: NameWithRelation[] }) => { - const tabs: ('eligible' | 'ineligible' | 'approved')[] = [] + const tabs: MigrationHelperTab[] = [] if (eligibleNames.length) { tabs.push('eligible') @@ -380,10 +384,16 @@ const MigrationsTab = ({ inelegibleNames: NameWithRelation[] approvedNames: NameWithRelation[] }) => { + const tabs = filterTabs({ approvedNames, eligibleNames, inelegibleNames }) + const { createTransactionFlow } = useTransactionFlow() - const [activeNameListTab, setNameListTab] = useState('eligible') + const [activeNameListTab, setNameListTab] = useState(tabs[0]) - const tabs = filterTabs({ approvedNames, eligibleNames, inelegibleNames }) + const names: Record = { + eligible: eligibleNames, + ineligible: inelegibleNames, + approved: approvedNames, + } return ( <> @@ -441,12 +451,12 @@ const MigrationsTab = ({ - {approvedNames.length === eligibleNames.length ? ( + {approvedNames.length !== 0 && approvedNames.length === eligibleNames.length ? ( {t('banner.all-approved')} ) : null} {isConnected ? ( name.parentName === 'eth') + const names = infiniteData.filter((name) => name.parentName === 'eth' && name.expiryDate) - const approvedNames = useApprovedNamesForMigration({ names }) + const approvedNames = useApprovedNamesForMigration({ names, owner: address }) const allNamesAreApproved = approvedNames.length !== 0 && approvedNames.length === names.length @@ -574,9 +584,14 @@ export const MigrationTab = ({ filter: { registrant: true, owner: true, resolvedAddress: true, wrappedOwner: true }, }) - const { eligibleNames, inelegibleNames } = filterNames(names) + const { eligibleNames: initialEligibleNames, inelegibleNames } = filterNames(names) + + const approvedNames = useApprovedNamesForMigration({ + names: initialEligibleNames, + owner: address, + }) - const approvedNames = useApprovedNamesForMigration({ names: eligibleNames }) + const eligibleNames = initialEligibleNames.filter((name) => !approvedNames.includes(name)) return match(tab) .with('ensv2', () => ( diff --git a/src/hooks/migration/useApprovedNamesForMigration.ts b/src/hooks/migration/useApprovedNamesForMigration.ts index d9a6bfe8c..ccf609f0f 100644 --- a/src/hooks/migration/useApprovedNamesForMigration.ts +++ b/src/hooks/migration/useApprovedNamesForMigration.ts @@ -1,13 +1,17 @@ -import { erc721Abi } from 'viem' +import { Address, erc721Abi } from 'viem' import { usePublicClient, useReadContracts } from 'wagmi' import { getChainContractAddress } from '@ensdomains/ensjs/contracts' import { NameWithRelation } from '@ensdomains/ensjs/subgraph' +import { migrationHelperContract } from '@app/migrationHelper/migrationHelper' + export const useApprovedNamesForMigration = ({ names, + owner, }: { names: NameWithRelation[] + owner?: Address }): NameWithRelation[] => { const client = usePublicClient() @@ -19,7 +23,7 @@ export const useApprovedNamesForMigration = ({ }), abi: erc721Abi, functionName: 'isApprovedForAll', - args: ['contract-go-here', true], + args: [owner, migrationHelperContract[client.chain.id]], })), multicallAddress: getChainContractAddress({ client, contract: 'multicall3' }), }) diff --git a/src/migrationHelper/migrationHelper.ts b/src/migrationHelper/migrationHelper.ts new file mode 100644 index 000000000..4d62f698b --- /dev/null +++ b/src/migrationHelper/migrationHelper.ts @@ -0,0 +1,9 @@ +import { goerli, holesky, localhost, mainnet, sepolia } from 'viem/chains' + +export const migrationHelperContract = { + [mainnet.id]: '0xnot-deployed-yet', + [localhost.id]: '0xnot-deployed-yet', + [goerli.id]: '0xwillnotdeploy', + [holesky.id]: '0x76aafA281Ed5155f83926a12ACB92e237e322A8C', + [sepolia.id]: '0xf9c8c83adda8d52d9284cdbef23da10b5f9869bf', +} as const diff --git a/src/transaction-flow/transaction/approveNameWrapperForMigration.ts b/src/transaction-flow/transaction/approveNameWrapperForMigration.ts index 5e3e85bd9..47811ab48 100644 --- a/src/transaction-flow/transaction/approveNameWrapperForMigration.ts +++ b/src/transaction-flow/transaction/approveNameWrapperForMigration.ts @@ -6,6 +6,7 @@ import { registrySetApprovalForAllSnippet, } from '@ensdomains/ensjs/contracts' +import { migrationHelperContract } from '@app/migrationHelper/migrationHelper' import { Transaction, TransactionDisplayItem, TransactionFunctionParameters } from '@app/types' type Data = { address: Address } @@ -38,13 +39,7 @@ const transaction = async ({ client }: TransactionFunctionParameters) => { data: encodeFunctionData({ abi: registrySetApprovalForAllSnippet, functionName: 'setApprovalForAll', - args: [ - getChainContractAddress({ - client, - contract: 'contract-address-will-go-here', - }), - true, - ], + args: [migrationHelperContract[client.chain.id], true], }), } } diff --git a/src/transaction-flow/transaction/approveRegistrarForMigration.ts b/src/transaction-flow/transaction/approveRegistrarForMigration.ts index 9ffee6155..885a37eee 100644 --- a/src/transaction-flow/transaction/approveRegistrarForMigration.ts +++ b/src/transaction-flow/transaction/approveRegistrarForMigration.ts @@ -7,6 +7,7 @@ import { registrySetApprovalForAllSnippet, } from '@ensdomains/ensjs/contracts' +import { migrationHelperContract } from '@app/migrationHelper/migrationHelper' import type { Transaction, TransactionDisplayItem, TransactionFunctionParameters } from '@app/types' type Data = { address: Address } @@ -39,13 +40,7 @@ const transaction = async ({ client }: TransactionFunctionParameters) => { data: encodeFunctionData({ abi: registrySetApprovalForAllSnippet, functionName: 'setApprovalForAll', - args: [ - getChainContractAddress({ - client, - contract: 'address-will-go-here', - }), - true, - ], + args: [migrationHelperContract[client.chain.id], true], }), } } From a3cc6b30494311752c4cd02f673d0253f9accaca Mon Sep 17 00:00:00 2001 From: v1rtl Date: Mon, 28 Oct 2024 13:47:44 +0200 Subject: [PATCH 10/24] properly render cta button --- src/components/pages/migrate/MigrationTab.tsx | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 31545462c..f6df6944a 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -188,15 +188,15 @@ const LandingTab = ({ isConnected, openConnectModal, setTab, - eligibleNames, - approvedNames, + allNamesAreApproved, + eligibleToApprove, }: { t: TFunction isConnected: boolean openConnectModal?: () => void setTab: (tab: MigrationTabType) => void - eligibleNames: NameWithRelation[] - approvedNames: NameWithRelation[] + allNamesAreApproved: boolean + eligibleToApprove: boolean }) => { return ( <> @@ -211,7 +211,7 @@ const LandingTab = ({ @@ -374,6 +374,7 @@ const MigrationsTab = ({ eligibleNames, inelegibleNames, approvedNames, + allNamesAreApproved, }: { t: TFunction isConnected: boolean @@ -383,6 +384,7 @@ const MigrationsTab = ({ eligibleNames: NameWithRelation[] inelegibleNames: NameWithRelation[] approvedNames: NameWithRelation[] + allNamesAreApproved: boolean }) => { const tabs = filterTabs({ approvedNames, eligibleNames, inelegibleNames }) @@ -451,7 +453,7 @@ const MigrationsTab = ({ - {approvedNames.length !== 0 && approvedNames.length === eligibleNames.length ? ( + {allNamesAreApproved ? ( {t('banner.all-approved')} ) : null} {isConnected ? ( @@ -593,9 +595,15 @@ export const MigrationTab = ({ const eligibleNames = initialEligibleNames.filter((name) => !approvedNames.includes(name)) + const allNamesAreApproved = approvedNames.length !== 0 && approvedNames.length === names.length + + const eligibleToApprove = eligibleNames.length !== 0 + return match(tab) .with('ensv2', () => ( - + )) .with('migrations', () => ( )) From 8f78445d2e58211aa381e1faa945d611fd47b8ce Mon Sep 17 00:00:00 2001 From: v1rtl Date: Mon, 28 Oct 2024 14:20:32 +0200 Subject: [PATCH 11/24] name list for rebates --- src/components/pages/migrate/MigrationTab.tsx | 105 +++++++++++------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index f6df6944a..3fd9abfcf 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -323,7 +323,7 @@ const LandingTab = ({ type MigrationHelperTab = Exclude -const filterNames = (names: NameWithRelation[]) => { +const filterNamesForMigration = (names: NameWithRelation[]) => { const eligibleNames: NameWithRelation[] = [] const inelegibleNames: NameWithRelation[] = [] @@ -339,27 +339,33 @@ const filterNames = (names: NameWithRelation[]) => { return { eligibleNames, inelegibleNames } } -const filterTabs = ({ - approvedNames, - eligibleNames, - inelegibleNames, -}: { +const filterTabs = ({ + approvedNames = [], + eligibleNames = [], + inelegibleNames = [], + claimedNames = [], +}: Partial<{ approvedNames: NameWithRelation[] eligibleNames: NameWithRelation[] inelegibleNames: NameWithRelation[] -}) => { - const tabs: MigrationHelperTab[] = [] + claimedNames: NameWithRelation[] +}>) => { + const tabs: T[] = [] if (eligibleNames.length) { - tabs.push('eligible') + tabs.push('eligible' as T) } if (inelegibleNames.length) { - tabs.push('ineligible') + tabs.push('ineligible' as T) } if (approvedNames.length) { - tabs.push('approved') + tabs.push('approved' as T) + } + + if (claimedNames.length) { + tabs.push('claimed' as T) } return tabs @@ -386,7 +392,7 @@ const MigrationsTab = ({ approvedNames: NameWithRelation[] allNamesAreApproved: boolean }) => { - const tabs = filterTabs({ approvedNames, eligibleNames, inelegibleNames }) + const tabs = filterTabs({ approvedNames, eligibleNames, inelegibleNames }) const { createTransactionFlow } = useTransactionFlow() const [activeNameListTab, setNameListTab] = useState(tabs[0]) @@ -503,32 +509,46 @@ const MigrationsTab = ({ ) } -const extensionTabs = ['eligible', 'claimed'] as const - -type ExtensionTabType = (typeof extensionTabs)[number] +type ExtensionTabType = Exclude const ExtensionTab = ({ t, isConnected, address, + allNamesAreApproved, + setTab, }: { t: TFunction isConnected: boolean address?: Address + allNamesAreApproved: boolean + setTab: (tab: MigrationTabType) => void }) => { const { infiniteData } = useNamesForAddress({ address, pageSize: 20, filter: { owner: false, wrappedOwner: true, registrant: true, resolvedAddress: false }, + enabled: allNamesAreApproved, }) - const names = infiniteData.filter((name) => name.parentName === 'eth' && name.expiryDate) + const allNames = infiniteData.filter((name) => name.parentName === 'eth' && name.expiryDate) - const approvedNames = useApprovedNamesForMigration({ names, owner: address }) + const [activeTab, setNameListTab] = useState('eligible') - const allNamesAreApproved = approvedNames.length !== 0 && approvedNames.length === names.length + const claimedNames = allNames.filter( + (name) => name.expiryDate && name.expiryDate.date > new Date(2030, 12, 31), + ) + + const eligibleNames = allNames.filter((name) => !claimedNames.includes(name)) + + const { openConnectModal } = useConnectModal() + + const names: Record = { + claimed: claimedNames, + eligible: eligibleNames, + } - const [activeTab, setTab] = useState('eligible') + const tabs = filterTabs({ claimedNames, eligibleNames }) return ( <> @@ -543,27 +563,32 @@ const ExtensionTab = ({ bloop - + {match({ isConnected, allNamesAreApproved }) + .with({ isConnected: true, allNamesAreApproved: true }, () => ( + + )) + .with({ isConnected: true, allNamesAreApproved: false }, () => ( + + )) + .with({ isConnected: false }, () => ( + + )) + .exhaustive()} - {/* {match({ isConnected, allNamesAreApproved }) - .with({ isConnected: true, allNamesAreApproved: true }, () => ( - - )) - .with({ isConnected: false }, () => null) - .exhaustive()} */} + {isConnected ? ( + + ) : null} ) } @@ -586,7 +611,7 @@ export const MigrationTab = ({ filter: { registrant: true, owner: true, resolvedAddress: true, wrappedOwner: true }, }) - const { eligibleNames: initialEligibleNames, inelegibleNames } = filterNames(names) + const { eligibleNames: initialEligibleNames, inelegibleNames } = filterNamesForMigration(names) const approvedNames = useApprovedNamesForMigration({ names: initialEligibleNames, @@ -620,6 +645,8 @@ export const MigrationTab = ({ }} /> )) - .with('extension', () => ) + .with('extension', () => ( + + )) .exhaustive() } From 018ffcf95c7b6a1dc46517474935fd55d18b2953 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Mon, 28 Oct 2024 15:20:28 +0200 Subject: [PATCH 12/24] don't render if container is empty --- src/components/pages/migrate/MigrationNamesList.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/pages/migrate/MigrationNamesList.tsx b/src/components/pages/migrate/MigrationNamesList.tsx index a09c2f7f9..fbd6b801d 100644 --- a/src/components/pages/migrate/MigrationNamesList.tsx +++ b/src/components/pages/migrate/MigrationNamesList.tsx @@ -157,6 +157,8 @@ export const MigrationNamesList = ({ }) => { const { t } = useTranslation('migrate') + if (!tabs.length) return null + return ( From 0d8650d35158671653c9de415f58826138420974 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Mon, 28 Oct 2024 17:07:34 +0200 Subject: [PATCH 13/24] fix rebates display list --- src/components/pages/migrate/MigrationTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 3fd9abfcf..33a6d9f83 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -536,7 +536,7 @@ const ExtensionTab = ({ const [activeTab, setNameListTab] = useState('eligible') const claimedNames = allNames.filter( - (name) => name.expiryDate && name.expiryDate.date > new Date(2030, 12, 31), + (name) => name.expiryDate && name.expiryDate.date > new Date(2030, 11, 31, 0, 0), ) const eligibleNames = allNames.filter((name) => !claimedNames.includes(name)) From 2abb9068a7a18c2f5e6cea613b07f8f2ae7a83d3 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Tue, 29 Oct 2024 20:36:03 +0200 Subject: [PATCH 14/24] wip calendar --- src/components/pages/migrate/Carousel.tsx | 5 ++- src/components/pages/migrate/MigrationTab.tsx | 12 +++++- .../migration/useApprovedNamesForMigration.ts | 2 +- src/migration/bulkRenewal.ts | 43 +++++++++++++++++++ .../migrationHelper.ts | 0 .../input/BulkRenewal/BulkRenewal-flow.tsx | 23 ++++++++++ src/transaction-flow/input/index.tsx | 4 ++ .../approveNameWrapperForMigration.ts | 2 +- .../approveRegistrarForMigration.ts | 2 +- 9 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 src/migration/bulkRenewal.ts rename src/{migrationHelper => migration}/migrationHelper.ts (100%) create mode 100644 src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx diff --git a/src/components/pages/migrate/Carousel.tsx b/src/components/pages/migrate/Carousel.tsx index 4c56f4bcd..26fc3ac64 100644 --- a/src/components/pages/migrate/Carousel.tsx +++ b/src/components/pages/migrate/Carousel.tsx @@ -12,8 +12,9 @@ export const Carousel = ({ children }: { children: ReactNode[] }) => { fixedWidth: 312, }} > - {children.map((child) => ( - {child} + {children.map((child, i) => ( + // eslint-disable-next-line react/no-array-index-key + {child} ))} ) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 33a6d9f83..4bbe21bba 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -550,6 +550,9 @@ const ExtensionTab = ({ const tabs = filterTabs({ claimedNames, eligibleNames }) + const { usePreparedDataInput } = useTransactionFlow() + const showExtendNamesInput = usePreparedDataInput('BulkRenewal') + return ( <>
@@ -565,7 +568,14 @@ const ExtensionTab = ({ {match({ isConnected, allNamesAreApproved }) .with({ isConnected: true, allNamesAreApproved: true }, () => ( - + )) .with({ isConnected: true, allNamesAreApproved: false }, () => ( + {match({ isConnected, allNamesAreApproved }) + .with({ isConnected: true, allNamesAreApproved: false }, () => ( + + )) + .with({ isConnected: true, allNamesAreApproved: true }, () => null) + .with({ isConnected: false }, () => ( + + )) + .exhaustive()} +
@@ -571,7 +580,7 @@ const ExtensionTab = ({ + + ) : null} + + ) + } return ( {avatar && } {name.truncatedName} Expires in {expiresIn} + Not owner ) } @@ -149,13 +204,16 @@ export const MigrationNamesList = ({ setTab, names, tabs, + mode, }: { activeTab: T names: NameWithRelation[] setTab: (tab: T) => void tabs: T[] + mode: 'migration' | 'extension' }) => { - const { t } = useTranslation('migrate') + const { t } = useTranslation(['migrate', 'common']) + const { address } = useAccount() if (!tabs.length) return null @@ -170,7 +228,7 @@ export const MigrationNamesList = ({
{names.map((name) => ( - + ))}
diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index bf1950728..90bf71be7 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -331,7 +331,7 @@ const filterNamesForMigration = (names: NameWithRelation[]) => { for (const name of names.filter( ({ expiryDate }) => expiryDate?.date && expiryDate?.date > new Date(), )) { - if (name.parentName === 'eth' && (name.relation.wrappedOwner || name.relation.registrant)) { + if (name.relation.wrappedOwner || name.relation.registrant) { eligibleNames.push(name) } else { inelegibleNames.push(name) @@ -478,6 +478,7 @@ const MigrationsTab = ({ activeTab={activeNameListTab} setTab={setNameListTab} tabs={tabs} + mode="migration" /> ) : null} @@ -603,6 +604,7 @@ const ExtensionTab = ({ {isConnected ? ( name.parentName === 'eth') + const { eligibleNames: initialEligibleNames, inelegibleNames } = filterNamesForMigration(names) const approvedNames = useApprovedNamesForMigration({ From 86f18a0aef217c71f07fd3cb7e90b9b897cc4f92 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 30 Oct 2024 00:04:20 +0200 Subject: [PATCH 19/24] display elegible tokens --- .../pages/migrate/EligibleForTokens.tsx | 79 +++++++++++++++++++ .../pages/migrate/MigrationNamesList.tsx | 20 ++++- .../input/BulkRenewal/BulkRenewal-flow.tsx | 37 +-------- .../input/ExtendNames/ExtendNames-flow.tsx | 2 + 4 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 src/components/pages/migrate/EligibleForTokens.tsx diff --git a/src/components/pages/migrate/EligibleForTokens.tsx b/src/components/pages/migrate/EligibleForTokens.tsx new file mode 100644 index 000000000..6af934334 --- /dev/null +++ b/src/components/pages/migrate/EligibleForTokens.tsx @@ -0,0 +1,79 @@ +import Link from 'next/link' +import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' + +import { InfoCircleSVG, OutlinkSVG, Typography } from '@ensdomains/thorin' + +import { REBATE_DATE } from '@app/utils/constants' + +const EligibleForTokensContainer = styled.div( + ({ theme }) => css` + padding: ${theme.space['4']}; + gap: ${theme.space['2']}; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + width: ${theme.space.full}; + border-radius: ${theme.radii['2xLarge']}; + background: ${theme.colors.greenSurface}; + + a { + display: flex; + flex-direction: row; + align-items: center; + gap: ${theme.space['2']}; + color: ${theme.colors.greenPrimary}; + } + `, +) + +const InelegibleForTokensContainer = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: row; + background: ${theme.colors.greenSurface}; + padding: ${theme.space['4']}; + gap: ${theme.space['4']}; + border-radius: ${theme.radii.large}; + align-items: center; + width: 100%; + svg { + color: ${theme.colors.greenDim}; + } + `, +) + +export const EligibleForTokens = ({ + amount, + extendedDate, +}: { + amount: number + extendedDate?: Date +}) => { + const { t } = useTranslation('common') + + if (!extendedDate) return null + + if (extendedDate < REBATE_DATE) { + return ( + + + names expiring in 2031 smth smth + + ) + } + + return ( + + Eligible for {amount} $ENS + something something + + + {t('action.learnMore')} + + + + + ) +} diff --git a/src/components/pages/migrate/MigrationNamesList.tsx b/src/components/pages/migrate/MigrationNamesList.tsx index 2dc4df7c7..331476fb6 100644 --- a/src/components/pages/migrate/MigrationNamesList.tsx +++ b/src/components/pages/migrate/MigrationNamesList.tsx @@ -8,6 +8,7 @@ import { useAccount, useEnsAvatar } from 'wagmi' import { NameWithRelation } from '@ensdomains/ensjs/subgraph' import { Button, CheckCircleSVG, Colors, DisabledSVG, PlusCircleSVG, Tag } from '@ensdomains/thorin' +import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' import { ensAvatarConfig } from '@app/utils/query/ipfsGateway' import { formatDurationOfDates } from '@app/utils/utils' @@ -152,6 +153,21 @@ const TagList = ({ name, address }: { name: NameWithRelation; address: Address } return {tags.map((tag) => tag)} } +const ExtendableNameButton = ({ name, t }: { name: NameWithRelation; t: TFunction }) => { + const { usePreparedDataInput } = useTransactionFlow() + const showExtendNamesInput = usePreparedDataInput('ExtendNames') + + return ( + + ) +} + const MigrationName = ({ name, t, @@ -181,9 +197,7 @@ const MigrationName = ({ {mode === 'extension' ? ( <> - + ) : null} diff --git a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx index 7bbedf72a..f0392071b 100644 --- a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx +++ b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx @@ -1,6 +1,5 @@ import Link from 'next/link' import { useState } from 'react' -import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' import { ContractFunctionRevertedError, decodeErrorResult, namehash } from 'viem' import { useClient, useReadContract } from 'wagmi' @@ -10,6 +9,7 @@ import { Dialog, Heading, Helper, OutlinkSVG, Typography } from '@ensdomains/tho import { InvoiceItem } from '@app/components/@atoms/Invoice/Invoice' import { DateSelection } from '@app/components/@molecules/DateSelection/DateSelection' +import { EligibleForTokens } from '@app/components/pages/migrate/EligibleForTokens' import { createTransactionItem } from '@app/transaction-flow/transaction' import { bulkRenewalContract } from '@app/transaction-flow/transaction/bulkRenew' import { REBATE_DATE } from '@app/utils/constants' @@ -55,28 +55,6 @@ const abi = [ }, ] as const -const EligibleForTokens = styled.div( - ({ theme }) => css` - padding: ${theme.space['4']}; - gap: ${theme.space['2']}; - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: center; - width: ${theme.space.full}; - border-radius: ${theme.radii['2xLarge']}; - background: ${theme.colors.greenSurface}; - - a { - display: flex; - flex-direction: row; - align-items: center; - gap: ${theme.space['2']}; - color: ${theme.colors.greenPrimary}; - } - `, -) - const BulkRenewalFlow = ({ data }: Props) => { // Sort from the ones that expire earlier to later const sortedNames = data.names.toSorted((a, b) => a.expiryDate!.value! - b.expiryDate!.value!) @@ -107,8 +85,6 @@ const BulkRenewalFlow = ({ data }: Props) => { args: [data.names.map((name) => namehash(name.name!)), BigInt(seconds)], }) - const { t } = useTranslation('common') - return ( <> @@ -122,16 +98,7 @@ const BulkRenewalFlow = ({ data }: Props) => { {status} {error ? {error.message} : null} - - Eligible for {data.names.length} $ENS - something something - - - {t('action.learnMore')} - - - - + ) } diff --git a/src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx b/src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx index 50343fdf2..43eab41b4 100644 --- a/src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx +++ b/src/transaction-flow/input/ExtendNames/ExtendNames-flow.tsx @@ -14,6 +14,7 @@ import { Invoice, InvoiceItem } from '@app/components/@atoms/Invoice/Invoice' import { PlusMinusControl } from '@app/components/@atoms/PlusMinusControl/PlusMinusControl' import { StyledName } from '@app/components/@atoms/StyledName/StyledName' import { DateSelection } from '@app/components/@molecules/DateSelection/DateSelection' +import { EligibleForTokens } from '@app/components/pages/migrate/EligibleForTokens' import { useEstimateGasWithStateOverride } from '@app/hooks/chain/useEstimateGasWithStateOverride' import { useExpiry } from '@app/hooks/ensjs/public/useExpiry' import { usePrice } from '@app/hooks/ensjs/public/usePrice' @@ -366,6 +367,7 @@ const ExtendNames = ({ data: { names, isSelf }, dispatch, onDismiss }: Props) => {t('input.extendNames.gasLimitError')} )} + ))} From face792ae33d75b3d493efc3ee3875aa1aee200e Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 30 Oct 2024 00:14:55 +0200 Subject: [PATCH 20/24] overflow tab manager --- src/components/pages/migrate/MigrationTab.tsx | 12 ++++++------ src/pages/migrate.tsx | 8 ++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/pages/migrate/MigrationTab.tsx b/src/components/pages/migrate/MigrationTab.tsx index 90bf71be7..c05132e18 100644 --- a/src/components/pages/migrate/MigrationTab.tsx +++ b/src/components/pages/migrate/MigrationTab.tsx @@ -104,15 +104,15 @@ const GridOneToThree = styled.div( `, ) -const Heading = styled.h1( - ({ theme }) => css` - font-size: 52px; - font-size: 850; +const Heading = styled.h1<{ $fontSize?: number }>( + ({ theme, $fontSize = 52 }) => css` + font-size: ${$fontSize}px; + font-weight: 850; color: ${theme.colors.textPrimary}; text-align: center; line-height: 104%; - @media (min-width: 360px) { + @media (min-width: 480px) { font-size: 60px; } @media (min-width: 640px) { @@ -572,7 +572,7 @@ const ExtensionTab = ({ {t('title.extension.part1')} {t('title.extension.part2')} - + {t('title.extension.part1')} {t('title.extension.part2')} bloop diff --git a/src/pages/migrate.tsx b/src/pages/migrate.tsx index a3b38daaf..ad75e278c 100644 --- a/src/pages/migrate.tsx +++ b/src/pages/migrate.tsx @@ -69,6 +69,14 @@ const TabManager = styled.div( display: flex; flex-direction: row; gap: ${theme.space['4']}; + + overflow-x: auto; + width: ${theme.space.full}; + + @media (min-width: 480px) { + width: auto; + overflow-x: none; + } `, ) From 0f2082526e288979d8a1b3596b99539a780e9752 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 30 Oct 2024 02:30:18 +0200 Subject: [PATCH 21/24] fix up --- .../input/BulkRenewal/BulkRenewal-flow.tsx | 33 +++++++++++++++++++ src/transaction-flow/transaction/bulkRenew.ts | 4 +-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx index f0392071b..7b5cdb8f4 100644 --- a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx +++ b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx @@ -53,6 +53,39 @@ const abi = [ stateMutability: 'view', type: 'function', }, + { + inputs: [ + { + internalType: 'string', + name: 'name', + type: 'string', + }, + ], + name: 'NameAvailable', + type: 'error', + }, + { + inputs: [ + { + internalType: 'string', + name: 'name', + type: 'string', + }, + ], + name: 'NameBeyondWantedExpiryDate', + type: 'error', + }, + { + inputs: [ + { + internalType: 'string', + name: 'name', + type: 'string', + }, + ], + name: 'NameMismatchedPrice', + type: 'error', + }, ] as const const BulkRenewalFlow = ({ data }: Props) => { diff --git a/src/transaction-flow/transaction/bulkRenew.ts b/src/transaction-flow/transaction/bulkRenew.ts index f8f2f07ef..46d0bbdd0 100644 --- a/src/transaction-flow/transaction/bulkRenew.ts +++ b/src/transaction-flow/transaction/bulkRenew.ts @@ -70,8 +70,8 @@ export const bulkRenewalContract = { [mainnet.id]: '0xnotdeployedyet', [goerli.id]: '0xdeprecated', [localhost.id]: '0xnotdeployedyet', - [holesky.id]: '0x76aafA281Ed5155f83926a12ACB92e237e322A8C', - [sepolia.id]: '0xf9c8c83adda8d52d9284cdbef23da10b5f9869bf', + [holesky.id]: '0x3dCE478E4C880E96Ad3BF022acae38bef43F13eB', + [sepolia.id]: '0x0E714019e4BC65164d29960805259C1fA70E508a', } as const const displayItems = ( From 0f19ad30d2c11475b16fdee1f2ad99673d5973f8 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 30 Oct 2024 02:32:50 +0200 Subject: [PATCH 22/24] namehash to labelhash fix --- src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx index 7b5cdb8f4..31569df87 100644 --- a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx +++ b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx @@ -1,7 +1,7 @@ import Link from 'next/link' import { useState } from 'react' import styled, { css } from 'styled-components' -import { ContractFunctionRevertedError, decodeErrorResult, namehash } from 'viem' +import { ContractFunctionRevertedError, decodeErrorResult, labelhash, namehash } from 'viem' import { useClient, useReadContract } from 'wagmi' import { NameWithRelation } from '@ensdomains/ensjs/subgraph' @@ -115,7 +115,7 @@ const BulkRenewalFlow = ({ data }: Props) => { abi, address: bulkRenewalContract[client.chain.id!]!, functionName: 'getTargetExpiryPriceData', - args: [data.names.map((name) => namehash(name.name!)), BigInt(seconds)], + args: [data.names.map((name) => name.labelhash), BigInt(seconds)], }) return ( From 412c61cdd33d2401ce2ea153b00e5bfdbe2c88ce Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 30 Oct 2024 03:05:19 +0200 Subject: [PATCH 23/24] rewrite BulkRenewal flow --- src/components/@atoms/Calendar/Calendar.tsx | 22 +++-- .../input/BulkRenewal/BulkRenewal-flow.tsx | 98 ++++++++++++++----- 2 files changed, 88 insertions(+), 32 deletions(-) diff --git a/src/components/@atoms/Calendar/Calendar.tsx b/src/components/@atoms/Calendar/Calendar.tsx index 37b7db57c..d012e4fdf 100644 --- a/src/components/@atoms/Calendar/Calendar.tsx +++ b/src/components/@atoms/Calendar/Calendar.tsx @@ -4,7 +4,7 @@ import styled, { css } from 'styled-components' import CalendarSVG from '@app/assets/Calendar.svg' import { useDefaultRef } from '@app/hooks/useDefaultRef' import { useBreakpoint } from '@app/utils/BreakpointProvider' -import { secondsToDate, secondsToDateInput } from '@app/utils/date' +import { dateToDateInput, secondsToDate, secondsToDateInput } from '@app/utils/date' import { formatExpiry } from '@app/utils/utils' const Label = styled.label<{ $highlighted?: boolean }>( @@ -66,10 +66,10 @@ const LabelInput = styled.input( type InputProps = InputHTMLAttributes type Props = { highlighted?: boolean - value: number + value: number | Date unit?: string name?: string - min?: number + min?: number | Date } & Omit export const Calendar = forwardRef( @@ -78,8 +78,8 @@ export const Calendar = forwardRef( ref: ForwardedRef, ) => { const inputRef = useDefaultRef(ref) - const [minDuratiion] = useState(min ?? value) - const minDate = secondsToDate(minDuratiion) + const [minDuration] = useState(min ?? value) + const minDate = typeof minDuration === 'number' ? secondsToDate(minDuration) : minDuration const breakpoint = useBreakpoint() @@ -91,8 +91,12 @@ export const Calendar = forwardRef( type="date" {...props} ref={inputRef} - value={secondsToDateInput(value)} - min={secondsToDateInput(minDuratiion)} + value={typeof value === 'number' ? secondsToDateInput(value) : dateToDateInput(value)} + min={ + typeof minDuration === 'number' + ? secondsToDateInput(minDuration) + : dateToDateInput(minDuration) + } onFocus={(e) => { e.target.select() }} @@ -117,7 +121,9 @@ export const Calendar = forwardRef( onClick={() => inputRef.current!.showPicker()} /> - {formatExpiry(secondsToDate(value), { short: !breakpoint.sm })} + {formatExpiry(typeof value === 'number' ? secondsToDate(value) : value, { + short: !breakpoint.sm, + })} diff --git a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx index 31569df87..e708353ac 100644 --- a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx +++ b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx @@ -1,20 +1,19 @@ -import Link from 'next/link' import { useState } from 'react' +import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' -import { ContractFunctionRevertedError, decodeErrorResult, labelhash, namehash } from 'viem' import { useClient, useReadContract } from 'wagmi' import { NameWithRelation } from '@ensdomains/ensjs/subgraph' import { Dialog, Heading, Helper, OutlinkSVG, Typography } from '@ensdomains/thorin' +import { Calendar } from '@app/components/@atoms/Calendar/Calendar' import { InvoiceItem } from '@app/components/@atoms/Invoice/Invoice' -import { DateSelection } from '@app/components/@molecules/DateSelection/DateSelection' +import { PlusMinusControl } from '@app/components/@atoms/PlusMinusControl/PlusMinusControl' import { EligibleForTokens } from '@app/components/pages/migrate/EligibleForTokens' import { createTransactionItem } from '@app/transaction-flow/transaction' import { bulkRenewalContract } from '@app/transaction-flow/transaction/bulkRenew' import { REBATE_DATE } from '@app/utils/constants' -import { calculateDatesDiff, secondsFromDateDiff, secondsToDate } from '@app/utils/date' -import { ONE_YEAR, secondsToYears } from '@app/utils/time' +import { formatDurationOfDates } from '@app/utils/utils' export type Props = { data: { names: NameWithRelation[] } } @@ -88,22 +87,31 @@ const abi = [ }, ] as const +const CalendarContainer = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['2']}; + align-items: center; + width: ${theme.space.full}; + `, +) + +const YearsViewSwitch = styled.button( + ({ theme }) => css` + color: ${theme.colors.bluePrimary}; + cursor: pointer; + font-size: ${theme.fontSizes.small}; + font-weight: ${theme.fontWeights.bold}; + `, +) + const BulkRenewalFlow = ({ data }: Props) => { // Sort from the ones that expire earlier to later const sortedNames = data.names.toSorted((a, b) => a.expiryDate!.value! - b.expiryDate!.value!) - const minDateDiff = calculateDatesDiff(sortedNames[0].expiryDate!.date, REBATE_DATE) - - const minSeconds = - secondsFromDateDiff({ - startDate: sortedNames[0].expiryDate!.date, - additionalDays: minDateDiff.diff.days, - additionalMonths: minDateDiff.diff.months, - additionalYears: minDateDiff.diff.years, - }) + 84600 - - const [seconds, setSeconds] = useState(minSeconds) - const [durationType, setDurationType] = useState<'years' | 'date'>('years') + const [date, setDate] = useState(REBATE_DATE) + const [durationType, setDurationType] = useState<'years' | 'date'>('date') const client = useClient() @@ -115,19 +123,61 @@ const BulkRenewalFlow = ({ data }: Props) => { abi, address: bulkRenewalContract[client.chain.id!]!, functionName: 'getTargetExpiryPriceData', - args: [data.names.map((name) => name.labelhash), BigInt(seconds)], + args: [data.names.map((name) => name.labelName!), BigInt(date.getTime() / 1000)], }) + const { t } = useTranslation() + + const now = new Date() + return ( <> - + + {durationType === 'date' ? ( + { + const { valueAsDate } = e.currentTarget + if (valueAsDate) { + setDate(valueAsDate) + } + }} + highlighted + min={REBATE_DATE} + /> + ) : ( + { + const newYears = parseInt(e.target.value) + + if (!Number.isNaN(newYears)) + setDate(new Date(now.getFullYear() + newYears, date.getMonth(), date.getDate())) + }} + /> + )} + + {formatDurationOfDates({ + startDate: now, + endDate: date, + postFix: ' extension. ', + t, + })} + setDurationType(durationType === 'years' ? 'date' : 'years')} + > + {t(`calendar.pick_by_${durationType === 'date' ? 'years' : 'date'}`, { + ns: 'common', + })} + + + {status} {error ? {error.message} : null} From b4132dbd1d7d700811b082d38089973c6e9685fe Mon Sep 17 00:00:00 2001 From: v1rtl Date: Wed, 30 Oct 2024 03:23:44 +0200 Subject: [PATCH 24/24] wip gas estimation --- .../input/BulkRenewal/BulkRenewal-flow.tsx | 80 ++++++++++++++++--- src/transaction-flow/transaction/index.ts | 2 + 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx index e708353ac..e330142ad 100644 --- a/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx +++ b/src/transaction-flow/input/BulkRenewal/BulkRenewal-flow.tsx @@ -1,18 +1,21 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' -import { useClient, useReadContract } from 'wagmi' +import { useAccount, useBalance, useClient, useEstimateGas, useReadContract } from 'wagmi' import { NameWithRelation } from '@ensdomains/ensjs/subgraph' import { Dialog, Heading, Helper, OutlinkSVG, Typography } from '@ensdomains/thorin' +import { CacheableComponent } from '@app/components/@atoms/CacheableComponent' import { Calendar } from '@app/components/@atoms/Calendar/Calendar' -import { InvoiceItem } from '@app/components/@atoms/Invoice/Invoice' +import { Invoice, InvoiceItem } from '@app/components/@atoms/Invoice/Invoice' import { PlusMinusControl } from '@app/components/@atoms/PlusMinusControl/PlusMinusControl' import { EligibleForTokens } from '@app/components/pages/migrate/EligibleForTokens' +import { useEstimateGasWithStateOverride } from '@app/hooks/chain/useEstimateGasWithStateOverride' import { createTransactionItem } from '@app/transaction-flow/transaction' import { bulkRenewalContract } from '@app/transaction-flow/transaction/bulkRenew' -import { REBATE_DATE } from '@app/utils/constants' +import { CURRENCY_FLUCTUATION_BUFFER_PERCENTAGE, REBATE_DATE } from '@app/utils/constants' +import useUserConfig from '@app/utils/useUserConfig' import { formatDurationOfDates } from '@app/utils/utils' export type Props = { data: { names: NameWithRelation[] } } @@ -106,15 +109,29 @@ const YearsViewSwitch = styled.button( `, ) -const BulkRenewalFlow = ({ data }: Props) => { - // Sort from the ones that expire earlier to later - const sortedNames = data.names.toSorted((a, b) => a.expiryDate!.value! - b.expiryDate!.value!) +const GasEstimationCacheableComponent = styled(CacheableComponent)( + ({ theme }) => css` + width: 100%; + gap: ${theme.space['4']}; + display: flex; + flex-direction: column; + `, +) +const BulkRenewalFlow = ({ data }: Props) => { const [date, setDate] = useState(REBATE_DATE) const [durationType, setDurationType] = useState<'years' | 'date'>('date') const client = useClient() + const { address } = useAccount() + + const { data: balance } = useBalance({ + address, + }) + + const dateAsBigInt = BigInt(date.getTime() / 1000) + const { data: expiryData, error, @@ -123,13 +140,52 @@ const BulkRenewalFlow = ({ data }: Props) => { abi, address: bulkRenewalContract[client.chain.id!]!, functionName: 'getTargetExpiryPriceData', - args: [data.names.map((name) => name.labelName!), BigInt(date.getTime() / 1000)], + args: [data.names.map((name) => name.labelName!), dateAsBigInt], + }) + + const [total, durations, prices] = expiryData! as [bigint, bigint[], bigint[]] + + const { + data: { gasEstimate: estimatedGasLimit, gasCost: transactionFee }, + error: estimateGasLimitError, + isLoading: isEstimateGasLoading, + gasPrice, + } = useEstimateGasWithStateOverride({ + transactions: [ + { + name: 'bulkRenew', + data: { + names: data.names.map((name) => name.labelhash), + durations, + prices, + }, + stateOverride: [{ address: address! }], + }, + ], }) - const { t } = useTranslation() + const { t } = useTranslation(['transactionFlow', 'common']) const now = new Date() + const { userConfig, setCurrency } = useUserConfig() + const currencyDisplay = userConfig.currency === 'fiat' ? userConfig.fiat : 'eth' + + const items: InvoiceItem[] = [ + { + label: t('input.extendNames.invoice.extension', { + time: formatDurationOfDates({ startDate: now, endDate: date, t }), + }), + value: dateAsBigInt, + bufferPercentage: CURRENCY_FLUCTUATION_BUFFER_PERCENTAGE, + }, + { + label: t('input.extendNames.invoice.transaction'), + value: transactionFee, + bufferPercentage: CURRENCY_FLUCTUATION_BUFFER_PERCENTAGE, + }, + ] + return ( <> @@ -179,8 +235,14 @@ const BulkRenewalFlow = ({ data }: Props) => {
- {status} {error ? {error.message} : null} + + + {(!!estimateGasLimitError || + (!!estimatedGasLimit && !!balance?.value && balance.value < estimatedGasLimit)) && ( + {t('input.extendNames.gasLimitError')} + )} + ) diff --git a/src/transaction-flow/transaction/index.ts b/src/transaction-flow/transaction/index.ts index 96a04cc5d..6a9dda0ac 100644 --- a/src/transaction-flow/transaction/index.ts +++ b/src/transaction-flow/transaction/index.ts @@ -2,6 +2,7 @@ import approveDnsRegistrar from './approveDnsRegistrar' import approveNameWrapper from './approveNameWrapper' import approveNameWrapperForMigration from './approveNameWrapperForMigration' import approveRegistrarForMigration from './approveRegistrarForMigration' +import bulkRenew from './bulkRenew' import burnFuses from './burnFuses' import changePermissions from './changePermissions' import claimDnsName from './claimDnsName' @@ -37,6 +38,7 @@ export const transactions = { approveRegistrarForMigration, approveNameWrapper, burnFuses, + bulkRenew, changePermissions, claimDnsName, commitName,
+ + + {t('partnership.text')} + + {t('partnership.watch')} + + + + {tabs.map((tab) => ( + + ))} + + +
+ {match(currentTab) + .with('ensv2', () => ( + <> + + {t('title.landing')} + + {t('title.landing')} + {/** @ts-expect-error styled-components don't know how to inherit types */} + {t('caption.ensv2')} + + )) + .with('migrations', () => ( + <> + + {t('title.migration')} + + {t('title.migration')} + {/** @ts-expect-error styled-components don't know how to inherit types */} + {t('caption.migration')} + + )) + .with('extension', () => ( + <> + + + {t('title.extension.part1')} {t('title.extension.part2')} + + + + {t('title.extension.part1')} {t('title.extension.part2')} + + + )) + .exhaustive()} + + {match(currentTab) + .with('ensv2', () => ( + + )) + .with('migrations', () => ( + + )) + .with('extension', () => ( + + )) + .exhaustive()} + + + +
+ {match(currentTab) + .with('ensv2', () => ( + <> + + + 🎉 + + {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('get-started.title')} + +
+ + + + {t('get-started.upgrade.title')} + + {t('get-started.upgrade.caption')} + + +
+
+ + + {t('announcement.title')} + + + + + + + + + )) + .with('migrations', () => ( + <> + {isConnected ? ( + + ) : null} +
+ + {t('approval-benefits.title')} + +
+ + + + {t('approval-benefits.migration.title')} + + {t('approval-benefits.migration.caption')} + + + + + {t('approval-benefits.no-gas-cost.title')} + + {t('approval-benefits.no-gas-cost.caption')} + +
+
+ + )) + .otherwise(() => ( +

bloop

+ ))} + + +