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:
@ -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 />} />
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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 { 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}` : ''),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 { 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,
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,6 @@ export const useOnboardingStatus = (): OnboardingStatus | undefined => {
|
|||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
currentWorkspaceMember,
|
currentWorkspaceMember,
|
||||||
currentWorkspace,
|
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 { 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,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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 (
|
||||||
|
|||||||
@ -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 { 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)) /
|
||||||
|
|||||||
@ -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 { 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
|
||||||
|
|||||||
@ -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 />
|
)}
|
||||||
)}
|
</>
|
||||||
</>
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user