Upgrade Apollo Client to v4 and refactor error handling (#18584)
## Summary This PR upgrades Apollo Client from v3.10.0 to v4 and refactors error handling patterns across the codebase to use a new centralized `useSnackBarOnQueryError` hook. ## Key Changes - **Dependency Update**: Upgraded `@apollo/client` from `^3.10.0` to `^3.11.0` in root package.json - **New Hook**: Added `useSnackBarOnQueryError` hook for centralized Apollo query error handling with snack bar notifications - **Error Handling Refactor**: Updated 100+ files to use the new error handling pattern: - Removed direct `ApolloError` imports where no longer needed - Replaced manual error handling logic with `useSnackBarOnQueryError` hook - Simplified error handling in hooks and components across multiple modules - **GraphQL Codegen**: Updated codegen configuration files to work with Apollo Client v3.11.0 - **Type Definitions**: Added TypeScript declaration file for `apollo-upload-client` module - **Test Updates**: Updated test files to reflect new error handling patterns ## Notable Implementation Details - The new `useSnackBarOnQueryError` hook provides a consistent way to handle Apollo query errors with automatic snack bar notifications - Changes span across multiple feature areas: auth, object records, settings, workflows, billing, and more - All changes maintain backward compatibility while improving code maintainability and reducing duplication - Jest configuration updated to work with the new Apollo Client version https://claude.ai/code/session_019WGZ6Rd7sEHuBg9sTrXRqJ --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
|
||||
import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState';
|
||||
@@ -95,7 +95,7 @@ export const VerifyEmailEffect = () => {
|
||||
await verifyLoginToken(loginToken.token);
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
...(error instanceof ApolloError
|
||||
...(CombinedGraphQLErrors.is(error)
|
||||
? { apolloError: error }
|
||||
: { message: t`Email verification failed` }),
|
||||
options: {
|
||||
@@ -103,9 +103,8 @@ export const VerifyEmailEffect = () => {
|
||||
},
|
||||
});
|
||||
if (
|
||||
error instanceof ApolloError &&
|
||||
error.graphQLErrors[0].extensions?.subCode ===
|
||||
'EMAIL_ALREADY_VERIFIED'
|
||||
CombinedGraphQLErrors.is(error) &&
|
||||
error.errors[0].extensions?.subCode === 'EMAIL_ALREADY_VERIFIED'
|
||||
) {
|
||||
navigate(AppPath.SignInUp);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { supportChatState } from '@/client-config/states/supportChatState';
|
||||
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { useApolloClient } from '@apollo/client/react';
|
||||
import { MockedProvider } from '@apollo/client/testing/react';
|
||||
import { type ReactNode, act } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
@@ -73,7 +73,7 @@ jest.mock('@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain', () => ({
|
||||
}));
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider mocks={Object.values(mocks)} addTypename={false}>
|
||||
<MockedProvider mocks={Object.values(mocks)}>
|
||||
<MemoryRouter>
|
||||
<SnackBarComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'test-instance-id' }}
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
import { ApolloError, useApolloClient } from '@apollo/client';
|
||||
import {
|
||||
useApolloClient,
|
||||
useLazyQuery,
|
||||
useMutation,
|
||||
} from '@apollo/client/react';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { useCallback } from 'react';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import {
|
||||
useCheckUserExistsLazyQuery,
|
||||
useGetAuthTokensFromLoginTokenMutation,
|
||||
useGetAuthTokensFromOtpMutation,
|
||||
useGetLoginTokenFromCredentialsMutation,
|
||||
useSignInMutation,
|
||||
useSignUpInWorkspaceMutation,
|
||||
useSignUpMutation,
|
||||
useVerifyEmailAndGetLoginTokenMutation,
|
||||
useVerifyEmailAndGetWorkspaceAgnosticTokenMutation,
|
||||
type AuthToken,
|
||||
type AuthTokenPair,
|
||||
CheckUserExistsDocument,
|
||||
GetAuthTokensFromLoginTokenDocument,
|
||||
GetAuthTokensFromOtpDocument,
|
||||
GetLoginTokenFromCredentialsDocument,
|
||||
SignInDocument,
|
||||
SignUpInWorkspaceDocument,
|
||||
SignUpDocument,
|
||||
VerifyEmailAndGetLoginTokenDocument,
|
||||
VerifyEmailAndGetWorkspaceAgnosticTokenDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||
@@ -91,25 +96,30 @@ export const useAuth = () => {
|
||||
const { redirect } = useRedirect();
|
||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||
|
||||
const [getLoginTokenFromCredentials] =
|
||||
useGetLoginTokenFromCredentialsMutation();
|
||||
const [signIn] = useSignInMutation();
|
||||
const [signUp] = useSignUpMutation();
|
||||
const [signUpInWorkspace] = useSignUpInWorkspaceMutation();
|
||||
const [getAuthTokensFromLoginToken] =
|
||||
useGetAuthTokensFromLoginTokenMutation();
|
||||
const [verifyEmailAndGetLoginToken] =
|
||||
useVerifyEmailAndGetLoginTokenMutation();
|
||||
const [verifyEmailAndGetWorkspaceAgnosticToken] =
|
||||
useVerifyEmailAndGetWorkspaceAgnosticTokenMutation();
|
||||
const [getAuthTokensFromOtp] = useGetAuthTokensFromOtpMutation();
|
||||
const [getLoginTokenFromCredentials] = useMutation(
|
||||
GetLoginTokenFromCredentialsDocument,
|
||||
);
|
||||
const [signIn] = useMutation(SignInDocument);
|
||||
const [signUp] = useMutation(SignUpDocument);
|
||||
const [signUpInWorkspace] = useMutation(SignUpInWorkspaceDocument);
|
||||
const [getAuthTokensFromLoginToken] = useMutation(
|
||||
GetAuthTokensFromLoginTokenDocument,
|
||||
);
|
||||
const [verifyEmailAndGetLoginToken] = useMutation(
|
||||
VerifyEmailAndGetLoginTokenDocument,
|
||||
);
|
||||
const [verifyEmailAndGetWorkspaceAgnosticToken] = useMutation(
|
||||
VerifyEmailAndGetWorkspaceAgnosticTokenDocument,
|
||||
);
|
||||
const [getAuthTokensFromOtp] = useMutation(GetAuthTokensFromOtpDocument);
|
||||
|
||||
const workspacePublicData = useAtomStateValue(workspacePublicDataState);
|
||||
|
||||
const { setLastAuthenticateWorkspaceDomain } =
|
||||
useLastAuthenticatedWorkspaceDomain();
|
||||
const [checkUserExistsQuery, { data: checkUserExistsData }] =
|
||||
useCheckUserExistsLazyQuery();
|
||||
const [checkUserExistsQuery, { data: checkUserExistsData }] = useLazyQuery(
|
||||
CheckUserExistsDocument,
|
||||
);
|
||||
|
||||
const client = useApolloClient();
|
||||
|
||||
@@ -188,8 +198,8 @@ export const useAuth = () => {
|
||||
origin,
|
||||
},
|
||||
});
|
||||
if (isDefined(getLoginTokenResult.errors)) {
|
||||
throw getLoginTokenResult.errors;
|
||||
if (isDefined(getLoginTokenResult.error)) {
|
||||
throw getLoginTokenResult.error;
|
||||
}
|
||||
|
||||
if (!getLoginTokenResult.data?.getLoginTokenFromCredentials) {
|
||||
@@ -200,8 +210,8 @@ export const useAuth = () => {
|
||||
} catch (error) {
|
||||
// TODO: Get intellisense for graphql error extensions code (codegen?)
|
||||
if (
|
||||
error instanceof ApolloError &&
|
||||
error.graphQLErrors[0]?.extensions?.subCode === 'EMAIL_NOT_VERIFIED'
|
||||
CombinedGraphQLErrors.is(error) &&
|
||||
error.errors[0]?.extensions?.subCode === 'EMAIL_NOT_VERIFIED'
|
||||
) {
|
||||
setSearchParams({ email });
|
||||
setSignInUpStep(SignInUpStep.EmailVerification);
|
||||
@@ -228,8 +238,8 @@ export const useAuth = () => {
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(loginTokenResult.errors)) {
|
||||
throw loginTokenResult.errors;
|
||||
if (isDefined(loginTokenResult.error)) {
|
||||
throw loginTokenResult.error;
|
||||
}
|
||||
|
||||
if (!loginTokenResult.data?.verifyEmailAndGetLoginToken) {
|
||||
@@ -247,7 +257,7 @@ export const useAuth = () => {
|
||||
email: string,
|
||||
captchaToken?: string,
|
||||
) => {
|
||||
const { data, errors } = await verifyEmailAndGetWorkspaceAgnosticToken({
|
||||
const { data, error } = await verifyEmailAndGetWorkspaceAgnosticToken({
|
||||
variables: {
|
||||
email,
|
||||
emailVerificationToken,
|
||||
@@ -255,8 +265,8 @@ export const useAuth = () => {
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(errors)) {
|
||||
throw errors;
|
||||
if (isDefined(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!data?.verifyEmailAndGetWorkspaceAgnosticToken) {
|
||||
@@ -315,8 +325,8 @@ export const useAuth = () => {
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(getAuthTokensResult.errors)) {
|
||||
throw getAuthTokensResult.errors;
|
||||
if (isDefined(getAuthTokensResult.error)) {
|
||||
throw getAuthTokensResult.error;
|
||||
}
|
||||
|
||||
if (!getAuthTokensResult.data?.getAuthTokensFromLoginToken) {
|
||||
@@ -328,8 +338,8 @@ export const useAuth = () => {
|
||||
);
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof ApolloError &&
|
||||
error.graphQLErrors[0]?.extensions?.subCode ===
|
||||
CombinedGraphQLErrors.is(error) &&
|
||||
error.errors[0]?.extensions?.subCode ===
|
||||
'TWO_FACTOR_AUTHENTICATION_PROVISION_REQUIRED'
|
||||
) {
|
||||
handleSetLoginToken(loginToken);
|
||||
@@ -338,8 +348,8 @@ export const useAuth = () => {
|
||||
}
|
||||
|
||||
if (
|
||||
error instanceof ApolloError &&
|
||||
error.graphQLErrors[0]?.extensions?.subCode ===
|
||||
CombinedGraphQLErrors.is(error) &&
|
||||
error.errors[0]?.extensions?.subCode ===
|
||||
'TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED'
|
||||
) {
|
||||
handleSetLoginToken(loginToken);
|
||||
@@ -360,7 +370,7 @@ export const useAuth = () => {
|
||||
|
||||
const handleCredentialsSignIn = useCallback(
|
||||
async (email: string, password: string, captchaToken?: string) => {
|
||||
signIn({
|
||||
await signIn({
|
||||
variables: { email, password, captchaToken },
|
||||
onCompleted: async (data) => {
|
||||
handleSetAuthTokens(data.signIn.tokens);
|
||||
@@ -394,8 +404,8 @@ export const useAuth = () => {
|
||||
},
|
||||
onError: (error) => {
|
||||
if (
|
||||
error instanceof ApolloError &&
|
||||
error.graphQLErrors[0]?.extensions?.subCode === 'EMAIL_NOT_VERIFIED'
|
||||
CombinedGraphQLErrors.is(error) &&
|
||||
error.errors[0]?.extensions?.subCode === 'EMAIL_NOT_VERIFIED'
|
||||
) {
|
||||
setSearchParams({ email });
|
||||
setSignInUpStep(SignInUpStep.EmailVerification);
|
||||
@@ -427,8 +437,8 @@ export const useAuth = () => {
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(signUpResult.errors)) {
|
||||
throw signUpResult.errors;
|
||||
if (isDefined(signUpResult.error)) {
|
||||
throw signUpResult.error;
|
||||
}
|
||||
|
||||
if (isEmailVerificationRequired) {
|
||||
@@ -510,8 +520,8 @@ export const useAuth = () => {
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(signUpInWorkspaceResult.errors)) {
|
||||
throw signUpInWorkspaceResult.errors;
|
||||
if (isDefined(signUpInWorkspaceResult.error)) {
|
||||
throw signUpInWorkspaceResult.error;
|
||||
}
|
||||
|
||||
if (!signUpInWorkspaceResult.data?.signUpInWorkspace) {
|
||||
@@ -632,8 +642,8 @@ export const useAuth = () => {
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(getAuthTokensFromOtpResult.errors)) {
|
||||
throw getAuthTokensFromOtpResult.errors;
|
||||
if (isDefined(getAuthTokensFromOtpResult.error)) {
|
||||
throw getAuthTokensFromOtpResult.error;
|
||||
}
|
||||
|
||||
if (!getAuthTokensFromOtpResult.data?.getAuthTokensFromOTP) {
|
||||
|
||||
@@ -3,11 +3,9 @@ import {
|
||||
ApolloLink,
|
||||
HttpLink,
|
||||
InMemoryCache,
|
||||
type UriFunction,
|
||||
} from '@apollo/client';
|
||||
|
||||
import { loggerLink } from '@/apollo/utils/loggerLink';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type AuthTokenPair,
|
||||
RenewTokenDocument,
|
||||
@@ -19,7 +17,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
const logger = loggerLink(() => 'Twenty-Refresh');
|
||||
|
||||
const renewTokenMutation = async (
|
||||
uri: string | UriFunction | undefined,
|
||||
uri: string | undefined,
|
||||
refreshToken: string,
|
||||
) => {
|
||||
const httpLink = new HttpLink({ uri });
|
||||
@@ -30,18 +28,24 @@ const renewTokenMutation = async (
|
||||
cache: new InMemoryCache({}),
|
||||
});
|
||||
|
||||
const { data, errors } = await client.mutate<
|
||||
RenewTokenMutation,
|
||||
RenewTokenMutationVariables
|
||||
>({
|
||||
mutation: RenewTokenDocument,
|
||||
variables: {
|
||||
appToken: refreshToken,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
let data: RenewTokenMutation | null | undefined;
|
||||
try {
|
||||
const result = await client.mutate<
|
||||
RenewTokenMutation,
|
||||
RenewTokenMutationVariables
|
||||
>({
|
||||
mutation: RenewTokenDocument,
|
||||
variables: {
|
||||
appToken: refreshToken,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
data = result.data;
|
||||
} catch {
|
||||
throw new Error('Something went wrong during token renewal');
|
||||
}
|
||||
|
||||
if (isDefined(errors) || isUndefinedOrNull(data)) {
|
||||
if (isUndefinedOrNull(data)) {
|
||||
throw new Error('Something went wrong during token renewal');
|
||||
}
|
||||
|
||||
@@ -49,7 +53,7 @@ const renewTokenMutation = async (
|
||||
};
|
||||
|
||||
export const renewToken = async (
|
||||
uri: string | UriFunction | undefined,
|
||||
uri: string | undefined,
|
||||
tokenPair: AuthTokenPair | undefined | null,
|
||||
) => {
|
||||
if (!tokenPair) {
|
||||
|
||||
+7
-7
@@ -9,15 +9,13 @@ import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import {
|
||||
type PublicWorkspaceData,
|
||||
useEmailPasswordResetLinkMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { type PublicWorkspaceData } from '~/generated-metadata/graphql';
|
||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||
|
||||
// Mocks
|
||||
jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar');
|
||||
jest.mock('~/generated-metadata/graphql');
|
||||
jest.mock('@apollo/client/react');
|
||||
|
||||
dynamicActivate(SOURCE_LOCALE);
|
||||
|
||||
@@ -63,7 +61,7 @@ describe('useHandleResetPassword', () => {
|
||||
enqueueErrorSnackBar: enqueueErrorSnackBarMock,
|
||||
enqueueSuccessSnackBar: enqueueSuccessSnackBarMock,
|
||||
});
|
||||
(useEmailPasswordResetLinkMutation as jest.Mock).mockReturnValue([
|
||||
(useMutation as unknown as jest.Mock).mockReturnValue([
|
||||
emailPasswordResetLinkMock,
|
||||
]);
|
||||
});
|
||||
@@ -127,6 +125,8 @@ describe('useHandleResetPassword', () => {
|
||||
const { result } = renderHooks();
|
||||
await act(() => result.current.handleResetPassword('test@example.com')());
|
||||
|
||||
expect(enqueueErrorSnackBarMock).toHaveBeenCalledWith({});
|
||||
expect(enqueueErrorSnackBarMock).toHaveBeenCalledWith({
|
||||
message: errorMessage,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,8 @@ import { GET_AUTHORIZATION_URL_FOR_SSO } from '@/auth/graphql/mutations/getAutho
|
||||
import { useSSO } from '@/auth/sign-in-up/hooks/useSSO';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { MockedProvider } from '@apollo/client/testing/react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
@@ -55,9 +55,7 @@ const apolloMocks = [
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
<MockedProvider mocks={apolloMocks}>{children}</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
@@ -86,8 +84,8 @@ describe('useSSO', () => {
|
||||
await result.current.redirectToSSOLoginPage(identityProviderId);
|
||||
|
||||
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||
apolloError: new ApolloError({
|
||||
graphQLErrors: [{ message: 'Error message' }],
|
||||
apolloError: new CombinedGraphQLErrors({
|
||||
errors: [{ message: 'Error message' }],
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
+11
-7
@@ -2,14 +2,16 @@ import { useCallback } from 'react';
|
||||
|
||||
import { useOrigin } from '@/domain-manager/hooks/useOrigin';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useResendEmailVerificationTokenMutation } from '~/generated-metadata/graphql';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { ResendEmailVerificationTokenDocument } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useHandleResendEmailVerificationToken = () => {
|
||||
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||
const [resendEmailVerificationToken, { loading }] =
|
||||
useResendEmailVerificationTokenMutation();
|
||||
const [resendEmailVerificationToken, { loading }] = useMutation(
|
||||
ResendEmailVerificationTokenDocument,
|
||||
);
|
||||
const { origin } = useOrigin();
|
||||
|
||||
const handleResendEmailVerificationToken = useCallback(
|
||||
@@ -38,9 +40,11 @@ export const useHandleResendEmailVerificationToken = () => {
|
||||
enqueueErrorSnackBar({});
|
||||
}
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
...(error instanceof ApolloError ? { apolloError: error } : {}),
|
||||
});
|
||||
enqueueErrorSnackBar(
|
||||
CombinedGraphQLErrors.is(error)
|
||||
? { apolloError: error }
|
||||
: { message: error instanceof Error ? error.message : undefined },
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -3,14 +3,15 @@ import { useCallback } from 'react';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useEmailPasswordResetLinkMutation } from '~/generated-metadata/graphql';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { EmailPasswordResetLinkDocument } from '~/generated-metadata/graphql';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
export const useHandleResetPassword = () => {
|
||||
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||
const [emailPasswordResetLink] = useEmailPasswordResetLinkMutation();
|
||||
const [emailPasswordResetLink] = useMutation(EmailPasswordResetLinkDocument);
|
||||
const workspacePublicData = useAtomStateValue(workspacePublicDataState);
|
||||
const currentUser = useAtomStateValue(currentUserState);
|
||||
|
||||
@@ -41,9 +42,11 @@ export const useHandleResetPassword = () => {
|
||||
enqueueErrorSnackBar({});
|
||||
}
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
...(error instanceof ApolloError ? { apolloError: error } : {}),
|
||||
});
|
||||
enqueueErrorSnackBar(
|
||||
CombinedGraphQLErrors.is(error)
|
||||
? { apolloError: error }
|
||||
: { message: error instanceof Error ? error.message : undefined },
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* @license Enterprise */
|
||||
|
||||
import { GET_AUTHORIZATION_URL_FOR_SSO } from '@/auth/graphql/mutations/getAuthorizationUrlForSSO';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError, useApolloClient } from '@apollo/client';
|
||||
import { useApolloClient } from '@apollo/client/react';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { GetAuthorizationUrlForSsoDocument } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useSSO = () => {
|
||||
const apolloClient = useApolloClient();
|
||||
@@ -16,7 +17,7 @@ export const useSSO = () => {
|
||||
let authorizationUrlForSSOResult;
|
||||
try {
|
||||
authorizationUrlForSSOResult = await apolloClient.mutate({
|
||||
mutation: GET_AUTHORIZATION_URL_FOR_SSO,
|
||||
mutation: GetAuthorizationUrlForSsoDocument,
|
||||
variables: {
|
||||
input: {
|
||||
identityProviderId,
|
||||
@@ -24,16 +25,21 @@ export const useSSO = () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
return enqueueErrorSnackBar({
|
||||
...(error instanceof ApolloError ? { apolloError: error } : {}),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
return enqueueErrorSnackBar(
|
||||
CombinedGraphQLErrors.is(error)
|
||||
? { apolloError: error }
|
||||
: { message: error instanceof Error ? error.message : undefined },
|
||||
);
|
||||
}
|
||||
|
||||
redirect(
|
||||
const authorizationURL =
|
||||
authorizationUrlForSSOResult.data?.getAuthorizationUrlForSSO
|
||||
.authorizationURL,
|
||||
);
|
||||
?.authorizationURL;
|
||||
|
||||
if (authorizationURL) {
|
||||
redirect(authorizationURL);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useCaptcha } from '@/client-config/hooks/useCaptcha';
|
||||
import { useBuildSearchParamsFromUrlSyncedStates } from '@/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates';
|
||||
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { isErrorLike } from '@apollo/client/errors';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -178,9 +178,9 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
captchaToken: token,
|
||||
verifyEmailRedirectPath,
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
enqueueErrorSnackBar({
|
||||
...(error instanceof ApolloError ? { apolloError: error } : {}),
|
||||
...(isErrorLike(error) ? { apolloError: error } : {}),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { useSignUpInNewWorkspaceMutation } from '~/generated-metadata/graphql';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { SignUpInNewWorkspaceDocument } from '~/generated-metadata/graphql';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
@@ -12,7 +13,9 @@ export const useSignUpInNewWorkspace = () => {
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const { t } = useLingui();
|
||||
|
||||
const [signUpInNewWorkspaceMutation] = useSignUpInNewWorkspaceMutation();
|
||||
const [signUpInNewWorkspaceMutation] = useMutation(
|
||||
SignUpInNewWorkspaceDocument,
|
||||
);
|
||||
|
||||
const createWorkspace = async ({ newTab } = { newTab: true }) => {
|
||||
try {
|
||||
@@ -27,11 +30,16 @@ export const useSignUpInNewWorkspace = () => {
|
||||
newTab ? '_blank' : '_self',
|
||||
);
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
...(error instanceof ApolloError
|
||||
enqueueErrorSnackBar(
|
||||
CombinedGraphQLErrors.is(error)
|
||||
? { apolloError: error }
|
||||
: { message: t`Workspace creation failed` }),
|
||||
});
|
||||
: {
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t`Workspace creation failed`,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+48
-28
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
@@ -9,7 +9,8 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useGetWorkspaceFromInviteHashQuery } from '~/generated-metadata/graphql';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { GetWorkspaceFromInviteHashDocument } from '~/generated-metadata/graphql';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
export const useWorkspaceFromInviteHash = () => {
|
||||
@@ -18,33 +19,52 @@ export const useWorkspaceFromInviteHash = () => {
|
||||
const workspaceInviteHash = useParams().workspaceInviteHash;
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
const [initiallyLoggedIn] = useState(isDefined(currentWorkspace));
|
||||
const [hasRedirected, setHasRedirected] = useState(false);
|
||||
|
||||
const { data: workspaceFromInviteHash, loading } =
|
||||
useGetWorkspaceFromInviteHashQuery({
|
||||
skip: !workspaceInviteHash,
|
||||
variables: { inviteHash: workspaceInviteHash || '' },
|
||||
onError: (error) => {
|
||||
enqueueErrorSnackBar({ apolloError: error });
|
||||
navigate(AppPath.Index);
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (
|
||||
isDefined(currentWorkspace) &&
|
||||
isDefined(data?.findWorkspaceFromInviteHash) &&
|
||||
currentWorkspace.id === data.findWorkspaceFromInviteHash.id
|
||||
) {
|
||||
const workspaceDisplayName =
|
||||
data?.findWorkspaceFromInviteHash?.displayName;
|
||||
initiallyLoggedIn &&
|
||||
enqueueInfoSnackBar({
|
||||
message: workspaceDisplayName
|
||||
? t`You already belong to the workspace ${workspaceDisplayName}`
|
||||
: t`You already belong to this workspace`,
|
||||
});
|
||||
navigate(AppPath.Index);
|
||||
}
|
||||
},
|
||||
});
|
||||
const {
|
||||
data: workspaceFromInviteHash,
|
||||
loading,
|
||||
error,
|
||||
} = useQuery(GetWorkspaceFromInviteHashDocument, {
|
||||
skip: !workspaceInviteHash,
|
||||
variables: { inviteHash: workspaceInviteHash || '' },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
enqueueErrorSnackBar({ apolloError: error });
|
||||
navigate(AppPath.Index);
|
||||
}
|
||||
}, [error, enqueueErrorSnackBar, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!workspaceFromInviteHash || hasRedirected) return;
|
||||
|
||||
const inviteWorkspace = workspaceFromInviteHash.findWorkspaceFromInviteHash;
|
||||
|
||||
if (
|
||||
isDefined(currentWorkspace) &&
|
||||
isDefined(inviteWorkspace) &&
|
||||
currentWorkspace.id === inviteWorkspace.id
|
||||
) {
|
||||
setHasRedirected(true);
|
||||
const workspaceDisplayName = inviteWorkspace.displayName;
|
||||
initiallyLoggedIn &&
|
||||
enqueueInfoSnackBar({
|
||||
message: workspaceDisplayName
|
||||
? t`You already belong to the workspace ${workspaceDisplayName}`
|
||||
: t`You already belong to this workspace`,
|
||||
});
|
||||
navigate(AppPath.Index);
|
||||
}
|
||||
}, [
|
||||
workspaceFromInviteHash,
|
||||
currentWorkspace,
|
||||
hasRedirected,
|
||||
initiallyLoggedIn,
|
||||
enqueueInfoSnackBar,
|
||||
navigate,
|
||||
]);
|
||||
return {
|
||||
workspace: workspaceFromInviteHash?.findWorkspaceFromInviteHash,
|
||||
workspaceInviteHash,
|
||||
|
||||
Reference in New Issue
Block a user