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.`
This commit is contained in:
Marie
2025-07-03 14:42:10 +02:00
committed by GitHub
parent 1f1318febf
commit 288f0919db
133 changed files with 1501 additions and 711 deletions

View File

@ -7,10 +7,10 @@ import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
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 { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ApolloError } from '@apollo/client';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { zodResolver } from '@hookform/resolvers/zod';
@ -78,7 +78,7 @@ const StyledMainButton = styled(MainButton)`
export const PasswordReset = () => {
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
const workspacePublicData = useRecoilValue(workspacePublicDataState);
@ -108,8 +108,8 @@ export const PasswordReset = () => {
},
skip: !passwordResetToken || isTokenValid,
onError: (error) => {
enqueueSnackBar(error?.message ?? 'Token Invalid', {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error,
});
navigate(AppPath.Index);
},
@ -137,15 +137,15 @@ export const PasswordReset = () => {
});
if (!data?.updatePasswordViaResetToken.success) {
enqueueSnackBar(t`There was an error while updating password.`, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
message: t`There was an error while updating password.`,
});
return;
}
if (isLoggedIn) {
enqueueSnackBar(t`Password has been updated`, {
variant: SnackBarVariant.Success,
enqueueSuccessSnackBar({
message: t`Password has been updated`,
});
navigate(AppPath.Index);
return;
@ -161,14 +161,9 @@ export const PasswordReset = () => {
navigate(AppPath.Index);
} catch (err) {
logError(err);
enqueueSnackBar(
err instanceof Error
? err.message
: t`An error occurred while updating password`,
{
variant: SnackBarVariant.Error,
},
);
enqueueErrorSnackBar({
apolloError: err instanceof ApolloError ? err : undefined,
});
}
};

View File

@ -15,12 +15,12 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { ApolloError } from '@apollo/client';
import { Trans, useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import { H2Title } from 'twenty-ui/display';
@ -59,7 +59,7 @@ type Form = z.infer<typeof validationSchema>;
export const CreateProfile = () => {
const { t } = useLingui();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
currentWorkspaceMemberState,
);
@ -131,15 +131,15 @@ export const CreateProfile = () => {
setNextOnboardingStatus();
} catch (error: any) {
enqueueSnackBar(error?.message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
});
}
},
[
currentWorkspaceMember?.id,
setNextOnboardingStatus,
enqueueSnackBar,
enqueueErrorSnackBar,
setCurrentWorkspaceMember,
setCurrentUser,
updateOneRecord,

View File

@ -13,10 +13,10 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItem';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ApolloError } from '@apollo/client';
import { Trans, useLingui } from '@lingui/react/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { motion } from 'framer-motion';
@ -65,7 +65,7 @@ const StyledPendingCreationLoader = styled(motion.div)`
export const CreateWorkspace = () => {
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
@ -127,14 +127,15 @@ export const CreateWorkspace = () => {
setNextOnboardingStatus();
} catch (error: any) {
setPendingCreationLoaderStep(PendingCreationLoaderStep.None);
enqueueSnackBar(error?.message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
});
}
},
[
activateWorkspace,
enqueueSnackBar,
enqueueErrorSnackBar,
loadCurrentUser,
refreshObjectMetadataItems,
setNextOnboardingStatus,

View File

@ -4,7 +4,6 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { Modal } from '@/ui/layout/modal/components/Modal';
@ -65,7 +64,7 @@ type FormInput = z.infer<typeof validationSchema>;
export const InviteTeam = () => {
const { t } = useLingui();
const theme = useTheme();
const { enqueueSnackBar } = useSnackBar();
const { enqueueSuccessSnackBar } = useSnackBar();
const { sendInvitation } = useCreateWorkspaceInvitation();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
@ -120,10 +119,12 @@ export const InviteTeam = () => {
if (isDefined(currentWorkspace?.inviteHash)) {
const inviteLink = `${window.location.origin}/invite/${currentWorkspace?.inviteHash}`;
navigator.clipboard.writeText(inviteLink);
enqueueSnackBar(t`Link copied to clipboard`, {
variant: SnackBarVariant.Success,
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
enqueueSuccessSnackBar({
message: t`Link copied to clipboard`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
duration: 2000,
},
});
}
};
@ -143,15 +144,17 @@ export const InviteTeam = () => {
throw result.errors;
}
if (emails.length > 0) {
enqueueSnackBar(t`Invite link sent to email addresses`, {
variant: SnackBarVariant.Success,
duration: 2000,
enqueueSuccessSnackBar({
message: t`Invite link sent to email addresses`,
options: {
duration: 2000,
},
});
}
setNextOnboardingStatus();
},
[enqueueSnackBar, sendInvitation, setNextOnboardingStatus, t],
[enqueueSuccessSnackBar, sendInvitation, setNextOnboardingStatus, t],
);
const handleSkip = async () => {

View File

@ -12,7 +12,6 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
@ -23,6 +22,7 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink';
import { WorkspaceInviteTeam } from '@/workspace/components/WorkspaceInviteTeam';
import { ApolloError } from '@apollo/client';
import { formatDistanceToNow } from 'date-fns';
import { isDefined } from 'twenty-shared/utils';
import {
@ -94,7 +94,7 @@ const StyledNoMembers = styled(TableCell)`
export const SettingsWorkspaceMembers = () => {
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const theme = useTheme();
const [workspaceMemberToDelete, setWorkspaceMemberToDelete] = useState<
string | undefined
@ -127,9 +127,9 @@ export const SettingsWorkspaceMembers = () => {
};
useGetWorkspaceInvitationsQuery({
onError: (error: Error) => {
enqueueSnackBar(error.message, {
variant: SnackBarVariant.Error,
onError: (error: ApolloError) => {
enqueueErrorSnackBar({
apolloError: error,
});
},
onCompleted: (data) => {
@ -140,9 +140,11 @@ export const SettingsWorkspaceMembers = () => {
const handleRemoveWorkspaceInvitation = async (appTokenId: string) => {
const result = await deleteWorkspaceInvitation({ appTokenId });
if (isDefined(result.errors)) {
enqueueSnackBar(t`Error deleting invitation`, {
variant: SnackBarVariant.Error,
duration: 2000,
enqueueErrorSnackBar({
message: t`Error deleting invitation`,
options: {
duration: 2000,
},
});
}
};
@ -150,9 +152,11 @@ export const SettingsWorkspaceMembers = () => {
const handleResendWorkspaceInvitation = async (appTokenId: string) => {
const result = await resendInvitation({ appTokenId });
if (isDefined(result.errors)) {
enqueueSnackBar(t`Error resending invitation`, {
variant: SnackBarVariant.Error,
duration: 2000,
enqueueErrorSnackBar({
message: t`Error resending invitation`,
options: {
duration: 2000,
},
});
}
};

View File

@ -11,20 +11,20 @@ import {
settingsDataModelObjectAboutFormSchema,
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { ApolloError } from '@apollo/client';
import { useLingui } from '@lingui/react/macro';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { useState } from 'react';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { useState } from 'react';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SettingsNewObject = () => {
const { t } = useLingui();
const navigate = useNavigateSettings();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const [isLoading, setIsLoading] = useState(false);
const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem();
@ -56,8 +56,8 @@ export const SettingsNewObject = () => {
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
});
} finally {
setIsLoading(false);

View File

@ -23,9 +23,9 @@ import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/vali
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { ApolloError } from '@apollo/client';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import { H2Title, IconArchive, IconArchiveOff } from 'twenty-ui/display';
@ -47,7 +47,7 @@ export const SettingsObjectFieldEdit = () => {
const navigateApp = useNavigateApp();
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const { objectNamePlural = '', fieldName = '' } = useParams();
const { findObjectMetadataItemByNamePlural } =
@ -147,8 +147,8 @@ export const SettingsObjectFieldEdit = () => {
});
}
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
});
}
};

View File

@ -13,7 +13,6 @@ import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/vali
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { View } from '@/views/types/View';
@ -51,7 +50,7 @@ export const SettingsObjectNewFieldConfigure = () => {
const fieldType =
(searchParams.get('fieldType') as SettingsFieldType) ||
FieldMetadataType.TEXT;
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const { findActiveObjectMetadataItemByNamePlural } =
useFilteredObjectMetadataItems();
@ -161,14 +160,11 @@ export const SettingsObjectNewFieldConfigure = () => {
'duplicate key value violates unique constraint "IndexOnNameObjectMetadataIdAndWorkspaceIdUnique"',
);
enqueueSnackBar(
isDuplicateFieldNameInObject
enqueueErrorSnackBar({
message: isDuplicateFieldNameInObject
? t`Please use different names for your source and destination fields`
: (error as Error).message,
{
variant: SnackBarVariant.Error,
},
);
: undefined,
});
}
};
if (!activeObjectMetadataItem) return null;

View File

@ -17,7 +17,6 @@ import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { computeNewExpirationDate } from '@/settings/developers/utils/computeNewExpirationDate';
import { formatExpiration } from '@/settings/developers/utils/formatExpiration';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
@ -50,7 +49,7 @@ const REGENERATE_API_KEY_MODAL_ID = 'regenerate-api-key-modal';
export const SettingsDevelopersApiKeyDetail = () => {
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const { openModal } = useModal();
const [isLoading, setIsLoading] = useState(false);
@ -97,9 +96,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
navigate(SettingsPath.APIs);
}
} catch (err) {
enqueueSnackBar(t`Error deleting api key: ${err}`, {
variant: SnackBarVariant.Error,
});
enqueueErrorSnackBar({ message: t`Error deleting api key.` });
} finally {
setIsLoading(false);
}
@ -149,8 +146,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
}
}
} catch (err) {
enqueueSnackBar(t`Error regenerating api key: ${err}`, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
message: t`Error regenerating api key.`,
});
} finally {
setIsLoading(false);

View File

@ -17,15 +17,15 @@ import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/u
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { ApolloError } from '@apollo/client';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { CreateRemoteServerInput } from '~/generated-metadata/graphql';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
const createRemoteServerInputPostgresSchema =
settingsIntegrationPostgreSQLConnectionFormSchema.transform<CreateRemoteServerInput>(
@ -79,7 +79,7 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
);
const { createOneDatabaseConnection } = useCreateOneDatabaseConnection();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const isIntegrationEnabled = useIsSettingsIntegrationEnabled(databaseKey);
@ -131,8 +131,8 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
connectionId,
});
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
});
}
};

