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

Compare commits

...

15 Commits

Author SHA1 Message Date
Kyle Spearrin
85cc2865b6 show locale name for language selection 2019-09-06 09:33:35 -04:00
Kyle Spearrin
2dc74b26f3 version bump 2019-08-30 14:19:52 -04:00
Kyle Spearrin
3d0ed43920 update jslib 2019-08-29 10:02:39 -04:00
Kyle Spearrin
dc54943a19 added hebrew language 2019-08-29 07:20:36 -04:00
Kyle Spearrin
c6ae5368fe securesafe and logmeonce csv importers 2019-08-26 10:12:36 -04:00
Kyle Spearrin
c947354517 locale string typo 2019-08-23 07:58:42 -04:00
Kyle Spearrin
076f01b65f data port 2019-08-20 17:23:27 -04:00
Kyle Spearrin
e37292a276 isViewOpen returns promise 2019-08-20 13:47:58 -04:00
Kyle Spearrin
7d76473580 sca card failure warning 2019-08-10 19:51:49 -04:00
Kyle Spearrin
8bafbbd2ff handle seats and storage adjustment for sca 2019-08-10 13:43:47 -04:00
Kyle Spearrin
80c5dff5ad adjust storage with payment intent/method handling 2019-08-10 13:00:07 -04:00
Kyle Spearrin
a4571a2617 handleCardPayment for incomplete payments 2019-08-09 23:57:30 -04:00
Kyle Spearrin
18608a8b63 fix lint issue 2019-08-09 11:14:46 -04:00
Kyle Spearrin
c9116ad7ab update jslib 2019-08-03 19:59:25 -04:00
Kyle Spearrin
d982902986 update jslib 2019-08-02 09:51:11 -04:00
19 changed files with 174 additions and 46 deletions

2
jslib

Submodule jslib updated: 692d1ec201...255bd3962d

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "bitwarden-web",
"version": "2.11.0",
"version": "2.12.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -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",

View File

@@ -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');

View File

@@ -26,3 +26,4 @@
</small>
</div>
</form>
<app-payment [showMethods]="false"></app-payment>

View File

@@ -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 { }
}

View File

@@ -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({

View File

@@ -27,3 +27,4 @@
</small>
</div>
</form>
<app-payment [showMethods]="false"></app-payment>

View File

@@ -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 { }
}

View File

@@ -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 });

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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,

View File

@@ -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;

View File

@@ -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" &rarr; "Export To" and
select "CSV File" to save the CSV file.
</ng-container>
</app-callout>
<div class="row">
<div class="col-6">

View File

@@ -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."
}
}

View File

@@ -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',
];
}

View File

@@ -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 {