mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 07:13:32 +00:00
* Add billable-entity * Add payment types * Add billing.client * Update stripe.service * Add payment method components * Add address.pipe * Add billing address components * Add account credit components * Add component index * Add feature flag * Re-work organization warnings code * Add organization-payment-details.component * Backfill translations * Set up organization FF routing * Add account-payment-details.component * Set up account FF routing * Add provider-payment-details.component * Set up provider FF routing * Use inline component templates for re-usable payment components * Remove errant rebase file * Removed public accessibility modifier * Fix failing test
195 lines
5.9 KiB
TypeScript
195 lines
5.9 KiB
TypeScript
import { Component, Input, OnDestroy, OnInit } from "@angular/core";
|
|
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
|
import { map, Observable, startWith, Subject, takeUntil } from "rxjs";
|
|
|
|
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
|
|
|
|
import { SharedModule } from "../../../shared";
|
|
import { BillingAddress, selectableCountries, taxIdTypes } from "../types";
|
|
|
|
export interface BillingAddressControls {
|
|
country: string;
|
|
postalCode: string;
|
|
line1: string | null;
|
|
line2: string | null;
|
|
city: string | null;
|
|
state: string | null;
|
|
taxId: string | null;
|
|
}
|
|
|
|
export type BillingAddressFormGroup = FormGroup<ControlsOf<BillingAddressControls>>;
|
|
|
|
type Scenario =
|
|
| {
|
|
type: "checkout";
|
|
supportsTaxId: boolean;
|
|
}
|
|
| {
|
|
type: "update";
|
|
existing?: BillingAddress;
|
|
supportsTaxId: boolean;
|
|
};
|
|
|
|
@Component({
|
|
selector: "app-enter-billing-address",
|
|
template: `
|
|
<form [formGroup]="group">
|
|
<div class="tw-grid tw-grid-cols-12 tw-gap-4">
|
|
<div class="tw-col-span-6">
|
|
<bit-form-field [disableMargin]="true">
|
|
<bit-label>{{ "country" | i18n }}</bit-label>
|
|
<bit-select [formControl]="group.controls.country">
|
|
@for (selectableCountry of selectableCountries; track selectableCountry.value) {
|
|
<bit-option
|
|
[value]="selectableCountry.value"
|
|
[disabled]="selectableCountry.disabled"
|
|
[label]="selectableCountry.name"
|
|
></bit-option>
|
|
}
|
|
</bit-select>
|
|
</bit-form-field>
|
|
</div>
|
|
<div class="tw-col-span-6">
|
|
<bit-form-field [disableMargin]="true">
|
|
<bit-label>{{ "zipPostalCode" | i18n }}</bit-label>
|
|
<input
|
|
bitInput
|
|
type="text"
|
|
[formControl]="group.controls.postalCode"
|
|
autocomplete="postal-code"
|
|
/>
|
|
</bit-form-field>
|
|
</div>
|
|
<div class="tw-col-span-6">
|
|
<bit-form-field [disableMargin]="true">
|
|
<bit-label>{{ "address1" | i18n }}</bit-label>
|
|
<input
|
|
bitInput
|
|
type="text"
|
|
[formControl]="group.controls.line1"
|
|
autocomplete="address-line1"
|
|
/>
|
|
</bit-form-field>
|
|
</div>
|
|
<div class="tw-col-span-6">
|
|
<bit-form-field [disableMargin]="true">
|
|
<bit-label>{{ "address2" | i18n }}</bit-label>
|
|
<input
|
|
bitInput
|
|
type="text"
|
|
[formControl]="group.controls.line2"
|
|
autocomplete="address-line2"
|
|
/>
|
|
</bit-form-field>
|
|
</div>
|
|
<div class="tw-col-span-6">
|
|
<bit-form-field [disableMargin]="true">
|
|
<bit-label>{{ "cityTown" | i18n }}</bit-label>
|
|
<input
|
|
bitInput
|
|
type="text"
|
|
[formControl]="group.controls.city"
|
|
autocomplete="address-level2"
|
|
/>
|
|
</bit-form-field>
|
|
</div>
|
|
<div class="tw-col-span-6">
|
|
<bit-form-field [disableMargin]="true">
|
|
<bit-label>{{ "stateProvince" | i18n }}</bit-label>
|
|
<input
|
|
bitInput
|
|
type="text"
|
|
[formControl]="group.controls.state"
|
|
autocomplete="address-level1"
|
|
/>
|
|
</bit-form-field>
|
|
</div>
|
|
@if (supportsTaxId$ | async) {
|
|
<div class="tw-col-span-6">
|
|
<bit-form-field [disableMargin]="true">
|
|
<bit-label>{{ "taxIdNumber" | i18n }}</bit-label>
|
|
<input bitInput type="text" [formControl]="group.controls.taxId" />
|
|
</bit-form-field>
|
|
</div>
|
|
}
|
|
</div>
|
|
</form>
|
|
`,
|
|
standalone: true,
|
|
imports: [SharedModule],
|
|
})
|
|
export class EnterBillingAddressComponent implements OnInit, OnDestroy {
|
|
@Input({ required: true }) scenario!: Scenario;
|
|
@Input({ required: true }) group!: BillingAddressFormGroup;
|
|
|
|
protected selectableCountries = selectableCountries;
|
|
protected supportsTaxId$!: Observable<boolean>;
|
|
|
|
private destroy$ = new Subject<void>();
|
|
|
|
ngOnInit() {
|
|
switch (this.scenario.type) {
|
|
case "checkout": {
|
|
this.disableAddressControls();
|
|
break;
|
|
}
|
|
case "update": {
|
|
if (this.scenario.existing) {
|
|
this.group.patchValue({
|
|
...this.scenario.existing,
|
|
taxId: this.scenario.existing.taxId?.value,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
this.supportsTaxId$ = this.group.controls.country.valueChanges.pipe(
|
|
startWith(this.group.value.country ?? this.selectableCountries[0].value),
|
|
map((country) => {
|
|
if (!this.scenario.supportsTaxId) {
|
|
return false;
|
|
}
|
|
|
|
return taxIdTypes.filter((taxIdType) => taxIdType.iso === country).length > 0;
|
|
}),
|
|
);
|
|
|
|
this.supportsTaxId$.pipe(takeUntil(this.destroy$)).subscribe((supportsTaxId) => {
|
|
if (supportsTaxId) {
|
|
this.group.controls.taxId.enable();
|
|
} else {
|
|
this.group.controls.taxId.disable();
|
|
}
|
|
});
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this.destroy$.next();
|
|
this.destroy$.complete();
|
|
}
|
|
|
|
disableAddressControls = () => {
|
|
this.group.controls.line1.disable();
|
|
this.group.controls.line2.disable();
|
|
this.group.controls.city.disable();
|
|
this.group.controls.state.disable();
|
|
};
|
|
|
|
static getFormGroup = (): BillingAddressFormGroup =>
|
|
new FormGroup({
|
|
country: new FormControl<string>("", {
|
|
nonNullable: true,
|
|
validators: [Validators.required],
|
|
}),
|
|
postalCode: new FormControl<string>("", {
|
|
nonNullable: true,
|
|
validators: [Validators.required],
|
|
}),
|
|
line1: new FormControl<string | null>(null),
|
|
line2: new FormControl<string | null>(null),
|
|
city: new FormControl<string | null>(null),
|
|
state: new FormControl<string | null>(null),
|
|
taxId: new FormControl<string | null>(null),
|
|
});
|
|
}
|