mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85cc2865b6 | ||
|
|
2dc74b26f3 | ||
|
|
3d0ed43920 | ||
|
|
dc54943a19 | ||
|
|
c6ae5368fe | ||
|
|
c947354517 | ||
|
|
076f01b65f | ||
|
|
e37292a276 | ||
|
|
7d76473580 | ||
|
|
8bafbbd2ff | ||
|
|
80c5dff5ad | ||
|
|
a4571a2617 | ||
|
|
18608a8b63 | ||
|
|
c9116ad7ab | ||
|
|
d982902986 |
2
jslib
2
jslib
Submodule jslib updated: 692d1ec201...255bd3962d
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bitwarden-web",
|
||||
"version": "2.11.0",
|
||||
"version": "2.12.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bitwarden-web",
|
||||
"version": "2.11.0",
|
||||
"version": "2.12.0",
|
||||
"scripts": {
|
||||
"sub:init": "git submodule update --init --recursive",
|
||||
"sub:update": "git submodule update --remote",
|
||||
|
||||
@@ -180,6 +180,7 @@ import localeEnGb from '@angular/common/locales/en-GB';
|
||||
import localeEs from '@angular/common/locales/es';
|
||||
import localeEt from '@angular/common/locales/et';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import localeHe from '@angular/common/locales/he';
|
||||
import localeIt from '@angular/common/locales/it';
|
||||
import localeJa from '@angular/common/locales/ja';
|
||||
import localeKo from '@angular/common/locales/ko';
|
||||
@@ -203,9 +204,10 @@ registerLocaleData(localeEnGb, 'en-GB');
|
||||
registerLocaleData(localeEs, 'es');
|
||||
registerLocaleData(localeEt, 'et');
|
||||
registerLocaleData(localeFr, 'fr');
|
||||
registerLocaleData(localeHe, 'he');
|
||||
registerLocaleData(localeIt, 'it');
|
||||
registerLocaleData(localeJa, 'ja');
|
||||
registerLocaleData(localeJa, 'ko');
|
||||
registerLocaleData(localeKo, 'ko');
|
||||
registerLocaleData(localeNb, 'nb');
|
||||
registerLocaleData(localeNl, 'nl');
|
||||
registerLocaleData(localePl, 'pl');
|
||||
|
||||
@@ -26,3 +26,4 @@
|
||||
</small>
|
||||
</div>
|
||||
</form>
|
||||
<app-payment [showMethods]="false"></app-payment>
|
||||
|
||||
@@ -3,8 +3,14 @@ import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
@@ -13,6 +19,8 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { SeatRequest } from 'jslib/models/request/seatRequest';
|
||||
|
||||
import { PaymentComponent } from '../../settings/payment.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-adjust-seats',
|
||||
templateUrl: 'adjust-seats.component.html',
|
||||
@@ -25,11 +33,14 @@ export class AdjustSeatsComponent {
|
||||
@Output() onAdjusted = new EventEmitter<number>();
|
||||
@Output() onCanceled = new EventEmitter();
|
||||
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
|
||||
seatAdjustment = 0;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private router: Router, private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -39,12 +50,32 @@ export class AdjustSeatsComponent {
|
||||
request.seatAdjustment *= -1;
|
||||
}
|
||||
|
||||
this.formPromise = this.apiService.postOrganizationSeat(this.organizationId, request);
|
||||
let paymentFailed = false;
|
||||
const action = async () => {
|
||||
const result = await this.apiService.postOrganizationSeat(this.organizationId, request);
|
||||
if (result != null && result.paymentIntentClientSecret != null) {
|
||||
try {
|
||||
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||
} catch {
|
||||
paymentFailed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.formPromise = action();
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.add ? 'Added Seats' : 'Removed Seats' });
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedSeats', request.seatAdjustment.toString()));
|
||||
this.onAdjusted.emit(this.seatAdjustment);
|
||||
if (paymentFailed) {
|
||||
this.toasterService.popAsync({
|
||||
body: this.i18nService.t('couldNotChargeCardPayInvoice'),
|
||||
type: 'warning',
|
||||
timeout: 10000,
|
||||
});
|
||||
this.router.navigate(['../billing'], { relativeTo: this.activatedRoute });
|
||||
} else {
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedSeats', request.seatAdjustment.toString()));
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { ExportComponent as BaseExportComponent } from '../../tools/export.component';
|
||||
|
||||
import { EventType } from 'jslib/enums/eventType';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -27,3 +27,4 @@
|
||||
</small>
|
||||
</div>
|
||||
</form>
|
||||
<app-payment [showMethods]="false"></app-payment>
|
||||
|
||||
@@ -3,8 +3,14 @@ import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
@@ -13,6 +19,10 @@ import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { StorageRequest } from 'jslib/models/request/storageRequest';
|
||||
|
||||
import { PaymentResponse } from 'jslib/models/response/paymentResponse';
|
||||
|
||||
import { PaymentComponent } from './payment.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-adjust-storage',
|
||||
templateUrl: 'adjust-storage.component.html',
|
||||
@@ -25,11 +35,14 @@ export class AdjustStorageComponent {
|
||||
@Output() onAdjusted = new EventEmitter<number>();
|
||||
@Output() onCanceled = new EventEmitter();
|
||||
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
|
||||
storageAdjustment = 0;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService) { }
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private router: Router, private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
@@ -39,16 +52,38 @@ export class AdjustStorageComponent {
|
||||
request.storageGbAdjustment *= -1;
|
||||
}
|
||||
|
||||
if (this.organizationId == null) {
|
||||
this.formPromise = this.apiService.postAccountStorage(request);
|
||||
} else {
|
||||
this.formPromise = this.apiService.postOrganizationStorage(this.organizationId, request);
|
||||
}
|
||||
let paymentFailed = false;
|
||||
const action = async () => {
|
||||
let response: Promise<PaymentResponse>;
|
||||
if (this.organizationId == null) {
|
||||
response = this.formPromise = this.apiService.postAccountStorage(request);
|
||||
} else {
|
||||
response = this.formPromise = this.apiService.postOrganizationStorage(this.organizationId, request);
|
||||
}
|
||||
const result = await response;
|
||||
if (result != null && result.paymentIntentClientSecret != null) {
|
||||
try {
|
||||
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||
} catch {
|
||||
paymentFailed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.formPromise = action();
|
||||
await this.formPromise;
|
||||
this.analytics.eventTrack.next({ action: this.add ? 'Added Storage' : 'Removed Storage' });
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedStorage', request.storageGbAdjustment.toString()));
|
||||
this.onAdjusted.emit(this.storageAdjustment);
|
||||
if (paymentFailed) {
|
||||
this.toasterService.popAsync({
|
||||
body: this.i18nService.t('couldNotChargeCardPayInvoice'),
|
||||
type: 'warning',
|
||||
timeout: 10000,
|
||||
});
|
||||
this.router.navigate(['../billing'], { relativeTo: this.activatedRoute });
|
||||
} else {
|
||||
this.toasterService.popAsync('success', null,
|
||||
this.i18nService.t('adjustedStorage', request.storageGbAdjustment.toString()));
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,11 @@ export class OptionsComponent implements OnInit {
|
||||
|
||||
const localeOptions: any[] = [];
|
||||
i18nService.supportedTranslationLocales.forEach((locale) => {
|
||||
localeOptions.push({ name: locale, value: locale });
|
||||
let name = locale;
|
||||
if (i18nService.localeNames.has(locale)) {
|
||||
name += (' - ' + i18nService.localeNames.get(locale));
|
||||
}
|
||||
localeOptions.push({ name: name, value: locale });
|
||||
});
|
||||
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
|
||||
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
|
||||
|
||||
@@ -203,6 +203,9 @@
|
||||
<h2 class="spaced-header mb-4">{{'paymentInformation' | i18n}}</h2>
|
||||
<app-payment [hideCredit]="true"></app-payment>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!createOrganization">
|
||||
<app-payment [showMethods]="false"></app-payment>
|
||||
</ng-container>
|
||||
<small class="text-muted font-italic mt-2 d-block" *ngIf="!createOrganization">
|
||||
{{'paymentCharged' | i18n : (interval | i18n) }}</small>
|
||||
</ng-container>
|
||||
|
||||
@@ -169,7 +169,10 @@ export class OrganizationPlansComponent {
|
||||
} else {
|
||||
request.planType = this.plans[this.plan].annualPlanType;
|
||||
}
|
||||
await this.apiService.postOrganizationUpgrade(this.organizationId, request);
|
||||
const result = await this.apiService.postOrganizationUpgrade(this.organizationId, request);
|
||||
if (!result.success && result.paymentIntentClientSecret != null) {
|
||||
await this.paymentComponent.handleStripeCardPayment(result.paymentIntentClientSecret, null);
|
||||
}
|
||||
orgId = this.organizationId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="mb-4 text-lg" *ngIf="showOptions">
|
||||
<div class="mb-4 text-lg" *ngIf="showOptions && showMethods">
|
||||
<div class="form-check form-check-inline mr-4">
|
||||
<input class="form-check-input" type="radio" name="Method" id="method-card" [value]="paymentMethodType.Card"
|
||||
[(ngModel)]="method" (change)="changeMethod()">
|
||||
@@ -24,7 +24,7 @@
|
||||
<i class="fa fa-fw fa-dollar"></i> {{'accountCredit' | i18n}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="method === paymentMethodType.Card">
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.Card">
|
||||
<div class="row">
|
||||
<div class="form-group col-4">
|
||||
<label for="stripe-card-number-element">{{'number' | i18n}}</label>
|
||||
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="method === paymentMethodType.BankAccount">
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.BankAccount">
|
||||
<app-callout type="warning" title="{{'verifyBankAccount' | i18n}}">
|
||||
{{'verifyBankAccountInitialDesc' | i18n}} {{'verifyBankAccountFailureWarning' | i18n}}
|
||||
</app-callout>
|
||||
@@ -81,13 +81,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="method === paymentMethodType.PayPal">
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.PayPal">
|
||||
<div class="mb-3">
|
||||
<div id="bt-dropin-container" class="mb-1"></div>
|
||||
<small class="text-muted">{{'paypalClickSubmit' | i18n}}</small>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="method === paymentMethodType.Credit">
|
||||
<ng-container *ngIf="showMethods && method === paymentMethodType.Credit">
|
||||
<app-callout type="note">
|
||||
{{'makeSureEnoughCredit' | i18n}}
|
||||
</app-callout>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
|
||||
import { PaymentMethodType } from 'jslib/enums/paymentMethodType';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { WebConstants } from '../../services/webConstants';
|
||||
@@ -34,6 +35,7 @@ const StripeElementClasses = {
|
||||
templateUrl: 'payment.component.html',
|
||||
})
|
||||
export class PaymentComponent implements OnInit {
|
||||
@Input() showMethods = true;
|
||||
@Input() showOptions = true;
|
||||
@Input() method = PaymentMethodType.Card;
|
||||
@Input() hideBank = false;
|
||||
@@ -60,7 +62,7 @@ export class PaymentComponent implements OnInit {
|
||||
private stripeCardExpiryElement: any = null;
|
||||
private stripeCardCvcElement: any = null;
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService) {
|
||||
constructor(private platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
|
||||
this.stripeScript = window.document.createElement('script');
|
||||
this.stripeScript.src = 'https://js.stripe.com/v3/';
|
||||
this.stripeScript.async = true;
|
||||
@@ -162,30 +164,60 @@ export class PaymentComponent implements OnInit {
|
||||
reject(err.message);
|
||||
});
|
||||
} else if (this.method === PaymentMethodType.Card || this.method === PaymentMethodType.BankAccount) {
|
||||
let sourceObj: any = null;
|
||||
let createObj: any = null;
|
||||
if (this.method === PaymentMethodType.Card) {
|
||||
sourceObj = this.stripeCardNumberElement;
|
||||
this.apiService.postSetupPayment().then((clientSecret) =>
|
||||
this.stripe.handleCardSetup(clientSecret, this.stripeCardNumberElement))
|
||||
.then((result: any) => {
|
||||
if (result.error) {
|
||||
reject(result.error.message);
|
||||
} else if (result.setupIntent && result.setupIntent.status === 'succeeded') {
|
||||
resolve([result.setupIntent.payment_method, this.method]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sourceObj = 'bank_account';
|
||||
createObj = this.bank;
|
||||
this.stripe.createToken('bank_account', this.bank).then((result: any) => {
|
||||
if (result.error) {
|
||||
reject(result.error.message);
|
||||
} else if (result.token && result.token.id != null) {
|
||||
resolve([result.token.id, this.method]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.stripe.createToken(sourceObj, createObj).then((result: any) => {
|
||||
if (result.error) {
|
||||
reject(result.error.message);
|
||||
} else if (result.token && result.token.id != null) {
|
||||
resolve([result.token.id, this.method]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleStripeCardPayment(clientSecret: string, successCallback: () => Promise<any>): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.showMethods && this.stripeCardNumberElement == null) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
const handleCardPayment = () => this.showMethods ?
|
||||
this.stripe.handleCardPayment(clientSecret, this.stripeCardNumberElement) :
|
||||
this.stripe.handleCardPayment(clientSecret);
|
||||
return handleCardPayment().then(async (result: any) => {
|
||||
if (result.error) {
|
||||
reject(result.error.message);
|
||||
} else if (result.paymentIntent && result.paymentIntent.status === 'succeeded') {
|
||||
if (successCallback != null) {
|
||||
await successCallback();
|
||||
}
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private setStripeElement() {
|
||||
window.setTimeout(() => {
|
||||
if (this.method === PaymentMethodType.Card) {
|
||||
if (this.showMethods && this.method === PaymentMethodType.Card) {
|
||||
if (this.stripeCardNumberElement == null) {
|
||||
this.stripeCardNumberElement = this.stripeElements.create('cardNumber', {
|
||||
style: StripeElementStyle,
|
||||
|
||||
@@ -84,8 +84,13 @@ export class PremiumComponent implements OnInit {
|
||||
}
|
||||
fd.append('additionalStorageGb', (this.additionalStorage || 0).toString());
|
||||
return this.apiService.postPremium(fd);
|
||||
}).then(() => {
|
||||
return this.finalizePremium();
|
||||
}).then((paymentResponse) => {
|
||||
if (!paymentResponse.success && paymentResponse.paymentIntentClientSecret != null) {
|
||||
return this.paymentComponent.handleStripeCardPayment(paymentResponse.paymentIntentClientSecret,
|
||||
() => this.finalizePremium());
|
||||
} else {
|
||||
return this.finalizePremium();
|
||||
}
|
||||
});
|
||||
}
|
||||
await this.formPromise;
|
||||
|
||||
@@ -232,6 +232,13 @@
|
||||
the QR code with your mobile device. Various CSV files will then be saved to your computer's
|
||||
downloads folder.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'securesafecsv'">
|
||||
Export your SecureSafe password safe to a CSV file with a comma delimiter.
|
||||
</ng-container>
|
||||
<ng-container *ngIf="format === 'logmeoncecsv'">
|
||||
Open the LogMeOnce browser extension, then navigate to "Open Menu" → "Export To" and
|
||||
select "CSV File" to save the CSV file.
|
||||
</ng-container>
|
||||
</app-callout>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
|
||||
@@ -1200,7 +1200,7 @@
|
||||
"message": "Web vault, desktop application, CLI, and all browser extensions on a device with a USB port that can accept your YubiKey."
|
||||
},
|
||||
"twoFactorYubikeySupportMobile": {
|
||||
"message": "Mobile apps on a device with NFC capabilities or a USB port that can accept your YubiKey."
|
||||
"message": "Mobile apps on a device with NFC capabilities or a data port that can accept your YubiKey."
|
||||
},
|
||||
"yubikeyX": {
|
||||
"message": "YubiKey $INDEX$",
|
||||
@@ -2620,7 +2620,7 @@
|
||||
"description": "A billing plan/package. For example: families, teams, enterprise, etc."
|
||||
},
|
||||
"changeBillingPlanUpgrade": {
|
||||
"message": "Upgrade your account to another plan be providing the information below. Please ensure that you have an active payment method added to the account.",
|
||||
"message": "Upgrade your account to another plan by providing the information below. Please ensure that you have an active payment method added to the account.",
|
||||
"description": "A billing plan/package. For example: families, teams, enterprise, etc."
|
||||
},
|
||||
"changeBillingPlanDesc": {
|
||||
@@ -2926,5 +2926,8 @@
|
||||
},
|
||||
"selectOneCollection": {
|
||||
"message": "You must select at least one collection."
|
||||
},
|
||||
"couldNotChargeCardPayInvoice": {
|
||||
"message": "We were not able to charge your card. Please view and pay the unpaid invoice listed below."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export class I18nService extends BaseI18nService {
|
||||
});
|
||||
|
||||
this.supportedTranslationLocales = [
|
||||
'en', 'ca', 'cs', 'da', 'de', 'en-GB', 'es', 'et', 'fr', 'it', 'ja', 'ko', 'nb', 'nl', 'pl',
|
||||
'en', 'ca', 'cs', 'da', 'de', 'en-GB', 'es', 'et', 'fr', 'he', 'it', 'ja', 'ko', 'nb', 'nl', 'pl',
|
||||
'pt-PT', 'pt-BR', 'ru', 'sk', 'sv', 'uk', 'zh-CN', 'zh-TW',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
|
||||
return 'UA-81915606-3';
|
||||
}
|
||||
|
||||
isViewOpen(): boolean {
|
||||
return false;
|
||||
isViewOpen(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
lockTimeout(): number {
|
||||
|
||||
Reference in New Issue
Block a user