View File

@ -1,10 +1,10 @@
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { ApolloError } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { Controller, useForm } from 'react-hook-form';
@ -20,7 +20,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
const { t } = useLingui();
const { enqueueSnackBar } = useSnackBar();
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
const [createApprovedAccessDomain] = useCreateApprovedAccessDomainMutation();
@ -62,20 +62,20 @@ export const SettingsSecurityApprovedAccessDomain = () => {
},
},
onCompleted: () => {
enqueueSnackBar(t`Please check your email for a verification link.`, {
variant: SnackBarVariant.Success,
enqueueSuccessSnackBar({
message: t`Please check your email for a verification link.`,
});
navigate(SettingsPath.Security);
},
onError: (error) => {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
});
},
});
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
});
}
};

View File

@ -7,21 +7,21 @@ import { SettingSecurityNewSSOIdentityFormValues } from '@/settings/security/typ
import { sSOIdentityProviderDefaultValues } from '@/settings/security/utils/sSOIdentityProviderDefaultValues';
import { SSOIdentitiesProvidersParamsSchema } from '@/settings/security/validation-schemas/SSOIdentityProviderSchema';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { ApolloError } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import pick from 'lodash.pick';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { t } from '@lingui/core/macro';
export const SettingsSecuritySSOIdentifyProvider = () => {
const navigate = useNavigateSettings();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar } = useSnackBar();
const { createSSOIdentityProvider } = useCreateSSOIdentityProvider();
const form = useForm<SettingSecurityNewSSOIdentityFormValues>({
@ -48,8 +48,8 @@ export const SettingsSecuritySSOIdentifyProvider = () => {
navigate(SettingsPath.Security);
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error instanceof ApolloError ? error : undefined,
});
}
};

