From 288f0919dbf2f5215d81f742b9f5ed7a35ddb13f Mon Sep 17 00:00:00 2001
From: Marie <51697796+ijreilly@users.noreply.github.com>
Date: Thu, 3 Jul 2025 14:42:10 +0200
Subject: [PATCH] Define server error messages to display in FE from the server
(#12973)
Currently, when a server query or mutation from the front-end fails, the
error message defined server-side is displayed in a snackbar in the
front-end.
These error messages usually contain technical details that don't belong
to the user interface, such as "ObjectMetadataCollection not found" or
"invalid ENUM value for ...".
**BE**
In addition to the original error message that is still needed (for the
request response, debugging, sentry monitoring etc.), we add a
`displayedErrorMessage` that will be used in the snackbars. It's only
relevant to add it for the messages that will reach the FE (ie. not in
jobs or in rest api for instance) and if it can help the user sort out /
fix things (ie. we do add displayedErrorMessage for "Cannot create
multiple draft versions for the same workflow" or "Cannot delete
[field], please update the label identifier field first", but not
"Object metadata does not exist"), even if in practice in the FE users
should not be able to perform an action that will not work (ie should
not be able to save creation of multiple draft versions of the same
workflows).
**FE**
To ease the usage we replaced enqueueSnackBar with enqueueErrorSnackBar
and enqueueSuccessSnackBar with an api that only requires to pass on the
error.
If no displayedErrorMessage is specified then the default error message
is `An error occured.`
---
.../src/hooks/useCopyToClipboard.tsx | 23 +--
.../activities/hooks/useCustomResolver.ts | 9 +-
.../auth/components/VerifyEmailEffect.tsx | 34 ++--
.../hooks/__tests__/useVerifyLogin.test.ts | 11 +-
.../src/modules/auth/hooks/useVerifyLogin.ts | 8 +-
.../__tests__/useHandleResetPassword.test.ts | 26 ++-
.../hooks/__tests__/useSSO.test.tsx | 13 +-
.../useHandleResendEmailVerificationToken.ts | 27 ++--
.../hooks/useHandleResetPassword.ts | 27 ++--
.../modules/auth/sign-in-up/hooks/useSSO.ts | 9 +-
.../auth/sign-in-up/hooks/useSignInUp.ts | 20 ++-
.../hooks/useSignUpInNewWorkspace.ts | 10 +-
.../hooks/useWorkspaceFromInviteHash.ts | 21 ++-
.../SettingsBillingSubscriptionInfo.tsx | 24 ++-
.../hooks/useEndSubscriptionTrialPeriod.ts | 25 ++-
.../billing/hooks/useHandleCheckoutSession.ts | 13 +-
.../components/ErrorMessageEffect.tsx | 13 +-
.../components/PromiseRejectionEffect.tsx | 25 +--
.../hooks/useFindManyObjectMetadataItems.ts | 7 +-
.../hooks/useBatchCreateManyRecords.ts | 12 +-
.../object-record/hooks/useDeleteOneRecord.ts | 3 +-
.../hooks/useFindDuplicateRecords.ts | 7 +-
.../hooks/useHandleFindManyRecordsError.ts | 11 +-
.../hooks/useObjectRecordSearchRecords.ts | 13 +-
.../ObjectOptionsDropdownMenuContent.tsx | 23 +--
.../components/LightCopyIconButton.tsx | 13 +-
.../display/components/PhonesFieldDisplay.tsx | 23 +--
...penObjectRecordsSpreadsheetImportDialog.ts | 7 +-
.../accounts/hooks/useImapConnectionForm.ts | 25 ++-
.../components/SettingsAdminGeneral.tsx | 7 +-
.../SettingsAdminWorkspaceContent.tsx | 15 +-
.../SettingsAdminConfigCopyableText.tsx | 11 +-
.../hooks/useConfigVariableActions.ts | 19 ++-
.../components/WorkerMetricsGraph.tsx | 7 +-
...SettingsUpdateDataModelObjectAboutForm.tsx | 19 ++-
...SettingsDataModelObjectIdentifiersForm.tsx | 12 +-
.../developers/components/ApiKeyInput.tsx | 15 +-
.../hooks/__tests__/useWebhookForm.test.tsx | 52 +++---
.../developers/hooks/useWebhookForm.ts | 48 +++---
...tegrationEditDatabaseConnectionContent.tsx | 10 +-
.../roles/role/components/SettingsRole.tsx | 7 +-
...SettingsSSOIdentitiesProvidersListCard.tsx | 10 +-
.../components/SSO/SettingsSSOOIDCForm.tsx | 27 ++--
.../components/SSO/SettingsSSOSAMLForm.tsx | 43 ++---
.../SettingsSecuritySSORowDropdownMenu.tsx | 19 ++-
...ttingsSecurityAuthProvidersOptionsList.tsx | 21 ++-
.../SettingsApprovedAccessDomainsListCard.tsx | 8 +-
...ityApprovedAccessDomainRowDropdownMenu.tsx | 12 +-
...tyApprovedAccessDomainValidationEffect.tsx | 20 ++-
.../components/ToggleImpersonate.tsx | 8 +-
.../components/SpreadsheetImportStepper.tsx | 9 +-
.../UploadStep/components/DropZone.tsx | 11 +-
.../snack-bar-manager/hooks/useSnackBar.ts | 85 +++++++++-
...ultiWorkspaceDropdownDefaultComponents.tsx | 10 +-
.../WorkflowEditTriggerWebhookForm.tsx | 13 +-
.../components/WorkspaceInviteLink.tsx | 15 +-
.../components/WorkspaceInviteTeam.tsx | 19 +--
.../src/pages/auth/PasswordReset.tsx | 27 ++--
.../src/pages/onboarding/CreateProfile.tsx | 10 +-
.../src/pages/onboarding/CreateWorkspace.tsx | 11 +-
.../src/pages/onboarding/InviteTeam.tsx | 23 +--
.../settings/SettingsWorkspaceMembers.tsx | 26 +--
.../settings/data-model/SettingsNewObject.tsx | 14 +-
.../data-model/SettingsObjectFieldEdit.tsx | 8 +-
.../SettingsObjectNewFieldConfigure.tsx | 14 +-
.../SettingsDevelopersApiKeyDetail.tsx | 11 +-
...ttingsIntegrationNewDatabaseConnection.tsx | 12 +-
.../SettingsSecurityApprovedAccessDomain.tsx | 16 +-
.../SettingsSecuritySSOIdentifyProvider.tsx | 10 +-
.../SettingsServerlessFunctionDetail.tsx | 31 ++--
.../workspace/SettingsCustomDomainRecords.tsx | 17 +-
.../settings/workspace/SettingsDomain.tsx | 31 ++--
...et-error-message-from-apollo-error.util.ts | 14 ++
...approved-access-domain-exception-filter.ts | 6 +-
.../approved-access-domain.exception.ts | 8 +-
.../approved-access-domain.service.ts | 12 +-
.../core-modules/auth/auth.exception.ts | 8 +-
.../auth-graphql-api-exception.filter.ts | 25 ++-
.../auth/services/auth.service.ts | 3 +
.../auth/services/sign-in-up.service.ts | 22 +++
...mail-verification-exception-filter.util.ts | 21 ++-
.../hooks/use-graphql-error-handler.hook.ts | 25 ++-
.../generate-graphql-error-from-error.util.ts | 14 +-
.../graphql/utils/graphql-errors.util.ts | 10 +-
.../record-transformer.exception.ts | 8 +-
...rmer-graphql-api-exception-handler.util.ts | 4 +-
.../utils/transform-phones-value.util.ts | 7 +
...ow-trigger-graphql-api-exception.filter.ts | 12 +-
.../field-metadata.exception.ts | 8 +-
.../field-metadata/field-metadata.service.ts | 7 +
.../field-metadata-enum-validation.service.ts | 25 +--
...data-graphql-api-exception-handler.util.ts | 16 +-
.../object-metadata.exception.ts | 8 +-
...te-object-metadata-input.util.spec.ts.snap | 2 +-
...data-graphql-api-exception-handler.util.ts | 8 +-
.../validate-object-metadata-input.util.ts | 10 +-
.../permissions/permissions.exception.ts | 2 -
...sion-graphql-api-exception-handler.util.ts | 11 +-
.../validate-metadata-name.spec.ts.snap | 2 +-
.../compute-metadata-name-from-label.util.ts | 7 +
.../exceptions/invalid-metadata.exception.ts | 8 +-
.../validate-field-name-availability.utils.ts | 5 +
...idate-metadata-name-is-camel-case.utils.ts | 3 +-
...e-metadata-name-is-not-reserved-keyword.ts | 5 +
...ate-metadata-name-is-not-too-long.utils.ts | 4 +-
...te-metadata-name-is-not-too-short.utils.ts | 4 +-
...er-and-contain-digits-nor-letters.utils.ts | 4 +-
...ect-with-same-name-exists-or-throw.util.ts | 5 +
.../workflow-query-validation.exception.ts | 8 +-
.../workflow-version-step.exception.ts | 8 +-
.../utils/assert-workflow-statuses-not-set.ts | 5 +
.../assert-workflow-version-has-steps.ts | 5 +
.../assert-workflow-version-is-draft.util.ts | 5 +
...orkflow-version-trigger-is-defined.util.ts | 5 +
...ow-version-validation.workspace-service.ts | 13 ++
...workflow-version-step.workspace-service.ts | 4 +
.../workflow-step-executor.exception.ts | 8 +-
.../utils/get-previous-step-output.util.ts | 50 ++++++
.../exceptions/workflow-trigger.exception.ts | 8 +-
.../utils/assert-form-step-is-valid.util.ts | 13 ++
.../assert-version-can-be-activated.util.ts | 56 +++++++
.../compute-cron-pattern-from-schedule.ts | 9 +-
.../workflow-trigger.workspace-service.ts | 7 +
.../src/utils/custom-exception.ts | 4 +-
...ct-records-permissions.integration-spec.ts | 9 +-
...ta-related-record.integration-spec.ts.snap | 6 +
...eld-metadata-enum.integration-spec.ts.snap | 148 +++++++++++++++++-
...um-field-metadata.integration-spec.ts.snap | 144 ++++++++++++++++-
...ld-metadata-phone.integration-spec.ts.snap | 28 ++++
...ate-one-field-metadata.integration-spec.ts | 2 +
...e-object-metadata.integration-spec.ts.snap | 12 +-
...relation-creation.integration-spec.ts.snap | 11 +-
...e-object-metadata.integration-spec.ts.snap | 6 +
133 files changed, 1501 insertions(+), 711 deletions(-)
create mode 100644 packages/twenty-front/src/utils/get-error-message-from-apollo-error.util.ts
create mode 100644 packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/get-previous-step-output.util.ts
diff --git a/packages/twenty-front/src/hooks/useCopyToClipboard.tsx b/packages/twenty-front/src/hooks/useCopyToClipboard.tsx
index 59776c0e7..588590444 100644
--- a/packages/twenty-front/src/hooks/useCopyToClipboard.tsx
+++ b/packages/twenty-front/src/hooks/useCopyToClipboard.tsx
@@ -1,4 +1,3 @@
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
@@ -6,23 +5,27 @@ import { IconCopy, IconExclamationCircle } from 'twenty-ui/display';
export const useCopyToClipboard = () => {
const theme = useTheme();
- const { enqueueSnackBar } = useSnackBar();
+ const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
const { t } = useLingui();
const copyToClipboard = async (valueAsString: string) => {
try {
await navigator.clipboard.writeText(valueAsString);
- enqueueSnackBar(t`Copied to clipboard`, {
- variant: SnackBarVariant.Success,
- icon: ,
- duration: 2000,
+ enqueueSuccessSnackBar({
+ message: t`Copied to clipboard`,
+ options: {
+ icon: ,
+ duration: 2000,
+ },
});
} catch {
- enqueueSnackBar(t`Couldn't copy to clipboard`, {
- variant: SnackBarVariant.Error,
- icon: ,
- duration: 2000,
+ enqueueErrorSnackBar({
+ message: t`Couldn't copy to clipboard`,
+ options: {
+ icon: ,
+ duration: 2000,
+ },
});
}
};
diff --git a/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts b/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts
index ee1e50772..273b85f75 100644
--- a/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts
+++ b/packages/twenty-front/src/modules/activities/hooks/useCustomResolver.ts
@@ -1,14 +1,13 @@
-import { useState } from 'react';
import {
DocumentNode,
OperationVariables,
TypedDocumentNode,
useQuery,
} from '@apollo/client';
+import { useState } from 'react';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
type CustomResolverQueryResult<
@@ -37,7 +36,7 @@ export const useCustomResolver = <
isFetchingMore: boolean;
fetchMoreRecords: () => Promise;
} => {
- const { enqueueSnackBar } = useSnackBar();
+ const { enqueueErrorSnackBar } = useSnackBar();
const [page, setPage] = useState({
pageNumber: 1,
@@ -62,8 +61,8 @@ export const useCustomResolver = <
} = useQuery>(query, {
variables: queryVariables,
onError: (error) => {
- enqueueSnackBar(error.message || `Error loading ${objectName}`, {
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ apolloError: error,
});
},
});
diff --git a/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx
index 640424baf..fad0b7d57 100644
--- a/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx
+++ b/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx
@@ -1,6 +1,5 @@
import { useAuth } from '@/auth/hooks/useAuth';
import { AppPath } from '@/types/AppPath';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ApolloError } from '@apollo/client';
@@ -20,7 +19,7 @@ import { EmailVerificationSent } from '../sign-in-up/components/EmailVerificatio
export const VerifyEmailEffect = () => {
const { getLoginTokenFromEmailVerificationToken } = useAuth();
- const { enqueueSnackBar } = useSnackBar();
+ const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
const [searchParams] = useSearchParams();
const [isError, setIsError] = useState(false);
@@ -39,9 +38,11 @@ export const VerifyEmailEffect = () => {
useEffect(() => {
const verifyEmailToken = async () => {
if (!email || !emailVerificationToken) {
- enqueueSnackBar(t`Invalid email verification link.`, {
- dedupeKey: 'email-verification-link-dedupe-key',
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ message: t`Invalid email verification link.`,
+ options: {
+ dedupeKey: 'email-verification-link-dedupe-key',
+ },
});
return navigate(AppPath.SignInUp);
}
@@ -53,9 +54,11 @@ export const VerifyEmailEffect = () => {
email,
);
- enqueueSnackBar(t`Email verified.`, {
- dedupeKey: 'email-verification-dedupe-key',
- variant: SnackBarVariant.Success,
+ enqueueSuccessSnackBar({
+ message: t`Email verified.`,
+ options: {
+ dedupeKey: 'email-verification-dedupe-key',
+ },
});
const workspaceUrl = getWorkspaceUrl(workspaceUrls);
@@ -71,14 +74,13 @@ export const VerifyEmailEffect = () => {
verifyLoginToken(loginToken.token);
} catch (error) {
- const message: string =
- error instanceof ApolloError
- ? error.message
- : 'Email verification failed';
-
- enqueueSnackBar(t`${message}`, {
- dedupeKey: 'email-verification-error-dedupe-key',
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ ...(error instanceof ApolloError
+ ? { apolloError: error }
+ : { message: t`Email verification failed` }),
+ options: {
+ dedupeKey: 'email-verification-error-dedupe-key',
+ },
});
if (
error instanceof ApolloError &&
diff --git a/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts b/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts
index 4df3160d7..8ff6d1494 100644
--- a/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts
+++ b/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts
@@ -4,14 +4,13 @@ import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { AppPath } from '@/types/AppPath';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { useAuth } from '../useAuth';
import { useVerifyLogin } from '../useVerifyLogin';
-import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
import { SOURCE_LOCALE } from 'twenty-shared/translations';
+import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
jest.mock('../useAuth', () => ({
useAuth: jest.fn(),
@@ -37,7 +36,7 @@ const renderHooks = () => {
describe('useVerifyLogin', () => {
const mockGetAuthTokensFromLoginToken = jest.fn();
- const mockEnqueueSnackBar = jest.fn();
+ const mockEnqueueErrorSnackBar = jest.fn();
const mockNavigate = jest.fn();
beforeEach(() => {
@@ -48,7 +47,7 @@ describe('useVerifyLogin', () => {
});
(useSnackBar as jest.Mock).mockReturnValue({
- enqueueSnackBar: mockEnqueueSnackBar,
+ enqueueErrorSnackBar: mockEnqueueErrorSnackBar,
});
(useNavigateApp as jest.Mock).mockReturnValue(mockNavigate);
@@ -70,8 +69,8 @@ describe('useVerifyLogin', () => {
await result.current.verifyLoginToken('test-token');
- expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Authentication failed', {
- variant: SnackBarVariant.Error,
+ expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
+ message: 'Authentication failed',
});
expect(mockNavigate).toHaveBeenCalledWith(AppPath.SignInUp);
});
diff --git a/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts b/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts
index 6862c1753..dcca65900 100644
--- a/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts
+++ b/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts
@@ -1,5 +1,3 @@
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
-
import { useAuth } from '@/auth/hooks/useAuth';
import { AppPath } from '@/types/AppPath';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@@ -7,7 +5,7 @@ import { useLingui } from '@lingui/react/macro';
import { useNavigateApp } from '~/hooks/useNavigateApp';
export const useVerifyLogin = () => {
- const { enqueueSnackBar } = useSnackBar();
+ const { enqueueErrorSnackBar } = useSnackBar();
const navigate = useNavigateApp();
const { getAuthTokensFromLoginToken } = useAuth();
const { t } = useLingui();
@@ -16,8 +14,8 @@ export const useVerifyLogin = () => {
try {
await getAuthTokensFromLoginToken(loginToken);
} catch (error) {
- enqueueSnackBar(t`Authentication failed`, {
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ message: t`Authentication failed`,
});
navigate(AppPath.SignInUp);
}
diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useHandleResetPassword.test.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useHandleResetPassword.test.ts
index 40fbdfe83..922213e24 100644
--- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useHandleResetPassword.test.ts
+++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useHandleResetPassword.test.ts
@@ -5,7 +5,6 @@ import { RecoilRoot } from 'recoil';
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SOURCE_LOCALE } from 'twenty-shared/translations';
import {
@@ -36,14 +35,16 @@ const renderHooks = () => {
};
describe('useHandleResetPassword', () => {
- const enqueueSnackBarMock = jest.fn();
+ const enqueueErrorSnackBarMock = jest.fn();
+ const enqueueSuccessSnackBarMock = jest.fn();
const emailPasswordResetLinkMock = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
(useSnackBar as jest.Mock).mockReturnValue({
- enqueueSnackBar: enqueueSnackBarMock,
+ enqueueErrorSnackBar: enqueueErrorSnackBarMock,
+ enqueueSuccessSnackBar: enqueueSuccessSnackBarMock,
});
(useEmailPasswordResetLinkMutation as jest.Mock).mockReturnValue([
emailPasswordResetLinkMock,
@@ -54,8 +55,8 @@ describe('useHandleResetPassword', () => {
const { result } = renderHooks();
await act(() => result.current.handleResetPassword('')());
- expect(enqueueSnackBarMock).toHaveBeenCalledWith('Invalid email', {
- variant: SnackBarVariant.Error,
+ expect(enqueueErrorSnackBarMock).toHaveBeenCalledWith({
+ message: 'Invalid email',
});
});
@@ -67,10 +68,9 @@ describe('useHandleResetPassword', () => {
const { result } = renderHooks();
await act(() => result.current.handleResetPassword('test@example.com')());
- expect(enqueueSnackBarMock).toHaveBeenCalledWith(
- 'Password reset link has been sent to the email',
- { variant: SnackBarVariant.Success },
- );
+ expect(enqueueSuccessSnackBarMock).toHaveBeenCalledWith({
+ message: 'Password reset link has been sent to the email',
+ });
});
it('should show error message if sending reset link fails', async () => {
@@ -81,9 +81,7 @@ describe('useHandleResetPassword', () => {
const { result } = renderHooks();
await act(() => result.current.handleResetPassword('test@example.com')());
- expect(enqueueSnackBarMock).toHaveBeenCalledWith('There was an issue', {
- variant: SnackBarVariant.Error,
- });
+ expect(enqueueErrorSnackBarMock).toHaveBeenCalledWith({});
});
it('should show error message in case of request error', async () => {
@@ -93,8 +91,6 @@ describe('useHandleResetPassword', () => {
const { result } = renderHooks();
await act(() => result.current.handleResetPassword('test@example.com')());
- expect(enqueueSnackBarMock).toHaveBeenCalledWith(errorMessage, {
- variant: SnackBarVariant.Error,
- });
+ expect(enqueueErrorSnackBarMock).toHaveBeenCalledWith({});
});
});
diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSSO.test.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSSO.test.tsx
index d3022d7bf..8a50396ed 100644
--- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSSO.test.tsx
+++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/__tests__/useSSO.test.tsx
@@ -2,19 +2,20 @@ import { GET_AUTHORIZATION_URL_FOR_SSO } from '@/auth/graphql/mutations/getAutho
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { ApolloError } from '@apollo/client';
import { MockedProvider } from '@apollo/client/testing';
-import { MemoryRouter } from 'react-router-dom';
import { renderHook } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
jest.mock('@/domain-manager/hooks/useRedirect');
jest.mock('~/generated/graphql');
-const mockEnqueueSnackBar = jest.fn();
+const mockEnqueueErrorSnackBar = jest.fn();
const mockRedirect = jest.fn();
(useSnackBar as jest.Mock).mockReturnValue({
- enqueueSnackBar: mockEnqueueSnackBar,
+ enqueueErrorSnackBar: mockEnqueueErrorSnackBar,
});
(useRedirect as jest.Mock).mockReturnValue({
redirect: mockRedirect,
@@ -84,8 +85,10 @@ describe('useSSO', () => {
await result.current.redirectToSSOLoginPage(identityProviderId);
- expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Error message', {
- variant: 'error',
+ expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
+ apolloError: new ApolloError({
+ graphQLErrors: [{ message: 'Error message' }],
+ }),
});
});
});
diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts
index 757b84995..69b3625cd 100644
--- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts
+++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts
@@ -1,13 +1,13 @@
import { useCallback } from 'react';
import { useOrigin } from '@/domain-manager/hooks/useOrigin';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { useResendEmailVerificationTokenMutation } from '~/generated-metadata/graphql';
export const useHandleResendEmailVerificationToken = () => {
- const { enqueueSnackBar } = useSnackBar();
+ const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
const [resendEmailVerificationToken, { loading }] =
useResendEmailVerificationTokenMutation();
const { origin } = useOrigin();
@@ -16,8 +16,8 @@ export const useHandleResendEmailVerificationToken = () => {
(email: string | null) => {
return async () => {
if (!email) {
- enqueueSnackBar(t`Invalid email`, {
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ message: t`Invalid email`,
});
return;
}
@@ -31,22 +31,25 @@ export const useHandleResendEmailVerificationToken = () => {
});
if (data?.resendEmailVerificationToken?.success === true) {
- enqueueSnackBar(t`Email verification link resent!`, {
- variant: SnackBarVariant.Success,
+ enqueueSuccessSnackBar({
+ message: t`Email verification link resent!`,
});
} else {
- enqueueSnackBar(t`There was an issue`, {
- variant: SnackBarVariant.Error,
- });
+ enqueueErrorSnackBar({});
}
} catch (error) {
- enqueueSnackBar((error as Error).message, {
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ ...(error instanceof ApolloError ? { apolloError: error } : {}),
});
}
};
},
- [enqueueSnackBar, resendEmailVerificationToken, origin],
+ [
+ enqueueErrorSnackBar,
+ enqueueSuccessSnackBar,
+ resendEmailVerificationToken,
+ origin,
+ ],
);
return { handleResendEmailVerificationToken, loading };
diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts
index b14c244dd..bbb94c694 100644
--- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts
+++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts
@@ -2,14 +2,14 @@ import { useCallback } from 'react';
import { currentUserState } from '@/auth/states/currentUserState';
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { ApolloError } from '@apollo/client';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { useEmailPasswordResetLinkMutation } from '~/generated-metadata/graphql';
export const useHandleResetPassword = () => {
- const { enqueueSnackBar } = useSnackBar();
+ const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation();
const workspacePublicData = useRecoilValue(workspacePublicDataState);
const currentUser = useRecoilValue(currentUserState);
@@ -20,15 +20,15 @@ export const useHandleResetPassword = () => {
(email = currentUser?.email) => {
return async () => {
if (!email) {
- enqueueSnackBar(t`Invalid email`, {
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ message: t`Invalid email`,
});
return;
}
if (!workspacePublicData?.id) {
- enqueueSnackBar(t`Invalid workspace`, {
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ message: t`Invalid workspace`,
});
return;
}
@@ -39,17 +39,15 @@ export const useHandleResetPassword = () => {
});
if (data?.emailPasswordResetLink?.success === true) {
- enqueueSnackBar(t`Password reset link has been sent to the email`, {
- variant: SnackBarVariant.Success,
+ enqueueSuccessSnackBar({
+ message: t`Password reset link has been sent to the email`,
});
} else {
- enqueueSnackBar(t`There was an issue`, {
- variant: SnackBarVariant.Error,
- });
+ enqueueErrorSnackBar({});
}
} catch (error) {
- enqueueSnackBar((error as Error).message, {
- variant: SnackBarVariant.Error,
+ enqueueErrorSnackBar({
+ ...(error instanceof ApolloError ? { apolloError: error } : {}),
});
}
};
@@ -57,7 +55,8 @@ export const useHandleResetPassword = () => {
[
currentUser?.email,
workspacePublicData?.id,
- enqueueSnackBar,
+ enqueueErrorSnackBar,
+ enqueueSuccessSnackBar,
t,
emailPasswordResetLink,
],
diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSSO.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSSO.ts
index 8db029fc9..f36fc6bfd 100644
--- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSSO.ts
+++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSSO.ts
@@ -2,16 +2,15 @@
import { GET_AUTHORIZATION_URL_FOR_SSO } from '@/auth/graphql/mutations/getAuthorizationUrlForSSO';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
-import { useApolloClient } from '@apollo/client';
+import { ApolloError, useApolloClient } from '@apollo/client';
import { useParams } from 'react-router-dom';
export const useSSO = () => {
const apolloClient = useApolloClient();
const workspaceInviteHash = useParams().workspaceInviteHash;
- const { enqueueSnackBar } = useSnackBar();
+ const { enqueueErrorSnackBar } = useSnackBar();
const { redirect } = useRedirect();
const redirectToSSOLoginPage = async (identityProviderId: string) => {
let authorizationUrlForSSOResult;
@@ -26,8 +25,8 @@ export const useSSO = () => {
},
});
} catch (error: any) {
- return enqueueSnackBar(error?.message ?? 'Unknown error', {
- variant: SnackBarVariant.Error,
+ return enqueueErrorSnackBar({
+ ...(error instanceof ApolloError ? { apolloError: error } : {}),
});
}
diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts
index 9a70346e4..a2f5d6e33 100644
--- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts
+++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts
@@ -11,17 +11,17 @@ import {
import { SignInUpMode } from '@/auth/types/signInUpMode';
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
import { useBuildSearchParamsFromUrlSyncedStates } from '@/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates';
+import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
import { AppPath } from '@/types/AppPath';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { ApolloError } from '@apollo/client';
import { useRecoilState } from 'recoil';
import { buildAppPathWithQueryParams } from '~/utils/buildAppPathWithQueryParams';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
import { useAuth } from '../../hooks/useAuth';
-import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
export const useSignInUp = (form: UseFormReturn