Files
twenty/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx
T
Félix Malfait b470cb21a1 Upgrade Apollo Client to v4 and refactor error handling (#18584)
## Summary
This PR upgrades Apollo Client from v3.10.0 to v4 and refactors error
handling patterns across the codebase to use a new centralized
`useSnackBarOnQueryError` hook.

## Key Changes

- **Dependency Update**: Upgraded `@apollo/client` from `^3.10.0` to
`^3.11.0` in root package.json
- **New Hook**: Added `useSnackBarOnQueryError` hook for centralized
Apollo query error handling with snack bar notifications
- **Error Handling Refactor**: Updated 100+ files to use the new error
handling pattern:
  - Removed direct `ApolloError` imports where no longer needed
- Replaced manual error handling logic with `useSnackBarOnQueryError`
hook
- Simplified error handling in hooks and components across multiple
modules
- **GraphQL Codegen**: Updated codegen configuration files to work with
Apollo Client v3.11.0
- **Type Definitions**: Added TypeScript declaration file for
`apollo-upload-client` module
- **Test Updates**: Updated test files to reflect new error handling
patterns

## Notable Implementation Details

- The new `useSnackBarOnQueryError` hook provides a consistent way to
handle Apollo query errors with automatic snack bar notifications
- Changes span across multiple feature areas: auth, object records,
settings, workflows, billing, and more
- All changes maintain backward compatibility while improving code
maintainability and reducing duplication
- Jest configuration updated to work with the new Apollo Client version

https://claude.ai/code/session_019WGZ6Rd7sEHuBg9sTrXRqJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 14:59:46 +01:00

136 lines
4.6 KiB
TypeScript

import { useAuth } from '@/auth/hooks/useAuth';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { AppPath } from 'twenty-shared/types';
import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState';
import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin';
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { ModalContent } from 'twenty-ui/layout';
import { useLingui } from '@lingui/react/macro';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
import { EmailVerificationSent } from '@/auth/sign-in-up/components/EmailVerificationSent';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
export const VerifyEmailEffect = () => {
const {
verifyEmailAndGetLoginToken,
verifyEmailAndGetWorkspaceAgnosticToken,
} = useAuth();
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
const [searchParams] = useSearchParams();
const [isError, setIsError] = useState(false);
const setVerifyEmailRedirectPath = useSetAtomState(
verifyEmailRedirectPathState,
);
const email = searchParams.get('email');
const emailVerificationToken = searchParams.get('emailVerificationToken');
const verifyEmailRedirectPath = searchParams.get('nextPath');
const navigate = useNavigateApp();
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const { verifyLoginToken } = useVerifyLogin();
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
const clientConfigApiStatus = useAtomStateValue(clientConfigApiStatusState);
const { t } = useLingui();
useEffect(() => {
const verifyEmailToken = async () => {
if (!email || !emailVerificationToken) {
enqueueErrorSnackBar({
message: t`Invalid email verification link.`,
options: {
dedupeKey: 'email-verification-link-dedupe-key',
},
});
return navigate(AppPath.SignInUp);
}
const successSnackbarParams = {
message: t`Email verified.`,
options: {
dedupeKey: 'email-verification-dedupe-key',
},
};
try {
if (!isOnAWorkspace) {
await verifyEmailAndGetWorkspaceAgnosticToken(
emailVerificationToken,
email,
);
return enqueueSuccessSnackBar(successSnackbarParams);
}
const { loginToken, workspaceUrls } = await verifyEmailAndGetLoginToken(
emailVerificationToken,
email,
);
enqueueSuccessSnackBar(successSnackbarParams);
const workspaceUrl = getWorkspaceUrl(workspaceUrls);
if (workspaceUrl.slice(0, -1) !== window.location.origin) {
return await redirectToWorkspaceDomain(workspaceUrl, AppPath.Verify, {
loginToken: loginToken.token,
});
}
if (isDefined(verifyEmailRedirectPath)) {
setVerifyEmailRedirectPath(verifyEmailRedirectPath);
}
await verifyLoginToken(loginToken.token);
} catch (error) {
enqueueErrorSnackBar({
...(CombinedGraphQLErrors.is(error)
? { apolloError: error }
: { message: t`Email verification failed` }),
options: {
dedupeKey: 'email-verification-error-dedupe-key',
},
});
if (
CombinedGraphQLErrors.is(error) &&
error.errors[0].extensions?.subCode === 'EMAIL_ALREADY_VERIFIED'
) {
navigate(AppPath.SignInUp);
}
setIsError(true);
}
};
if (!clientConfigApiStatus.isLoadedOnce) {
return;
}
verifyEmailToken();
// Verify email only needs to run once at mount
// oxlint-disable-next-line react-hooks/exhaustive-deps
}, [clientConfigApiStatus.isLoadedOnce]);
if (isError) {
return (
<ModalContent isVerticallyCentered isHorizontallyCentered>
<EmailVerificationSent email={email} isError={true} />
</ModalContent>
);
}
return <></>;
};