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:
martmull
2024-05-25 10:36:59 +02:00
committed by GitHub
parent f455ad4001
commit 9080981990
26 changed files with 976 additions and 418 deletions

View File

@ -43,7 +43,6 @@ import { Invite } from '~/pages/auth/Invite';
import { PasswordReset } from '~/pages/auth/PasswordReset'; import { PasswordReset } from '~/pages/auth/PasswordReset';
import { PaymentSuccess } from '~/pages/auth/PaymentSuccess'; import { PaymentSuccess } from '~/pages/auth/PaymentSuccess';
import { SignInUp } from '~/pages/auth/SignInUp'; import { SignInUp } from '~/pages/auth/SignInUp';
import { DefaultHomePage } from '~/pages/DefaultHomePage';
import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect'; import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect';
import { NotFound } from '~/pages/not-found/NotFound'; import { NotFound } from '~/pages/not-found/NotFound';
import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage'; import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
@ -139,10 +138,7 @@ const createRouter = (isBillingEnabled?: boolean) =>
path={AppPath.PlanRequiredSuccess} path={AppPath.PlanRequiredSuccess}
element={<PaymentSuccess />} element={<PaymentSuccess />}
/> />
<Route <Route path={indexAppPath.getIndexAppPath()} element={<></>} />
path={indexAppPath.getIndexAppPath()}
element={<DefaultHomePage />}
/>
<Route path={AppPath.TasksPage} element={<Tasks />} /> <Route path={AppPath.TasksPage} element={<Tasks />} />
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} /> <Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
<Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} /> <Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} />

View File

@ -5,11 +5,8 @@ import { IconCheckbox } from 'twenty-ui';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { useEventTracker } from '@/analytics/hooks/useEventTracker'; 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 { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState'; import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandType } from '@/command-menu/types/Command'; import { CommandType } from '@/command-menu/types/Command';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
@ -17,36 +14,32 @@ import { AppBasePath } from '@/types/AppBasePath';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useGetWorkspaceFromInviteHashLazyQuery } from '~/generated/graphql';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
// TODO: break down into smaller functions and / or hooks // TODO: break down into smaller functions and / or hooks
// - moved usePageChangeEffectNavigateLocation into dedicated hook
export const PageChangeEffect = () => { export const PageChangeEffect = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const isMatchingLocation = useIsMatchingLocation(); const isMatchingLocation = useIsMatchingLocation();
const { enqueueSnackBar } = useSnackBar();
const [previousLocation, setPreviousLocation] = useState(''); const [previousLocation, setPreviousLocation] = useState('');
const onboardingStatus = useOnboardingStatus();
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const location = useLocation(); const location = useLocation();
const pageChangeEffectNavigateLocation =
usePageChangeEffectNavigateLocation();
const eventTracker = useEventTracker(); const eventTracker = useEventTracker();
const [workspaceFromInviteHashQuery] =
useGetWorkspaceFromInviteHashLazyQuery();
const { addToCommandMenu, setToInitialCommandMenu } = useCommandMenu(); const { addToCommandMenu, setToInitialCommandMenu } = useCommandMenu();
const openCreateActivity = useOpenCreateActivityDrawer(); const openCreateActivity = useOpenCreateActivityDrawer();
const isSignUpDisabled = useRecoilValue(isSignUpDisabledState);
useEffect(() => { useEffect(() => {
if (!previousLocation || previousLocation !== location.pathname) { if (!previousLocation || previousLocation !== location.pathname) {
setPreviousLocation(location.pathname); setPreviousLocation(location.pathname);
@ -56,76 +49,10 @@ export const PageChangeEffect = () => {
}, [location, previousLocation]); }, [location, previousLocation]);
useEffect(() => { useEffect(() => {
const isMatchingOngoingUserCreationRoute = if (isDefined(pageChangeEffectNavigateLocation)) {
isMatchingLocation(AppPath.SignInUp) || navigate(pageChangeEffectNavigateLocation);
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);
} }
}, [ }, [navigate, pageChangeEffectNavigateLocation]);
enqueueSnackBar,
isMatchingLocation,
isSignUpDisabled,
location.pathname,
navigate,
onboardingStatus,
workspaceFromInviteHashQuery,
]);
useEffect(() => { useEffect(() => {
switch (true) { switch (true) {

View File

@ -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',
);
});
});

View File

@ -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,
);
});
});
});

