5095 move onboardingstatus computation from frontend to backend (#5954)
- move front `onboardingStatus` computing to server side - add logic to `useSetNextOnboardingStatus` - update some missing redirections in `usePageChangeEffectNavigateLocation` - separate subscriptionStatus from onboardingStatus
This commit is contained in:
@ -0,0 +1,61 @@
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { CurrentUser, currentUserState } from '@/auth/states/currentUserState';
|
||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
|
||||
import { OnboardingStatus } from '~/generated/graphql';
|
||||
|
||||
const tokenPair = {
|
||||
accessToken: { token: 'accessToken', expiresAt: 'expiresAt' },
|
||||
refreshToken: { token: 'refreshToken', expiresAt: 'expiresAt' },
|
||||
};
|
||||
const currentUser = {
|
||||
id: '1',
|
||||
onboardingStatus: null,
|
||||
} as CurrentUser;
|
||||
|
||||
const renderHooks = () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const onboardingStatus = useOnboardingStatus();
|
||||
const setCurrentUser = useSetRecoilState(currentUserState);
|
||||
const setTokenPair = useSetRecoilState(tokenPairState);
|
||||
|
||||
return {
|
||||
onboardingStatus,
|
||||
setCurrentUser,
|
||||
setTokenPair,
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: RecoilRoot,
|
||||
},
|
||||
);
|
||||
return { result };
|
||||
};
|
||||
|
||||
describe('useOnboardingStatus', () => {
|
||||
it(`should return "undefined" when user is not logged in`, async () => {
|
||||
const { result } = renderHooks();
|
||||
expect(result.current.onboardingStatus).toBe(undefined);
|
||||
});
|
||||
|
||||
Object.values(OnboardingStatus).forEach((onboardingStatus) => {
|
||||
it(`should return "${onboardingStatus}"`, async () => {
|
||||
const { result } = renderHooks();
|
||||
const { setTokenPair, setCurrentUser } = result.current;
|
||||
|
||||
act(() => {
|
||||
setTokenPair(tokenPair);
|
||||
setCurrentUser({
|
||||
...currentUser,
|
||||
onboardingStatus,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.onboardingStatus).toBe(onboardingStatus);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,87 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
|
||||
import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql';
|
||||
import {
|
||||
mockDefaultWorkspace,
|
||||
mockedUserData,
|
||||
} from '~/testing/mock-data/users';
|
||||
|
||||
jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
|
||||
useFindManyRecords: jest.fn(),
|
||||
}));
|
||||
const setupMockWorkspaceMembers = (withManyWorkspaceMembers = false) => {
|
||||
jest
|
||||
.requireMock('@/object-record/hooks/useFindManyRecords')
|
||||
.useFindManyRecords.mockReturnValue({
|
||||
records: withManyWorkspaceMembers ? [{}, {}] : [{}],
|
||||
});
|
||||
};
|
||||
|
||||
const renderHooks = (
|
||||
onboardingStatus: OnboardingStatus,
|
||||
withCurrentBillingSubscription: boolean,
|
||||
) => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const [currentUser, setCurrentUser] = useRecoilState(currentUserState);
|
||||
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
|
||||
const setNextOnboardingStatus = useSetNextOnboardingStatus();
|
||||
return {
|
||||
currentUser,
|
||||
setCurrentUser,
|
||||
setCurrentWorkspace,
|
||||
setNextOnboardingStatus,
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: RecoilRoot,
|
||||
},
|
||||
);
|
||||
act(() => {
|
||||
result.current.setCurrentUser({ ...mockedUserData, onboardingStatus });
|
||||
result.current.setCurrentWorkspace({
|
||||
...mockDefaultWorkspace,
|
||||
currentBillingSubscription: withCurrentBillingSubscription
|
||||
? { id: v4(), status: SubscriptionStatus.Active }
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
act(() => {
|
||||
result.current.setNextOnboardingStatus();
|
||||
});
|
||||
return result.current.currentUser?.onboardingStatus;
|
||||
};
|
||||
|
||||
describe('useSetNextOnboardingStatus', () => {
|
||||
it('should set next onboarding status for ProfileCreation', () => {
|
||||
setupMockWorkspaceMembers();
|
||||
const nextOnboardingStatus = renderHooks(
|
||||
OnboardingStatus.ProfileCreation,
|
||||
false,
|
||||
);
|
||||
expect(nextOnboardingStatus).toEqual(OnboardingStatus.SyncEmail);
|
||||
});
|
||||
|
||||
it('should set next onboarding status for SyncEmail', () => {
|
||||
setupMockWorkspaceMembers();
|
||||
const nextOnboardingStatus = renderHooks(OnboardingStatus.SyncEmail, false);
|
||||
expect(nextOnboardingStatus).toEqual(OnboardingStatus.InviteTeam);
|
||||
});
|
||||
|
||||
it('should skip invite when workspaceMembers exist', () => {
|
||||
setupMockWorkspaceMembers(true);
|
||||
const nextOnboardingStatus = renderHooks(OnboardingStatus.SyncEmail, true);
|
||||
expect(nextOnboardingStatus).toEqual(OnboardingStatus.Completed);
|
||||
});
|
||||
|
||||
it('should set next onboarding status for Completed', () => {
|
||||
setupMockWorkspaceMembers();
|
||||
const nextOnboardingStatus = renderHooks(OnboardingStatus.InviteTeam, true);
|
||||
expect(nextOnboardingStatus).toEqual(OnboardingStatus.Completed);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { OnboardingStatus } from '~/generated/graphql';
|
||||
|
||||
export const useOnboardingStatus = (): OnboardingStatus | null | undefined => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
return currentUser?.onboardingStatus;
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { CurrentUser, currentUserState } from '@/auth/states/currentUserState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { OnboardingStatus } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const getNextOnboardingStatus = (
|
||||
currentUser: CurrentUser | null,
|
||||
workspaceMembers: WorkspaceMember[],
|
||||
) => {
|
||||
if (currentUser?.onboardingStatus === OnboardingStatus.ProfileCreation) {
|
||||
return OnboardingStatus.SyncEmail;
|
||||
}
|
||||
if (
|
||||
currentUser?.onboardingStatus === OnboardingStatus.SyncEmail &&
|
||||
workspaceMembers.length === 1
|
||||
) {
|
||||
return OnboardingStatus.InviteTeam;
|
||||
}
|
||||
return OnboardingStatus.Completed;
|
||||
};
|
||||
|
||||
export const useSetNextOnboardingStatus = () => {
|
||||
const { records: workspaceMembers } = useFindManyRecords<WorkspaceMember>({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
});
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
const nextOnboardingStatus = getNextOnboardingStatus(
|
||||
currentUser,
|
||||
workspaceMembers,
|
||||
);
|
||||
set(currentUserState, (current) => {
|
||||
if (isDefined(current)) {
|
||||
return {
|
||||
...current,
|
||||
onboardingStatus: nextOnboardingStatus,
|
||||
};
|
||||
}
|
||||
return current;
|
||||
});
|
||||
},
|
||||
[workspaceMembers, currentUser],
|
||||
);
|
||||
};
|
||||
@ -1,41 +0,0 @@
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { OnboardingStep } from '~/generated/graphql';
|
||||
|
||||
const getNextOnboardingStep = (
|
||||
currentOnboardingStep: OnboardingStep,
|
||||
workspaceMembers: WorkspaceMember[],
|
||||
) => {
|
||||
if (currentOnboardingStep === OnboardingStep.SyncEmail) {
|
||||
return workspaceMembers && workspaceMembers.length > 1
|
||||
? null
|
||||
: OnboardingStep.InviteTeam;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useSetNextOnboardingStep = () => {
|
||||
const setCurrentUser = useSetRecoilState(currentUserState);
|
||||
const { records: workspaceMembers } = useFindManyRecords<WorkspaceMember>({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
});
|
||||
return useRecoilCallback(
|
||||
() => (currentOnboardingStep: OnboardingStep) => {
|
||||
setCurrentUser(
|
||||
(current) =>
|
||||
({
|
||||
...current,
|
||||
onboardingStep: getNextOnboardingStep(
|
||||
currentOnboardingStep,
|
||||
workspaceMembers,
|
||||
),
|
||||
}) as any,
|
||||
);
|
||||
},
|
||||
[setCurrentUser, workspaceMembers],
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user