poc - cal.com integration in onboarding flow (#12530)

This commit is contained in:
nitin
2025-06-19 15:27:38 +05:30
committed by GitHub
parent e4d44e9c39
commit a8fb039e65
36 changed files with 526 additions and 34 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

View File

@ -318,6 +318,7 @@ export type ClientConfig = {
api: ApiConfig;
authProviders: AuthProviders;
billing: Billing;
calendarBookingPageId?: Maybe<Scalars['String']>;
canManageFeatureFlags: Scalars['Boolean'];
captcha: Captcha;
chromeExtensionId?: Maybe<Scalars['String']>;
@ -948,6 +949,7 @@ export type Mutation = {
signUp: AvailableWorkspacesAndAccessTokensOutput;
signUpInNewWorkspace: SignUpOutput;
signUpInWorkspace: SignUpOutput;
skipBookOnboardingStep: OnboardingStepSuccess;
skipSyncEmailOnboardingStep: OnboardingStepSuccess;
submitFormStep: Scalars['Boolean'];
switchToEnterprisePlan: BillingUpdateOutput;
@ -1477,6 +1479,7 @@ export type OnDbEventInput = {
/** Onboarding status */
export enum OnboardingStatus {
BOOK_ONBOARDING = 'BOOK_ONBOARDING',
COMPLETED = 'COMPLETED',
INVITE_TEAM = 'INVITE_TEAM',
PLAN_REQUIRED = 'PLAN_REQUIRED',
@ -2747,7 +2750,7 @@ export type GetMeteredProductsUsageQuery = { __typename?: 'Query', getMeteredPro
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, isConfigVariablesInDbEnabled: boolean, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: SupportDriver, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } };
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, isConfigVariablesInDbEnabled: boolean, calendarBookingPageId?: string | null, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: SupportDriver, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } };
export type SearchQueryVariables = Exact<{
searchInput: Scalars['String'];
@ -2761,6 +2764,11 @@ export type SearchQueryVariables = Exact<{
export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: string, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } };
export type SkipBookOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;
export type SkipBookOnboardingStepMutation = { __typename?: 'Mutation', skipBookOnboardingStep: { __typename?: 'OnboardingStepSuccess', success: boolean } };
export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;
@ -4758,6 +4766,7 @@ export const GetClientConfigDocument = gql`
isGoogleMessagingEnabled
isGoogleCalendarEnabled
isConfigVariablesInDbEnabled
calendarBookingPageId
}
}
`;
@ -4849,6 +4858,38 @@ export function useSearchLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<Sea
export type SearchQueryHookResult = ReturnType<typeof useSearchQuery>;
export type SearchLazyQueryHookResult = ReturnType<typeof useSearchLazyQuery>;
export type SearchQueryResult = Apollo.QueryResult<SearchQuery, SearchQueryVariables>;
export const SkipBookOnboardingStepDocument = gql`
mutation SkipBookOnboardingStep {
skipBookOnboardingStep {
success
}
}
`;
export type SkipBookOnboardingStepMutationFn = Apollo.MutationFunction<SkipBookOnboardingStepMutation, SkipBookOnboardingStepMutationVariables>;
/**
* __useSkipBookOnboardingStepMutation__
*
* To run a mutation, you first call `useSkipBookOnboardingStepMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSkipBookOnboardingStepMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [skipBookOnboardingStepMutation, { data, loading, error }] = useSkipBookOnboardingStepMutation({
* variables: {
* },
* });
*/
export function useSkipBookOnboardingStepMutation(baseOptions?: Apollo.MutationHookOptions<SkipBookOnboardingStepMutation, SkipBookOnboardingStepMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SkipBookOnboardingStepMutation, SkipBookOnboardingStepMutationVariables>(SkipBookOnboardingStepDocument, options);
}
export type SkipBookOnboardingStepMutationHookResult = ReturnType<typeof useSkipBookOnboardingStepMutation>;
export type SkipBookOnboardingStepMutationResult = Apollo.MutationResult<SkipBookOnboardingStepMutation>;
export type SkipBookOnboardingStepMutationOptions = Apollo.BaseMutationOptions<SkipBookOnboardingStepMutation, SkipBookOnboardingStepMutationVariables>;
export const SkipSyncEmailOnboardingStepDocument = gql`
mutation SkipSyncEmailOnboardingStep {
skipSyncEmailOnboardingStep {

View File

@ -9,10 +9,10 @@ import { useRecoilValue } from 'recoil';
import { OnboardingStatus } from '~/generated/graphql';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
import { UNTESTED_APP_PATHS } from '~/testing/constants/UntestedAppPaths';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
jest.mock('@/onboarding/hooks/useOnboardingStatus');
const setupMockOnboardingStatus = (
@ -92,6 +92,7 @@ const testCases: {
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -101,6 +102,7 @@ const testCases: {
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
@ -110,6 +112,7 @@ const testCases: {
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' },
@ -119,6 +122,7 @@ const testCases: {
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: '/sync/emails' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: '/invite-team' },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -130,6 +134,7 @@ const testCases: {
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -139,6 +144,7 @@ const testCases: {
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -148,6 +154,7 @@ const testCases: {
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: undefined },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -157,6 +164,7 @@ const testCases: {
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: undefined },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -166,8 +174,29 @@ const testCases: {
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: undefined },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' },
{ loc: AppPath.BookCallDecision, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' },
{ loc: AppPath.BookCall, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: undefined },
{ loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' },
{ loc: AppPath.PlanRequired, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp },
@ -175,6 +204,7 @@ const testCases: {
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined },
@ -184,6 +214,7 @@ const testCases: {
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -193,6 +224,7 @@ const testCases: {
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -202,6 +234,7 @@ const testCases: {
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -211,6 +244,7 @@ const testCases: {
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -220,6 +254,7 @@ const testCases: {
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined, objectNamePluralFromParams: 'existing-object', objectNamePluralFromMetadata: 'existing-object' },
{ loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, objectNamePluralFromParams: 'non-existing-object', objectNamePluralFromMetadata: 'existing-object' },
@ -231,6 +266,7 @@ const testCases: {
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -240,6 +276,7 @@ const testCases: {
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -249,6 +286,7 @@ const testCases: {
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -258,6 +296,7 @@ const testCases: {
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -267,6 +306,7 @@ const testCases: {
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
@ -276,6 +316,7 @@ const testCases: {
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.SYNC_EMAIL, res: AppPath.SyncEmails },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.INVITE_TEAM, res: AppPath.InviteTeam },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.BOOK_ONBOARDING, res: AppPath.BookCallDecision },
{ loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
];

View File

@ -39,6 +39,8 @@ export const usePageChangeEffectNavigateLocation = () => {
AppPath.InviteTeam,
AppPath.PlanRequired,
AppPath.PlanRequiredSuccess,
AppPath.BookCallDecision,
AppPath.BookCall,
];
const objectNamePlural = useParams().objectNamePlural ?? '';
@ -60,7 +62,12 @@ export const usePageChangeEffectNavigateLocation = () => {
if (
onboardingStatus === OnboardingStatus.PLAN_REQUIRED &&
!someMatchingLocationOf([AppPath.PlanRequired, AppPath.PlanRequiredSuccess])
!someMatchingLocationOf([
AppPath.PlanRequired,
AppPath.PlanRequiredSuccess,
AppPath.BookCall,
AppPath.BookCallDecision,
])
) {
if (
isMatchingLocation(location, AppPath.VerifyEmail) &&
@ -86,6 +93,8 @@ export const usePageChangeEffectNavigateLocation = () => {
!someMatchingLocationOf([
AppPath.CreateWorkspace,
AppPath.PlanRequiredSuccess,
AppPath.BookCallDecision,
AppPath.BookCall,
])
) {
return AppPath.CreateWorkspace;
@ -112,6 +121,13 @@ export const usePageChangeEffectNavigateLocation = () => {
return AppPath.InviteTeam;
}
if (
onboardingStatus === OnboardingStatus.BOOK_ONBOARDING &&
!someMatchingLocationOf([AppPath.BookCallDecision, AppPath.BookCall])
) {
return AppPath.BookCallDecision;
}
if (
onboardingStatus === OnboardingStatus.COMPLETED &&
someMatchingLocationOf([...onboardingPaths, ...onGoingUserCreationPaths]) &&

View File

@ -19,6 +19,8 @@ import { SignInUp } from '~/pages/auth/SignInUp';
import { NotFound } from '~/pages/not-found/NotFound';
import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
import { BookCall } from '~/pages/onboarding/BookCall';
import { BookCallDecision } from '~/pages/onboarding/BookCallDecision';
import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan';
import { CreateProfile } from '~/pages/onboarding/CreateProfile';
import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace';
@ -53,6 +55,11 @@ export const useCreateAppRouter = (
path={AppPath.PlanRequiredSuccess}
element={<PaymentSuccess />}
/>
<Route
path={AppPath.BookCallDecision}
element={<BookCallDecision />}
/>
<Route path={AppPath.BookCall} element={<BookCall />} />
<Route path={indexAppPath.getIndexAppPath()} element={<></>} />
<Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} />
<Route path={AppPath.RecordShowPage} element={<RecordShowPage />} />

View File

@ -1,9 +1,11 @@
import { AuthModalMountEffect } from '@/auth/components/AuthModalMountEffect';
import { AUTH_MODAL_ID } from '@/auth/constants/AuthModalId';
import { getAuthModalConfig } from '@/auth/utils/getAuthModalConfig';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import styled from '@emotion/styled';
import React from 'react';
import { useLocation } from 'react-router-dom';
const StyledContent = styled.div`
align-items: center;
@ -14,13 +16,27 @@ type AuthModalProps = {
children: React.ReactNode;
};
export const AuthModal = ({ children }: AuthModalProps) => (
<>
<AuthModalMountEffect />
<Modal modalId={AUTH_MODAL_ID} padding={'none'} modalVariant="primary">
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
<StyledContent>{children}</StyledContent>
</ScrollWrapper>
</Modal>
</>
);
export const AuthModal = ({ children }: AuthModalProps) => {
const location = useLocation();
const config = getAuthModalConfig(location);
return (
<>
<AuthModalMountEffect />
<Modal
modalId={AUTH_MODAL_ID}
padding={'none'}
size={config.size}
modalVariant={config.variant}
>
{config.showScrollWrapper ? (
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
<StyledContent>{children}</StyledContent>
</ScrollWrapper>
) : (
<>{children}</>
)}
</Modal>
</>
);
};

View File

@ -0,0 +1,24 @@
import { AppPath } from '@/types/AppPath';
import { ModalSize, ModalVariants } from '@/ui/layout/modal/components/Modal';
type AuthModalConfigType = {
size: ModalSize;
variant: ModalVariants;
showScrollWrapper: boolean;
};
export const AUTH_MODAL_CONFIG: {
default: AuthModalConfigType;
[key: string]: AuthModalConfigType;
} = {
default: {
size: 'medium',
variant: 'primary',
showScrollWrapper: true,
},
[AppPath.BookCall]: {
size: 'extraLarge',
variant: 'transparent',
showScrollWrapper: false,
},
};

View File

@ -0,0 +1,18 @@
import { AUTH_MODAL_CONFIG } from '@/auth/constants/AuthModalConfig';
import { AppPath } from '@/types/AppPath';
import { Location } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
export const getAuthModalConfig = (location: Location) => {
for (const path of Object.values(AppPath)) {
if (
isMatchingLocation(location, path) &&
isDefined(AUTH_MODAL_CONFIG[path])
) {
return AUTH_MODAL_CONFIG[path];
}
}
return AUTH_MODAL_CONFIG.default;
};

View File

@ -2,6 +2,7 @@ import { useClientConfig } from '@/client-config/hooks/useClientConfig';
import { apiConfigState } from '@/client-config/states/apiConfigState';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { captchaState } from '@/client-config/states/captchaState';
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
@ -85,6 +86,10 @@ export const ClientConfigProviderEffect = () => {
isConfigVariablesInDbEnabledState,
);
const setCalendarBookingPageId = useSetRecoilState(
calendarBookingPageIdState,
);
const { data, loading, error, fetchClientConfig } = useClientConfig();
useEffect(() => {
@ -173,6 +178,8 @@ export const ClientConfigProviderEffect = () => {
...currentStatus,
isSaved: true,
}));
setCalendarBookingPageId(data?.clientConfig?.calendarBookingPageId ?? null);
}, [
data,
loading,
@ -198,6 +205,7 @@ export const ClientConfigProviderEffect = () => {
setGoogleCalendarEnabled,
setIsAttachmentPreviewEnabled,
setIsConfigVariablesInDbEnabled,
setCalendarBookingPageId,
]);
return <></>;

View File

@ -62,6 +62,7 @@ export const GET_CLIENT_CONFIG = gql`
isGoogleMessagingEnabled
isGoogleCalendarEnabled
isConfigVariablesInDbEnabled
calendarBookingPageId
}
}
`;

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui/utilities';
export const calendarBookingPageIdState = createState<string | null>({
key: 'calendarBookingPageIdState',
defaultValue: null,
});

View File

@ -0,0 +1 @@
export const BOOK_CALL_MODAL_ID = 'book-call-modal';

View File

@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const SKIP_BOOK_ONBOARDING_STEP = gql`
mutation SkipBookOnboardingStep {
skipBookOnboardingStep {
success
}
}
`;

View File

@ -5,12 +5,14 @@ import {
CurrentWorkspace,
currentWorkspaceState,
} from '@/auth/states/currentWorkspaceState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { isDefined } from 'twenty-shared/utils';
import { OnboardingStatus } from '~/generated/graphql';
const getNextOnboardingStatus = (
currentUser: CurrentUser | null,
currentWorkspace: CurrentWorkspace | null,
calendarBookingPageId: string | null,
) => {
if (currentUser?.onboardingStatus === OnboardingStatus.WORKSPACE_ACTIVATION) {
return OnboardingStatus.PROFILE_CREATION;
@ -25,12 +27,21 @@ const getNextOnboardingStatus = (
) {
return OnboardingStatus.INVITE_TEAM;
}
if (currentUser?.onboardingStatus === OnboardingStatus.INVITE_TEAM) {
return isDefined(calendarBookingPageId)
? OnboardingStatus.BOOK_ONBOARDING
: OnboardingStatus.COMPLETED;
}
if (currentUser?.onboardingStatus === OnboardingStatus.BOOK_ONBOARDING) {
return OnboardingStatus.COMPLETED;
}
return OnboardingStatus.COMPLETED;
};
export const useSetNextOnboardingStatus = () => {
const currentUser = useRecoilValue(currentUserState);
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState);
return useRecoilCallback(
({ set }) =>
@ -38,6 +49,7 @@ export const useSetNextOnboardingStatus = () => {
const nextOnboardingStatus = getNextOnboardingStatus(
currentUser,
currentWorkspace,
calendarBookingPageId,
);
set(currentUserState, (current) => {
if (isDefined(current)) {
@ -49,6 +61,6 @@ export const useSetNextOnboardingStatus = () => {
return current;
});
},
[currentWorkspace, currentUser],
[currentWorkspace, currentUser, calendarBookingPageId],
);
};

View File

@ -13,6 +13,8 @@ export enum AppPath {
InviteTeam = '/invite-team',
PlanRequired = '/plan-required',
PlanRequiredSuccess = '/plan-required/payment-success',
BookCallDecision = '/book-call-decision',
BookCall = '/book-call',
// Onboarded
Index = '/',

View File

@ -43,6 +43,8 @@ const testCases = [
{ loc: AppPath.InviteTeam, res: true },
{ loc: AppPath.PlanRequired, res: true },
{ loc: AppPath.PlanRequiredSuccess, res: true },
{ loc: AppPath.BookCallDecision, res: true },
{ loc: AppPath.BookCall, res: true },
{ loc: AppPath.Index, res: false },
{ loc: AppPath.RecordIndexPage, res: false },

View File

@ -19,7 +19,9 @@ export const useShowAuthModal = () => {
isMatchingLocation(location, AppPath.SignInUp) ||
isMatchingLocation(location, AppPath.CreateWorkspace) ||
isMatchingLocation(location, AppPath.PlanRequired) ||
isMatchingLocation(location, AppPath.PlanRequiredSuccess)
isMatchingLocation(location, AppPath.PlanRequiredSuccess) ||
isMatchingLocation(location, AppPath.BookCallDecision) ||
isMatchingLocation(location, AppPath.BookCall)
) {
return true;
}

View File

@ -25,11 +25,14 @@ const StyledModalDiv = styled(motion.div)<{
box-shadow: ${({ theme, modalVariant }) =>
modalVariant === 'primary'
? theme.boxShadow.superHeavy
: theme.boxShadow.strong};
background: ${({ theme }) => theme.background.primary};
: modalVariant === 'transparent'
? 'none'
: theme.boxShadow.strong};
background: ${({ theme, modalVariant }) =>
modalVariant === 'transparent' ? 'transparent' : theme.background.primary};
color: ${({ theme }) => theme.font.color.primary};
border-radius: ${({ theme, isMobile }) => {
if (isMobile) return `0`;
border-radius: ${({ theme, isMobile, modalVariant }) => {
if (isMobile || modalVariant === 'transparent') return `0`;
return theme.border.radius.md;
}};
overflow-x: hidden;
@ -123,7 +126,7 @@ const StyledBackDrop = styled(motion.div)<{
}>`
align-items: center;
background: ${({ theme, modalVariant }) =>
modalVariant === 'primary'
modalVariant === 'primary' || modalVariant === 'transparent'
? theme.background.overlayPrimary
: modalVariant === 'secondary'
? theme.background.overlaySecondary
@ -177,7 +180,11 @@ const ModalFooter = ({ children, className }: ModalFooterProps) => (
export type ModalSize = 'small' | 'medium' | 'large' | 'extraLarge';
export type ModalPadding = 'none' | 'small' | 'medium' | 'large';
export type ModalVariants = 'primary' | 'secondary' | 'tertiary';
export type ModalVariants =
| 'primary'
| 'secondary'
| 'tertiary'
| 'transparent';
export type ModalProps = React.PropsWithChildren & {
modalId: string;

View File

@ -0,0 +1,86 @@
import Cal from '@calcom/embed-react';
import styled from '@emotion/styled';
import { Link } from 'react-router-dom';
import { currentUserState } from '@/auth/states/currentUserState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { AppPath } from '@/types/AppPath';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { IconChevronLeft, IconChevronRightPipe } from 'twenty-ui/display';
import { LightButton } from 'twenty-ui/input';
import { useIsMobile } from 'twenty-ui/utilities';
import {
OnboardingStatus,
useSkipBookOnboardingStepMutation,
} from '~/generated/graphql';
const StyledModalFooter = styled(Modal.Footer)`
height: auto;
justify-content: center;
padding: ${({ theme }) => theme.spacing(3)};
`;
const StyledModalContent = styled(Modal.Content)`
overflow: hidden;
padding: 0;
`;
const StyledScrollWrapper = styled(ScrollWrapper)<{ isMobile: boolean }>`
${({ isMobile }) => !isMobile && 'height: auto;'}
`;
export const BookCall = () => {
const { t } = useLingui();
const theme = useTheme();
const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState);
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const currentUser = useRecoilValue(currentUserState);
const [skipBookOnboardingStepMutation] = useSkipBookOnboardingStepMutation();
const isMobile = useIsMobile();
const isPlanRequired =
currentUser?.onboardingStatus === OnboardingStatus.PLAN_REQUIRED;
const handleCompleteOnboarding = async () => {
await skipBookOnboardingStepMutation();
setNextOnboardingStatus();
};
return (
<>
<StyledModalContent isHorizontalCentered isVerticalCentered>
<StyledScrollWrapper
componentInstanceId="scroll-wrapper-modal-content"
isMobile={isMobile}
>
<Cal
calLink={calendarBookingPageId ?? ''}
config={{
layout: 'month_view',
theme: theme.name === 'light' ? 'light' : 'dark',
email: currentUser?.email ?? '',
}}
/>
</StyledScrollWrapper>
</StyledModalContent>
<StyledModalFooter>
{isPlanRequired ? (
<Link to={AppPath.PlanRequired}>
<LightButton Icon={IconChevronLeft} title={t`Back`} />
</Link>
) : (
<LightButton
Icon={IconChevronRightPipe}
title={t`Skip`}
onClick={handleCompleteOnboarding}
/>
)}
</StyledModalFooter>
</>
);
};

View File

@ -0,0 +1,74 @@
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { AppPath } from '@/types/AppPath';
import { Modal } from '@/ui/layout/modal/components/Modal';
import styled from '@emotion/styled';
import { Trans, useLingui } from '@lingui/react/macro';
import { Link } from 'react-router-dom';
import { LightButton, MainButton } from 'twenty-ui/input';
import { useSkipBookOnboardingStepMutation } from '~/generated/graphql';
const StyledCoverImage = styled.img`
border-radius: ${({ theme }) => theme.border.radius.sm};
height: 204px;
object-fit: cover;
width: 320px;
`;
const StyledModalContent = styled(Modal.Content)`
gap: ${({ theme }) => theme.spacing(8)};
`;
const StyledTitleContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
`;
const StyledButtonContainer = styled.div`
align-items: center;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
width: 100%;
`;
const StyledLink = styled(Link)`
text-decoration: none;
`;
export const BookCallDecision = () => {
const { t } = useLingui();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const [skipBookOnboardingStepMutation] = useSkipBookOnboardingStepMutation();
const handleFinish = async () => {
await skipBookOnboardingStepMutation();
setNextOnboardingStatus();
};
return (
<StyledModalContent isVerticalCentered isHorizontalCentered>
<StyledTitleContainer>
<Title noMarginTop>
<Trans>Book your onboarding</Trans>
</Title>
<SubTitle>
<Trans>
Our team can help you set up your workspace to match your specific
needs and workflows.
</Trans>
</SubTitle>
</StyledTitleContainer>
<StyledCoverImage src="/images/placeholders/onboarding-covers/onboarding-book-call-decision-cover.png" />
<StyledButtonContainer>
<StyledLink to={AppPath.BookCall}>
<MainButton title={t`Book onboarding`} width={198} />
</StyledLink>
<LightButton title={t`Finish`} onClick={handleFinish} />
</StyledButtonContainer>
</StyledModalContent>
);
};

View File

@ -9,6 +9,8 @@ import { TrialCard } from '@/billing/components/TrialCard';
import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSession';
import { isBillingPriceLicensed } from '@/billing/utils/isBillingPriceLicensed';
import { billingState } from '@/client-config/states/billingState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { AppPath } from '@/types/AppPath';
import { Modal } from '@/ui/layout/modal/components/Modal';
import styled from '@emotion/styled';
import { Trans, useLingui } from '@lingui/react/macro';
@ -97,6 +99,8 @@ export const ChooseYourPlan = () => {
billingCheckoutSessionState,
);
const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState);
const [verifyEmailNextPath, setVerifyEmailNextPath] = useRecoilState(
verifyEmailNextPathState,
);
@ -250,7 +254,11 @@ export const ChooseYourPlan = () => {
<Trans>Change Plan</Trans>
</ClickToActionLink>
<span />
<ClickToActionLink href={CAL_LINK} target="_blank" rel="noreferrer">
<ClickToActionLink
href={calendarBookingPageId ? AppPath.BookCall : CAL_LINK}
target={calendarBookingPageId ? '_self' : '_blank'}
rel={calendarBookingPageId ? '' : 'noreferrer'}
>
<Trans>Book a Call</Trans>
</ClickToActionLink>
</StyledLinkGroup>

View File

@ -1,6 +1,7 @@
import { SubTitle } from '@/auth/components/SubTitle';
import { Title } from '@/auth/components/Title';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
@ -66,9 +67,11 @@ export const InviteTeam = () => {
const theme = useTheme();
const { enqueueSnackBar } = useSnackBar();
const { sendInvitation } = useCreateWorkspaceInvitation();
const setNextOnboardingStatus = useSetNextOnboardingStatus();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState);
const hasCalendarBooking = isDefined(calendarBookingPageId);
const {
control,
handleSubmit,
@ -136,8 +139,6 @@ export const InviteTeam = () => {
);
const result = await sendInvitation({ emails });
setNextOnboardingStatus();
if (isDefined(result.errors)) {
throw result.errors;
}
@ -147,6 +148,8 @@ export const InviteTeam = () => {
duration: 2000,
});
}
setNextOnboardingStatus();
},
[enqueueSnackBar, sendInvitation, setNextOnboardingStatus, t],
);
@ -214,7 +217,7 @@ export const InviteTeam = () => {
</StyledAnimatedContainer>
<StyledButtonContainer>
<MainButton
title={t`Continue`}
title={hasCalendarBooking ? t`Continue` : t`Finish`}
disabled={!isValid || isSubmitting}
onClick={handleSubmit(onSubmit)}
fullWidth

View File

@ -83,6 +83,7 @@ describe('ClientConfigController', () => {
isGoogleMessagingEnabled: false,
isGoogleCalendarEnabled: false,
isConfigVariablesInDbEnabled: false,
calendarBookingPageId: undefined,
};
jest

View File

@ -147,4 +147,7 @@ export class ClientConfig {
@Field(() => Boolean)
isConfigVariablesInDbEnabled: boolean;
@Field(() => String, { nullable: true })
calendarBookingPageId?: string;
}

View File

@ -78,6 +78,7 @@ describe('ClientConfigService', () => {
MESSAGING_PROVIDER_GMAIL_ENABLED: true,
CALENDAR_PROVIDER_GOOGLE_ENABLED: true,
IS_CONFIG_VARIABLES_IN_DB_ENABLED: false,
CALENDAR_BOOKING_PAGE_ID: 'team/twenty/talk-to-us',
};
return mockValues[key];
@ -145,6 +146,7 @@ describe('ClientConfigService', () => {
isGoogleMessagingEnabled: true,
isGoogleCalendarEnabled: true,
isConfigVariablesInDbEnabled: false,
calendarBookingPageId: 'team/twenty/talk-to-us',
});
});

View File

@ -102,6 +102,9 @@ export class ClientConfigService {
isConfigVariablesInDbEnabled: this.twentyConfigService.get(
'IS_CONFIG_VARIABLES_IN_DB_ENABLED',
),
calendarBookingPageId: this.twentyConfigService.get(
'CALENDAR_BOOKING_PAGE_ID',
),
};
return clientConfig;

View File

@ -4,5 +4,6 @@ export enum OnboardingStatus {
PROFILE_CREATION = 'PROFILE_CREATION',
SYNC_EMAIL = 'SYNC_EMAIL',
INVITE_TEAM = 'INVITE_TEAM',
BOOK_ONBOARDING = 'BOOK_ONBOARDING',
COMPLETED = 'COMPLETED',
}

View File

@ -28,4 +28,16 @@ export class OnboardingResolver {
return { success: true };
}
@Mutation(() => OnboardingStepSuccess)
async skipBookOnboardingStep(
@AuthWorkspace() workspace: Workspace,
): Promise<OnboardingStepSuccess> {
await this.onboardingService.setOnboardingBookOnboardingPending({
workspaceId: workspace.id,
value: false,
});
return { success: true };
}
}

View File

@ -12,12 +12,14 @@ 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',
}
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;
};
@Injectable()
@ -62,6 +64,10 @@ export class OnboardingService {
const isInviteTeamPending =
userVars.get(OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING) === true;
const isBookOnboardingPending =
userVars.get(OnboardingStepKeys.ONBOARDING_BOOK_ONBOARDING_PENDING) ===
true;
if (isProfileCreationPending) {
return OnboardingStatus.PROFILE_CREATION;
}
@ -74,6 +80,10 @@ export class OnboardingService {
return OnboardingStatus.INVITE_TEAM;
}
if (isBookOnboardingPending) {
return OnboardingStatus.BOOK_ONBOARDING;
}
return OnboardingStatus.COMPLETED;
}
@ -153,4 +163,27 @@ export class OnboardingService {
value: true,
});
}
async setOnboardingBookOnboardingPending({
workspaceId,
value,
}: {
workspaceId: string;
value: boolean;
}) {
if (!value) {
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,
});
}
}

