diff --git a/packages/twenty-front/src/modules/apollo/components/ApolloProvider.tsx b/packages/twenty-front/src/modules/apollo/components/ApolloProvider.tsx index 37eb4c2355e..dc57de17e27 100644 --- a/packages/twenty-front/src/modules/apollo/components/ApolloProvider.tsx +++ b/packages/twenty-front/src/modules/apollo/components/ApolloProvider.tsx @@ -12,7 +12,7 @@ export const ApolloProvider = ({ children }: React.PropsWithChildren) => { const apolloClient = useApolloFactory({ uri: `${REACT_APP_SERVER_BASE_URL}/metadata`, - devtools: { enabled: true }, // should this be default , ie dependant on IS_DEBUG_MODE? + devtools: { enabled: process.env.IS_DEBUG_MODE === 'true' }, extraLinks: [captchaRefreshLink], }); diff --git a/packages/twenty-front/src/modules/apollo/hooks/useSnackBarOnQueryError.ts b/packages/twenty-front/src/modules/apollo/hooks/useSnackBarOnQueryError.ts index 39854809099..6c56b92fa1d 100644 --- a/packages/twenty-front/src/modules/apollo/hooks/useSnackBarOnQueryError.ts +++ b/packages/twenty-front/src/modules/apollo/hooks/useSnackBarOnQueryError.ts @@ -22,9 +22,9 @@ export const useSnackBarOnQueryError = ( enqueueErrorSnackBar( message ? { message } - : { - apolloError: CombinedGraphQLErrors.is(error) ? error : undefined, - }, + : CombinedGraphQLErrors.is(error) + ? { apolloError: error } + : { message: error.message }, ); }, [error, enqueueErrorSnackBar, message]); }; diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index f3edde17657..06b1bcf618d 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -370,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); diff --git a/packages/twenty-front/src/modules/auth/services/AuthService.ts b/packages/twenty-front/src/modules/auth/services/AuthService.ts index d2db2ed3f19..be310ddc100 100644 --- a/packages/twenty-front/src/modules/auth/services/AuthService.ts +++ b/packages/twenty-front/src/modules/auth/services/AuthService.ts @@ -6,7 +6,6 @@ import { } from '@apollo/client'; import { loggerLink } from '@/apollo/utils/loggerLink'; -import { isDefined } from 'twenty-shared/utils'; import { type AuthTokenPair, RenewTokenDocument, @@ -29,18 +28,24 @@ const renewTokenMutation = async ( cache: new InMemoryCache({}), }); - const { data, error } = 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(error) || isUndefinedOrNull(data)) { + if (isUndefinedOrNull(data)) { throw new Error('Something went wrong during token renewal'); } diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts index b91b97ab006..f3588d26623 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts @@ -40,9 +40,11 @@ export const useHandleResendEmailVerificationToken = () => { enqueueErrorSnackBar({}); } } catch (error) { - enqueueErrorSnackBar({ - ...(CombinedGraphQLErrors.is(error) ? { apolloError: error } : {}), - }); + enqueueErrorSnackBar( + CombinedGraphQLErrors.is(error) + ? { apolloError: error } + : { message: error instanceof Error ? error.message : undefined }, + ); } }; }, diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts index 83f1fc05f1a..30f61c936b4 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts @@ -42,9 +42,11 @@ export const useHandleResetPassword = () => { enqueueErrorSnackBar({}); } } catch (error) { - enqueueErrorSnackBar({ - ...(CombinedGraphQLErrors.is(error) ? { apolloError: error } : {}), - }); + enqueueErrorSnackBar( + CombinedGraphQLErrors.is(error) + ? { apolloError: error } + : { message: error instanceof Error ? error.message : undefined }, + ); } }; }, diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSSO.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSSO.ts index a5db2c4be9e..bcf4ca030d4 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSSO.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSSO.ts @@ -25,10 +25,12 @@ export const useSSO = () => { }, }, }); - } catch (error: any) { - return enqueueErrorSnackBar({ - ...(CombinedGraphQLErrors.is(error) ? { apolloError: error } : {}), - }); + } catch (error: unknown) { + return enqueueErrorSnackBar( + CombinedGraphQLErrors.is(error) + ? { apolloError: error } + : { message: error instanceof Error ? error.message : undefined }, + ); } const authorizationURL = diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignUpInNewWorkspace.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignUpInNewWorkspace.ts index 74f0860b463..8e1f333e847 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignUpInNewWorkspace.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignUpInNewWorkspace.ts @@ -30,11 +30,16 @@ export const useSignUpInNewWorkspace = () => { newTab ? '_blank' : '_self', ); } catch (error) { - enqueueErrorSnackBar({ - ...(CombinedGraphQLErrors.is(error) + enqueueErrorSnackBar( + CombinedGraphQLErrors.is(error) ? { apolloError: error } - : { message: t`Workspace creation failed` }), - }); + : { + message: + error instanceof Error + ? error.message + : t`Workspace creation failed`, + }, + ); } }; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts index baab9e76b0b..64728107d3c 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts @@ -19,6 +19,7 @@ export const useWorkspaceFromInviteHash = () => { const workspaceInviteHash = useParams().workspaceInviteHash; const currentWorkspace = useAtomStateValue(currentWorkspaceState); const [initiallyLoggedIn] = useState(isDefined(currentWorkspace)); + const [hasRedirected, setHasRedirected] = useState(false); const { data: workspaceFromInviteHash, @@ -37,16 +38,17 @@ export const useWorkspaceFromInviteHash = () => { }, [error, enqueueErrorSnackBar, navigate]); useEffect(() => { - if (!workspaceFromInviteHash) return; + if (!workspaceFromInviteHash || hasRedirected) return; + + const inviteWorkspace = workspaceFromInviteHash.findWorkspaceFromInviteHash; - const data = workspaceFromInviteHash; if ( isDefined(currentWorkspace) && - isDefined(data?.findWorkspaceFromInviteHash) && - currentWorkspace.id === data.findWorkspaceFromInviteHash.id + isDefined(inviteWorkspace) && + currentWorkspace.id === inviteWorkspace.id ) { - const workspaceDisplayName = - data?.findWorkspaceFromInviteHash?.displayName; + setHasRedirected(true); + const workspaceDisplayName = inviteWorkspace.displayName; initiallyLoggedIn && enqueueInfoSnackBar({ message: workspaceDisplayName @@ -58,6 +60,7 @@ export const useWorkspaceFromInviteHash = () => { }, [ workspaceFromInviteHash, currentWorkspace, + hasRedirected, initiallyLoggedIn, enqueueInfoSnackBar, navigate, diff --git a/packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx b/packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx index 8a5d4c5fc82..e41c1b32d10 100644 --- a/packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx +++ b/packages/twenty-front/src/modules/error-handler/components/PromiseRejectionEffect.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect } from 'react'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { ApolloError } from '@apollo/client'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import { isDefined, type CustomError } from 'twenty-shared/utils'; @@ -16,7 +17,7 @@ export const PromiseRejectionEffect = () => { const handlePromiseRejection = useCallback( async (event: PromiseRejectionEvent) => { const error = event.reason; - if (CombinedGraphQLErrors.is(error)) { + if (CombinedGraphQLErrors.is(error) || error instanceof ApolloError) { enqueueErrorSnackBar({ apolloError: error, }); @@ -28,7 +29,9 @@ export const PromiseRejectionEffect = () => { error?.name === 'AbortError'; if (!isAbortError) { - enqueueErrorSnackBar({}); + enqueueErrorSnackBar( + error instanceof Error ? { message: error.message } : {}, + ); } try { diff --git a/packages/twenty-front/src/modules/front-components/hooks/useRequestApplicationTokenRefresh.ts b/packages/twenty-front/src/modules/front-components/hooks/useRequestApplicationTokenRefresh.ts index 255c7863689..1acafcf109f 100644 --- a/packages/twenty-front/src/modules/front-components/hooks/useRequestApplicationTokenRefresh.ts +++ b/packages/twenty-front/src/modules/front-components/hooks/useRequestApplicationTokenRefresh.ts @@ -77,6 +77,8 @@ export const useRequestApplicationTokenRefresh = ({ // If the refresh token itself is expired, fall back to refetching // the front component which issues a fresh token pair server-side. try { + // With the default errorPolicy ('none'), client.mutate() rejects on + // GraphQL/network errors, so error handling happens in the catch block. const renewResult = await apolloClient.mutate< RenewApplicationTokenMutation, RenewApplicationTokenMutationVariables @@ -88,19 +90,6 @@ export const useRequestApplicationTokenRefresh = ({ }, }); - if (isDefined(renewResult.error)) { - if ( - CombinedGraphQLErrors.is(renewResult.error) && - hasApplicationRefreshTokenInvalidOrExpiredSubCode( - renewResult.error.errors, - ) - ) { - return await refetchFrontComponentForNewTokenPair(); - } - - throw new Error(`Token renewal failed: ${renewResult.error.message}`); - } - const renewedTokenPair = renewResult.data?.renewApplicationToken; if (!isDefined(renewedTokenPair)) { diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx index 8e878254c55..7f0e659ac96 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx @@ -1,3 +1,4 @@ +import { useSnackBarOnQueryError } from '@/apollo/hooks/useSnackBarOnQueryError'; import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; @@ -13,12 +14,14 @@ import { export const InformationBannerBillingSubscriptionPaused = () => { const { redirect } = useRedirect(); - const { data, loading } = useQuery(BillingPortalSessionDocument, { + const { data, loading, error } = useQuery(BillingPortalSessionDocument, { variables: { returnUrlPath: getSettingsPath(SettingsPath.Billing), }, }); + useSnackBarOnQueryError(error); + const { [PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails, } = usePermissionFlagMap();