Remove recoil sync (#11569)
Recoil-sync was causing issues with Firefox, replacing it with a simpler mechanism to hydrate variables on page load --------- Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
This commit is contained in:
@ -54,7 +54,6 @@
|
||||
"buffer": "^6.0.3",
|
||||
"docx": "^9.1.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"recoil-sync": "^0.2.0",
|
||||
"transliteration": "^2.3.5",
|
||||
"twenty-shared": "workspace:*",
|
||||
"twenty-ui": "workspace:*"
|
||||
|
||||
@ -9,16 +9,14 @@ import { i18n } from '@lingui/core';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { RecoilURLSyncJSON } from 'recoil-sync';
|
||||
import { initialI18nActivate } from '~/utils/i18n/initialI18nActivate';
|
||||
import { IconsProvider } from 'twenty-ui/display';
|
||||
import { initialI18nActivate } from '~/utils/i18n/initialI18nActivate';
|
||||
|
||||
initialI18nActivate();
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<RecoilURLSyncJSON location={{ part: 'queryParams' }}>
|
||||
<AppErrorBoundary
|
||||
resetOnLocationChange={false}
|
||||
FallbackComponent={AppRootErrorFallback}
|
||||
@ -37,7 +35,6 @@ export const App = () => {
|
||||
</SnackBarProviderScope>
|
||||
</I18nProvider>
|
||||
</AppErrorBoundary>
|
||||
</RecoilURLSyncJSON>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
|
||||
@ -26,6 +26,7 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
|
||||
import { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState';
|
||||
|
||||
// TODO: break down into smaller functions and / or hooks
|
||||
// - moved usePageChangeEffectNavigateLocation into dedicated hook
|
||||
@ -44,6 +45,8 @@ export const PageChangeEffect = () => {
|
||||
|
||||
const eventTracker = useEventTracker();
|
||||
|
||||
const { initializeQueryParamState } = useInitializeQueryParamState();
|
||||
|
||||
//TODO: refactor useResetTableRowSelection hook to not throw when the argument `recordTableId` is an empty string
|
||||
// - replace CoreObjectNamePlural.Person
|
||||
const objectNamePlural =
|
||||
@ -64,10 +67,12 @@ export const PageChangeEffect = () => {
|
||||
}, [location, previousLocation, executeTasksOnAnyLocationChange]);
|
||||
|
||||
useEffect(() => {
|
||||
initializeQueryParamState();
|
||||
|
||||
if (isDefined(pageChangeEffectNavigateLocation)) {
|
||||
navigate(pageChangeEffectNavigateLocation);
|
||||
}
|
||||
}, [navigate, pageChangeEffectNavigateLocation]);
|
||||
}, [navigate, pageChangeEffectNavigateLocation, initializeQueryParamState]);
|
||||
|
||||
useEffect(() => {
|
||||
const isLeavingRecordIndexPage = !!matchPath(
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isQueryParamInitializedState } from '@/app/states/isQueryParamInitializedState';
|
||||
import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState';
|
||||
import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
|
||||
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
|
||||
|
||||
// Initialize state that are hydrated from query parameters
|
||||
// We used to use recoil-sync to do this, but it was causing issues with Firefox
|
||||
export const useInitializeQueryParamState = () => {
|
||||
const initializeQueryParamState = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
() => {
|
||||
const isInitialized = snapshot
|
||||
.getLoadable(isQueryParamInitializedState)
|
||||
.getValue();
|
||||
|
||||
if (!isInitialized) {
|
||||
const handlers = {
|
||||
billingCheckoutSession: (value: string) => {
|
||||
try {
|
||||
const parsedValue = JSON.parse(decodeURIComponent(value));
|
||||
|
||||
if (
|
||||
typeof parsedValue === 'object' &&
|
||||
parsedValue !== null &&
|
||||
'plan' in parsedValue &&
|
||||
'interval' in parsedValue &&
|
||||
'requirePaymentMethod' in parsedValue
|
||||
) {
|
||||
set(
|
||||
billingCheckoutSessionState,
|
||||
parsedValue as BillingCheckoutSession,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Failed to parse billingCheckoutSession from URL',
|
||||
error,
|
||||
);
|
||||
set(
|
||||
billingCheckoutSessionState,
|
||||
BILLING_CHECKOUT_SESSION_DEFAULT_VALUE,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
|
||||
for (const [paramName, handler] of Object.entries(handlers)) {
|
||||
const value = queryParams.get(paramName);
|
||||
if (value !== null) {
|
||||
handler(value);
|
||||
}
|
||||
}
|
||||
|
||||
set(isQueryParamInitializedState, true);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return { initializeQueryParamState };
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export const isQueryParamInitializedState = createState<boolean>({
|
||||
key: 'isQueryParamInitializedState',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -10,18 +10,10 @@ const StyledContent = styled.div`
|
||||
|
||||
type AuthModalProps = {
|
||||
children: React.ReactNode;
|
||||
isOpenAnimated?: boolean;
|
||||
};
|
||||
|
||||
export const AuthModal = ({
|
||||
children,
|
||||
isOpenAnimated = true,
|
||||
}: AuthModalProps) => (
|
||||
<Modal
|
||||
padding={'none'}
|
||||
modalVariant="primary"
|
||||
isOpenAnimated={isOpenAnimated}
|
||||
>
|
||||
export const AuthModal = ({ children }: AuthModalProps) => (
|
||||
<Modal padding={'none'} modalVariant="primary">
|
||||
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
|
||||
<StyledContent>{children}</StyledContent>
|
||||
</ScrollWrapper>
|
||||
|
||||
@ -4,12 +4,10 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
|
||||
import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin';
|
||||
import { animateModalState } from '@/auth/states/animateModalState';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
import { EmailVerificationSent } from '../sign-in-up/components/EmailVerificationSent';
|
||||
@ -21,7 +19,6 @@ export const VerifyEmailEffect = () => {
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const [isError, setIsError] = useState(false);
|
||||
const setAnimateModal = useSetRecoilState(animateModalState);
|
||||
|
||||
const email = searchParams.get('email');
|
||||
const emailVerificationToken = searchParams.get('emailVerificationToken');
|
||||
@ -52,7 +49,6 @@ export const VerifyEmailEffect = () => {
|
||||
|
||||
const workspaceUrl = getWorkspaceUrl(workspaceUrls);
|
||||
if (workspaceUrl.slice(0, -1) !== window.location.origin) {
|
||||
setAnimateModal(false);
|
||||
return await redirectToWorkspaceDomain(workspaceUrl, AppPath.Verify, {
|
||||
loginToken: loginToken.token,
|
||||
});
|
||||
|
||||
@ -41,7 +41,6 @@ import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTi
|
||||
import { currentUserState } from '../states/currentUserState';
|
||||
import { tokenPairState } from '../states/tokenPairState';
|
||||
|
||||
import { animateModalState } from '@/auth/states/animateModalState';
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import {
|
||||
SignInUpStep,
|
||||
@ -115,7 +114,6 @@ export const useAuth = () => {
|
||||
const goToRecoilSnapshot = useGotoRecoilSnapshot();
|
||||
|
||||
const setDateTimeFormat = useSetRecoilState(dateTimeFormatState);
|
||||
const setAnimateModal = useSetRecoilState(animateModalState);
|
||||
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
|
||||
@ -422,7 +420,6 @@ export const useAuth = () => {
|
||||
}
|
||||
|
||||
if (isMultiWorkspaceEnabled) {
|
||||
setAnimateModal(false);
|
||||
return await redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(signUpResult.data.signUp.workspace.workspaceUrls),
|
||||
isEmailVerificationRequired ? AppPath.SignInUp : AppPath.Verify,
|
||||
@ -446,7 +443,6 @@ export const useAuth = () => {
|
||||
handleGetAuthTokensFromLoginToken,
|
||||
setSignInUpStep,
|
||||
setSearchParams,
|
||||
setAnimateModal,
|
||||
isEmailVerificationRequired,
|
||||
redirectToWorkspaceDomain,
|
||||
],
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
import { urlSyncEffect } from 'recoil-sync';
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export const animateModalState = createState<boolean>({
|
||||
key: 'animateModalState',
|
||||
defaultValue: true,
|
||||
effects: [
|
||||
urlSyncEffect({
|
||||
itemKey: 'animateModal',
|
||||
refine: (value: unknown) => {
|
||||
if (typeof value === 'boolean') {
|
||||
return {
|
||||
type: 'success',
|
||||
value: value as boolean,
|
||||
warnings: [],
|
||||
} as const;
|
||||
}
|
||||
return {
|
||||
type: 'failure',
|
||||
message: 'Invalid animateModalState',
|
||||
path: [] as any,
|
||||
} as const;
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -1,34 +1,8 @@
|
||||
import { BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type';
|
||||
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
|
||||
import { syncEffect } from 'recoil-sync';
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export const billingCheckoutSessionState = createState<BillingCheckoutSession>({
|
||||
key: 'billingCheckoutSessionState',
|
||||
defaultValue: BILLING_CHECKOUT_SESSION_DEFAULT_VALUE,
|
||||
effects: [
|
||||
syncEffect({
|
||||
itemKey: 'billingCheckoutSession',
|
||||
refine: (value: unknown) => {
|
||||
if (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
'plan' in value &&
|
||||
'interval' in value &&
|
||||
'requirePaymentMethod' in value
|
||||
) {
|
||||
return {
|
||||
type: 'success',
|
||||
value: value as BillingCheckoutSession,
|
||||
warnings: [],
|
||||
} as const;
|
||||
}
|
||||
return {
|
||||
type: 'failure',
|
||||
message: 'Invalid BillingCheckoutSessionState',
|
||||
path: [] as any,
|
||||
} as const;
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { animateModalState } from '@/auth/states/animateModalState';
|
||||
import { billingCheckoutSessionState } from '@/auth/states/billingCheckoutSessionState';
|
||||
import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/BillingCheckoutSessionDefaultValue';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
@ -7,7 +6,6 @@ export const useBuildSearchParamsFromUrlSyncedStates = () => {
|
||||
const buildSearchParamsFromUrlSyncedStates = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async () => {
|
||||
const animateModal = snapshot.getLoadable(animateModalState).getValue();
|
||||
const billingCheckoutSession = snapshot
|
||||
.getLoadable(billingCheckoutSessionState)
|
||||
.getValue();
|
||||
@ -18,7 +16,6 @@ export const useBuildSearchParamsFromUrlSyncedStates = () => {
|
||||
billingCheckoutSession: JSON.stringify(billingCheckoutSession),
|
||||
}
|
||||
: {}),
|
||||
...(animateModal === false ? { animateModal: 'false' } : {}),
|
||||
};
|
||||
|
||||
return output;
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
import { animateModalState } from '@/auth/states/animateModalState';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { WorkspaceUrls } from '~/generated/graphql';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
|
||||
export const useImpersonationRedirect = () => {
|
||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||
const setAnimateModal = useSetRecoilState(animateModalState);
|
||||
|
||||
const executeImpersonationRedirect = async (
|
||||
workspaceUrls: WorkspaceUrls,
|
||||
loginToken: string,
|
||||
) => {
|
||||
setAnimateModal(false);
|
||||
return await redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(workspaceUrls),
|
||||
AppPath.Verify,
|
||||
|
||||
@ -174,7 +174,6 @@ export type ModalProps = React.PropsWithChildren & {
|
||||
className?: string;
|
||||
hotkeyScope?: ModalHotkeyScope;
|
||||
onEnter?: () => void;
|
||||
isOpenAnimated?: boolean;
|
||||
modalVariant?: ModalVariants;
|
||||
} & (
|
||||
| { isClosable: true; onClose: () => void }
|
||||
@ -197,7 +196,6 @@ export const Modal = ({
|
||||
isClosable = false,
|
||||
onClose,
|
||||
modalVariant = 'primary',
|
||||
isOpenAnimated = true,
|
||||
}: ModalProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
@ -263,7 +261,7 @@ export const Modal = ({
|
||||
ref={modalRef}
|
||||
size={size}
|
||||
padding={padding}
|
||||
initial={isOpenAnimated ? 'hidden' : 'visible'}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
layout
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { AuthModal } from '@/auth/components/AuthModal';
|
||||
import { animateModalState } from '@/auth/states/animateModalState';
|
||||
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
import { AppFullScreenErrorFallback } from '@/error-handler/components/AppFullScreenErrorFallback';
|
||||
@ -19,7 +18,6 @@ import { Global, css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useScreenSize } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
@ -65,7 +63,6 @@ export const DefaultLayout = () => {
|
||||
const windowsWidth = useScreenSize().width;
|
||||
const showAuthModal = useShowAuthModal();
|
||||
const useShowFullScreen = useShowFullscreen();
|
||||
const animateModal = useRecoilValue(animateModalState);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -111,7 +108,7 @@ export const DefaultLayout = () => {
|
||||
</StyledMainContainer>
|
||||
<AnimatePresence mode="wait">
|
||||
<LayoutGroup>
|
||||
<AuthModal isOpenAnimated={animateModal}>
|
||||
<AuthModal>
|
||||
<Outlet />
|
||||
</AuthModal>
|
||||
</LayoutGroup>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
|
||||
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { animateModalState } from '@/auth/states/animateModalState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { Workspaces, workspacesState } from '@/auth/states/workspaces';
|
||||
import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl';
|
||||
@ -67,7 +66,6 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
||||
const setMultiWorkspaceDropdownState = useSetRecoilState(
|
||||
multiWorkspaceDropdownState,
|
||||
);
|
||||
const setAnimateModal = useSetRecoilState(animateModalState);
|
||||
|
||||
const handleChange = async (workspace: Workspaces[0]) => {
|
||||
redirectToWorkspaceDomain(getWorkspaceUrl(workspace.workspaceUrls));
|
||||
@ -76,7 +74,6 @@ export const MultiWorkspaceDropdownDefaultComponents = () => {
|
||||
const createWorkspace = () => {
|
||||
signUpInNewWorkspaceMutation({
|
||||
onCompleted: async (data) => {
|
||||
setAnimateModal(false);
|
||||
return await redirectToWorkspaceDomain(
|
||||
getWorkspaceUrl(data.signUpInNewWorkspace.workspace.workspaceUrls),
|
||||
AppPath.Verify,
|
||||
|
||||
@ -172,7 +172,7 @@ export const ChooseYourPlan = () => {
|
||||
?.baseProduct.name;
|
||||
|
||||
return (
|
||||
<Modal.Content>
|
||||
<Modal.Content isVerticalCentered>
|
||||
{isDefined(baseProductPrice) && isDefined(billing) ? (
|
||||
<>
|
||||
<Title noMarginTop>
|
||||
|
||||
27
yarn.lock
27
yarn.lock
@ -16495,13 +16495,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@recoiljs/refine@npm:^0.1.1":
|
||||
version: 0.1.1
|
||||
resolution: "@recoiljs/refine@npm:0.1.1"
|
||||
checksum: 10c0/27ca1c4ea500b1b99a3af5ee48d6749310e5138e83b87ddfb41304e2222fa64567acb985f340334ab73980202ab277a0f133c40817fbec786076c06bfb3f5363
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@redis/bloom@npm:1.2.0, @redis/bloom@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@redis/bloom@npm:1.2.0"
|
||||
@ -50073,18 +50066,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"recoil-sync@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "recoil-sync@npm:0.2.0"
|
||||
dependencies:
|
||||
"@recoiljs/refine": "npm:^0.1.1"
|
||||
transit-js: "npm:^0.8.874"
|
||||
peerDependencies:
|
||||
recoil: ">=0.7.3"
|
||||
checksum: 10c0/f3a671a3cfcecadb5bbb22d47b3b040721be1013ee91d4fa0570a64ca1707eb68e818b291a82e133551adf4a2dd2fc9a0715d550c1f1e3db82f3b8d9169e2a5a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"recoil@npm:^0.7.7":
|
||||
version: 0.7.7
|
||||
resolution: "recoil@npm:0.7.7"
|
||||
@ -54388,13 +54369,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"transit-js@npm:^0.8.874":
|
||||
version: 0.8.874
|
||||
resolution: "transit-js@npm:0.8.874"
|
||||
checksum: 10c0/6ca0b413f1e3780a4a56b9bbde54b67a2ffefda1c052f8dc68bf28c1a3df4c29baa6f17f60484c9f2fb056a8c7dcd1fe04d24e091fb99afe1525e4d4406ad58a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"transliteration@npm:^2.3.5":
|
||||
version: 2.3.5
|
||||
resolution: "transliteration@npm:2.3.5"
|
||||
@ -55005,7 +54979,6 @@ __metadata:
|
||||
eslint-plugin-unused-imports: "npm:^3.0.0"
|
||||
file-saver: "npm:^2.0.5"
|
||||
optionator: "npm:^0.9.1"
|
||||
recoil-sync: "npm:^0.2.0"
|
||||
transliteration: "npm:^2.3.5"
|
||||
twenty-shared: "workspace:*"
|
||||
twenty-ui: "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user