fix(twenty-front): error on captcha initialisation (#11039)

This commit is contained in:
Antoine Moreaux
2025-03-19 17:09:35 +01:00
committed by GitHub
parent cfdb3f5778
commit 28028ca4c0
6 changed files with 72 additions and 69 deletions

View File

@ -1,5 +1,4 @@
import { AppRouter } from '@/app/components/AppRouter'; import { AppRouter } from '@/app/components/AppRouter';
import { CaptchaProvider } from '@/captcha/components/CaptchaProvider';
import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect'; import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect';
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
@ -21,19 +20,17 @@ export const App = () => {
<RecoilURLSyncJSON location={{ part: 'queryParams' }}> <RecoilURLSyncJSON location={{ part: 'queryParams' }}>
<AppErrorBoundary> <AppErrorBoundary>
<I18nProvider i18n={i18n}> <I18nProvider i18n={i18n}>
<CaptchaProvider> <RecoilDebugObserverEffect />
<RecoilDebugObserverEffect /> <ApolloDevLogEffect />
<ApolloDevLogEffect /> <SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager"> <IconsProvider>
<IconsProvider> <ExceptionHandlerProvider>
<ExceptionHandlerProvider> <HelmetProvider>
<HelmetProvider> <AppRouter />
<AppRouter /> </HelmetProvider>
</HelmetProvider> </ExceptionHandlerProvider>
</ExceptionHandlerProvider> </IconsProvider>
</IconsProvider> </SnackBarProviderScope>
</SnackBarProviderScope>
</CaptchaProvider>
</I18nProvider> </I18nProvider>
</AppErrorBoundary> </AppErrorBoundary>
</RecoilURLSyncJSON> </RecoilURLSyncJSON>

View File

@ -1,3 +1,4 @@
import { CaptchaProvider } from '@/captcha/components/CaptchaProvider';
import { ApolloProvider } from '@/apollo/components/ApolloProvider'; import { ApolloProvider } from '@/apollo/components/ApolloProvider';
import { GotoHotkeysEffectsProvider } from '@/app/effect-components/GotoHotkeysEffectsProvider'; import { GotoHotkeysEffectsProvider } from '@/app/effect-components/GotoHotkeysEffectsProvider';
import { PageChangeEffect } from '@/app/effect-components/PageChangeEffect'; import { PageChangeEffect } from '@/app/effect-components/PageChangeEffect';
@ -32,46 +33,48 @@ export const AppRouterProviders = () => {
const pageTitle = getPageTitleFromPath(pathname); const pageTitle = getPageTitleFromPath(pathname);
return ( return (
<ApolloProvider> <CaptchaProvider>
<BaseThemeProvider> <ApolloProvider>
<ClientConfigProviderEffect /> <BaseThemeProvider>
<ClientConfigProvider> <ClientConfigProviderEffect />
<ChromeExtensionSidecarEffect /> <ClientConfigProvider>
<ChromeExtensionSidecarProvider> <ChromeExtensionSidecarEffect />
<UserProviderEffect /> <ChromeExtensionSidecarProvider>
<WorkspaceProviderEffect /> <UserProviderEffect />
<UserProvider> <WorkspaceProviderEffect />
<AuthProvider> <UserProvider>
<ApolloMetadataClientProvider> <AuthProvider>
<ObjectMetadataItemsProvider> <ApolloMetadataClientProvider>
<ObjectMetadataItemsGater> <ObjectMetadataItemsProvider>
<PrefetchDataProvider> <ObjectMetadataItemsGater>
<UserThemeProviderEffect /> <PrefetchDataProvider>
<SnackBarProvider> <UserThemeProviderEffect />
<DialogManagerScope dialogManagerScopeId="dialog-manager"> <SnackBarProvider>
<DialogManager> <DialogManagerScope dialogManagerScopeId="dialog-manager">
<StrictMode> <DialogManager>
<PromiseRejectionEffect /> <StrictMode>
<GotoHotkeysEffectsProvider /> <PromiseRejectionEffect />
<ServerPreconnect /> <GotoHotkeysEffectsProvider />
<PageTitle title={pageTitle} /> <ServerPreconnect />
<PageFavicon /> <PageTitle title={pageTitle} />
<Outlet /> <PageFavicon />
</StrictMode> <Outlet />
</DialogManager> </StrictMode>
</DialogManagerScope> </DialogManager>
</SnackBarProvider> </DialogManagerScope>
<MainContextStoreProvider /> </SnackBarProvider>
</PrefetchDataProvider> <MainContextStoreProvider />
<PageChangeEffect /> </PrefetchDataProvider>
</ObjectMetadataItemsGater> <PageChangeEffect />
</ObjectMetadataItemsProvider> </ObjectMetadataItemsGater>
</ApolloMetadataClientProvider> </ObjectMetadataItemsProvider>
</AuthProvider> </ApolloMetadataClientProvider>
</UserProvider> </AuthProvider>
</ChromeExtensionSidecarProvider> </UserProvider>
</ClientConfigProvider> </ChromeExtensionSidecarProvider>
</BaseThemeProvider> </ClientConfigProvider>
</ApolloProvider> </BaseThemeProvider>
</ApolloProvider>
</CaptchaProvider>
); );
}; };

View File

@ -24,6 +24,7 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation'; import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath';
// TODO: break down into smaller functions and / or hooks // TODO: break down into smaller functions and / or hooks
// - moved usePageChangeEffectNavigateLocation into dedicated hook // - moved usePageChangeEffectNavigateLocation into dedicated hook
@ -178,15 +179,10 @@ export const PageChangeEffect = () => {
const isCaptchaScriptLoaded = useRecoilValue(isCaptchaScriptLoadedState); const isCaptchaScriptLoaded = useRecoilValue(isCaptchaScriptLoadedState);
useEffect(() => { useEffect(() => {
if ( if (isCaptchaScriptLoaded && isCaptchaRequiredForPath(location.pathname)) {
isCaptchaScriptLoaded &&
(isMatchingLocation(AppPath.SignInUp) ||
isMatchingLocation(AppPath.Invite) ||
isMatchingLocation(AppPath.ResetPassword))
) {
requestFreshCaptchaToken(); requestFreshCaptchaToken();
} }
}, [isCaptchaScriptLoaded, isMatchingLocation, requestFreshCaptchaToken]); }, [isCaptchaScriptLoaded, location.pathname, requestFreshCaptchaToken]);
return <></>; return <></>;
}; };

