1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +00:00

[SG-69] Billing payment step (#3133)

* billing folder added

* initial commit

* [SG-74] Trial Initiation Component with Vertical Stepper (#2913)

* Vertical stepper PoC

* Convert stepper css to tailwind

* trial component start

* trial component params

* tailwind-ify header

* Support teams, enterprise, and families layout param and more layout ui work

* Some more theming fixes

* Rename TrialModule to TrialInitiationModule

* Stepper fixes, plus more functionality demo

* Cleanup

* layout params and placeholders

* Only allow trial route to be hit if not logged in

* fix typo

* Use background-alt2 color for header

* Move vertical stepper out of trial-initiation

* Create components for the different plan types

* Remove width on steps

* Remove content projection for label

* Tailwind style fixes

* Extract step content into a component

* Remove layout param for now

* Remove step tags

* remove pointer classes from step button

* Remove most tailwind important designations

* Update apps/web/src/app/modules/vertical-stepper/vertical-step.component.ts

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Tailwind and layout fixes

* Remove container

* lint & prettier fixes

* Remove extra CdkStep declaration

* Styles fixes

* Style logo directly

* Remove 0 margin on image

* Fix tiling and responsiveness

* Minor padding fixes for org pages

* Update apps/web/src/app/modules/trial-initiation/trial-initiation.component.html

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* prettier fix

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* [SG-65] Reusable Registration Form (#2946)

* created reusable registration form

* fixed conflicts

* replicated reactive form changes in other clients

* removed comments

* client template cleanup

* client template cleanup

* removed comments in template file

* changed to component suffix

* switched show password to use component

* comments resolution

* comments resolution

* added toast disable functionality

* removed unused locale

* mode custom input validator generic

* fixed button

* fixed linter

* removed horizontal rule

* switched to button component

* Added billng step

* Added keys to locale

* billing trial initiation step

* billing trial initiation step

* Dont load billing content until the step is selected

* billing trial initiation step

* billing trial initiation step

* billing trial initiation step

* made the get plans endpoint anonymous

* merged with master and extra changes

* major changes on billing step

* billing step sub label

* Made changes to billing step sub label

* removed unused variable

* removed unused logic

* cleanup

* fixed suggestions

* removed unused reference

* added billing sub label

Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
Co-authored-by: addison <addisonbeck1@gmail.com>
This commit is contained in:
Gbubemi Smith
2022-07-20 02:00:25 +01:00
committed by GitHub
parent 8aca6459cf
commit f07e071f09
16 changed files with 259 additions and 31 deletions

View File

@@ -0,0 +1,48 @@
<form #form [formGroup]="formGroup" [appApiAction]="formPromise" (ngSubmit)="submit()">
<div class="tw-container tw-mb-3">
<div class="tw-mb-6">
<h2 class="tw-text-base tw-font-semibold tw-mb-3">{{ "billingPlanLabel" | i18n }}</h2>
<div class="tw-items-center tw-mb-1" *ngFor="let selectablePlan of selectablePlans">
<label class="tw-block tw- tw-text-main" for="interval{{ selectablePlan.type }}">
<input
checked
class="tw-w-4 tw-h-4 tw-align-middle"
id="interval{{ selectablePlan.type }}"
name="plan"
type="radio"
[value]="selectablePlan.type"
formControlName="plan"
/>
<ng-container *ngIf="selectablePlan.isAnnual">
{{ "annual" | i18n }} -
{{
(selectablePlan.basePrice === 0 ? selectablePlan.seatPrice : selectablePlan.basePrice)
| currency: "$"
}}
/{{ "yr" | i18n }}
</ng-container>
<ng-container *ngIf="!selectablePlan.isAnnual">
{{ "monthly" | i18n }} -
{{
(selectablePlan.basePrice === 0 ? selectablePlan.seatPrice : selectablePlan.basePrice)
| currency: "$"
}}
/{{ "monthAbbr" | i18n }}
</ng-container>
</label>
</div>
</div>
<div class="tw-mb-4 tw-overflow-auto">
<h2 class="tw-text-base tw-mb-3 tw-font-semibold">{{ "paymentType" | i18n }}</h2>
<app-payment [hideCredit]="true" [trialFlow]="true"></app-payment>
<app-tax-info [trialFlow]="true" (onCountryChanged)="changedCountry()"></app-tax-info>
</div>
<div class="tw-flex tw-space-x-2">
<bit-submit-button [loading]="form.loading">{{ "startTrial" | i18n }}</bit-submit-button>
<button bitButton type="button" buttonType="secondary" (click)="stepBack()">Back</button>
</div>
</div>
</form>

View File

@@ -0,0 +1,68 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms";
import { Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
import { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { OrganizationPlansComponent } from "src/app/settings/organization-plans.component";
@Component({
selector: "app-billing",
templateUrl: "./billing.component.html",
})
export class BillingComponent extends OrganizationPlansComponent {
@Input() orgInfoForm: FormGroup;
@Output() previousStep = new EventEmitter();
constructor(
apiService: ApiService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
cryptoService: CryptoService,
router: Router,
syncService: SyncService,
policyService: PolicyService,
organizationService: OrganizationService,
logService: LogService,
messagingService: MessagingService,
formBuilder: FormBuilder
) {
super(
apiService,
i18nService,
platformUtilsService,
cryptoService,
router,
syncService,
policyService,
organizationService,
logService,
messagingService,
formBuilder
);
}
async ngOnInit() {
this.formGroup.patchValue({
name: this.orgInfoForm.get("name")?.value,
billingEmail: this.orgInfoForm.get("email")?.value,
additionalSeats: 1,
plan: this.plan,
product: this.product,
});
this.isInTrialFlow = true;
await super.ngOnInit();
}
stepBack() {
this.previousStep.emit();
}
}

View File

@@ -0,0 +1,12 @@
import { NgModule } from "@angular/core";
import { SharedModule } from "../shared.module";
import { BillingComponent } from "./billing.component";
@NgModule({
imports: [SharedModule],
declarations: [BillingComponent],
exports: [BillingComponent],
})
export class BillingModule {}

View File

@@ -114,7 +114,6 @@ import { EmergencyAccessComponent } from "../settings/emergency-access.component
import { EmergencyAddEditComponent } from "../settings/emergency-add-edit.component"; import { EmergencyAddEditComponent } from "../settings/emergency-add-edit.component";
import { OrganizationPlansComponent } from "../settings/organization-plans.component"; import { OrganizationPlansComponent } from "../settings/organization-plans.component";
import { PaymentMethodComponent } from "../settings/payment-method.component"; import { PaymentMethodComponent } from "../settings/payment-method.component";
import { PaymentComponent } from "../settings/payment.component";
import { PreferencesComponent } from "../settings/preferences.component"; import { PreferencesComponent } from "../settings/preferences.component";
import { PremiumComponent } from "../settings/premium.component"; import { PremiumComponent } from "../settings/premium.component";
import { ProfileComponent } from "../settings/profile.component"; import { ProfileComponent } from "../settings/profile.component";
@@ -125,7 +124,6 @@ import { SettingsComponent } from "../settings/settings.component";
import { SponsoredFamiliesComponent } from "../settings/sponsored-families.component"; import { SponsoredFamiliesComponent } from "../settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "../settings/sponsoring-org-row.component"; import { SponsoringOrgRowComponent } from "../settings/sponsoring-org-row.component";
import { SubscriptionComponent } from "../settings/subscription.component"; import { SubscriptionComponent } from "../settings/subscription.component";
import { TaxInfoComponent } from "../settings/tax-info.component";
import { TwoFactorAuthenticatorComponent } from "../settings/two-factor-authenticator.component"; import { TwoFactorAuthenticatorComponent } from "../settings/two-factor-authenticator.component";
import { TwoFactorDuoComponent } from "../settings/two-factor-duo.component"; import { TwoFactorDuoComponent } from "../settings/two-factor-duo.component";
import { TwoFactorEmailComponent } from "../settings/two-factor-email.component"; import { TwoFactorEmailComponent } from "../settings/two-factor-email.component";
@@ -271,7 +269,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
PasswordGeneratorHistoryComponent, PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent, PasswordGeneratorPolicyComponent,
PasswordRepromptComponent, PasswordRepromptComponent,
PaymentComponent,
PaymentMethodComponent, PaymentMethodComponent,
PersonalOwnershipPolicyComponent, PersonalOwnershipPolicyComponent,
PreferencesComponent, PreferencesComponent,
@@ -304,7 +301,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
SponsoringOrgRowComponent, SponsoringOrgRowComponent,
SsoComponent, SsoComponent,
SubscriptionComponent, SubscriptionComponent,
TaxInfoComponent,
ToolsComponent, ToolsComponent,
TwoFactorAuthenticationPolicyComponent, TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent, TwoFactorAuthenticatorComponent,
@@ -425,7 +421,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
PasswordGeneratorHistoryComponent, PasswordGeneratorHistoryComponent,
PasswordGeneratorPolicyComponent, PasswordGeneratorPolicyComponent,
PasswordRepromptComponent, PasswordRepromptComponent,
PaymentComponent,
PaymentMethodComponent, PaymentMethodComponent,
PersonalOwnershipPolicyComponent, PersonalOwnershipPolicyComponent,
PreferencesComponent, PreferencesComponent,
@@ -458,7 +453,6 @@ import { OrganizationBadgeModule } from "./vault/modules/organization-badge/orga
SponsoringOrgRowComponent, SponsoringOrgRowComponent,
SsoComponent, SsoComponent,
SubscriptionComponent, SubscriptionComponent,
TaxInfoComponent,
ToolsComponent, ToolsComponent,
TwoFactorAuthenticationPolicyComponent, TwoFactorAuthenticationPolicyComponent,
TwoFactorAuthenticatorComponent, TwoFactorAuthenticatorComponent,

View File

@@ -67,6 +67,8 @@ import {
} from "@bitwarden/components"; } from "@bitwarden/components";
import { PasswordStrengthComponent } from "../components/password-strength.component"; import { PasswordStrengthComponent } from "../components/password-strength.component";
import { PaymentComponent } from "../settings/payment.component";
import { TaxInfoComponent } from "../settings/tax-info.component";
registerLocaleData(localeAf, "af"); registerLocaleData(localeAf, "af");
registerLocaleData(localeAz, "az"); registerLocaleData(localeAz, "az");
@@ -120,7 +122,7 @@ registerLocaleData(localeZhCn, "zh-CN");
registerLocaleData(localeZhTw, "zh-TW"); registerLocaleData(localeZhTw, "zh-TW");
@NgModule({ @NgModule({
declarations: [PasswordStrengthComponent], declarations: [PasswordStrengthComponent, PaymentComponent, TaxInfoComponent],
imports: [ imports: [
CommonModule, CommonModule,
DragDropModule, DragDropModule,
@@ -155,8 +157,10 @@ registerLocaleData(localeZhTw, "zh-TW");
ButtonModule, ButtonModule,
MenuModule, MenuModule,
FormFieldModule, FormFieldModule,
PasswordStrengthComponent,
SubmitButtonModule, SubmitButtonModule,
PasswordStrengthComponent,
PaymentComponent,
TaxInfoComponent,
], ],
providers: [DatePipe], providers: [DatePipe],
bootstrap: [], bootstrap: [],

View File

@@ -57,11 +57,15 @@
Next Next
</button> </button>
</app-vertical-step> </app-vertical-step>
<app-vertical-step label="Billing"> <app-vertical-step label="Billing" [subLabel]="billingSubLabel">
<!-- Replace with Billing step --> <app-billing
<p>This is content of "Step 3"</p> *ngIf="stepper.selectedIndex === 2"
<button bitButton buttonType="secondary" cdkStepperPrevious>Back</button> [plan]="plan"
<button bitButton buttonType="primary" cdkStepperNext>Complete step</button> [product]="product"
[orgInfoForm]="orgInfoFormGroup"
(previousStep)="previousStep()"
(onTrialBillingSuccess)="billingSuccess($event)"
></app-billing>
</app-vertical-step> </app-vertical-step>
<app-vertical-step label="Confirmation Details" subLabel="Fancy sub label"> <app-vertical-step label="Confirmation Details" subLabel="Fancy sub label">
<!-- Replace with Confirmation details step --> <!-- Replace with Confirmation details step -->

View File

@@ -10,6 +10,8 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy.service"; import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { PlanType } from "@bitwarden/common/enums/planType";
import { ProductType } from "@bitwarden/common/enums/productType";
import { PolicyData } from "@bitwarden/common/models/data/policyData"; import { PolicyData } from "@bitwarden/common/models/data/policyData";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
import { Policy } from "@bitwarden/common/models/domain/policy"; import { Policy } from "@bitwarden/common/models/domain/policy";
@@ -24,6 +26,10 @@ export class TrialInitiationComponent implements OnInit {
email = ""; email = "";
org = "teams"; org = "teams";
orgInfoSubLabel = ""; orgInfoSubLabel = "";
orgId = "";
billingSubLabel = "";
plan: PlanType;
product: ProductType;
accountCreateOnly = true; accountCreateOnly = true;
policies: Policy[]; policies: Policy[];
enforcedPolicyOptions: MasterPasswordPolicyOptions; enforcedPolicyOptions: MasterPasswordPolicyOptions;
@@ -31,11 +37,7 @@ export class TrialInitiationComponent implements OnInit {
orgInfoFormGroup = this.formBuilder.group({ orgInfoFormGroup = this.formBuilder.group({
name: ["", [Validators.required]], name: ["", [Validators.required]],
additionalStorage: [0, [Validators.min(0), Validators.max(99)]], email: [""],
additionalSeats: [0, [Validators.min(0), Validators.max(100000)]],
businessName: [""],
plan: [],
product: [],
}); });
constructor( constructor(
@@ -54,9 +56,23 @@ export class TrialInitiationComponent implements OnInit {
if (qParams.email != null && qParams.email.indexOf("@") > -1) { if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email; this.email = qParams.email;
} }
if (qParams.org) {
this.org = qParams.org; if (!qParams.org) {
this.accountCreateOnly = false; return;
}
this.org = qParams.org;
this.accountCreateOnly = false;
if (qParams.org === "families") {
this.plan = PlanType.FamiliesAnnually;
this.product = ProductType.Families;
} else if (qParams.org === "teams") {
this.plan = PlanType.TeamsAnnually;
this.product = ProductType.Teams;
} else if (qParams.org === "enterprise") {
this.plan = PlanType.EnterpriseAnnually;
this.product = ProductType.Enterprise;
} }
}); });
@@ -93,10 +109,26 @@ export class TrialInitiationComponent implements OnInit {
} else if (event.previouslySelectedIndex === 1) { } else if (event.previouslySelectedIndex === 1) {
this.orgInfoSubLabel = this.orgInfoFormGroup.controls.name.value; this.orgInfoSubLabel = this.orgInfoFormGroup.controls.name.value;
} }
//set billing sub label
if (event.selectedIndex === 2) {
this.billingSubLabel = this.i18nService.t("billingTrialSubLabel");
}
} }
createdAccount(email: string) { createdAccount(email: string) {
this.email = email; this.email = email;
this.orgInfoFormGroup.get("email")?.setValue(email);
this.verticalStepper.next(); this.verticalStepper.next();
} }
billingSuccess(event: any) {
this.orgId = event?.orgId;
this.billingSubLabel = event?.subLabelText;
this.verticalStepper.next();
}
previousStep() {
this.verticalStepper.previous();
}
} }

View File

@@ -9,6 +9,7 @@ import { RegisterFormModule } from "../register-form/register-form.module";
import { SharedModule } from "../shared.module"; import { SharedModule } from "../shared.module";
import { VerticalStepperModule } from "../vertical-stepper/vertical-stepper.module"; import { VerticalStepperModule } from "../vertical-stepper/vertical-stepper.module";
import { BillingModule } from "./../billing/billing.module";
import { EnterpriseContentComponent } from "./enterprise-content.component"; import { EnterpriseContentComponent } from "./enterprise-content.component";
import { FamiliesContentComponent } from "./families-content.component"; import { FamiliesContentComponent } from "./families-content.component";
import { TeamsContentComponent } from "./teams-content.component"; import { TeamsContentComponent } from "./teams-content.component";
@@ -22,6 +23,7 @@ import { TrialInitiationComponent } from "./trial-initiation.component";
FormFieldModule, FormFieldModule,
RegisterFormModule, RegisterFormModule,
OrganizationCreateModule, OrganizationCreateModule,
BillingModule,
], ],
declarations: [ declarations: [
TrialInitiationComponent, TrialInitiationComponent,

View File

@@ -43,12 +43,14 @@ export class OrganizationPlansComponent implements OnInit {
@Input() providerId: string; @Input() providerId: string;
@Output() onSuccess = new EventEmitter(); @Output() onSuccess = new EventEmitter();
@Output() onCanceled = new EventEmitter(); @Output() onCanceled = new EventEmitter();
@Output() onTrialBillingSuccess = new EventEmitter();
loading = true; loading = true;
selfHosted = false; selfHosted = false;
productTypes = ProductType; productTypes = ProductType;
formPromise: Promise<any>; formPromise: Promise<any>;
singleOrgPolicyBlock = false; singleOrgPolicyBlock = false;
isInTrialFlow = false;
discount = 0; discount = 0;
formGroup = this.formBuilder.group({ formGroup = this.formBuilder.group({
@@ -149,7 +151,7 @@ export class OrganizationPlansComponent implements OnInit {
} }
get selectablePlans() { get selectablePlans() {
return this.plans.filter( return this.plans?.filter(
(plan) => (plan) =>
!plan.legacyYear && !plan.disabled && plan.product === this.formGroup.controls.product.value !plan.legacyYear && !plan.disabled && plan.product === this.formGroup.controls.product.value
); );
@@ -321,10 +323,18 @@ export class OrganizationPlansComponent implements OnInit {
await this.apiService.refreshIdentityToken(); await this.apiService.refreshIdentityToken();
await this.syncService.fullSync(true); await this.syncService.fullSync(true);
if (!this.acceptingSponsorship) {
if (!this.acceptingSponsorship && !this.isInTrialFlow) {
this.router.navigate(["/organizations/" + orgId]); this.router.navigate(["/organizations/" + orgId]);
} }
if (this.isInTrialFlow) {
this.onTrialBillingSuccess.emit({
orgId: orgId,
subLabelText: this.billingSubLabelText(),
});
}
return orgId; return orgId;
}; };
@@ -448,4 +458,18 @@ export class OrganizationPlansComponent implements OnInit {
return orgId; return orgId;
} }
private billingSubLabelText(): string {
const selectedPlan = this.selectedPlan;
const price = selectedPlan.basePrice === 0 ? selectedPlan.seatPrice : selectedPlan.basePrice;
let text = "";
if (selectedPlan.isAnnual) {
text += `${this.i18nService.t("annual")} ($${price}/${this.i18nService.t("yr")})`;
} else {
text += `${this.i18nService.t("monthly")} ($${price}/${this.i18nService.t("monthAbbr")})`;
}
return text;
}
} }

View File

@@ -58,11 +58,11 @@
</div> </div>
<ng-container *ngIf="showMethods && method === paymentMethodType.Card"> <ng-container *ngIf="showMethods && method === paymentMethodType.Card">
<div class="row"> <div class="row">
<div class="form-group col-4"> <div [ngClass]="trialFlow ? 'col-4' : 'col-4'" class="form-group">
<label for="stripe-card-number-element">{{ "number" | i18n }}</label> <label for="stripe-card-number-element">{{ "number" | i18n }}</label>
<div id="stripe-card-number-element" class="form-control stripe-form-control"></div> <div id="stripe-card-number-element" class="form-control stripe-form-control"></div>
</div> </div>
<div class="form-group col-8 d-flex align-items-end"> <div *ngIf="!trialFlow" class="form-group col-8 d-flex align-items-end">
<img <img
src="../../images/cards.png" src="../../images/cards.png"
alt="Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay" alt="Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay"
@@ -70,11 +70,11 @@
height="32" height="32"
/> />
</div> </div>
<div class="form-group col-4"> <div [ngClass]="trialFlow ? 'col-3' : 'col-4'" class="form-group">
<label for="stripe-card-expiry-element">{{ "expiration" | i18n }}</label> <label for="stripe-card-expiry-element">{{ "expiration" | i18n }}</label>
<div id="stripe-card-expiry-element" class="form-control stripe-form-control"></div> <div id="stripe-card-expiry-element" class="form-control stripe-form-control"></div>
</div> </div>
<div class="form-group col-4"> <div [ngClass]="trialFlow ? 'col-5' : 'col-4'" class="form-group">
<div class="d-flex"> <div class="d-flex">
<label for="stripe-card-cvc-element"> <label for="stripe-card-cvc-element">
{{ "securityCode" | i18n }} {{ "securityCode" | i18n }}

View File

@@ -25,6 +25,7 @@ export class PaymentComponent implements OnInit, OnDestroy {
@Input() hideBank = false; @Input() hideBank = false;
@Input() hidePaypal = false; @Input() hidePaypal = false;
@Input() hideCredit = false; @Input() hideCredit = false;
@Input() trialFlow = false;
private destroy$: Subject<void> = new Subject<void>(); private destroy$: Subject<void> = new Subject<void>();

View File

@@ -265,7 +265,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="col-3"> <div [ngClass]="trialFlow ? 'col-4' : 'col-3'">
<div class="form-group"> <div class="form-group">
<label for="addressPostalCode">{{ "zipPostalCode" | i18n }}</label> <label for="addressPostalCode">{{ "zipPostalCode" | i18n }}</label>
<input <input

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Output } from "@angular/core"; import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -12,6 +12,7 @@ import { TaxRateResponse } from "@bitwarden/common/models/response/taxRateRespon
templateUrl: "tax-info.component.html", templateUrl: "tax-info.component.html",
}) })
export class TaxInfoComponent { export class TaxInfoComponent {
@Input() trialFlow = false;
@Output() onCountryChanged = new EventEmitter(); @Output() onCountryChanged = new EventEmitter();
loading = true; loading = true;

View File

@@ -644,6 +644,9 @@
"newAccountCreated": { "newAccountCreated": {
"message": "Your new account has been created! You may now log in." "message": "Your new account has been created! You may now log in."
}, },
"trialAccountCreated": {
"message": "Account created successfully."
},
"masterPassSent": { "masterPassSent": {
"message": "We've sent you an email with your master password hint." "message": "We've sent you an email with your master password hint."
}, },
@@ -1669,6 +1672,12 @@
"billing": { "billing": {
"message": "Billing" "message": "Billing"
}, },
"billingPlanLabel": {
"message": "Billing Plan"
},
"paymentType": {
"message": "Payment Type"
},
"accountCredit": { "accountCredit": {
"message": "Account Credit", "message": "Account Credit",
"description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)." "description": "Financial term. In the case of Bitwarden, a positive balance means that you owe money, while a negative balance means that you have a credit (Bitwarden owes you money)."
@@ -1785,6 +1794,9 @@
"year": { "year": {
"message": "year" "message": "year"
}, },
"yr": {
"message": "yr"
},
"month": { "month": {
"message": "month" "message": "month"
}, },
@@ -1813,6 +1825,9 @@
"billingInformation": { "billingInformation": {
"message": "Billing Information" "message": "Billing Information"
}, },
"billingTrialSubLabel": {
"message": "Your payment method will not be charged during the 7 day free trial."
},
"creditCard": { "creditCard": {
"message": "Credit Card" "message": "Credit Card"
}, },
@@ -2175,6 +2190,9 @@
"annually": { "annually": {
"message": "Annually" "message": "Annually"
}, },
"annual": {
"message": "Annual"
},
"basePrice": { "basePrice": {
"message": "Base Price" "message": "Base Price"
}, },

View File

@@ -17,6 +17,7 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service"; import { StateService } from "@bitwarden/common/abstractions/state.service";
import { DEFAULT_KDF_ITERATIONS, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType"; import { DEFAULT_KDF_ITERATIONS, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType";
import { PasswordLogInCredentials } from "@bitwarden/common/models/domain/logInCredentials";
import { KeysRequest } from "@bitwarden/common/models/request/keysRequest"; import { KeysRequest } from "@bitwarden/common/models/request/keysRequest";
import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenceEventRequest"; import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenceEventRequest";
import { RegisterRequest } from "@bitwarden/common/models/request/registerRequest"; import { RegisterRequest } from "@bitwarden/common/models/request/registerRequest";
@@ -200,10 +201,29 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
throw e; throw e;
} }
} }
this.platformUtilsService.showToast("success", null, this.i18nService.t("newAccountCreated"));
if (this.isInTrialFlow) { if (this.isInTrialFlow) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("trialAccountCreated")
);
//login user here
const credentials = new PasswordLogInCredentials(
email,
masterPassword,
this.captchaToken,
null
);
await this.authService.logIn(credentials);
this.createdAccount.emit(this.formGroup.get("email")?.value); this.createdAccount.emit(this.formGroup.get("email")?.value);
} else { } else {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("newAccountCreated")
);
this.router.navigate([this.successRoute], { queryParams: { email: email } }); this.router.navigate([this.successRoute], { queryParams: { email: email } });
} }
} catch (e) { } catch (e) {

View File

@@ -1395,7 +1395,7 @@ export class ApiService implements ApiServiceAbstraction {
// Plan APIs // Plan APIs
async getPlans(): Promise<ListResponse<PlanResponse>> { async getPlans(): Promise<ListResponse<PlanResponse>> {
const r = await this.send("GET", "/plans/", null, true, true); const r = await this.send("GET", "/plans/", null, false, true);
return new ListResponse(r, PlanResponse); return new ListResponse(r, PlanResponse);
} }