View File

@ -630,6 +630,14 @@ export class ConfigVariables {
@IsOptional()
CHROME_EXTENSION_ID: string;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.Other,
description: 'Page ID for Cal.com booking integration',
type: ConfigVariableType.STRING,
})
@IsOptional()
CALENDAR_BOOKING_PAGE_ID?: string;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.Logging,
description: 'Enable or disable buffering for logs before sending',

View File

@ -70,6 +70,7 @@ describe('WorkspaceInvitationService', () => {
provide: OnboardingService,
useValue: {
setOnboardingInviteTeamPending: jest.fn(),
setOnboardingBookOnboardingPending: jest.fn(),
},
},
{
@ -181,6 +182,12 @@ describe('WorkspaceInvitationService', () => {
workspaceId: workspace.id,
value: false,
});
expect(
onboardingService.setOnboardingBookOnboardingPending,
).toHaveBeenCalledWith({
workspaceId: workspace.id,
value: true,
});
});
});
});

View File

@ -324,6 +324,11 @@ export class WorkspaceInvitationService {
value: false,
});
await this.onboardingService.setOnboardingBookOnboardingPending({
workspaceId: workspace.id,
value: true,
});
const result = invitationsPr.reduce<{
errors: string[];
result: ReturnType<

View File

@ -4,18 +4,18 @@ export {
IconAlertCircle,
IconAlertTriangle,
IconApi,
IconAppWindow,
IconApps,
IconAppWindow,
IconArchive,
IconArchiveOff,
IconArrowBackUp,
IconArrowDown,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpRight,
IconArrowsDiagonal,
IconArrowsVertical,
IconArrowUp,
IconArrowUpRight,
IconAt,
IconBaselineDensitySmall,
IconBell,
@ -47,8 +47,9 @@ export {
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconChevronUp,
IconChevronRightPipe,
IconChevronsRight,
IconChevronUp,
IconCircleDot,
IconCircleOff,
IconCirclePlus,

View File

@ -66,18 +66,18 @@ export {
IconAlertCircle,
IconAlertTriangle,
IconApi,
IconAppWindow,
IconApps,
IconAppWindow,
IconArchive,
IconArchiveOff,
IconArrowBackUp,
IconArrowDown,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpRight,
IconArrowsDiagonal,
IconArrowsVertical,
IconArrowUp,
IconArrowUpRight,
IconAt,
IconBaselineDensitySmall,
IconBell,
@ -109,8 +109,9 @@ export {
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconChevronUp,
IconChevronRightPipe,
IconChevronsRight,
IconChevronUp,
IconCircleDot,
IconCircleOff,
IconCirclePlus,