mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-26 13:31:45 +03:00
5509 remove flash on intermediate verify step when sign in with sso (#5526)
- remove flash on /verify - remove flash on signInUp - remove useless redirections and hooks - Remove DefaultHomePage component - Move redirections to /objects/companies in PageChangeEffect - add useShowAuthModal hooks and tests - add usePageChangeEffectNaviteLocation hooks and tests - fix refresh token expired produces blank screen
This commit is contained in:
parent
f455ad4001
commit
9080981990
@ -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={<PaymentSuccess />}
|
||||
/>
|
||||
<Route
|
||||
path={indexAppPath.getIndexAppPath()}
|
||||
element={<DefaultHomePage />}
|
||||
/>
|
||||
<Route path={indexAppPath.getIndexAppPath()} element={<></>} />
|
||||
<Route path={AppPath.TasksPage} element={<Tasks />} />
|
||||
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
|
||||
<Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} />
|
||||
|
@ -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) {
|
||||
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -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}` : ''),
|
||||
};
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
@ -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<Options<any>> = {}) => {
|
||||
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | 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<Options<any>> = {}) => {
|
||||
},
|
||||
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<Options<any>> = {}) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
setTokenPair,
|
||||
setCurrentUser,
|
||||
setCurrentWorkspaceMember,
|
||||
setCurrentWorkspace,
|
||||
setWorkspaces,
|
||||
isDebugMode,
|
||||
currentWorkspace?.currentCacheVersion,
|
||||
setPreviousUrl,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -20,6 +20,6 @@ export const useOnboardingStatus = (): OnboardingStatus | undefined => {
|
||||
isLoggedIn,
|
||||
currentWorkspaceMember,
|
||||
currentWorkspace,
|
||||
isBillingEnabled: billing?.isBillingEnabled,
|
||||
isBillingEnabled: billing?.isBillingEnabled || false,
|
||||
});
|
||||
};
|
||||
|
@ -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]);
|
||||
};
|
@ -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 };
|
||||
};
|
@ -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<Form>) => {
|
||||
|
||||
const workspaceInviteHash = useParams().workspaceInviteHash;
|
||||
|
||||
const { navigateAfterSignInUp } = useNavigateAfterSignInUp();
|
||||
|
||||
const [isInviteMode] = useState(() => isMatchingLocation(AppPath.Invite));
|
||||
|
||||
const [signInUpStep, setSignInUpStep] = useState<SignInUpStep>(
|
||||
@ -105,24 +102,18 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
throw new Error('Email and password are required');
|
||||
}
|
||||
|
||||
const {
|
||||
workspace: currentWorkspace,
|
||||
workspaceMember: currentWorkspaceMember,
|
||||
} =
|
||||
signInUpMode === SignInUpMode.SignIn && !isInviteMode
|
||||
? await signInWithCredentials(
|
||||
data.email.toLowerCase().trim(),
|
||||
data.password,
|
||||
token,
|
||||
)
|
||||
: await signUpWithCredentials(
|
||||
data.email.toLowerCase().trim(),
|
||||
data.password,
|
||||
workspaceInviteHash,
|
||||
token,
|
||||
);
|
||||
|
||||
navigateAfterSignInUp(currentWorkspace, currentWorkspaceMember);
|
||||
signInUpMode === SignInUpMode.SignIn && !isInviteMode
|
||||
? await signInWithCredentials(
|
||||
data.email.toLowerCase().trim(),
|
||||
data.password,
|
||||
token,
|
||||
)
|
||||
: await signUpWithCredentials(
|
||||
data.email.toLowerCase().trim(),
|
||||
data.password,
|
||||
workspaceInviteHash,
|
||||
token,
|
||||
);
|
||||
} catch (err: any) {
|
||||
enqueueSnackBar(err?.message, {
|
||||
variant: SnackBarVariant.Error,
|
||||
@ -136,7 +127,6 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
signInWithCredentials,
|
||||
signUpWithCredentials,
|
||||
workspaceInviteHash,
|
||||
navigateAfterSignInUp,
|
||||
enqueueSnackBar,
|
||||
],
|
||||
);
|
||||
|
@ -1,12 +1,51 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState';
|
||||
import { useGetWorkspaceFromInviteHashQuery } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useWorkspaceFromInviteHash = () => {
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const navigate = useNavigate();
|
||||
const workspaceInviteHash = useParams().workspaceInviteHash;
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const [initiallyLoggedIn] = useState(isDefined(currentWorkspace));
|
||||
const setIsDefaultLayoutAuthModalVisible = useSetRecoilState(
|
||||
isDefaultLayoutAuthModalVisibleState,
|
||||
);
|
||||
const { data: workspaceFromInviteHash, loading } =
|
||||
useGetWorkspaceFromInviteHashQuery({
|
||||
variables: { inviteHash: workspaceInviteHash || '' },
|
||||
onError: () => {
|
||||
enqueueSnackBar('workspace does not exist', {
|
||||
variant: SnackBarVariant.Error,
|
||||
});
|
||||
navigate(AppPath.Index);
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (
|
||||
isDefined(currentWorkspace) &&
|
||||
data?.findWorkspaceFromInviteHash &&
|
||||
currentWorkspace.id === data.findWorkspaceFromInviteHash.id
|
||||
) {
|
||||
initiallyLoggedIn &&
|
||||
enqueueSnackBar(
|
||||
`You already belong to ${data?.findWorkspaceFromInviteHash?.displayName} workspace`,
|
||||
{
|
||||
variant: SnackBarVariant.Info,
|
||||
},
|
||||
);
|
||||
navigate(AppPath.Index);
|
||||
} else {
|
||||
setIsDefaultLayoutAuthModalVisible(true);
|
||||
}
|
||||
},
|
||||
});
|
||||
return {
|
||||
workspace: workspaceFromInviteHash?.findWorkspaceFromInviteHash,
|
||||
|
@ -9,6 +9,7 @@ describe('getOnboardingStatus', () => {
|
||||
isLoggedIn: false,
|
||||
currentWorkspaceMember: null,
|
||||
currentWorkspace: null,
|
||||
isBillingEnabled: false,
|
||||
});
|
||||
|
||||
const ongoingWorkspaceActivation = getOnboardingStatus({
|
||||
@ -18,6 +19,7 @@ describe('getOnboardingStatus', () => {
|
||||
id: '1',
|
||||
activationStatus: 'inactive',
|
||||
} as CurrentWorkspace,
|
||||
isBillingEnabled: false,
|
||||
});
|
||||
|
||||
const ongoingProfileCreation = getOnboardingStatus({
|
||||
@ -30,6 +32,7 @@ describe('getOnboardingStatus', () => {
|
||||
id: '1',
|
||||
activationStatus: 'active',
|
||||
} as CurrentWorkspace,
|
||||
isBillingEnabled: false,
|
||||
});
|
||||
|
||||
const completed = getOnboardingStatus({
|
||||
@ -45,6 +48,7 @@ describe('getOnboardingStatus', () => {
|
||||
id: '1',
|
||||
activationStatus: 'active',
|
||||
} as CurrentWorkspace,
|
||||
isBillingEnabled: false,
|
||||
});
|
||||
|
||||
const incomplete = getOnboardingStatus({
|
||||
@ -78,6 +82,7 @@ describe('getOnboardingStatus', () => {
|
||||
activationStatus: 'active',
|
||||
subscriptionStatus: 'incomplete',
|
||||
} as CurrentWorkspace,
|
||||
isBillingEnabled: false,
|
||||
});
|
||||
|
||||
const canceled = getOnboardingStatus({
|
||||
|
@ -25,7 +25,7 @@ export const getOnboardingStatus = ({
|
||||
'createdAt' | 'updatedAt' | 'userId' | 'userEmail' | '__typename'
|
||||
> | null;
|
||||
currentWorkspace: CurrentWorkspace | null;
|
||||
isBillingEnabled?: boolean;
|
||||
isBillingEnabled: boolean;
|
||||
}) => {
|
||||
if (!isLoggedIn) {
|
||||
return OnboardingStatus.OngoingUserCreation;
|
||||
@ -38,7 +38,7 @@ export const getOnboardingStatus = ({
|
||||
}
|
||||
|
||||
if (
|
||||
isBillingEnabled === true &&
|
||||
isBillingEnabled &&
|
||||
currentWorkspace.subscriptionStatus === 'incomplete'
|
||||
) {
|
||||
return OnboardingStatus.Incomplete;
|
||||
@ -55,31 +55,19 @@ export const getOnboardingStatus = ({
|
||||
return OnboardingStatus.OngoingProfileCreation;
|
||||
}
|
||||
|
||||
if (
|
||||
isBillingEnabled === true &&
|
||||
currentWorkspace.subscriptionStatus === 'canceled'
|
||||
) {
|
||||
if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'canceled') {
|
||||
return OnboardingStatus.Canceled;
|
||||
}
|
||||
|
||||
if (
|
||||
isBillingEnabled === true &&
|
||||
currentWorkspace.subscriptionStatus === 'past_due'
|
||||
) {
|
||||
if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'past_due') {
|
||||
return OnboardingStatus.PastDue;
|
||||
}
|
||||
|
||||
if (
|
||||
isBillingEnabled === true &&
|
||||
currentWorkspace.subscriptionStatus === 'unpaid'
|
||||
) {
|
||||
if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'unpaid') {
|
||||
return OnboardingStatus.Unpaid;
|
||||
}
|
||||
|
||||
if (
|
||||
isBillingEnabled === true &&
|
||||
!currentWorkspace.currentBillingSubscription
|
||||
) {
|
||||
if (isBillingEnabled && !currentWorkspace.currentBillingSubscription) {
|
||||
return OnboardingStatus.CompletedWithoutSubscription;
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
IconUsers,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { useSignOutAndRedirect } from '@/auth/hooks/useSignOutAndRedirect';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
@ -25,7 +25,7 @@ import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/compo
|
||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||
|
||||
export const SettingsNavigationDrawerItems = () => {
|
||||
const handleLogout = useSignOutAndRedirect();
|
||||
const { signOut } = useAuth();
|
||||
|
||||
const billing = useRecoilValue(billingState);
|
||||
|
||||
@ -113,7 +113,7 @@ export const SettingsNavigationDrawerItems = () => {
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
label="Logout"
|
||||
onClick={handleLogout}
|
||||
onClick={signOut}
|
||||
Icon={IconDoorEnter}
|
||||
/>
|
||||
</NavigationDrawerSection>
|
||||
|
@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { H2Title } from 'twenty-ui';
|
||||
|
||||
import { useSignOutAndRedirect } from '@/auth/hooks/useSignOutAndRedirect';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
@ -15,11 +15,11 @@ export const DeleteAccount = () => {
|
||||
const [deleteUserAccount] = useDeleteUserAccountMutation();
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const userEmail = currentUser?.email;
|
||||
const handleLogout = useSignOutAndRedirect();
|
||||
const { signOut } = useAuth();
|
||||
|
||||
const deleteAccount = async () => {
|
||||
await deleteUserAccount();
|
||||
handleLogout();
|
||||
await signOut();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { H2Title } from 'twenty-ui';
|
||||
|
||||
import { useSignOutAndRedirect } from '@/auth/hooks/useSignOutAndRedirect';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import {
|
||||
ConfirmationModal,
|
||||
@ -18,11 +18,11 @@ export const DeleteWorkspace = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
const userEmail = currentUser?.email;
|
||||
|
||||
const handleLogout = useSignOutAndRedirect();
|
||||
const { signOut } = useAuth();
|
||||
|
||||
const deleteWorkspace = async () => {
|
||||
await deleteCurrentWorkspace();
|
||||
handleLogout();
|
||||
await signOut();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -0,0 +1,269 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
||||
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
||||
import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
|
||||
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 getResult = (isDefaultLayoutAuthModalVisible = true) =>
|
||||
renderHook(
|
||||
() => {
|
||||
const setIsDefaultLayoutAuthModalVisible = useSetRecoilState(
|
||||
isDefaultLayoutAuthModalVisibleState,
|
||||
);
|
||||
setIsDefaultLayoutAuthModalVisible(isDefaultLayoutAuthModalVisible);
|
||||
|
||||
return useShowAuthModal();
|
||||
},
|
||||
{
|
||||
wrapper: RecoilRoot,
|
||||
},
|
||||
);
|
||||
|
||||
// prettier-ignore
|
||||
const testCases = [
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.Incomplete, res: false },
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingUserCreation, res: false },
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingWorkspaceActivation, res: false },
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.OngoingProfileCreation, res: false },
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.Verify, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.SignInUp, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.Canceled, res: true },
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.Unpaid, res: true },
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.PastDue, res: true },
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.Completed, res: true },
|
||||
{ loc: AppPath.Invite, status: OnboardingStatus.CompletedWithoutSubscription, res: true },
|
||||
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.Canceled, res: true },
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.Unpaid, res: true },
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.PastDue, res: true },
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.Completed, res: true },
|
||||
{ loc: AppPath.ResetPassword, status: OnboardingStatus.CompletedWithoutSubscription, res: true },
|
||||
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.CreateWorkspace, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.CreateProfile, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Canceled, res: true },
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.PlanRequired, status: OnboardingStatus.CompletedWithoutSubscription, res: true },
|
||||
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.PlanRequiredSuccess, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.Index, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.TasksPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.OpportunitiesPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.RecordIndexPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.RecordShowPage, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.SettingsCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.DevelopersCatchAll, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.Impersonate, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.Authorize, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.NotFoundWildcard, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.Incomplete, res: true },
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.Canceled, res: false },
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.Unpaid, res: false },
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.PastDue, res: false },
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingUserCreation, res: true },
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingWorkspaceActivation, res: true },
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.OngoingProfileCreation, res: true },
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.Completed, res: false },
|
||||
{ loc: AppPath.NotFound, status: OnboardingStatus.CompletedWithoutSubscription, res: false },
|
||||
];
|
||||
|
||||
describe('useShowAuthModal', () => {
|
||||
testCases.forEach((testCase) => {
|
||||
it(`testCase for location ${testCase.loc} with onboardingStatus ${testCase.status} should return ${testCase.res}`, () => {
|
||||
setupMockOnboardingStatus(testCase.status);
|
||||
setupMockIsMatchingLocation(testCase.loc);
|
||||
const { result } = getResult();
|
||||
if (testCase.res) {
|
||||
expect(result.current).toBeTruthy();
|
||||
} else {
|
||||
expect(result.current).toBeFalsy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('test with token validation loading', () => {
|
||||
it(`with appPath ${AppPath.Invite} and isDefaultLayoutAuthModalVisible=false`, () => {
|
||||
setupMockOnboardingStatus(OnboardingStatus.Completed);
|
||||
setupMockIsMatchingLocation(AppPath.Invite);
|
||||
const { result } = getResult(false);
|
||||
expect(result.current).toBeFalsy();
|
||||
});
|
||||
it(`with appPath ${AppPath.ResetPassword} and isDefaultLayoutAuthModalVisible=false`, () => {
|
||||
setupMockOnboardingStatus(OnboardingStatus.Completed);
|
||||
setupMockIsMatchingLocation(AppPath.ResetPassword);
|
||||
const { result } = getResult(false);
|
||||
expect(result.current).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,42 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
||||
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
|
||||
export const useShowAuthModal = () => {
|
||||
const isMatchingLocation = useIsMatchingLocation();
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
const isDefaultLayoutAuthModalVisible = useRecoilValue(
|
||||
isDefaultLayoutAuthModalVisibleState,
|
||||
);
|
||||
return useMemo(() => {
|
||||
if (isMatchingLocation(AppPath.Verify)) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
isMatchingLocation(AppPath.Invite) ||
|
||||
isMatchingLocation(AppPath.ResetPassword)
|
||||
) {
|
||||
return isDefaultLayoutAuthModalVisible;
|
||||
}
|
||||
if (
|
||||
OnboardingStatus.Incomplete === onboardingStatus ||
|
||||
OnboardingStatus.OngoingUserCreation === onboardingStatus ||
|
||||
OnboardingStatus.OngoingProfileCreation === onboardingStatus ||
|
||||
OnboardingStatus.OngoingWorkspaceActivation === onboardingStatus
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (isMatchingLocation(AppPath.PlanRequired)) {
|
||||
return (
|
||||
OnboardingStatus.CompletedWithoutSubscription === onboardingStatus ||
|
||||
OnboardingStatus.Canceled === onboardingStatus
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}, [isDefaultLayoutAuthModalVisible, isMatchingLocation, onboardingStatus]);
|
||||
};
|
@ -1,12 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { css, Global, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
|
||||
|
||||
import { AuthModal } from '@/auth/components/Modal';
|
||||
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
|
||||
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
|
||||
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
|
||||
@ -15,11 +12,10 @@ import { MobileNavigationBar } from '@/navigation/components/MobileNavigationBar
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
|
||||
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
||||
import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
|
||||
const StyledLayout = styled.div`
|
||||
background: ${({ theme }) => theme.background.noisy};
|
||||
@ -64,28 +60,11 @@ const StyledMainContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const DefaultLayout = () => {
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
const isMobile = useIsMobile();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
const theme = useTheme();
|
||||
const widowsWidth = useScreenSize().width;
|
||||
const isMatchingLocation = useIsMatchingLocation();
|
||||
const showAuthModal = useMemo(() => {
|
||||
return (
|
||||
(onboardingStatus &&
|
||||
[
|
||||
OnboardingStatus.Incomplete,
|
||||
OnboardingStatus.OngoingUserCreation,
|
||||
OnboardingStatus.OngoingProfileCreation,
|
||||
OnboardingStatus.OngoingWorkspaceActivation,
|
||||
].includes(onboardingStatus)) ||
|
||||
isMatchingLocation(AppPath.ResetPassword) ||
|
||||
isMatchingLocation(AppPath.Invite) ||
|
||||
(isMatchingLocation(AppPath.PlanRequired) &&
|
||||
(OnboardingStatus.CompletedWithoutSubscription ||
|
||||
OnboardingStatus.Canceled))
|
||||
);
|
||||
}, [isMatchingLocation, onboardingStatus]);
|
||||
const windowsWidth = useScreenSize().width;
|
||||
const showAuthModal = useShowAuthModal();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -104,7 +83,7 @@ export const DefaultLayout = () => {
|
||||
animate={{
|
||||
marginLeft:
|
||||
isSettingsPage && !isMobile
|
||||
? (widowsWidth -
|
||||
? (windowsWidth -
|
||||
(OBJECT_SETTINGS_WIDTH +
|
||||
DESKTOP_NAV_DRAWER_WIDTHS.menu +
|
||||
64)) /
|
||||
|
@ -0,0 +1,6 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const isDefaultLayoutAuthModalVisibleState = createState<boolean>({
|
||||
key: 'isDefaultLayoutAuthModalVisibleState',
|
||||
defaultValue: false,
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
|
||||
|
||||
export const DefaultHomePage = () => {
|
||||
const { defaultHomePagePath } = useDefaultHomePagePath();
|
||||
|
||||
return <Navigate to={defaultHomePagePath} />;
|
||||
};
|
@ -5,7 +5,7 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { useSignOutAndRedirect } from '@/auth/hooks/useSignOutAndRedirect';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { SubscriptionBenefit } from '@/billing/components/SubscriptionBenefit';
|
||||
import { SubscriptionCard } from '@/billing/components/SubscriptionCard';
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
@ -95,7 +95,7 @@ export const ChooseYourPlan = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const handleLogout = useSignOutAndRedirect();
|
||||
const { signOut } = useAuth();
|
||||
|
||||
const computeInfo = (
|
||||
price: ProductPriceEntity,
|
||||
@ -175,7 +175,7 @@ export const ChooseYourPlan = () => {
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<StyledLinkGroup>
|
||||
<ActionLink onClick={handleLogout}>Log out</ActionLink>
|
||||
<ActionLink onClick={signOut}>Log out</ActionLink>
|
||||
<span />
|
||||
<ActionLink href={CAL_LINK} target="_blank" rel="noreferrer">
|
||||
Book a Call
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useMemo } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
@ -10,10 +9,7 @@ import { SignInUpForm } from '@/auth/sign-in-up/components/SignInUpForm';
|
||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||
import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { Loader } from '@/ui/feedback/loader/components/Loader';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||
import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching';
|
||||
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
|
||||
@ -26,13 +22,8 @@ const StyledContentContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const Invite = () => {
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
workspace: workspaceFromInviteHash,
|
||||
loading: workspaceFromInviteHashLoading,
|
||||
workspaceInviteHash,
|
||||
} = useWorkspaceFromInviteHash();
|
||||
const { workspace: workspaceFromInviteHash, workspaceInviteHash } =
|
||||
useWorkspaceFromInviteHash();
|
||||
const { form } = useSignInUpForm();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const [addUserToWorkspace] = useAddUserToWorkspaceMutation();
|
||||
@ -56,68 +47,41 @@ export const Invite = () => {
|
||||
await switchWorkspace(workspaceFromInviteHash.id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isDefined(workspaceFromInviteHash) &&
|
||||
!workspaceFromInviteHashLoading
|
||||
) {
|
||||
enqueueSnackBar('workspace does not exist', {
|
||||
variant: SnackBarVariant.Error,
|
||||
});
|
||||
if (isDefined(currentWorkspace)) {
|
||||
navigate(AppPath.Index);
|
||||
} else {
|
||||
navigate(AppPath.SignInUp);
|
||||
}
|
||||
}
|
||||
if (
|
||||
if (
|
||||
!isDefined(workspaceFromInviteHash) ||
|
||||
(isDefined(workspaceFromInviteHash) &&
|
||||
isDefined(currentWorkspace) &&
|
||||
currentWorkspace.id === workspaceFromInviteHash?.id
|
||||
) {
|
||||
enqueueSnackBar(
|
||||
`You already belong to ${workspaceFromInviteHash?.displayName} workspace`,
|
||||
{
|
||||
variant: SnackBarVariant.Info,
|
||||
},
|
||||
);
|
||||
navigate(AppPath.Index);
|
||||
}
|
||||
}, [
|
||||
navigate,
|
||||
enqueueSnackBar,
|
||||
currentWorkspace,
|
||||
workspaceFromInviteHash,
|
||||
workspaceFromInviteHashLoading,
|
||||
]);
|
||||
workspaceFromInviteHash.id === currentWorkspace.id)
|
||||
) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
!workspaceFromInviteHashLoading && (
|
||||
<>
|
||||
<AnimatedEaseIn>
|
||||
<Logo workspaceLogo={workspaceFromInviteHash?.logo} />
|
||||
</AnimatedEaseIn>
|
||||
<Title animate>{title}</Title>
|
||||
{isDefined(currentWorkspace) && workspaceFromInviteHash ? (
|
||||
<>
|
||||
<StyledContentContainer>
|
||||
<MainButton
|
||||
variant="secondary"
|
||||
title="Continue"
|
||||
type="submit"
|
||||
onClick={handleUserJoinWorkspace}
|
||||
Icon={() => form.formState.isSubmitting && <Loader />}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledContentContainer>
|
||||
<FooterNote>
|
||||
By using Twenty, you agree to the Terms of Service and Privacy
|
||||
Policy.
|
||||
</FooterNote>
|
||||
</>
|
||||
) : (
|
||||
<SignInUpForm />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
<>
|
||||
<AnimatedEaseIn>
|
||||
<Logo workspaceLogo={workspaceFromInviteHash?.logo} />
|
||||
</AnimatedEaseIn>
|
||||
<Title animate>{title}</Title>
|
||||
{isDefined(currentWorkspace) ? (
|
||||
<>
|
||||
<StyledContentContainer>
|
||||
<MainButton
|
||||
variant="secondary"
|
||||
title="Continue"
|
||||
type="submit"
|
||||
onClick={handleUserJoinWorkspace}
|
||||
Icon={() => form.formState.isSubmitting && <Loader />}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledContentContainer>
|
||||
<FooterNote>
|
||||
By using Twenty, you agree to the Terms of Service and Privacy
|
||||
Policy.
|
||||
</FooterNote>
|
||||
</>
|
||||
) : (
|
||||
<SignInUpForm />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -7,19 +7,20 @@ import styled from '@emotion/styled';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Logo } from '@/auth/components/Logo';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { useNavigateAfterSignInUp } from '@/auth/sign-in-up/hooks/useNavigateAfterSignInUp';
|
||||
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { MainButton } from '@/ui/input/button/components/MainButton';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState';
|
||||
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
|
||||
import {
|
||||
useUpdatePasswordViaResetTokenMutation,
|
||||
@ -73,6 +74,7 @@ export const PasswordReset = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
const [isTokenValid, setIsTokenValid] = useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@ -80,6 +82,9 @@ export const PasswordReset = () => {
|
||||
|
||||
const isLoggedIn = useIsLogged();
|
||||
|
||||
const setIsDefaultLayoutAuthModalVisibleState = useSetRecoilState(
|
||||
isDefaultLayoutAuthModalVisibleState,
|
||||
);
|
||||
const { control, handleSubmit } = useForm<Form>({
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
@ -89,7 +94,7 @@ export const PasswordReset = () => {
|
||||
resolver: zodResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { loading: isValidatingToken } = useValidatePasswordResetTokenQuery({
|
||||
useValidatePasswordResetTokenQuery({
|
||||
variables: {
|
||||
token: passwordResetToken ?? '',
|
||||
},
|
||||
@ -98,13 +103,11 @@ export const PasswordReset = () => {
|
||||
enqueueSnackBar(error?.message ?? 'Token Invalid', {
|
||||
variant: SnackBarVariant.Error,
|
||||
});
|
||||
if (!isLoggedIn) {
|
||||
navigate(AppPath.SignInUp);
|
||||
} else {
|
||||
navigate(AppPath.Index);
|
||||
}
|
||||
navigate(AppPath.Index);
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
setIsTokenValid(true);
|
||||
setIsDefaultLayoutAuthModalVisibleState(true);
|
||||
if (isNonEmptyString(data?.validatePasswordResetToken?.email)) {
|
||||
setEmail(data.validatePasswordResetToken.email);
|
||||
}
|
||||
@ -116,8 +119,6 @@ export const PasswordReset = () => {
|
||||
|
||||
const { signInWithCredentials } = useAuth();
|
||||
|
||||
const { navigateAfterSignInUp } = useNavigateAfterSignInUp();
|
||||
|
||||
const onSubmit = async (formData: Form) => {
|
||||
try {
|
||||
const { data } = await updatePasswordViaToken({
|
||||
@ -142,12 +143,7 @@ export const PasswordReset = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
workspace: currentWorkspace,
|
||||
workspaceMember: currentWorkspaceMember,
|
||||
} = await signInWithCredentials(email || '', formData.newPassword);
|
||||
|
||||
navigateAfterSignInUp(currentWorkspace, currentWorkspaceMember);
|
||||
await signInWithCredentials(email || '', formData.newPassword);
|
||||
} catch (err) {
|
||||
logError(err);
|
||||
enqueueSnackBar(
|
||||
@ -160,89 +156,90 @@ export const PasswordReset = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledMainContainer>
|
||||
<AnimatedEaseIn>
|
||||
<Logo />
|
||||
</AnimatedEaseIn>
|
||||
<Title animate>Reset Password</Title>
|
||||
<StyledContentContainer>
|
||||
{isValidatingToken && (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.quaternary}
|
||||
highlightColor={theme.background.secondary}
|
||||
>
|
||||
<Skeleton
|
||||
height={32}
|
||||
count={2}
|
||||
style={{
|
||||
marginBottom: theme.spacing(2),
|
||||
}}
|
||||
/>
|
||||
</SkeletonTheme>
|
||||
)}
|
||||
{email && (
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)}>
|
||||
<StyledFullWidthMotionDiv
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 800,
|
||||
damping: 35,
|
||||
}}
|
||||
isTokenValid && (
|
||||
<StyledMainContainer>
|
||||
<AnimatedEaseIn>
|
||||
<Logo />
|
||||
</AnimatedEaseIn>
|
||||
<Title animate>Reset Password</Title>
|
||||
<StyledContentContainer>
|
||||
{!email ? (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.quaternary}
|
||||
highlightColor={theme.background.secondary}
|
||||
>
|
||||
<StyledInputContainer>
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={email}
|
||||
placeholder="Email"
|
||||
fullWidth
|
||||
disabled
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
</StyledFullWidthMotionDiv>
|
||||
<StyledFullWidthMotionDiv
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 800,
|
||||
damping: 35,
|
||||
}}
|
||||
>
|
||||
<Controller
|
||||
name="newPassword"
|
||||
control={control}
|
||||
render={({
|
||||
field: { onChange, onBlur, value },
|
||||
fieldState: { error },
|
||||
}) => (
|
||||
<StyledInputContainer>
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={value}
|
||||
type="password"
|
||||
placeholder="New Password"
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
error={error?.message}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
)}
|
||||
<Skeleton
|
||||
height={32}
|
||||
count={2}
|
||||
style={{
|
||||
marginBottom: theme.spacing(2),
|
||||
}}
|
||||
/>
|
||||
</StyledFullWidthMotionDiv>
|
||||
</SkeletonTheme>
|
||||
) : (
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)}>
|
||||
<StyledFullWidthMotionDiv
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 800,
|
||||
damping: 35,
|
||||
}}
|
||||
>
|
||||
<StyledInputContainer>
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={email}
|
||||
placeholder="Email"
|
||||
fullWidth
|
||||
disabled
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
</StyledFullWidthMotionDiv>
|
||||
<StyledFullWidthMotionDiv
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 800,
|
||||
damping: 35,
|
||||
}}
|
||||
>
|
||||
<Controller
|
||||
name="newPassword"
|
||||
control={control}
|
||||
render={({
|
||||
field: { onChange, onBlur, value },
|
||||
fieldState: { error },
|
||||
}) => (
|
||||
<StyledInputContainer>
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={value}
|
||||
type="password"
|
||||
placeholder="New Password"
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
error={error?.message}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledInputContainer>
|
||||
)}
|
||||
/>
|
||||
</StyledFullWidthMotionDiv>
|
||||
|
||||
<MainButton
|
||||
variant="secondary"
|
||||
title="Change Password"
|
||||
type="submit"
|
||||
fullWidth
|
||||
disabled={isUpdatingPassword}
|
||||
/>
|
||||
</StyledForm>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
</StyledMainContainer>
|
||||
<MainButton
|
||||
variant="secondary"
|
||||
title="Change Password"
|
||||
type="submit"
|
||||
fullWidth
|
||||
disabled={isUpdatingPassword}
|
||||
/>
|
||||
</StyledForm>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
</StyledMainContainer>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user