5623 add an inviteteam onboarding step (#5769)

## Changes
- add a new invite Team onboarding step
- update currentUser.state to currentUser.onboardingStep

## Edge cases
We will never display invite team onboarding step 
- if number of workspaceMember > 1
- if a workspaceMember as been deleted

## Important changes
Update typeorm package version to 0.3.20 because we needed a fix on
`indexPredicates` pushed in 0.3.20 version
(https://github.com/typeorm/typeorm/issues/10191)

## Result
<img width="844" alt="image"
src="https://github.com/twentyhq/twenty/assets/29927851/0dab54cf-7c66-4c64-b0c9-b0973889a148">



https://github.com/twentyhq/twenty/assets/29927851/13268d0a-cfa7-42a4-84c6-9e1fbbe48912
This commit is contained in:
martmull
2024-06-12 21:13:18 +02:00
committed by GitHub
parent 2fdd2f4949
commit 3986824017
60 changed files with 1009 additions and 372 deletions

View File

@ -5,30 +5,30 @@ import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEase
type TitleProps = React.PropsWithChildren & {
animate?: boolean;
withMarginTop?: boolean;
noMarginTop?: boolean;
};
const StyledTitle = styled.div<Pick<TitleProps, 'withMarginTop'>>`
const StyledTitle = styled.div<Pick<TitleProps, 'noMarginTop'>>`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: ${({ theme }) => theme.spacing(4)};
margin-top: ${({ theme, withMarginTop }) =>
withMarginTop ? theme.spacing(4) : 0};
margin-top: ${({ theme, noMarginTop }) =>
!noMarginTop ? theme.spacing(4) : 0};
`;
export const Title = ({
children,
animate = false,
withMarginTop = true,
noMarginTop = false,
}: TitleProps) => {
if (animate) {
return (
<StyledTitle withMarginTop={withMarginTop}>
<StyledTitle noMarginTop={noMarginTop}>
<AnimatedEaseIn>{children}</AnimatedEaseIn>
</StyledTitle>
);
}
return <StyledTitle withMarginTop={withMarginTop}>{children}</StyledTitle>;
return <StyledTitle noMarginTop={noMarginTop}>{children}</StyledTitle>;
};

View File

@ -12,6 +12,7 @@ import {
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { billingState } from '@/client-config/states/billingState';
import { OnboardingStep } from '~/generated/graphql';
const tokenPair = {
accessToken: { token: 'accessToken', expiresAt: 'expiresAt' },
@ -26,7 +27,7 @@ const currentUser = {
email: 'test@test',
supportUserHash: '1',
canImpersonate: false,
state: { skipSyncEmailOnboardingStep: true },
onboardingStep: null,
} as CurrentUser;
const currentWorkspace = {
activationStatus: 'active',
@ -196,7 +197,7 @@ describe('useOnboardingStatus', () => {
setBilling(billing);
setCurrentUser({
...currentUser,
state: { skipSyncEmailOnboardingStep: false },
onboardingStep: OnboardingStep.SyncEmail,
});
setCurrentWorkspace({
...currentWorkspace,
@ -214,6 +215,39 @@ describe('useOnboardingStatus', () => {
expect(result.current.onboardingStatus).toBe('ongoing_sync_email');
});
it('should return "ongoing_invite_team"', async () => {
const { result } = renderHooks();
const {
setTokenPair,
setBilling,
setCurrentUser,
setCurrentWorkspace,
setCurrentWorkspaceMember,
} = result.current;
act(() => {
setTokenPair(tokenPair);
setBilling(billing);
setCurrentUser({
...currentUser,
onboardingStep: OnboardingStep.InviteTeam,
});
setCurrentWorkspace({
...currentWorkspace,
subscriptionStatus: 'active',
});
setCurrentWorkspaceMember({
...currentWorkspaceMember,
name: {
firstName: 'John',
lastName: 'Doe',
},
});
});
expect(result.current.onboardingStatus).toBe('ongoing_invite_team');
});
it('should return "completed"', async () => {
const { result } = renderHooks();
const {

View File

@ -4,6 +4,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { IconGoogle, IconMicrosoft } from 'twenty-ui';
import { FooterNote } from '@/auth/sign-in-up/components/FooterNote';
@ -69,7 +70,7 @@ export const SignInUpForm = () => {
const handleKeyDown = async (
event: React.KeyboardEvent<HTMLInputElement>,
) => {
if (event.key === 'Enter') {
if (event.key === Key.Enter) {
event.preventDefault();
if (signInUpStep === SignInUpStep.Init) {

View File

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

View File

@ -1,6 +1,7 @@
import { CurrentUser } from '@/auth/states/currentUserState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { OnboardingStep } from '~/generated/graphql';
import { getOnboardingStatus } from '../getOnboardingStatus';
@ -22,7 +23,7 @@ describe('getOnboardingStatus', () => {
activationStatus: 'inactive',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
onboardingStep: null,
} as CurrentUser,
isBillingEnabled: false,
});
@ -38,7 +39,7 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
onboardingStep: null,
} as CurrentUser,
isBillingEnabled: false,
});
@ -57,7 +58,26 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: false },
onboardingStep: OnboardingStep.SyncEmail,
} as CurrentUser,
isBillingEnabled: false,
});
const ongoingInviteTeam = getOnboardingStatus({
isLoggedIn: true,
currentWorkspaceMember: {
id: '1',
name: {
firstName: 'John',
lastName: 'Doe',
},
} as WorkspaceMember,
currentWorkspace: {
id: '1',
activationStatus: 'active',
} as CurrentWorkspace,
currentUser: {
onboardingStep: OnboardingStep.InviteTeam,
} as CurrentUser,
isBillingEnabled: false,
});
@ -76,7 +96,7 @@ describe('getOnboardingStatus', () => {
activationStatus: 'active',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
onboardingStep: null,
} as CurrentUser,
isBillingEnabled: false,
});
@ -96,7 +116,7 @@ describe('getOnboardingStatus', () => {
subscriptionStatus: 'incomplete',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
onboardingStep: null,
} as CurrentUser,
isBillingEnabled: true,
});
@ -116,7 +136,7 @@ describe('getOnboardingStatus', () => {
subscriptionStatus: 'incomplete',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
onboardingStep: null,
} as CurrentUser,
isBillingEnabled: false,
});
@ -136,7 +156,7 @@ describe('getOnboardingStatus', () => {
subscriptionStatus: 'canceled',
} as CurrentWorkspace,
currentUser: {
state: { skipSyncEmailOnboardingStep: true },
onboardingStep: null,
} as CurrentUser,
isBillingEnabled: true,
});
@ -145,6 +165,7 @@ describe('getOnboardingStatus', () => {
expect(ongoingWorkspaceActivation).toBe('ongoing_workspace_activation');
expect(ongoingProfileCreation).toBe('ongoing_profile_creation');
expect(ongoingSyncEmail).toBe('ongoing_sync_email');
expect(ongoingInviteTeam).toBe('ongoing_invite_team');
expect(completed).toBe('completed');
expect(incomplete).toBe('incomplete');
expect(canceled).toBe('canceled');

View File

@ -1,6 +1,7 @@
import { CurrentUser } from '@/auth/states/currentUserState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { OnboardingStep } from '~/generated/graphql';
export enum OnboardingStatus {
Incomplete = 'incomplete',
@ -11,6 +12,7 @@ export enum OnboardingStatus {
OngoingWorkspaceActivation = 'ongoing_workspace_activation',
OngoingProfileCreation = 'ongoing_profile_creation',
OngoingSyncEmail = 'ongoing_sync_email',
OngoingInviteTeam = 'ongoing_invite_team',
Completed = 'completed',
CompletedWithoutSubscription = 'completed_without_subscription',
}
@ -59,10 +61,14 @@ export const getOnboardingStatus = ({
return OnboardingStatus.OngoingProfileCreation;
}
if (!currentUser.state.skipSyncEmailOnboardingStep) {
if (currentUser.onboardingStep === OnboardingStep.SyncEmail) {
return OnboardingStatus.OngoingSyncEmail;
}
if (currentUser.onboardingStep === OnboardingStep.InviteTeam) {
return OnboardingStatus.OngoingInviteTeam;
}
if (isBillingEnabled && currentWorkspace.subscriptionStatus === 'canceled') {
return OnboardingStatus.Canceled;
}