View File

@ -1,21 +1,31 @@
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey'; import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { AppPath } from '@/types/AppPath';
import { isDefined } from '~/utils/isDefined';
export const useDefaultHomePagePath = () => { export const useDefaultHomePagePath = () => {
const currentUser = useRecoilValue(currentUserState);
const { objectMetadataItem: companyObjectMetadataItem } = const { objectMetadataItem: companyObjectMetadataItem } =
useObjectMetadataItem({ useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Company, objectNameSingular: CoreObjectNameSingular.Company,
}); });
const { records } = usePrefetchedData(PrefetchKey.AllViews); const { records } = usePrefetchedData(PrefetchKey.AllViews);
if (!isDefined(currentUser)) {
return { defaultHomePagePath: AppPath.SignInUp };
}
const companyViewId = records.find( const companyViewId = records.find(
(view: any) => view?.objectMetadataId === companyObjectMetadataItem.id, (view: any) => view?.objectMetadataId === companyObjectMetadataItem.id,
)?.id; )?.id;
const defaultHomePagePath =
'/objects/companies' + (companyViewId ? `?view=${companyViewId}` : '');
return { defaultHomePagePath }; return {
defaultHomePagePath:
'/objects/companies' + (companyViewId ? `?view=${companyViewId}` : ''),
};
}; };

View File

@ -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;
};

View File

@ -1,11 +1,14 @@
import { useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client'; 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 { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { previousUrlState } from '@/auth/states/previousUrlState'; import { previousUrlState } from '@/auth/states/previousUrlState';
import { tokenPairState } from '@/auth/states/tokenPairState'; import { tokenPairState } from '@/auth/states/tokenPairState';
import { workspacesState } from '@/auth/states/workspaces';
import { isDebugModeState } from '@/client-config/states/isDebugModeState'; import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { REACT_APP_SERVER_BASE_URL } from '~/config'; 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>> = {}) => { export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
// eslint-disable-next-line @nx/workspace-no-state-useref // eslint-disable-next-line @nx/workspace-no-state-useref
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null); const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const [isDebugMode] = useRecoilState(isDebugModeState); const [isDebugMode] = useRecoilState(isDebugModeState);
const navigate = useNavigate(); const navigate = useNavigate();
const isMatchingLocation = useIsMatchingLocation(); const isMatchingLocation = useIsMatchingLocation();
const [tokenPair, setTokenPair] = useRecoilState(tokenPairState); 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 [, setPreviousUrl] = useRecoilState(previousUrlState);
const location = useLocation(); const location = useLocation();
@ -55,6 +66,10 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
}, },
onUnauthenticatedError: () => { onUnauthenticatedError: () => {
setTokenPair(null); setTokenPair(null);
setCurrentUser(null);
setCurrentWorkspaceMember(null);
setCurrentWorkspace(null);
setWorkspaces(null);
if ( if (
!isMatchingLocation(AppPath.Verify) && !isMatchingLocation(AppPath.Verify) &&
!isMatchingLocation(AppPath.SignInUp) && !isMatchingLocation(AppPath.SignInUp) &&
@ -75,6 +90,10 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
setTokenPair, setTokenPair,
setCurrentUser,
setCurrentWorkspaceMember,
setCurrentWorkspace,
setWorkspaces,
isDebugMode, isDebugMode,
currentWorkspace?.currentCacheVersion, currentWorkspace?.currentCacheVersion,
setPreviousUrl, setPreviousUrl,

View File

@ -1,16 +1,13 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom'; import { useNavigate, useSearchParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { useAuth } from '@/auth/hooks/useAuth'; import { useAuth } from '@/auth/hooks/useAuth';
import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
export const VerifyEffect = () => { export const VerifyEffect = () => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const loginToken = searchParams.get('loginToken'); const loginToken = searchParams.get('loginToken');
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const isLogged = useIsLogged(); const isLogged = useIsLogged();
const navigate = useNavigate(); const navigate = useNavigate();
@ -23,12 +20,6 @@ export const VerifyEffect = () => {
navigate(AppPath.SignInUp); navigate(AppPath.SignInUp);
} else { } else {
await verify(loginToken); await verify(loginToken);
if (currentWorkspace?.activationStatus === 'active') {
navigate(AppPath.Index);
} else {
navigate(AppPath.CreateWorkspace);
}
} }
}; };

View File

@ -20,6 +20,6 @@ export const useOnboardingStatus = (): OnboardingStatus | undefined => {
isLoggedIn, isLoggedIn,
currentWorkspaceMember, currentWorkspaceMember,
currentWorkspace, currentWorkspace,
isBillingEnabled: billing?.isBillingEnabled, isBillingEnabled: billing?.isBillingEnabled || false,
}); });
}; };

