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/public/locales/en/common.json b/public/locales/en/common.json index e2b0f4c2a..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", @@ -403,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/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/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 && ( text.key === VERIFICATION_RECORD_KEY)?.value, } diff --git a/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts b/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts index b23a2739a..fd1f2bcbe 100644 --- a/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts +++ b/src/hooks/verification/useVerificationOAuthHandler/useVerificationOAuthHandler.ts @@ -1,5 +1,6 @@ import { useSearchParams } from 'next/navigation' import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' import { match } from 'ts-pattern' import { useAccount } from 'wagmi' @@ -26,11 +27,12 @@ export const useVerificationOAuthHandler = (): UseVerificationOAuthHandlerReturn const iss = searchParams.get('iss') const code = searchParams.get('code') const router = useRouterWithHistory() + const { t } = useTranslation('common') const { createTransactionFlow } = useTransactionFlow() const { address: userAddress } = useAccount() - const isReady = !!createTransactionFlow && !!router && !!userAddress && !!iss && !!code + const isReady = !!createTransactionFlow && !!router && !!iss && !!code const { data, isLoading, error } = useVerificationOAuth({ verifier: issToVerificationProtocol(iss), @@ -43,7 +45,7 @@ export const useVerificationOAuthHandler = (): UseVerificationOAuthHandlerReturn const onDismiss = () => setDialogProps(undefined) useEffect(() => { - if (data && !isLoading && userAddress) { + if (data && !isLoading) { const newProps = match(data) .with( { @@ -55,6 +57,7 @@ export const useVerificationOAuthHandler = (): UseVerificationOAuthHandlerReturn onDismiss, router, createTransactionFlow, + t, }), ) .otherwise(() => undefined) diff --git a/src/hooks/verification/useVerificationOAuthHandler/utils/dentityHandler.ts b/src/hooks/verification/useVerificationOAuthHandler/utils/dentityHandler.ts index ff609d27c..5a00fb73f 100644 --- a/src/hooks/verification/useVerificationOAuthHandler/utils/dentityHandler.ts +++ b/src/hooks/verification/useVerificationOAuthHandler/utils/dentityHandler.ts @@ -1,3 +1,4 @@ +import { TFunction } from 'i18next' import { match, P } from 'ts-pattern' import { Hash } from 'viem' @@ -9,6 +10,7 @@ import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory' import { getDestination } from '@app/routes' import { CreateTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' +import { shortenAddress } from '../../../../utils/utils' import { UseVerificationOAuthReturnType } from '../../useVerificationOAuth/useVerificationOAuth' import { createVerificationTransactionFlow } from './createVerificationTransactionFlow' @@ -38,12 +40,14 @@ export const dentityVerificationHandler = onDismiss, router, createTransactionFlow, + t, }: { userAddress?: Hash onClose: () => void onDismiss: () => void router: ReturnType createTransactionFlow: CreateTransactionFlow + t: TFunction }) => (json: UseVerificationOAuthReturnType): VerificationErrorDialogProps => { return match(json) @@ -55,14 +59,15 @@ export const dentityVerificationHandler = resolverAddress: P.string, }, ({ owner, manager }) => { - if (owner && manager) return manager === userAddress - return owner === userAddress + if (owner && manager) return manager.toLowerCase() === userAddress?.toLowerCase() + return owner.toLowerCase() === userAddress?.toLowerCase() }, ({ verifier, name, resolverAddress, verifiedPresentationUri, verificationRecord }) => { router.push(`/${name}`) const vcUris = verificationRecord ? tryJsonParse(verificationRecord) : [] + // If the user has already verified the presentation, do not create a new transaction if (Array.isArray(vcUris) && vcUris.includes(verifiedPresentationUri)) return undefined createVerificationTransactionFlow({ @@ -82,16 +87,16 @@ export const dentityVerificationHandler = open: true, onDismiss, onClose, - title: 'Verification failed', - message: 'Resolver address is required to complete verification flow', + title: t('verificationErrorDialog.title'), + message: t('verificationErrorDialog.resolverRequired'), actions: { leading: { - children: 'Close', + children: t('action.close'), colorStyle: 'accentSecondary', onClick: () => onClose(), } as ButtonProps, trailing: { - children: 'Go to profile', + children: t('action.goToProfile'), as: 'a', href: getDestination(`/${json.name}`), } as ButtonProps, @@ -103,12 +108,37 @@ export const dentityVerificationHandler = () => ({ open: true, - title: 'Verification failed', - message: - '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.', + title: t('verificationErrorDialog.title'), + message: t('verificationErrorDialog.ownerNotManager'), actions: { trailing: { - children: 'Done', + children: t('action.done'), + onClick: () => onClose(), + } as ButtonProps, + }, + onClose, + onDismiss, + }) as VerificationErrorDialogProps, + ) + .with( + { + owner: P.string, + name: P.string, + verifiedPresentationUri: P.string, + resolverAddress: P.string, + }, + ({ manager, owner, primaryName }) => + ({ + open: true, + title: t('verificationErrorDialog.title'), + message: { + t, + i18nKey: 'verificationErrorDialog.wrongAccount', + values: { nameOrAddress: primaryName ?? shortenAddress(manager ?? owner) }, + }, + actions: { + trailing: { + children: t('action.done'), onClick: () => onClose(), } as ButtonProps, }, @@ -120,11 +150,11 @@ export const dentityVerificationHandler = () => ({ open: true, - title: 'Verification failed', - message: "We could't verify your account. Please return to Dentity and try again.", + title: t('verificationErrorDialog.title'), + message: t('verificationErrorDialog.default'), actions: { trailing: { - children: 'Close', + children: t('action.close'), colorStyle: 'accentSecondary', onClick: () => onClose(), } as ButtonProps, diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ff0e339a8..77fccae3c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -116,7 +116,7 @@ export default function Page() { - {dialogProps && } + ) } diff --git a/src/transaction-flow/input/VerifyProfile/components/VerificationOptionButton.tsx b/src/transaction-flow/input/VerifyProfile/components/VerificationOptionButton.tsx index b3e039843..cc8ec7f84 100644 --- a/src/transaction-flow/input/VerifyProfile/components/VerificationOptionButton.tsx +++ b/src/transaction-flow/input/VerifyProfile/components/VerificationOptionButton.tsx @@ -1,4 +1,5 @@ import { ComponentPropsWithRef, ReactNode } from 'react' +import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' import { RightArrowSVG, Tag, Typography } from '@ensdomains/thorin' @@ -53,6 +54,7 @@ const ArrowWrapper = styled.div( ) export const VerificationOptionButton = ({ icon, children, verified, ...props }: Props) => { + const { t } = useTranslation('transactionFlow') return ( {icon && {icon}} @@ -61,7 +63,7 @@ export const VerificationOptionButton = ({ icon, children, verified, ...props }: {verified && ( - Added + {t('input.verifyProfile.list.added')} )}