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:
@ -0,0 +1,9 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class OnboardingStepSuccess {
|
||||
@Field(() => Boolean, {
|
||||
description: 'Boolean that confirms query was dispatched',
|
||||
})
|
||||
success: boolean;
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
export enum OnboardingStep {
|
||||
SYNC_EMAIL = 'SYNC_EMAIL',
|
||||
INVITE_TEAM = 'INVITE_TEAM',
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { OnboardingResolver } from 'src/engine/core-modules/onboarding/onboarding.resolver';
|
||||
import { KeyValuePairModule } from 'src/engine/core-modules/key-value-pair/key-value-pair.module';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
|
||||
@Module({
|
||||
imports: [DataSourceModule, UserWorkspaceModule, KeyValuePairModule],
|
||||
exports: [OnboardingService],
|
||||
providers: [OnboardingService, OnboardingResolver],
|
||||
})
|
||||
export class OnboardingModule {}
|
||||
@ -0,0 +1,29 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { OnboardingStepSuccess } from 'src/engine/core-modules/onboarding/dtos/onboarding-step-success.dto';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver()
|
||||
export class OnboardingResolver {
|
||||
constructor(private readonly onboardingService: OnboardingService) {}
|
||||
|
||||
@Mutation(() => OnboardingStepSuccess)
|
||||
async skipSyncEmailOnboardingStep(
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<OnboardingStepSuccess> {
|
||||
await this.onboardingService.skipSyncEmailOnboardingStep(
|
||||
user.id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { KeyValuePairService } from 'src/engine/core-modules/key-value-pair/key-value-pair.service';
|
||||
import { OnboardingStep } from 'src/engine/core-modules/onboarding/enums/onboarding-step.enum';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||
|
||||
enum OnboardingStepValues {
|
||||
SKIPPED = 'SKIPPED',
|
||||
}
|
||||
|
||||
enum OnboardingStepKeys {
|
||||
SYNC_EMAIL_ONBOARDING_STEP = 'SYNC_EMAIL_ONBOARDING_STEP',
|
||||
INVITE_TEAM_ONBOARDING_STEP = 'INVITE_TEAM_ONBOARDING_STEP',
|
||||
}
|
||||
|
||||
type OnboardingKeyValueType = {
|
||||
[OnboardingStepKeys.SYNC_EMAIL_ONBOARDING_STEP]: OnboardingStepValues;
|
||||
[OnboardingStepKeys.INVITE_TEAM_ONBOARDING_STEP]: OnboardingStepValues;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class OnboardingService {
|
||||
constructor(
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
private readonly keyValuePairService: KeyValuePairService<OnboardingKeyValueType>,
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
) {}
|
||||
|
||||
private async isSyncEmailOnboardingStep(user: User, workspace: Workspace) {
|
||||
const syncEmailValue = await this.keyValuePairService.get({
|
||||
userId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
key: OnboardingStepKeys.SYNC_EMAIL_ONBOARDING_STEP,
|
||||
});
|
||||
const isSyncEmailSkipped = syncEmailValue === OnboardingStepValues.SKIPPED;
|
||||
const connectedAccounts =
|
||||
await this.connectedAccountRepository.getAllByUserId(
|
||||
user.id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return !isSyncEmailSkipped && !connectedAccounts?.length;
|
||||
}
|
||||
|
||||
private async isInviteTeamOnboardingStep(workspace: Workspace) {
|
||||
const inviteTeamValue = await this.keyValuePairService.get({
|
||||
workspaceId: workspace.id,
|
||||
key: OnboardingStepKeys.INVITE_TEAM_ONBOARDING_STEP,
|
||||
});
|
||||
const isInviteTeamSkipped =
|
||||
inviteTeamValue === OnboardingStepValues.SKIPPED;
|
||||
const workspaceMemberCount =
|
||||
await this.userWorkspaceService.getWorkspaceMemberCount(workspace.id);
|
||||
|
||||
return (
|
||||
!isInviteTeamSkipped &&
|
||||
(!workspaceMemberCount || workspaceMemberCount <= 1)
|
||||
);
|
||||
}
|
||||
|
||||
async getOnboardingStep(
|
||||
user: User,
|
||||
workspace: Workspace,
|
||||
): Promise<OnboardingStep | null> {
|
||||
if (await this.isSyncEmailOnboardingStep(user, workspace)) {
|
||||
return OnboardingStep.SYNC_EMAIL;
|
||||
}
|
||||
|
||||
if (await this.isInviteTeamOnboardingStep(workspace)) {
|
||||
return OnboardingStep.INVITE_TEAM;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async skipInviteTeamOnboardingStep(workspaceId: string) {
|
||||
await this.keyValuePairService.set({
|
||||
workspaceId,
|
||||
key: OnboardingStepKeys.INVITE_TEAM_ONBOARDING_STEP,
|
||||
value: OnboardingStepValues.SKIPPED,
|
||||
});
|
||||
}
|
||||
|
||||
async skipSyncEmailOnboardingStep(userId: string, workspaceId: string) {
|
||||
await this.keyValuePairService.set({
|
||||
userId,
|
||||
workspaceId,
|
||||
key: OnboardingStepKeys.SYNC_EMAIL_ONBOARDING_STEP,
|
||||
value: OnboardingStepValues.SKIPPED,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user