View File

@ -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]);
};

View File

@ -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 };
};

View File

@ -2,7 +2,6 @@ import { useCallback, useState } from 'react';
import { SubmitHandler, UseFormReturn } from 'react-hook-form'; import { SubmitHandler, UseFormReturn } from 'react-hook-form';
import { useParams } from 'react-router-dom'; 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 { Form } from '@/auth/sign-in-up/hooks/useSignInUpForm';
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken'; import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken'; import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
@ -31,8 +30,6 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
const workspaceInviteHash = useParams().workspaceInviteHash; const workspaceInviteHash = useParams().workspaceInviteHash;
const { navigateAfterSignInUp } = useNavigateAfterSignInUp();
const [isInviteMode] = useState(() => isMatchingLocation(AppPath.Invite)); const [isInviteMode] = useState(() => isMatchingLocation(AppPath.Invite));
const [signInUpStep, setSignInUpStep] = useState<SignInUpStep>( const [signInUpStep, setSignInUpStep] = useState<SignInUpStep>(
@ -105,24 +102,18 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
throw new Error('Email and password are required'); throw new Error('Email and password are required');
} }
const { signInUpMode === SignInUpMode.SignIn && !isInviteMode
workspace: currentWorkspace, ? await signInWithCredentials(
workspaceMember: currentWorkspaceMember, data.email.toLowerCase().trim(),
} = data.password,
signInUpMode === SignInUpMode.SignIn && !isInviteMode token,
? await signInWithCredentials( )
data.email.toLowerCase().trim(), : await signUpWithCredentials(
data.password, data.email.toLowerCase().trim(),
token, data.password,
) workspaceInviteHash,
: await signUpWithCredentials( token,
data.email.toLowerCase().trim(), );
data.password,
workspaceInviteHash,
token,
);
navigateAfterSignInUp(currentWorkspace, currentWorkspaceMember);
} catch (err: any) { } catch (err: any) {
enqueueSnackBar(err?.message, { enqueueSnackBar(err?.message, {
variant: SnackBarVariant.Error, variant: SnackBarVariant.Error,
@ -136,7 +127,6 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
signInWithCredentials, signInWithCredentials,
signUpWithCredentials, signUpWithCredentials,
workspaceInviteHash, workspaceInviteHash,
navigateAfterSignInUp,
enqueueSnackBar, enqueueSnackBar,
], ],
); );

View File

@ -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 { useGetWorkspaceFromInviteHashQuery } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
export const useWorkspaceFromInviteHash = () => { export const useWorkspaceFromInviteHash = () => {
const { enqueueSnackBar } = useSnackBar();
const navigate = useNavigate();
const workspaceInviteHash = useParams().workspaceInviteHash; const workspaceInviteHash = useParams().workspaceInviteHash;
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const [initiallyLoggedIn] = useState(isDefined(currentWorkspace));
const setIsDefaultLayoutAuthModalVisible = useSetRecoilState(
isDefaultLayoutAuthModalVisibleState,
);
const { data: workspaceFromInviteHash, loading } = const { data: workspaceFromInviteHash, loading } =
useGetWorkspaceFromInviteHashQuery({ useGetWorkspaceFromInviteHashQuery({
variables: { inviteHash: workspaceInviteHash || '' }, 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 { return {
workspace: workspaceFromInviteHash?.findWorkspaceFromInviteHash, workspace: workspaceFromInviteHash?.findWorkspaceFromInviteHash,

View File

@ -9,6 +9,7 @@ describe('getOnboardingStatus', () => {
isLoggedIn: false, isLoggedIn: false,
currentWorkspaceMember: null, currentWorkspaceMember: null,
currentWorkspace: null, currentWorkspace: null,
isBillingEnabled: false,
}); });
const ongoingWorkspaceActivation = getOnboardingStatus({ const ongoingWorkspaceActivation = getOnboardingStatus({
@ -18,6 +19,7 @@ describe('getOnboardingStatus', () => {
id: '1', id: '1',
activationStatus: 'inactive', activationStatus: 'inactive',
} as CurrentWorkspace, } as CurrentWorkspace,
isBillingEnabled: false,
}); });
const ongoingProfileCreation = getOnboardingStatus({ const ongoingProfileCreation = getOnboardingStatus({
@ -30,6 +32,7 @@ describe('getOnboardingStatus', () => {
id: '1', id: '1',
activationStatus: 'active', activationStatus: 'active',
} as CurrentWorkspace, } as CurrentWorkspace,
isBillingEnabled: false,
}); });
const completed = getOnboardingStatus({ const completed = getOnboardingStatus({
@ -45,6 +48,7 @@ describe('getOnboardingStatus', () => {
id: '1', id: '1',
activationStatus: 'active', activationStatus: 'active',
} as CurrentWorkspace, } as CurrentWorkspace,
isBillingEnabled: false,
}); });
const incomplete = getOnboardingStatus({ const incomplete = getOnboardingStatus({
@ -78,6 +82,7 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active', activationStatus: 'active',
subscriptionStatus: 'incomplete', subscriptionStatus: 'incomplete',
} as CurrentWorkspace, } as CurrentWorkspace,
isBillingEnabled: false,
}); });
const canceled = getOnboardingStatus({ const canceled = getOnboardingStatus({

View File

@ -25,7 +25,7 @@ export const getOnboardingStatus = ({
'createdAt' | 'updatedAt' | 'userId' | 'userEmail' | '__typename' 'createdAt' | 'updatedAt' | 'userId' | 'userEmail' | '__typename'
> | null; > | null;
currentWorkspace: CurrentWorkspace | null; currentWorkspace: CurrentWorkspace | null;
isBillingEnabled?: boolean; isBillingEnabled: boolean;
}) => { }) => {
if (!isLoggedIn) { if (!isLoggedIn) {
return OnboardingStatus.OngoingUserCreation; return OnboardingStatus.OngoingUserCreation;
@ -38,7 +38,7 @@ export const getOnboardingStatus = ({
} }
if ( if (
isBillingEnabled === true && isBillingEnabled &&
currentWorkspace.subscriptionStatus === 'incomplete' currentWorkspace.subscriptionStatus === 'incomplete'
) { ) {
return OnboardingStatus.Incomplete; return OnboardingStatus.Incomplete;
@ -55,31 +55,19 @@ export const getOnboardingStatus = ({
return OnboardingStatus.OngoingProfileCreation; return OnboardingStatus.OngoingProfileCreation;
} }
if ( if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'canceled') {
isBillingEnabled === true &&
currentWorkspace.subscriptionStatus === 'canceled'
) {
return OnboardingStatus.Canceled; return OnboardingStatus.Canceled;
} }
if ( if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'past_due') {
isBillingEnabled === true &&
currentWorkspace.subscriptionStatus === 'past_due'
) {
return OnboardingStatus.PastDue; return OnboardingStatus.PastDue;
} }
if ( if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'unpaid') {
isBillingEnabled === true &&
currentWorkspace.subscriptionStatus === 'unpaid'
) {
return OnboardingStatus.Unpaid; return OnboardingStatus.Unpaid;
} }
if ( if (isBillingEnabled && !currentWorkspace.currentBillingSubscription) {
isBillingEnabled === true &&
!currentWorkspace.currentBillingSubscription
) {
return OnboardingStatus.CompletedWithoutSubscription; return OnboardingStatus.CompletedWithoutSubscription;
} }

View File

@ -15,7 +15,7 @@ import {
IconUsers, IconUsers,
} from 'twenty-ui'; } from 'twenty-ui';
import { useSignOutAndRedirect } from '@/auth/hooks/useSignOutAndRedirect'; import { useAuth } from '@/auth/hooks/useAuth';
import { billingState } from '@/client-config/states/billingState'; import { billingState } from '@/client-config/states/billingState';
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem'; import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
import { SettingsPath } from '@/types/SettingsPath'; 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'; import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
export const SettingsNavigationDrawerItems = () => { export const SettingsNavigationDrawerItems = () => {
const handleLogout = useSignOutAndRedirect(); const { signOut } = useAuth();
const billing = useRecoilValue(billingState); const billing = useRecoilValue(billingState);
@ -113,7 +113,7 @@ export const SettingsNavigationDrawerItems = () => {
/> />
<NavigationDrawerItem <NavigationDrawerItem
label="Logout" label="Logout"
onClick={handleLogout} onClick={signOut}
Icon={IconDoorEnter} Icon={IconDoorEnter}
/> />
</NavigationDrawerSection> </NavigationDrawerSection>

View File

@ -2,7 +2,7 @@ import { useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { H2Title } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { useSignOutAndRedirect } from '@/auth/hooks/useSignOutAndRedirect'; import { useAuth } from '@/auth/hooks/useAuth';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
@ -15,11 +15,11 @@ export const DeleteAccount = () => {
const [deleteUserAccount] = useDeleteUserAccountMutation(); const [deleteUserAccount] = useDeleteUserAccountMutation();
const currentUser = useRecoilValue(currentUserState); const currentUser = useRecoilValue(currentUserState);
const userEmail = currentUser?.email; const userEmail = currentUser?.email;
const handleLogout = useSignOutAndRedirect(); const { signOut } = useAuth();
const deleteAccount = async () => { const deleteAccount = async () => {
await deleteUserAccount(); await deleteUserAccount();
handleLogout(); await signOut();
}; };
return ( return (

View File

@ -2,7 +2,7 @@ import { useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { H2Title } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { useSignOutAndRedirect } from '@/auth/hooks/useSignOutAndRedirect'; import { useAuth } from '@/auth/hooks/useAuth';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { import {
ConfirmationModal, ConfirmationModal,
@ -18,11 +18,11 @@ export const DeleteWorkspace = () => {
const currentUser = useRecoilValue(currentUserState); const currentUser = useRecoilValue(currentUserState);
const userEmail = currentUser?.email; const userEmail = currentUser?.email;
const handleLogout = useSignOutAndRedirect(); const { signOut } = useAuth();
const deleteWorkspace = async () => { const deleteWorkspace = async () => {
await deleteCurrentWorkspace(); await deleteCurrentWorkspace();
handleLogout(); await signOut();
}; };
return ( return (

View File

@ -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,
);
});
});
});

View File

@ -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]);
};

View File

@ -1,12 +1,9 @@
import { useMemo } from 'react';
import { Outlet } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
import { css, Global, useTheme } from '@emotion/react'; import { css, Global, useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion'; import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
import { AuthModal } from '@/auth/components/Modal'; 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 { CommandMenu } from '@/command-menu/components/CommandMenu';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary'; import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu'; 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 { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings'; import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage'; 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 { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize'; import { useScreenSize } from '@/ui/utilities/screen-size/hooks/useScreenSize';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
const StyledLayout = styled.div` const StyledLayout = styled.div`
background: ${({ theme }) => theme.background.noisy}; background: ${({ theme }) => theme.background.noisy};
@ -64,28 +60,11 @@ const StyledMainContainer = styled.div`
`; `;
export const DefaultLayout = () => { export const DefaultLayout = () => {
const onboardingStatus = useOnboardingStatus();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const isSettingsPage = useIsSettingsPage(); const isSettingsPage = useIsSettingsPage();
const theme = useTheme(); const theme = useTheme();
const widowsWidth = useScreenSize().width; const windowsWidth = useScreenSize().width;
const isMatchingLocation = useIsMatchingLocation(); const showAuthModal = useShowAuthModal();
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]);
return ( return (
<> <>
@ -104,7 +83,7 @@ export const DefaultLayout = () => {
animate={{ animate={{
marginLeft: marginLeft:
isSettingsPage && !isMobile isSettingsPage && !isMobile
? (widowsWidth - ? (windowsWidth -
(OBJECT_SETTINGS_WIDTH + (OBJECT_SETTINGS_WIDTH +
DESKTOP_NAV_DRAWER_WIDTHS.menu + DESKTOP_NAV_DRAWER_WIDTHS.menu +
64)) / 64)) /

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';
export const isDefaultLayoutAuthModalVisibleState = createState<boolean>({
key: 'isDefaultLayoutAuthModalVisibleState',
defaultValue: false,
});

View File

@ -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} />;
};

View File

@ -5,7 +5,7 @@ import { useRecoilValue } from 'recoil';
import { SubTitle } from '@/auth/components/SubTitle'; import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title'; 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 { SubscriptionBenefit } from '@/billing/components/SubscriptionBenefit';
import { SubscriptionCard } from '@/billing/components/SubscriptionCard'; import { SubscriptionCard } from '@/billing/components/SubscriptionCard';
import { billingState } from '@/client-config/states/billingState'; import { billingState } from '@/client-config/states/billingState';
@ -95,7 +95,7 @@ export const ChooseYourPlan = () => {
}; };
}; };
const handleLogout = useSignOutAndRedirect(); const { signOut } = useAuth();
const computeInfo = ( const computeInfo = (
price: ProductPriceEntity, price: ProductPriceEntity,
@ -175,7 +175,7 @@ export const ChooseYourPlan = () => {
disabled={isSubmitting} disabled={isSubmitting}
/> />
<StyledLinkGroup> <StyledLinkGroup>
<ActionLink onClick={handleLogout}>Log out</ActionLink> <ActionLink onClick={signOut}>Log out</ActionLink>
<span /> <span />
<ActionLink href={CAL_LINK} target="_blank" rel="noreferrer"> <ActionLink href={CAL_LINK} target="_blank" rel="noreferrer">
Book a Call Book a Call

View File

@ -1,5 +1,4 @@
import { useEffect, useMemo } from 'react'; import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; 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 { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash'; import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { AppPath } from '@/types/AppPath';
import { Loader } from '@/ui/feedback/loader/components/Loader'; 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 { MainButton } from '@/ui/input/button/components/MainButton';
import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching'; import { useWorkspaceSwitching } from '@/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching';
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn'; import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
@ -26,13 +22,8 @@ const StyledContentContainer = styled.div`
`; `;
export const Invite = () => { export const Invite = () => {
const { enqueueSnackBar } = useSnackBar(); const { workspace: workspaceFromInviteHash, workspaceInviteHash } =
const navigate = useNavigate(); useWorkspaceFromInviteHash();
const {
workspace: workspaceFromInviteHash,
loading: workspaceFromInviteHashLoading,
workspaceInviteHash,
} = useWorkspaceFromInviteHash();
const { form } = useSignInUpForm(); const { form } = useSignInUpForm();
const currentWorkspace = useRecoilValue(currentWorkspaceState); const currentWorkspace = useRecoilValue(currentWorkspaceState);
const [addUserToWorkspace] = useAddUserToWorkspaceMutation(); const [addUserToWorkspace] = useAddUserToWorkspaceMutation();
@ -56,68 +47,41 @@ export const Invite = () => {
await switchWorkspace(workspaceFromInviteHash.id); await switchWorkspace(workspaceFromInviteHash.id);
}; };
useEffect(() => { if (
if ( !isDefined(workspaceFromInviteHash) ||
!isDefined(workspaceFromInviteHash) && (isDefined(workspaceFromInviteHash) &&
!workspaceFromInviteHashLoading
) {
enqueueSnackBar('workspace does not exist', {
variant: SnackBarVariant.Error,
});
if (isDefined(currentWorkspace)) {
navigate(AppPath.Index);
} else {
navigate(AppPath.SignInUp);
}
}
if (
isDefined(currentWorkspace) && isDefined(currentWorkspace) &&
currentWorkspace.id === workspaceFromInviteHash?.id workspaceFromInviteHash.id === currentWorkspace.id)
) { ) {
enqueueSnackBar( return <></>;
`You already belong to ${workspaceFromInviteHash?.displayName} workspace`, }
{
variant: SnackBarVariant.Info,
},
);
navigate(AppPath.Index);
}
}, [
navigate,
enqueueSnackBar,
currentWorkspace,
workspaceFromInviteHash,
workspaceFromInviteHashLoading,
]);
return ( return (
!workspaceFromInviteHashLoading && ( <>
<> <AnimatedEaseIn>
<AnimatedEaseIn> <Logo workspaceLogo={workspaceFromInviteHash?.logo} />
<Logo workspaceLogo={workspaceFromInviteHash?.logo} /> </AnimatedEaseIn>
</AnimatedEaseIn> <Title animate>{title}</Title>
<Title animate>{title}</Title> {isDefined(currentWorkspace) ? (
{isDefined(currentWorkspace) && workspaceFromInviteHash ? ( <>
<> <StyledContentContainer>
<StyledContentContainer> <MainButton
<MainButton variant="secondary"
variant="secondary" title="Continue"
title="Continue" type="submit"
type="submit" onClick={handleUserJoinWorkspace}
onClick={handleUserJoinWorkspace} Icon={() => form.formState.isSubmitting && <Loader />}
Icon={() => form.formState.isSubmitting && <Loader />} fullWidth
fullWidth />
/> </StyledContentContainer>
</StyledContentContainer> <FooterNote>
<FooterNote> By using Twenty, you agree to the Terms of Service and Privacy
By using Twenty, you agree to the Terms of Service and Privacy Policy.
Policy. </FooterNote>
</FooterNote> </>
</> ) : (
) : ( <SignInUpForm />
<SignInUpForm /> )}
)} </>
</>
)
); );
}; };

View File

@ -7,19 +7,20 @@ import styled from '@emotion/styled';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useSetRecoilState } from 'recoil';
import { z } from 'zod'; import { z } from 'zod';
import { Logo } from '@/auth/components/Logo'; import { Logo } from '@/auth/components/Logo';
import { Title } from '@/auth/components/Title'; import { Title } from '@/auth/components/Title';
import { useAuth } from '@/auth/hooks/useAuth'; import { useAuth } from '@/auth/hooks/useAuth';
import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useNavigateAfterSignInUp } from '@/auth/sign-in-up/hooks/useNavigateAfterSignInUp';
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex'; import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { MainButton } from '@/ui/input/button/components/MainButton'; import { MainButton } from '@/ui/input/button/components/MainButton';
import { TextInputV2 } from '@/ui/input/components/TextInputV2'; import { TextInputV2 } from '@/ui/input/components/TextInputV2';
import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefaultLayoutAuthModalVisibleState';
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn'; import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
import { import {
useUpdatePasswordViaResetTokenMutation, useUpdatePasswordViaResetTokenMutation,
@ -73,6 +74,7 @@ export const PasswordReset = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [isTokenValid, setIsTokenValid] = useState(false);
const theme = useTheme(); const theme = useTheme();
@ -80,6 +82,9 @@ export const PasswordReset = () => {
const isLoggedIn = useIsLogged(); const isLoggedIn = useIsLogged();
const setIsDefaultLayoutAuthModalVisibleState = useSetRecoilState(
isDefaultLayoutAuthModalVisibleState,
);
const { control, handleSubmit } = useForm<Form>({ const { control, handleSubmit } = useForm<Form>({
mode: 'onChange', mode: 'onChange',
defaultValues: { defaultValues: {
@ -89,7 +94,7 @@ export const PasswordReset = () => {
resolver: zodResolver(validationSchema), resolver: zodResolver(validationSchema),
}); });
const { loading: isValidatingToken } = useValidatePasswordResetTokenQuery({ useValidatePasswordResetTokenQuery({
variables: { variables: {
token: passwordResetToken ?? '', token: passwordResetToken ?? '',
}, },
@ -98,13 +103,11 @@ export const PasswordReset = () => {
enqueueSnackBar(error?.message ?? 'Token Invalid', { enqueueSnackBar(error?.message ?? 'Token Invalid', {
variant: SnackBarVariant.Error, variant: SnackBarVariant.Error,
}); });
if (!isLoggedIn) { navigate(AppPath.Index);
navigate(AppPath.SignInUp);
} else {
navigate(AppPath.Index);
}
}, },
onCompleted: (data) => { onCompleted: (data) => {
setIsTokenValid(true);
setIsDefaultLayoutAuthModalVisibleState(true);
if (isNonEmptyString(data?.validatePasswordResetToken?.email)) { if (isNonEmptyString(data?.validatePasswordResetToken?.email)) {
setEmail(data.validatePasswordResetToken.email); setEmail(data.validatePasswordResetToken.email);
} }
@ -116,8 +119,6 @@ export const PasswordReset = () => {
const { signInWithCredentials } = useAuth(); const { signInWithCredentials } = useAuth();
const { navigateAfterSignInUp } = useNavigateAfterSignInUp();
const onSubmit = async (formData: Form) => { const onSubmit = async (formData: Form) => {
try { try {
const { data } = await updatePasswordViaToken({ const { data } = await updatePasswordViaToken({
@ -142,12 +143,7 @@ export const PasswordReset = () => {
return; return;
} }
const { await signInWithCredentials(email || '', formData.newPassword);
workspace: currentWorkspace,
workspaceMember: currentWorkspaceMember,
} = await signInWithCredentials(email || '', formData.newPassword);
navigateAfterSignInUp(currentWorkspace, currentWorkspaceMember);
} catch (err) { } catch (err) {
logError(err); logError(err);
enqueueSnackBar( enqueueSnackBar(
@ -160,89 +156,90 @@ export const PasswordReset = () => {
}; };
return ( return (
<StyledMainContainer> isTokenValid && (
<AnimatedEaseIn> <StyledMainContainer>
<Logo /> <AnimatedEaseIn>
</AnimatedEaseIn> <Logo />
<Title animate>Reset Password</Title> </AnimatedEaseIn>
<StyledContentContainer> <Title animate>Reset Password</Title>
{isValidatingToken && ( <StyledContentContainer>
<SkeletonTheme {!email ? (
baseColor={theme.background.quaternary} <SkeletonTheme
highlightColor={theme.background.secondary} 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,
}}
> >
<StyledInputContainer> <Skeleton
<TextInputV2 height={32}
autoFocus count={2}
value={email} style={{
placeholder="Email" marginBottom: theme.spacing(2),
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> </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 <MainButton
variant="secondary" variant="secondary"
title="Change Password" title="Change Password"
type="submit" type="submit"
fullWidth fullWidth
disabled={isUpdatingPassword} disabled={isUpdatingPassword}
/> />
</StyledForm> </StyledForm>
)} )}
</StyledContentContainer> </StyledContentContainer>
</StyledMainContainer> </StyledMainContainer>
)
); );
}; };