Commit 75a3b2f1 authored by tom goriunov's avatar tom goriunov Committed by GitHub

unverified user journey improvements (#1029)

* unverified email page

* add cookies and redirects

* remove unnecessary code

* page cookie reset
parent 420246dc
......@@ -257,6 +257,7 @@ frontend:
- "/apps"
- "/static"
- "/auth/profile"
- "/auth/unverified-email"
- "/txs"
- "/tx"
- "/blocks"
......
......@@ -212,6 +212,7 @@ frontend:
- "/apps"
- "/static"
- "/auth/profile"
- "/auth/unverified-email"
- "/txs"
- "/tx"
- "/blocks"
......
......@@ -25,6 +25,7 @@ frontend:
- "/apps"
- "/static"
- "/auth/profile"
- "/auth/unverified-email"
- "/txs"
- "/tx"
- "/blocks"
......
......@@ -24,6 +24,7 @@ frontend:
- "/apps"
- "/static"
- "/auth/profile"
- "/auth/unverified-email"
- "/txs"
- "/tx"
- "/blocks"
......
<svg viewBox="0 0 177 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.961 7.644a2 2 0 0 0-2.251-2.812l-76.313 17.5a2 2 0 0 0-.727 3.569l17.58 12.744v17.636a2.001 2.001 0 0 0 3.321 1.502l10.716-9.426 21.384 9.744a2 2 0 0 0 2.634-.957l23.656-49.5ZM108.308 35.92 93.583 25.247l62.923-14.43-48.198 25.104Zm.942 1.764v14.173l8.367-7.36a.385.385 0 0 1 .022-.018l.016-.014a.998.998 0 0 1 .214-.242l37.608-30.616-46.227 24.077Zm31.293 15.962-19.927-9.08 39.719-32.335-19.792 41.415ZM93.278 65.729a1.5 1.5 0 0 0 1.93 2.296 93.435 93.435 0 0 0 2.449-2.13 57.65 57.65 0 0 0 .819-.753l.044-.042.012-.011.004-.004.001-.001L97.5 64l1.038 1.083a1.5 1.5 0 0 0-2.075-2.167l-.002.002-.008.008-.037.035a19.011 19.011 0 0 1-.154.145 90.663 90.663 0 0 1-2.984 2.623Zm-5.037 7.714a1.5 1.5 0 0 0-1.751-2.436 105.47 105.47 0 0 1-7.163 4.73 1.5 1.5 0 1 0 1.547 2.57c2.69-1.618 5.17-3.284 7.367-4.864Zm-15.172 9.06a1.5 1.5 0 0 0-1.28-2.714c-2.556 1.205-5.207 2.277-7.906 3.13a1.5 1.5 0 0 0 .905 2.86c2.847-.9 5.624-2.024 8.28-3.276Zm-17.046 5.22a1.5 1.5 0 0 0-.358-2.98A34.938 34.938 0 0 1 51.5 85c-1.392 0-2.728-.09-4.012-.26a1.5 1.5 0 1 0-.394 2.973c1.416.188 2.884.287 4.406.287 1.51 0 3.02-.097 4.523-.278Zm-17.422-2.388a1.5 1.5 0 1 0 1.212-2.745c-2.517-1.11-4.783-2.55-6.816-4.194a1.5 1.5 0 1 0-1.887 2.332c2.216 1.792 4.705 3.377 7.491 4.607ZM24.974 74.523a1.5 1.5 0 1 0 2.338-1.881C25.51 70.403 24 68.076 22.756 65.86a1.5 1.5 0 0 0-2.616 1.47c1.311 2.333 2.911 4.803 4.834 7.193Zm-8.515-15a1.5 1.5 0 0 0 2.796-1.086 49.986 49.986 0 0 1-1-2.813 32.043 32.043 0 0 1-.29-.956l-.013-.045-.002-.01a1.5 1.5 0 0 0-2.899.775l1.45-.388-1.45.388.001.003.002.005.004.018.018.061.064.227c.057.196.143.478.258.837.23.718.579 1.741 1.06 2.984Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 201 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M127.992 76.305c0 3.301-.633 6.4-1.9 9.294-1.221 2.895-2.917 5.428-5.088 7.599-2.171 2.17-4.704 3.89-7.598 5.156-2.895 1.22-5.993 1.831-9.295 1.831h-4.138c-3.302 0-6.423-.61-9.362-1.831-2.895-1.267-5.428-2.986-7.599-5.156-2.17-2.171-3.89-4.704-5.156-7.599-1.221-2.895-1.832-5.993-1.832-9.294v-52.24c0-3.3.611-6.399 1.832-9.294 1.267-2.894 2.985-5.427 5.156-7.598 2.171-2.17 4.704-3.867 7.599-5.088 2.94-1.266 6.06-1.9 9.362-1.9h4.138c3.302 0 6.4.633 9.295 1.9 2.894 1.221 5.427 2.917 7.598 5.088 2.171 2.171 3.867 4.704 5.088 7.598 1.267 2.895 1.9 5.993 1.9 9.295v52.239Zm-8.752-52.24c0-2.17-.407-4.183-1.221-6.037-.814-1.9-1.922-3.55-3.324-4.953-1.402-1.402-3.053-2.51-4.953-3.324-1.854-.814-3.867-1.221-6.038-1.221h-3.324c-2.171 0-4.206.407-6.106 1.221a16.718 16.718 0 0 0-4.952 3.324c-1.403 1.402-2.51 3.053-3.325 4.953-.814 1.854-1.22 3.867-1.22 6.038v52.239c0 2.17.406 4.206 1.22 6.105a16.72 16.72 0 0 0 3.325 4.953 16.72 16.72 0 0 0 4.952 3.324c1.9.814 3.935 1.222 6.106 1.222h3.324c2.171 0 4.184-.408 6.038-1.222 1.9-.814 3.551-1.922 4.953-3.324a16.72 16.72 0 0 0 3.324-4.953c.814-1.9 1.221-3.934 1.221-6.105v-52.24Zm-71.411 57.94v16.824a8.752 8.752 0 0 1-8.752-8.751v-8.073H3.275a3.275 3.275 0 0 1-2.85-4.89L42.38 3.021a2.914 2.914 0 0 1 5.45 1.436v69.202h8.345a8.345 8.345 0 0 1-8.345 8.344ZM39.077 25.49 12.28 73.659h26.797V25.49ZM198.22 86.25c1.276-2.917 1.914-6.039 1.914-9.365v-6.7c0-2.324-.319-4.534-.957-6.63a22.47 22.47 0 0 0-2.598-5.948 22.916 22.916 0 0 0-3.965-4.922 22.222 22.222 0 0 0-5.195-3.76 25.193 25.193 0 0 0 5.195-3.76 24.48 24.48 0 0 0 3.965-4.99 22.69 22.69 0 0 0 2.598-5.878c.638-2.142.957-4.375.957-6.7v-3.35c0-3.326-.638-6.448-1.914-9.364-1.231-2.917-2.94-5.47-5.127-7.657-2.188-2.187-4.74-3.896-7.657-5.127C182.519.823 179.397.185 176.07.185h-4.17c-3.327 0-6.471.638-9.433 1.914-2.917 1.23-5.469 2.94-7.657 5.127-2.187 2.188-3.919 4.74-5.195 7.657-1.23 2.916-1.846 6.038-1.846 9.365a2.974 2.974 0 0 0 2.497 2.935l2.408.393a3.372 3.372 0 0 0 3.914-3.328c0-2.188.41-4.216 1.23-6.084.82-1.914 1.937-3.578 3.35-4.99a16.835 16.835 0 0 1 4.99-3.35c1.914-.82 3.965-1.23 6.152-1.23h3.35c2.187 0 4.215.41 6.084 1.23 1.914.82 3.577 1.937 4.99 3.35 1.413 1.412 2.529 3.076 3.35 4.99.82 1.868 1.23 3.896 1.23 6.084v4.785c0 2.188-.41 4.238-1.23 6.152a15.982 15.982 0 0 1-3.35 4.922c-1.413 1.413-3.076 2.53-4.99 3.35-1.869.82-3.897 1.23-6.084 1.23h-2.358a4.205 4.205 0 1 0 0 8.409h2.358c2.187 0 4.215.41 6.084 1.23 1.914.82 3.577 1.937 4.99 3.35 1.413 1.412 2.529 3.076 3.35 4.99.82 1.868 1.23 3.896 1.23 6.084v8.135c0 2.187-.41 4.238-1.23 6.152a16.856 16.856 0 0 1-3.35 4.99c-1.413 1.413-3.076 2.53-4.99 3.35-1.869.82-3.897 1.23-6.084 1.23h-3.35c-2.187 0-4.238-.41-6.152-1.23a16.834 16.834 0 0 1-4.99-3.35 16.837 16.837 0 0 1-3.35-4.99c-.82-1.914-1.23-3.965-1.23-6.153a3.34 3.34 0 0 0-3.851-3.299l-2.468.383a2.952 2.952 0 0 0-2.5 2.916c0 3.327.616 6.45 1.846 9.366 1.276 2.916 3.008 5.469 5.195 7.656 2.188 2.188 4.74 3.92 7.657 5.195 2.962 1.231 6.106 1.846 9.433 1.846h4.17c3.327 0 6.449-.615 9.365-1.846 2.917-1.276 5.469-3.007 7.657-5.195 2.187-2.188 3.896-4.74 5.127-7.656Z" fill="currentColor"/>
</svg>
......@@ -5,6 +5,8 @@ import isBrowser from './isBrowser';
export enum NAMES {
NAV_BAR_COLLAPSED='nav_bar_collapsed',
API_TOKEN='_explorer_key',
INVALID_SESSION='invalid_session',
CONFIRM_EMAIL_PAGE_VIEWED='confirm_email_page_viewed',
TXS_SORT='txs_sort',
COLOR_MODE='chakra-ui-color-mode',
INDEXING_ALERT='indexing_alert',
......
import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import type { Route } from 'nextjs-routes';
import React from 'react';
import type { UserInfo } from 'types/api/account';
import type { ResourceError } from 'lib/api/resources';
import { resourceKey } from 'lib/api/resources';
import useLoginUrl from 'lib/hooks/useLoginUrl';
......@@ -13,22 +10,15 @@ export default function useIsAccountActionAllowed() {
const queryClient = useQueryClient();
const profileData = queryClient.getQueryData<UserInfo>([ resourceKey('user_info') ]);
const profileState = queryClient.getQueryState<unknown, ResourceError<{ message: string }>>([ resourceKey('user_info') ]);
const isAuth = Boolean(profileData);
const loginUrl = useLoginUrl();
const router = useRouter();
return React.useCallback((accountRoute: Route) => {
if (profileState?.error?.status === 403) {
router.push(accountRoute);
return false;
}
return React.useCallback(() => {
if (!isAuth) {
window.location.assign(loginUrl);
return false;
}
return true;
}, [ isAuth, loginUrl, profileState?.error?.status, router ]);
}, [ isAuth, loginUrl ]);
}
......@@ -41,6 +41,7 @@ const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/api/csrf': 'Node API: CSRF token',
'/api/healthz': 'Node API: Health check',
'/auth/auth0': 'Auth',
'/auth/unverified-email': 'Auth',
};
export default function getPageType(pathname: Route['pathname']) {
......
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { route } from 'nextjs-routes';
import appConfig from 'configs/app/config';
import { httpLogger } from 'lib/api/logger';
import { DAY } from 'lib/consts';
import * as cookies from 'lib/cookies';
export function account(req: NextRequest) {
if (!appConfig.isAccountSupported) {
return;
}
const apiTokenCookie = req.cookies.get(cookies.NAMES.API_TOKEN);
// if user doesn't have api token cookie and he is trying to access account page
// do redirect to auth page
if (!apiTokenCookie) {
// we don't have any info from router here, so just do straight forward sub-string search (sorry)
const isAccountRoute =
req.nextUrl.pathname.includes('/account/') ||
(req.nextUrl.pathname === '/txs' && req.nextUrl.searchParams.get('tab') === 'watchlist');
const isProfileRoute = req.nextUrl.pathname.includes('/auth/profile');
if ((isAccountRoute || isProfileRoute)) {
const authUrl = appConfig.authUrl + route({ pathname: '/auth/auth0', query: { path: req.nextUrl.pathname } });
return NextResponse.redirect(authUrl);
}
}
// if user hasn't confirmed email yet
if (req.cookies.get(cookies.NAMES.INVALID_SESSION)) {
// if user has both cookies, make redirect to logout
if (apiTokenCookie) {
// temporary solution
// TODO check app for integrity https://github.com/blockscout/frontend/issues/1028 and make typescript happy here
if (!appConfig.logoutUrl) {
httpLogger.logger.error({
message: 'Logout URL is not configured',
});
return;
}
// yes, we could have checked that the current URL is not the logout URL, but we hadn't
// logout URL is always external URL in auth0.com sub-domain
// at least we hope so
const res = NextResponse.redirect(appConfig.logoutUrl);
res.cookies.delete(cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED); // reset cookie to show email verification page again
return res;
}
// if user hasn't seen email verification page, make redirect to it
if (!req.cookies.get(cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED)) {
if (!req.nextUrl.pathname.includes('/auth/unverified-email')) {
const url = appConfig.baseUrl + route({ pathname: '/auth/unverified-email' });
const res = NextResponse.redirect(url);
res.cookies.set({
name: cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED,
value: 'true',
expires: Date.now() + 7 * DAY,
});
return res;
}
}
}
}
export { account } from './account';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { route } from 'nextjs-routes';
import appConfig from 'configs/app/config';
import { NAMES } from 'lib/cookies';
import generateCspPolicy from 'lib/csp/generateCspPolicy';
import * as middlewares from 'lib/next/middlewares/index';
const cspPolicy = generateCspPolicy();
......@@ -16,16 +14,9 @@ export function middleware(req: NextRequest) {
return;
}
// we don't have any info from router here, so just do straight forward sub-string search (sorry)
const isAccountRoute =
req.nextUrl.pathname.includes('/account/') ||
(req.nextUrl.pathname === '/txs' && req.nextUrl.searchParams.get('tab') === 'watchlist');
const isProfileRoute = req.nextUrl.pathname.includes('/auth/profile');
const apiToken = req.cookies.get(NAMES.API_TOKEN);
if ((isAccountRoute || isProfileRoute) && !apiToken && appConfig.isAccountSupported) {
const authUrl = appConfig.authUrl + route({ pathname: '/auth/auth0', query: { path: req.nextUrl.pathname } });
return NextResponse.redirect(authUrl);
const accountResponse = middlewares.account(req);
if (accountResponse) {
return accountResponse;
}
const end = Date.now();
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import UnverifiedEmail from 'ui/pages/UnverifiedEmail';
import Page from 'ui/shared/Page/Page';
const UnverifiedEmailPage: NextPage = () => {
const title = getNetworkTitle();
return (
<>
<Head><title>{ title }</title></Head>
<Page>
<UnverifiedEmail/>
</Page>
</>
);
};
export default UnverifiedEmailPage;
export { getServerSideProps } from 'lib/next/account/getServerSideProps';
......@@ -24,6 +24,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/apps">
| StaticRoute<"/auth/auth0">
| StaticRoute<"/auth/profile">
| StaticRoute<"/auth/unverified-email">
| DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }>
| StaticRoute<"/blocks">
| StaticRoute<"/csv-export">
......
......@@ -25,7 +25,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
const isAccountActionAllowed = useIsAccountActionAllowed();
const handleClick = React.useCallback(() => {
if (!isAccountActionAllowed({ pathname: '/account/watchlist' })) {
if (!isAccountActionAllowed()) {
return;
}
watchListId ? deleteModalProps.onOpen() : addModalProps.onOpen();
......
......@@ -25,7 +25,7 @@ const ApiKeysPage: React.FC = () => {
const [ apiKeyModalData, setApiKeyModalData ] = useState<ApiKey>();
const [ deleteModalData, setDeleteModalData ] = useState<ApiKey>();
const { data, isPlaceholderData, isError, error } = useApiQuery('api_keys', {
const { data, isPlaceholderData, isError } = useApiQuery('api_keys', {
queryOptions: {
placeholderData: Array(3).fill(API_KEY),
},
......@@ -60,9 +60,6 @@ const ApiKeysPage: React.FC = () => {
const content = (() => {
if (isError) {
if (error.status === 403) {
throw new Error('Unverified email error', { cause: error });
}
return <DataFetchAlert/>;
}
......
......@@ -22,7 +22,7 @@ const CustomAbiPage: React.FC = () => {
const [ customAbiModalData, setCustomAbiModalData ] = useState<CustomAbi>();
const [ deleteModalData, setDeleteModalData ] = useState<CustomAbi>();
const { data, isPlaceholderData, isError, error } = useApiQuery('custom_abi', {
const { data, isPlaceholderData, isError } = useApiQuery('custom_abi', {
queryOptions: {
placeholderData: Array(3).fill(CUSTOM_ABI),
},
......@@ -56,9 +56,6 @@ const CustomAbiPage: React.FC = () => {
const content = (() => {
if (isError) {
if (error.status === 403) {
throw new Error('Unverified email error', { cause: error });
}
return <DataFetchAlert/>;
}
......
......@@ -9,7 +9,7 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import UserAvatar from 'ui/shared/UserAvatar';
const MyProfile = () => {
const { data, isLoading, isError, error } = useFetchProfileInfo();
const { data, isLoading, isError } = useFetchProfileInfo();
useRedirectForInvalidAuthToken();
const content = (() => {
......@@ -18,9 +18,6 @@ const MyProfile = () => {
}
if (isError) {
if (error.status === 403) {
throw new Error('Unverified email error', { cause: error });
}
return <DataFetchAlert/>;
}
......
import { Box, Text, Button, Heading, Icon, chakra } from '@chakra-ui/react';
import React from 'react';
import icon403 from 'icons/error-pages/403.svg';
import iconEmailSent from 'icons/email-sent.svg';
import useApiFetch from 'lib/api/useApiFetch';
import dayjs from 'lib/date/dayjs';
import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
......@@ -9,17 +9,19 @@ import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode';
import useToast from 'lib/hooks/useToast';
interface Props {
className?: string;
email?: string;
email?: string; // TODO: obtain email from API
}
const AppErrorUnverifiedEmail = ({ className, email }: Props) => {
const UnverifiedEmail = ({ email }: Props) => {
const apiFetch = useApiFetch();
const [ isLoading, setIsLoading ] = React.useState(false);
const toast = useToast();
const handleButtonClick = React.useCallback(async() => {
const toastId = 'resend-email-error';
setIsLoading(true);
try {
await apiFetch('email_resend');
toast({
......@@ -44,6 +46,10 @@ const AppErrorUnverifiedEmail = ({ className, email }: Props) => {
return;
}
if (!payload.seconds_before_next_resend) {
return;
}
const timeUntilNextResend = dayjs().add(payload.seconds_before_next_resend, 'seconds').fromNow();
return `Email resend is available ${ timeUntilNextResend }.`;
})();
......@@ -58,12 +64,14 @@ const AppErrorUnverifiedEmail = ({ className, email }: Props) => {
isClosable: true,
});
}
setIsLoading(false);
}, [ apiFetch, toast ]);
return (
<Box className={ className }>
<Icon as={ icon403 } width="200px" height="auto"/>
<Heading mt={ 8 } size="2xl" fontFamily="body">Email is not verified</Heading>
<Box>
<Icon as={ iconEmailSent } width="180px" height="auto" mt="52px"/>
<Heading mt={ 6 } size="2xl">Verify your email address</Heading>
<Text variant="secondary" mt={ 3 }>
<span>Please confirm your email address to use the My Account feature. A confirmation email was sent to </span>
<span>{ email || 'your email address' }</span>
......@@ -73,6 +81,8 @@ const AppErrorUnverifiedEmail = ({ className, email }: Props) => {
mt={ 8 }
size="lg"
variant="outline"
isLoading={ isLoading }
loadingText="Resending..."
onClick={ handleButtonClick }
>
Resend verification email
......@@ -81,4 +91,4 @@ const AppErrorUnverifiedEmail = ({ className, email }: Props) => {
);
};
export default chakra(AppErrorUnverifiedEmail);
export default chakra(UnverifiedEmail);
......@@ -7,7 +7,6 @@ import type { VerifiedAddress, TokenInfoApplication, TokenInfoApplications, Veri
import appConfig from 'configs/app/config';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useFetchProfileInfo from 'lib/hooks/useFetchProfileInfo';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
import getQueryParamString from 'lib/router/getQueryParamString';
import { TOKEN_INFO_APPLICATION, VERIFIED_ADDRESS } from 'stubs/account';
......@@ -55,7 +54,6 @@ const VerifiedAddresses = () => {
},
},
});
const profileQuery = useFetchProfileInfo();
const isLoading = addressesQuery.isPlaceholderData || applicationsQuery.isPlaceholderData;
......@@ -101,10 +99,6 @@ const VerifiedAddresses = () => {
});
}, [ queryClient ]);
if (profileQuery.isError && profileQuery.error.status === 403) {
throw new Error('Unverified email error', { cause: profileQuery.error });
}
const addButton = (
<Skeleton mt={ 8 } isLoaded={ !isLoading } display="inline-block">
<Button size="lg" onClick={ modalProps.onOpen }>
......
......@@ -20,7 +20,7 @@ import WatchlistTable from 'ui/watchlist/WatchlistTable/WatchlistTable';
const WatchList: React.FC = () => {
const apiFetch = useApiFetch();
const { data, isPlaceholderData, isError, error } = useQuery<unknown, ResourceError, TWatchlist>(
const { data, isPlaceholderData, isError } = useQuery<unknown, ResourceError, TWatchlist>(
[ resourceKey('watchlist') ],
async() => {
const watchlistAddresses = await apiFetch<'watchlist', Array<WatchlistAddress>>('watchlist');
......@@ -96,9 +96,6 @@ const WatchList: React.FC = () => {
);
if (isError) {
if (error.status === 403) {
throw new Error('Unverified email error', { cause: error });
}
return <DataFetchAlert/>;
}
......
......@@ -14,7 +14,7 @@ import AddressTagTable from './AddressTagTable/AddressTagTable';
import DeletePrivateTagModal from './DeletePrivateTagModal';
const PrivateAddressTags = () => {
const { data: addressTagsData, isError, error, isPlaceholderData, refetch } = useApiQuery('private_tags_address', {
const { data: addressTagsData, isError, isPlaceholderData, refetch } = useApiQuery('private_tags_address', {
queryOptions: {
refetchOnMount: false,
placeholderData: Array(3).fill(PRIVATE_TAG_ADDRESS),
......@@ -52,9 +52,6 @@ const PrivateAddressTags = () => {
}, [ deleteModalProps ]);
if (isError) {
if (error.status === 403) {
throw new Error('Unverified email error', { cause: error });
}
return <DataFetchAlert/>;
}
......
......@@ -14,7 +14,7 @@ import TransactionTagListItem from './TransactionTagTable/TransactionTagListItem
import TransactionTagTable from './TransactionTagTable/TransactionTagTable';
const PrivateTransactionTags = () => {
const { data: transactionTagsData, isPlaceholderData, isError, error } = useApiQuery('private_tags_tx', {
const { data: transactionTagsData, isPlaceholderData, isError } = useApiQuery('private_tags_tx', {
queryOptions: {
refetchOnMount: false,
placeholderData: Array(3).fill(PRIVATE_TAG_TX),
......@@ -55,9 +55,6 @@ const PrivateTransactionTags = () => {
);
if (isError) {
if (error.status === 403) {
throw new Error('Unverified email error', { cause: error });
}
return <DataFetchAlert/>;
}
......
......@@ -21,7 +21,7 @@ const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
const deleteModalProps = useDisclosure();
const [ deleteModalData, setDeleteModalData ] = useState<PublicTag>();
const { data, isPlaceholderData, isError, error } = useApiQuery('public_tags', {
const { data, isPlaceholderData, isError } = useApiQuery('public_tags', {
queryOptions: {
placeholderData: Array(3).fill(PUBLIC_TAG),
},
......@@ -55,9 +55,6 @@ const PublicTagsData = ({ changeToFormScreen, onTagDelete }: Props) => {
);
if (isError) {
if (error.status === 403) {
throw new Error('Unverified email error', { cause: error });
}
return <DataFetchAlert/>;
}
......
import { MenuItem, Icon, chakra, useDisclosure } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import type { Route } from 'nextjs-routes';
import React from 'react';
import type { Address } from 'types/api/address';
......@@ -12,7 +11,7 @@ import PrivateTagModal from 'ui/privateTags/AddressModal/AddressModal';
interface Props {
className?: string;
hash: string;
onBeforeClick: (route: Route) => boolean;
onBeforeClick: () => boolean;
}
const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => {
......@@ -23,7 +22,7 @@ const PrivateTagMenuItem = ({ className, hash, onBeforeClick }: Props) => {
const addressData = queryClient.getQueryData<Address>(queryKey);
const handleClick = React.useCallback(() => {
if (!onBeforeClick({ pathname: '/account/tag-address' })) {
if (!onBeforeClick()) {
return;
}
......
import { MenuItem, Icon, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import type { Route } from 'nextjs-routes';
import React from 'react';
import iconPublicTags from 'icons/publictags.svg';
......@@ -8,14 +7,14 @@ import iconPublicTags from 'icons/publictags.svg';
interface Props {
className?: string;
hash: string;
onBeforeClick: (route: Route) => boolean;
onBeforeClick: () => boolean;
}
const PublicTagMenuItem = ({ className, hash, onBeforeClick }: Props) => {
const router = useRouter();
const handleClick = React.useCallback(() => {
if (!onBeforeClick({ pathname: '/account/public-tags-request' })) {
if (!onBeforeClick()) {
return;
}
......
......@@ -9,7 +9,6 @@ import * as mixpanel from 'lib/mixpanel';
import AppError from 'ui/shared/AppError/AppError';
import AppErrorBlockConsensus from 'ui/shared/AppError/AppErrorBlockConsensus';
import AppErrorInvalidTxHash from 'ui/shared/AppError/AppErrorInvalidTxHash';
import AppErrorUnverifiedEmail from 'ui/shared/AppError/AppErrorUnverifiedEmail';
import ErrorBoundary from 'ui/shared/ErrorBoundary';
import PageContent from 'ui/shared/Page/PageContent';
import Footer from 'ui/snippets/footer/Footer';
......@@ -46,19 +45,11 @@ const Page = ({
const isInvalidTxHash = error?.message.includes('Invalid tx hash');
const isBlockConsensus = messageInPayload?.includes('Block lost consensus');
const isUnverifiedEmail = statusCode === 403 && messageInPayload?.includes('Unverified email');
if (isInvalidTxHash) {
return <PageContent isHomePage={ isHomePage }><AppErrorInvalidTxHash/></PageContent>;
}
if (isUnverifiedEmail) {
const email = resourceErrorPayload && 'email' in resourceErrorPayload && typeof resourceErrorPayload.email === 'string' ?
resourceErrorPayload.email :
undefined;
return <PageContent isHomePage={ isHomePage }><AppErrorUnverifiedEmail mt="50px" email={ email }/></PageContent>;
}
if (isBlockConsensus) {
const hash = resourceErrorPayload && 'hash' in resourceErrorPayload && typeof resourceErrorPayload.hash === 'string' ?
resourceErrorPayload.hash :
......
......@@ -14,7 +14,7 @@ const ProfileMenuDesktop = () => {
React.useEffect(() => {
if (!isLoading) {
setHasMenu(Boolean(data) || error?.status === 403);
setHasMenu(Boolean(data));
}
}, [ data, error?.status, isLoading ]);
......
......@@ -16,7 +16,7 @@ const ProfileMenuMobile = () => {
React.useEffect(() => {
if (!isLoading) {
setHasMenu(Boolean(data) || error?.status === 403);
setHasMenu(Boolean(data));
}
}, [ data, error?.status, isLoading ]);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment