1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 13:53:34 +00:00

[PM-5018] Migrate Add Credit component (#8321)

* Migrated Add Credit component

* PM-5018 Addressed Review comments for Add Credit Component

* PM-5018 Design team comments addressed

* PM-5018 Latest review comments are addressed

* PM-5018 Minor comments addressed
This commit is contained in:
KiruthigaManivannan
2024-05-21 18:33:19 +05:30
committed by GitHub
parent a3d69047c7
commit c8998d0c00
6 changed files with 136 additions and 154 deletions

View File

@@ -0,0 +1,61 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog dialogSize="default" [title]="'addCredit' | i18n">
<ng-container bitDialogContent>
<p bitTypography="body1">{{ "creditDelayed" | i18n }}</p>
<div class="tw-grid tw-grid-cols-2">
<bit-radio-group formControlName="method">
<bit-radio-button id="credit-method-paypal" [value]="paymentMethodType.PayPal">
<bit-label> <i class="bwi bwi-paypal"></i>PayPal</bit-label>
</bit-radio-button>
<bit-radio-button id="credit-method-bitcoin" [value]="paymentMethodType.BitPay">
<bit-label> <i class="bwi bwi-bitcoin"></i>Bitcoin</bit-label>
</bit-radio-button>
</bit-radio-group>
</div>
<div class="tw-grid tw-grid-cols-2">
<bit-form-field>
<bit-label>{{ "amount" | i18n }}</bit-label>
<input
bitInput
type="text"
formControlName="creditAmount"
(blur)="formatAmount()"
required
/>
<span bitPrefix>$USD</span>
</bit-form-field>
</div>
</ng-container>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="primary">
{{ "submit" | i18n }}
</button>
<button
type="button"
bitButton
bitFormButton
buttonType="secondary"
[bitDialogClose]="DialogResult.Cancelled"
>
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>
<form #ppButtonForm action="{{ ppButtonFormAction }}" method="post" target="_top">
<input type="hidden" name="cmd" value="_xclick" />
<input type="hidden" name="business" value="{{ ppButtonBusinessId }}" />
<input type="hidden" name="button_subtype" value="services" />
<input type="hidden" name="no_note" value="1" />
<input type="hidden" name="no_shipping" value="1" />
<input type="hidden" name="rm" value="1" />
<input type="hidden" name="return" value="{{ returnUrl }}" />
<input type="hidden" name="cancel_return" value="{{ returnUrl }}" />
<input type="hidden" name="currency_code" value="USD" />
<input type="hidden" name="image_url" value="https://bitwarden.com/images/paypal-banner.png" />
<input type="hidden" name="bn" value="PP-BuyNowBF:btn_buynow_LG.gif:NonHosted" />
<input type="hidden" name="amount" value="{{ formGroup.get('creditAmount').value }}" />
<input type="hidden" name="custom" value="{{ ppButtonCustomField }}" />
<input type="hidden" name="item_name" value="Bitwarden Account Credit" />
<input type="hidden" name="item_number" value="{{ subject }}" />
</form>

View File

@@ -1,12 +1,6 @@
import { import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
Component, import { Component, ElementRef, Inject, OnInit, ViewChild } from "@angular/core";
ElementRef, import { FormControl, FormGroup, Validators } from "@angular/forms";
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from "@angular/core";
import { firstValueFrom, map } from "rxjs"; import { firstValueFrom, map } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -17,6 +11,16 @@ import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/b
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components";
export interface AddCreditDialogData {
organizationId: string;
}
export enum AddCreditDialogResult {
Added = "added",
Cancelled = "cancelled",
}
export type PayPalConfig = { export type PayPalConfig = {
businessId?: string; businessId?: string;
@@ -24,17 +28,9 @@ export type PayPalConfig = {
}; };
@Component({ @Component({
selector: "app-add-credit", templateUrl: "add-credit-dialog.component.html",
templateUrl: "add-credit.component.html",
}) })
export class AddCreditComponent implements OnInit { export class AddCreditDialogComponent implements OnInit {
@Input() creditAmount: string;
@Input() showOptions = true;
@Input() method = PaymentMethodType.PayPal;
@Input() organizationId: string;
@Output() onAdded = new EventEmitter();
@Output() onCanceled = new EventEmitter();
@ViewChild("ppButtonForm", { read: ElementRef, static: true }) ppButtonFormRef: ElementRef; @ViewChild("ppButtonForm", { read: ElementRef, static: true }) ppButtonFormRef: ElementRef;
paymentMethodType = PaymentMethodType; paymentMethodType = PaymentMethodType;
@@ -44,14 +40,22 @@ export class AddCreditComponent implements OnInit {
ppLoading = false; ppLoading = false;
subject: string; subject: string;
returnUrl: string; returnUrl: string;
formPromise: Promise<any>; organizationId: string;
private userId: string; private userId: string;
private name: string; private name: string;
private email: string; private email: string;
private region: string; private region: string;
protected DialogResult = AddCreditDialogResult;
protected formGroup = new FormGroup({
method: new FormControl(PaymentMethodType.PayPal),
creditAmount: new FormControl(null, [Validators.required]),
});
constructor( constructor(
private dialogRef: DialogRef,
@Inject(DIALOG_DATA) protected data: AddCreditDialogData,
private accountService: AccountService, private accountService: AccountService,
private apiService: ApiService, private apiService: ApiService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
@@ -59,6 +63,7 @@ export class AddCreditComponent implements OnInit {
private logService: LogService, private logService: LogService,
private configService: ConfigService, private configService: ConfigService,
) { ) {
this.organizationId = data.organizationId;
const payPalConfig = process.env.PAYPAL_CONFIG as PayPalConfig; const payPalConfig = process.env.PAYPAL_CONFIG as PayPalConfig;
this.ppButtonFormAction = payPalConfig.buttonAction; this.ppButtonFormAction = payPalConfig.buttonAction;
this.ppButtonBusinessId = payPalConfig.businessId; this.ppButtonBusinessId = payPalConfig.businessId;
@@ -93,7 +98,18 @@ export class AddCreditComponent implements OnInit {
this.returnUrl = window.location.href; this.returnUrl = window.location.href;
} }
async submit() { get creditAmount() {
return this.formGroup.value.creditAmount;
}
set creditAmount(value: string) {
this.formGroup.get("creditAmount").setValue(value);
}
get method() {
return this.formGroup.value.method;
}
submit = async () => {
if (this.creditAmount == null || this.creditAmount === "") { if (this.creditAmount == null || this.creditAmount === "") {
return; return;
} }
@@ -104,33 +120,20 @@ export class AddCreditComponent implements OnInit {
return; return;
} }
if (this.method === PaymentMethodType.BitPay) { if (this.method === PaymentMethodType.BitPay) {
try { const req = new BitPayInvoiceRequest();
const req = new BitPayInvoiceRequest(); req.email = this.email;
req.email = this.email; req.name = this.name;
req.name = this.name; req.credit = true;
req.credit = true; req.amount = this.creditAmountNumber;
req.amount = this.creditAmountNumber; req.organizationId = this.organizationId;
req.organizationId = this.organizationId; req.userId = this.userId;
req.userId = this.userId; req.returnUrl = this.returnUrl;
req.returnUrl = this.returnUrl; const bitPayUrl: string = await this.apiService.postBitPayInvoice(req);
this.formPromise = this.apiService.postBitPayInvoice(req); this.platformUtilsService.launchUri(bitPayUrl);
const bitPayUrl: string = await this.formPromise;
this.platformUtilsService.launchUri(bitPayUrl);
} catch (e) {
this.logService.error(e);
}
return; return;
} }
try { this.dialogRef.close(AddCreditDialogResult.Added);
this.onAdded.emit(); };
} catch (e) {
this.logService.error(e);
}
}
cancel() {
this.onCanceled.emit();
}
formatAmount() { formatAmount() {
try { try {
@@ -160,3 +163,15 @@ export class AddCreditComponent implements OnInit {
return null; return null;
} }
} }
/**
* Strongly typed helper to open a AddCreditDialog
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
export function openAddCreditDialog(
dialogService: DialogService,
config: DialogConfig<AddCreditDialogData>,
) {
return dialogService.open<AddCreditDialogResult>(AddCreditDialogComponent, config);
}

View File

@@ -1,80 +0,0 @@
<form #form class="card" (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate>
<div class="card-body">
<button type="button" class="close" appA11yTitle="{{ 'cancel' | i18n }}" (click)="cancel()">
<span aria-hidden="true">&times;</span>
</button>
<h3 class="card-body-header">{{ "addCredit" | i18n }}</h3>
<div class="mb-4 text-lg" *ngIf="showOptions">
<div class="form-check form-check-inline">
<input
class="form-check-input"
type="radio"
name="Method"
id="credit-method-paypal"
[value]="paymentMethodType.PayPal"
[(ngModel)]="method"
/>
<label class="form-check-label" for="credit-method-paypal">
<i class="bwi bwi-fw bwi-paypal" aria-hidden="true"></i> PayPal</label
>
</div>
<div class="form-check form-check-inline">
<input
class="form-check-input"
type="radio"
name="Method"
id="credit-method-bitcoin"
[value]="paymentMethodType.BitPay"
[(ngModel)]="method"
/>
<label class="form-check-label" for="credit-method-bitcoin">
<i class="bwi bwi-fw bwi-bitcoin" aria-hidden="true"></i> Bitcoin</label
>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-4">
<label for="creditAmount">{{ "amount" | i18n }}</label>
<div class="input-group">
<div class="input-group-prepend"><span class="input-group-text">$USD</span></div>
<input
id="creditAmount"
class="form-control"
type="text"
name="CreditAmount"
[(ngModel)]="creditAmount"
(blur)="formatAmount()"
required
/>
</div>
</div>
</div>
<small class="form-text text-muted">{{ "creditDelayed" | i18n }}</small>
</div>
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading || ppLoading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "submit" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
</div>
</form>
<form #ppButtonForm action="{{ ppButtonFormAction }}" method="post" target="_top">
<input type="hidden" name="cmd" value="_xclick" />
<input type="hidden" name="business" value="{{ ppButtonBusinessId }}" />
<input type="hidden" name="button_subtype" value="services" />
<input type="hidden" name="no_note" value="1" />
<input type="hidden" name="no_shipping" value="1" />
<input type="hidden" name="rm" value="1" />
<input type="hidden" name="return" value="{{ returnUrl }}" />
<input type="hidden" name="cancel_return" value="{{ returnUrl }}" />
<input type="hidden" name="currency_code" value="USD" />
<input type="hidden" name="image_url" value="https://bitwarden.com/images/paypal-banner.png" />
<input type="hidden" name="bn" value="PP-BuyNowBF:btn_buynow_LG.gif:NonHosted" />
<input type="hidden" name="amount" value="{{ creditAmount }}" />
<input type="hidden" name="custom" value="{{ ppButtonCustomField }}" />
<input type="hidden" name="item_name" value="Bitwarden Account Credit" />
<input type="hidden" name="item_number" value="{{ subject }}" />
</form>

View File

@@ -3,7 +3,7 @@ import { NgModule } from "@angular/core";
import { HeaderModule } from "../../layouts/header/header.module"; import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared"; import { SharedModule } from "../../shared";
import { AddCreditComponent } from "./add-credit.component"; import { AddCreditDialogComponent } from "./add-credit-dialog.component";
import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog.component"; import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog.component";
import { AdjustStorageComponent } from "./adjust-storage.component"; import { AdjustStorageComponent } from "./adjust-storage.component";
import { BillingHistoryComponent } from "./billing-history.component"; import { BillingHistoryComponent } from "./billing-history.component";
@@ -17,7 +17,7 @@ import { UpdateLicenseComponent } from "./update-license.component";
@NgModule({ @NgModule({
imports: [SharedModule, PaymentComponent, TaxInfoComponent, HeaderModule], imports: [SharedModule, PaymentComponent, TaxInfoComponent, HeaderModule],
declarations: [ declarations: [
AddCreditComponent, AddCreditDialogComponent,
AdjustPaymentDialogComponent, AdjustPaymentDialogComponent,
AdjustStorageComponent, AdjustStorageComponent,
BillingHistoryComponent, BillingHistoryComponent,

View File

@@ -33,22 +33,9 @@
<strong>{{ creditOrBalance | currency: "$" }}</strong> <strong>{{ creditOrBalance | currency: "$" }}</strong>
</p> </p>
<p>{{ "creditAppliedDesc" | i18n }}</p> <p>{{ "creditAppliedDesc" | i18n }}</p>
<button <button type="button" bitButton buttonType="secondary" [bitAction]="addCredit">
type="button"
bitButton
buttonType="secondary"
(click)="addCredit()"
*ngIf="!showAddCredit"
>
{{ "addCredit" | i18n }} {{ "addCredit" | i18n }}
</button> </button>
<app-add-credit
[organizationId]="organizationId"
(onAdded)="closeAddCredit(true)"
(onCanceled)="closeAddCredit(false)"
*ngIf="showAddCredit"
>
</app-add-credit>
<h2 class="spaced-header">{{ "paymentMethod" | i18n }}</h2> <h2 class="spaced-header">{{ "paymentMethod" | i18n }}</h2>
<p *ngIf="!paymentSource">{{ "noPaymentMethod" | i18n }}</p> <p *ngIf="!paymentSource">{{ "noPaymentMethod" | i18n }}</p>
<ng-container *ngIf="paymentSource"> <ng-container *ngIf="paymentSource">

View File

@@ -15,6 +15,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService } from "@bitwarden/components"; import { DialogService } from "@bitwarden/components";
import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component";
import { import {
AdjustPaymentDialogResult, AdjustPaymentDialogResult,
openAdjustPaymentDialog, openAdjustPaymentDialog,
@@ -30,7 +31,6 @@ export class PaymentMethodComponent implements OnInit {
loading = false; loading = false;
firstLoaded = false; firstLoaded = false;
showAddCredit = false;
billing: BillingPaymentResponse; billing: BillingPaymentResponse;
org: OrganizationSubscriptionResponse; org: OrganizationSubscriptionResponse;
sub: SubscriptionResponse; sub: SubscriptionResponse;
@@ -111,18 +111,17 @@ export class PaymentMethodComponent implements OnInit {
this.loading = false; this.loading = false;
} }
addCredit() { addCredit = async () => {
this.showAddCredit = true; const dialogRef = openAddCreditDialog(this.dialogService, {
} data: {
organizationId: this.organizationId,
closeAddCredit(load: boolean) { },
this.showAddCredit = false; });
if (load) { const result = await lastValueFrom(dialogRef.closed);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. if (result === AddCreditDialogResult.Added) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises await this.load();
this.load();
} }
} };
changePayment = async () => { changePayment = async () => {
const dialogRef = openAdjustPaymentDialog(this.dialogService, { const dialogRef = openAdjustPaymentDialog(this.dialogService, {