1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-26044] Update Offboarding Survey for User and Organization (#17472)

* feat(billing): update messages to add reasons

* feat(billing): update survey with switching reason based on param

* fix(billing): revert value of switching reasons

* fix(billing): revert removal of tooExpensive message

* fix(billing): Add plan type to params and update switching logic

* fix(billing): update to include logic

* fix(billing): PR feedback
This commit is contained in:
Stephon Brown
2025-11-20 13:38:33 -05:00
committed by GitHub
parent 81453ede1b
commit 9afba33f58
4 changed files with 66 additions and 36 deletions

View File

@@ -344,6 +344,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
data: {
type: "Organization",
id: this.organizationId,
plan: this.sub.plan.type,
},
});

View File

@@ -21,7 +21,8 @@
</bit-label>
<textarea rows="4" bitInput formControlName="feedback"></textarea>
<bit-hint>{{
"charactersCurrentAndMaximum" | i18n: formGroup.value.feedback.length : MaxFeedbackLength
"charactersCurrentAndMaximum"
| i18n: formGroup.value.feedback?.length ?? 0 : MaxFeedbackLength
}}</bit-hint>
</bit-form-field>
</div>

View File

@@ -1,9 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, Inject } from "@angular/core";
import { ChangeDetectionStrategy, Component, Inject } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { PlanType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import {
@@ -21,6 +22,7 @@ type UserOffboardingParams = {
type OrganizationOffboardingParams = {
type: "Organization";
id: string;
plan: PlanType;
};
export type OffboardingSurveyDialogParams = UserOffboardingParams | OrganizationOffboardingParams;
@@ -46,50 +48,20 @@ export const openOffboardingSurvey = (
dialogConfig,
);
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-cancel-subscription-form",
templateUrl: "offboarding-survey.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class OffboardingSurveyComponent {
protected ResultType = OffboardingSurveyDialogResultType;
protected readonly MaxFeedbackLength = 400;
protected readonly reasons: Reason[] = [
{
value: null,
text: this.i18nService.t("selectPlaceholder"),
},
{
value: "missing_features",
text: this.i18nService.t("missingFeatures"),
},
{
value: "switched_service",
text: this.i18nService.t("movingToAnotherTool"),
},
{
value: "too_complex",
text: this.i18nService.t("tooDifficultToUse"),
},
{
value: "unused",
text: this.i18nService.t("notUsingEnough"),
},
{
value: "too_expensive",
text: this.i18nService.t("tooExpensive"),
},
{
value: "other",
text: this.i18nService.t("other"),
},
];
protected readonly reasons: Reason[] = [];
protected formGroup = this.formBuilder.group({
reason: [this.reasons[0].value, [Validators.required]],
reason: [null, [Validators.required]],
feedback: ["", [Validators.maxLength(this.MaxFeedbackLength)]],
});
@@ -101,7 +73,35 @@ export class OffboardingSurveyComponent {
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private toastService: ToastService,
) {}
) {
this.reasons = [
{
value: null,
text: this.i18nService.t("selectPlaceholder"),
},
{
value: "missing_features",
text: this.i18nService.t("missingFeatures"),
},
{
value: "switched_service",
text: this.i18nService.t("movingToAnotherTool"),
},
{
value: "too_complex",
text: this.i18nService.t("tooDifficultToUse"),
},
{
value: "unused",
text: this.i18nService.t("notUsingEnough"),
},
this.getSwitchingReason(),
{
value: "other",
text: this.i18nService.t("other"),
},
];
}
submit = async () => {
this.formGroup.markAllAsTouched();
@@ -127,4 +127,24 @@ export class OffboardingSurveyComponent {
this.dialogRef.close(this.ResultType.Submitted);
};
private getSwitchingReason(): Reason {
if (this.dialogParams.type === "User") {
return {
value: "too_expensive",
text: this.i18nService.t("switchToFreePlan"),
};
}
const isFamilyPlan = [
PlanType.FamiliesAnnually,
PlanType.FamiliesAnnually2019,
PlanType.FamiliesAnnually2025,
].includes(this.dialogParams.plan);
return {
value: "too_expensive",
text: this.i18nService.t(isFamilyPlan ? "switchToFreeOrg" : "tooExpensive"),
};
}
}

View File

@@ -9824,6 +9824,14 @@
"message": "Too expensive",
"description": "An option for the offboarding survey shown when a user cancels their subscription."
},
"switchToFreePlan": {
"message": "Switching to free plan",
"description": "An option for the offboarding survey shown when a user cancels their subscription."
},
"switchToFreeOrg": {
"message": "Switching to free organization",
"description": "An option for the offboarding survey shown when a user cancels their subscription."
},
"freeForOneYear": {
"message": "Free for 1 year"
},