View File

@ -8,17 +8,18 @@ import { usePublishOneServerlessFunction } from '@/settings/serverless-functions
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { ApolloError } from '@apollo/client';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import { IconCode, IconSettings, IconTestPipe } from 'twenty-ui/display';
import { useDebouncedCallback } from 'use-debounce';
import { getErrorMessageFromApolloError } from '~/utils/get-error-message-from-apollo-error.util';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
@ -26,7 +27,7 @@ const SERVERLESS_FUNCTION_DETAIL_ID = 'serverless-function-detail';
export const SettingsServerlessFunctionDetail = () => {
const { serverlessFunctionId = '' } = useParams();
const { enqueueSnackBar } = useSnackBar();
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
activeTabIdComponentState,
SERVERLESS_FUNCTION_DETAIL_ID,
@ -88,12 +89,9 @@ export const SettingsServerlessFunctionDetail = () => {
}));
await handleSave();
} catch (err) {
enqueueSnackBar(
(err as Error)?.message || 'An error occurred while reset function',
{
variant: SnackBarVariant.Error,
},
);
enqueueErrorSnackBar({
apolloError: err instanceof ApolloError ? err : undefined,
});
}
};
@ -102,17 +100,16 @@ export const SettingsServerlessFunctionDetail = () => {
await publishOneServerlessFunction({
id: serverlessFunctionId,
});
enqueueSnackBar(`New function version has been published`, {
variant: SnackBarVariant.Success,
enqueueSuccessSnackBar({
message: `New function version has been published`,
});
} catch (err) {
enqueueSnackBar(
(err as Error)?.message ||
'An error occurred while publishing new version',
{
variant: SnackBarVariant.Error,
},
);
enqueueErrorSnackBar({
message:
err instanceof ApolloError
? getErrorMessageFromApolloError(err)
: 'An error occurred while publishing new version',
});
}
};

View File

@ -1,17 +1,16 @@
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { IconCopy } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { useDebouncedCallback } from 'use-debounce';
import { CustomDomainValidRecords } from '~/generated/graphql';
import { useTheme } from '@emotion/react';
import { Button } from 'twenty-ui/input';
import { IconCopy } from 'twenty-ui/display';
const StyledTable = styled(Table)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
@ -46,7 +45,7 @@ export const SettingsCustomDomainRecords = ({
}: {
records: CustomDomainValidRecords['records'];
}) => {
const { enqueueSnackBar } = useSnackBar();
const { enqueueSuccessSnackBar } = useSnackBar();
const theme = useTheme();
@ -54,9 +53,11 @@ export const SettingsCustomDomainRecords = ({
const copyToClipboard = (value: string) => {
navigator.clipboard.writeText(value);
enqueueSnackBar(t`Copied to clipboard!`, {
variant: SnackBarVariant.Success,
icon: <IconCopy size={theme.icon.size.md} />,
enqueueSuccessSnackBar({
message: t`Copied to clipboard!`,
options: {
icon: <IconCopy size={theme.icon.size.md} />,
},
});
};

View File

@ -6,7 +6,6 @@ import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirect
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
@ -60,7 +59,7 @@ export const SettingsDomain = () => {
})
.required();
const { enqueueSnackBar } = useSnackBar();
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
const [updateWorkspace] = useUpdateWorkspaceMutation();
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
@ -105,11 +104,11 @@ export const SettingsDomain = () => {
customDomain:
customDomain && customDomain.length > 0 ? customDomain : null,
});
enqueueSnackBar(t`Custom domain updated`, {
variant: SnackBarVariant.Success,
enqueueSuccessSnackBar({
message: t`Custom domain updated`,
});
},
onError: (error) => {
onError: (error: ApolloError) => {
if (
error instanceof ApolloError &&
error.graphQLErrors[0]?.extensions?.code === 'CONFLICT'
@ -119,8 +118,8 @@ export const SettingsDomain = () => {
message: t`Subdomain already taken`,
});
}
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error,
});
},
});
@ -136,7 +135,7 @@ export const SettingsDomain = () => {
subdomain,
},
},
onError: (error) => {
onError: (error: ApolloError) => {
if (
error instanceof ApolloError &&
error.graphQLErrors[0]?.extensions?.code === 'CONFLICT'
@ -147,8 +146,8 @@ export const SettingsDomain = () => {
message: t`Subdomain already taken`,
});
}
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
enqueueErrorSnackBar({
apolloError: error,
});
},
onCompleted: async () => {
@ -163,8 +162,8 @@ export const SettingsDomain = () => {
subdomain,
});
enqueueSnackBar(t`Subdomain updated`, {
variant: SnackBarVariant.Success,
enqueueSuccessSnackBar({
message: t`Subdomain updated`,
});
await redirectToWorkspaceDomain(currentUrl.toString());
@ -179,14 +178,14 @@ export const SettingsDomain = () => {
subdomainValue === currentWorkspace?.subdomain &&
customDomainValue === currentWorkspace?.customDomain
) {
return enqueueSnackBar(t`No change detected`, {
variant: SnackBarVariant.Error,
return enqueueErrorSnackBar({
message: t`No change detected`,
});
}
if (!values || !currentWorkspace) {
return enqueueSnackBar(t`Invalid form values`, {
variant: SnackBarVariant.Error,
return enqueueErrorSnackBar({
message: t`Invalid form values`,
});
}