refactor(captcha): simplify interval handling in hook (#10974)

Close #10708

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Antoine Moreaux
2025-03-18 14:23:31 +01:00
committed by GitHub
parent 6255207aa3
commit 291d6082c9
4 changed files with 33 additions and 22 deletions

View File

@ -1,16 +1,19 @@
import { useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
import { getCaptchaUrlByProvider } from '@/captcha/utils/getCaptchaUrlByProvider';
import { captchaState } from '@/client-config/states/captchaState';
import { CaptchaDriverType } from '~/generated/graphql';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const CaptchaProviderScriptLoaderEffect = () => {
const captcha = useRecoilValue(captchaState);
const setIsCaptchaScriptLoaded = useSetRecoilState(
isCaptchaScriptLoadedState,
);
const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken();
useEffect(() => {
if (!captcha?.provider || !captcha.siteKey) {
@ -43,6 +46,29 @@ export const CaptchaProviderScriptLoaderEffect = () => {
document.body.appendChild(scriptElement);
}
}, [captcha?.provider, captcha?.siteKey, setIsCaptchaScriptLoaded]);
useEffect(() => {
if (isUndefinedOrNull(captcha?.provider)) {
return;
}
let refreshInterval: NodeJS.Timeout;
switch (captcha.provider) {
case CaptchaDriverType.GoogleRecaptcha:
// Google reCAPTCHA tokens expire after 120 seconds, refresh at 110 seconds
refreshInterval = setInterval(requestFreshCaptchaToken, 110 * 1000);
break;
case CaptchaDriverType.Turnstile:
// Cloudflare Turnstile tokens expire after 500 seconds, refresh at 480 seconds
refreshInterval = setInterval(requestFreshCaptchaToken, 480 * 1000);
break;
default:
// Note: hCaptcha has a callback system for expiration that we're not implementing now
return;
}
return () => clearInterval(refreshInterval);
}, [captcha?.provider, requestFreshCaptchaToken]);
return <></>;
};

View File

@ -1,14 +1,13 @@
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MultiWorkspaceDropdownId';
import { NavigationDrawerHotKeyScope } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerHotKeyScope';
import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/states/multiWorkspaceDropdownState';
import { useRecoilState } from 'recoil';
import { useMemo } from 'react';
import { MultiWorkspaceDropdownClickableComponent } from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownClickableComponent';
import { MultiWorkspaceDropdownDefaultComponents } from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents';
import { MultiWorkspaceDropdownThemesComponents } from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownThemesComponents';
import { MultiWorkspaceDropdownWorkspacesListComponents } from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MULTI_WORKSPACE_DROPDOWN_ID } from '@/ui/navigation/navigation-drawer/constants/MultiWorkspaceDropdownId';
import { multiWorkspaceDropdownState } from '@/ui/navigation/navigation-drawer/states/multiWorkspaceDropdownState';
import { NavigationDrawerHotKeyScope } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerHotKeyScope';
import { useMemo } from 'react';
import { useRecoilState } from 'recoil';
export const MultiWorkspaceDropdownButton = () => {
const [multiWorkspaceDropdown, setMultiWorkspaceDropdown] = useRecoilState(
@ -26,8 +25,6 @@ export const MultiWorkspaceDropdownButton = () => {
}
}, [multiWorkspaceDropdown]);
const { isDropdownOpen } = useDropdown(MULTI_WORKSPACE_DROPDOWN_ID);
return (
<Dropdown
dropdownId={MULTI_WORKSPACE_DROPDOWN_ID}
@ -35,11 +32,7 @@ export const MultiWorkspaceDropdownButton = () => {
scope: NavigationDrawerHotKeyScope.MultiWorkspaceDropdownButton,
}}
dropdownOffset={{ y: -35, x: -5 }}
clickableComponent={
<MultiWorkspaceDropdownClickableComponent
isDropdownOpen={isDropdownOpen}
/>
}
clickableComponent={<MultiWorkspaceDropdownClickableComponent />}
dropdownComponents={<DropdownComponents />}
onClose={() => {
setMultiWorkspaceDropdown('default');

View File

@ -11,11 +11,7 @@ import { useRecoilValue } from 'recoil';
import { useTheme } from '@emotion/react';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
export const MultiWorkspaceDropdownClickableComponent = ({
isDropdownOpen,
}: {
isDropdownOpen?: boolean;
}) => {
export const MultiWorkspaceDropdownClickableComponent = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const theme = useTheme();
@ -36,7 +32,6 @@ export const MultiWorkspaceDropdownClickableComponent = ({
</NavigationDrawerAnimatedCollapseWrapper>
<NavigationDrawerAnimatedCollapseWrapper>
<StyledIconChevronDown
isDropdownOpen={isDropdownOpen}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>

View File

@ -32,12 +32,9 @@ export const StyledLabel = styled.div`
export const StyledIconChevronDown = styled(IconChevronDown)<{
disabled?: boolean;
isDropdownOpen?: boolean;
}>`
align-items: center;
color: ${({ disabled, theme }) =>
disabled ? theme.font.color.extraLight : theme.font.color.tertiary};
display: flex;
rotate: ${({ isDropdownOpen }) => (isDropdownOpen ? '-180deg' : '0deg')};
transition: rotate 0.1s ease-in-out;
`;