View File

@ -2,9 +2,12 @@ import React from 'react';
import { CaptchaProviderScriptLoaderEffect } from '@/captcha/components/CaptchaProviderScriptLoaderEffect'; import { CaptchaProviderScriptLoaderEffect } from '@/captcha/components/CaptchaProviderScriptLoaderEffect';
import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath';
import { useLocation } from 'react-router-dom';
export const CaptchaProvider = ({ children }: React.PropsWithChildren) => { export const CaptchaProvider = ({ children }: React.PropsWithChildren) => {
if (!isCaptchaRequiredForPath(window.location.pathname)) { const location = useLocation();
if (!isCaptchaRequiredForPath(location.pathname)) {
return <>{children}</>; return <>{children}</>;
} }

View File

@ -1,5 +1,5 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
@ -10,7 +10,7 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const CaptchaProviderScriptLoaderEffect = () => { export const CaptchaProviderScriptLoaderEffect = () => {
const captcha = useRecoilValue(captchaState); const captcha = useRecoilValue(captchaState);
const setIsCaptchaScriptLoaded = useSetRecoilState( const [isCaptchaScriptLoaded, setIsCaptchaScriptLoaded] = useRecoilState(
isCaptchaScriptLoadedState, isCaptchaScriptLoadedState,
); );
const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken(); const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken();
@ -46,8 +46,9 @@ export const CaptchaProviderScriptLoaderEffect = () => {
document.body.appendChild(scriptElement); document.body.appendChild(scriptElement);
} }
}, [captcha?.provider, captcha?.siteKey, setIsCaptchaScriptLoaded]); }, [captcha?.provider, captcha?.siteKey, setIsCaptchaScriptLoaded]);
useEffect(() => { useEffect(() => {
if (isUndefinedOrNull(captcha?.provider)) { if (isUndefinedOrNull(captcha?.provider) || !isCaptchaScriptLoaded) {
return; return;
} }
@ -68,7 +69,7 @@ export const CaptchaProviderScriptLoaderEffect = () => {
} }
return () => clearInterval(refreshInterval); return () => clearInterval(refreshInterval);
}, [captcha?.provider, requestFreshCaptchaToken]); }, [captcha?.provider, requestFreshCaptchaToken, isCaptchaScriptLoaded]);
return <></>; return <></>;
}; };

View File

@ -6,6 +6,7 @@ import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPa
import { captchaState } from '@/client-config/states/captchaState'; import { captchaState } from '@/client-config/states/captchaState';
import { CaptchaDriverType } from '~/generated-metadata/graphql'; import { CaptchaDriverType } from '~/generated-metadata/graphql';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { useLocation } from 'react-router-dom';
declare global { declare global {
interface Window { interface Window {
@ -20,10 +21,12 @@ export const useRequestFreshCaptchaToken = () => {
isRequestingCaptchaTokenState, isRequestingCaptchaTokenState,
); );
const location = useLocation();
const requestFreshCaptchaToken = useRecoilCallback( const requestFreshCaptchaToken = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
async () => { async () => {
if (!isCaptchaRequiredForPath(window.location.pathname)) { if (!isCaptchaRequiredForPath(location.pathname)) {
return; return;
} }
@ -62,7 +65,7 @@ export const useRequestFreshCaptchaToken = () => {
}); });
} }
}, },
[setCaptchaToken, setIsRequestingCaptchaToken], [location.pathname, setCaptchaToken, setIsRequestingCaptchaToken],
); );
return { requestFreshCaptchaToken }; return { requestFreshCaptchaToken };