Various SDK improvements (#18115)
## Summary - **Refactor frontend metadata loading architecture**: Split the monolithic `EagerMetadataLoadEffect` into focused provider effects (`UserMetadataProviderEffect`, `ObjectMetadataProviderEffect`, `ViewMetadataProviderEffect`) orchestrated by `MetadataProviderEffects`. Replaced `UserProvider` + `ObjectMetadataItemsProvider` with a single `MetadataGater` that gates rendering on `isAppMetadataReadyState`. The metadata store now validates view-object consistency before promoting views, and `updateDraft` skips no-op updates via deep equality checks. - **SDK CLI improvements**: Added `app:typecheck` command, improved error handling in API sync (extracts GraphQL error messages), added `serializeError` utility for human-readable error output, added `error` file status to dev mode orchestrator with UI support, and fixed ClickHouse migration/seed commands to use `transpile-only`.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { AgentChatProvider } from '@/ai/components/AgentChatProvider';
|
||||
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
|
||||
import { MetadataGater } from '@/metadata-store/components/MetadataGater';
|
||||
import { IsAppMetadataReadyEffect } from '@/metadata-store/effect-components/IsAppMetadataReadyEffect';
|
||||
import { GotoHotkeysEffectsProvider } from '@/app/effect-components/GotoHotkeysEffectsProvider';
|
||||
import { MetadataProviderInitialEffects } from '@/metadata-store/effect-components/MetadataProviderInitialEffects';
|
||||
import { PageChangeEffect } from '@/app/effect-components/PageChangeEffect';
|
||||
import { AuthProvider } from '@/auth/components/AuthProvider';
|
||||
import { CaptchaProvider } from '@/captcha/components/CaptchaProvider';
|
||||
@@ -13,7 +16,7 @@ import { ErrorMessageEffect } from '@/error-handler/components/ErrorMessageEffec
|
||||
import { PromiseRejectionEffect } from '@/error-handler/components/PromiseRejectionEffect';
|
||||
import { HeadlessFrontComponentMountRoot } from '@/front-components/components/HeadlessFrontComponentMountRoot';
|
||||
import { ApolloCoreProvider } from '@/object-metadata/components/ApolloCoreProvider';
|
||||
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
|
||||
import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider';
|
||||
import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider';
|
||||
import { SSEProvider } from '@/sse-db-event/components/SSEProvider';
|
||||
import { SupportChatEffect } from '@/support/components/SupportChatEffect';
|
||||
@@ -25,10 +28,7 @@ import { BaseThemeProvider } from '@/ui/theme/components/BaseThemeProvider';
|
||||
import { UserThemeProviderEffect } from '@/ui/theme/components/UserThemeProviderEffect';
|
||||
import { PageFavicon } from '@/ui/utilities/page-favicon/components/PageFavicon';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
|
||||
import { EagerMetadataLoadEffect } from '@/users/components/EagerMetadataLoadEffect';
|
||||
import { LazyMetadataLoadEffect } from '@/users/components/LazyMetadataLoadEffect';
|
||||
import { MetadataProviderEffect } from '@/users/components/MetadataProviderEffect';
|
||||
import { UserProvider } from '@/users/components/UserProvider';
|
||||
import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect';
|
||||
import { StrictMode } from 'react';
|
||||
import { Outlet, useLocation } from 'react-router-dom';
|
||||
@@ -42,19 +42,19 @@ export const AppRouterProviders = () => {
|
||||
<ApolloProvider>
|
||||
<BaseThemeProvider>
|
||||
<ClientConfigProviderEffect />
|
||||
<MetadataProviderEffect />
|
||||
<EagerMetadataLoadEffect />
|
||||
<MetadataProviderInitialEffects />
|
||||
<LazyMetadataLoadEffect />
|
||||
<IsAppMetadataReadyEffect />
|
||||
<WorkspaceProviderEffect />
|
||||
<ClientConfigProvider>
|
||||
<CaptchaProvider>
|
||||
<ChromeExtensionSidecarEffect />
|
||||
<ChromeExtensionSidecarProvider>
|
||||
<UserProvider>
|
||||
<MetadataGater>
|
||||
<AuthProvider>
|
||||
<ApolloCoreProvider>
|
||||
<SSEProvider>
|
||||
<ObjectMetadataItemsProvider>
|
||||
<PreComputedChipGeneratorsProvider>
|
||||
<PrefetchDataProvider>
|
||||
<UserThemeProviderEffect />
|
||||
<SnackBarProvider>
|
||||
@@ -81,11 +81,11 @@ export const AppRouterProviders = () => {
|
||||
<SupportChatEffect />
|
||||
</PrefetchDataProvider>
|
||||
<PageChangeEffect />
|
||||
</ObjectMetadataItemsProvider>
|
||||
</PreComputedChipGeneratorsProvider>
|
||||
</SSEProvider>
|
||||
</ApolloCoreProvider>
|
||||
</AuthProvider>
|
||||
</UserProvider>
|
||||
</MetadataGater>
|
||||
</ChromeExtensionSidecarProvider>
|
||||
</CaptchaProvider>
|
||||
</ClientConfigProvider>
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import {
|
||||
metadataStoreState,
|
||||
type MetadataKey,
|
||||
type MetadataLoadEntry,
|
||||
} from '@/app/states/metadataStoreState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const EMPTY_ENTRY: MetadataLoadEntry = {
|
||||
current: [],
|
||||
draft: [],
|
||||
status: 'empty',
|
||||
};
|
||||
|
||||
// Views reference objects via objectMetadataId — both drafts must agree on IDs.
|
||||
const areDraftsConsistent = (
|
||||
objectsDraft: object[],
|
||||
viewsDraft: object[],
|
||||
): boolean => {
|
||||
const objectIds = new Set(
|
||||
objectsDraft.map((item) => (item as { id: string }).id),
|
||||
);
|
||||
|
||||
return viewsDraft.every((view) =>
|
||||
objectIds.has((view as { objectMetadataId: string }).objectMetadataId),
|
||||
);
|
||||
};
|
||||
|
||||
export const useMetadataStore = () => {
|
||||
const store = useStore();
|
||||
|
||||
const updateDraft = useCallback(
|
||||
(key: MetadataKey, data: object[]) => {
|
||||
store.set(metadataStoreState.atomFamily(key), (prev) => ({
|
||||
...prev,
|
||||
draft: data,
|
||||
status: 'draft_pending' as const,
|
||||
}));
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
// Validates consistency then atomically promotes all 'draft_pending' drafts
|
||||
// to current. If objects and views are inconsistent (e.g. mocked objects with
|
||||
// real views), resets the stale entries back to 'empty' so effects re-fetch.
|
||||
const applyChanges = useCallback(() => {
|
||||
const objectsEntry = store.get(metadataStoreState.atomFamily('objects'));
|
||||
const viewsEntry = store.get(metadataStoreState.atomFamily('views'));
|
||||
|
||||
if (
|
||||
objectsEntry.status === 'draft_pending' &&
|
||||
viewsEntry.status === 'draft_pending' &&
|
||||
!areDraftsConsistent(objectsEntry.draft, viewsEntry.draft)
|
||||
) {
|
||||
store.set(metadataStoreState.atomFamily('objects'), EMPTY_ENTRY);
|
||||
store.set(metadataStoreState.atomFamily('views'), EMPTY_ENTRY);
|
||||
return;
|
||||
}
|
||||
|
||||
const allKeys: MetadataKey[] = [
|
||||
'objects',
|
||||
'views',
|
||||
'pageLayouts',
|
||||
'logicFunctions',
|
||||
];
|
||||
|
||||
for (const key of allKeys) {
|
||||
const entry = store.get(metadataStoreState.atomFamily(key));
|
||||
|
||||
if (entry.status === 'draft_pending') {
|
||||
store.set(metadataStoreState.atomFamily(key), {
|
||||
current: entry.draft,
|
||||
draft: [],
|
||||
status: 'loaded',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
store.set(shouldAppBeLoadingState.atom, false);
|
||||
}, [store]);
|
||||
|
||||
return { updateDraft, applyChanges };
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import { metadataStoreState } from '@/app/states/metadataStoreState';
|
||||
import { createSelectorV2 } from '@/ui/utilities/state/jotai/utils/createSelectorV2';
|
||||
|
||||
export const isAppLoadingState = createSelectorV2<boolean>({
|
||||
key: 'isAppLoadingState',
|
||||
get: ({ get }) => {
|
||||
const objectsEntry = get(metadataStoreState, 'objects');
|
||||
const viewsEntry = get(metadataStoreState, 'views');
|
||||
|
||||
return objectsEntry.status !== 'loaded' || viewsEntry.status !== 'loaded';
|
||||
},
|
||||
});
|
||||
@@ -35,7 +35,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
|
||||
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useSignUpInNewWorkspace } from '@/auth/sign-in-up/hooks/useSignUpInNewWorkspace';
|
||||
import { metadataStoreState } from '@/app/states/metadataStoreState';
|
||||
import { useReloadWorkspaceMetadata } from '@/metadata-store/hooks/useReloadWorkspaceMetadata';
|
||||
import { lastAuthenticatedMethodState } from '@/auth/states/lastAuthenticatedMethodState';
|
||||
import { loginTokenState } from '@/auth/states/loginTokenState';
|
||||
import {
|
||||
@@ -58,8 +58,6 @@ import { useOrigin } from '@/domain-manager/hooks/useOrigin';
|
||||
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { sseClientState } from '@/sse-db-event/states/sseClientState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser';
|
||||
@@ -89,7 +87,8 @@ export const useAuth = () => {
|
||||
);
|
||||
const { loadCurrentUser } = useLoadCurrentUser();
|
||||
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { reloadWorkspaceMetadata, resetToMockedMetadata } =
|
||||
useReloadWorkspaceMetadata();
|
||||
const { createWorkspace } = useSignUpInNewWorkspace();
|
||||
|
||||
const setSignInUpStep = useSetRecoilStateV2(signInUpStepState);
|
||||
@@ -123,7 +122,6 @@ export const useAuth = () => {
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
|
||||
|
||||
const clearSession = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
@@ -161,26 +159,6 @@ export const useAuth = () => {
|
||||
|
||||
jotaiStore.set(workspaceAuthProvidersState.atom, authProvidersValue);
|
||||
jotaiStore.set(workspacePublicDataState.atom, workspacePublicDataValue);
|
||||
jotaiStore.set(metadataStoreState.atomFamily('objects'), {
|
||||
current: [],
|
||||
draft: [],
|
||||
status: 'empty',
|
||||
});
|
||||
jotaiStore.set(metadataStoreState.atomFamily('views'), {
|
||||
current: [],
|
||||
draft: [],
|
||||
status: 'empty',
|
||||
});
|
||||
jotaiStore.set(metadataStoreState.atomFamily('pageLayouts'), {
|
||||
current: [],
|
||||
draft: [],
|
||||
status: 'empty',
|
||||
});
|
||||
jotaiStore.set(metadataStoreState.atomFamily('logicFunctions'), {
|
||||
current: [],
|
||||
draft: [],
|
||||
status: 'empty',
|
||||
});
|
||||
jotaiStore.set(domainConfigurationState.atom, domainConfigurationValue);
|
||||
jotaiStore.set(
|
||||
isCaptchaScriptLoadedState.atom,
|
||||
@@ -209,14 +187,14 @@ export const useAuth = () => {
|
||||
|
||||
await client.clearStore();
|
||||
setLastAuthenticateWorkspaceDomain(null);
|
||||
await loadMockedObjectMetadataItems();
|
||||
await resetToMockedMetadata();
|
||||
navigate(AppPath.SignInUp);
|
||||
},
|
||||
[
|
||||
goToRecoilSnapshot,
|
||||
client,
|
||||
setLastAuthenticateWorkspaceDomain,
|
||||
loadMockedObjectMetadataItems,
|
||||
resetToMockedMetadata,
|
||||
navigate,
|
||||
],
|
||||
);
|
||||
@@ -344,19 +322,15 @@ export const useAuth = () => {
|
||||
const handleLoadWorkspaceAfterAuthentication = useCallback(
|
||||
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();
|
||||
await reloadWorkspaceMetadata();
|
||||
},
|
||||
[
|
||||
loadCurrentUser,
|
||||
handleSetAuthTokens,
|
||||
refreshObjectMetadataItems,
|
||||
reloadWorkspaceMetadata,
|
||||
setIsAppEffectRedirectEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { metadataStoreState } from '@/app/states/metadataStoreState';
|
||||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { MainContextStoreProviderEffect } from '@/context-store/components/MainContextStoreProviderEffect';
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView';
|
||||
|
||||
+18
-12
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import { isAppLoadingState } from '@/app/states/isAppLoadingState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { useDateTimeFormat } from '@/localization/hooks/useDateTimeFormat';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { UserContext } from '@/users/contexts/UserContext';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -10,23 +11,28 @@ import { AppPath } from 'twenty-shared/types';
|
||||
import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader';
|
||||
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
||||
|
||||
export const UserProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const isAppLoading = useRecoilValueV2(isAppLoadingState);
|
||||
export const MetadataGater = ({ children }: React.PropsWithChildren) => {
|
||||
const isAppMetadataReady = useRecoilValueV2(isAppMetadataReadyState);
|
||||
const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState);
|
||||
const isLoggedIn = useIsLogged();
|
||||
const location = useLocation();
|
||||
|
||||
const { dateFormat, timeFormat, timeZone } = useDateTimeFormat();
|
||||
|
||||
const shouldShowLoader =
|
||||
isAppLoading &&
|
||||
isLoggedIn &&
|
||||
!isMatchingLocation(location, AppPath.Verify) &&
|
||||
!isMatchingLocation(location, AppPath.VerifyEmail) &&
|
||||
!isMatchingLocation(location, AppPath.CreateWorkspace);
|
||||
const isOnExcludedPath =
|
||||
isMatchingLocation(location, AppPath.Verify) ||
|
||||
isMatchingLocation(location, AppPath.VerifyEmail) ||
|
||||
isMatchingLocation(location, AppPath.CreateWorkspace);
|
||||
|
||||
return shouldShowLoader ? (
|
||||
<UserOrMetadataLoader />
|
||||
) : (
|
||||
const shouldShowLoader =
|
||||
(!isAppMetadataReady && isLoggedIn && !isOnExcludedPath) ||
|
||||
objectMetadataItems.length === 0;
|
||||
|
||||
if (shouldShowLoader) {
|
||||
return <UserOrMetadataLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
dateFormat,
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
|
||||
export const IsAppMetadataReadyEffect = () => {
|
||||
const isLoggedIn = useIsLogged();
|
||||
const currentUser = useRecoilValueV2(currentUserState);
|
||||
const currentWorkspace = useRecoilValueV2(currentWorkspaceState);
|
||||
const objectsEntry = useFamilyRecoilValueV2(metadataStoreState, 'objects');
|
||||
const viewsEntry = useFamilyRecoilValueV2(metadataStoreState, 'views');
|
||||
const setIsAppMetadataReady = useSetRecoilStateV2(isAppMetadataReadyState);
|
||||
|
||||
console.log('objectsEntry', objectsEntry);
|
||||
console.log('viewsEntry', viewsEntry);
|
||||
console.log('isLoggedIn', isLoggedIn);
|
||||
console.log('currentUser', currentUser);
|
||||
console.log('currentWorkspace', currentWorkspace);
|
||||
console.log('setIsAppMetadataReady', setIsAppMetadataReady);
|
||||
|
||||
useEffect(() => {
|
||||
const hasActiveWorkspace = isWorkspaceActiveOrSuspended(currentWorkspace);
|
||||
|
||||
const areObjectsLoaded = objectsEntry.status === 'loaded';
|
||||
const areViewsLoaded = viewsEntry.status === 'loaded';
|
||||
|
||||
if (!areObjectsLoaded) {
|
||||
setIsAppMetadataReady(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const isReady =
|
||||
!isLoggedIn ||
|
||||
(isDefined(currentUser) && (!hasActiveWorkspace || areViewsLoaded));
|
||||
|
||||
setIsAppMetadataReady(isReady);
|
||||
}, [
|
||||
isLoggedIn,
|
||||
currentUser,
|
||||
currentWorkspace,
|
||||
objectsEntry.status,
|
||||
viewsEntry.status,
|
||||
setIsAppMetadataReady,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { ObjectMetadataProviderInitialEffect } from '@/metadata-store/effect-components/ObjectMetadataProviderInitialEffect';
|
||||
import { UserMetadataProviderInitialEffect } from '@/metadata-store/effect-components/UserMetadataProviderInitialEffect';
|
||||
import { ViewMetadataProviderInitialEffect } from '@/metadata-store/effect-components/ViewMetadataProviderInitialEffect';
|
||||
|
||||
export const MetadataProviderInitialEffects = () => (
|
||||
<>
|
||||
<UserMetadataProviderInitialEffect />
|
||||
<ObjectMetadataProviderInitialEffect />
|
||||
<ViewMetadataProviderInitialEffect />
|
||||
</>
|
||||
);
|
||||
+29
-20
@@ -1,45 +1,54 @@
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useStore } from 'jotai';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const ObjectMetadataItemsLoadEffect = () => {
|
||||
const currentUser = useRecoilValueV2(currentUserState);
|
||||
export const ObjectMetadataProviderInitialEffect = () => {
|
||||
const isCurrentUserLoaded = useRecoilValueV2(isCurrentUserLoadedState);
|
||||
const currentWorkspace = useRecoilValueV2(currentWorkspaceState);
|
||||
const store = useStore();
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
return;
|
||||
}
|
||||
if (isInitialized) return;
|
||||
if (!isCurrentUserLoaded) return;
|
||||
|
||||
const shouldLoadReal = isWorkspaceActiveOrSuspended(currentWorkspace);
|
||||
|
||||
const loadObjectMetadata = async () => {
|
||||
if (
|
||||
isUndefinedOrNull(currentUser) ||
|
||||
!isWorkspaceActiveOrSuspended(currentWorkspace)
|
||||
) {
|
||||
await loadMockedObjectMetadataItems();
|
||||
} else {
|
||||
if (shouldLoadReal) {
|
||||
await refreshObjectMetadataItems();
|
||||
} else {
|
||||
await loadMockedObjectMetadataItems();
|
||||
}
|
||||
|
||||
const loadedItems = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objects', loadedItems);
|
||||
applyChanges();
|
||||
setIsInitialized(true);
|
||||
};
|
||||
|
||||
loadObjectMetadata();
|
||||
}, [
|
||||
currentUser,
|
||||
currentWorkspace,
|
||||
loadMockedObjectMetadataItems,
|
||||
refreshObjectMetadataItems,
|
||||
isInitialized,
|
||||
isCurrentUserLoaded,
|
||||
currentWorkspace,
|
||||
refreshObjectMetadataItems,
|
||||
loadMockedObjectMetadataItems,
|
||||
store,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
return null;
|
||||
};
|
||||
+39
-24
@@ -1,5 +1,3 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
@@ -8,6 +6,7 @@ import { currentWorkspaceDeletedMembersState } from '@/auth/states/currentWorksp
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
|
||||
import { useInitializeFormatPreferences } from '@/localization/hooks/useInitializeFormatPreferences';
|
||||
import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
@@ -15,7 +14,8 @@ import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetReco
|
||||
import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { enUS } from 'date-fns/locale';
|
||||
import { useStore } from 'jotai';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { AppPath, type ObjectPermissions } from 'twenty-shared/types';
|
||||
@@ -29,10 +29,12 @@ import { dateLocaleStateV2 } from '~/localization/states/dateLocaleStateV2';
|
||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
||||
|
||||
export const MetadataProviderEffect = () => {
|
||||
export const UserMetadataProviderInitialEffect = () => {
|
||||
const location = useLocation();
|
||||
|
||||
const isLoggedIn = useIsLogged();
|
||||
const currentUser = useRecoilValueV2(currentUserState);
|
||||
const store = useStore();
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const setCurrentUser = useSetRecoilStateV2(currentUserState);
|
||||
const setCurrentWorkspace = useSetRecoilStateV2(currentWorkspaceState);
|
||||
@@ -40,10 +42,22 @@ export const MetadataProviderEffect = () => {
|
||||
currentUserWorkspaceState,
|
||||
);
|
||||
const setAvailableWorkspaces = useSetRecoilStateV2(availableWorkspacesState);
|
||||
const { initializeFormatPreferences } = useInitializeFormatPreferences();
|
||||
const isLoggedIn = useIsLogged();
|
||||
const setCurrentWorkspaceMember = useSetRecoilStateV2(
|
||||
currentWorkspaceMemberState,
|
||||
);
|
||||
const setCurrentWorkspaceMembers = useSetRecoilStateV2(
|
||||
currentWorkspaceMembersState,
|
||||
);
|
||||
const setCurrentWorkspaceMembersWithDeleted = useSetRecoilStateV2(
|
||||
currentWorkspaceDeletedMembersState,
|
||||
);
|
||||
const setIsCurrentUserLoaded = useSetRecoilStateV2(isCurrentUserLoadedState);
|
||||
|
||||
const store = useStore();
|
||||
const { initializeFormatPreferences } = useInitializeFormatPreferences();
|
||||
|
||||
const isOnAuthPath =
|
||||
isMatchingLocation(location, AppPath.Verify) ||
|
||||
isMatchingLocation(location, AppPath.VerifyEmail);
|
||||
|
||||
const updateLocaleCatalog = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
@@ -63,28 +77,23 @@ export const MetadataProviderEffect = () => {
|
||||
[store],
|
||||
);
|
||||
|
||||
const setCurrentWorkspaceMember = useSetRecoilStateV2(
|
||||
currentWorkspaceMemberState,
|
||||
);
|
||||
const setCurrentWorkspaceMembers = useSetRecoilStateV2(
|
||||
currentWorkspaceMembersState,
|
||||
);
|
||||
const setCurrentWorkspaceMembersWithDeleted = useSetRecoilStateV2(
|
||||
currentWorkspaceDeletedMembersState,
|
||||
);
|
||||
|
||||
const shouldSkip =
|
||||
!isLoggedIn ||
|
||||
isDefined(currentUser) ||
|
||||
isMatchingLocation(location, AppPath.Verify) ||
|
||||
isMatchingLocation(location, AppPath.VerifyEmail);
|
||||
const shouldSkipUserQuery =
|
||||
!isLoggedIn || isDefined(currentUser) || isOnAuthPath;
|
||||
|
||||
const { data: userQueryData, loading: userQueryLoading } =
|
||||
useGetCurrentUserQuery({
|
||||
skip: shouldSkip,
|
||||
skip: shouldSkipUserQuery,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) return;
|
||||
|
||||
if (!isLoggedIn) {
|
||||
setIsCurrentUserLoaded(true);
|
||||
setIsInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userQueryLoading || !isDefined(userQueryData?.currentUser)) {
|
||||
return;
|
||||
}
|
||||
@@ -160,7 +169,12 @@ export const MetadataProviderEffect = () => {
|
||||
if (isDefined(availableWorkspaces)) {
|
||||
setAvailableWorkspaces(availableWorkspaces);
|
||||
}
|
||||
|
||||
setIsCurrentUserLoaded(true);
|
||||
setIsInitialized(true);
|
||||
}, [
|
||||
isInitialized,
|
||||
isLoggedIn,
|
||||
userQueryLoading,
|
||||
userQueryData?.currentUser,
|
||||
setCurrentUser,
|
||||
@@ -172,6 +186,7 @@ export const MetadataProviderEffect = () => {
|
||||
initializeFormatPreferences,
|
||||
setCurrentWorkspaceMembersWithDeleted,
|
||||
updateLocaleCatalog,
|
||||
setIsCurrentUserLoaded,
|
||||
]);
|
||||
|
||||
return null;
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
import {
|
||||
ViewType as CoreViewType,
|
||||
useFindAllCoreViewsLazyQuery,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
const INDEX_VIEW_TYPES = [
|
||||
CoreViewType.TABLE,
|
||||
CoreViewType.KANBAN,
|
||||
CoreViewType.CALENDAR,
|
||||
];
|
||||
|
||||
export const ViewMetadataProviderInitialEffect = () => {
|
||||
const isLoggedIn = useIsLogged();
|
||||
const isCurrentUserLoaded = useRecoilValueV2(isCurrentUserLoadedState);
|
||||
const currentWorkspace = useRecoilValueV2(currentWorkspaceState);
|
||||
const store = useStore();
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
const [findAllCoreViews] = useFindAllCoreViewsLazyQuery();
|
||||
|
||||
const setIndexCoreViews = useCallback(
|
||||
(indexViews: CoreViewWithRelations[]) => {
|
||||
const existingCoreViews = store.get(coreViewsState.atom);
|
||||
const existingFieldsWidgetViews = existingCoreViews.filter(
|
||||
(view) => view.type === CoreViewType.FIELDS_WIDGET,
|
||||
);
|
||||
const mergedViews = [...indexViews, ...existingFieldsWidgetViews];
|
||||
|
||||
if (!isDeeplyEqual(existingCoreViews, mergedViews)) {
|
||||
store.set(coreViewsState.atom, mergedViews);
|
||||
}
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) return;
|
||||
if (!isCurrentUserLoaded) return;
|
||||
|
||||
if (!isLoggedIn || !isWorkspaceActiveOrSuspended(currentWorkspace)) {
|
||||
setIsInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const loadViews = async () => {
|
||||
const result = await findAllCoreViews({
|
||||
variables: { viewTypes: INDEX_VIEW_TYPES },
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
if (isDefined(result.data?.getCoreViews)) {
|
||||
setIndexCoreViews(result.data.getCoreViews);
|
||||
updateDraft('views', result.data.getCoreViews);
|
||||
applyChanges();
|
||||
}
|
||||
|
||||
setIsInitialized(true);
|
||||
};
|
||||
|
||||
loadViews();
|
||||
}, [
|
||||
isInitialized,
|
||||
isCurrentUserLoaded,
|
||||
isLoggedIn,
|
||||
currentWorkspace,
|
||||
findAllCoreViews,
|
||||
setIndexCoreViews,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
ALL_METADATA_KEYS,
|
||||
metadataStoreState,
|
||||
type MetadataKey,
|
||||
type MetadataLoadEntry,
|
||||
} from '@/metadata-store/states/metadataStoreState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { type createStore, useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
type JotaiStore = ReturnType<typeof createStore>;
|
||||
|
||||
const EMPTY_ENTRY: MetadataLoadEntry = {
|
||||
current: [],
|
||||
draft: [],
|
||||
status: 'empty',
|
||||
};
|
||||
|
||||
const areViewsConsistentWithObjects = (
|
||||
viewsDraft: object[],
|
||||
objectsCurrent: object[],
|
||||
): boolean => {
|
||||
const objectIds = new Set(
|
||||
objectsCurrent.map((item) => (item as { id: string }).id),
|
||||
);
|
||||
|
||||
return viewsDraft.every((view) =>
|
||||
objectIds.has((view as { objectMetadataId: string }).objectMetadataId),
|
||||
);
|
||||
};
|
||||
|
||||
export const resetMetadataStore = (store: JotaiStore) => {
|
||||
for (const key of ALL_METADATA_KEYS) {
|
||||
store.set(metadataStoreState.atomFamily(key), EMPTY_ENTRY);
|
||||
}
|
||||
|
||||
store.set(isAppMetadataReadyState.atom, false);
|
||||
};
|
||||
|
||||
const promoteEntry = (store: JotaiStore, key: MetadataKey) => {
|
||||
const entry = store.get(metadataStoreState.atomFamily(key));
|
||||
|
||||
store.set(metadataStoreState.atomFamily(key), {
|
||||
current: entry.draft,
|
||||
draft: [],
|
||||
status: 'loaded',
|
||||
});
|
||||
};
|
||||
|
||||
export const useMetadataStore = () => {
|
||||
const store = useStore();
|
||||
|
||||
const updateDraft = useCallback(
|
||||
(key: MetadataKey, data: object[]) => {
|
||||
const currentEntry = store.get(metadataStoreState.atomFamily(key));
|
||||
|
||||
if (
|
||||
currentEntry.status === 'loaded' &&
|
||||
isDeeplyEqual(currentEntry.current, data)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
store.set(metadataStoreState.atomFamily(key), (prev) => ({
|
||||
...prev,
|
||||
draft: data,
|
||||
status: 'draft_pending' as const,
|
||||
}));
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
const applyChanges = useCallback((): boolean => {
|
||||
let promoted = false;
|
||||
|
||||
for (const key of ALL_METADATA_KEYS) {
|
||||
if (key === 'views') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entry = store.get(metadataStoreState.atomFamily(key));
|
||||
|
||||
if (entry.status === 'draft_pending') {
|
||||
promoteEntry(store, key);
|
||||
promoted = true;
|
||||
}
|
||||
}
|
||||
|
||||
const viewsEntry = store.get(metadataStoreState.atomFamily('views'));
|
||||
|
||||
if (viewsEntry.status === 'draft_pending') {
|
||||
const objectsEntry = store.get(metadataStoreState.atomFamily('objects'));
|
||||
|
||||
if (
|
||||
areViewsConsistentWithObjects(viewsEntry.draft, objectsEntry.current)
|
||||
) {
|
||||
promoteEntry(store, 'views');
|
||||
promoted = true;
|
||||
}
|
||||
}
|
||||
|
||||
return promoted;
|
||||
}, [store]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
resetMetadataStore(store);
|
||||
}, [store]);
|
||||
|
||||
return { updateDraft, applyChanges, resetMetadataStore: reset };
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useReloadWorkspaceMetadata = () => {
|
||||
const store = useStore();
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
|
||||
const { updateDraft, applyChanges, resetMetadataStore } = useMetadataStore();
|
||||
|
||||
const reloadWorkspaceMetadata = useCallback(async () => {
|
||||
resetMetadataStore();
|
||||
|
||||
await refreshObjectMetadataItems();
|
||||
const loadedObjects = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objects', loadedObjects);
|
||||
applyChanges();
|
||||
|
||||
const loadedViews = store.get(coreViewsState.atom);
|
||||
updateDraft('views', loadedViews);
|
||||
applyChanges();
|
||||
}, [
|
||||
resetMetadataStore,
|
||||
refreshObjectMetadataItems,
|
||||
store,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
]);
|
||||
|
||||
const resetToMockedMetadata = useCallback(async () => {
|
||||
resetMetadataStore();
|
||||
|
||||
await loadMockedObjectMetadataItems();
|
||||
const loadedObjects = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objects', loadedObjects);
|
||||
applyChanges();
|
||||
}, [
|
||||
resetMetadataStore,
|
||||
loadMockedObjectMetadataItems,
|
||||
store,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
]);
|
||||
|
||||
return { reloadWorkspaceMetadata, resetToMockedMetadata };
|
||||
};
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
|
||||
|
||||
export const shouldAppBeLoadingState = createStateV2<boolean>({
|
||||
key: 'shouldAppBeLoadingState',
|
||||
export const isAppMetadataReadyState = createStateV2<boolean>({
|
||||
key: 'isAppMetadataReadyState',
|
||||
defaultValue: false,
|
||||
});
|
||||
+7
@@ -8,6 +8,13 @@ export type MetadataKey =
|
||||
| 'pageLayouts'
|
||||
| 'logicFunctions';
|
||||
|
||||
export const ALL_METADATA_KEYS: MetadataKey[] = [
|
||||
'objects',
|
||||
'views',
|
||||
'pageLayouts',
|
||||
'logicFunctions',
|
||||
];
|
||||
|
||||
export type MetadataLoadEntry = {
|
||||
current: object[];
|
||||
draft: object[];
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PreComputedChipGeneratorsProvider } from '@/object-metadata/components/PreComputedChipGeneratorsProvider';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader';
|
||||
|
||||
export const ObjectMetadataItemsProvider = ({
|
||||
children,
|
||||
}: React.PropsWithChildren) => {
|
||||
const objectMetadataItems = useRecoilValueV2(objectMetadataItemsState);
|
||||
|
||||
const shouldAppBeLoading = useRecoilValueV2(shouldAppBeLoadingState);
|
||||
|
||||
const shouldDisplayChildren =
|
||||
!shouldAppBeLoading && objectMetadataItems.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldDisplayChildren ? (
|
||||
<PreComputedChipGeneratorsProvider>
|
||||
{children}
|
||||
</PreComputedChipGeneratorsProvider>
|
||||
) : (
|
||||
<UserOrMetadataLoader />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+16
-11
@@ -8,18 +8,11 @@ import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-met
|
||||
import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector';
|
||||
import { useFamilySelectorValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilySelectorValueV2';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useColumnDefinitionsFromObjectMetadata = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
) => {
|
||||
const activeFieldMetadataItems = objectMetadataItem.readableFields.filter(
|
||||
(field) =>
|
||||
field.isActive &&
|
||||
// Allow label identifier field (e.g. id for junction tables) even if it's a hidden system field
|
||||
(!isHiddenSystemField(field) ||
|
||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId),
|
||||
);
|
||||
|
||||
const filterableFieldMetadataItems = useFamilySelectorValueV2(
|
||||
availableFieldMetadataItemsForFilterFamilySelector,
|
||||
{
|
||||
@@ -34,10 +27,16 @@ export const useColumnDefinitionsFromObjectMetadata = (
|
||||
},
|
||||
);
|
||||
|
||||
const restrictedFieldMetadataIds: string[] = [];
|
||||
const columnDefinitions: ColumnDefinition<FieldMetadata>[] = useMemo(() => {
|
||||
const activeFieldMetadataItems =
|
||||
objectMetadataItem.readableFields.filter(
|
||||
(field) =>
|
||||
field.isActive &&
|
||||
(!isHiddenSystemField(field) ||
|
||||
field.id === objectMetadataItem.labelIdentifierFieldMetadataId),
|
||||
);
|
||||
|
||||
const columnDefinitions: ColumnDefinition<FieldMetadata>[] =
|
||||
activeFieldMetadataItems
|
||||
return activeFieldMetadataItems
|
||||
.map((field, index) =>
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
position: index,
|
||||
@@ -47,6 +46,7 @@ export const useColumnDefinitionsFromObjectMetadata = (
|
||||
)
|
||||
.filter(filterAvailableTableColumns)
|
||||
.filter((column) => {
|
||||
const restrictedFieldMetadataIds: string[] = [];
|
||||
return !restrictedFieldMetadataIds.includes(column.fieldMetadataId);
|
||||
})
|
||||
.map((column) => {
|
||||
@@ -65,6 +65,11 @@ export const useColumnDefinitionsFromObjectMetadata = (
|
||||
isSortable: existsInSortDefinitions,
|
||||
};
|
||||
});
|
||||
}, [
|
||||
filterableFieldMetadataItems,
|
||||
sortableFieldMetadataItems,
|
||||
objectMetadataItem,
|
||||
]);
|
||||
|
||||
return {
|
||||
columnDefinitions,
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
|
||||
import {
|
||||
@@ -39,7 +39,7 @@ const meta: Meta<typeof DashboardWidgetPlaceholder> = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
instanceId: PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
|
||||
+15
-15
@@ -13,7 +13,7 @@ import { CatalogDecorator, type CatalogStory } from 'twenty-ui/testing';
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
@@ -284,7 +284,7 @@ export const WithNumberChart: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(widget);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
@@ -372,7 +372,7 @@ export const WithGaugeChart: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(widget);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
@@ -464,7 +464,7 @@ export const WithBarChart: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(widget);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
@@ -559,7 +559,7 @@ export const SmallWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(widget);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
@@ -658,7 +658,7 @@ export const MediumWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(widget);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
@@ -757,7 +757,7 @@ export const LargeWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(widget);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
@@ -852,7 +852,7 @@ export const WideWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(widget);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
@@ -951,7 +951,7 @@ export const TallWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(widget);
|
||||
snapshot.set(
|
||||
pageLayoutPersistedComponentState.atomFamily({
|
||||
@@ -1046,7 +1046,7 @@ export const WithManyToOneRelationFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
PageLayoutType.RECORD_PAGE,
|
||||
@@ -1163,7 +1163,7 @@ export const WithOneToManyRelationFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
PageLayoutType.RECORD_PAGE,
|
||||
@@ -1272,7 +1272,7 @@ export const OneToManyRelationFieldWidgetWithSeeAllButton: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
PageLayoutType.RECORD_PAGE,
|
||||
@@ -1408,7 +1408,7 @@ export const OnMobile: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
PageLayoutType.RECORD_PAGE,
|
||||
@@ -1507,7 +1507,7 @@ export const InSidePanel: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
PageLayoutType.RECORD_PAGE,
|
||||
@@ -1667,7 +1667,7 @@ export const Catalog: CatalogStory<Story, typeof WidgetRenderer> = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
|
||||
if (state === 'hover') {
|
||||
snapshot.set(
|
||||
|
||||
+18
-18
@@ -10,7 +10,7 @@ import { expect, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { ApolloCoreClientContext } from '@/object-metadata/contexts/ApolloCoreClientContext';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { isAppMetadataReadyState } from '@/metadata-store/states/isAppMetadataReadyState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { recordStoreFamilyStateV2 } from '@/object-record/record-store/states/recordStoreFamilyStateV2';
|
||||
@@ -369,7 +369,7 @@ export const TextFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -467,7 +467,7 @@ export const AddressFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -568,7 +568,7 @@ export const NumberFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -666,7 +666,7 @@ export const LinkFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -764,7 +764,7 @@ export const ManyToOneRelationFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -873,7 +873,7 @@ export const OneToManyRelationFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -973,7 +973,7 @@ export const BooleanFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -1070,7 +1070,7 @@ export const CurrencyFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -1167,7 +1167,7 @@ export const EmailsFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
personObjectMetadataItem.id,
|
||||
@@ -1265,7 +1265,7 @@ export const PhonesFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
personObjectMetadataItem.id,
|
||||
@@ -1363,7 +1363,7 @@ export const SelectFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
opportunityObjectMetadataItem.id,
|
||||
@@ -1466,7 +1466,7 @@ export const MultiSelectFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -1568,7 +1568,7 @@ export const TimelineActivityRelationFieldWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
timelineActivityObjectMetadataItem.id,
|
||||
@@ -1676,7 +1676,7 @@ export const ManyToOneRelationCardWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -1793,7 +1793,7 @@ export const OneToManyRelationCardWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
@@ -1892,7 +1892,7 @@ export const TimelineActivityRelationCardWidget: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
timelineActivityObjectMetadataItem.id,
|
||||
@@ -2062,7 +2062,7 @@ export const OneToManyRelationCardWidgetWithProgressiveLoading: Story = {
|
||||
objectMetadataItemsState.atom,
|
||||
generatedMockObjectMetadataItems,
|
||||
);
|
||||
jotaiStore.set(shouldAppBeLoadingState.atom, false);
|
||||
jotaiStore.set(isAppMetadataReadyState.atom, true);
|
||||
const pageLayoutData = createPageLayoutWithWidget(
|
||||
widget,
|
||||
companyObjectMetadataItem.id,
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { shouldAppBeLoadingState } from '@/object-metadata/states/shouldAppBeLoadingState';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
|
||||
export const useImpersonationAuth = () => {
|
||||
const { getAuthTokensFromLoginToken } = useAuth();
|
||||
const setShouldAppBeLoading = useSetRecoilStateV2(shouldAppBeLoadingState);
|
||||
const setIsAppEffectRedirectEnabled = useSetRecoilStateV2(
|
||||
isAppEffectRedirectEnabledState,
|
||||
);
|
||||
|
||||
const executeImpersonationAuth = async (loginToken: string) => {
|
||||
setShouldAppBeLoading(true);
|
||||
setIsAppEffectRedirectEnabled(false);
|
||||
await getAuthTokensFromLoginToken(loginToken);
|
||||
setShouldAppBeLoading(false);
|
||||
setIsAppEffectRedirectEnabled(true);
|
||||
};
|
||||
|
||||
|
||||
@@ -207,7 +207,6 @@ Core metadata states used by record-table, record-board, views, etc.
|
||||
```
|
||||
object-metadata/states/lastFieldMetadataItemUpdateState.ts
|
||||
object-metadata/states/objectMetadataItemsState.ts
|
||||
object-metadata/states/shouldAppBeLoadingState.ts
|
||||
```
|
||||
|
||||
### Selectors (12 files)
|
||||
|
||||
@@ -45,6 +45,7 @@ export const createStateV2 = <ValueType>({
|
||||
useCookieStorage.cookieKey,
|
||||
defaultValue,
|
||||
storage,
|
||||
{ getOnInit: true },
|
||||
) as StateAtom<ValueType>;
|
||||
} else if (useLocalStorage) {
|
||||
baseAtom = atomWithStorage<ValueType>(
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import { useMetadataStore } from '@/app/hooks/useMetadataStore';
|
||||
import { metadataStoreState } from '@/app/states/metadataStoreState';
|
||||
import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useLoadMockedObjectMetadataItems } from '@/object-metadata/hooks/useLoadMockedObjectMetadataItems';
|
||||
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
import {
|
||||
ViewType as CoreViewType,
|
||||
useFindAllCoreViewsQuery,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
||||
|
||||
const INDEX_VIEW_TYPES = [
|
||||
CoreViewType.TABLE,
|
||||
CoreViewType.KANBAN,
|
||||
CoreViewType.CALENDAR,
|
||||
];
|
||||
|
||||
export const EagerMetadataLoadEffect = () => {
|
||||
const location = useLocation();
|
||||
const isLoggedIn = useIsLogged();
|
||||
const currentUser = useRecoilValueV2(currentUserState);
|
||||
const currentWorkspace = useRecoilValueV2(currentWorkspaceState);
|
||||
const store = useStore();
|
||||
|
||||
const objectsEntry = useFamilyRecoilValueV2(metadataStoreState, 'objects');
|
||||
const viewsEntry = useFamilyRecoilValueV2(metadataStoreState, 'views');
|
||||
|
||||
const { refreshObjectMetadataItems } = useRefreshObjectMetadataItems();
|
||||
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
const isOnAuthPath =
|
||||
isMatchingLocation(location, AppPath.Verify) ||
|
||||
isMatchingLocation(location, AppPath.VerifyEmail);
|
||||
|
||||
const { data: queryDataCoreViews } = useFindAllCoreViewsQuery({
|
||||
skip: !isLoggedIn || viewsEntry.status !== 'empty' || isOnAuthPath,
|
||||
variables: { viewTypes: INDEX_VIEW_TYPES },
|
||||
});
|
||||
|
||||
const setIndexCoreViews = useCallback(
|
||||
(indexViews: CoreViewWithRelations[]) => {
|
||||
const existingCoreViews = store.get(coreViewsState.atom);
|
||||
const existingFieldsWidgetViews = existingCoreViews.filter(
|
||||
(view) => view.type === CoreViewType.FIELDS_WIDGET,
|
||||
);
|
||||
const mergedViews = [...indexViews, ...existingFieldsWidgetViews];
|
||||
|
||||
if (!isDeeplyEqual(existingCoreViews, mergedViews)) {
|
||||
store.set(coreViewsState.atom, mergedViews);
|
||||
}
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (objectsEntry.status !== 'empty') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLoggedIn && !isDefined(currentUser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loadObjectMetadata = async () => {
|
||||
if (!isLoggedIn || !isWorkspaceActiveOrSuspended(currentWorkspace)) {
|
||||
await loadMockedObjectMetadataItems();
|
||||
} else {
|
||||
await refreshObjectMetadataItems();
|
||||
}
|
||||
|
||||
const loadedItems = store.get(objectMetadataItemsState.atom);
|
||||
updateDraft('objects', loadedItems);
|
||||
};
|
||||
|
||||
loadObjectMetadata();
|
||||
}, [
|
||||
currentUser,
|
||||
currentWorkspace,
|
||||
isLoggedIn,
|
||||
loadMockedObjectMetadataItems,
|
||||
objectsEntry.status,
|
||||
refreshObjectMetadataItems,
|
||||
store,
|
||||
updateDraft,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(queryDataCoreViews?.getCoreViews)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIndexCoreViews(queryDataCoreViews.getCoreViews);
|
||||
updateDraft('views', queryDataCoreViews.getCoreViews);
|
||||
}, [queryDataCoreViews?.getCoreViews, setIndexCoreViews, updateDraft]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
objectsEntry.status === 'draft_pending' &&
|
||||
viewsEntry.status === 'draft_pending'
|
||||
) {
|
||||
applyChanges();
|
||||
}
|
||||
}, [objectsEntry.status, viewsEntry.status, applyChanges]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { metadataStoreState } from '@/app/states/metadataStoreState';
|
||||
import { useMetadataStore } from '@/metadata-store/hooks/useMetadataStore';
|
||||
import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { recordPageLayoutsState } from '@/page-layout/states/recordPageLayoutsState';
|
||||
import { type PageLayout } from '@/page-layout/types/PageLayout';
|
||||
@@ -8,8 +8,8 @@ import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
@@ -29,6 +29,7 @@ export const LazyMetadataLoadEffect = () => {
|
||||
const store = useStore();
|
||||
|
||||
const setLogicFunctions = useSetRecoilState(logicFunctionsState);
|
||||
const { updateDraft, applyChanges } = useMetadataStore();
|
||||
|
||||
const isOnAuthPath =
|
||||
isMatchingLocation(location, AppPath.Verify) ||
|
||||
@@ -75,14 +76,8 @@ export const LazyMetadataLoadEffect = () => {
|
||||
if (!isDeeplyEqual(existingRecordPageLayouts, recordPageLayouts)) {
|
||||
set(recordPageLayoutsState, recordPageLayouts);
|
||||
}
|
||||
|
||||
store.set(metadataStoreState.atomFamily('pageLayouts'), {
|
||||
current: recordPageLayouts,
|
||||
draft: [],
|
||||
status: 'loaded',
|
||||
});
|
||||
},
|
||||
[store],
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -102,7 +97,14 @@ export const LazyMetadataLoadEffect = () => {
|
||||
queryDataRecordPageLayouts.getPageLayouts.map(transformPageLayout);
|
||||
|
||||
setRecordPageLayouts(transformedPageLayouts);
|
||||
}, [queryDataRecordPageLayouts?.getPageLayouts, setRecordPageLayouts]);
|
||||
updateDraft('pageLayouts', transformedPageLayouts);
|
||||
applyChanges();
|
||||
}, [
|
||||
queryDataRecordPageLayouts?.getPageLayouts,
|
||||
setRecordPageLayouts,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(logicFunctionsData?.findManyLogicFunctions)) {
|
||||
@@ -110,13 +112,14 @@ export const LazyMetadataLoadEffect = () => {
|
||||
}
|
||||
|
||||
setLogicFunctions(logicFunctionsData.findManyLogicFunctions);
|
||||
|
||||
store.set(metadataStoreState.atomFamily('logicFunctions'), {
|
||||
current: logicFunctionsData.findManyLogicFunctions,
|
||||
draft: [],
|
||||
status: 'loaded',
|
||||
});
|
||||
}, [logicFunctionsData?.findManyLogicFunctions, setLogicFunctions, store]);
|
||||
updateDraft('logicFunctions', logicFunctionsData.findManyLogicFunctions);
|
||||
applyChanges();
|
||||
}, [
|
||||
logicFunctionsData?.findManyLogicFunctions,
|
||||
setLogicFunctions,
|
||||
updateDraft,
|
||||
applyChanges,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user