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:
@ -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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -6,23 +5,27 @@ import { IconCopy, IconExclamationCircle } from 'twenty-ui/display';
|
|||||||
|
|
||||||
export const useCopyToClipboard = () => {
|
export const useCopyToClipboard = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const copyToClipboard = async (valueAsString: string) => {
|
const copyToClipboard = async (valueAsString: string) => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(valueAsString);
|
await navigator.clipboard.writeText(valueAsString);
|
||||||
|
|
||||||
enqueueSnackBar(t`Copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
enqueueSnackBar(t`Couldn't copy to clipboard`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Couldn't copy to clipboard`,
|
||||||
icon: <IconExclamationCircle size={16} color="red" />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconExclamationCircle size={16} color="red" />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import {
|
import {
|
||||||
DocumentNode,
|
DocumentNode,
|
||||||
OperationVariables,
|
OperationVariables,
|
||||||
TypedDocumentNode,
|
TypedDocumentNode,
|
||||||
useQuery,
|
useQuery,
|
||||||
} from '@apollo/client';
|
} from '@apollo/client';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
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';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
|
||||||
type CustomResolverQueryResult<
|
type CustomResolverQueryResult<
|
||||||
@ -37,7 +36,7 @@ export const useCustomResolver = <
|
|||||||
isFetchingMore: boolean;
|
isFetchingMore: boolean;
|
||||||
fetchMoreRecords: () => Promise<void>;
|
fetchMoreRecords: () => Promise<void>;
|
||||||
} => {
|
} => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [page, setPage] = useState({
|
const [page, setPage] = useState({
|
||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
@ -62,8 +61,8 @@ export const useCustomResolver = <
|
|||||||
} = useQuery<CustomResolverQueryResult<T>>(query, {
|
} = useQuery<CustomResolverQueryResult<T>>(query, {
|
||||||
variables: queryVariables,
|
variables: queryVariables,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
enqueueSnackBar(error.message || `Error loading ${objectName}`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { useAuth } from '@/auth/hooks/useAuth';
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
import { AppPath } from '@/types/AppPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { ApolloError } from '@apollo/client';
|
import { ApolloError } from '@apollo/client';
|
||||||
|
|
||||||
@ -20,7 +19,7 @@ import { EmailVerificationSent } from '../sign-in-up/components/EmailVerificatio
|
|||||||
export const VerifyEmailEffect = () => {
|
export const VerifyEmailEffect = () => {
|
||||||
const { getLoginTokenFromEmailVerificationToken } = useAuth();
|
const { getLoginTokenFromEmailVerificationToken } = useAuth();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [isError, setIsError] = useState(false);
|
const [isError, setIsError] = useState(false);
|
||||||
@ -39,9 +38,11 @@ export const VerifyEmailEffect = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const verifyEmailToken = async () => {
|
const verifyEmailToken = async () => {
|
||||||
if (!email || !emailVerificationToken) {
|
if (!email || !emailVerificationToken) {
|
||||||
enqueueSnackBar(t`Invalid email verification link.`, {
|
enqueueErrorSnackBar({
|
||||||
dedupeKey: 'email-verification-link-dedupe-key',
|
message: t`Invalid email verification link.`,
|
||||||
variant: SnackBarVariant.Error,
|
options: {
|
||||||
|
dedupeKey: 'email-verification-link-dedupe-key',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return navigate(AppPath.SignInUp);
|
return navigate(AppPath.SignInUp);
|
||||||
}
|
}
|
||||||
@ -53,9 +54,11 @@ export const VerifyEmailEffect = () => {
|
|||||||
email,
|
email,
|
||||||
);
|
);
|
||||||
|
|
||||||
enqueueSnackBar(t`Email verified.`, {
|
enqueueSuccessSnackBar({
|
||||||
dedupeKey: 'email-verification-dedupe-key',
|
message: t`Email verified.`,
|
||||||
variant: SnackBarVariant.Success,
|
options: {
|
||||||
|
dedupeKey: 'email-verification-dedupe-key',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspaceUrl = getWorkspaceUrl(workspaceUrls);
|
const workspaceUrl = getWorkspaceUrl(workspaceUrls);
|
||||||
@ -71,14 +74,13 @@ export const VerifyEmailEffect = () => {
|
|||||||
|
|
||||||
verifyLoginToken(loginToken.token);
|
verifyLoginToken(loginToken.token);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message: string =
|
enqueueErrorSnackBar({
|
||||||
error instanceof ApolloError
|
...(error instanceof ApolloError
|
||||||
? error.message
|
? { apolloError: error }
|
||||||
: 'Email verification failed';
|
: { message: t`Email verification failed` }),
|
||||||
|
options: {
|
||||||
enqueueSnackBar(t`${message}`, {
|
dedupeKey: 'email-verification-error-dedupe-key',
|
||||||
dedupeKey: 'email-verification-error-dedupe-key',
|
},
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
error instanceof ApolloError &&
|
error instanceof ApolloError &&
|
||||||
|
|||||||
@ -4,14 +4,13 @@ import { renderHook } from '@testing-library/react';
|
|||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
|
|
||||||
import { AppPath } from '@/types/AppPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||||
import { useAuth } from '../useAuth';
|
import { useAuth } from '../useAuth';
|
||||||
import { useVerifyLogin } from '../useVerifyLogin';
|
import { useVerifyLogin } from '../useVerifyLogin';
|
||||||
|
|
||||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
|
||||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||||
|
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||||
|
|
||||||
jest.mock('../useAuth', () => ({
|
jest.mock('../useAuth', () => ({
|
||||||
useAuth: jest.fn(),
|
useAuth: jest.fn(),
|
||||||
@ -37,7 +36,7 @@ const renderHooks = () => {
|
|||||||
|
|
||||||
describe('useVerifyLogin', () => {
|
describe('useVerifyLogin', () => {
|
||||||
const mockGetAuthTokensFromLoginToken = jest.fn();
|
const mockGetAuthTokensFromLoginToken = jest.fn();
|
||||||
const mockEnqueueSnackBar = jest.fn();
|
const mockEnqueueErrorSnackBar = jest.fn();
|
||||||
const mockNavigate = jest.fn();
|
const mockNavigate = jest.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -48,7 +47,7 @@ describe('useVerifyLogin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
(useSnackBar as jest.Mock).mockReturnValue({
|
(useSnackBar as jest.Mock).mockReturnValue({
|
||||||
enqueueSnackBar: mockEnqueueSnackBar,
|
enqueueErrorSnackBar: mockEnqueueErrorSnackBar,
|
||||||
});
|
});
|
||||||
|
|
||||||
(useNavigateApp as jest.Mock).mockReturnValue(mockNavigate);
|
(useNavigateApp as jest.Mock).mockReturnValue(mockNavigate);
|
||||||
@ -70,8 +69,8 @@ describe('useVerifyLogin', () => {
|
|||||||
|
|
||||||
await result.current.verifyLoginToken('test-token');
|
await result.current.verifyLoginToken('test-token');
|
||||||
|
|
||||||
expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Authentication failed', {
|
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||||
variant: SnackBarVariant.Error,
|
message: 'Authentication failed',
|
||||||
});
|
});
|
||||||
expect(mockNavigate).toHaveBeenCalledWith(AppPath.SignInUp);
|
expect(mockNavigate).toHaveBeenCalledWith(AppPath.SignInUp);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
|
|
||||||
import { useAuth } from '@/auth/hooks/useAuth';
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
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';
|
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||||
|
|
||||||
export const useVerifyLogin = () => {
|
export const useVerifyLogin = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const navigate = useNavigateApp();
|
const navigate = useNavigateApp();
|
||||||
const { getAuthTokensFromLoginToken } = useAuth();
|
const { getAuthTokensFromLoginToken } = useAuth();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
@ -16,8 +14,8 @@ export const useVerifyLogin = () => {
|
|||||||
try {
|
try {
|
||||||
await getAuthTokensFromLoginToken(loginToken);
|
await getAuthTokensFromLoginToken(loginToken);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar(t`Authentication failed`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Authentication failed`,
|
||||||
});
|
});
|
||||||
navigate(AppPath.SignInUp);
|
navigate(AppPath.SignInUp);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { RecoilRoot } from 'recoil';
|
|||||||
|
|
||||||
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||||
import {
|
import {
|
||||||
@ -36,14 +35,16 @@ const renderHooks = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('useHandleResetPassword', () => {
|
describe('useHandleResetPassword', () => {
|
||||||
const enqueueSnackBarMock = jest.fn();
|
const enqueueErrorSnackBarMock = jest.fn();
|
||||||
|
const enqueueSuccessSnackBarMock = jest.fn();
|
||||||
const emailPasswordResetLinkMock = jest.fn();
|
const emailPasswordResetLinkMock = jest.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
(useSnackBar as jest.Mock).mockReturnValue({
|
(useSnackBar as jest.Mock).mockReturnValue({
|
||||||
enqueueSnackBar: enqueueSnackBarMock,
|
enqueueErrorSnackBar: enqueueErrorSnackBarMock,
|
||||||
|
enqueueSuccessSnackBar: enqueueSuccessSnackBarMock,
|
||||||
});
|
});
|
||||||
(useEmailPasswordResetLinkMutation as jest.Mock).mockReturnValue([
|
(useEmailPasswordResetLinkMutation as jest.Mock).mockReturnValue([
|
||||||
emailPasswordResetLinkMock,
|
emailPasswordResetLinkMock,
|
||||||
@ -54,8 +55,8 @@ describe('useHandleResetPassword', () => {
|
|||||||
const { result } = renderHooks();
|
const { result } = renderHooks();
|
||||||
await act(() => result.current.handleResetPassword('')());
|
await act(() => result.current.handleResetPassword('')());
|
||||||
|
|
||||||
expect(enqueueSnackBarMock).toHaveBeenCalledWith('Invalid email', {
|
expect(enqueueErrorSnackBarMock).toHaveBeenCalledWith({
|
||||||
variant: SnackBarVariant.Error,
|
message: 'Invalid email',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -67,10 +68,9 @@ describe('useHandleResetPassword', () => {
|
|||||||
const { result } = renderHooks();
|
const { result } = renderHooks();
|
||||||
await act(() => result.current.handleResetPassword('test@example.com')());
|
await act(() => result.current.handleResetPassword('test@example.com')());
|
||||||
|
|
||||||
expect(enqueueSnackBarMock).toHaveBeenCalledWith(
|
expect(enqueueSuccessSnackBarMock).toHaveBeenCalledWith({
|
||||||
'Password reset link has been sent to the email',
|
message: 'Password reset link has been sent to the email',
|
||||||
{ variant: SnackBarVariant.Success },
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show error message if sending reset link fails', async () => {
|
it('should show error message if sending reset link fails', async () => {
|
||||||
@ -81,9 +81,7 @@ describe('useHandleResetPassword', () => {
|
|||||||
const { result } = renderHooks();
|
const { result } = renderHooks();
|
||||||
await act(() => result.current.handleResetPassword('test@example.com')());
|
await act(() => result.current.handleResetPassword('test@example.com')());
|
||||||
|
|
||||||
expect(enqueueSnackBarMock).toHaveBeenCalledWith('There was an issue', {
|
expect(enqueueErrorSnackBarMock).toHaveBeenCalledWith({});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show error message in case of request error', async () => {
|
it('should show error message in case of request error', async () => {
|
||||||
@ -93,8 +91,6 @@ describe('useHandleResetPassword', () => {
|
|||||||
const { result } = renderHooks();
|
const { result } = renderHooks();
|
||||||
await act(() => result.current.handleResetPassword('test@example.com')());
|
await act(() => result.current.handleResetPassword('test@example.com')());
|
||||||
|
|
||||||
expect(enqueueSnackBarMock).toHaveBeenCalledWith(errorMessage, {
|
expect(enqueueErrorSnackBarMock).toHaveBeenCalledWith({});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
||||||
jest.mock('@/domain-manager/hooks/useRedirect');
|
jest.mock('@/domain-manager/hooks/useRedirect');
|
||||||
jest.mock('~/generated/graphql');
|
jest.mock('~/generated/graphql');
|
||||||
|
|
||||||
const mockEnqueueSnackBar = jest.fn();
|
const mockEnqueueErrorSnackBar = jest.fn();
|
||||||
const mockRedirect = jest.fn();
|
const mockRedirect = jest.fn();
|
||||||
|
|
||||||
(useSnackBar as jest.Mock).mockReturnValue({
|
(useSnackBar as jest.Mock).mockReturnValue({
|
||||||
enqueueSnackBar: mockEnqueueSnackBar,
|
enqueueErrorSnackBar: mockEnqueueErrorSnackBar,
|
||||||
});
|
});
|
||||||
(useRedirect as jest.Mock).mockReturnValue({
|
(useRedirect as jest.Mock).mockReturnValue({
|
||||||
redirect: mockRedirect,
|
redirect: mockRedirect,
|
||||||
@ -84,8 +85,10 @@ describe('useSSO', () => {
|
|||||||
|
|
||||||
await result.current.redirectToSSOLoginPage(identityProviderId);
|
await result.current.redirectToSSOLoginPage(identityProviderId);
|
||||||
|
|
||||||
expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Error message', {
|
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||||
variant: 'error',
|
apolloError: new ApolloError({
|
||||||
|
graphQLErrors: [{ message: 'Error message' }],
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useOrigin } from '@/domain-manager/hooks/useOrigin';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useResendEmailVerificationTokenMutation } from '~/generated-metadata/graphql';
|
import { useResendEmailVerificationTokenMutation } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export const useHandleResendEmailVerificationToken = () => {
|
export const useHandleResendEmailVerificationToken = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const [resendEmailVerificationToken, { loading }] =
|
const [resendEmailVerificationToken, { loading }] =
|
||||||
useResendEmailVerificationTokenMutation();
|
useResendEmailVerificationTokenMutation();
|
||||||
const { origin } = useOrigin();
|
const { origin } = useOrigin();
|
||||||
@ -16,8 +16,8 @@ export const useHandleResendEmailVerificationToken = () => {
|
|||||||
(email: string | null) => {
|
(email: string | null) => {
|
||||||
return async () => {
|
return async () => {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
enqueueSnackBar(t`Invalid email`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Invalid email`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -31,22 +31,25 @@ export const useHandleResendEmailVerificationToken = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data?.resendEmailVerificationToken?.success === true) {
|
if (data?.resendEmailVerificationToken?.success === true) {
|
||||||
enqueueSnackBar(t`Email verification link resent!`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Email verification link resent!`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
enqueueSnackBar(t`There was an issue`, {
|
enqueueErrorSnackBar({});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
...(error instanceof ApolloError ? { apolloError: error } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[enqueueSnackBar, resendEmailVerificationToken, origin],
|
[
|
||||||
|
enqueueErrorSnackBar,
|
||||||
|
enqueueSuccessSnackBar,
|
||||||
|
resendEmailVerificationToken,
|
||||||
|
origin,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { handleResendEmailVerificationToken, loading };
|
return { handleResendEmailVerificationToken, loading };
|
||||||
|
|||||||
@ -2,14 +2,14 @@ import { useCallback } from 'react';
|
|||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useEmailPasswordResetLinkMutation } from '~/generated-metadata/graphql';
|
import { useEmailPasswordResetLinkMutation } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export const useHandleResetPassword = () => {
|
export const useHandleResetPassword = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation();
|
const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation();
|
||||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
@ -20,15 +20,15 @@ export const useHandleResetPassword = () => {
|
|||||||
(email = currentUser?.email) => {
|
(email = currentUser?.email) => {
|
||||||
return async () => {
|
return async () => {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
enqueueSnackBar(t`Invalid email`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Invalid email`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!workspacePublicData?.id) {
|
if (!workspacePublicData?.id) {
|
||||||
enqueueSnackBar(t`Invalid workspace`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Invalid workspace`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -39,17 +39,15 @@ export const useHandleResetPassword = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data?.emailPasswordResetLink?.success === true) {
|
if (data?.emailPasswordResetLink?.success === true) {
|
||||||
enqueueSnackBar(t`Password reset link has been sent to the email`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Password reset link has been sent to the email`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
enqueueSnackBar(t`There was an issue`, {
|
enqueueErrorSnackBar({});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
...(error instanceof ApolloError ? { apolloError: error } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -57,7 +55,8 @@ export const useHandleResetPassword = () => {
|
|||||||
[
|
[
|
||||||
currentUser?.email,
|
currentUser?.email,
|
||||||
workspacePublicData?.id,
|
workspacePublicData?.id,
|
||||||
enqueueSnackBar,
|
enqueueErrorSnackBar,
|
||||||
|
enqueueSuccessSnackBar,
|
||||||
t,
|
t,
|
||||||
emailPasswordResetLink,
|
emailPasswordResetLink,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -2,16 +2,15 @@
|
|||||||
|
|
||||||
import { GET_AUTHORIZATION_URL_FOR_SSO } from '@/auth/graphql/mutations/getAuthorizationUrlForSSO';
|
import { GET_AUTHORIZATION_URL_FOR_SSO } from '@/auth/graphql/mutations/getAuthorizationUrlForSSO';
|
||||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
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 { 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';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
export const useSSO = () => {
|
export const useSSO = () => {
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
const workspaceInviteHash = useParams().workspaceInviteHash;
|
const workspaceInviteHash = useParams().workspaceInviteHash;
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { redirect } = useRedirect();
|
const { redirect } = useRedirect();
|
||||||
const redirectToSSOLoginPage = async (identityProviderId: string) => {
|
const redirectToSSOLoginPage = async (identityProviderId: string) => {
|
||||||
let authorizationUrlForSSOResult;
|
let authorizationUrlForSSOResult;
|
||||||
@ -26,8 +25,8 @@ export const useSSO = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return enqueueSnackBar(error?.message ?? 'Unknown error', {
|
return enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
...(error instanceof ApolloError ? { apolloError: error } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,17 +11,17 @@ import {
|
|||||||
import { SignInUpMode } from '@/auth/types/signInUpMode';
|
import { SignInUpMode } from '@/auth/types/signInUpMode';
|
||||||
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
||||||
import { useBuildSearchParamsFromUrlSyncedStates } from '@/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates';
|
import { useBuildSearchParamsFromUrlSyncedStates } from '@/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates';
|
||||||
|
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||||
import { AppPath } from '@/types/AppPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { buildAppPathWithQueryParams } from '~/utils/buildAppPathWithQueryParams';
|
import { buildAppPathWithQueryParams } from '~/utils/buildAppPathWithQueryParams';
|
||||||
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
||||||
import { useAuth } from '../../hooks/useAuth';
|
import { useAuth } from '../../hooks/useAuth';
|
||||||
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
|
||||||
|
|
||||||
export const useSignInUp = (form: UseFormReturn<Form>) => {
|
export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [signInUpStep, setSignInUpStep] = useRecoilState(signInUpStepState);
|
const [signInUpStep, setSignInUpStep] = useRecoilState(signInUpStepState);
|
||||||
const [signInUpMode, setSignInUpMode] = useRecoilState(signInUpModeState);
|
const [signInUpMode, setSignInUpMode] = useRecoilState(signInUpModeState);
|
||||||
@ -66,9 +66,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
|||||||
captchaToken: token,
|
captchaToken: token,
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
enqueueSnackBar(`${error.message}`, {
|
enqueueErrorSnackBar({ apolloError: error });
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
setSignInUpMode(
|
setSignInUpMode(
|
||||||
@ -83,7 +81,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
|||||||
readCaptchaToken,
|
readCaptchaToken,
|
||||||
form,
|
form,
|
||||||
checkUserExistsQuery,
|
checkUserExistsQuery,
|
||||||
enqueueSnackBar,
|
enqueueErrorSnackBar,
|
||||||
setSignInUpStep,
|
setSignInUpStep,
|
||||||
setSignInUpMode,
|
setSignInUpMode,
|
||||||
]);
|
]);
|
||||||
@ -145,9 +143,9 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
|||||||
captchaToken: token,
|
captchaToken: token,
|
||||||
verifyEmailNextPath,
|
verifyEmailNextPath,
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (error: any) {
|
||||||
enqueueSnackBar(err?.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
...(error instanceof ApolloError ? { apolloError: error } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -161,7 +159,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
|||||||
signUpWithCredentialsInWorkspace,
|
signUpWithCredentialsInWorkspace,
|
||||||
workspaceInviteHash,
|
workspaceInviteHash,
|
||||||
workspacePersonalInviteToken,
|
workspacePersonalInviteToken,
|
||||||
enqueueSnackBar,
|
enqueueErrorSnackBar,
|
||||||
buildSearchParamsFromUrlSyncedStates,
|
buildSearchParamsFromUrlSyncedStates,
|
||||||
isOnAWorkspace,
|
isOnAWorkspace,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
import { AppPath } from '@/types/AppPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useSignUpInNewWorkspaceMutation } from '~/generated-metadata/graphql';
|
import { useSignUpInNewWorkspaceMutation } from '~/generated-metadata/graphql';
|
||||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||||
|
|
||||||
export const useSignUpInNewWorkspace = () => {
|
export const useSignUpInNewWorkspace = () => {
|
||||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [signUpInNewWorkspaceMutation] = useSignUpInNewWorkspaceMutation();
|
const [signUpInNewWorkspaceMutation] = useSignUpInNewWorkspaceMutation();
|
||||||
|
|
||||||
@ -23,10 +23,8 @@ export const useSignUpInNewWorkspace = () => {
|
|||||||
newTab ? '_blank' : '_self',
|
newTab ? '_blank' : '_self',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: ApolloError) => {
|
||||||
enqueueSnackBar(error.message, {
|
enqueueErrorSnackBar({ apolloError: error });
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,16 +4,16 @@ import { useRecoilValue } from 'recoil';
|
|||||||
|
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { useGetWorkspaceFromInviteHashQuery } from '~/generated-metadata/graphql';
|
import { useGetWorkspaceFromInviteHashQuery } from '~/generated-metadata/graphql';
|
||||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||||
|
|
||||||
export const useWorkspaceFromInviteHash = () => {
|
export const useWorkspaceFromInviteHash = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar, enqueueInfoSnackBar } = useSnackBar();
|
||||||
const navigate = useNavigateApp();
|
const navigate = useNavigateApp();
|
||||||
const workspaceInviteHash = useParams().workspaceInviteHash;
|
const workspaceInviteHash = useParams().workspaceInviteHash;
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
@ -24,9 +24,7 @@ export const useWorkspaceFromInviteHash = () => {
|
|||||||
skip: !workspaceInviteHash,
|
skip: !workspaceInviteHash,
|
||||||
variables: { inviteHash: workspaceInviteHash || '' },
|
variables: { inviteHash: workspaceInviteHash || '' },
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
enqueueSnackBar(error.message, {
|
enqueueErrorSnackBar({ apolloError: error });
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
navigate(AppPath.Index);
|
navigate(AppPath.Index);
|
||||||
},
|
},
|
||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
@ -35,13 +33,14 @@ export const useWorkspaceFromInviteHash = () => {
|
|||||||
data?.findWorkspaceFromInviteHash &&
|
data?.findWorkspaceFromInviteHash &&
|
||||||
currentWorkspace.id === data.findWorkspaceFromInviteHash.id
|
currentWorkspace.id === data.findWorkspaceFromInviteHash.id
|
||||||
) {
|
) {
|
||||||
|
const workspaceDisplayName =
|
||||||
|
data?.findWorkspaceFromInviteHash?.displayName;
|
||||||
initiallyLoggedIn &&
|
initiallyLoggedIn &&
|
||||||
enqueueSnackBar(
|
enqueueInfoSnackBar({
|
||||||
`You already belong to ${data?.findWorkspaceFromInviteHash?.displayName} workspace`,
|
message: workspaceDisplayName
|
||||||
{
|
? t`You already belong to the workspace ${workspaceDisplayName}`
|
||||||
variant: SnackBarVariant.Info,
|
: t`You already belong to this workspace`,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
navigate(AppPath.Index);
|
navigate(AppPath.Index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { SubscriptionInfoRowContainer } from '@/billing/components/SubscriptionI
|
|||||||
|
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { formatMonthlyPrices } from '@/billing/utils/formatMonthlyPrices';
|
import { formatMonthlyPrices } from '@/billing/utils/formatMonthlyPrices';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||||
@ -49,7 +48,7 @@ export const SettingsBillingSubscriptionInfo = () => {
|
|||||||
|
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const subscriptionStatus = useSubscriptionStatus();
|
const subscriptionStatus = useSubscriptionStatus();
|
||||||
|
|
||||||
@ -134,12 +133,12 @@ export const SettingsBillingSubscriptionInfo = () => {
|
|||||||
};
|
};
|
||||||
setCurrentWorkspace(newCurrentWorkspace);
|
setCurrentWorkspace(newCurrentWorkspace);
|
||||||
}
|
}
|
||||||
enqueueSnackBar(t`Subscription has been switched to Yearly.`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Subscription has been switched to Yearly.`,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
enqueueSnackBar(t`Error while switching subscription to Yearly.`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Error while switching subscription to Yearly.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -160,16 +159,13 @@ export const SettingsBillingSubscriptionInfo = () => {
|
|||||||
};
|
};
|
||||||
setCurrentWorkspace(newCurrentWorkspace);
|
setCurrentWorkspace(newCurrentWorkspace);
|
||||||
}
|
}
|
||||||
enqueueSnackBar(t`Subscription has been switched to Organization Plan.`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Subscription has been switched to Organization Plan.`,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
t`Error while switching subscription to Organization Plan.`,
|
message: t`Error while switching subscription to Organization Plan.`,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@ -8,7 +7,7 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { useEndSubscriptionTrialPeriodMutation } from '~/generated-metadata/graphql';
|
import { useEndSubscriptionTrialPeriodMutation } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export const useEndSubscriptionTrialPeriod = () => {
|
export const useEndSubscriptionTrialPeriod = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
const [endSubscriptionTrialPeriod] = useEndSubscriptionTrialPeriodMutation();
|
const [endSubscriptionTrialPeriod] = useEndSubscriptionTrialPeriodMutation();
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||||
currentWorkspaceState,
|
currentWorkspaceState,
|
||||||
@ -25,12 +24,9 @@ export const useEndSubscriptionTrialPeriod = () => {
|
|||||||
const hasPaymentMethod = endTrialPeriodOutput?.hasPaymentMethod;
|
const hasPaymentMethod = endTrialPeriodOutput?.hasPaymentMethod;
|
||||||
|
|
||||||
if (isDefined(hasPaymentMethod) && hasPaymentMethod === false) {
|
if (isDefined(hasPaymentMethod) && hasPaymentMethod === false) {
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
t`No payment method found. Please update your billing details.`,
|
message: t`No payment method found. Please update your billing details.`,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -49,16 +45,13 @@ export const useEndSubscriptionTrialPeriod = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueueSnackBar(t`Subscription activated.`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Subscription activated.`,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
t`Error while ending trial period. Please contact Twenty team.`,
|
message: t`Error while ending trial period. Please contact Twenty team.`,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
BillingPlanKey,
|
BillingPlanKey,
|
||||||
@ -21,7 +21,7 @@ export const useHandleCheckoutSession = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { redirect } = useRedirect();
|
const { redirect } = useRedirect();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [checkoutSession] = useCheckoutSessionMutation();
|
const [checkoutSession] = useCheckoutSessionMutation();
|
||||||
|
|
||||||
@ -39,12 +39,9 @@ export const useHandleCheckoutSession = ({
|
|||||||
});
|
});
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
if (!data?.checkoutSession.url) {
|
if (!data?.checkoutSession.url) {
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
'Checkout session error. Please retry or contact Twenty team',
|
message: t`Checkout session error. Please retry or contact Twenty team`,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
redirect(data.checkoutSession.url);
|
redirect(data.checkoutSession.url);
|
||||||
|
|||||||
@ -1,26 +1,27 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export const ErrorMessageEffect = () => {
|
export const ErrorMessageEffect = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const errorMessage = searchParams.get('errorMessage');
|
const errorMessage = searchParams.get('errorMessage');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDefined(errorMessage)) {
|
if (isDefined(errorMessage)) {
|
||||||
enqueueSnackBar(errorMessage, {
|
enqueueErrorSnackBar({
|
||||||
dedupeKey: 'error-message-dedupe-key',
|
message: errorMessage,
|
||||||
variant: SnackBarVariant.Error,
|
options: {
|
||||||
|
dedupeKey: 'error-message-dedupe-key',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const newSearchParams = new URLSearchParams(searchParams);
|
const newSearchParams = new URLSearchParams(searchParams);
|
||||||
newSearchParams.delete('errorMessage');
|
newSearchParams.delete('errorMessage');
|
||||||
setSearchParams(newSearchParams);
|
setSearchParams(newSearchParams);
|
||||||
}
|
}
|
||||||
}, [enqueueSnackBar, errorMessage, searchParams, setSearchParams]);
|
}, [enqueueErrorSnackBar, errorMessage, searchParams, setSearchParams]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import { CustomError } from '@/error-handler/CustomError';
|
import { CustomError } from '@/error-handler/CustomError';
|
||||||
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -14,29 +12,20 @@ const hasErrorCode = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PromiseRejectionEffect = () => {
|
export const PromiseRejectionEffect = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const handlePromiseRejection = useCallback(
|
const handlePromiseRejection = useCallback(
|
||||||
async (event: PromiseRejectionEvent) => {
|
async (event: PromiseRejectionEvent) => {
|
||||||
const error = event.reason;
|
const error = event.reason;
|
||||||
|
|
||||||
if (error instanceof ObjectMetadataItemNotFoundError) {
|
|
||||||
enqueueSnackBar(
|
|
||||||
`Error with custom object that cannot be found : ${event.reason}`,
|
|
||||||
{
|
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
enqueueSnackBar(`${error.message}`, {
|
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.name === 'ApolloError' && !isEmpty(error.graphQLErrors)) {
|
if (error.name === 'ApolloError' && !isEmpty(error.graphQLErrors)) {
|
||||||
|
enqueueErrorSnackBar({
|
||||||
|
apolloError: error,
|
||||||
|
});
|
||||||
return; // already handled by apolloLink
|
return; // already handled by apolloLink
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enqueueErrorSnackBar({});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { captureException } = await import('@sentry/react');
|
const { captureException } = await import('@sentry/react');
|
||||||
captureException(error, (scope) => {
|
captureException(error, (scope) => {
|
||||||
@ -52,7 +41,7 @@ export const PromiseRejectionEffect = () => {
|
|||||||
console.error('Failed to capture exception with Sentry:', sentryError);
|
console.error('Failed to capture exception with Sentry:', sentryError);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[enqueueSnackBar],
|
[enqueueErrorSnackBar],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useQuery } from '@apollo/client';
|
import { useQuery } from '@apollo/client';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import {
|
import {
|
||||||
ObjectMetadataItemsQuery,
|
ObjectMetadataItemsQuery,
|
||||||
@ -17,7 +16,7 @@ export const useFindManyObjectMetadataItems = ({
|
|||||||
}: {
|
}: {
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { data, loading, error, refetch } = useQuery<
|
const { data, loading, error, refetch } = useQuery<
|
||||||
ObjectMetadataItemsQuery,
|
ObjectMetadataItemsQuery,
|
||||||
@ -26,8 +25,8 @@ export const useFindManyObjectMetadataItems = ({
|
|||||||
skip,
|
skip,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
logError('useFindManyObjectMetadataItems error : ' + error);
|
logError('useFindManyObjectMetadataItems error : ' + error);
|
||||||
enqueueSnackBar(`${error.message}`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import {
|
|||||||
} from '@/object-record/hooks/useCreateManyRecords';
|
} from '@/object-record/hooks/useCreateManyRecords';
|
||||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { ApolloError } from '@apollo/client';
|
import { ApolloError } from '@apollo/client';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
@ -43,7 +42,7 @@ export const useBatchCreateManyRecords = <
|
|||||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueWarningSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const batchCreateManyRecords = async ({
|
const batchCreateManyRecords = async ({
|
||||||
recordsToCreate,
|
recordsToCreate,
|
||||||
@ -84,13 +83,12 @@ export const useBatchCreateManyRecords = <
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ApolloError && error.message.includes('aborted')) {
|
if (error instanceof ApolloError && error.message.includes('aborted')) {
|
||||||
const formattedCreatedRecordsCount = formatNumber(createdRecordsCount);
|
const formattedCreatedRecordsCount = formatNumber(createdRecordsCount);
|
||||||
enqueueSnackBar(
|
enqueueWarningSnackBar({
|
||||||
t`Record creation stopped. ${formattedCreatedRecordsCount} records created.`,
|
message: t`Record creation stopped. ${formattedCreatedRecordsCount} records created.`,
|
||||||
{
|
options: {
|
||||||
variant: SnackBarVariant.Warning,
|
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
|
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
|
||||||
@ -121,7 +122,7 @@ export const useDeleteOneRecord = ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.catch((error: Error) => {
|
.catch((error: ApolloError) => {
|
||||||
if (!shouldHandleOptimisticCache) {
|
if (!shouldHandleOptimisticCache) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { RecordGqlOperationFindDuplicatesResult } from '@/object-record/graphql/
|
|||||||
import { useFindDuplicateRecordsQuery } from '@/object-record/hooks/useFindDuplicatesRecordsQuery';
|
import { useFindDuplicateRecordsQuery } from '@/object-record/hooks/useFindDuplicatesRecordsQuery';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { getFindDuplicateRecordsQueryResponseField } from '@/object-record/utils/getFindDuplicateRecordsQueryResponseField';
|
import { getFindDuplicateRecordsQueryResponseField } from '@/object-record/utils/getFindDuplicateRecordsQueryResponseField';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { logError } from '~/utils/logError';
|
import { logError } from '~/utils/logError';
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const queryResponseField = getFindDuplicateRecordsQueryResponseField(
|
const queryResponseField = getFindDuplicateRecordsQueryResponseField(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
@ -59,8 +58,8 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
`useFindDuplicateRecords for "${objectMetadataItem.nameSingular}" error : ` +
|
`useFindDuplicateRecords for "${objectMetadataItem.nameSingular}" error : ` +
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
enqueueSnackBar(`Error finding duplicates:", ${error.message}`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { ApolloError } from '@apollo/client';
|
import { ApolloError } from '@apollo/client';
|
||||||
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { logError } from '~/utils/logError';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { logError } from '~/utils/logError';
|
||||||
|
|
||||||
export const useHandleFindManyRecordsError = ({
|
export const useHandleFindManyRecordsError = ({
|
||||||
handleError,
|
handleError,
|
||||||
@ -13,7 +12,7 @@ export const useHandleFindManyRecordsError = ({
|
|||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
handleError?: (error?: Error) => void;
|
handleError?: (error?: Error) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const handleFindManyRecordsError = useCallback(
|
const handleFindManyRecordsError = useCallback(
|
||||||
(error: ApolloError) => {
|
(error: ApolloError) => {
|
||||||
@ -21,12 +20,12 @@ export const useHandleFindManyRecordsError = ({
|
|||||||
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
|
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
enqueueSnackBar(`${error.message}`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
handleError?.(error);
|
handleError?.(error);
|
||||||
},
|
},
|
||||||
[enqueueSnackBar, handleError, objectMetadataItem.namePlural],
|
[enqueueErrorSnackBar, handleError, objectMetadataItem.namePlural],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
|
|||||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { WatchQueryFetchPolicy } from '@apollo/client';
|
import { WatchQueryFetchPolicy } from '@apollo/client';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@ -34,10 +33,9 @@ export const useObjectRecordSearchRecords = ({
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const apolloCoreClient = useApolloCoreClient();
|
const apolloCoreClient = useApolloCoreClient();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
|
||||||
|
|
||||||
const { data, loading, error, previousData } = useSearchQuery({
|
const { data, loading, error, previousData } = useSearchQuery({
|
||||||
skip:
|
skip:
|
||||||
skip ||
|
skip ||
|
||||||
@ -57,12 +55,9 @@ export const useObjectRecordSearchRecords = ({
|
|||||||
`useSearchRecords for "${objectMetadataItem.namePlural}" error : ` +
|
`useSearchRecords for "${objectMetadataItem.namePlural}" error : ` +
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
`Error during useSearchRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
|
apolloError: error,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropd
|
|||||||
import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown';
|
import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown';
|
||||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
@ -65,7 +64,7 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const isDefaultView = currentView?.key === 'INDEX';
|
const isDefaultView = currentView?.key === 'INDEX';
|
||||||
|
|
||||||
@ -171,10 +170,12 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
onEnter={() => {
|
onEnter={() => {
|
||||||
const currentUrl = window.location.href;
|
const currentUrl = window.location.href;
|
||||||
navigator.clipboard.writeText(currentUrl);
|
navigator.clipboard.writeText(currentUrl);
|
||||||
enqueueSnackBar('Link copied to clipboard', {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Link copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -183,10 +184,12 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const currentUrl = window.location.href;
|
const currentUrl = window.location.href;
|
||||||
navigator.clipboard.writeText(currentUrl);
|
navigator.clipboard.writeText(currentUrl);
|
||||||
enqueueSnackBar('Link copied to clipboard', {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Link copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
LeftIcon={IconCopy}
|
LeftIcon={IconCopy}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { IconCopy } from 'twenty-ui/display';
|
import { IconCopy } from 'twenty-ui/display';
|
||||||
@ -16,7 +15,7 @@ export type LightCopyIconButtonProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const LightCopyIconButton = ({ copyText }: LightCopyIconButtonProps) => {
|
export const LightCopyIconButton = ({ copyText }: LightCopyIconButtonProps) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
@ -25,10 +24,12 @@ export const LightCopyIconButton = ({ copyText }: LightCopyIconButtonProps) => {
|
|||||||
<LightIconButton
|
<LightIconButton
|
||||||
Icon={IconCopy}
|
Icon={IconCopy}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
enqueueSnackBar(t`Text copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Text copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
navigator.clipboard.writeText(copyText);
|
navigator.clipboard.writeText(copyText);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
||||||
import { usePhonesFieldDisplay } from '@/object-record/record-field/meta-types/hooks/usePhonesFieldDisplay';
|
import { usePhonesFieldDisplay } from '@/object-record/record-field/meta-types/hooks/usePhonesFieldDisplay';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { PhonesDisplay } from '@/ui/field/display/components/PhonesDisplay';
|
import { PhonesDisplay } from '@/ui/field/display/components/PhonesDisplay';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -12,7 +11,7 @@ export const PhonesFieldDisplay = () => {
|
|||||||
|
|
||||||
const { isFocused } = useFieldFocus();
|
const { isFocused } = useFieldFocus();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
@ -29,16 +28,20 @@ export const PhonesFieldDisplay = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(phoneNumber);
|
await navigator.clipboard.writeText(phoneNumber);
|
||||||
enqueueSnackBar(t`Phone number copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Phone number copied to clipboard`,
|
||||||
icon: <IconCircleCheck size={16} color="green" />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCircleCheck size={16} color="green" />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
enqueueSnackBar(t`Error copying to clipboard`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Error copying to clipboard`,
|
||||||
icon: <IconExclamationCircle size={16} color="red" />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconExclamationCircle size={16} color="red" />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { SpreadsheetImportCreateRecordsBatchSize } from '@/spreadsheet-import/co
|
|||||||
import { useOpenSpreadsheetImportDialog } from '@/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog';
|
import { useOpenSpreadsheetImportDialog } from '@/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog';
|
||||||
import { spreadsheetImportCreatedRecordsProgressState } from '@/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState';
|
import { spreadsheetImportCreatedRecordsProgressState } from '@/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState';
|
||||||
import { SpreadsheetImportDialogOptions } from '@/spreadsheet-import/types';
|
import { SpreadsheetImportDialogOptions } from '@/spreadsheet-import/types';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
@ -17,7 +16,7 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
|
|||||||
objectNameSingular: string,
|
objectNameSingular: string,
|
||||||
) => {
|
) => {
|
||||||
const { openSpreadsheetImportDialog } = useOpenSpreadsheetImportDialog<any>();
|
const { openSpreadsheetImportDialog } = useOpenSpreadsheetImportDialog<any>();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
const { objectMetadataItem } = useObjectMetadataItem({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
@ -79,8 +78,8 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
|
|||||||
upsert: true,
|
upsert: true,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
enqueueSnackBar(error?.message || 'Something went wrong', {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import {
|
import {
|
||||||
ConnectionParameters,
|
ConnectionParameters,
|
||||||
@ -38,7 +38,7 @@ export const useImapConnectionForm = ({
|
|||||||
}: UseImapConnectionFormProps = {}) => {
|
}: UseImapConnectionFormProps = {}) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const navigate = useNavigateSettings();
|
const navigate = useNavigateSettings();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
|
|
||||||
@ -70,16 +70,12 @@ export const useImapConnectionForm = ({
|
|||||||
formValues: ConnectionParameters & { handle: string },
|
formValues: ConnectionParameters & { handle: string },
|
||||||
) => {
|
) => {
|
||||||
if (!currentWorkspace?.id) {
|
if (!currentWorkspace?.id) {
|
||||||
enqueueSnackBar('Workspace ID is missing', {
|
enqueueErrorSnackBar({});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentWorkspaceMember?.id) {
|
if (!currentWorkspaceMember?.id) {
|
||||||
enqueueSnackBar('Workspace member ID is missing', {
|
enqueueErrorSnackBar({});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,19 +108,16 @@ export const useImapConnectionForm = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
enqueueSnackBar(
|
enqueueSuccessSnackBar({
|
||||||
connectedAccountId
|
message: connectedAccountId
|
||||||
? t`IMAP connection successfully updated`
|
? t`IMAP connection successfully updated`
|
||||||
: t`IMAP connection successfully created`,
|
: t`IMAP connection successfully created`,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Success,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
navigate(SettingsPath.Accounts);
|
navigate(SettingsPath.Accounts);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
||||||
import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent';
|
import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent';
|
||||||
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
|
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||||
@ -40,7 +39,7 @@ const StyledContainer = styled.div`
|
|||||||
|
|
||||||
export const SettingsAdminGeneral = () => {
|
export const SettingsAdminGeneral = () => {
|
||||||
const [userIdentifier, setUserIdentifier] = useState('');
|
const [userIdentifier, setUserIdentifier] = useState('');
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
||||||
activeTabIdComponentState,
|
activeTabIdComponentState,
|
||||||
@ -76,8 +75,8 @@ export const SettingsAdminGeneral = () => {
|
|||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
setIsUserLookupLoading(false);
|
setIsUserLookupLoading(false);
|
||||||
enqueueSnackBar(error.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { useImpersonationAuth } from '@/settings/admin-panel/hooks/useImpersonat
|
|||||||
import { useImpersonationRedirect } from '@/settings/admin-panel/hooks/useImpersonationRedirect';
|
import { useImpersonationRedirect } from '@/settings/admin-panel/hooks/useImpersonationRedirect';
|
||||||
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
|
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
|
||||||
import { WorkspaceInfo } from '@/settings/admin-panel/types/WorkspaceInfo';
|
import { WorkspaceInfo } from '@/settings/admin-panel/types/WorkspaceInfo';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Table } from '@/ui/layout/table/components/Table';
|
import { Table } from '@/ui/layout/table/components/Table';
|
||||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||||
@ -57,7 +56,7 @@ export const SettingsAdminWorkspaceContent = ({
|
|||||||
activeWorkspace,
|
activeWorkspace,
|
||||||
}: SettingsAdminWorkspaceContentProps) => {
|
}: SettingsAdminWorkspaceContentProps) => {
|
||||||
const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState);
|
const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState);
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const [currentUser] = useRecoilState(currentUserState);
|
const [currentUser] = useRecoilState(currentUserState);
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
|
||||||
@ -74,9 +73,7 @@ export const SettingsAdminWorkspaceContent = ({
|
|||||||
|
|
||||||
const handleImpersonate = async (workspaceId: string) => {
|
const handleImpersonate = async (workspaceId: string) => {
|
||||||
if (!userLookupResult?.user.id) {
|
if (!userLookupResult?.user.id) {
|
||||||
enqueueSnackBar(t`Please search for a user first`, {
|
enqueueErrorSnackBar({ message: t`Please search for a user first` });
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,8 +95,8 @@ export const SettingsAdminWorkspaceContent = ({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
enqueueSnackBar(`Failed to impersonate user. ${error.message}`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: `Failed to impersonate user. ${error.message}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
@ -128,8 +125,8 @@ export const SettingsAdminWorkspaceContent = ({
|
|||||||
if (isDefined(previousValue)) {
|
if (isDefined(previousValue)) {
|
||||||
updateFeatureFlagState(workspaceId, featureFlag, previousValue);
|
updateFeatureFlagState(workspaceId, featureFlag, previousValue);
|
||||||
}
|
}
|
||||||
enqueueSnackBar(`Failed to update feature flag. ${error.message}`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: `Failed to update feature flag. ${error.message}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -33,15 +32,17 @@ export const SettingsAdminConfigCopyableText = ({
|
|||||||
multiline = false,
|
multiline = false,
|
||||||
maxRows,
|
maxRows,
|
||||||
}: SettingsAdminConfigCopyableTextProps) => {
|
}: SettingsAdminConfigCopyableTextProps) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const copyToClipboardDebounced = useDebouncedCallback((value: string) => {
|
const copyToClipboardDebounced = useDebouncedCallback((value: string) => {
|
||||||
navigator.clipboard.writeText(value);
|
navigator.clipboard.writeText(value);
|
||||||
enqueueSnackBar(t`Copied to clipboard!`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Copied to clipboard!`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { useLingui } from '@lingui/react/macro';
|
|||||||
|
|
||||||
import { useClientConfig } from '@/client-config/hooks/useClientConfig';
|
import { useClientConfig } from '@/client-config/hooks/useClientConfig';
|
||||||
import { GET_DATABASE_CONFIG_VARIABLE } from '@/settings/admin-panel/config-variables/graphql/queries/getDatabaseConfigVariable';
|
import { GET_DATABASE_CONFIG_VARIABLE } from '@/settings/admin-panel/config-variables/graphql/queries/getDatabaseConfigVariable';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { ConfigVariableValue } from 'twenty-shared/types';
|
import { ConfigVariableValue } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -14,7 +13,7 @@ import {
|
|||||||
|
|
||||||
export const useConfigVariableActions = (variableName: string) => {
|
export const useConfigVariableActions = (variableName: string) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { refetch: refetchClientConfig } = useClientConfig();
|
const { refetch: refetchClientConfig } = useClientConfig();
|
||||||
|
|
||||||
const [updateDatabaseConfigVariable] =
|
const [updateDatabaseConfigVariable] =
|
||||||
@ -68,12 +67,12 @@ export const useConfigVariableActions = (variableName: string) => {
|
|||||||
|
|
||||||
await refetchClientConfig();
|
await refetchClientConfig();
|
||||||
|
|
||||||
enqueueSnackBar(t`Variable updated successfully.`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Variable updated successfully.`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar(t`Failed to update variable`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Failed to update variable`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -98,12 +97,12 @@ export const useConfigVariableActions = (variableName: string) => {
|
|||||||
|
|
||||||
await refetchClientConfig();
|
await refetchClientConfig();
|
||||||
|
|
||||||
enqueueSnackBar(t`Variable deleted successfully.`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Variable deleted successfully.`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar(t`Failed to remove override`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Failed to remove override`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
||||||
import { WorkerMetricsTooltip } from '@/settings/admin-panel/health-status/components/WorkerMetricsTooltip';
|
import { WorkerMetricsTooltip } from '@/settings/admin-panel/health-status/components/WorkerMetricsTooltip';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -45,7 +44,7 @@ export const WorkerMetricsGraph = ({
|
|||||||
timeRange,
|
timeRange,
|
||||||
}: WorkerMetricsGraphProps) => {
|
}: WorkerMetricsGraphProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { loading, data } = useGetQueueMetricsQuery({
|
const { loading, data } = useGetQueueMetricsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
@ -54,8 +53,8 @@ export const WorkerMetricsGraph = ({
|
|||||||
},
|
},
|
||||||
fetchPolicy: 'no-cache',
|
fetchPolicy: 'no-cache',
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
enqueueSnackBar(`Error fetching worker metrics: ${error.message}`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: `Error fetching worker metrics: ${error.message}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import {
|
|||||||
settingsDataModelObjectAboutFormSchema,
|
settingsDataModelObjectAboutFormSchema,
|
||||||
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
|
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
@ -23,7 +23,7 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
}: SettingsUpdateDataModelObjectAboutFormProps) => {
|
}: SettingsUpdateDataModelObjectAboutFormProps) => {
|
||||||
const navigate = useNavigateSettings();
|
const navigate = useNavigateSettings();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const setUpdatedObjectNamePlural = useSetRecoilState(
|
const setUpdatedObjectNamePlural = useSetRecoilState(
|
||||||
updatedObjectNamePluralState,
|
updatedObjectNamePluralState,
|
||||||
);
|
);
|
||||||
@ -117,15 +117,20 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
if (error instanceof ZodError) {
|
if (error instanceof ZodError) {
|
||||||
enqueueSnackBar(error.issues[0].message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: error.issues[0].message,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueueSnackBar((error as Error).message, {
|
if (error instanceof ApolloError) {
|
||||||
variant: SnackBarVariant.Error,
|
enqueueErrorSnackBar({
|
||||||
});
|
apolloError: error,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueErrorSnackBar({});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,9 +7,9 @@ import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdat
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFieldMetadataItems';
|
import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFieldMetadataItems';
|
||||||
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
|
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { Select } from '@/ui/input/components/Select';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@ -48,7 +48,7 @@ export const SettingsDataModelObjectIdentifiersForm = ({
|
|||||||
mode: 'onTouched',
|
mode: 'onTouched',
|
||||||
resolver: zodResolver(settingsDataModelObjectIdentifiersFormSchema),
|
resolver: zodResolver(settingsDataModelObjectIdentifiersFormSchema),
|
||||||
});
|
});
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||||
|
|
||||||
const handleSave = async (
|
const handleSave = async (
|
||||||
@ -67,12 +67,12 @@ export const SettingsDataModelObjectIdentifiersForm = ({
|
|||||||
formConfig.reset(undefined, { keepValues: true });
|
formConfig.reset(undefined, { keepValues: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ZodError) {
|
if (error instanceof ZodError) {
|
||||||
enqueueSnackBar(error.issues[0].message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: error.issues[0].message,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { IconCopy } from 'twenty-ui/display';
|
import { IconCopy } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -25,7 +24,7 @@ export const ApiKeyInput = ({ apiKey }: ApiKeyInputProps) => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledLinkContainer>
|
<StyledLinkContainer>
|
||||||
@ -35,10 +34,12 @@ export const ApiKeyInput = ({ apiKey }: ApiKeyInputProps) => {
|
|||||||
Icon={IconCopy}
|
Icon={IconCopy}
|
||||||
title={t`Copy`}
|
title={t`Copy`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
enqueueSnackBar(t`API Key copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`API Key copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
navigator.clipboard.writeText(apiKey);
|
navigator.clipboard.writeText(apiKey);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -5,11 +5,13 @@ import { MemoryRouter } from 'react-router-dom';
|
|||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
|
|
||||||
import { WebhookFormMode } from '@/settings/developers/constants/WebhookFormMode';
|
import { WebhookFormMode } from '@/settings/developers/constants/WebhookFormMode';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useWebhookForm } from '../useWebhookForm';
|
import { useWebhookForm } from '../useWebhookForm';
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
const mockNavigateSettings = jest.fn();
|
const mockNavigateSettings = jest.fn();
|
||||||
const mockEnqueueSnackBar = jest.fn();
|
const mockEnqueueSuccessSnackBar = jest.fn();
|
||||||
|
const mockEnqueueErrorSnackBar = jest.fn();
|
||||||
const mockCreateOneRecord = jest.fn();
|
const mockCreateOneRecord = jest.fn();
|
||||||
const mockUpdateOneRecord = jest.fn();
|
const mockUpdateOneRecord = jest.fn();
|
||||||
const mockDeleteOneRecord = jest.fn();
|
const mockDeleteOneRecord = jest.fn();
|
||||||
@ -20,7 +22,8 @@ jest.mock('~/hooks/useNavigateSettings', () => ({
|
|||||||
|
|
||||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({
|
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({
|
||||||
useSnackBar: () => ({
|
useSnackBar: () => ({
|
||||||
enqueueSnackBar: mockEnqueueSnackBar,
|
enqueueSuccessSnackBar: mockEnqueueSuccessSnackBar,
|
||||||
|
enqueueErrorSnackBar: mockEnqueueErrorSnackBar,
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -106,14 +109,15 @@ describe('useWebhookForm', () => {
|
|||||||
secret: 'test-secret',
|
secret: 'test-secret',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockEnqueueSnackBar).toHaveBeenCalledWith(
|
expect(mockEnqueueSuccessSnackBar).toHaveBeenCalledWith({
|
||||||
'Webhook https://test.com/webhook created successfully',
|
message: 'Webhook https://test.com/webhook created successfully',
|
||||||
{ variant: 'success' },
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle creation errors', async () => {
|
it('should handle creation errors', async () => {
|
||||||
const error = new Error('Creation failed');
|
const error = new ApolloError({
|
||||||
|
graphQLErrors: [{ message: 'Creation failed' }],
|
||||||
|
});
|
||||||
mockCreateOneRecord.mockRejectedValue(error);
|
mockCreateOneRecord.mockRejectedValue(error);
|
||||||
|
|
||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
@ -130,8 +134,8 @@ describe('useWebhookForm', () => {
|
|||||||
|
|
||||||
await result.current.handleSave(formData);
|
await result.current.handleSave(formData);
|
||||||
|
|
||||||
expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Creation failed', {
|
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||||
variant: 'error',
|
apolloError: error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -216,7 +220,9 @@ describe('useWebhookForm', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle update errors', async () => {
|
it('should handle update errors', async () => {
|
||||||
const error = new Error('Update failed');
|
const error = new ApolloError({
|
||||||
|
graphQLErrors: [{ message: 'Update failed' }],
|
||||||
|
});
|
||||||
mockUpdateOneRecord.mockRejectedValue(error);
|
mockUpdateOneRecord.mockRejectedValue(error);
|
||||||
|
|
||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
@ -237,8 +243,8 @@ describe('useWebhookForm', () => {
|
|||||||
|
|
||||||
await result.current.handleSave(formData);
|
await result.current.handleSave(formData);
|
||||||
|
|
||||||
expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Update failed', {
|
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||||
variant: 'error',
|
apolloError: error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -297,10 +303,9 @@ describe('useWebhookForm', () => {
|
|||||||
await result.current.deleteWebhook();
|
await result.current.deleteWebhook();
|
||||||
|
|
||||||
expect(mockDeleteOneRecord).toHaveBeenCalledWith(webhookId);
|
expect(mockDeleteOneRecord).toHaveBeenCalledWith(webhookId);
|
||||||
expect(mockEnqueueSnackBar).toHaveBeenCalledWith(
|
expect(mockEnqueueSuccessSnackBar).toHaveBeenCalledWith({
|
||||||
'Webhook deleted successfully',
|
message: 'Webhook deleted successfully',
|
||||||
{ variant: 'success' },
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle deletion without webhookId', async () => {
|
it('should handle deletion without webhookId', async () => {
|
||||||
@ -311,14 +316,15 @@ describe('useWebhookForm', () => {
|
|||||||
|
|
||||||
await result.current.deleteWebhook();
|
await result.current.deleteWebhook();
|
||||||
|
|
||||||
expect(mockEnqueueSnackBar).toHaveBeenCalledWith(
|
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||||
'Webhook ID is required for deletion',
|
message: 'Webhook ID is required for deletion',
|
||||||
{ variant: 'error' },
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle deletion errors', async () => {
|
it('should handle deletion errors', async () => {
|
||||||
const error = new Error('Deletion failed');
|
const error = new ApolloError({
|
||||||
|
graphQLErrors: [{ message: 'Deletion failed' }],
|
||||||
|
});
|
||||||
mockDeleteOneRecord.mockRejectedValue(error);
|
mockDeleteOneRecord.mockRejectedValue(error);
|
||||||
|
|
||||||
const { result } = renderHook(
|
const { result } = renderHook(
|
||||||
@ -332,8 +338,8 @@ describe('useWebhookForm', () => {
|
|||||||
|
|
||||||
await result.current.deleteWebhook();
|
await result.current.deleteWebhook();
|
||||||
|
|
||||||
expect(mockEnqueueSnackBar).toHaveBeenCalledWith('Deletion failed', {
|
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||||
variant: 'error',
|
apolloError: error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -13,8 +13,9 @@ import {
|
|||||||
WebhookFormValues,
|
WebhookFormValues,
|
||||||
} from '@/settings/developers/validation-schemas/webhookFormSchema';
|
} from '@/settings/developers/validation-schemas/webhookFormSchema';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
@ -28,7 +29,7 @@ type UseWebhookFormProps = {
|
|||||||
|
|
||||||
export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||||
const navigate = useNavigateSettings();
|
const navigate = useNavigateSettings();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const isCreationMode = mode === WebhookFormMode.Create;
|
const isCreationMode = mode === WebhookFormMode.Create;
|
||||||
|
|
||||||
@ -134,28 +135,29 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
|||||||
...webhookData,
|
...webhookData,
|
||||||
});
|
});
|
||||||
|
|
||||||
enqueueSnackBar(
|
const targetUrl = createdWebhook?.targetUrl
|
||||||
`Webhook ${createdWebhook?.targetUrl} created successfully`,
|
? `${createdWebhook?.targetUrl}`
|
||||||
{
|
: '';
|
||||||
variant: SnackBarVariant.Success,
|
|
||||||
},
|
enqueueSuccessSnackBar({
|
||||||
);
|
message: t`Webhook ${targetUrl} created successfully`,
|
||||||
|
});
|
||||||
|
|
||||||
navigate(
|
navigate(
|
||||||
createdWebhook ? SettingsPath.WebhookDetail : SettingsPath.Webhooks,
|
createdWebhook ? SettingsPath.WebhookDetail : SettingsPath.Webhooks,
|
||||||
createdWebhook ? { webhookId: createdWebhook.id } : undefined,
|
createdWebhook ? { webhookId: createdWebhook.id } : undefined,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = async (formValues: WebhookFormValues) => {
|
const handleUpdate = async (formValues: WebhookFormValues) => {
|
||||||
if (!webhookId) {
|
if (!webhookId) {
|
||||||
enqueueSnackBar('Webhook ID is required for updates', {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Webhook ID is required for updates`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -177,12 +179,14 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
|||||||
|
|
||||||
formConfig.reset(formValues);
|
formConfig.reset(formValues);
|
||||||
|
|
||||||
enqueueSnackBar(`Webhook ${webhookData.targetUrl} updated successfully`, {
|
const targetUrl = webhookData.targetUrl ? `${webhookData.targetUrl}` : '';
|
||||||
variant: SnackBarVariant.Success,
|
|
||||||
|
enqueueSuccessSnackBar({
|
||||||
|
message: t`Webhook ${targetUrl} updated successfully`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -222,22 +226,22 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
|||||||
|
|
||||||
const deleteWebhook = async () => {
|
const deleteWebhook = async () => {
|
||||||
if (!webhookId) {
|
if (!webhookId) {
|
||||||
enqueueSnackBar('Webhook ID is required for deletion', {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Webhook ID is required for deletion`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteOneWebhook(webhookId);
|
await deleteOneWebhook(webhookId);
|
||||||
enqueueSnackBar('Webhook deleted successfully', {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Webhook deleted successfully`,
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate(SettingsPath.Webhooks);
|
navigate(SettingsPath.Webhooks);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,13 +9,14 @@ import {
|
|||||||
} from '@/settings/integrations/database-connection/utils/editDatabaseConnection';
|
} from '@/settings/integrations/database-connection/utils/editDatabaseConnection';
|
||||||
import { SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration';
|
import { SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Section } from '@react-email/components';
|
import { Section } from '@react-email/components';
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import { H2Title, Info } from 'twenty-ui/display';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import {
|
||||||
RemoteServer,
|
RemoteServer,
|
||||||
@ -24,7 +25,6 @@ import {
|
|||||||
} from '~/generated-metadata/graphql';
|
} from '~/generated-metadata/graphql';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
import { H2Title, Info } from 'twenty-ui/display';
|
|
||||||
|
|
||||||
export const SettingsIntegrationEditDatabaseConnectionContent = ({
|
export const SettingsIntegrationEditDatabaseConnectionContent = ({
|
||||||
connection,
|
connection,
|
||||||
@ -37,7 +37,7 @@ export const SettingsIntegrationEditDatabaseConnectionContent = ({
|
|||||||
databaseKey: string;
|
databaseKey: string;
|
||||||
tables: RemoteTable[];
|
tables: RemoteTable[];
|
||||||
}) => {
|
}) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const navigate = useNavigateSettings();
|
const navigate = useNavigateSettings();
|
||||||
|
|
||||||
const editConnectionSchema = getEditionSchemaForForm(databaseKey);
|
const editConnectionSchema = getEditionSchemaForForm(databaseKey);
|
||||||
@ -87,8 +87,8 @@ export const SettingsIntegrationEditDatabaseConnectionContent = ({
|
|||||||
connectionId: connection?.id,
|
connectionId: connection?.id,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
|||||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||||
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||||
@ -81,7 +80,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
|
|
||||||
const { loadCurrentUser } = useAuth();
|
const { loadCurrentUser } = useAuth();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
if (!isDefined(settingsRolesIsLoading)) {
|
if (!isDefined(settingsRolesIsLoading)) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@ -129,8 +128,8 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (isDefined(dirtyFields.label) && dirtyFields.label === '') {
|
if (isDefined(dirtyFields.label) && dirtyFields.label === '') {
|
||||||
enqueueSnackBar(t`Role name cannot be empty`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Role name cannot be empty`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
|||||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||||
import { SettingsSSOIdentitiesProvidersListCardWrapper } from '@/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCardWrapper';
|
import { SettingsSSOIdentitiesProvidersListCardWrapper } from '@/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCardWrapper';
|
||||||
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
|
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import isPropValid from '@emotion/is-prop-valid';
|
import isPropValid from '@emotion/is-prop-valid';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@ -26,7 +26,7 @@ const StyledLink = styled(Link, {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsSSOIdentitiesProvidersListCard = () => {
|
export const SettingsSSOIdentitiesProvidersListCard = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
|
||||||
@ -42,9 +42,9 @@ export const SettingsSSOIdentitiesProvidersListCard = () => {
|
|||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
setSSOIdentitiesProviders(data?.getSSOIdentityProviders ?? []);
|
setSSOIdentitiesProviders(data?.getSSOIdentityProviders ?? []);
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: ApolloError) => {
|
||||||
enqueueSnackBar(error.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
/* @license Enterprise */
|
/* @license Enterprise */
|
||||||
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { H2Title, IconCopy } from 'twenty-ui/display';
|
import { H2Title, IconCopy } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
const StyledInputsContainer = styled.div`
|
const StyledInputsContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -37,7 +36,7 @@ const StyledButtonCopy = styled.div`
|
|||||||
|
|
||||||
export const SettingsSSOOIDCForm = () => {
|
export const SettingsSSOOIDCForm = () => {
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
@ -66,10 +65,12 @@ export const SettingsSSOOIDCForm = () => {
|
|||||||
Icon={IconCopy}
|
Icon={IconCopy}
|
||||||
title={t`Copy`}
|
title={t`Copy`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
enqueueSnackBar(t`Authorized URL copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Authorized URL copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
navigator.clipboard.writeText(authorizedUrl);
|
navigator.clipboard.writeText(authorizedUrl);
|
||||||
}}
|
}}
|
||||||
@ -91,10 +92,12 @@ export const SettingsSSOOIDCForm = () => {
|
|||||||
Icon={IconCopy}
|
Icon={IconCopy}
|
||||||
title={t`Copy`}
|
title={t`Copy`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
enqueueSnackBar(t`Redirect Url copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Redirect Url copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
navigator.clipboard.writeText(redirectionUrl);
|
navigator.clipboard.writeText(redirectionUrl);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
/* @license Enterprise */
|
/* @license Enterprise */
|
||||||
|
|
||||||
import { parseSAMLMetadataFromXMLFile } from '@/settings/security/utils/parseSAMLMetadataFromXMLFile';
|
import { parseSAMLMetadataFromXMLFile } from '@/settings/security/utils/parseSAMLMetadataFromXMLFile';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
@ -9,9 +8,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { ChangeEvent, useRef } from 'react';
|
import { ChangeEvent, useRef } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import {
|
import {
|
||||||
H2Title,
|
H2Title,
|
||||||
HorizontalSeparator,
|
HorizontalSeparator,
|
||||||
@ -20,7 +17,9 @@ import {
|
|||||||
IconDownload,
|
IconDownload,
|
||||||
IconUpload,
|
IconUpload,
|
||||||
} from 'twenty-ui/display';
|
} from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
const StyledUploadFileContainer = styled.div`
|
const StyledUploadFileContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -56,7 +55,7 @@ const StyledButtonCopy = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsSSOSAMLForm = () => {
|
export const SettingsSSOSAMLForm = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { setValue, getValues, watch, trigger } = useFormContext();
|
const { setValue, getValues, watch, trigger } = useFormContext();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
@ -67,9 +66,11 @@ export const SettingsSSOSAMLForm = () => {
|
|||||||
const samlMetadataParsed = parseSAMLMetadataFromXMLFile(text);
|
const samlMetadataParsed = parseSAMLMetadataFromXMLFile(text);
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
if (!samlMetadataParsed.success) {
|
if (!samlMetadataParsed.success) {
|
||||||
return enqueueSnackBar(t`Invalid File`, {
|
return enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Invalid File`,
|
||||||
duration: 2000,
|
options: {
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setValue('ssoURL', samlMetadataParsed.data.ssoUrl);
|
setValue('ssoURL', samlMetadataParsed.data.ssoUrl);
|
||||||
@ -103,9 +104,11 @@ export const SettingsSSOSAMLForm = () => {
|
|||||||
`${REACT_APP_SERVER_BASE_URL}/auth/saml/metadata/${getValues('id')}`,
|
`${REACT_APP_SERVER_BASE_URL}/auth/saml/metadata/${getValues('id')}`,
|
||||||
);
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return enqueueSnackBar(t`Metadata file generation failed`, {
|
return enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Metadata file generation failed`,
|
||||||
duration: 2000,
|
options: {
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
@ -177,10 +180,12 @@ export const SettingsSSOSAMLForm = () => {
|
|||||||
Icon={IconCopy}
|
Icon={IconCopy}
|
||||||
title="Copy"
|
title="Copy"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
enqueueSnackBar('ACS Url copied to clipboard', {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`ACS Url copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
navigator.clipboard.writeText(acsUrl);
|
navigator.clipboard.writeText(acsUrl);
|
||||||
}}
|
}}
|
||||||
@ -202,10 +207,12 @@ export const SettingsSSOSAMLForm = () => {
|
|||||||
Icon={IconCopy}
|
Icon={IconCopy}
|
||||||
title={t`Copy`}
|
title={t`Copy`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
enqueueSnackBar(t`Entity ID copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Entity ID copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
navigator.clipboard.writeText(entityID);
|
navigator.clipboard.writeText(entityID);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useDeleteSSOIdentityProvider } from '@/settings/security/hooks/useDeleteSSOIdentityProvider';
|
import { useDeleteSSOIdentityProvider } from '@/settings/security/hooks/useDeleteSSOIdentityProvider';
|
||||||
import { useUpdateSSOIdentityProvider } from '@/settings/security/hooks/useUpdateSSOIdentityProvider';
|
import { useUpdateSSOIdentityProvider } from '@/settings/security/hooks/useUpdateSSOIdentityProvider';
|
||||||
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
|
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
@ -24,7 +23,7 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
|||||||
}: SettingsSecuritySSORowDropdownMenuProps) => {
|
}: SettingsSecuritySSORowDropdownMenuProps) => {
|
||||||
const dropdownId = `settings-account-row-${SSOIdp.id}`;
|
const dropdownId = `settings-account-row-${SSOIdp.id}`;
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { closeDropdown } = useCloseDropdown();
|
const { closeDropdown } = useCloseDropdown();
|
||||||
|
|
||||||
@ -40,9 +39,11 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
|||||||
identityProviderId,
|
identityProviderId,
|
||||||
});
|
});
|
||||||
if (isDefined(result.errors)) {
|
if (isDefined(result.errors)) {
|
||||||
enqueueSnackBar(t`Error deleting SSO Identity Provider`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Error deleting SSO Identity Provider`,
|
||||||
duration: 2000,
|
options: {
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -58,9 +59,11 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
|||||||
: SsoIdentityProviderStatus.Active,
|
: SsoIdentityProviderStatus.Active,
|
||||||
});
|
});
|
||||||
if (isDefined(result.errors)) {
|
if (isDefined(result.errors)) {
|
||||||
enqueueSnackBar(t`Error editing SSO Identity Provider`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Error editing SSO Identity Provider`,
|
||||||
duration: 2000,
|
options: {
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
|||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||||
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
|
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
@ -30,7 +30,7 @@ const StyledSettingsSecurityOptionsList = styled.div`
|
|||||||
export const SettingsSecurityAuthProvidersOptionsList = () => {
|
export const SettingsSecurityAuthProvidersOptionsList = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const SSOIdentitiesProviders = useRecoilValue(SSOIdentitiesProvidersState);
|
const SSOIdentitiesProviders = useRecoilValue(SSOIdentitiesProvidersState);
|
||||||
const authProviders = useRecoilValue(authProvidersState);
|
const authProviders = useRecoilValue(authProvidersState);
|
||||||
|
|
||||||
@ -72,12 +72,9 @@ export const SettingsSecurityAuthProvidersOptionsList = () => {
|
|||||||
allAuthProvidersEnabled.filter((isAuthEnabled) => isAuthEnabled).length <=
|
allAuthProvidersEnabled.filter((isAuthEnabled) => isAuthEnabled).length <=
|
||||||
1
|
1
|
||||||
) {
|
) {
|
||||||
return enqueueSnackBar(
|
return enqueueErrorSnackBar({
|
||||||
t`At least one authentication method must be enabled`,
|
message: t`At least one authentication method must be enabled`,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentWorkspace({
|
setCurrentWorkspace({
|
||||||
@ -97,8 +94,8 @@ export const SettingsSecurityAuthProvidersOptionsList = () => {
|
|||||||
...currentWorkspace,
|
...currentWorkspace,
|
||||||
[key]: !currentWorkspace[key],
|
[key]: !currentWorkspace[key],
|
||||||
});
|
});
|
||||||
enqueueSnackBar(err?.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: err instanceof ApolloError ? err : undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -120,8 +117,8 @@ export const SettingsSecurityAuthProvidersOptionsList = () => {
|
|||||||
isPublicInviteLinkEnabled: value,
|
isPublicInviteLinkEnabled: value,
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
enqueueSnackBar(err?.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: err instanceof ApolloError ? err : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
|||||||
import { SettingsSecurityApprovedAccessDomainRowDropdownMenu } from '@/settings/security/components/approvedAccessDomains/SettingsSecurityApprovedAccessDomainRowDropdownMenu';
|
import { SettingsSecurityApprovedAccessDomainRowDropdownMenu } from '@/settings/security/components/approvedAccessDomains/SettingsSecurityApprovedAccessDomainRowDropdownMenu';
|
||||||
import { SettingsSecurityApprovedAccessDomainValidationEffect } from '@/settings/security/components/approvedAccessDomains/SettingsSecurityApprovedAccessDomainValidationEffect';
|
import { SettingsSecurityApprovedAccessDomainValidationEffect } from '@/settings/security/components/approvedAccessDomains/SettingsSecurityApprovedAccessDomainValidationEffect';
|
||||||
import { approvedAccessDomainsState } from '@/settings/security/states/ApprovedAccessDomainsState';
|
import { approvedAccessDomainsState } from '@/settings/security/states/ApprovedAccessDomainsState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
@ -22,7 +22,7 @@ const StyledLink = styled(Link)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsApprovedAccessDomainsListCard = () => {
|
export const SettingsApprovedAccessDomainsListCard = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
@ -36,8 +36,8 @@ export const SettingsApprovedAccessDomainsListCard = () => {
|
|||||||
setApprovedAccessDomains(data?.getApprovedAccessDomains ?? []);
|
setApprovedAccessDomains(data?.getApprovedAccessDomains ?? []);
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
enqueueSnackBar(error.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { approvedAccessDomainsState } from '@/settings/security/states/ApprovedAccessDomainsState';
|
import { approvedAccessDomainsState } from '@/settings/security/states/ApprovedAccessDomainsState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { UnwrapRecoilValue, useSetRecoilState } from 'recoil';
|
import { UnwrapRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconDotsVertical, IconTrash } from 'twenty-ui/display';
|
import { IconDotsVertical, IconTrash } from 'twenty-ui/display';
|
||||||
@ -25,7 +25,7 @@ export const SettingsSecurityApprovedAccessDomainRowDropdownMenu = ({
|
|||||||
approvedAccessDomainsState,
|
approvedAccessDomainsState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { closeDropdown } = useCloseDropdown();
|
const { closeDropdown } = useCloseDropdown();
|
||||||
|
|
||||||
@ -47,9 +47,11 @@ export const SettingsSecurityApprovedAccessDomainRowDropdownMenu = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (isDefined(result.errors)) {
|
if (isDefined(result.errors)) {
|
||||||
enqueueSnackBar('Error deleting approved access domain', {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Could not delete approved access domain`,
|
||||||
duration: 2000,
|
options: {
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -8,7 +8,7 @@ import { useValidateApprovedAccessDomainMutation } from '~/generated-metadata/gr
|
|||||||
export const SettingsSecurityApprovedAccessDomainValidationEffect = () => {
|
export const SettingsSecurityApprovedAccessDomainValidationEffect = () => {
|
||||||
const [validateApprovedAccessDomainMutation] =
|
const [validateApprovedAccessDomainMutation] =
|
||||||
useValidateApprovedAccessDomainMutation();
|
useValidateApprovedAccessDomainMutation();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const approvedAccessDomainId = searchParams.get('wtdId');
|
const approvedAccessDomainId = searchParams.get('wtdId');
|
||||||
const validationToken = searchParams.get('validationToken');
|
const validationToken = searchParams.get('validationToken');
|
||||||
@ -23,15 +23,19 @@ export const SettingsSecurityApprovedAccessDomainValidationEffect = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
enqueueSnackBar('Approved access domain validated', {
|
enqueueSuccessSnackBar({
|
||||||
dedupeKey: 'approved-access-domain-validation-dedupe-key',
|
message: t`Approved access domain validated`,
|
||||||
variant: SnackBarVariant.Success,
|
options: {
|
||||||
|
dedupeKey: 'approved-access-domain-validation-dedupe-key',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
enqueueSnackBar('Error validating approved access domain', {
|
enqueueErrorSnackBar({
|
||||||
dedupeKey: 'approved-access-domain-validation-error-dedupe-key',
|
message: t`Error validating approved access domain`,
|
||||||
variant: SnackBarVariant.Error,
|
options: {
|
||||||
|
dedupeKey: 'approved-access-domain-validation-error-dedupe-key',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,15 +2,15 @@ import { useRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { IconLifebuoy } from 'twenty-ui/display';
|
import { IconLifebuoy } from 'twenty-ui/display';
|
||||||
import { Card } from 'twenty-ui/layout';
|
import { Card } from 'twenty-ui/layout';
|
||||||
import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql';
|
import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export const ToggleImpersonate = () => {
|
export const ToggleImpersonate = () => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
|
||||||
currentWorkspaceState,
|
currentWorkspaceState,
|
||||||
@ -35,8 +35,8 @@ export const ToggleImpersonate = () => {
|
|||||||
allowImpersonation: value,
|
allowImpersonation: value,
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
enqueueSnackBar(err?.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: err instanceof ApolloError ? err : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import styled from '@emotion/styled';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
|
|
||||||
@ -47,15 +46,15 @@ export const SpreadsheetImportStepper = ({
|
|||||||
|
|
||||||
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
|
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const handleError = useCallback(
|
const handleError = useCallback(
|
||||||
(description: string) => {
|
(description: string) => {
|
||||||
enqueueSnackBar(description, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: description,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[enqueueSnackBar],
|
[enqueueErrorSnackBar],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBack = useCallback(() => {
|
const handleBack = useCallback(() => {
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { SpreadsheetMaxRecordImportCapacity } from '@/spreadsheet-import/constan
|
|||||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||||
import { useDownloadFakeRecords } from '@/spreadsheet-import/steps/components/UploadStep/hooks/useDownloadFakeRecords';
|
import { useDownloadFakeRecords } from '@/spreadsheet-import/steps/components/UploadStep/hooks/useDownloadFakeRecords';
|
||||||
import { readFileAsync } from '@/spreadsheet-import/utils/readFilesAsync';
|
import { readFileAsync } from '@/spreadsheet-import/utils/readFilesAsync';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { MainButton } from 'twenty-ui/input';
|
import { MainButton } from 'twenty-ui/input';
|
||||||
@ -113,7 +112,7 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
|
|||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { downloadSample } = useDownloadFakeRecords();
|
const { downloadSample } = useDownloadFakeRecords();
|
||||||
|
|
||||||
@ -132,9 +131,11 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
|
|||||||
onDropRejected: (fileRejections) => {
|
onDropRejected: (fileRejections) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
fileRejections.forEach((fileRejection) => {
|
fileRejections.forEach((fileRejection) => {
|
||||||
enqueueSnackBar(`${fileRejection.file.name} upload rejected`, {
|
enqueueErrorSnackBar({
|
||||||
detailedMessage: fileRejection.errors[0].message,
|
message: `${fileRejection.file.name} upload rejected`,
|
||||||
variant: SnackBarVariant.Error,
|
options: {
|
||||||
|
detailedMessage: fileRejection.errors[0].message,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,13 +2,17 @@ import { useCallback } from 'react';
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext';
|
import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext';
|
||||||
import {
|
import {
|
||||||
snackBarInternalScopedState,
|
snackBarInternalScopedState,
|
||||||
SnackBarOptions,
|
SnackBarOptions,
|
||||||
} from '@/ui/feedback/snack-bar-manager/states/snackBarInternalScopedState';
|
} from '@/ui/feedback/snack-bar-manager/states/snackBarInternalScopedState';
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { getErrorMessageFromApolloError } from '~/utils/get-error-message-from-apollo-error.util';
|
||||||
|
|
||||||
export const useSnackBar = () => {
|
export const useSnackBar = () => {
|
||||||
const scopeId = useAvailableScopeIdOrThrow(
|
const scopeId = useAvailableScopeIdOrThrow(
|
||||||
@ -54,16 +58,91 @@ export const useSnackBar = () => {
|
|||||||
[scopeId],
|
[scopeId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const enqueueSnackBar = useCallback(
|
const enqueueSuccessSnackBar = useCallback(
|
||||||
(message: string, options?: Omit<SnackBarOptions, 'message' | 'id'>) => {
|
({
|
||||||
|
message,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
message: string;
|
||||||
|
options?: Omit<SnackBarOptions, 'message' | 'id'>;
|
||||||
|
}) => {
|
||||||
setSnackBarQueue({
|
setSnackBarQueue({
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
message,
|
message,
|
||||||
...options,
|
...options,
|
||||||
|
variant: SnackBarVariant.Success,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setSnackBarQueue],
|
[setSnackBarQueue],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { handleSnackBarClose, enqueueSnackBar };
|
const enqueueInfoSnackBar = useCallback(
|
||||||
|
({
|
||||||
|
message,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
message: string;
|
||||||
|
options?: Omit<SnackBarOptions, 'message' | 'id'>;
|
||||||
|
}) => {
|
||||||
|
setSnackBarQueue({
|
||||||
|
id: uuidv4(),
|
||||||
|
message,
|
||||||
|
...options,
|
||||||
|
variant: SnackBarVariant.Info,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setSnackBarQueue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const enqueueWarningSnackBar = useCallback(
|
||||||
|
({
|
||||||
|
message,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
message: string;
|
||||||
|
options?: Omit<SnackBarOptions, 'message' | 'id'>;
|
||||||
|
}) => {
|
||||||
|
setSnackBarQueue({
|
||||||
|
id: uuidv4(),
|
||||||
|
message,
|
||||||
|
...options,
|
||||||
|
variant: SnackBarVariant.Warning,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setSnackBarQueue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const enqueueErrorSnackBar = useCallback(
|
||||||
|
({
|
||||||
|
apolloError,
|
||||||
|
message,
|
||||||
|
options,
|
||||||
|
}: (
|
||||||
|
| { apolloError: ApolloError; message?: never }
|
||||||
|
| { apolloError?: never; message?: string }
|
||||||
|
) & {
|
||||||
|
options?: Omit<SnackBarOptions, 'message' | 'id'>;
|
||||||
|
}) => {
|
||||||
|
const errorMessage = message
|
||||||
|
? message
|
||||||
|
: apolloError
|
||||||
|
? getErrorMessageFromApolloError(apolloError)
|
||||||
|
: t`An error occurred.`;
|
||||||
|
setSnackBarQueue({
|
||||||
|
id: uuidv4(),
|
||||||
|
message: errorMessage,
|
||||||
|
...options,
|
||||||
|
variant: SnackBarVariant.Error,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setSnackBarQueue],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleSnackBarClose,
|
||||||
|
enqueueSuccessSnackBar,
|
||||||
|
enqueueErrorSnackBar,
|
||||||
|
enqueueInfoSnackBar,
|
||||||
|
enqueueWarningSnackBar,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUr
|
|||||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
@ -20,6 +19,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
|||||||
import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MultiWorkspaceDropdownId';
|
import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MultiWorkspaceDropdownId';
|
||||||
import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/states/multiWorkspaceDropdownState';
|
import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/states/multiWorkspaceDropdownState';
|
||||||
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
|
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
@ -59,7 +59,7 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
|||||||
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
const { buildWorkspaceUrl } = useBuildWorkspaceUrl();
|
||||||
const { closeDropdown } = useCloseDropdown();
|
const { closeDropdown } = useCloseDropdown();
|
||||||
const { signOut } = useAuth();
|
const { signOut } = useAuth();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { colorScheme, colorSchemeList } = useColorScheme();
|
const { colorScheme, colorSchemeList } = useColorScheme();
|
||||||
|
|
||||||
const [signUpInNewWorkspaceMutation] = useSignUpInNewWorkspaceMutation();
|
const [signUpInNewWorkspaceMutation] = useSignUpInNewWorkspaceMutation();
|
||||||
@ -86,9 +86,9 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
|||||||
'_blank',
|
'_blank',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: ApolloError) => {
|
||||||
enqueueSnackBar(error.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { FormRawJsonFieldInput } from '@/object-record/record-field/form-types/components/FormRawJsonFieldInput';
|
import { FormRawJsonFieldInput } from '@/object-record/record-field/form-types/components/FormRawJsonFieldInput';
|
||||||
import { getFunctionOutputSchema } from '@/serverless-functions/utils/getFunctionOutputSchema';
|
import { getFunctionOutputSchema } from '@/serverless-functions/utils/getFunctionOutputSchema';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { Select } from '@/ui/input/components/Select';
|
||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
|
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
||||||
import { WorkflowWebhookTrigger } from '@/workflow/types/Workflow';
|
import { WorkflowWebhookTrigger } from '@/workflow/types/Workflow';
|
||||||
@ -24,7 +24,6 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { IconCopy, useIcons } from 'twenty-ui/display';
|
import { IconCopy, useIcons } from 'twenty-ui/display';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
|
||||||
|
|
||||||
type WorkflowEditTriggerWebhookFormProps = {
|
type WorkflowEditTriggerWebhookFormProps = {
|
||||||
trigger: WorkflowWebhookTrigger;
|
trigger: WorkflowWebhookTrigger;
|
||||||
@ -50,7 +49,7 @@ export const WorkflowEditTriggerWebhookForm = ({
|
|||||||
trigger,
|
trigger,
|
||||||
triggerOptions,
|
triggerOptions,
|
||||||
}: WorkflowEditTriggerWebhookFormProps) => {
|
}: WorkflowEditTriggerWebhookFormProps) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const [errorMessages, setErrorMessages] = useState<FormErrorMessages>({});
|
const [errorMessages, setErrorMessages] = useState<FormErrorMessages>({});
|
||||||
@ -75,9 +74,11 @@ export const WorkflowEditTriggerWebhookForm = ({
|
|||||||
|
|
||||||
const copyToClipboard = async () => {
|
const copyToClipboard = async () => {
|
||||||
await navigator.clipboard.writeText(webhookUrl);
|
await navigator.clipboard.writeText(webhookUrl);
|
||||||
enqueueSnackBar(t`Copied to clipboard!`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Copied to clipboard!`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { IconCopy, IconLink } from 'twenty-ui/display';
|
import { IconCopy, IconLink } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -29,7 +28,7 @@ export const WorkspaceInviteLink = ({
|
|||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer data-chromatic="ignore">
|
<StyledContainer data-chromatic="ignore">
|
||||||
@ -42,10 +41,12 @@ export const WorkspaceInviteLink = ({
|
|||||||
accent="blue"
|
accent="blue"
|
||||||
title={t`Copy link`}
|
title={t`Copy link`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
enqueueSnackBar(t`Link copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Link copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
navigator.clipboard.writeText(inviteLink);
|
navigator.clipboard.writeText(inviteLink);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { useEffect } from 'react';
|
|||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { sanitizeEmailList } from '@/workspace/utils/sanitizeEmailList';
|
import { sanitizeEmailList } from '@/workspace/utils/sanitizeEmailList';
|
||||||
@ -71,7 +70,7 @@ type FormInput = {
|
|||||||
export const WorkspaceInviteTeam = () => {
|
export const WorkspaceInviteTeam = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { sendInvitation } = useCreateWorkspaceInvitation();
|
const { sendInvitation } = useCreateWorkspaceInvitation();
|
||||||
|
|
||||||
const { reset, handleSubmit, control, formState, watch } = useForm<FormInput>(
|
const { reset, handleSubmit, control, formState, watch } = useForm<FormInput>(
|
||||||
@ -89,21 +88,19 @@ export const WorkspaceInviteTeam = () => {
|
|||||||
const emailsList = sanitizeEmailList(emails.split(','));
|
const emailsList = sanitizeEmailList(emails.split(','));
|
||||||
const { data } = await sendInvitation({ emails: emailsList });
|
const { data } = await sendInvitation({ emails: emailsList });
|
||||||
if (isDefined(data) && data.sendInvitations.result.length > 0) {
|
if (isDefined(data) && data.sendInvitations.result.length > 0) {
|
||||||
enqueueSnackBar(
|
enqueueSuccessSnackBar({
|
||||||
`${data.sendInvitations.result.length} invitations sent`,
|
message: `${data.sendInvitations.result.length} invitations sent`,
|
||||||
{
|
options: {
|
||||||
variant: SnackBarVariant.Success,
|
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDefined(data) && !data.sendInvitations.success) {
|
if (isDefined(data) && !data.sendInvitations.success) {
|
||||||
data.sendInvitations.errors.forEach((error) => {
|
enqueueErrorSnackBar({
|
||||||
enqueueSnackBar(error, {
|
options: {
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,10 +7,10 @@ import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState
|
|||||||
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
||||||
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
|
||||||
import { AppPath } from '@/types/AppPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
@ -78,7 +78,7 @@ const StyledMainButton = styled(MainButton)`
|
|||||||
|
|
||||||
export const PasswordReset = () => {
|
export const PasswordReset = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
const workspacePublicData = useRecoilValue(workspacePublicDataState);
|
||||||
|
|
||||||
@ -108,8 +108,8 @@ export const PasswordReset = () => {
|
|||||||
},
|
},
|
||||||
skip: !passwordResetToken || isTokenValid,
|
skip: !passwordResetToken || isTokenValid,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
enqueueSnackBar(error?.message ?? 'Token Invalid', {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
navigate(AppPath.Index);
|
navigate(AppPath.Index);
|
||||||
},
|
},
|
||||||
@ -137,15 +137,15 @@ export const PasswordReset = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!data?.updatePasswordViaResetToken.success) {
|
if (!data?.updatePasswordViaResetToken.success) {
|
||||||
enqueueSnackBar(t`There was an error while updating password.`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`There was an error while updating password.`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
enqueueSnackBar(t`Password has been updated`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Password has been updated`,
|
||||||
});
|
});
|
||||||
navigate(AppPath.Index);
|
navigate(AppPath.Index);
|
||||||
return;
|
return;
|
||||||
@ -161,14 +161,9 @@ export const PasswordReset = () => {
|
|||||||
navigate(AppPath.Index);
|
navigate(AppPath.Index);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logError(err);
|
logError(err);
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
err instanceof Error
|
apolloError: err instanceof ApolloError ? err : undefined,
|
||||||
? err.message
|
});
|
||||||
: t`An error occurred while updating password`,
|
|
||||||
{
|
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -15,12 +15,12 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
|||||||
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
||||||
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
|
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { H2Title } from 'twenty-ui/display';
|
import { H2Title } from 'twenty-ui/display';
|
||||||
@ -59,7 +59,7 @@ type Form = z.infer<typeof validationSchema>;
|
|||||||
export const CreateProfile = () => {
|
export const CreateProfile = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const setNextOnboardingStatus = useSetNextOnboardingStatus();
|
const setNextOnboardingStatus = useSetNextOnboardingStatus();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
|
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
|
||||||
currentWorkspaceMemberState,
|
currentWorkspaceMemberState,
|
||||||
);
|
);
|
||||||
@ -131,15 +131,15 @@ export const CreateProfile = () => {
|
|||||||
|
|
||||||
setNextOnboardingStatus();
|
setNextOnboardingStatus();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
enqueueSnackBar(error?.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
currentWorkspaceMember?.id,
|
currentWorkspaceMember?.id,
|
||||||
setNextOnboardingStatus,
|
setNextOnboardingStatus,
|
||||||
enqueueSnackBar,
|
enqueueErrorSnackBar,
|
||||||
setCurrentWorkspaceMember,
|
setCurrentWorkspaceMember,
|
||||||
setCurrentUser,
|
setCurrentUser,
|
||||||
updateOneRecord,
|
updateOneRecord,
|
||||||
|
|||||||
@ -13,10 +13,10 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
|||||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItem';
|
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItem';
|
||||||
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
||||||
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
@ -65,7 +65,7 @@ const StyledPendingCreationLoader = styled(motion.div)`
|
|||||||
|
|
||||||
export const CreateWorkspace = () => {
|
export const CreateWorkspace = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const setNextOnboardingStatus = useSetNextOnboardingStatus();
|
const setNextOnboardingStatus = useSetNextOnboardingStatus();
|
||||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||||
|
|
||||||
@ -127,14 +127,15 @@ export const CreateWorkspace = () => {
|
|||||||
setNextOnboardingStatus();
|
setNextOnboardingStatus();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setPendingCreationLoaderStep(PendingCreationLoaderStep.None);
|
setPendingCreationLoaderStep(PendingCreationLoaderStep.None);
|
||||||
enqueueSnackBar(error?.message, {
|
|
||||||
variant: SnackBarVariant.Error,
|
enqueueErrorSnackBar({
|
||||||
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
activateWorkspace,
|
activateWorkspace,
|
||||||
enqueueSnackBar,
|
enqueueErrorSnackBar,
|
||||||
loadCurrentUser,
|
loadCurrentUser,
|
||||||
refreshObjectMetadataItems,
|
refreshObjectMetadataItems,
|
||||||
setNextOnboardingStatus,
|
setNextOnboardingStatus,
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
|||||||
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
|
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
|
||||||
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
@ -65,7 +64,7 @@ type FormInput = z.infer<typeof validationSchema>;
|
|||||||
export const InviteTeam = () => {
|
export const InviteTeam = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const { sendInvitation } = useCreateWorkspaceInvitation();
|
const { sendInvitation } = useCreateWorkspaceInvitation();
|
||||||
const setNextOnboardingStatus = useSetNextOnboardingStatus();
|
const setNextOnboardingStatus = useSetNextOnboardingStatus();
|
||||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
@ -120,10 +119,12 @@ export const InviteTeam = () => {
|
|||||||
if (isDefined(currentWorkspace?.inviteHash)) {
|
if (isDefined(currentWorkspace?.inviteHash)) {
|
||||||
const inviteLink = `${window.location.origin}/invite/${currentWorkspace?.inviteHash}`;
|
const inviteLink = `${window.location.origin}/invite/${currentWorkspace?.inviteHash}`;
|
||||||
navigator.clipboard.writeText(inviteLink);
|
navigator.clipboard.writeText(inviteLink);
|
||||||
enqueueSnackBar(t`Link copied to clipboard`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Link copied to clipboard`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
duration: 2000,
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -143,15 +144,17 @@ export const InviteTeam = () => {
|
|||||||
throw result.errors;
|
throw result.errors;
|
||||||
}
|
}
|
||||||
if (emails.length > 0) {
|
if (emails.length > 0) {
|
||||||
enqueueSnackBar(t`Invite link sent to email addresses`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Invite link sent to email addresses`,
|
||||||
duration: 2000,
|
options: {
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setNextOnboardingStatus();
|
setNextOnboardingStatus();
|
||||||
},
|
},
|
||||||
[enqueueSnackBar, sendInvitation, setNextOnboardingStatus, t],
|
[enqueueSuccessSnackBar, sendInvitation, setNextOnboardingStatus, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSkip = async () => {
|
const handleSkip = async () => {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
|||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
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 { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||||
import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink';
|
import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink';
|
||||||
import { WorkspaceInviteTeam } from '@/workspace/components/WorkspaceInviteTeam';
|
import { WorkspaceInviteTeam } from '@/workspace/components/WorkspaceInviteTeam';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
@ -94,7 +94,7 @@ const StyledNoMembers = styled(TableCell)`
|
|||||||
|
|
||||||
export const SettingsWorkspaceMembers = () => {
|
export const SettingsWorkspaceMembers = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [workspaceMemberToDelete, setWorkspaceMemberToDelete] = useState<
|
const [workspaceMemberToDelete, setWorkspaceMemberToDelete] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
@ -127,9 +127,9 @@ export const SettingsWorkspaceMembers = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useGetWorkspaceInvitationsQuery({
|
useGetWorkspaceInvitationsQuery({
|
||||||
onError: (error: Error) => {
|
onError: (error: ApolloError) => {
|
||||||
enqueueSnackBar(error.message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
@ -140,9 +140,11 @@ export const SettingsWorkspaceMembers = () => {
|
|||||||
const handleRemoveWorkspaceInvitation = async (appTokenId: string) => {
|
const handleRemoveWorkspaceInvitation = async (appTokenId: string) => {
|
||||||
const result = await deleteWorkspaceInvitation({ appTokenId });
|
const result = await deleteWorkspaceInvitation({ appTokenId });
|
||||||
if (isDefined(result.errors)) {
|
if (isDefined(result.errors)) {
|
||||||
enqueueSnackBar(t`Error deleting invitation`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Error deleting invitation`,
|
||||||
duration: 2000,
|
options: {
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -150,9 +152,11 @@ export const SettingsWorkspaceMembers = () => {
|
|||||||
const handleResendWorkspaceInvitation = async (appTokenId: string) => {
|
const handleResendWorkspaceInvitation = async (appTokenId: string) => {
|
||||||
const result = await resendInvitation({ appTokenId });
|
const result = await resendInvitation({ appTokenId });
|
||||||
if (isDefined(result.errors)) {
|
if (isDefined(result.errors)) {
|
||||||
enqueueSnackBar(t`Error resending invitation`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Error resending invitation`,
|
||||||
duration: 2000,
|
options: {
|
||||||
|
duration: 2000,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,20 +11,20 @@ import {
|
|||||||
settingsDataModelObjectAboutFormSchema,
|
settingsDataModelObjectAboutFormSchema,
|
||||||
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
|
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useState } from 'react';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
|
||||||
import { H2Title } from 'twenty-ui/display';
|
import { H2Title } from 'twenty-ui/display';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
import { useState } from 'react';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
export const SettingsNewObject = () => {
|
export const SettingsNewObject = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const navigate = useNavigateSettings();
|
const navigate = useNavigateSettings();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem();
|
const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem();
|
||||||
|
|
||||||
@ -56,8 +56,8 @@ export const SettingsNewObject = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(error);
|
console.error(error);
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@ -23,9 +23,9 @@ import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/vali
|
|||||||
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { H2Title, IconArchive, IconArchiveOff } from 'twenty-ui/display';
|
import { H2Title, IconArchive, IconArchiveOff } from 'twenty-ui/display';
|
||||||
@ -47,7 +47,7 @@ export const SettingsObjectFieldEdit = () => {
|
|||||||
const navigateApp = useNavigateApp();
|
const navigateApp = useNavigateApp();
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { objectNamePlural = '', fieldName = '' } = useParams();
|
const { objectNamePlural = '', fieldName = '' } = useParams();
|
||||||
const { findObjectMetadataItemByNamePlural } =
|
const { findObjectMetadataItemByNamePlural } =
|
||||||
@ -147,8 +147,8 @@ export const SettingsObjectFieldEdit = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/vali
|
|||||||
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { View } from '@/views/types/View';
|
import { View } from '@/views/types/View';
|
||||||
@ -51,7 +50,7 @@ export const SettingsObjectNewFieldConfigure = () => {
|
|||||||
const fieldType =
|
const fieldType =
|
||||||
(searchParams.get('fieldType') as SettingsFieldType) ||
|
(searchParams.get('fieldType') as SettingsFieldType) ||
|
||||||
FieldMetadataType.TEXT;
|
FieldMetadataType.TEXT;
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { findActiveObjectMetadataItemByNamePlural } =
|
const { findActiveObjectMetadataItemByNamePlural } =
|
||||||
useFilteredObjectMetadataItems();
|
useFilteredObjectMetadataItems();
|
||||||
@ -161,14 +160,11 @@ export const SettingsObjectNewFieldConfigure = () => {
|
|||||||
'duplicate key value violates unique constraint "IndexOnNameObjectMetadataIdAndWorkspaceIdUnique"',
|
'duplicate key value violates unique constraint "IndexOnNameObjectMetadataIdAndWorkspaceIdUnique"',
|
||||||
);
|
);
|
||||||
|
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
isDuplicateFieldNameInObject
|
message: isDuplicateFieldNameInObject
|
||||||
? t`Please use different names for your source and destination fields`
|
? t`Please use different names for your source and destination fields`
|
||||||
: (error as Error).message,
|
: undefined,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!activeObjectMetadataItem) return null;
|
if (!activeObjectMetadataItem) return null;
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
|
|||||||
import { computeNewExpirationDate } from '@/settings/developers/utils/computeNewExpirationDate';
|
import { computeNewExpirationDate } from '@/settings/developers/utils/computeNewExpirationDate';
|
||||||
import { formatExpiration } from '@/settings/developers/utils/formatExpiration';
|
import { formatExpiration } from '@/settings/developers/utils/formatExpiration';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
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 = () => {
|
export const SettingsDevelopersApiKeyDetail = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
@ -97,9 +96,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
|||||||
navigate(SettingsPath.APIs);
|
navigate(SettingsPath.APIs);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
enqueueSnackBar(t`Error deleting api key: ${err}`, {
|
enqueueErrorSnackBar({ message: t`Error deleting api key.` });
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -149,8 +146,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
enqueueSnackBar(t`Error regenerating api key: ${err}`, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Error regenerating api key.`,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@ -17,15 +17,15 @@ import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/u
|
|||||||
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
|
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
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 { CreateRemoteServerInput } from '~/generated-metadata/graphql';
|
||||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
import { H2Title } from 'twenty-ui/display';
|
|
||||||
import { Section } from 'twenty-ui/layout';
|
|
||||||
|
|
||||||
const createRemoteServerInputPostgresSchema =
|
const createRemoteServerInputPostgresSchema =
|
||||||
settingsIntegrationPostgreSQLConnectionFormSchema.transform<CreateRemoteServerInput>(
|
settingsIntegrationPostgreSQLConnectionFormSchema.transform<CreateRemoteServerInput>(
|
||||||
@ -79,7 +79,7 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { createOneDatabaseConnection } = useCreateOneDatabaseConnection();
|
const { createOneDatabaseConnection } = useCreateOneDatabaseConnection();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const isIntegrationEnabled = useIsSettingsIntegrationEnabled(databaseKey);
|
const isIntegrationEnabled = useIsSettingsIntegrationEnabled(databaseKey);
|
||||||
|
|
||||||
@ -131,8 +131,8 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
|
|||||||
connectionId,
|
connectionId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
@ -20,7 +20,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
|||||||
|
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const [createApprovedAccessDomain] = useCreateApprovedAccessDomainMutation();
|
const [createApprovedAccessDomain] = useCreateApprovedAccessDomainMutation();
|
||||||
|
|
||||||
@ -62,20 +62,20 @@ export const SettingsSecurityApprovedAccessDomain = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
enqueueSnackBar(t`Please check your email for a verification link.`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Please check your email for a verification link.`,
|
||||||
});
|
});
|
||||||
navigate(SettingsPath.Security);
|
navigate(SettingsPath.Security);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,21 +7,21 @@ import { SettingSecurityNewSSOIdentityFormValues } from '@/settings/security/typ
|
|||||||
import { sSOIdentityProviderDefaultValues } from '@/settings/security/utils/sSOIdentityProviderDefaultValues';
|
import { sSOIdentityProviderDefaultValues } from '@/settings/security/utils/sSOIdentityProviderDefaultValues';
|
||||||
import { SSOIdentitiesProvidersParamsSchema } from '@/settings/security/validation-schemas/SSOIdentityProviderSchema';
|
import { SSOIdentitiesProvidersParamsSchema } from '@/settings/security/validation-schemas/SSOIdentityProviderSchema';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
import { t } from '@lingui/core/macro';
|
|
||||||
|
|
||||||
export const SettingsSecuritySSOIdentifyProvider = () => {
|
export const SettingsSecuritySSOIdentifyProvider = () => {
|
||||||
const navigate = useNavigateSettings();
|
const navigate = useNavigateSettings();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { createSSOIdentityProvider } = useCreateSSOIdentityProvider();
|
const { createSSOIdentityProvider } = useCreateSSOIdentityProvider();
|
||||||
|
|
||||||
const form = useForm<SettingSecurityNewSSOIdentityFormValues>({
|
const form = useForm<SettingSecurityNewSSOIdentityFormValues>({
|
||||||
@ -48,8 +48,8 @@ export const SettingsSecuritySSOIdentifyProvider = () => {
|
|||||||
|
|
||||||
navigate(SettingsPath.Security);
|
navigate(SettingsPath.Security);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error instanceof ApolloError ? error : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,17 +8,18 @@ import { usePublishOneServerlessFunction } from '@/settings/serverless-functions
|
|||||||
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||||
import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction';
|
import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconCode, IconSettings, IconTestPipe } from 'twenty-ui/display';
|
import { IconCode, IconSettings, IconTestPipe } from 'twenty-ui/display';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
import { getErrorMessageFromApolloError } from '~/utils/get-error-message-from-apollo-error.util';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ const SERVERLESS_FUNCTION_DETAIL_ID = 'serverless-function-detail';
|
|||||||
|
|
||||||
export const SettingsServerlessFunctionDetail = () => {
|
export const SettingsServerlessFunctionDetail = () => {
|
||||||
const { serverlessFunctionId = '' } = useParams();
|
const { serverlessFunctionId = '' } = useParams();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||||
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
||||||
activeTabIdComponentState,
|
activeTabIdComponentState,
|
||||||
SERVERLESS_FUNCTION_DETAIL_ID,
|
SERVERLESS_FUNCTION_DETAIL_ID,
|
||||||
@ -88,12 +89,9 @@ export const SettingsServerlessFunctionDetail = () => {
|
|||||||
}));
|
}));
|
||||||
await handleSave();
|
await handleSave();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
(err as Error)?.message || 'An error occurred while reset function',
|
apolloError: err instanceof ApolloError ? err : undefined,
|
||||||
{
|
});
|
||||||
variant: SnackBarVariant.Error,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,17 +100,16 @@ export const SettingsServerlessFunctionDetail = () => {
|
|||||||
await publishOneServerlessFunction({
|
await publishOneServerlessFunction({
|
||||||
id: serverlessFunctionId,
|
id: serverlessFunctionId,
|
||||||
});
|
});
|
||||||
enqueueSnackBar(`New function version has been published`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: `New function version has been published`,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
enqueueSnackBar(
|
enqueueErrorSnackBar({
|
||||||
(err as Error)?.message ||
|
message:
|
||||||
'An error occurred while publishing new version',
|
err instanceof ApolloError
|
||||||
{
|
? getErrorMessageFromApolloError(err)
|
||||||
variant: SnackBarVariant.Error,
|
: 'An error occurred while publishing new version',
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { Table } from '@/ui/layout/table/components/Table';
|
import { Table } from '@/ui/layout/table/components/Table';
|
||||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { IconCopy } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import { CustomDomainValidRecords } from '~/generated/graphql';
|
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)`
|
const StyledTable = styled(Table)`
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
@ -46,7 +45,7 @@ export const SettingsCustomDomainRecords = ({
|
|||||||
}: {
|
}: {
|
||||||
records: CustomDomainValidRecords['records'];
|
records: CustomDomainValidRecords['records'];
|
||||||
}) => {
|
}) => {
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -54,9 +53,11 @@ export const SettingsCustomDomainRecords = ({
|
|||||||
|
|
||||||
const copyToClipboard = (value: string) => {
|
const copyToClipboard = (value: string) => {
|
||||||
navigator.clipboard.writeText(value);
|
navigator.clipboard.writeText(value);
|
||||||
enqueueSnackBar(t`Copied to clipboard!`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Copied to clipboard!`,
|
||||||
icon: <IconCopy size={theme.icon.size.md} />,
|
options: {
|
||||||
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirect
|
|||||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||||
@ -60,7 +59,7 @@ export const SettingsDomain = () => {
|
|||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
const [updateWorkspace] = useUpdateWorkspaceMutation();
|
||||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||||
|
|
||||||
@ -105,11 +104,11 @@ export const SettingsDomain = () => {
|
|||||||
customDomain:
|
customDomain:
|
||||||
customDomain && customDomain.length > 0 ? customDomain : null,
|
customDomain && customDomain.length > 0 ? customDomain : null,
|
||||||
});
|
});
|
||||||
enqueueSnackBar(t`Custom domain updated`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Custom domain updated`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error: ApolloError) => {
|
||||||
if (
|
if (
|
||||||
error instanceof ApolloError &&
|
error instanceof ApolloError &&
|
||||||
error.graphQLErrors[0]?.extensions?.code === 'CONFLICT'
|
error.graphQLErrors[0]?.extensions?.code === 'CONFLICT'
|
||||||
@ -119,8 +118,8 @@ export const SettingsDomain = () => {
|
|||||||
message: t`Subdomain already taken`,
|
message: t`Subdomain already taken`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -136,7 +135,7 @@ export const SettingsDomain = () => {
|
|||||||
subdomain,
|
subdomain,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error: ApolloError) => {
|
||||||
if (
|
if (
|
||||||
error instanceof ApolloError &&
|
error instanceof ApolloError &&
|
||||||
error.graphQLErrors[0]?.extensions?.code === 'CONFLICT'
|
error.graphQLErrors[0]?.extensions?.code === 'CONFLICT'
|
||||||
@ -147,8 +146,8 @@ export const SettingsDomain = () => {
|
|||||||
message: t`Subdomain already taken`,
|
message: t`Subdomain already taken`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
enqueueSnackBar((error as Error).message, {
|
enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
apolloError: error,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onCompleted: async () => {
|
onCompleted: async () => {
|
||||||
@ -163,8 +162,8 @@ export const SettingsDomain = () => {
|
|||||||
subdomain,
|
subdomain,
|
||||||
});
|
});
|
||||||
|
|
||||||
enqueueSnackBar(t`Subdomain updated`, {
|
enqueueSuccessSnackBar({
|
||||||
variant: SnackBarVariant.Success,
|
message: t`Subdomain updated`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await redirectToWorkspaceDomain(currentUrl.toString());
|
await redirectToWorkspaceDomain(currentUrl.toString());
|
||||||
@ -179,14 +178,14 @@ export const SettingsDomain = () => {
|
|||||||
subdomainValue === currentWorkspace?.subdomain &&
|
subdomainValue === currentWorkspace?.subdomain &&
|
||||||
customDomainValue === currentWorkspace?.customDomain
|
customDomainValue === currentWorkspace?.customDomain
|
||||||
) {
|
) {
|
||||||
return enqueueSnackBar(t`No change detected`, {
|
return enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`No change detected`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values || !currentWorkspace) {
|
if (!values || !currentWorkspace) {
|
||||||
return enqueueSnackBar(t`Invalid form values`, {
|
return enqueueErrorSnackBar({
|
||||||
variant: SnackBarVariant.Error,
|
message: t`Invalid form values`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { ApolloError } from '@apollo/client';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const getErrorMessageFromApolloError = (error: ApolloError): string => {
|
||||||
|
if (!isDefined(error.graphQLErrors?.[0]?.extensions?.userFriendlyMessage)) {
|
||||||
|
return t`An error occurred.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(error.graphQLErrors[0].extensions?.userFriendlyMessage as string) ??
|
||||||
|
t`An error occurred.`
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -17,7 +17,11 @@ export class ApprovedAccessDomainExceptionFilter implements ExceptionFilter {
|
|||||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_VALIDATION_TOKEN_INVALID:
|
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_VALIDATION_TOKEN_INVALID:
|
||||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED:
|
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED:
|
||||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN:
|
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN:
|
||||||
throw new ForbiddenError(exception.message);
|
throw new ForbiddenError(exception.message, {
|
||||||
|
extensions: {
|
||||||
|
userFriendlyMessage: exception.userFriendlyMessage,
|
||||||
|
},
|
||||||
|
});
|
||||||
default: {
|
default: {
|
||||||
const _exhaustiveCheck: never = exception.code;
|
const _exhaustiveCheck: never = exception.code;
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,12 @@ import { CustomException } from 'src/utils/custom-exception';
|
|||||||
|
|
||||||
export class ApprovedAccessDomainException extends CustomException {
|
export class ApprovedAccessDomainException extends CustomException {
|
||||||
declare code: ApprovedAccessDomainExceptionCode;
|
declare code: ApprovedAccessDomainExceptionCode;
|
||||||
constructor(message: string, code: ApprovedAccessDomainExceptionCode) {
|
constructor(
|
||||||
super(message, code);
|
message: string,
|
||||||
|
code: ApprovedAccessDomainExceptionCode,
|
||||||
|
{ userFriendlyMessage }: { userFriendlyMessage?: string } = {},
|
||||||
|
) {
|
||||||
|
super(message, code, userFriendlyMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { render } from '@react-email/render';
|
import { render } from '@react-email/render';
|
||||||
import { SendApprovedAccessDomainValidation } from 'twenty-emails';
|
import { SendApprovedAccessDomainValidation } from 'twenty-emails';
|
||||||
import { APP_LOCALES } from 'twenty-shared/translations';
|
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||||
@ -18,8 +19,8 @@ import { DomainManagerService } from 'src/engine/core-modules/domain-manager/ser
|
|||||||
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
import { EmailService } from 'src/engine/core-modules/email/email.service';
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { isWorkDomain } from 'src/utils/is-work-email';
|
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
import { isWorkDomain } from 'src/utils/is-work-email';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||||
@ -42,6 +43,9 @@ export class ApprovedAccessDomainService {
|
|||||||
throw new ApprovedAccessDomainException(
|
throw new ApprovedAccessDomainException(
|
||||||
'Approved access domain has already been validated',
|
'Approved access domain has already been validated',
|
||||||
ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VERIFIED,
|
ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VERIFIED,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Approved access domain has already been validated`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +53,9 @@ export class ApprovedAccessDomainService {
|
|||||||
throw new ApprovedAccessDomainException(
|
throw new ApprovedAccessDomainException(
|
||||||
'Approved access domain does not match email domain',
|
'Approved access domain does not match email domain',
|
||||||
ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_DOES_NOT_MATCH_DOMAIN_EMAIL,
|
ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_DOES_NOT_MATCH_DOMAIN_EMAIL,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Approved access domain does not match email domain`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +125,9 @@ export class ApprovedAccessDomainService {
|
|||||||
throw new ApprovedAccessDomainException(
|
throw new ApprovedAccessDomainException(
|
||||||
'Approved access domain has already been validated',
|
'Approved access domain has already been validated',
|
||||||
ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED,
|
ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Approved access domain has already been validated`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,12 @@ import { CustomException } from 'src/utils/custom-exception';
|
|||||||
|
|
||||||
export class AuthException extends CustomException {
|
export class AuthException extends CustomException {
|
||||||
declare code: AuthExceptionCode;
|
declare code: AuthExceptionCode;
|
||||||
constructor(message: string, code: AuthExceptionCode) {
|
constructor(
|
||||||
super(message, code);
|
message: string,
|
||||||
|
code: AuthExceptionCode,
|
||||||
|
{ userFriendlyMessage }: { userFriendlyMessage?: string } = {},
|
||||||
|
) {
|
||||||
|
super(message, code, userFriendlyMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Catch, ExceptionFilter } from '@nestjs/common';
|
import { Catch, ExceptionFilter } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthException,
|
AuthException,
|
||||||
AuthExceptionCode,
|
AuthExceptionCode,
|
||||||
@ -16,26 +18,39 @@ export class AuthGraphqlApiExceptionFilter implements ExceptionFilter {
|
|||||||
catch(exception: AuthException) {
|
catch(exception: AuthException) {
|
||||||
switch (exception.code) {
|
switch (exception.code) {
|
||||||
case AuthExceptionCode.CLIENT_NOT_FOUND:
|
case AuthExceptionCode.CLIENT_NOT_FOUND:
|
||||||
throw new NotFoundError(exception.message);
|
throw new NotFoundError(exception.message, {
|
||||||
|
userFriendlyMessage: exception.userFriendlyMessage,
|
||||||
|
});
|
||||||
case AuthExceptionCode.INVALID_INPUT:
|
case AuthExceptionCode.INVALID_INPUT:
|
||||||
throw new UserInputError(exception.message);
|
throw new UserInputError(exception.message, {
|
||||||
|
userFriendlyMessage: exception.userFriendlyMessage,
|
||||||
|
});
|
||||||
case AuthExceptionCode.FORBIDDEN_EXCEPTION:
|
case AuthExceptionCode.FORBIDDEN_EXCEPTION:
|
||||||
case AuthExceptionCode.INSUFFICIENT_SCOPES:
|
case AuthExceptionCode.INSUFFICIENT_SCOPES:
|
||||||
case AuthExceptionCode.OAUTH_ACCESS_DENIED:
|
case AuthExceptionCode.OAUTH_ACCESS_DENIED:
|
||||||
case AuthExceptionCode.SSO_AUTH_FAILED:
|
case AuthExceptionCode.SSO_AUTH_FAILED:
|
||||||
case AuthExceptionCode.USE_SSO_AUTH:
|
case AuthExceptionCode.USE_SSO_AUTH:
|
||||||
case AuthExceptionCode.SIGNUP_DISABLED:
|
case AuthExceptionCode.SIGNUP_DISABLED:
|
||||||
case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED:
|
|
||||||
case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED:
|
|
||||||
case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE:
|
case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE:
|
||||||
case AuthExceptionCode.INVALID_JWT_TOKEN_TYPE:
|
case AuthExceptionCode.INVALID_JWT_TOKEN_TYPE:
|
||||||
throw new ForbiddenError(exception.message);
|
throw new ForbiddenError(exception.message, {
|
||||||
|
userFriendlyMessage: exception.userFriendlyMessage,
|
||||||
|
});
|
||||||
|
case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED:
|
||||||
|
case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED:
|
||||||
|
throw new ForbiddenError(exception.message, {
|
||||||
|
userFriendlyMessage: t`Authentication is not enabled with this provider.`,
|
||||||
|
});
|
||||||
case AuthExceptionCode.EMAIL_NOT_VERIFIED:
|
case AuthExceptionCode.EMAIL_NOT_VERIFIED:
|
||||||
case AuthExceptionCode.INVALID_DATA:
|
case AuthExceptionCode.INVALID_DATA:
|
||||||
throw new ForbiddenError(exception.message, {
|
throw new ForbiddenError(exception.message, {
|
||||||
subCode: AuthExceptionCode.EMAIL_NOT_VERIFIED,
|
subCode: AuthExceptionCode.EMAIL_NOT_VERIFIED,
|
||||||
|
userFriendlyMessage: t`Email is not verified.`,
|
||||||
});
|
});
|
||||||
case AuthExceptionCode.UNAUTHENTICATED:
|
case AuthExceptionCode.UNAUTHENTICATED:
|
||||||
|
throw new AuthenticationError(exception.message, {
|
||||||
|
userFriendlyMessage: t`You must be authenticated to perform this action.`,
|
||||||
|
});
|
||||||
case AuthExceptionCode.USER_NOT_FOUND:
|
case AuthExceptionCode.USER_NOT_FOUND:
|
||||||
case AuthExceptionCode.WORKSPACE_NOT_FOUND:
|
case AuthExceptionCode.WORKSPACE_NOT_FOUND:
|
||||||
throw new AuthenticationError(exception.message);
|
throw new AuthenticationError(exception.message);
|
||||||
|
|||||||
@ -169,6 +169,9 @@ export class AuthService {
|
|||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
'Wrong password',
|
'Wrong password',
|
||||||
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Wrong password`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { HttpService } from '@nestjs/axios';
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { TWENTY_ICONS_BASE_URL } from 'twenty-shared/constants';
|
import { TWENTY_ICONS_BASE_URL } from 'twenty-shared/constants';
|
||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
@ -67,6 +68,9 @@ export class SignInUpService {
|
|||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
'Email is required',
|
'Email is required',
|
||||||
AuthExceptionCode.INVALID_INPUT,
|
AuthExceptionCode.INVALID_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Email is required`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +115,9 @@ export class SignInUpService {
|
|||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
'Password too weak',
|
'Password too weak',
|
||||||
AuthExceptionCode.INVALID_INPUT,
|
AuthExceptionCode.INVALID_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Password too weak`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,6 +137,9 @@ export class SignInUpService {
|
|||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
'Wrong password',
|
'Wrong password',
|
||||||
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Wrong password`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,6 +163,9 @@ export class SignInUpService {
|
|||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
'Email is required',
|
'Email is required',
|
||||||
AuthExceptionCode.INVALID_INPUT,
|
AuthExceptionCode.INVALID_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Email is required`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +207,9 @@ export class SignInUpService {
|
|||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
'Workspace is not ready to welcome new members',
|
'Workspace is not ready to welcome new members',
|
||||||
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Workspace is not ready to welcome new members`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +223,9 @@ export class SignInUpService {
|
|||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
'User is not part of the workspace',
|
'User is not part of the workspace',
|
||||||
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`User is not part of the workspace`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -340,6 +359,9 @@ export class SignInUpService {
|
|||||||
throw new AuthException(
|
throw new AuthException(
|
||||||
'Email is required',
|
'Email is required',
|
||||||
AuthExceptionCode.INVALID_INPUT,
|
AuthExceptionCode.INVALID_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Email is required`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Catch, ExceptionFilter } from '@nestjs/common';
|
import { Catch, ExceptionFilter } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EmailVerificationException,
|
EmailVerificationException,
|
||||||
EmailVerificationExceptionCode,
|
EmailVerificationExceptionCode,
|
||||||
@ -13,17 +15,32 @@ import {
|
|||||||
export class EmailVerificationExceptionFilter implements ExceptionFilter {
|
export class EmailVerificationExceptionFilter implements ExceptionFilter {
|
||||||
catch(exception: EmailVerificationException) {
|
catch(exception: EmailVerificationException) {
|
||||||
switch (exception.code) {
|
switch (exception.code) {
|
||||||
|
case EmailVerificationExceptionCode.TOKEN_EXPIRED:
|
||||||
|
throw new ForbiddenError(exception.message, {
|
||||||
|
subCode: exception.code,
|
||||||
|
userFriendlyMessage: t`Request has expired, please try again.`,
|
||||||
|
});
|
||||||
case EmailVerificationExceptionCode.INVALID_TOKEN:
|
case EmailVerificationExceptionCode.INVALID_TOKEN:
|
||||||
case EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE:
|
case EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE:
|
||||||
case EmailVerificationExceptionCode.TOKEN_EXPIRED:
|
|
||||||
case EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED:
|
case EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED:
|
||||||
throw new ForbiddenError(exception.message, {
|
throw new ForbiddenError(exception.message, {
|
||||||
subCode: exception.code,
|
subCode: exception.code,
|
||||||
});
|
});
|
||||||
case EmailVerificationExceptionCode.EMAIL_MISSING:
|
case EmailVerificationExceptionCode.EMAIL_MISSING:
|
||||||
|
throw new UserInputError(exception.message, {
|
||||||
|
subCode: exception.code,
|
||||||
|
});
|
||||||
case EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED:
|
case EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED:
|
||||||
case EmailVerificationExceptionCode.INVALID_EMAIL:
|
throw new UserInputError(exception.message, {
|
||||||
|
subCode: exception.code,
|
||||||
|
userFriendlyMessage: t`Email already verified.`,
|
||||||
|
});
|
||||||
case EmailVerificationExceptionCode.EMAIL_VERIFICATION_NOT_REQUIRED:
|
case EmailVerificationExceptionCode.EMAIL_VERIFICATION_NOT_REQUIRED:
|
||||||
|
throw new UserInputError(exception.message, {
|
||||||
|
subCode: exception.code,
|
||||||
|
userFriendlyMessage: t`Email verification not required.`,
|
||||||
|
});
|
||||||
|
case EmailVerificationExceptionCode.INVALID_EMAIL:
|
||||||
throw new UserInputError(exception.message, {
|
throw new UserInputError(exception.message, {
|
||||||
subCode: exception.code,
|
subCode: exception.code,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
OnExecuteDoneHookResultOnNextHook,
|
OnExecuteDoneHookResultOnNextHook,
|
||||||
Plugin,
|
Plugin,
|
||||||
} from '@envelop/core';
|
} from '@envelop/core';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { GraphQLError, Kind, OperationDefinitionNode, print } from 'graphql';
|
import { GraphQLError, Kind, OperationDefinitionNode, print } from 'graphql';
|
||||||
|
|
||||||
import { GraphQLContext } from 'src/engine/api/graphql/graphql-config/interfaces/graphql-context.interface';
|
import { GraphQLContext } from 'src/engine/api/graphql/graphql-config/interfaces/graphql-context.interface';
|
||||||
@ -24,8 +25,7 @@ import {
|
|||||||
|
|
||||||
const DEFAULT_EVENT_ID_KEY = 'exceptionEventId';
|
const DEFAULT_EVENT_ID_KEY = 'exceptionEventId';
|
||||||
const SCHEMA_VERSION_HEADER = 'x-schema-version';
|
const SCHEMA_VERSION_HEADER = 'x-schema-version';
|
||||||
const SCHEMA_MISMATCH_ERROR =
|
const SCHEMA_MISMATCH_ERROR = 'Schema version mismatch.';
|
||||||
'Your workspace has been updated with a new data model. Please refresh the page.';
|
|
||||||
|
|
||||||
type GraphQLErrorHandlerHookOptions = {
|
type GraphQLErrorHandlerHookOptions = {
|
||||||
metricsService: MetricsService;
|
metricsService: MetricsService;
|
||||||
@ -191,11 +191,22 @@ export const useGraphQLErrorHandlerHook = <
|
|||||||
const transformedErrors = processedErrors.map((error) => {
|
const transformedErrors = processedErrors.map((error) => {
|
||||||
const graphqlError =
|
const graphqlError =
|
||||||
error instanceof BaseGraphQLError
|
error instanceof BaseGraphQLError
|
||||||
? error
|
? {
|
||||||
|
...error,
|
||||||
|
extensions: {
|
||||||
|
...error.extensions,
|
||||||
|
userFriendlyMessage:
|
||||||
|
error.extensions.userFriendlyMessage ??
|
||||||
|
t`An error occurred.`,
|
||||||
|
},
|
||||||
|
}
|
||||||
: generateGraphQLErrorFromError(error);
|
: generateGraphQLErrorFromError(error);
|
||||||
|
|
||||||
if (error.eventId && eventIdKey) {
|
if (error.eventId && eventIdKey) {
|
||||||
graphqlError.extensions[eventIdKey] = error.eventId;
|
graphqlError.extensions = {
|
||||||
|
...graphqlError.extensions,
|
||||||
|
[eventIdKey]: error.eventId,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return graphqlError;
|
return graphqlError;
|
||||||
@ -224,7 +235,11 @@ export const useGraphQLErrorHandlerHook = <
|
|||||||
requestMetadataVersion &&
|
requestMetadataVersion &&
|
||||||
requestMetadataVersion !== `${currentMetadataVersion}`
|
requestMetadataVersion !== `${currentMetadataVersion}`
|
||||||
) {
|
) {
|
||||||
throw new GraphQLError(SCHEMA_MISMATCH_ERROR);
|
throw new GraphQLError(SCHEMA_MISMATCH_ERROR, {
|
||||||
|
extensions: {
|
||||||
|
userFriendlyMessage: t`Your workspace has been updated with a new data model. Please refresh the page.`,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,13 +1,25 @@
|
|||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BaseGraphQLError,
|
BaseGraphQLError,
|
||||||
ErrorCode,
|
ErrorCode,
|
||||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
export const generateGraphQLErrorFromError = (error: Error) => {
|
export const generateGraphQLErrorFromError = (
|
||||||
|
error: Error | CustomException,
|
||||||
|
) => {
|
||||||
const graphqlError = new BaseGraphQLError(
|
const graphqlError = new BaseGraphQLError(
|
||||||
error.message,
|
error.message,
|
||||||
ErrorCode.INTERNAL_SERVER_ERROR,
|
ErrorCode.INTERNAL_SERVER_ERROR,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (error instanceof CustomException) {
|
||||||
|
graphqlError.extensions.userFriendlyMessage =
|
||||||
|
error.userFriendlyMessage ?? t`An error occurred.`;
|
||||||
|
} else {
|
||||||
|
graphqlError.extensions.userFriendlyMessage = t`An error occurred.`;
|
||||||
|
}
|
||||||
|
|
||||||
return graphqlError;
|
return graphqlError;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -159,8 +159,9 @@ export class UserInputError extends BaseGraphQLError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class NotFoundError extends BaseGraphQLError {
|
export class NotFoundError extends BaseGraphQLError {
|
||||||
constructor(message: string) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
super(message, ErrorCode.NOT_FOUND);
|
constructor(message: string, extensions?: Record<string, any>) {
|
||||||
|
super(message, ErrorCode.NOT_FOUND, extensions);
|
||||||
|
|
||||||
Object.defineProperty(this, 'name', { value: 'NotFoundError' });
|
Object.defineProperty(this, 'name', { value: 'NotFoundError' });
|
||||||
}
|
}
|
||||||
@ -175,8 +176,9 @@ export class MethodNotAllowedError extends BaseGraphQLError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ConflictError extends BaseGraphQLError {
|
export class ConflictError extends BaseGraphQLError {
|
||||||
constructor(message: string) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
super(message, ErrorCode.CONFLICT);
|
constructor(message: string, extensions?: Record<string, any>) {
|
||||||
|
super(message, ErrorCode.CONFLICT, extensions);
|
||||||
|
|
||||||
Object.defineProperty(this, 'name', { value: 'ConflictError' });
|
Object.defineProperty(this, 'name', { value: 'ConflictError' });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,12 @@ import { CustomException } from 'src/utils/custom-exception';
|
|||||||
|
|
||||||
export class RecordTransformerException extends CustomException {
|
export class RecordTransformerException extends CustomException {
|
||||||
declare code: RecordTransformerExceptionCode;
|
declare code: RecordTransformerExceptionCode;
|
||||||
constructor(message: string, code: RecordTransformerExceptionCode) {
|
constructor(
|
||||||
super(message, code);
|
message: string,
|
||||||
|
code: RecordTransformerExceptionCode,
|
||||||
|
userFriendlyMessage?: string,
|
||||||
|
) {
|
||||||
|
super(message, code, userFriendlyMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,9 @@ export const recordTransformerGraphqlApiExceptionHandler = (
|
|||||||
case RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE:
|
case RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE:
|
||||||
case RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE:
|
case RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE:
|
||||||
case RecordTransformerExceptionCode.INVALID_URL:
|
case RecordTransformerExceptionCode.INVALID_URL:
|
||||||
throw new UserInputError(error.message);
|
throw new UserInputError(error.message, {
|
||||||
|
userFriendlyMessage: error.userFriendlyMessage,
|
||||||
|
});
|
||||||
default: {
|
default: {
|
||||||
assertUnreachable(error.code);
|
assertUnreachable(error.code);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import {
|
import {
|
||||||
CountryCallingCode,
|
CountryCallingCode,
|
||||||
@ -61,6 +62,7 @@ const validatePrimaryPhoneCountryCodeAndCallingCode = ({
|
|||||||
throw new RecordTransformerException(
|
throw new RecordTransformerException(
|
||||||
`Invalid country code ${countryCode}`,
|
`Invalid country code ${countryCode}`,
|
||||||
RecordTransformerExceptionCode.INVALID_PHONE_COUNTRY_CODE,
|
RecordTransformerExceptionCode.INVALID_PHONE_COUNTRY_CODE,
|
||||||
|
t`Invalid country code ${countryCode}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +76,7 @@ const validatePrimaryPhoneCountryCodeAndCallingCode = ({
|
|||||||
throw new RecordTransformerException(
|
throw new RecordTransformerException(
|
||||||
`Invalid calling code ${callingCode}`,
|
`Invalid calling code ${callingCode}`,
|
||||||
RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE,
|
RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE,
|
||||||
|
t`Invalid calling code ${callingCode}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +89,7 @@ const validatePrimaryPhoneCountryCodeAndCallingCode = ({
|
|||||||
throw new RecordTransformerException(
|
throw new RecordTransformerException(
|
||||||
`Provided country code and calling code are conflicting`,
|
`Provided country code and calling code are conflicting`,
|
||||||
RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE,
|
RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE,
|
||||||
|
t`Provided country code and calling code are conflicting`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -106,6 +110,7 @@ const parsePhoneNumberExceptionWrapper = ({
|
|||||||
throw new RecordTransformerException(
|
throw new RecordTransformerException(
|
||||||
`Provided phone number is invalid ${number}`,
|
`Provided phone number is invalid ${number}`,
|
||||||
RecordTransformerExceptionCode.INVALID_PHONE_NUMBER,
|
RecordTransformerExceptionCode.INVALID_PHONE_NUMBER,
|
||||||
|
t`Provided phone number is invalid ${number}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -129,6 +134,7 @@ const validateAndInferMetadataFromPrimaryPhoneNumber = ({
|
|||||||
throw new RecordTransformerException(
|
throw new RecordTransformerException(
|
||||||
'Provided and inferred country code are conflicting',
|
'Provided and inferred country code are conflicting',
|
||||||
RecordTransformerExceptionCode.CONFLICTING_PHONE_COUNTRY_CODE,
|
RecordTransformerExceptionCode.CONFLICTING_PHONE_COUNTRY_CODE,
|
||||||
|
t`Provided and inferred country code are conflicting`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +146,7 @@ const validateAndInferMetadataFromPrimaryPhoneNumber = ({
|
|||||||
throw new RecordTransformerException(
|
throw new RecordTransformerException(
|
||||||
'Provided and inferred calling code are conflicting',
|
'Provided and inferred calling code are conflicting',
|
||||||
RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE,
|
RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE,
|
||||||
|
t`Provided and inferred calling code are conflicting`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,9 +19,17 @@ export const handleWorkflowTriggerException = (
|
|||||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
|
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
|
||||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS:
|
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS:
|
||||||
case WorkflowTriggerExceptionCode.FORBIDDEN:
|
case WorkflowTriggerExceptionCode.FORBIDDEN:
|
||||||
throw new UserInputError(exception.message);
|
throw new UserInputError(exception.message, {
|
||||||
|
extensions: {
|
||||||
|
userFriendlyMessage: exception.userFriendlyMessage,
|
||||||
|
},
|
||||||
|
});
|
||||||
case WorkflowTriggerExceptionCode.NOT_FOUND:
|
case WorkflowTriggerExceptionCode.NOT_FOUND:
|
||||||
throw new NotFoundError(exception.message);
|
throw new NotFoundError(exception.message, {
|
||||||
|
extensions: {
|
||||||
|
userFriendlyMessage: exception.userFriendlyMessage,
|
||||||
|
},
|
||||||
|
});
|
||||||
case WorkflowTriggerExceptionCode.INTERNAL_ERROR:
|
case WorkflowTriggerExceptionCode.INTERNAL_ERROR:
|
||||||
throw exception;
|
throw exception;
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@ -2,8 +2,12 @@ import { CustomException } from 'src/utils/custom-exception';
|
|||||||
|
|
||||||
export class FieldMetadataException extends CustomException {
|
export class FieldMetadataException extends CustomException {
|
||||||
declare code: FieldMetadataExceptionCode;
|
declare code: FieldMetadataExceptionCode;
|
||||||
constructor(message: string, code: FieldMetadataExceptionCode) {
|
constructor(
|
||||||
super(message, code);
|
message: string,
|
||||||
|
code: FieldMetadataExceptionCode,
|
||||||
|
{ userFriendlyMessage }: { userFriendlyMessage?: string } = {},
|
||||||
|
) {
|
||||||
|
super(message, code, userFriendlyMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { i18n } from '@lingui/core';
|
import { i18n } from '@lingui/core';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
import { APP_LOCALES } from 'twenty-shared/translations';
|
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||||
@ -363,6 +364,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
throw new FieldMetadataException(
|
throw new FieldMetadataException(
|
||||||
'Cannot delete, please update the label identifier field first',
|
'Cannot delete, please update the label identifier field first',
|
||||||
FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
|
FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Cannot delete, please update the label identifier field first`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,6 +578,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
throw new FieldMetadataException(
|
throw new FieldMetadataException(
|
||||||
`Name "${fieldMetadataInput.name}" is not available, check that it is not duplicating another field's name.`,
|
`Name "${fieldMetadataInput.name}" is not available, check that it is not duplicating another field's name.`,
|
||||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Name is not available, it may be duplicating another field's name.`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||||
@ -26,7 +27,10 @@ import {
|
|||||||
import { EnumFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
import { EnumFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
||||||
import { isSnakeCaseString } from 'src/utils/is-snake-case-string';
|
import { isSnakeCaseString } from 'src/utils/is-snake-case-string';
|
||||||
|
|
||||||
type Validator<T> = { validator: (str: T) => boolean; message: string };
|
type Validator<T> = {
|
||||||
|
validator: (str: T) => boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
type FieldMetadataUpdateCreateInput = CreateFieldInput | UpdateFieldInput;
|
type FieldMetadataUpdateCreateInput = CreateFieldInput | UpdateFieldInput;
|
||||||
|
|
||||||
@ -55,6 +59,9 @@ export class FieldMetadataEnumValidationService {
|
|||||||
throw new FieldMetadataException(
|
throw new FieldMetadataException(
|
||||||
message,
|
message,
|
||||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: message,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,23 +87,23 @@ export class FieldMetadataEnumValidationService {
|
|||||||
const validators: Validator<string>[] = [
|
const validators: Validator<string>[] = [
|
||||||
{
|
{
|
||||||
validator: (label) => !isDefined(label),
|
validator: (label) => !isDefined(label),
|
||||||
message: 'Option label is required',
|
message: t`Option label is required`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: exceedsDatabaseIdentifierMaximumLength,
|
validator: exceedsDatabaseIdentifierMaximumLength,
|
||||||
message: `Option label "${sanitizedLabel}" exceeds 63 characters`,
|
message: t`Option label exceeds 63 characters`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: beneathDatabaseIdentifierMinimumLength,
|
validator: beneathDatabaseIdentifierMinimumLength,
|
||||||
message: `Option label "${sanitizedLabel}" is beneath 1 character`,
|
message: t`Option label "${sanitizedLabel}" is beneath 1 character`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: (label) => label.includes(','),
|
validator: (label) => label.includes(','),
|
||||||
message: 'Label must not contain a comma',
|
message: t`Label must not contain a comma`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: (label) => !isNonEmptyString(label) || label === ' ',
|
validator: (label) => !isNonEmptyString(label) || label === ' ',
|
||||||
message: 'Label must not be empty',
|
message: t`Label must not be empty`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -109,15 +116,15 @@ export class FieldMetadataEnumValidationService {
|
|||||||
const validators: Validator<string>[] = [
|
const validators: Validator<string>[] = [
|
||||||
{
|
{
|
||||||
validator: (value) => !isDefined(value),
|
validator: (value) => !isDefined(value),
|
||||||
message: 'Option value is required',
|
message: t`Option value is required`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: exceedsDatabaseIdentifierMaximumLength,
|
validator: exceedsDatabaseIdentifierMaximumLength,
|
||||||
message: `Option value "${sanitizedValue}" exceeds 63 characters`,
|
message: t`Option value exceeds 63 characters`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: beneathDatabaseIdentifierMinimumLength,
|
validator: beneathDatabaseIdentifierMinimumLength,
|
||||||
message: `Option value "${sanitizedValue}" is beneath 1 character`,
|
message: t`Option value "${sanitizedValue}" is beneath 1 character`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: (value) => !isSnakeCaseString(value),
|
validator: (value) => !isSnakeCaseString(value),
|
||||||
|
|||||||
@ -18,13 +18,21 @@ export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
|||||||
if (error instanceof FieldMetadataException) {
|
if (error instanceof FieldMetadataException) {
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND:
|
case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND:
|
||||||
throw new NotFoundError(error.message);
|
throw new NotFoundError(error.message, {
|
||||||
|
userFriendlyMessage: error.userFriendlyMessage,
|
||||||
|
});
|
||||||
case FieldMetadataExceptionCode.INVALID_FIELD_INPUT:
|
case FieldMetadataExceptionCode.INVALID_FIELD_INPUT:
|
||||||
throw new UserInputError(error.message);
|
throw new UserInputError(error.message, {
|
||||||
|
userFriendlyMessage: error.userFriendlyMessage,
|
||||||
|
});
|
||||||
case FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED:
|
case FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED:
|
||||||
throw new ForbiddenError(error.message);
|
throw new ForbiddenError(error.message, {
|
||||||
|
userFriendlyMessage: error.userFriendlyMessage,
|
||||||
|
});
|
||||||
case FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS:
|
case FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS:
|
||||||
throw new ConflictError(error.message);
|
throw new ConflictError(error.message, {
|
||||||
|
userFriendlyMessage: error.userFriendlyMessage,
|
||||||
|
});
|
||||||
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||||
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
|
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
|
||||||
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
|
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
|
||||||
|
|||||||
@ -2,8 +2,12 @@ import { CustomException } from 'src/utils/custom-exception';
|
|||||||
|
|
||||||
export class ObjectMetadataException extends CustomException {
|
export class ObjectMetadataException extends CustomException {
|
||||||
declare code: ObjectMetadataExceptionCode;
|
declare code: ObjectMetadataExceptionCode;
|
||||||
constructor(message: string, code: ObjectMetadataExceptionCode) {
|
constructor(
|
||||||
super(message, code);
|
message: string,
|
||||||
|
code: ObjectMetadataExceptionCode,
|
||||||
|
{ userFriendlyMessage }: { userFriendlyMessage?: string } = {},
|
||||||
|
) {
|
||||||
|
super(message, code, userFriendlyMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`validateObjectMetadataInputOrThrow should fail when name exceeds maximum length 1`] = `"String "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters limit"`;
|
exports[`validateObjectMetadataInputOrThrow should fail when name exceeds maximum length 1`] = `"Name is too long: it exceeds the 63 characters limit."`;
|
||||||
|
|
||||||
exports[`validateObjectMetadataInputOrThrow should fail when namePlural has invalid characters 1`] = `"String "μ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
exports[`validateObjectMetadataInputOrThrow should fail when namePlural has invalid characters 1`] = `"String "μ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
||||||
|
|
||||||
|
|||||||
@ -20,11 +20,15 @@ export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
|||||||
case ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
case ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||||
throw new NotFoundError(error.message);
|
throw new NotFoundError(error.message);
|
||||||
case ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT:
|
case ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT:
|
||||||
throw new UserInputError(error.message);
|
throw new UserInputError(error.message, {
|
||||||
|
userFriendlyMessage: error.userFriendlyMessage,
|
||||||
|
});
|
||||||
case ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED:
|
case ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED:
|
||||||
throw new ForbiddenError(error.message);
|
throw new ForbiddenError(error.message);
|
||||||
case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS:
|
case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS:
|
||||||
throw new ConflictError(error.message);
|
throw new ConflictError(error.message, {
|
||||||
|
userFriendlyMessage: error.userFriendlyMessage,
|
||||||
|
});
|
||||||
case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD:
|
case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD:
|
||||||
throw error;
|
throw error;
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@ -29,9 +29,14 @@ export const validateObjectMetadataInputNameOrThrow = (name: string): void => {
|
|||||||
validateMetadataNameOrThrow(name);
|
validateMetadataNameOrThrow(name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof InvalidMetadataException) {
|
if (error instanceof InvalidMetadataException) {
|
||||||
|
const errorMessage = error.message;
|
||||||
|
|
||||||
throw new ObjectMetadataException(
|
throw new ObjectMetadataException(
|
||||||
error.message,
|
errorMessage,
|
||||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: errorMessage,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +67,9 @@ const validateObjectMetadataInputLabelOrThrow = (name: string): void => {
|
|||||||
throw new ObjectMetadataException(
|
throw new ObjectMetadataException(
|
||||||
error.message,
|
error.message,
|
||||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: error.userFriendlyMessage,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,6 @@ export enum PermissionsExceptionCode {
|
|||||||
CANNOT_UPDATE_SELF_ROLE = 'CANNOT_UPDATE_SELF_ROLE',
|
CANNOT_UPDATE_SELF_ROLE = 'CANNOT_UPDATE_SELF_ROLE',
|
||||||
NO_ROLE_FOUND_FOR_USER_WORKSPACE = 'NO_ROLE_FOUND_FOR_USER_WORKSPACE',
|
NO_ROLE_FOUND_FOR_USER_WORKSPACE = 'NO_ROLE_FOUND_FOR_USER_WORKSPACE',
|
||||||
INVALID_ARG = 'INVALID_ARG_PERMISSIONS',
|
INVALID_ARG = 'INVALID_ARG_PERMISSIONS',
|
||||||
PERMISSIONS_V2_NOT_ENABLED = 'PERMISSIONS_V2_NOT_ENABLED',
|
|
||||||
ROLE_LABEL_ALREADY_EXISTS = 'ROLE_LABEL_ALREADY_EXISTS',
|
ROLE_LABEL_ALREADY_EXISTS = 'ROLE_LABEL_ALREADY_EXISTS',
|
||||||
DEFAULT_ROLE_NOT_FOUND = 'DEFAULT_ROLE_NOT_FOUND',
|
DEFAULT_ROLE_NOT_FOUND = 'DEFAULT_ROLE_NOT_FOUND',
|
||||||
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND_PERMISSIONS',
|
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND_PERMISSIONS',
|
||||||
@ -53,7 +52,6 @@ export enum PermissionsExceptionMessage {
|
|||||||
UNKNOWN_REQUIRED_PERMISSION = 'Unknown required permission',
|
UNKNOWN_REQUIRED_PERMISSION = 'Unknown required permission',
|
||||||
CANNOT_UPDATE_SELF_ROLE = 'Cannot update self role',
|
CANNOT_UPDATE_SELF_ROLE = 'Cannot update self role',
|
||||||
NO_ROLE_FOUND_FOR_USER_WORKSPACE = 'No role found for userWorkspace',
|
NO_ROLE_FOUND_FOR_USER_WORKSPACE = 'No role found for userWorkspace',
|
||||||
PERMISSIONS_V2_NOT_ENABLED = 'Permissions V2 is not enabled',
|
|
||||||
ROLE_LABEL_ALREADY_EXISTS = 'A role with this label already exists',
|
ROLE_LABEL_ALREADY_EXISTS = 'A role with this label already exists',
|
||||||
DEFAULT_ROLE_NOT_FOUND = 'Default role not found',
|
DEFAULT_ROLE_NOT_FOUND = 'Default role not found',
|
||||||
OBJECT_METADATA_NOT_FOUND = 'Object metadata not found',
|
OBJECT_METADATA_NOT_FOUND = 'Object metadata not found',
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ForbiddenError,
|
ForbiddenError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
@ -13,11 +15,16 @@ export const permissionGraphqlApiExceptionHandler = (
|
|||||||
) => {
|
) => {
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case PermissionsExceptionCode.PERMISSION_DENIED:
|
case PermissionsExceptionCode.PERMISSION_DENIED:
|
||||||
|
throw new ForbiddenError(error.message, {
|
||||||
|
userFriendlyMessage: 'User does not have permission.',
|
||||||
|
});
|
||||||
|
case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS:
|
||||||
|
throw new ForbiddenError(error.message, {
|
||||||
|
userFriendlyMessage: t`A role with this label already exists.`,
|
||||||
|
});
|
||||||
case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN:
|
case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN:
|
||||||
case PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE:
|
case PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE:
|
||||||
case PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER:
|
case PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER:
|
||||||
case PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED:
|
|
||||||
case PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS:
|
|
||||||
case PermissionsExceptionCode.ROLE_NOT_EDITABLE:
|
case PermissionsExceptionCode.ROLE_NOT_EDITABLE:
|
||||||
case PermissionsExceptionCode.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT:
|
case PermissionsExceptionCode.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT:
|
||||||
throw new ForbiddenError(error.message);
|
throw new ForbiddenError(error.message);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ exports[`validateMetadataNameOrThrow throws error when string has spaces 1`] = `
|
|||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when string is a reserved word 1`] = `"The name "role" is not available"`;
|
exports[`validateMetadataNameOrThrow throws error when string is a reserved word 1`] = `"The name "role" is not available"`;
|
||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when string is above 63 characters 1`] = `"String "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" exceeds 63 characters limit"`;
|
exports[`validateMetadataNameOrThrow throws error when string is above 63 characters 1`] = `"Name is too long: it exceeds the 63 characters limit."`;
|
||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when string is empty 1`] = `"Input is too short: """`;
|
exports[`validateMetadataNameOrThrow throws error when string is empty 1`] = `"Input is too short: """`;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import camelCase from 'lodash.camelcase';
|
import camelCase from 'lodash.camelcase';
|
||||||
import { slugify } from 'transliteration';
|
import { slugify } from 'transliteration';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -12,6 +13,9 @@ export const computeMetadataNameFromLabel = (label: string): string => {
|
|||||||
throw new InvalidMetadataException(
|
throw new InvalidMetadataException(
|
||||||
'Label is required',
|
'Label is required',
|
||||||
InvalidMetadataExceptionCode.LABEL_REQUIRED,
|
InvalidMetadataExceptionCode.LABEL_REQUIRED,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Label is required`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,6 +35,9 @@ export const computeMetadataNameFromLabel = (label: string): string => {
|
|||||||
throw new InvalidMetadataException(
|
throw new InvalidMetadataException(
|
||||||
`Invalid label: "${label}"`,
|
`Invalid label: "${label}"`,
|
||||||
InvalidMetadataExceptionCode.INVALID_LABEL,
|
InvalidMetadataExceptionCode.INVALID_LABEL,
|
||||||
|
{
|
||||||
|
userFriendlyMessage: t`Invalid label: "${label}"`,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user