fix: address PR review comments for Apollo v4 migration
- Add missing await on signIn mutation in handleCredentialsSignIn (Sentry) - Preserve plain Error messages in useSnackBarOnQueryError instead of dropping to generic text - Handle non-GraphQL errors in catch blocks across auth hooks (useHandleResetPassword, useSignUpInNewWorkspace, useHandleResendEmailVerificationToken, useSSO) by passing error.message - Gate Apollo DevTools with IS_DEBUG_MODE in metadata ApolloProvider - Wrap client.mutate in try/catch in AuthService for Apollo v4 compat - Handle ApolloError in PromiseRejectionEffect alongside CombinedGraphQLErrors - Prevent duplicate redirects in useWorkspaceFromInviteHash effect - Wire billing query error to useSnackBarOnQueryError - Remove unreachable renewResult.error branch in useRequestApplicationTokenRefresh (catch block handles it) https://claude.ai/code/session_017uafSHYDZYzv7Tp6qnVDbB
This commit is contained in:
@@ -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],
|
||||
});
|
||||
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -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 },
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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`,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+9
-6
@@ -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,
|
||||
|
||||
+5
-2
@@ -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 {
|
||||
|
||||
+2
-13
@@ -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)) {
|
||||
|
||||
+4
-1
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user