feat(auth): enhance error handling for sign-up and existing user checks (#14953)

Refactored error handling in `useSignUpInNewWorkspace` and `useSignInUp`
hooks to provide user-friendly messages. Added validation for existing
users during sign-up, updating server-side logic to throw specific
exceptions (`USER_ALREADY_EXIST`).

Fix
https://twenty-v7.sentry.io/issues/6686753138/events/latest/?environment=prod&project=4507072499810304&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20QueryFailedError&referrer=latest-event&sort=date

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Antoine Moreaux
2025-10-08 15:43:43 +02:00
committed by GitHub
parent 2e9779b511
commit 8e83be43fe
19 changed files with 555 additions and 144 deletions
@@ -173,7 +173,7 @@ export const SignInUpGlobalScopeForm = () => {
return;
}
continueWithCredentials();
await continueWithCredentials();
};
const onEmailChange = (email: string) => {
@@ -19,9 +19,11 @@ import { AppPath } from 'twenty-shared/types';
import { buildAppPathWithQueryParams } from '~/utils/buildAppPathWithQueryParams';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
import { useAuth } from '../../hooks/useAuth';
import { useLingui } from '@lingui/react/macro';
export const useSignInUp = (form: UseFormReturn<Form>) => {
const { enqueueErrorSnackBar } = useSnackBar();
const { t } = useLingui();
const [signInUpStep, setSignInUpStep] = useRecoilState(signInUpStepState);
const [signInUpMode, setSignInUpMode] = useRecoilState(signInUpModeState);
@@ -55,28 +57,33 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
setSignInUpStep(SignInUpStep.Email);
}, [setSignInUpStep]);
const errorMsgUserAlreadyExist = t`An error occurred while checking user existence`;
const continueWithCredentials = useCallback(async () => {
const token = await readCaptchaToken();
if (!form.getValues('email')) {
return;
}
checkUserExistsQuery({
variables: {
email: form.getValues('email').toLowerCase().trim(),
captchaToken: token,
},
onError: (error) => {
enqueueErrorSnackBar({ apolloError: error });
},
onCompleted: (data) => {
setSignInUpMode(
data?.checkUserExists.exists
? SignInUpMode.SignIn
: SignInUpMode.SignUp,
);
setSignInUpStep(SignInUpStep.Password);
},
});
try {
const { data } = await checkUserExistsQuery({
variables: {
email: form.getValues('email').toLowerCase().trim(),
captchaToken: token,
},
});
setSignInUpMode(
data?.checkUserExists.exists
? SignInUpMode.SignIn
: SignInUpMode.SignUp,
);
setSignInUpStep(SignInUpStep.Password);
} catch (error) {
enqueueErrorSnackBar({
...(error instanceof ApolloError
? { apolloError: error }
: { message: errorMsgUserAlreadyExist }),
});
}
}, [
readCaptchaToken,
form,
@@ -84,6 +91,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
enqueueErrorSnackBar,
setSignInUpStep,
setSignInUpMode,
errorMsgUserAlreadyExist,
]);
const submitCredentials: SubmitHandler<Form> = useCallback(
@@ -1,32 +1,38 @@
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { type ApolloError } from '@apollo/client';
import { ApolloError } from '@apollo/client';
import { AppPath } from 'twenty-shared/types';
import { useSignUpInNewWorkspaceMutation } from '~/generated-metadata/graphql';
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
import { useLingui } from '@lingui/react/macro';
export const useSignUpInNewWorkspace = () => {
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const { enqueueErrorSnackBar } = useSnackBar();
const { t } = useLingui();
const [signUpInNewWorkspaceMutation] = useSignUpInNewWorkspaceMutation();
const createWorkspace = async ({ newTab } = { newTab: true }) => {
await signUpInNewWorkspaceMutation({
onCompleted: async (data) => {
return await redirectToWorkspaceDomain(
getWorkspaceUrl(data.signUpInNewWorkspace.workspace.workspaceUrls),
AppPath.Verify,
{
loginToken: data.signUpInNewWorkspace.loginToken.token,
},
newTab ? '_blank' : '_self',
);
},
onError: (error: ApolloError) => {
enqueueErrorSnackBar({ apolloError: error });
},
});
try {
const { data } = await signUpInNewWorkspaceMutation();
assertIsDefinedOrThrow(data?.signUpInNewWorkspace);
return await redirectToWorkspaceDomain(
getWorkspaceUrl(data.signUpInNewWorkspace.workspace.workspaceUrls),
AppPath.Verify,
{
loginToken: data.signUpInNewWorkspace.loginToken.token,
},
newTab ? '_blank' : '_self',
);
} catch (error) {
enqueueErrorSnackBar({
...(error instanceof ApolloError
? { apolloError: error }
: { message: t`Workspace creation failed` }),
});
}
};
return {