-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[Payment due @ChavdaSachin] Onboarding: drop cohort gate now that server generates per-tier welcome #89469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Payment due @ChavdaSachin] Onboarding: drop cohort gate now that server generates per-tier welcome #89469
Changes from all commits
8761658
e159a01
0fca290
7d6a407
41c1461
77fe2a0
7e8f3cc
2f08696
0ffa9db
4f17b1a
81ffd5b
2403e58
a25fb1a
86e23d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,7 @@ import type PolicyData from '@hooks/usePolicyData/types'; | |
| import type {PolicyTagList} from '@pages/workspace/tags/types'; | ||
| import type {ThemeColors} from '@styles/theme/types'; | ||
| import type {IOUAction, IOURequestType, IOUType, OnboardingAccounting} from '@src/CONST'; | ||
| import CONST, {TASK_TO_FEATURE} from '@src/CONST'; | ||
| import CONST from '@src/CONST'; | ||
| import type {ParentNavigationSummaryParams} from '@src/languages/params'; | ||
| import type {TranslationPaths} from '@src/languages/types'; | ||
| import NAVIGATORS from '@src/NAVIGATORS'; | ||
|
|
@@ -36,7 +36,6 @@ import SCREENS from '@src/SCREENS'; | |
| import type { | ||
| BankAccountList, | ||
| Beta, | ||
| BetaConfiguration, | ||
| BillingGraceEndPeriod, | ||
| IntroSelected, | ||
| OnyxInputOrEntry, | ||
|
|
@@ -1140,12 +1139,6 @@ Onyx.connect({ | |
| }, | ||
| }); | ||
|
|
||
| let betaConfiguration: OnyxEntry<BetaConfiguration> = {}; | ||
| Onyx.connect({ | ||
| key: ONYXKEYS.BETA_CONFIGURATION, | ||
| callback: (value) => (betaConfiguration = value ?? {}), | ||
| }); | ||
|
|
||
| let deprecatedAllTransactions: OnyxCollection<Transaction> = {}; | ||
| let deprecatedReportsTransactions: Record<string, Transaction[]> = {}; | ||
| Onyx.connect({ | ||
|
|
@@ -11584,55 +11577,14 @@ type PrepareOnboardingOnyxDataParams = { | |
| userReportedIntegration?: OnboardingAccounting; | ||
| wasInvited?: boolean; | ||
| companySize: OnboardingCompanySize | undefined; | ||
| selectedInterestedFeatures?: string[]; | ||
| isInvitedAccountant?: boolean; | ||
| onboardingPurposeSelected?: OnboardingPurpose; | ||
| betas: OnyxEntry<Beta[]>; | ||
| // TODO: isSelfTourViewed will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66424 | ||
| isSelfTourViewed?: boolean; | ||
| // TODO: hasCompletedGuidedSetupFlow will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66424 | ||
| hasCompletedGuidedSetupFlow?: boolean; | ||
| }; | ||
|
|
||
| function getBespokeWelcomeMessage(companySize: OnboardingCompanySize | undefined, userReportedIntegration?: OnboardingAccounting): string { | ||
| // Use markdown (not HTML) because buildOptimisticAddCommentReportAction -> getParsedComment | ||
| // escapes HTML entities before parsing, so raw HTML tags would render as literal text. | ||
| const welcomeHeader = "# Your free trial has started! Let's get you set up.\n👋 Hey there! I'm your Expensify setup specialist. "; | ||
|
|
||
| let message = welcomeHeader; | ||
| switch (companySize) { | ||
| case CONST.ONBOARDING_COMPANY_SIZE.MEDIUM: | ||
| case CONST.ONBOARDING_COMPANY_SIZE.LARGE: | ||
| message += | ||
| 'For an organization your size, the fastest path to value is setting up approval workflows, ' + | ||
| 'connecting your accounting software, and rolling out the Expensify Card to your team. ' + | ||
| "I'm here to walk you through each step — just ask!"; | ||
| break; | ||
| case CONST.ONBOARDING_COMPANY_SIZE.SMALL: | ||
| case CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL: | ||
| message += | ||
| 'For a growing team like yours, the fastest way to get value is to set up expense categories, ' + | ||
| 'configure approval workflows, and invite your team members. ' + | ||
| "I'm here to walk you through each step — just ask!"; | ||
| break; | ||
| default: | ||
| message += | ||
| 'For a small team like yours, the fastest way to get value is to set up a few expense categories, ' + | ||
| 'invite your team members, and have them start snapping receipts right away. ' + | ||
| "I'm here to walk you through each step — just ask!"; | ||
| break; | ||
| } | ||
|
|
||
| if (userReportedIntegration && userReportedIntegration !== 'other') { | ||
| const friendlyName = CONST.ONBOARDING_ACCOUNTING_MAPPING[userReportedIntegration as keyof typeof CONST.ONBOARDING_ACCOUNTING_MAPPING]; | ||
| if (friendlyName) { | ||
| message += `\n\nSince you use ${friendlyName}, I can help you connect it so your expenses sync automatically — just say the word!`; | ||
| } | ||
| } | ||
|
|
||
| return message; | ||
| } | ||
|
|
||
| function prepareOnboardingOnyxData({ | ||
| introSelected, | ||
| engagementChoice, | ||
|
|
@@ -11642,11 +11594,9 @@ function prepareOnboardingOnyxData({ | |
| userReportedIntegration, | ||
| wasInvited, | ||
| companySize, | ||
| selectedInterestedFeatures, | ||
| isInvitedAccountant, | ||
| onboardingPurposeSelected, | ||
| isSelfTourViewed, | ||
| betas, | ||
| hasCompletedGuidedSetupFlow, | ||
| }: PrepareOnboardingOnyxDataParams) { | ||
| if (engagementChoice === CONST.ONBOARDING_CHOICES.PERSONAL_SPEND) { | ||
|
|
@@ -11659,15 +11609,10 @@ function prepareOnboardingOnyxData({ | |
| onboardingMessage = getOnboardingMessages().onboardingMessages[CONST.ONBOARDING_CHOICES.SUBMIT]; | ||
| } | ||
|
|
||
| // Phase 1 cohort (MANAGE_TEAM + micro company size) bypasses the beta gate — the backend | ||
| // handles gating via NVP, so all micro users get followups without needing the beta flag. | ||
| // Includes MICRO_SMALL, MICRO_MEDIUM, and the deprecated MICRO for backwards compatibility. | ||
| const isPhase1Cohort = | ||
| companySize === CONST.ONBOARDING_COMPANY_SIZE.MICRO_SMALL || companySize === CONST.ONBOARDING_COMPANY_SIZE.MICRO_MEDIUM || companySize === CONST.ONBOARDING_COMPANY_SIZE.MICRO; | ||
| // Followups path: MANAGE_TEAM + (Phase 1 cohort OR suggestedFollowups beta). Reaches every | ||
| // MANAGE_TEAM cohort user, including `+` aliases and phone-primary sign-ups. | ||
| const shouldUseFollowupsInsteadOfTasks = | ||
| engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && (isPhase1Cohort || Permissions.isBetaEnabled(CONST.BETAS.SUGGESTED_FOLLOWUPS, betas, betaConfiguration)); | ||
| // Every MANAGE_TEAM signup uses the bespoke direct-post path. The server generates the | ||
| // per-tier welcome body in `UserAPI::buildBespokeWelcomeMessage`, so the App no longer | ||
| // needs a cohort gate or the suggestedFollowups beta to opt in. | ||
| const shouldUseFollowupsInsteadOfTasks = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM; | ||
| // Post to #admins room when followups fire OR the existing tasks-in-admins predicate approves. | ||
| const shouldPostTasksInAdminsRoom = shouldUseFollowupsInsteadOfTasks || isPostingTasksInAdminsRoom(engagementChoice); | ||
| const adminsChatReport = deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`]; | ||
|
|
@@ -11740,29 +11685,10 @@ function prepareOnboardingOnyxData({ | |
| reportComment: textComment.commentText, | ||
| }; | ||
|
|
||
| // When using followups instead of tasks, generate a bespoke welcome message from Concierge. | ||
| // The frontend displays it optimistically; the server uses it to generate suggested followups. | ||
| let bespokeWelcomeMessage: string | undefined; | ||
| let optimisticConciergeReportActionID: string | undefined; | ||
| let bespokeAction: OptimisticReportAction | undefined; | ||
|
|
||
| if (shouldUseFollowupsInsteadOfTasks) { | ||
| const bespokeMarkdown = getBespokeWelcomeMessage(companySize, userReportedIntegration); | ||
| optimisticConciergeReportActionID = rand64(); | ||
| // delegateAccountIDParam: will be threaded in PR 14; buildOptimisticAddCommentReportAction falls back to module-level Onyx.connect value (https://github.com/Expensify/App/issues/66425) | ||
| bespokeAction = buildOptimisticAddCommentReportAction({ | ||
| text: bespokeMarkdown, | ||
| actorAccountID: CONST.ACCOUNT_ID.CONCIERGE, | ||
| createdOffset: 2, | ||
| reportID: targetChatReportID, | ||
| reportActionID: optimisticConciergeReportActionID, | ||
| delegateAccountIDParam: undefined, | ||
| }); | ||
| // Reuse the HTML that buildOptimisticAddCommentReportAction already parsed via getParsedComment, | ||
| // so we avoid calling getParsedComment a second time with the same input. | ||
| // The backend passes this to the LLM as HTML for AddComment, which expects HTML. | ||
| bespokeWelcomeMessage = bespokeAction.commentText; | ||
| } | ||
| // Generate a deduplication ID for the server-side bespoke welcome. The server posts directly using | ||
| // this ID via addComment (idempotent on reportActionID), so we never add an optimistic action | ||
| // here — the real message arrives from the server without a flash. | ||
| const optimisticConciergeReportActionID: string | undefined = shouldUseFollowupsInsteadOfTasks ? rand64() : undefined; | ||
|
marcochavezf marked this conversation as resolved.
|
||
|
|
||
| let createWorkspaceTaskReportID; | ||
| let addExpenseApprovalsTaskReportID; | ||
|
|
@@ -11772,41 +11698,13 @@ function prepareOnboardingOnyxData({ | |
| const tasks = shouldUseFollowupsInsteadOfTasks ? [] : onboardingMessage.tasks; | ||
| const tasksData = tasks | ||
| .filter((task) => { | ||
| if (engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM) { | ||
| if (!!selectedInterestedFeatures && TASK_TO_FEATURE[task.type] && !selectedInterestedFeatures.includes(TASK_TO_FEATURE[task.type])) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| if (([CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES, CONST.ONBOARDING_TASK_TYPE.SETUP_TAGS] as string[]).includes(task.type) && userReportedIntegration) { | ||
| return false; | ||
| } | ||
|
|
||
| if (([CONST.ONBOARDING_TASK_TYPE.ADD_ACCOUNTING_INTEGRATION, CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES_AND_TAGS] as string[]).includes(task.type) && !userReportedIntegration) { | ||
| return false; | ||
| } | ||
| type SkipViewTourOnboardingChoices = | ||
| | typeof CONST.ONBOARDING_CHOICES.SUBMIT | ||
| | typeof CONST.ONBOARDING_CHOICES.CHAT_SPLIT | ||
| | typeof CONST.ONBOARDING_CHOICES.PERSONAL_SPEND | ||
| | typeof CONST.ONBOARDING_CHOICES.EMPLOYER | ||
| | typeof CONST.ONBOARDING_CHOICES.TRACK_PERSONAL | ||
| | typeof CONST.ONBOARDING_CHOICES.MANAGE_TEAM; | ||
| if ( | ||
| task.type === CONST.ONBOARDING_TASK_TYPE.VIEW_TOUR && | ||
| [ | ||
| CONST.ONBOARDING_CHOICES.EMPLOYER, | ||
| CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, | ||
| CONST.ONBOARDING_CHOICES.TRACK_PERSONAL, | ||
| CONST.ONBOARDING_CHOICES.SUBMIT, | ||
| CONST.ONBOARDING_CHOICES.CHAT_SPLIT, | ||
| CONST.ONBOARDING_CHOICES.MANAGE_TEAM, | ||
| ].includes(introSelected?.choice as SkipViewTourOnboardingChoices) && | ||
| engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM | ||
| ) { | ||
| return false; | ||
| } | ||
|
|
||
| // Exclude createWorkspace and viewTour tasks from #admin room, for test drive receivers, | ||
| // since these users already have them in concierge | ||
| if ( | ||
|
|
@@ -11832,8 +11730,7 @@ function prepareOnboardingOnyxData({ | |
| CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, | ||
| targetChatType, | ||
| ); | ||
| const emailCreatingAction = | ||
| engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ? (allPersonalDetails?.[actorAccountID]?.login ?? CONST.EMAIL.CONCIERGE) : CONST.EMAIL.CONCIERGE; | ||
| const emailCreatingAction = CONST.EMAIL.CONCIERGE; | ||
| const taskCreatedAction = buildOptimisticCreatedReportAction({emailCreatingAction}); | ||
| const taskReportAction = buildOptimisticTaskCommentReportAction(currentTask.reportID, taskTitle, 0, `task for ${taskTitle}`, targetChatReportID, actorAccountID, index + 3); | ||
| currentTask.parentReportActionID = taskReportAction.reportAction.reportActionID; | ||
|
|
@@ -12063,7 +11960,7 @@ function prepareOnboardingOnyxData({ | |
| key: `${ONYXKEYS.COLLECTION.REPORT}${targetChatReportID}`, | ||
| value: { | ||
| hasOutstandingChildTask, | ||
| ...(skipSignOff && !message ? {} : {lastVisibleActionCreated}), | ||
| ...(shouldUseFollowupsInsteadOfTasks || (skipSignOff && !message) ? {} : {lastVisibleActionCreated}), | ||
| lastActorAccountID: actorAccountID, | ||
| }, | ||
|
Comment on lines
+11963
to
11965
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the MANAGE_TEAM followups path, this merge now skips Useful? React with 👍 / 👎. |
||
| }, | ||
|
|
@@ -12089,26 +11986,6 @@ function prepareOnboardingOnyxData({ | |
| }); | ||
| } | ||
|
|
||
| if (bespokeAction && optimisticConciergeReportActionID) { | ||
| optimisticData.push( | ||
| { | ||
| onyxMethod: Onyx.METHOD.MERGE, | ||
| key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, | ||
| value: { | ||
| [optimisticConciergeReportActionID]: bespokeAction.reportAction as ReportAction, | ||
| }, | ||
| }, | ||
| { | ||
| onyxMethod: Onyx.METHOD.MERGE, | ||
| key: `${ONYXKEYS.COLLECTION.REPORT}${targetChatReportID}`, | ||
| value: { | ||
| lastVisibleActionCreated: bespokeAction.reportAction.created, | ||
| lastActorAccountID: CONST.ACCOUNT_ID.CONCIERGE, | ||
| }, | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| if (!wasInvited) { | ||
| optimisticData.push({ | ||
| onyxMethod: Onyx.METHOD.MERGE, | ||
|
|
@@ -12131,16 +12008,6 @@ function prepareOnboardingOnyxData({ | |
| }); | ||
| } | ||
|
|
||
| if (bespokeAction && optimisticConciergeReportActionID) { | ||
| successData.push({ | ||
| onyxMethod: Onyx.METHOD.MERGE, | ||
| key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, | ||
| value: { | ||
| [optimisticConciergeReportActionID]: {pendingAction: null, isOptimisticAction: null}, | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| let failureReport: Partial<Report> = { | ||
| lastMessageText: '', | ||
| lastVisibleActionCreated: '', | ||
|
|
@@ -12195,18 +12062,6 @@ function prepareOnboardingOnyxData({ | |
| }); | ||
| } | ||
|
|
||
| if (bespokeAction && optimisticConciergeReportActionID) { | ||
| failureData.push({ | ||
| onyxMethod: Onyx.METHOD.MERGE, | ||
| key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, | ||
| value: { | ||
| [optimisticConciergeReportActionID]: { | ||
| errors: getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), | ||
| } as ReportAction, | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| if (!wasInvited) { | ||
| failureData.push({ | ||
| onyxMethod: Onyx.METHOD.MERGE, | ||
|
|
@@ -12381,7 +12236,7 @@ function prepareOnboardingOnyxData({ | |
| }); | ||
| } | ||
|
|
||
| return {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters, bespokeWelcomeMessage, optimisticConciergeReportActionID}; | ||
| return {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters, optimisticConciergeReportActionID}; | ||
| } | ||
|
|
||
| /** | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.