Remove book a call step from onboarding (#22597)

The book a call screen was shown as a dedicated onboarding step after
sending team invites. It is no longer part of the flow: the
`BOOK_ONBOARDING` status, its pending user var, the
`skipBookOnboardingStep` mutation and the `BookCallDecision` screen are
removed, and onboarding completes right after the plan step.

The `/book-call` Cal.com page remains, reachable only from the "Book a
Call" link on the upgrade screen, with a back link to `/plan-required`.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22597?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-07-07 10:34:20 +02:00
committed by GitHub
parent ee28ae363f
commit d3b79320b1
21 changed files with 10 additions and 348 deletions
@@ -5,6 +5,5 @@ export enum OnboardingStatus {
SYNC_EMAIL = 'SYNC_EMAIL',
APPS_INSTALLATION = 'APPS_INSTALLATION',
INVITE_TEAM = 'INVITE_TEAM',
BOOK_ONBOARDING = 'BOOK_ONBOARDING',
COMPLETED = 'COMPLETED',
}
@@ -56,19 +56,6 @@ export class OnboardingResolver {
return { success: true };
}
@Mutation(() => OnboardingStepSuccessDTO)
@UseGuards(NoPermissionGuard)
async skipBookOnboardingStep(
@AuthWorkspace() workspace: WorkspaceEntity,
): Promise<OnboardingStepSuccessDTO> {
await this.onboardingService.setOnboardingBookOnboardingPending({
workspaceId: workspace.id,
value: false,
});
return { success: true };
}
@Mutation(() => OnboardingStepSuccessDTO)
@UseGuards(NoPermissionGuard)
async triggerInstallAppsOnboardingStep(
@@ -1,7 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { type QueryRunner, Repository } from 'typeorm';
@@ -26,7 +25,6 @@ export enum OnboardingStepKeys {
ONBOARDING_CONNECT_ACCOUNT_PENDING = 'ONBOARDING_CONNECT_ACCOUNT_PENDING',
ONBOARDING_INVITE_TEAM_PENDING = 'ONBOARDING_INVITE_TEAM_PENDING',
ONBOARDING_CREATE_PROFILE_PENDING = 'ONBOARDING_CREATE_PROFILE_PENDING',
ONBOARDING_BOOK_ONBOARDING_PENDING = 'ONBOARDING_BOOK_ONBOARDING_PENDING',
ONBOARDING_INSTALL_APPS_PENDING = 'ONBOARDING_INSTALL_APPS_PENDING',
}
@@ -34,7 +32,6 @@ export type OnboardingKeyValueTypeMap = {
[OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING]: boolean;
[OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING]: boolean;
[OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING]: boolean;
[OnboardingStepKeys.ONBOARDING_BOOK_ONBOARDING_PENDING]: boolean;
[OnboardingStepKeys.ONBOARDING_INSTALL_APPS_PENDING]: boolean;
};
@@ -103,10 +100,6 @@ export class OnboardingService {
const isInviteTeamPending =
userVars.get(OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING) === true;
const isBookOnboardingPending =
userVars.get(OnboardingStepKeys.ONBOARDING_BOOK_ONBOARDING_PENDING) ===
true;
if (isConnectAccountPending) {
return OnboardingStatus.SYNC_EMAIL;
}
@@ -131,26 +124,6 @@ export class OnboardingService {
return OnboardingStatus.PLAN_REQUIRED;
}
if (isBookOnboardingPending) {
const calendarBookingPageId = this.twentyConfigService.get(
'CALENDAR_BOOKING_PAGE_ID',
);
const isBookingConfigured =
isDefined(calendarBookingPageId) &&
isNonEmptyString(calendarBookingPageId);
if (!isBookingConfigured) {
await this.userVarsService.delete({
workspaceId: workspace.id,
key: OnboardingStepKeys.ONBOARDING_BOOK_ONBOARDING_PENDING,
});
return OnboardingStatus.COMPLETED;
}
return OnboardingStatus.BOOK_ONBOARDING;
}
return OnboardingStatus.COMPLETED;
}
@@ -462,35 +435,4 @@ export class OnboardingService {
value: false,
});
}
async setOnboardingBookOnboardingPending({
workspaceId,
value,
}: {
workspaceId: string;
value: boolean;
}) {
const calendarBookingPageId = this.twentyConfigService.get(
'CALENDAR_BOOKING_PAGE_ID',
);
const isBookingConfigured =
isDefined(calendarBookingPageId) &&
isNonEmptyString(calendarBookingPageId);
if (!value || !isBookingConfigured) {
await this.userVarsService.delete({
workspaceId,
key: OnboardingStepKeys.ONBOARDING_BOOK_ONBOARDING_PENDING,
});
return;
}
await this.userVarsService.set({
workspaceId,
key: OnboardingStepKeys.ONBOARDING_BOOK_ONBOARDING_PENDING,
value: true,
});
}
}
@@ -91,7 +91,6 @@ describe('WorkspaceInvitationService', () => {
provide: OnboardingService,
useValue: {
setOnboardingInviteTeamPending: jest.fn(),
setOnboardingBookOnboardingPending: jest.fn(),
isOnboardingInviteTeamPending: jest.fn().mockResolvedValue(false),
},
},
@@ -227,12 +226,6 @@ describe('WorkspaceInvitationService', () => {
workspaceId: workspace.id,
value: false,
});
expect(
onboardingService.setOnboardingBookOnboardingPending,
).toHaveBeenCalledWith({
workspaceId: workspace.id,
value: true,
});
});
it('should mint reward-eligible tokens when the invite-team step is pending', async () => {
@@ -385,11 +385,6 @@ export class WorkspaceInvitationService {
value: false,
});
await this.onboardingService.setOnboardingBookOnboardingPending({
workspaceId: workspace.id,
value: true,
});
const i18n = this.i18nService.getI18nInstance(sender.locale);
const result = invitationResults.reduce<{