diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx
index 3d0b1646c..ed890f480 100644
--- a/packages/twenty-front/src/App.tsx
+++ b/packages/twenty-front/src/App.tsx
@@ -43,7 +43,6 @@ import { Invite } from '~/pages/auth/Invite';
import { PasswordReset } from '~/pages/auth/PasswordReset';
import { PaymentSuccess } from '~/pages/auth/PaymentSuccess';
import { SignInUp } from '~/pages/auth/SignInUp';
-import { DefaultHomePage } from '~/pages/DefaultHomePage';
import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect';
import { NotFound } from '~/pages/not-found/NotFound';
import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
@@ -139,10 +138,7 @@ const createRouter = (isBillingEnabled?: boolean) =>
path={AppPath.PlanRequiredSuccess}
element={}
/>
- }
- />
+ >} />
} />
} />
} />
diff --git a/packages/twenty-front/src/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/effect-components/PageChangeEffect.tsx
index 9f8186343..d7b4a58ed 100644
--- a/packages/twenty-front/src/effect-components/PageChangeEffect.tsx
+++ b/packages/twenty-front/src/effect-components/PageChangeEffect.tsx
@@ -5,11 +5,8 @@ import { IconCheckbox } from 'twenty-ui';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { useEventTracker } from '@/analytics/hooks/useEventTracker';
-import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
-import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
-import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandType } from '@/command-menu/types/Command';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
@@ -17,36 +14,32 @@ import { AppBasePath } from '@/types/AppBasePath';
import { AppPath } from '@/types/AppPath';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { SettingsPath } from '@/types/SettingsPath';
-import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
-import { useGetWorkspaceFromInviteHashLazyQuery } from '~/generated/graphql';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
+import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
import { isDefined } from '~/utils/isDefined';
// TODO: break down into smaller functions and / or hooks
+// - moved usePageChangeEffectNavigateLocation into dedicated hook
export const PageChangeEffect = () => {
const navigate = useNavigate();
const isMatchingLocation = useIsMatchingLocation();
- const { enqueueSnackBar } = useSnackBar();
const [previousLocation, setPreviousLocation] = useState('');
- const onboardingStatus = useOnboardingStatus();
-
const setHotkeyScope = useSetHotkeyScope();
const location = useLocation();
+ const pageChangeEffectNavigateLocation =
+ usePageChangeEffectNavigateLocation();
+
const eventTracker = useEventTracker();
- const [workspaceFromInviteHashQuery] =
- useGetWorkspaceFromInviteHashLazyQuery();
const { addToCommandMenu, setToInitialCommandMenu } = useCommandMenu();
const openCreateActivity = useOpenCreateActivityDrawer();
- const isSignUpDisabled = useRecoilValue(isSignUpDisabledState);
-
useEffect(() => {
if (!previousLocation || previousLocation !== location.pathname) {
setPreviousLocation(location.pathname);
@@ -56,76 +49,10 @@ export const PageChangeEffect = () => {
}, [location, previousLocation]);
useEffect(() => {
- const isMatchingOngoingUserCreationRoute =
- isMatchingLocation(AppPath.SignInUp) ||
- isMatchingLocation(AppPath.Invite) ||
- isMatchingLocation(AppPath.Verify);
-
- const isMatchingOnboardingRoute =
- isMatchingOngoingUserCreationRoute ||
- isMatchingLocation(AppPath.CreateWorkspace) ||
- isMatchingLocation(AppPath.CreateProfile) ||
- isMatchingLocation(AppPath.PlanRequired) ||
- isMatchingLocation(AppPath.PlanRequiredSuccess);
-
- if (
- onboardingStatus === OnboardingStatus.OngoingUserCreation &&
- !isMatchingOngoingUserCreationRoute &&
- !isMatchingLocation(AppPath.ResetPassword)
- ) {
- navigate(AppPath.SignInUp);
- } else if (
- isDefined(onboardingStatus) &&
- onboardingStatus === OnboardingStatus.Incomplete &&
- !isMatchingLocation(AppPath.PlanRequired)
- ) {
- navigate(AppPath.PlanRequired);
- } else if (
- isDefined(onboardingStatus) &&
- [OnboardingStatus.Unpaid, OnboardingStatus.Canceled].includes(
- onboardingStatus,
- ) &&
- !(
- isMatchingLocation(AppPath.SettingsCatchAll) ||
- isMatchingLocation(AppPath.PlanRequired)
- )
- ) {
- navigate(
- `${AppPath.SettingsCatchAll.replace('/*', '')}/${SettingsPath.Billing}`,
- );
- } else if (
- onboardingStatus === OnboardingStatus.OngoingWorkspaceActivation &&
- !isMatchingLocation(AppPath.CreateWorkspace) &&
- !isMatchingLocation(AppPath.PlanRequiredSuccess)
- ) {
- navigate(AppPath.CreateWorkspace);
- } else if (
- onboardingStatus === OnboardingStatus.OngoingProfileCreation &&
- !isMatchingLocation(AppPath.CreateProfile)
- ) {
- navigate(AppPath.CreateProfile);
- } else if (
- onboardingStatus === OnboardingStatus.Completed &&
- isMatchingOnboardingRoute &&
- !isMatchingLocation(AppPath.Invite)
- ) {
- navigate(AppPath.Index);
- } else if (
- onboardingStatus === OnboardingStatus.CompletedWithoutSubscription &&
- isMatchingOnboardingRoute &&
- !isMatchingLocation(AppPath.PlanRequired)
- ) {
- navigate(AppPath.Index);
+ if (isDefined(pageChangeEffectNavigateLocation)) {
+ navigate(pageChangeEffectNavigateLocation);
}
- }, [
- enqueueSnackBar,
- isMatchingLocation,
- isSignUpDisabled,
- location.pathname,
- navigate,
- onboardingStatus,
- workspaceFromInviteHashQuery,
- ]);
+ }, [navigate, pageChangeEffectNavigateLocation]);
useEffect(() => {
switch (true) {
diff --git a/packages/twenty-front/src/hooks/__tests__/useDefaultHomePagePath.test.ts b/packages/twenty-front/src/hooks/__tests__/useDefaultHomePagePath.test.ts
new file mode 100644
index 000000000..1d2834937
--- /dev/null
+++ b/packages/twenty-front/src/hooks/__tests__/useDefaultHomePagePath.test.ts
@@ -0,0 +1,72 @@
+import { renderHook } from '@testing-library/react';
+import { RecoilRoot, useSetRecoilState } from 'recoil';
+
+import { currentUserState } from '@/auth/states/currentUserState';
+import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
+import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
+import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
+import { AppPath } from '@/types/AppPath';
+import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
+import { mockedUsersData } from '~/testing/mock-data/users';
+
+const objectMetadataItem = getObjectMetadataItemsMock()[0];
+jest.mock('@/object-metadata/hooks/useObjectMetadataItem');
+jest.mocked(useObjectMetadataItem).mockReturnValue({
+ objectMetadataItem,
+});
+
+jest.mock('@/prefetch/hooks/usePrefetchedData');
+const setupMockPrefetchedData = (viewId?: string) => {
+ jest.mocked(usePrefetchedData).mockReturnValue({
+ isDataPrefetched: true,
+ records: viewId
+ ? [
+ {
+ id: viewId,
+ __typename: 'object',
+ objectMetadataId: objectMetadataItem.id,
+ },
+ ]
+ : [],
+ });
+};
+
+const renderHooks = (withCurrentUser: boolean) => {
+ const { result } = renderHook(
+ () => {
+ const setCurrentUser = useSetRecoilState(currentUserState);
+ if (withCurrentUser) {
+ setCurrentUser(mockedUsersData[0]);
+ }
+ return useDefaultHomePagePath();
+ },
+ {
+ wrapper: RecoilRoot,
+ },
+ );
+ return { result };
+};
+describe('useDefaultHomePagePath', () => {
+ it('should return proper path when no currentUser', () => {
+ setupMockPrefetchedData();
+ const { result } = renderHooks(false);
+ expect(result.current.defaultHomePagePath).toEqual(AppPath.SignInUp);
+ });
+ it('should return proper path when no currentUser and existing view', () => {
+ setupMockPrefetchedData('viewId');
+ const { result } = renderHooks(false);
+ expect(result.current.defaultHomePagePath).toEqual(AppPath.SignInUp);
+ });
+ it('should return proper path when currentUser is defined', () => {
+ setupMockPrefetchedData();
+ const { result } = renderHooks(true);
+ expect(result.current.defaultHomePagePath).toEqual('/objects/companies');
+ });
+ it('should return proper path when currentUser is defined and view exists', () => {
+ setupMockPrefetchedData('viewId');
+ const { result } = renderHooks(true);
+ expect(result.current.defaultHomePagePath).toEqual(
+ '/objects/companies?view=viewId',
+ );
+ });
+});
diff --git a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts
new file mode 100644
index 000000000..af1587928
--- /dev/null
+++ b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts
@@ -0,0 +1,238 @@
+import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
+import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
+import { AppPath } from '@/types/AppPath';
+import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
+import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
+import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
+
+jest.mock('@/auth/hooks/useOnboardingStatus');
+const setupMockOnboardingStatus = (onboardingStatus: OnboardingStatus) => {
+ jest.mocked(useOnboardingStatus).mockReturnValueOnce(onboardingStatus);
+};
+
+jest.mock('~/hooks/useIsMatchingLocation');
+const mockUseIsMatchingLocation = jest.mocked(useIsMatchingLocation);
+
+const setupMockIsMatchingLocation = (pathname: string) => {
+ mockUseIsMatchingLocation.mockReturnValueOnce(
+ (path: string) => path === pathname,
+ );
+};
+
+const defaultHomePagePath = '/objects/companies';
+
+jest.mock('~/hooks/useDefaultHomePagePath');
+jest.mocked(useDefaultHomePagePath).mockReturnValue({
+ defaultHomePagePath: '/objects/companies',
+});
+
+// prettier-ignore
+const testCases = [
+ { loc: AppPath.Verify, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.Verify, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.Verify, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.Verify, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.Verify, status: OnboardingStatus.OngoingUserCreation, res: undefined },
+ { loc: AppPath.Verify, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.Verify, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.Verify, status: OnboardingStatus.Completed, res: defaultHomePagePath },
+ { loc: AppPath.Verify, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
+
+ { loc: AppPath.SignInUp, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.SignInUp, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.SignInUp, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.SignInUp, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingUserCreation, res: undefined },
+ { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.SignInUp, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.SignInUp, status: OnboardingStatus.Completed, res: defaultHomePagePath },
+ { loc: AppPath.SignInUp, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
+
+ { loc: AppPath.Invite, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.Invite, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.Invite, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.Invite, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.Invite, status: OnboardingStatus.OngoingUserCreation, res: undefined },
+ { loc: AppPath.Invite, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.Invite, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.Invite, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.Invite, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingUserCreation, res: undefined },
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.ResetPassword, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingWorkspaceActivation, res: undefined },
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.Completed, res: defaultHomePagePath },
+ { loc: AppPath.CreateWorkspace, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
+
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingProfileCreation, res: undefined },
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.Completed, res: defaultHomePagePath },
+ { loc: AppPath.CreateProfile, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
+
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.Incomplete, res: undefined },
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.Canceled, res: undefined },
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.Unpaid, res: undefined },
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.Completed, res: defaultHomePagePath },
+ { loc: AppPath.PlanRequired, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingWorkspaceActivation, res: undefined },
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Completed, res: defaultHomePagePath },
+ { loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
+
+ { loc: AppPath.Index, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.Index, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.Index, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.Index, status: OnboardingStatus.PastDue, res: defaultHomePagePath },
+ { loc: AppPath.Index, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.Index, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.Index, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.Index, status: OnboardingStatus.Completed, res: defaultHomePagePath },
+ { loc: AppPath.Index, status: OnboardingStatus.CompletedWithoutSubscription, res: defaultHomePagePath },
+
+ { loc: AppPath.TasksPage, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.TasksPage, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.TasksPage, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.TasksPage, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.TasksPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.TasksPage, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.TasksPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.OpportunitiesPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.RecordIndexPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.RecordShowPage, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Canceled, res: undefined },
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Unpaid, res: undefined },
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.SettingsCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.Impersonate, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.Impersonate, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.Impersonate, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.Impersonate, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.Impersonate, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.Impersonate, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.Impersonate, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.Authorize, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.Authorize, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.Authorize, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.Authorize, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.Authorize, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.Authorize, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.Authorize, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.Authorize, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.Authorize, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.NotFoundWildcard, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+
+ { loc: AppPath.NotFound, status: OnboardingStatus.Incomplete, res: AppPath.PlanRequired },
+ { loc: AppPath.NotFound, status: OnboardingStatus.Canceled, res: '/settings/billing' },
+ { loc: AppPath.NotFound, status: OnboardingStatus.Unpaid, res: '/settings/billing' },
+ { loc: AppPath.NotFound, status: OnboardingStatus.PastDue, res: undefined },
+ { loc: AppPath.NotFound, status: OnboardingStatus.OngoingUserCreation, res: AppPath.SignInUp },
+ { loc: AppPath.NotFound, status: OnboardingStatus.OngoingWorkspaceActivation, res: AppPath.CreateWorkspace },
+ { loc: AppPath.NotFound, status: OnboardingStatus.OngoingProfileCreation, res: AppPath.CreateProfile },
+ { loc: AppPath.NotFound, status: OnboardingStatus.Completed, res: undefined },
+ { loc: AppPath.NotFound, status: OnboardingStatus.CompletedWithoutSubscription, res: undefined },
+];
+
+describe('usePageChangeEffectNavigateLocation', () => {
+ testCases.forEach((testCase) => {
+ it(`with location ${testCase.loc} and onboardingStatus ${testCase.status} should return ${testCase.res}`, () => {
+ setupMockIsMatchingLocation(testCase.loc);
+ setupMockOnboardingStatus(testCase.status);
+ expect(usePageChangeEffectNavigateLocation()).toEqual(testCase.res);
+ });
+ });
+
+ describe('tests should be exhaustive', () => {
+ it('all location and onboarding status should be tested', () => {
+ expect(testCases.length).toEqual(
+ Object.keys(AppPath).length * Object.keys(OnboardingStatus).length,
+ );
+ });
+ });
+});
diff --git a/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx b/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx
index 8ed52fe8c..8332f5113 100644
--- a/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx
+++ b/packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx
@@ -1,21 +1,31 @@
+import { useRecoilValue } from 'recoil';
+
+import { currentUserState } from '@/auth/states/currentUserState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
+import { AppPath } from '@/types/AppPath';
+import { isDefined } from '~/utils/isDefined';
export const useDefaultHomePagePath = () => {
+ const currentUser = useRecoilValue(currentUserState);
const { objectMetadataItem: companyObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Company,
});
-
const { records } = usePrefetchedData(PrefetchKey.AllViews);
+ if (!isDefined(currentUser)) {
+ return { defaultHomePagePath: AppPath.SignInUp };
+ }
+
const companyViewId = records.find(
(view: any) => view?.objectMetadataId === companyObjectMetadataItem.id,
)?.id;
- const defaultHomePagePath =
- '/objects/companies' + (companyViewId ? `?view=${companyViewId}` : '');
- return { defaultHomePagePath };
+ return {
+ defaultHomePagePath:
+ '/objects/companies' + (companyViewId ? `?view=${companyViewId}` : ''),
+ };
};
diff --git a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts
new file mode 100644
index 000000000..b31df0a3a
--- /dev/null
+++ b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts
@@ -0,0 +1,96 @@
+import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
+import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
+import { AppPath } from '@/types/AppPath';
+import { SettingsPath } from '@/types/SettingsPath';
+import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
+import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
+import { isDefined } from '~/utils/isDefined';
+
+export const usePageChangeEffectNavigateLocation = () => {
+ const isMatchingLocation = useIsMatchingLocation();
+ const onboardingStatus = useOnboardingStatus();
+ const { defaultHomePagePath } = useDefaultHomePagePath();
+
+ const isMatchingOpenRoute =
+ isMatchingLocation(AppPath.Invite) ||
+ isMatchingLocation(AppPath.ResetPassword);
+
+ const isMatchingOngoingUserCreationRoute =
+ isMatchingOpenRoute ||
+ isMatchingLocation(AppPath.SignInUp) ||
+ isMatchingLocation(AppPath.Verify);
+
+ const isMatchingOnboardingRoute =
+ isMatchingOngoingUserCreationRoute ||
+ isMatchingLocation(AppPath.CreateWorkspace) ||
+ isMatchingLocation(AppPath.CreateProfile) ||
+ isMatchingLocation(AppPath.PlanRequired) ||
+ isMatchingLocation(AppPath.PlanRequiredSuccess);
+
+ if (
+ onboardingStatus === OnboardingStatus.OngoingUserCreation &&
+ !isMatchingOngoingUserCreationRoute
+ ) {
+ return AppPath.SignInUp;
+ }
+
+ if (
+ onboardingStatus === OnboardingStatus.Incomplete &&
+ !isMatchingLocation(AppPath.PlanRequired)
+ ) {
+ return AppPath.PlanRequired;
+ }
+
+ if (
+ isDefined(onboardingStatus) &&
+ [OnboardingStatus.Unpaid, OnboardingStatus.Canceled].includes(
+ onboardingStatus,
+ ) &&
+ !(
+ isMatchingLocation(AppPath.SettingsCatchAll) ||
+ isMatchingLocation(AppPath.PlanRequired)
+ )
+ ) {
+ return `${AppPath.SettingsCatchAll.replace('/*', '')}/${
+ SettingsPath.Billing
+ }`;
+ }
+
+ if (
+ onboardingStatus === OnboardingStatus.OngoingWorkspaceActivation &&
+ !isMatchingLocation(AppPath.CreateWorkspace) &&
+ !isMatchingLocation(AppPath.PlanRequiredSuccess)
+ ) {
+ return AppPath.CreateWorkspace;
+ }
+
+ if (
+ onboardingStatus === OnboardingStatus.OngoingProfileCreation &&
+ !isMatchingLocation(AppPath.CreateProfile)
+ ) {
+ return AppPath.CreateProfile;
+ }
+
+ if (
+ onboardingStatus === OnboardingStatus.Completed &&
+ isMatchingOnboardingRoute &&
+ !isMatchingOpenRoute
+ ) {
+ return defaultHomePagePath;
+ }
+
+ if (
+ onboardingStatus === OnboardingStatus.CompletedWithoutSubscription &&
+ isMatchingOnboardingRoute &&
+ !isMatchingOpenRoute &&
+ !isMatchingLocation(AppPath.PlanRequired)
+ ) {
+ return defaultHomePagePath;
+ }
+
+ if (isMatchingLocation(AppPath.Index)) {
+ return defaultHomePagePath;
+ }
+
+ return;
+};
diff --git a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts
index d0c3c6eda..351ac8289 100644
--- a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts
+++ b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts
@@ -1,11 +1,14 @@
import { useMemo, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client';
-import { useRecoilState, useRecoilValue } from 'recoil';
+import { useRecoilState, useSetRecoilState } from 'recoil';
+import { currentUserState } from '@/auth/states/currentUserState';
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { previousUrlState } from '@/auth/states/previousUrlState';
import { tokenPairState } from '@/auth/states/tokenPairState';
+import { workspacesState } from '@/auth/states/workspaces';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { AppPath } from '@/types/AppPath';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
@@ -18,12 +21,20 @@ import { ApolloFactory, Options } from '../services/apollo.factory';
export const useApolloFactory = (options: Partial> = {}) => {
// eslint-disable-next-line @nx/workspace-no-state-useref
const apolloRef = useRef | null>(null);
- const currentWorkspace = useRecoilValue(currentWorkspaceState);
const [isDebugMode] = useRecoilState(isDebugModeState);
const navigate = useNavigate();
const isMatchingLocation = useIsMatchingLocation();
const [tokenPair, setTokenPair] = useRecoilState(tokenPairState);
+ const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
+ currentWorkspaceState,
+ );
+ const setCurrentUser = useSetRecoilState(currentUserState);
+ const setCurrentWorkspaceMember = useSetRecoilState(
+ currentWorkspaceMemberState,
+ );
+
+ const setWorkspaces = useSetRecoilState(workspacesState);
const [, setPreviousUrl] = useRecoilState(previousUrlState);
const location = useLocation();
@@ -55,6 +66,10 @@ export const useApolloFactory = (options: Partial> = {}) => {
},
onUnauthenticatedError: () => {
setTokenPair(null);
+ setCurrentUser(null);
+ setCurrentWorkspaceMember(null);
+ setCurrentWorkspace(null);
+ setWorkspaces(null);
if (
!isMatchingLocation(AppPath.Verify) &&
!isMatchingLocation(AppPath.SignInUp) &&
@@ -75,6 +90,10 @@ export const useApolloFactory = (options: Partial> = {}) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
setTokenPair,
+ setCurrentUser,
+ setCurrentWorkspaceMember,
+ setCurrentWorkspace,
+ setWorkspaces,
isDebugMode,
currentWorkspace?.currentCacheVersion,
setPreviousUrl,
diff --git a/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx
index 81cb71065..a066ddf7e 100644
--- a/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx
+++ b/packages/twenty-front/src/modules/auth/components/VerifyEffect.tsx
@@ -1,16 +1,13 @@
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
-import { useRecoilValue } from 'recoil';
import { useAuth } from '@/auth/hooks/useAuth';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
-import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { AppPath } from '@/types/AppPath';
export const VerifyEffect = () => {
const [searchParams] = useSearchParams();
const loginToken = searchParams.get('loginToken');
- const currentWorkspace = useRecoilValue(currentWorkspaceState);
const isLogged = useIsLogged();
const navigate = useNavigate();
@@ -23,12 +20,6 @@ export const VerifyEffect = () => {
navigate(AppPath.SignInUp);
} else {
await verify(loginToken);
-
- if (currentWorkspace?.activationStatus === 'active') {
- navigate(AppPath.Index);
- } else {
- navigate(AppPath.CreateWorkspace);
- }
}
};
diff --git a/packages/twenty-front/src/modules/auth/hooks/useOnboardingStatus.ts b/packages/twenty-front/src/modules/auth/hooks/useOnboardingStatus.ts
index 91a8a60e8..63c5d7add 100644
--- a/packages/twenty-front/src/modules/auth/hooks/useOnboardingStatus.ts
+++ b/packages/twenty-front/src/modules/auth/hooks/useOnboardingStatus.ts
@@ -20,6 +20,6 @@ export const useOnboardingStatus = (): OnboardingStatus | undefined => {
isLoggedIn,
currentWorkspaceMember,
currentWorkspace,
- isBillingEnabled: billing?.isBillingEnabled,
+ isBillingEnabled: billing?.isBillingEnabled || false,
});
};
diff --git a/packages/twenty-front/src/modules/auth/hooks/useSignOutAndRedirect.ts b/packages/twenty-front/src/modules/auth/hooks/useSignOutAndRedirect.ts
deleted file mode 100644
index a9c2c7baf..000000000
--- a/packages/twenty-front/src/modules/auth/hooks/useSignOutAndRedirect.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useCallback } from 'react';
-import { useNavigate } from 'react-router-dom';
-
-import { useAuth } from '@/auth/hooks/useAuth';
-import { AppPath } from '@/types/AppPath';
-
-export const useSignOutAndRedirect = () => {
- const { signOut } = useAuth();
- const navigate = useNavigate();
-
- return useCallback(() => {
- signOut();
- navigate(AppPath.SignInUp);
- }, [signOut, navigate]);
-};
diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts
deleted file mode 100644
index 195a4e44d..000000000
--- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { useCallback } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { useRecoilValue } from 'recoil';
-
-import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
-import { previousUrlState } from '@/auth/states/previousUrlState';
-import { billingState } from '@/client-config/states/billingState';
-import { AppPath } from '@/types/AppPath';
-import { WorkspaceMember } from '~/generated/graphql';
-
-export const useNavigateAfterSignInUp = () => {
- const navigate = useNavigate();
- const billing = useRecoilValue(billingState);
- const previousUrl = useRecoilValue(previousUrlState);
- const navigateAfterSignInUp = useCallback(
- (
- currentWorkspace: CurrentWorkspace,
- currentWorkspaceMember: WorkspaceMember | null,
- ) => {
- if (
- billing?.isBillingEnabled === true &&
- !['active', 'trialing'].includes(currentWorkspace.subscriptionStatus)
- ) {
- navigate(AppPath.PlanRequired);
- return;
- }
-
- if (currentWorkspace.activationStatus !== 'active') {
- navigate(AppPath.CreateWorkspace);
- return;
- }
-
- if (
- !currentWorkspaceMember?.name.firstName ||
- !currentWorkspaceMember?.name.lastName
- ) {
- navigate(AppPath.CreateProfile);
- return;
- }
- if (previousUrl !== '') navigate(previousUrl);
- else navigate(AppPath.Index);
- },
- [billing, previousUrl, navigate],
- );
- return { navigateAfterSignInUp };
-};
diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx
index 0c23b372b..ba8ddd471 100644
--- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx
+++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx
@@ -2,7 +2,6 @@ import { useCallback, useState } from 'react';
import { SubmitHandler, UseFormReturn } from 'react-hook-form';
import { useParams } from 'react-router-dom';
-import { useNavigateAfterSignInUp } from '@/auth/sign-in-up/hooks/useNavigateAfterSignInUp';
import { Form } from '@/auth/sign-in-up/hooks/useSignInUpForm';
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
@@ -31,8 +30,6 @@ export const useSignInUp = (form: UseFormReturn