fix: login redirection to active objects (#15366)
Fixes - https://github.com/twentyhq/twenty/issues/15364 - Removed `activeNonSystemObjectMetadataItems` from the hook as it was not necessary. Hook use `alphaSortedActiveNonSystemObjectMetadataItems` now. - Correctly check for read permissions. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
co-authored by
Charles Bochet
parent
51fdaf2731
commit
0cbd1e8ec5
@@ -12,6 +12,7 @@ import {
|
||||
useEventTracker,
|
||||
} from '@/analytics/hooks/useEventTracker';
|
||||
import { useExecuteTasksOnAnyLocationChange } from '@/app/hooks/useExecuteTasksOnAnyLocationChange';
|
||||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
|
||||
import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath';
|
||||
@@ -88,6 +89,10 @@ export const PageChangeEffect = () => {
|
||||
const { executeTasksOnAnyLocationChange } =
|
||||
useExecuteTasksOnAnyLocationChange();
|
||||
|
||||
const isAppEffectRedirectEnabled = useRecoilValue(
|
||||
isAppEffectRedirectEnabledState,
|
||||
);
|
||||
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const { resetFocusStackToFocusItem } = useResetFocusStackToFocusItem();
|
||||
@@ -110,10 +115,18 @@ export const PageChangeEffect = () => {
|
||||
useEffect(() => {
|
||||
initializeQueryParamState();
|
||||
|
||||
if (isDefined(pageChangeEffectNavigateLocation)) {
|
||||
if (
|
||||
isDefined(pageChangeEffectNavigateLocation) &&
|
||||
isAppEffectRedirectEnabled
|
||||
) {
|
||||
navigate(pageChangeEffectNavigateLocation);
|
||||
}
|
||||
}, [navigate, pageChangeEffectNavigateLocation, initializeQueryParamState]);
|
||||
}, [
|
||||
navigate,
|
||||
pageChangeEffectNavigateLocation,
|
||||
initializeQueryParamState,
|
||||
isAppEffectRedirectEnabled,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const isLeavingRecordIndexPage = !!matchPath(
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
export const isAppEffectRedirectEnabledState = createState<boolean>({
|
||||
key: 'isAppEffectRedirectEnabledState',
|
||||
defaultValue: true,
|
||||
});
|
||||
@@ -66,10 +66,14 @@ import { type AuthToken } from '~/generated/graphql';
|
||||
import { cookieStorage } from '~/utils/cookie-storage';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
import { loginTokenState } from '../states/loginTokenState';
|
||||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
|
||||
export const useAuth = () => {
|
||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||
const setLoginToken = useSetRecoilState(loginTokenState);
|
||||
const setIsAppEffectRedirectEnabled = useSetRecoilState(
|
||||
isAppEffectRedirectEnabledState,
|
||||
);
|
||||
|
||||
const { origin } = useOrigin();
|
||||
const { requestFreshCaptchaToken } = useRequestFreshCaptchaToken();
|
||||
@@ -317,13 +321,20 @@ export const useAuth = () => {
|
||||
async (authTokens: AuthTokenPair) => {
|
||||
handleSetAuthTokens(authTokens);
|
||||
|
||||
setIsAppEffectRedirectEnabled(false);
|
||||
|
||||
// TODO: We can't parallelize this yet because when loadCurrentUSer is loaded
|
||||
// then UserProvider updates its children and PrefetchDataProvider is then triggered
|
||||
// which requires the correct metadata to be loaded (not the mocks)
|
||||
await loadCurrentUser();
|
||||
await refreshObjectMetadataItems();
|
||||
},
|
||||
[loadCurrentUser, handleSetAuthTokens, refreshObjectMetadataItems],
|
||||
[
|
||||
loadCurrentUser,
|
||||
handleSetAuthTokens,
|
||||
refreshObjectMetadataItems,
|
||||
setIsAppEffectRedirectEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
const handleGetAuthTokensFromLoginToken = useCallback(
|
||||
|
||||
@@ -16,10 +16,8 @@ export const useDefaultHomePagePath = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const {
|
||||
activeNonSystemObjectMetadataItems,
|
||||
alphaSortedActiveNonSystemObjectMetadataItems,
|
||||
} = useFilteredObjectMetadataItems();
|
||||
const { alphaSortedActiveNonSystemObjectMetadataItems } =
|
||||
useFilteredObjectMetadataItems();
|
||||
|
||||
const readableAlphaSortedActiveNonSystemObjectMetadataItems = useMemo(() => {
|
||||
return alphaSortedActiveNonSystemObjectMetadataItems.filter((item) => {
|
||||
@@ -36,11 +34,11 @@ export const useDefaultHomePagePath = () => {
|
||||
|
||||
const getActiveObjectMetadataItemMatchingId = useCallback(
|
||||
(objectMetadataId: string) => {
|
||||
return activeNonSystemObjectMetadataItems.find(
|
||||
return readableAlphaSortedActiveNonSystemObjectMetadataItems.find(
|
||||
(item) => item.id === objectMetadataId,
|
||||
);
|
||||
},
|
||||
[activeNonSystemObjectMetadataItems],
|
||||
[readableAlphaSortedActiveNonSystemObjectMetadataItems],
|
||||
);
|
||||
|
||||
const getFirstView = useRecoilCallback(({ snapshot }) => {
|
||||
@@ -76,20 +74,13 @@ export const useDefaultHomePagePath = () => {
|
||||
.getLoadable(lastVisitedObjectMetadataItemIdState)
|
||||
.getValue();
|
||||
|
||||
if (
|
||||
!isDefined(lastVisitedObjectMetadataItemId) ||
|
||||
!getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: lastVisitedObjectMetadataItemId,
|
||||
}).canReadObjectRecords
|
||||
) {
|
||||
return firstObjectPathInfo;
|
||||
}
|
||||
|
||||
const lastVisitedObjectMetadataItem =
|
||||
getActiveObjectMetadataItemMatchingId(
|
||||
lastVisitedObjectMetadataItemId,
|
||||
);
|
||||
const lastVisitedObjectMetadataItem = isDefined(
|
||||
lastVisitedObjectMetadataItemId,
|
||||
)
|
||||
? getActiveObjectMetadataItemMatchingId(
|
||||
lastVisitedObjectMetadataItemId,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (isDefined(lastVisitedObjectMetadataItem)) {
|
||||
return {
|
||||
@@ -101,12 +92,7 @@ export const useDefaultHomePagePath = () => {
|
||||
return firstObjectPathInfo;
|
||||
};
|
||||
},
|
||||
[
|
||||
firstObjectPathInfo,
|
||||
getActiveObjectMetadataItemMatchingId,
|
||||
getFirstView,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
[firstObjectPathInfo, getActiveObjectMetadataItemMatchingId, getFirstView],
|
||||
);
|
||||
|
||||
const defaultHomePagePath = useMemo(() => {
|
||||
|
||||
+3
-5
@@ -2,8 +2,8 @@ import React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider';
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader';
|
||||
|
||||
export const ObjectMetadataItemsProvider = ({
|
||||
@@ -11,12 +11,10 @@ export const ObjectMetadataItemsProvider = ({
|
||||
}: React.PropsWithChildren) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const isAppWaitingForFreshObjectMetadata = useRecoilValue(
|
||||
isAppWaitingForFreshObjectMetadataState,
|
||||
);
|
||||
const shouldAppBeLoading = useRecoilValue(shouldAppBeLoadingState);
|
||||
|
||||
const shouldDisplayChildren =
|
||||
!isAppWaitingForFreshObjectMetadata && objectMetadataItems.length > 0;
|
||||
!shouldAppBeLoading && objectMetadataItems.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
+9
-5
@@ -1,5 +1,6 @@
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
@@ -17,12 +18,15 @@ export const useLoadMockedObjectMetadataItems = () => {
|
||||
set(objectMetadataItemsState, generatedMockObjectMetadataItems);
|
||||
}
|
||||
|
||||
if (snapshot.getLoadable(shouldAppBeLoadingState).getValue() === true) {
|
||||
set(shouldAppBeLoadingState, false);
|
||||
}
|
||||
|
||||
if (
|
||||
snapshot
|
||||
.getLoadable(isAppWaitingForFreshObjectMetadataState)
|
||||
.getValue() === true
|
||||
snapshot.getLoadable(isAppEffectRedirectEnabledState).getValue() ===
|
||||
false
|
||||
) {
|
||||
set(isAppWaitingForFreshObjectMetadataState, false);
|
||||
set(isAppEffectRedirectEnabledState, true);
|
||||
}
|
||||
},
|
||||
[],
|
||||
|
||||
+13
-2
@@ -1,7 +1,8 @@
|
||||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { enrichObjectMetadataItemsWithPermissions } from '@/object-metadata/utils/enrichObjectMetadataItemsWithPermissions';
|
||||
import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems';
|
||||
@@ -75,7 +76,17 @@ export const useRefreshObjectMetadataItems = (
|
||||
newObjectMetadataItems.length > 0
|
||||
) {
|
||||
set(objectMetadataItemsState, newObjectMetadataItems);
|
||||
set(isAppWaitingForFreshObjectMetadataState, false);
|
||||
}
|
||||
|
||||
if (snapshot.getLoadable(shouldAppBeLoadingState).getValue() === true) {
|
||||
set(shouldAppBeLoadingState, false);
|
||||
}
|
||||
|
||||
if (
|
||||
snapshot.getLoadable(isAppEffectRedirectEnabledState).getValue() ===
|
||||
false
|
||||
) {
|
||||
set(isAppEffectRedirectEnabledState, true);
|
||||
}
|
||||
|
||||
return newObjectMetadataItems;
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
export const isAppWaitingForFreshObjectMetadataState = createState<boolean>({
|
||||
key: 'isAppWaitingForFreshObjectMetadataState',
|
||||
defaultValue: false,
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
export const shouldAppBeLoadingState = createState<boolean>({
|
||||
key: 'shouldAppBeLoadingState',
|
||||
defaultValue: false,
|
||||
});
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { PageLayoutTestWrapper } from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
|
||||
import { WidgetPlaceholder } from '@/page-layout/widgets/components/WidgetPlaceholder';
|
||||
import { type Meta, type StoryObj } from '@storybook/react';
|
||||
@@ -18,7 +18,7 @@ const meta: Meta<typeof WidgetPlaceholder> = {
|
||||
objectMetadataItemsState,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
snapshot.set(isAppWaitingForFreshObjectMetadataState, false);
|
||||
snapshot.set(shouldAppBeLoadingState, false);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { type MutableSnapshot } from 'recoil';
|
||||
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { PageLayoutTestWrapper } from '@/page-layout/hooks/__tests__/PageLayoutTestWrapper';
|
||||
import { WidgetRenderer } from '@/page-layout/widgets/components/WidgetRenderer';
|
||||
@@ -122,7 +122,7 @@ const meta: Meta<typeof WidgetRenderer> = {
|
||||
objectMetadataItemsState,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
snapshot.set(isAppWaitingForFreshObjectMetadataState, false);
|
||||
snapshot.set(shouldAppBeLoadingState, false);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
+9
-5
@@ -1,17 +1,21 @@
|
||||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
export const useImpersonationAuth = () => {
|
||||
const { getAuthTokensFromLoginToken } = useAuth();
|
||||
const setIsAppWaitingForFreshObjectMetadata = useSetRecoilState(
|
||||
isAppWaitingForFreshObjectMetadataState,
|
||||
const setShouldAppBeLoading = useSetRecoilState(shouldAppBeLoadingState);
|
||||
const setIsAppEffectRedirectEnabled = useSetRecoilState(
|
||||
isAppEffectRedirectEnabledState,
|
||||
);
|
||||
|
||||
const executeImpersonationAuth = async (loginToken: string) => {
|
||||
setIsAppWaitingForFreshObjectMetadata(true);
|
||||
setShouldAppBeLoading(true);
|
||||
setIsAppEffectRedirectEnabled(false);
|
||||
await getAuthTokensFromLoginToken(loginToken);
|
||||
setIsAppWaitingForFreshObjectMetadata(false);
|
||||
setShouldAppBeLoading(false);
|
||||
setIsAppEffectRedirectEnabled(true);
|
||||
};
|
||||
|
||||
return { executeImpersonationAuth };
|
||||
|
||||
Reference in New Issue
Block a user