diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 73c6a0a06..ac27c8c28 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -97,7 +97,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28] + shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] steps: - uses: actions/checkout@v3 diff --git a/e2e/specs/stateless/importName.spec.ts b/e2e/specs/stateless/_importName.spec.ts similarity index 100% rename from e2e/specs/stateless/importName.spec.ts rename to e2e/specs/stateless/_importName.spec.ts diff --git a/e2e/specs/stateless/extendNames.spec.ts b/e2e/specs/stateless/extendNames.spec.ts index bc54d5e07..51ddd1e73 100644 --- a/e2e/specs/stateless/extendNames.spec.ts +++ b/e2e/specs/stateless/extendNames.spec.ts @@ -1,7 +1,12 @@ /* eslint-disable no-await-in-loop */ import { expect } from '@playwright/test' -import { dateToDateInput, roundDurationWithDay, secondsToDateInput } from '@app/utils/date' +import { + dateToDateInput, + roundDurationWithDay, + secondsFromDateDiff, + secondsToDateInput, +} from '@app/utils/date' import { daysToSeconds } from '@app/utils/time' import { test } from '../../../playwright' @@ -353,14 +358,14 @@ test('should be able to extend a name by a month', async ({ }) await test.step('should set and render a date properly', async () => { - const expiryTime = (await profilePage.getExpiryTimestamp()) / 1000 + const expiryTimestamp = await profilePage.getExpiryTimestamp() + const expiryTime = expiryTimestamp / 1000 const calendar = page.getByTestId('calendar') - const monthLater = await page.evaluate( - (ts) => { - return new Date(ts) - }, - (expiryTime + daysToSeconds(31)) * 1000, - ) + const monthLater = await page.evaluate((ts) => { + const expiryDate = new Date(ts) + expiryDate.setMonth(expiryDate.getMonth() + 1) + return expiryDate + }, expiryTimestamp) await calendar.fill(dateToDateInput(monthLater)) await expect(page.getByTestId('calendar-date')).toHaveValue( @@ -372,7 +377,7 @@ test('should be able to extend a name by a month', async ({ await expect(extendNamesModal.getInvoiceExtensionFee).toContainText('0.0003') await expect(extendNamesModal.getInvoiceTransactionFee).toContainText('0.0001') await expect(extendNamesModal.getInvoiceTotal).toContainText('0.0004') - await expect(page.getByText('1 month extension', { exact: true })).toBeVisible() + await expect(page.getByText(/1 month .* extension/)).toBeVisible() }) await test.step('should extend', async () => { @@ -381,7 +386,9 @@ test('should be able to extend a name by a month', async ({ await transactionModal.autoComplete() const newTimestamp = await profilePage.getExpiryTimestamp() - const comparativeTimestamp = timestamp + daysToSeconds(31) * 1000 + const comparativeTimestamp = + timestamp + + secondsFromDateDiff({ startDate: new Date(timestamp), additionalMonths: 1 }) * 1000 await expect(comparativeTimestamp).toEqual(newTimestamp) }) }) @@ -416,18 +423,18 @@ test('should be able to extend a name by a day', async ({ }) await test.step('should set and render a date properly', async () => { - const expiryTime = (await profilePage.getExpiryTimestamp()) / 1000 + const expiryTimestamp = await profilePage.getExpiryTimestamp() + const expiryTime = expiryTimestamp / 1000 const calendar = page.getByTestId('calendar') - const monthLater = await page.evaluate( - (ts) => { - return new Date(ts) - }, - (expiryTime + daysToSeconds(1)) * 1000, - ) + const dayLater = await page.evaluate((ts) => { + const expiryDate = new Date(ts) + expiryDate.setDate(expiryDate.getDate() + 1) + return expiryDate + }, expiryTimestamp) - await calendar.fill(dateToDateInput(monthLater)) + await calendar.fill(dateToDateInput(dayLater)) await expect(page.getByTestId('calendar-date')).toHaveValue( - secondsToDateInput(expiryTime + roundDurationWithDay(monthLater, expiryTime)), + secondsToDateInput(expiryTime + roundDurationWithDay(dayLater, expiryTime)), ) }) @@ -497,14 +504,14 @@ test('should be able to extend a name in grace period by a month', async ({ }) await test.step('should set and render a date properly', async () => { - const expiryTime = (await profilePage.getExpiryTimestamp()) / 1000 + const expiryTimestamp = await profilePage.getExpiryTimestamp() + const expiryTime = expiryTimestamp / 1000 const calendar = page.getByTestId('calendar') - const monthLater = await page.evaluate( - (ts) => { - return new Date(ts) - }, - (expiryTime + daysToSeconds(31)) * 1000, - ) + const monthLater = await page.evaluate((ts) => { + const expiryDate = new Date(ts) + expiryDate.setMonth(expiryDate.getMonth() + 1) + return expiryDate + }, expiryTimestamp) await calendar.fill(dateToDateInput(monthLater)) await expect(page.getByTestId('calendar-date')).toHaveValue( @@ -516,7 +523,7 @@ test('should be able to extend a name in grace period by a month', async ({ await expect(extendNamesModal.getInvoiceExtensionFee).toContainText('0.0003') await expect(extendNamesModal.getInvoiceTransactionFee).toContainText('0.0001') await expect(extendNamesModal.getInvoiceTotal).toContainText('0.0004') - await expect(page.getByText('1 month extension', { exact: true })).toBeVisible() + await expect(page.getByText(/1 month .* extension/)).toBeVisible() }) await test.step('should extend', async () => { @@ -525,7 +532,9 @@ test('should be able to extend a name in grace period by a month', async ({ await transactionModal.autoComplete() const newTimestamp = await profilePage.getExpiryTimestamp() - const comparativeTimestamp = timestamp + daysToSeconds(31) * 1000 + const comparativeTimestamp = + timestamp + + secondsFromDateDiff({ startDate: new Date(timestamp), additionalMonths: 1 }) * 1000 await expect(comparativeTimestamp).toEqual(newTimestamp) }) }) @@ -578,18 +587,18 @@ test('should be able to extend a name in grace period by 1 day', async ({ }) await test.step('should set and render a date properly', async () => { - const expiryTime = (await profilePage.getExpiryTimestamp()) / 1000 + const expiryTimestamp = await profilePage.getExpiryTimestamp() + const expiryTime = expiryTimestamp / 1000 const calendar = page.getByTestId('calendar') - const monthLater = await page.evaluate( - (ts) => { - return new Date(ts) - }, - (expiryTime + daysToSeconds(1)) * 1000, - ) + const dayLater = await page.evaluate((ts) => { + const expiryDate = new Date(ts) + expiryDate.setDate(expiryDate.getDate() + 1) + return expiryDate + }, expiryTimestamp) - await calendar.fill(dateToDateInput(monthLater)) + await calendar.fill(dateToDateInput(dayLater)) await expect(page.getByTestId('calendar-date')).toHaveValue( - secondsToDateInput(expiryTime + roundDurationWithDay(monthLater, expiryTime)), + secondsToDateInput(expiryTime + roundDurationWithDay(dayLater, expiryTime)), ) }) diff --git a/e2e/specs/stateless/registerName.spec.ts b/e2e/specs/stateless/registerName.spec.ts index b41b0e5a8..5824d7696 100644 --- a/e2e/specs/stateless/registerName.spec.ts +++ b/e2e/specs/stateless/registerName.spec.ts @@ -5,8 +5,7 @@ import { ethRegistrarControllerCommitSnippet } from '@ensdomains/ensjs/contracts import { setPrimaryName } from '@ensdomains/ensjs/wallet' import { Web3RequestKind } from '@ensdomains/headless-web3-provider' -// import { secondsToDateInput } from '@app/utils/date' -import { daysToSeconds, yearsToSeconds } from '@app/utils/time' +import { dateToDateInput } from '@app/utils/date' import { test } from '../../../playwright' import { createAccounts } from '../../../playwright/fixtures/accounts' @@ -54,7 +53,6 @@ test.describe.serial('normal registration', () => { await expect(page.getByTestId('payment-choice-ethereum')).toBeChecked() await expect(registrationPage.primaryNameToggle).toBeChecked() - await page.pause() // should show adjusted gas estimate when primary name setting checked const estimate = await registrationPage.getGas() expect(estimate).toBeGreaterThan(0) @@ -100,9 +98,25 @@ test.describe.serial('normal registration', () => { // should go to transactions step and open commit transaction immediately await expect(page.getByTestId('next-button')).toHaveText('Begin') await page.getByTestId('next-button').click() + await transactionModal.closeButton.click() + + await expect( + page.getByText( + 'You will need to complete two transactions to secure your name. The second transaction must be completed within 24 hours of the first.', + ), + ).toBeVisible() + await page.getByTestId('start-timer-button').click() + await expect(page.getByText('Open Wallet')).toBeVisible() await transactionModal.confirm() + // should show countdown text + await expect( + page.getByText( + 'This wait prevents others from front running your transaction. You will be prompted to complete a second transaction when the timer is complete.', + ), + ).toBeVisible() + // should show countdown await expect(page.getByTestId('countdown-circle')).toBeVisible() await expect(page.getByTestId('countdown-complete-check')).toBeVisible() @@ -112,6 +126,13 @@ test.describe.serial('normal registration', () => { const startTimerButton = page.getByTestId('start-timer-button') await expect(startTimerButton).not.toBeVisible() await testClient.increaseTime({ seconds: 60 }) + + // Should show registration text + await expect( + page.getByText( + 'Your name is not registered until you’ve completed the second transaction. You have 23 hours remaining to complete it.', + ), + ).toBeVisible() await expect(page.getByTestId('finish-button')).toBeEnabled() // should save the registration state and the transaction status @@ -119,12 +140,12 @@ test.describe.serial('normal registration', () => { await expect(page.getByTestId('finish-button')).toBeEnabled() // should allow finalising registration and automatically go to the complete step - await page.getByTestId('finish-button').click() await expect( page.getByText( - 'You will need to complete two transactions to secure your name. The second transaction must be completed within 24 hours of the first.', + 'Your name is not registered until you’ve completed the second transaction. You have 23 hours remaining to complete it.', ), ).toBeVisible() + await page.getByTestId('finish-button').click() await expect(page.getByText('Open Wallet')).toBeVisible() await transactionModal.confirm() @@ -258,7 +279,9 @@ test('should allow registering a name and resuming from the commit toast', async await page.goto(`/${name}/register`) await login.connect() + await page.pause() await page.getByTestId('payment-choice-ethereum').click() + await page.getByTestId('primary-name-toggle').uncheck() await page.getByTestId('next-button').click() await page.getByTestId('next-button').click() @@ -296,39 +319,32 @@ test('should allow registering with a specific date', async ({ page, login, make const calendar = await page.getByTestId('calendar') const browserTime = await page.evaluate(() => Math.floor(Date.now() / 1000)) - const oneYearLaterInput = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + yearsToSeconds(1)) * 1000, - ) - // const oneYear = browserTime + yearsToSeconds(1) + + const oneYearLater = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setFullYear(_date.getFullYear() + 1) + return _date + }, browserTime * 1000) await test.step('should have a correct default date', async () => { - expect(calendar).toHaveValue(oneYearLaterInput) + expect(calendar).toHaveValue(dateToDateInput(oneYearLater)) expect(page.getByText('1 year registration', { exact: true })).toBeVisible() }) await test.step('should set a date', async () => { - const oneYearAndHalfLaterInput = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + yearsToSeconds(2.5)) * 1000, - ) - // const oneYearAndAHalfLater = secondsToDateInput(oneYear + yearsToSeconds(1.5)) + const twoYearsAndHalfLater = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setFullYear(_date.getFullYear() + 2) + _date.setMonth(_date.getMonth() + 6) + return _date + }, browserTime * 1000) - await calendar.fill(oneYearAndHalfLaterInput) + await calendar.fill(dateToDateInput(twoYearsAndHalfLater)) - await expect(page.getByTestId('calendar-date')).toHaveValue(oneYearAndHalfLaterInput) + await page.pause() + await expect(page.getByTestId('calendar-date')).toHaveValue( + dateToDateInput(twoYearsAndHalfLater), + ) expect(page.getByText('2 years, 6 months registration', { exact: true })).toBeVisible() }) @@ -377,20 +393,18 @@ test('should allow registering a premium name with a specific date', async ({ const calendar = page.getByTestId('calendar') await test.step('should set a date', async () => { - const oneYearAndHalfLaterInput = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + yearsToSeconds(2.5)) * 1000, - ) + const twoYearsAndHalfLater = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setFullYear(_date.getFullYear() + 2) + _date.setMonth(_date.getMonth() + 6) + return _date + }, browserTime * 1000) - await calendar.fill(oneYearAndHalfLaterInput) + await calendar.fill(dateToDateInput(twoYearsAndHalfLater)) - await expect(page.getByTestId('calendar-date')).toHaveValue(oneYearAndHalfLaterInput) + await expect(page.getByTestId('calendar-date')).toHaveValue( + dateToDateInput(twoYearsAndHalfLater), + ) expect(page.getByText('2 years, 6 months registration', { exact: true })).toBeVisible() }) @@ -446,25 +460,21 @@ test('should allow registering a premium name for two months', async ({ }) const browserTime = await page.evaluate(() => Math.floor(Date.now() / 1000)) + const calendar = page.getByTestId('calendar') await test.step('should set a date', async () => { - const oneYearAndHalfLaterInput = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + daysToSeconds(61)) * 1000, - ) + const twoMonthsLater = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setMonth(_date.getMonth() + 2) + return _date + }, browserTime * 1000) - await calendar.fill(oneYearAndHalfLaterInput) + await calendar.fill(dateToDateInput(twoMonthsLater)) - await expect(page.getByTestId('calendar-date')).toHaveValue(oneYearAndHalfLaterInput) + await expect(page.getByTestId('calendar-date')).toHaveValue(dateToDateInput(twoMonthsLater)) - expect(page.getByText('2 months registration', { exact: true })).toBeVisible() + expect(page.getByText(/2 months .* registration/)).toBeVisible() }) await page.getByTestId('payment-choice-ethereum').click() @@ -521,37 +531,27 @@ test('should not allow registering a premium name for less than 28 days', async const calendar = page.getByTestId('calendar') await test.step('should not allow less than 28 days', async () => { - const lessThan27Days = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + daysToSeconds(27)) * 1000, - ) + const lessThan27Days = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setDate(_date.getDate() + 27) + return _date + }, browserTime * 1000) - await calendar.fill(lessThan27Days) + await calendar.fill(dateToDateInput(lessThan27Days)) - await expect(page.getByTestId('calendar-date')).not.toHaveValue(lessThan27Days) + await expect(page.getByTestId('calendar-date')).not.toHaveValue(dateToDateInput(lessThan27Days)) }) await test.step('should allow 28 days', async () => { - const set28days = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + daysToSeconds(28)) * 1000, - ) + const set28days = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setDate(_date.getDate() + 28) + return _date + }, browserTime * 1000) - await calendar.fill(set28days) + await calendar.fill(dateToDateInput(set28days)) - await expect(page.getByTestId('calendar-date')).toHaveValue(set28days) + await expect(page.getByTestId('calendar-date')).toHaveValue(dateToDateInput(set28days)) expect(page.getByText('28 days registration', { exact: true })).toBeVisible() }) @@ -615,23 +615,16 @@ test('should allow normal registration for a month', async ({ const browserTime = await page.evaluate(() => Math.floor(Date.now() / 1000)) await test.step('should set a date', async () => { - const oneMonthLaterInput = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + daysToSeconds(31)) * 1000, - ) - // const oneYearAndAHalfLater = secondsToDateInput(oneYear + yearsToSeconds(1.5)) - - await calendar.fill(oneMonthLaterInput) + const oneMonthLater = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setMonth(_date.getMonth() + 1) + return _date + }, browserTime * 1000) - await expect(page.getByTestId('calendar-date')).toHaveValue(oneMonthLaterInput) + await calendar.fill(dateToDateInput(oneMonthLater)) - expect(page.getByText('1 month registration', { exact: true })).toBeVisible() + await expect(page.getByTestId('calendar-date')).toHaveValue(dateToDateInput(oneMonthLater)) + await expect(page.getByText(/1 month .* registration/)).toBeVisible() }) // should have payment choice ethereum checked and show primary name setting as checked @@ -743,33 +736,26 @@ test('should not allow normal registration less than 28 days', async ({ const browserTime = await page.evaluate(() => Math.floor(Date.now() / 1000)) await test.step('should set a date', async () => { - const lessThanMinDaysLaterInput = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + daysToSeconds(27)) * 1000, - ) - await calendar.fill(lessThanMinDaysLaterInput) - await expect(page.getByTestId('calendar-date')).not.toHaveValue(lessThanMinDaysLaterInput) - - const minDaysLaterInput = await page.evaluate( - (timestamp) => { - const _date = new Date(timestamp) - const year = _date.getFullYear() - const month = String(_date.getMonth() + 1).padStart(2, '0') // Month is zero-indexed - const day = String(_date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - }, - (browserTime + daysToSeconds(28)) * 1000, + const lessThanMinDaysLater = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setDate(_date.getDate() + 27) + return _date + }, browserTime * 1000) + + await calendar.fill(dateToDateInput(lessThanMinDaysLater)) + await expect(page.getByTestId('calendar-date')).not.toHaveValue( + dateToDateInput(lessThanMinDaysLater), ) - await calendar.fill(minDaysLaterInput) + const minDaysLater = await page.evaluate((timestamp) => { + const _date = new Date(timestamp) + _date.setDate(_date.getDate() + 28) + return _date + }, browserTime * 1000) + + await calendar.fill(dateToDateInput(minDaysLater)) - await expect(page.getByTestId('calendar-date')).toHaveValue(minDaysLaterInput) + await expect(page.getByTestId('calendar-date')).toHaveValue(dateToDateInput(minDaysLater)) expect(page.getByText('28 days registration', { exact: true })).toBeVisible() }) diff --git a/e2e/specs/stateless/verifications.spec.ts b/e2e/specs/stateless/verifications.spec.ts index 706bb1848..3f23e22e4 100644 --- a/e2e/specs/stateless/verifications.spec.ts +++ b/e2e/specs/stateless/verifications.spec.ts @@ -506,6 +506,49 @@ test.describe('OAuth flow', () => { await page.pause() }) + test('Should show an error message if user is not logged in', async ({ + page, + login, + makeName, + }) => { + const name = await makeName({ + label: 'dentity', + type: 'legacy', + owner: 'user', + manager: 'user2', + }) + + await page.route(`${VERIFICATION_OAUTH_BASE_URL}/dentity/token`, async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + name, + verifiedPresentationUri: `${DENTITY_VPTOKEN_ENDPOINT}?name=${name}&federated_token=federated_token`, + }), + }) + }) + + await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`) + + await page.pause() + + await expect(page.getByText('Verification failed')).toBeVisible() + await expect( + page.getByText('You must be connected as 0x709...c79C8 to set the verification record.'), + ).toBeVisible() + + await page.locator('.modal').getByRole('button', { name: 'Done' }).click() + + await page.pause() + await login.connect('user2') + + // Page should redirect to the profile page + await expect(page).toHaveURL(`/${name}`) + + await page.pause() + }) + test('Should redirect to profile page without showing set verification record if it already set', async ({ page, login, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9af9e0a5..820b63458 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5705,8 +5705,8 @@ packages: fmix@0.1.0: resolution: {integrity: sha512-Y6hyofImk9JdzU8k5INtTXX1cu8LDlePWDFU5sftm9H+zKCr5SGrVjdhkvsim646cw5zD0nADj8oHyXMZmCZ9w==} - focus-visible@5.2.0: - resolution: {integrity: sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==} + focus-visible@5.2.1: + resolution: {integrity: sha512-8Bx950VD1bWTQJEH/AM6SpEk+SU55aVnp4Ujhuuxy3eMEBCRwBnTBnVXr9YAPvZL3/CNjCa8u4IWfNmEO53whA==} follow-redirects@1.15.6: resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} @@ -10725,7 +10725,7 @@ snapshots: '@babel/highlight@7.24.6': dependencies: - '@babel/helper-validator-identifier': 7.24.6 + '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.0.1 @@ -11180,7 +11180,7 @@ snapshots: '@babel/helper-hoist-variables': 7.24.6 '@babel/helper-module-transforms': 7.24.6(@babel/core@7.24.6) '@babel/helper-plugin-utils': 7.24.6 - '@babel/helper-validator-identifier': 7.24.6 + '@babel/helper-validator-identifier': 7.24.7 '@babel/plugin-transform-modules-umd@7.24.6(@babel/core@7.24.6)': dependencies: @@ -11614,7 +11614,7 @@ snapshots: '@babel/template@7.24.6': dependencies: - '@babel/code-frame': 7.24.6 + '@babel/code-frame': 7.24.7 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 @@ -11860,7 +11860,7 @@ snapshots: '@ensdomains/thorin@0.6.50(react-dom@18.3.1(react@18.3.1))(react-transition-state@1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1))': dependencies: clsx: 1.2.1 - focus-visible: 5.2.0 + focus-visible: 5.2.1 lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -14101,7 +14101,7 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 18.19.33 + '@types/node': 18.19.44 '@types/node@12.20.55': {} @@ -14189,7 +14189,7 @@ snapshots: '@types/set-cookie-parser@2.4.7': dependencies: - '@types/node': 18.19.33 + '@types/node': 18.19.44 '@types/stack-utils@2.0.3': {} @@ -17357,7 +17357,7 @@ snapshots: dependencies: imul: 1.0.1 - focus-visible@5.2.0: {} + focus-visible@5.2.1: {} follow-redirects@1.15.6(debug@4.3.4): optionalDependencies: @@ -19791,7 +19791,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.6 + '@babel/code-frame': 7.24.7 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 7ff878099..0169d77dd 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -49,7 +49,8 @@ "editRoles": "Edit roles", "setReminder": "Set reminder", "import": "Import", - "connect": "Connect" + "connect": "Connect", + "goToProfile": "Go to profile" }, "unit": { "years_one": "{{count}} year", @@ -58,6 +59,10 @@ "months_other": "{{count}} months", "days_one": "{{count}} day", "days_other": "{{count}} days", + "hours_one": "{{count}} hour", + "hours_other": "{{count}} hours", + "minutes_one": "{{count}} minute", + "minutes_other": "{{count}} minutes", "invalid_date": "Invalid Date", "yrs_one": "{{count}} yr", "yrs_other": "{{count}} yrs", @@ -399,5 +404,17 @@ "calendar": { "pick_by_years": "Pick by years", "pick_by_date": "Pick by date" + }, + "verification": { + "verifiedBy": "Verified by {{ issuer }}", + "personhoodVerified": "Personhood verified", + "verificationFailed": "Verification failed, please reverify your profile" + }, + "verificationErrorDialog": { + "title": "Verification failed", + "resolverRequired": "A valid resolver is required to complete the verification flow", + "ownerNotManager": "You must be connected as the Manager of this name to set the verification record. You can view and update the Manager under the Ownership tab.", + "wrongAccount": "You must be connected as {{ nameOrAddress }} to set the verification record.", + "default": "We could't verify your account. Please return to Dentity and try again." } } diff --git a/public/locales/en/profile.json b/public/locales/en/profile.json index 9a6c109e1..49baa8dfe 100644 --- a/public/locales/en/profile.json +++ b/public/locales/en/profile.json @@ -14,6 +14,11 @@ "ownership": "Ownership", "viewDetails": "View Details", "banner": { + "empty": { + "title": "Personalize your profile", + "description": "Add crypto addresses, social links, an avatar and more!", + "action": "Get started" + }, "available": { "title": "{{name}} is available", "description": "This name expired on {{date}}. Click here to view the registration page." diff --git a/public/locales/en/register.json b/public/locales/en/register.json index 958cf2828..3ef9d18e3 100644 --- a/public/locales/en/register.json +++ b/public/locales/en/register.json @@ -182,7 +182,14 @@ }, "transactions": { "heading": "Almost there", - "subheading": "You will need to complete two transactions to secure your name. The second transaction must be completed within 24 hours of the first.", + "subheading": { + "default": "You will need to complete two transactions to secure your name. The second transaction must be completed within 24 hours of the first.", + "commiting": "This wait prevents others from front running your transaction. You will be prompted to complete a second transaction when the timer is complete.", + "commitComplete": "Your name is not registered until you’ve completed the second transaction. You have {{duration}} remaining to complete it.", + "commitCompleteNoDuration": "Your name is not registered until you’ve completed the second transaction.", + "commitExpired": "Your registration has expired. You will need to start the process again.", + "frontRunning": "When someone sees your transaction and registers the name before your transaction can complete." + }, "startTimer": "Start timer", "wait": "Wait", "transactionFailed": "Transaction Failed", diff --git a/public/locales/en/transactionFlow.json b/public/locales/en/transactionFlow.json index b129383fc..d14f88051 100644 --- a/public/locales/en/transactionFlow.json +++ b/public/locales/en/transactionFlow.json @@ -399,7 +399,8 @@ "verifyProfile": { "list": { "title": "Verify your profile", - "message": " You can verify profile information and add proofs of personhood. Verified records will be marked on your profile with a blue check." + "message": " You can verify profile information and add proofs of personhood. Verified records will be marked on your profile with a blue check.", + "added": "Added" }, "dentity": { "title": "Dentity verification", diff --git a/src/assets/Stars.svg b/src/assets/Stars.svg new file mode 100644 index 000000000..4a9a44656 --- /dev/null +++ b/src/assets/Stars.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/components/@atoms/TextWithTooltip/TextWithTooltip.tsx b/src/components/@atoms/TextWithTooltip/TextWithTooltip.tsx new file mode 100644 index 000000000..ab9240428 --- /dev/null +++ b/src/components/@atoms/TextWithTooltip/TextWithTooltip.tsx @@ -0,0 +1,73 @@ +import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' + +import { OutlinkSVG, QuestionCircleSVG, Tooltip, Typography } from '@ensdomains/thorin' + +const TooltipContent = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + align-items: center; + gap: ${theme.space[2]}; + text-align: center; + color: ${theme.colors.indigo}; + pointer-events: all; + `, +) + +const Link = styled.a( + ({ theme }) => css` + display: flex; + align-items: center; + gap: ${theme.space[1]}; + color: ${theme.colors.indigo}; + `, +) + +const Container = styled.button( + ({ theme }) => css` + height: ${theme.space[7]}; + display: inline-flex; + align-items: center; + text-decoration: underline dashed ${theme.colors.indigo}; + `, +) + +export const TextWithTooltip = ({ + link, + tooltipContent, + children, +}: { + tooltipContent: string + link?: string + children?: React.ReactNode +}) => { + const { t } = useTranslation('common') + return ( + + + + {tooltipContent} + + {link && ( + + + {t('action.learnMore')} + + + + )} + + } + background="indigoSurface" + > + + + {children} + + + + ) +} diff --git a/src/components/@molecules/DateSelection/DateSelection.test.tsx b/src/components/@molecules/DateSelection/DateSelection.test.tsx index 4b9ada15e..2ddad8ce2 100644 --- a/src/components/@molecules/DateSelection/DateSelection.test.tsx +++ b/src/components/@molecules/DateSelection/DateSelection.test.tsx @@ -3,6 +3,7 @@ import { act, mockFunction, render, renderHook, screen, userEvent, waitFor } fro import { useState } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { secondsFromDateDiff } from '@app/utils/date' import { ONE_DAY, ONE_YEAR } from '@app/utils/time' import { DateSelection } from './DateSelection' @@ -26,50 +27,64 @@ describe('DateSelection', () => { vi.resetAllMocks() }) it('should render a plus minus counter if no name was provided', () => { - render( {}} />) + render( + {}} + durationType="years" + />, + ) expect(screen.getByTestId('plus-minus-control-input')).toBeInTheDocument() }) it('should show a calendar if user is picking by date', async () => { - render( {}} />) - - screen.getByTestId('date-selection').click() - + render( + {}} durationType="date" />, + ) expect(screen.getByText('unit.years.1 registration.')).toBeVisible() }) it('should set back to one year when switching to a year toggle if previously was set to less', async () => { const { result } = renderHook(() => useState(ONE_YEAR)) - const { rerender } = render( - , + let { result: durationTypeResult } = renderHook(() => useState<'years' | 'date'>('years')) + + const DateSelectionComponent = () => ( + ) + const { rerender } = render() + const dateSelection = screen.getByTestId('date-selection') await userEvent.click(dateSelection) + rerender() + await waitFor(() => { expect(screen.getByText('calendar.pick_by_years')).toBeVisible() }) - act(() => { - result.current[1](ONE_DAY * 30) - }) + result.current[1](secondsFromDateDiff({ startDate: new Date(), additionalMonths: 1 })) - rerender( - , - ) + rerender() expect(screen.getByText('unit.months.1 registration.')).toBeVisible() await userEvent.click(dateSelection) + rerender() + await waitFor(() => { expect(screen.getByText('calendar.pick_by_date')).toBeVisible() }) - rerender( - , - ) + rerender() expect(screen.getByText('unit.years.1 registration.')).toBeVisible() }) diff --git a/src/components/@molecules/DateSelection/DateSelection.tsx b/src/components/@molecules/DateSelection/DateSelection.tsx index ceb5ee73a..3c19320e2 100644 --- a/src/components/@molecules/DateSelection/DateSelection.tsx +++ b/src/components/@molecules/DateSelection/DateSelection.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' @@ -6,8 +6,8 @@ import { Typography } from '@ensdomains/thorin' import { Calendar } from '@app/components/@atoms/Calendar/Calendar' import { PlusMinusControl } from '@app/components/@atoms/PlusMinusControl/PlusMinusControl' -import { roundDurationWithDay } from '@app/utils/date' -import { formatDuration, ONE_YEAR, secondsToYears, yearsToSeconds } from '@app/utils/utils' +import { roundDurationWithDay, secondsFromDateDiff } from '@app/utils/date' +import { formatDurationOfDates, secondsToYears } from '@app/utils/utils' const YearsViewSwitch = styled.button( ({ theme }) => css` @@ -33,6 +33,8 @@ const now = Math.floor(Date.now() / 1000) export const DateSelection = ({ seconds, setSeconds, + durationType, + onChangeDurationType, name, minSeconds, mode = 'register', @@ -40,18 +42,17 @@ export const DateSelection = ({ }: { seconds: number setSeconds: (seconds: number) => void + durationType: 'years' | 'date' name?: string minSeconds: number mode?: 'register' | 'extend' expiry?: number + onChangeDurationType?: (type: 'years' | 'date') => void }) => { - const [yearPickView, setYearPickView] = useState<'years' | 'date'>('years') - const toggleYearPickView = () => setYearPickView(yearPickView === 'date' ? 'years' : 'date') + const currentTime = expiry ?? now const { t } = useTranslation() - const extensionPeriod = formatDuration(seconds, t) - useEffect(() => { if (minSeconds > seconds) setSeconds(minSeconds) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -59,18 +60,22 @@ export const DateSelection = ({ const dateInYears = Math.floor(secondsToYears(seconds)) + // When the duration type is years, normalise the seconds to a year value useEffect(() => { - if (yearPickView === 'years' && dateInYears < 1) { - setSeconds(ONE_YEAR) + if (durationType === 'years' && currentTime) { + setSeconds( + secondsFromDateDiff({ + startDate: new Date(currentTime * 1000), + additionalYears: Math.max(1, dateInYears), + }), + ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dateInYears, yearPickView]) - - const currentTime = expiry ?? now + }, [dateInYears, durationType]) return ( - {yearPickView === 'date' ? ( + {durationType === 'date' ? ( { @@ -91,20 +96,29 @@ export const DateSelection = ({ onChange={(e) => { const newYears = parseInt(e.target.value) - if (!Number.isNaN(newYears)) setSeconds(yearsToSeconds(newYears)) + if (!Number.isNaN(newYears)) + setSeconds( + secondsFromDateDiff({ + startDate: new Date(currentTime * 1000), + additionalYears: newYears, + }), + ) }} /> )} - - {extensionPeriod === t('unit.invalid_date', { ns: 'common' }) - ? extensionPeriod - : `${extensionPeriod} ${mode === 'register' ? 'registration.' : 'extension.'}`}{' '} + + {formatDurationOfDates({ + startDate: new Date(currentTime * 1000), + endDate: new Date((currentTime + seconds) * 1000), + postFix: mode === 'register' ? ' registration. ' : ' extension. ', + t, + })} toggleYearPickView()} + onClick={() => onChangeDurationType?.(durationType === 'years' ? 'date' : 'years')} > - {t(`calendar.pick_by_${yearPickView === 'date' ? 'years' : 'date'}`, { ns: 'common' })} + {t(`calendar.pick_by_${durationType === 'date' ? 'years' : 'date'}`, { ns: 'common' })} diff --git a/src/components/@molecules/VerificationBadge/components/VerificationBadgeAccountTooltipContent.tsx b/src/components/@molecules/VerificationBadge/components/VerificationBadgeAccountTooltipContent.tsx index af599e5b0..a62ebc861 100644 --- a/src/components/@molecules/VerificationBadge/components/VerificationBadgeAccountTooltipContent.tsx +++ b/src/components/@molecules/VerificationBadge/components/VerificationBadgeAccountTooltipContent.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' import { match } from 'ts-pattern' @@ -41,6 +42,7 @@ const Content = styled.div( ) export const VerificationBadgeAccountTooltipContent = ({ verifiers }: Props) => { + const { t } = useTranslation('common') const verifier = verifiers?.[0] return match(verifier) .with('dentity', () => ( @@ -48,7 +50,7 @@ export const VerificationBadgeAccountTooltipContent = ({ verifiers }: Props) => - Verified by Dentity + {t('verification.verifiedBy', { issuer: 'Dentity' })} diff --git a/src/components/@molecules/VerificationBadge/components/VerificationBadgeVerifierTooltipContent.tsx b/src/components/@molecules/VerificationBadge/components/VerificationBadgeVerifierTooltipContent.tsx index e136862ce..dc77793cb 100644 --- a/src/components/@molecules/VerificationBadge/components/VerificationBadgeVerifierTooltipContent.tsx +++ b/src/components/@molecules/VerificationBadge/components/VerificationBadgeVerifierTooltipContent.tsx @@ -1,10 +1,10 @@ +import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' import { match } from 'ts-pattern' import { Colors, Typography } from '@ensdomains/thorin' import VerifiedPersonSVG from '@app/assets/VerifiedPerson.svg' -import { SupportOutlink } from '@app/components/@atoms/SupportOutlink' import { CenteredTypography } from '@app/transaction-flow/input/ProfileEditor/components/CenteredTypography' const Container = styled.div<{ $color: Colors }>( @@ -42,6 +42,7 @@ export const VerificationBadgeVerifierTooltipContent = ({ }: { isVerified: boolean }) => { + const { t } = useTranslation('common') return ( {match(isVerified) @@ -51,17 +52,13 @@ export const VerificationBadgeVerifierTooltipContent = ({ - Personhood verified + {t('verification.personhoodVerified')} )) .otherwise(() => ( - - Verification failed, please reverify your profile - - {/* TODO: NEED DOCUMENTATION LINK */} - Learn more + {t('verification.verificationFailed')} ))} diff --git a/src/components/pages/VerificationErrorDialog.tsx b/src/components/pages/VerificationErrorDialog.tsx index 77ec80c22..66735becd 100644 --- a/src/components/pages/VerificationErrorDialog.tsx +++ b/src/components/pages/VerificationErrorDialog.tsx @@ -1,4 +1,5 @@ import { ComponentProps } from 'react' +import { Trans } from 'react-i18next' import { Button, Dialog } from '@ensdomains/thorin' @@ -7,26 +8,31 @@ import { CenteredTypography } from '@app/transaction-flow/input/ProfileEditor/co export type ButtonProps = ComponentProps export type VerificationErrorDialogProps = - | (Omit, 'variant' | 'children'> & { - title: string - message: string - actions: { + | (Omit, 'open' | 'variant' | 'children'> & { + open?: boolean + title?: string + message?: string | ComponentProps + actions?: { leading?: ButtonProps trailing: ButtonProps } }) | undefined -export const VerificationErrorDialog = (props: VerificationErrorDialogProps) => { +export const VerificationErrorDialog = ( + props: VerificationErrorDialogProps = { open: false, title: '', message: '' }, +) => { if (!props) return null - const { title, message, actions, ...dialogProps } = props + const { title, message, actions, open, ...dialogProps } = props + + const _message = typeof message === 'string' ? message : return ( - + - {message} + {_message} {actions && ( info: infoBanner, warning, header: ( - - {visibileTabs.map((tabItem) => ( - setTab(tabItem)} - > - - {t(`tabs.${tabItem}.name`)} - - - ))} - + <> + + {visibileTabs.map((tabItem) => ( + setTab(tabItem)} + > + + {t(`tabs.${tabItem}.name`)} + + + ))} + + + ), titleExtra: profile?.address ? ( ({ + useProtectedRoute: vi.fn(), +})) +vi.mock('@app/utils/BreakpointProvider') +vi.mock('next/router', async () => await vi.importActual('next-router-mock')) +vi.mock('@app/hooks/useProfile') +vi.mock('@app/hooks/pages/profile/[name]/profile/useProfileActions/useProfileActions') + +const mockUseProfile = mockFunction(useProfile) +const mockUseProfileActions = mockFunction(useProfileActions) + +describe('ProfileEmptyBanner', () => { + it('should not display banner if have records', () => { + const name = 'test' + + mockUseProfile.mockImplementation(() => ({ + data: { + texts: [ + { + key: 'avatar', + value: 'http://localhost:3000', + }, + ], + coins: [ + { + id: 60, + name: 'eth', + value: '0x8327FcD61f5e90e1E05A3F49DCbc9346b7d111111', + }, + ], + contentHash: null, + abi: null, + resolverAddress: '0x8327FcD61f5e90e1E05A3F49DCbc9346b7d111112', + isMigrated: true, + createdAt: { + date: '2024-08-02T10:33:00.000Z', + value: 1722594780000, + }, + address: '0x8327FcD61f5e90e1E05A3F49DCbc9346b7d175f7', + }, + isLoading: false, + })) + + mockUseProfileActions.mockImplementation(() => ({ + profileActions: [ + { + label: 'tabs.profile.actions.editProfile.label', + }, + ], + })) + + render() + expect(screen.queryByTestId('profile-empty-banner')).not.toBeInTheDocument() + }) + + it('should display banner if have no records', () => { + const name = 'test' + + mockUseProfile.mockImplementation(() => ({ + data: { + text: [], + coins: [], + }, + isLoading: false, + })) + + mockUseProfileActions.mockImplementation(() => ({ + profileActions: [ + { + label: 'tabs.profile.actions.editProfile.label', + }, + ], + })) + + render() + expect(screen.queryByTestId('profile-empty-banner')).toBeInTheDocument() + }) +}) diff --git a/src/components/pages/profile/[name]/ProfileEmptyBanner.tsx b/src/components/pages/profile/[name]/ProfileEmptyBanner.tsx new file mode 100644 index 000000000..afe7ed287 --- /dev/null +++ b/src/components/pages/profile/[name]/ProfileEmptyBanner.tsx @@ -0,0 +1,69 @@ +import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' + +import { Button, mq, Typography } from '@ensdomains/thorin' + +import StarsSVG from '@app/assets/Stars.svg' +import { useProfileActions } from '@app/hooks/pages/profile/[name]/profile/useProfileActions/useProfileActions' +import { useProfile } from '@app/hooks/useProfile' + +import { profileToProfileRecords } from './registration/steps/Profile/profileRecordUtils' + +const Container = styled.div( + ({ theme }) => css` + margin-top: ${theme.space['4']}; + display: grid; + grid-template-columns: 48px 1fr auto; + align-items: center; + gap: ${theme.space['6']}; + padding: ${theme.space['6']}; + width: 100%; + border: 4px solid #fff; + border-radius: 16px; + background: linear-gradient(#e7f4ef 100%, #fdf0dd 100%); + + ${mq.sm.max(css` + grid-template-columns: 1fr; + text-align: center; + gap: ${theme.space['4']}; + padding: ${theme.space['4']}; + `)} + `, +) + +export function ProfileEmptyBanner({ name }: { name: string }) { + const { t } = useTranslation('profile') + + const { data: profile, isLoading: isProfileLoading } = useProfile({ name }) + const existingRecords = profileToProfileRecords(profile) + const profileActions = useProfileActions({ + name, + }) + + const records = existingRecords.filter(({ value }) => value) + + const action = (profileActions.profileActions ?? []).find( + (i) => i.label === t('tabs.profile.actions.editProfile.label'), + ) + + if (records.length || isProfileLoading || !action) return null + + return ( + +
+ +
+
+ + {t('banner.empty.title')} + + + {t('banner.empty.description')} + +
+ +
+ ) +} diff --git a/src/components/pages/profile/[name]/registration/FullInvoice.tsx b/src/components/pages/profile/[name]/registration/FullInvoice.tsx index c8e9bb3a4..da8c90822 100644 --- a/src/components/pages/profile/[name]/registration/FullInvoice.tsx +++ b/src/components/pages/profile/[name]/registration/FullInvoice.tsx @@ -9,7 +9,7 @@ import { Invoice } from '@app/components/@atoms/Invoice/Invoice' import { useEstimateFullRegistration } from '@app/hooks/gasEstimation/useEstimateRegistration' import { CURRENCY_FLUCTUATION_BUFFER_PERCENTAGE } from '@app/utils/constants' import useUserConfig from '@app/utils/useUserConfig' -import { formatDuration, ONE_DAY } from '@app/utils/utils' +import { formatDurationOfDates, ONE_DAY } from '@app/utils/utils' const OptionBar = styled.div( () => css` @@ -46,11 +46,16 @@ const FullInvoice = ({ const { userConfig, setCurrency } = useUserConfig() const currencyDisplay = userConfig.currency === 'fiat' ? userConfig.fiat : 'eth' - const invoiceItems = useMemo( - () => [ + const invoiceItems = useMemo(() => { + const now = Math.floor(Date.now()) + return [ { label: t('invoice.timeRegistration', { - time: formatDuration(seconds, t), + time: formatDurationOfDates({ + startDate: new Date(), + endDate: new Date(now + seconds * 1000), + t, + }), }), bufferPercentage: CURRENCY_FLUCTUATION_BUFFER_PERCENTAGE, value: totalDurationBasedFee, @@ -70,9 +75,8 @@ const FullInvoice = ({ }, ] : []), - ], - [t, seconds, totalDurationBasedFee, estimatedGasFee, hasPremium, premiumFee], - ) + ] + }, [t, seconds, totalDurationBasedFee, estimatedGasFee, hasPremium, premiumFee]) return ( diff --git a/src/components/pages/profile/[name]/registration/Registration.tsx b/src/components/pages/profile/[name]/registration/Registration.tsx index baf650902..5b61385e8 100644 --- a/src/components/pages/profile/[name]/registration/Registration.tsx +++ b/src/components/pages/profile/[name]/registration/Registration.tsx @@ -142,6 +142,7 @@ const Registration = ({ nameDetails, isLoading }: Props) => { seconds, reverseRecord, paymentMethodChoice, + durationType, }: RegistrationStepData['pricing']) => { if (paymentMethodChoice === PaymentMethod.moonpay) { initiateMoonpayRegistrationMutation.mutate(secondsToYears(seconds)) @@ -149,7 +150,7 @@ const Registration = ({ nameDetails, isLoading }: Props) => { } dispatch({ name: 'setPricingData', - payload: { seconds, reverseRecord }, + payload: { seconds, reverseRecord, durationType }, selected, }) if (!item.queue.includes('profile')) { diff --git a/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx b/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx index 873ecb57e..dc0413a6c 100644 --- a/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx +++ b/src/components/pages/profile/[name]/registration/steps/Pricing/Pricing.tsx @@ -380,6 +380,7 @@ export type ActionButtonProps = { seconds: number balance: GetBalanceData | undefined totalRequiredBalance?: bigint + durationType: 'date' | 'years' } export const ActionButton = (props: ActionButtonProps) => { @@ -404,12 +405,13 @@ export const ActionButton = (props: ActionButtonProps) => { reverseRecord, seconds, paymentMethodChoice, + durationType, callback, }) => ( ), ) - .otherwise(({ reverseRecord, seconds, paymentMethodChoice, callback }) => ( + .otherwise(({ reverseRecord, seconds, paymentMethodChoice, durationType, callback }) => (