diff --git a/jslib b/jslib index 20622db73c..8be95bfe57 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 20622db73c5c2a56777944bb06f32a21bf2e763f +Subproject commit 8be95bfe574a7ae2c8173921bbdfe82451436081 diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1fb0a0a5e0..1466208961 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -54,6 +54,7 @@ import { TwoFactorSetupComponent } from './settings/two-factor-setup.component'; import { TwoFactorU2fComponent } from './settings/two-factor-u2f.component'; import { TwoFactorVerifyComponent } from './settings/two-factor-verify.component'; import { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component'; +import { UpdateLicenseComponent } from './settings/update-license.component'; import { UserBillingComponent } from './settings/user-billing.component'; import { BreachReportComponent } from './tools/breach-report.component'; @@ -171,6 +172,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe'; TwoFactorU2fComponent, TwoFactorVerifyComponent, TwoFactorYubiKeyComponent, + UpdateLicenseComponent, UserBillingComponent, UserLayoutComponent, VaultComponent, diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index e29fd20ac8..83d0a9e766 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -114,8 +114,8 @@ export function initFactory(): Function { const isDev = platformUtilsService.isDev(); await apiService.setUrls({ base: isDev ? null : window.location.origin, - api: isDev ? 'https://api.bitwarden.com' : null, - identity: isDev ? 'https://identity.bitwarden.com' : null, + api: isDev ? 'http://localhost:4000' : null, + identity: isDev ? 'http://localhost:33656' : null, }); lockService.init(true); diff --git a/src/app/settings/premium.component.html b/src/app/settings/premium.component.html index 44ebb866eb..e7f5ea6365 100644 --- a/src/app/settings/premium.component.html +++ b/src/app/settings/premium.component.html @@ -22,9 +22,17 @@ {{'premiumSignUpFuture' | i18n}} -

{{'premiumPrice' | i18n : (premiumPrice | currency:'$')}}

+

{{'premiumPrice' | i18n : (premiumPrice | currency:'$')}}

+ + {{'purchasePremium' | i18n}} + -
+ +

{{'uploadLicenseFilePremium' | i18n}}

+ +
+

{{'addons' | i18n}}

diff --git a/src/app/settings/premium.component.ts b/src/app/settings/premium.component.ts index c9b753dfa5..966abbb46a 100644 --- a/src/app/settings/premium.component.ts +++ b/src/app/settings/premium.component.ts @@ -10,6 +10,7 @@ import { Angulartics2 } from 'angulartics2'; import { ApiService } from 'jslib/abstractions/api.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { PaymentComponent } from './payment.component'; @@ -21,6 +22,7 @@ export class PremiumComponent { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @Output() onPremiumPurchased = new EventEmitter(); + selfHosted = false; premiumPrice = 10; storageGbPrice = 4; additionalStorage = 0; @@ -28,7 +30,10 @@ export class PremiumComponent { formPromise: Promise; constructor(private apiService: ApiService, private i18nService: I18nService, - private analytics: Angulartics2, private toasterService: ToasterService) { } + private analytics: Angulartics2, private toasterService: ToasterService, + platformUtilsService: PlatformUtilsService) { + this.selfHosted = platformUtilsService.isSelfHost(); + } async submit() { try { @@ -44,6 +49,13 @@ export class PremiumComponent { } catch { } } + async finalizePremium() { + await this.apiService.refreshIdentityToken(); + this.analytics.eventTrack.next({ action: 'Signed Up Premium' }); + this.toasterService.popAsync('success', null, this.i18nService.t('premiumUpdated')); + this.onPremiumPurchased.emit(); + } + get additionalStorageTotal(): number { return this.storageGbPrice * this.additionalStorage; } @@ -51,11 +63,4 @@ export class PremiumComponent { get total(): number { return this.additionalStorageTotal + this.premiumPrice; } - - private async finalizePremium() { - await this.apiService.refreshIdentityToken(); - this.analytics.eventTrack.next({ action: 'Signed Up Premium' }); - this.toasterService.popAsync('success', null, this.i18nService.t('premiumUpdated')); - this.onPremiumPurchased.emit(); - } } diff --git a/src/app/settings/update-license.component.html b/src/app/settings/update-license.component.html new file mode 100644 index 0000000000..9cf14d908f --- /dev/null +++ b/src/app/settings/update-license.component.html @@ -0,0 +1,14 @@ + +
+ + + {{'licenseFileDesc' | i18n : (user ? 'bitwarden_premium_license.json' : 'bitwarden_organization_license.json')}} +
+ + + diff --git a/src/app/settings/update-license.component.ts b/src/app/settings/update-license.component.ts new file mode 100644 index 0000000000..7ce70a6907 --- /dev/null +++ b/src/app/settings/update-license.component.ts @@ -0,0 +1,68 @@ +import { + Component, + EventEmitter, + Input, + Output, +} from '@angular/core'; + +import { ToasterService } from 'angular2-toaster'; +import { Angulartics2 } from 'angulartics2'; + +import { ApiService } from 'jslib/abstractions/api.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { TokenService } from 'jslib/abstractions/token.service'; + +@Component({ + selector: 'app-update-license', + templateUrl: 'update-license.component.html', +}) +export class UpdateLicenseComponent { + @Input() create = true; + @Input() user = true; + @Output() onUpdated = new EventEmitter(); + @Output() onCanceled = new EventEmitter(); + + storageAdjustment = 0; + formPromise: Promise; + + constructor(private apiService: ApiService, private i18nService: I18nService, + private analytics: Angulartics2, private toasterService: ToasterService, + private tokenService: TokenService) { } + + async submit() { + const fileEl = document.getElementById('file') as HTMLInputElement; + const files = fileEl.files; + if (files == null || files.length === 0) { + this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('selectFile')); + return; + } + + try { + if (this.user) { + const fd = new FormData(); + fd.append('license', files[0]); + if (this.create) { + if (!this.tokenService.getEmailVerified()) { + this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('accountEmailMustBeVerified')); + return; + } + this.formPromise = this.apiService.postPremium(fd); + } else { + this.formPromise = this.apiService.postAccountLicense(fd); + } + } + await this.formPromise; + if (!this.create) { + this.analytics.eventTrack.next({ action: 'Updated License' }); + this.toasterService.popAsync('success', null, this.i18nService.t('updatedLicense')); + } + this.onUpdated.emit(); + } catch { } + } + + cancel() { + this.onCanceled.emit(); + } +} diff --git a/src/app/settings/user-billing.component.html b/src/app/settings/user-billing.component.html index 30969e35e1..601e8292e0 100644 --- a/src/app/settings/user-billing.component.html +++ b/src/app/settings/user-billing.component.html @@ -9,7 +9,7 @@ - {{'subscriptionCanceled' | i18n}} + {{'subscriptionCanceled' | i18n}}

{{'subscriptionPendingCanceled' | i18n}}

@@ -53,12 +54,20 @@
- - - {{'manageSubscription' | i18n}} - +
+ + + {{'manageSubscription' | i18n}} + +
+
+
+

{{'updateLicense' | i18n}}

+ +
+
diff --git a/src/app/settings/user-billing.component.ts b/src/app/settings/user-billing.component.ts index 8a0848b429..8f998c2133 100644 --- a/src/app/settings/user-billing.component.ts +++ b/src/app/settings/user-billing.component.ts @@ -26,6 +26,7 @@ export class UserBillingComponent implements OnInit { adjustStorageAdd = true; showAdjustStorage = false; showAdjustPayment = false; + showUpdateLicense = false; billing: BillingResponse; paymentMethodType = PaymentMethodType; selfHosted = false; @@ -110,6 +111,14 @@ export class UserBillingComponent implements OnInit { if (this.loading) { return; } + this.showUpdateLicense = true; + } + + closeUpdateLicense(load: boolean) { + this.showUpdateLicense = false; + if (load) { + this.load(); + } } adjustStorage(add: boolean) { diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 600dcf1b73..12c9abc2d8 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1331,6 +1331,9 @@ "updateLicense": { "message": "Update License" }, + "updatedLicense": { + "message": "Updated license" + }, "manageSubscription": { "message": "Manage Subscription" }, @@ -1413,5 +1416,26 @@ }, "updatedPaymentMethod": { "message": "Updated payment method." + }, + "purchasePremium": { + "message": "Purchase Premium" + }, + "licenseFile": { + "message": "License File" + }, + "licenseFileDesc": { + "message": "Your license file will be named something like $FILE_NAME$", + "placeholders": { + "file_name": { + "content": "$1", + "example": "bitwarden_premium_license.json" + } + } + }, + "uploadLicenseFilePremium": { + "message": "To upgrade your account to a premium membership you need to upload a valid license file." + }, + "accountEmailMustBeVerified": { + "message": "Your account's email address must be verified." } }