5622 add a syncemail onboarding step (#5689)

- add sync email onboarding step
- refactor calendar and email visibility enums
- add a new table `keyValuePair` in `core` schema
- add a new resolved boolean field `skipSyncEmail` in current user




https://github.com/twentyhq/twenty/assets/29927851/de791475-5bfe-47f9-8e90-76c349fba56f
This commit is contained in:
martmull
2024-06-05 18:16:53 +02:00
committed by GitHub
parent fda0d2a170
commit 9f6a6c3282
92 changed files with 2707 additions and 1246 deletions

View File

@ -3,6 +3,7 @@ import { renderHook } from '@testing-library/react';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { CurrentUser, currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import {
CurrentWorkspace,
@ -20,6 +21,13 @@ const billing = {
billingUrl: 'testing.com',
isBillingEnabled: true,
};
const currentUser = {
id: '1',
email: 'test@test',
supportUserHash: '1',
canImpersonate: false,
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser;
const currentWorkspace = {
activationStatus: 'active',
id: '1',
@ -27,7 +35,7 @@ const currentWorkspace = {
currentBillingSubscription: {
status: 'trialing',
},
};
} as CurrentWorkspace;
const currentWorkspaceMember = {
id: '1',
locale: '',
@ -46,12 +54,14 @@ const renderHooks = () => {
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
const setCurrentUser = useSetRecoilState(currentUserState);
const setTokenPair = useSetRecoilState(tokenPairState);
const setVerifyPending = useSetRecoilState(isVerifyPendingState);
return {
onboardingStatus,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
setTokenPair,
@ -77,6 +87,7 @@ describe('useOnboardingStatus', () => {
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
@ -84,10 +95,11 @@ describe('useOnboardingStatus', () => {
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'incomplete',
} as CurrentWorkspace);
});
setCurrentWorkspaceMember(currentWorkspaceMember);
});
@ -99,6 +111,7 @@ describe('useOnboardingStatus', () => {
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
@ -106,10 +119,11 @@ describe('useOnboardingStatus', () => {
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'canceled',
} as CurrentWorkspace);
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
name: {
@ -124,16 +138,18 @@ describe('useOnboardingStatus', () => {
it('should return "ongoing_workspace_activation"', async () => {
const { result } = renderHooks();
const { setTokenPair, setBilling, setCurrentWorkspace } = result.current;
const { setTokenPair, setBilling, setCurrentUser, setCurrentWorkspace } =
result.current;
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({
...currentWorkspace,
activationStatus: 'inactive',
subscriptionStatus: 'active',
} as CurrentWorkspace);
});
});
expect(result.current.onboardingStatus).toBe(
@ -146,6 +162,7 @@ describe('useOnboardingStatus', () => {
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
@ -153,21 +170,56 @@ describe('useOnboardingStatus', () => {
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'active',
} as CurrentWorkspace);
});
setCurrentWorkspaceMember(currentWorkspaceMember);
});
expect(result.current.onboardingStatus).toBe('ongoing_profile_creation');
});
it('should return "ongoing_sync_email"', async () => {
const { result } = renderHooks();
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser({
...currentUser,
state: { skipSyncEmailOnboardingStep: false },
});
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'active',
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
name: {
firstName: 'John',
lastName: 'Doe',
},
});
});
expect(result.current.onboardingStatus).toBe('ongoing_sync_email');
});
it('should return "completed"', async () => {
const { result } = renderHooks();
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
@ -175,10 +227,11 @@ describe('useOnboardingStatus', () => {
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'active',
} as CurrentWorkspace);
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
name: {
@ -196,6 +249,7 @@ describe('useOnboardingStatus', () => {
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
@ -203,10 +257,11 @@ describe('useOnboardingStatus', () => {
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'past_due',
} as CurrentWorkspace);
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
name: {
@ -224,6 +279,7 @@ describe('useOnboardingStatus', () => {
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
@ -231,10 +287,11 @@ describe('useOnboardingStatus', () => {
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'unpaid',
} as CurrentWorkspace);
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
name: {
@ -252,6 +309,7 @@ describe('useOnboardingStatus', () => {
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
@ -259,6 +317,7 @@ describe('useOnboardingStatus', () => {
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser(currentUser);
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'trialing',

View File

@ -1,5 +1,6 @@
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { billingState } from '@/client-config/states/billingState';
@ -14,12 +15,14 @@ export const useOnboardingStatus = (): OnboardingStatus | undefined => {
const billing = useRecoilValue(billingState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const currentUser = useRecoilValue(currentUserState);
const isLoggedIn = useIsLogged();
return getOnboardingStatus({
isLoggedIn,
currentWorkspaceMember,
currentWorkspace,
currentUser,
isBillingEnabled: billing?.isBillingEnabled || false,
});
};

View File

@ -4,7 +4,7 @@ import { User } from '~/generated/graphql';
export type CurrentUser = Pick<
User,
'id' | 'email' | 'supportUserHash' | 'canImpersonate'
'id' | 'email' | 'supportUserHash' | 'canImpersonate' | 'state'
>;
export const currentUserState = createState<CurrentUser | null>({

View File

@ -1,3 +1,4 @@
import { CurrentUser } from '@/auth/states/currentUserState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
@ -9,6 +10,7 @@ describe('getOnboardingStatus', () => {
isLoggedIn: false,
currentWorkspaceMember: null,
currentWorkspace: null,
currentUser: null,
isBillingEnabled: false,
});
@ -19,6 +21,9 @@ describe('getOnboardingStatus', () => {
id: '1',
activationStatus: 'inactive',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: false,
});
@ -32,6 +37,28 @@ describe('getOnboardingStatus', () => {
id: '1',
activationStatus: 'active',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: false,
});
const ongoingSyncEmail = getOnboardingStatus({
isLoggedIn: true,
currentWorkspaceMember: {
id: '1',
name: {
firstName: 'John',
lastName: 'Doe',
},
} as WorkspaceMember,
currentWorkspace: {
id: '1',
activationStatus: 'active',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: false },
} as CurrentUser,
isBillingEnabled: false,
});
@ -48,6 +75,9 @@ describe('getOnboardingStatus', () => {
id: '1',
activationStatus: 'active',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: false,
});
@ -65,6 +95,9 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active',
subscriptionStatus: 'incomplete',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: true,
});
@ -82,6 +115,9 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active',
subscriptionStatus: 'incomplete',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: false,
});
@ -99,12 +135,16 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active',
subscriptionStatus: 'canceled',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
} as CurrentUser,
isBillingEnabled: true,
});
expect(ongoingUserCreation).toBe('ongoing_user_creation');
expect(ongoingWorkspaceActivation).toBe('ongoing_workspace_activation');
expect(ongoingProfileCreation).toBe('ongoing_profile_creation');
expect(ongoingSyncEmail).toBe('ongoing_sync_email');
expect(completed).toBe('completed');
expect(incomplete).toBe('incomplete');
expect(canceled).toBe('canceled');

View File

@ -1,3 +1,4 @@
import { CurrentUser } from '@/auth/states/currentUserState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
@ -9,6 +10,7 @@ export enum OnboardingStatus {
OngoingUserCreation = 'ongoing_user_creation',
OngoingWorkspaceActivation = 'ongoing_workspace_activation',
OngoingProfileCreation = 'ongoing_profile_creation',
OngoingSyncEmail = 'ongoing_sync_email',
Completed = 'completed',
CompletedWithoutSubscription = 'completed_without_subscription',
}
@ -17,6 +19,7 @@ export const getOnboardingStatus = ({
isLoggedIn,
currentWorkspaceMember,
currentWorkspace,
currentUser,
isBillingEnabled,
}: {
isLoggedIn: boolean;
@ -25,6 +28,7 @@ export const getOnboardingStatus = ({
'createdAt' | 'updatedAt' | 'userId' | 'userEmail' | '__typename'
> | null;
currentWorkspace: CurrentWorkspace | null;
currentUser: CurrentUser | null;
isBillingEnabled: boolean;
}) => {
if (!isLoggedIn) {
@ -33,7 +37,7 @@ export const getOnboardingStatus = ({
// After SignInUp, the user should have a current workspace assigned.
// If not, it indicates that the data is still being requested.
if (!currentWorkspace) {
if (!currentWorkspace || !currentUser) {
return undefined;
}
@ -55,6 +59,10 @@ export const getOnboardingStatus = ({
return OnboardingStatus.OngoingProfileCreation;
}
if (!currentUser.state.skipSyncEmailOnboardingStep) {
return OnboardingStatus.OngoingSyncEmail;
}
if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'canceled') {
return OnboardingStatus.Canceled;
}