diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 42b252d0bd..d476181231 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -39,6 +39,7 @@ import { DeauthorizeSessionsComponent } from './settings/deauthorize-sessions.co import { DeleteAccountComponent } from './settings/delete-account.component'; import { DomainRulesComponent } from './settings/domain-rules.component'; import { OptionsComponent } from './settings/options.component'; +import { PaymentComponent } from './settings/payment.component'; import { PremiumComponent } from './settings/premium.component'; import { ProfileComponent } from './settings/profile.component'; import { PurgeVaultComponent } from './settings/purge-vault.component'; @@ -144,6 +145,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe'; OrganizationLayoutComponent, PasswordGeneratorComponent, PasswordGeneratorHistoryComponent, + PaymentComponent, PremiumComponent, ProfileComponent, PurgeVaultComponent, diff --git a/src/app/settings/domain-rules.component.html b/src/app/settings/domain-rules.component.html index 9d6d9676e0..8b994d8f5b 100644 --- a/src/app/settings/domain-rules.component.html +++ b/src/app/settings/domain-rules.component.html @@ -26,7 +26,7 @@ {{'save' | i18n}} -

{{'globalEqDomains' | i18n}}

+

{{'globalEqDomains' | i18n}}

diff --git a/src/app/settings/payment.component.html b/src/app/settings/payment.component.html new file mode 100644 index 0000000000..8c3498ed9c --- /dev/null +++ b/src/app/settings/payment.component.html @@ -0,0 +1,311 @@ +
+
+ + +
+
+ + +
+
+ +
+
+ + + {{'creditCardAllBrandsAccepted' | i18n}} +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ {{'paypalClickSubmit' | i18n}} +
+
diff --git a/src/app/settings/payment.component.ts b/src/app/settings/payment.component.ts new file mode 100644 index 0000000000..103e43205d --- /dev/null +++ b/src/app/settings/payment.component.ts @@ -0,0 +1,144 @@ +import { + Component, + OnInit, +} from '@angular/core'; + +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; + +const Keys = { + stripeTest: 'pk_test_KPoCfZXu7mznb9uSCPZ2JpTD', + stripeLive: 'pk_live_bpN0P37nMxrMQkcaHXtAybJk', + btSandbox: 'sandbox_r72q8jq6_9pnxkwm75f87sdc2', + btProduction: 'production_qfbsv8kc_njj2zjtyngtjmbjd', +}; + +@Component({ + selector: 'app-payment', + templateUrl: 'payment.component.html', +}) +export class PaymentComponent implements OnInit { + method = 'card'; + card: any = { + number: null, + exp_month: null, + exp_year: null, + address_country: '', + address_zip: null, + }; + cardExpMonthOptions: any[]; + cardExpYearOptions: any[]; + + private stripeScript: HTMLScriptElement; + private btScript: HTMLScriptElement; + private btInstance: any = null; + + constructor(i18nService: I18nService, private platformUtilsService: PlatformUtilsService) { + this.stripeScript = window.document.createElement('script'); + this.stripeScript.src = 'https://js.stripe.com/v2/'; + this.stripeScript.async = true; + this.stripeScript.onload = () => { + (window as any).Stripe.setPublishableKey( + this.platformUtilsService.isDev() ? Keys.stripeTest : Keys.stripeLive); + }; + this.btScript = window.document.createElement('script'); + this.btScript.src = 'https://js.braintreegateway.com/web/dropin/1.4.0/js/dropin.min.js'; + this.btScript.async = true; + + this.cardExpMonthOptions = [ + { name: '-- ' + i18nService.t('select') + ' --', value: null }, + { name: '01 - ' + i18nService.t('january'), value: '01' }, + { name: '02 - ' + i18nService.t('february'), value: '02' }, + { name: '03 - ' + i18nService.t('march'), value: '03' }, + { name: '04 - ' + i18nService.t('april'), value: '04' }, + { name: '05 - ' + i18nService.t('may'), value: '05' }, + { name: '06 - ' + i18nService.t('june'), value: '06' }, + { name: '07 - ' + i18nService.t('july'), value: '07' }, + { name: '08 - ' + i18nService.t('august'), value: '08' }, + { name: '09 - ' + i18nService.t('september'), value: '09' }, + { name: '10 - ' + i18nService.t('october'), value: '10' }, + { name: '11 - ' + i18nService.t('november'), value: '11' }, + { name: '12 - ' + i18nService.t('december'), value: '12' }, + ]; + + this.cardExpYearOptions = [ + { name: '-- ' + i18nService.t('select') + ' --', value: null }, + ]; + const year = (new Date()).getFullYear(); + for (let i = year; i < (year + 10); i++) { + this.cardExpYearOptions.push({ name: i.toString(), value: i.toString().slice(-2) }); + } + } + + ngOnInit() { + window.document.head.appendChild(this.stripeScript); + window.document.head.appendChild(this.btScript); + } + + ngOnDestroy() { + window.document.head.removeChild(this.stripeScript); + window.document.head.removeChild(this.btScript); + Array.from(window.document.querySelectorAll('iframe')).forEach((el) => { + if (el.src != null && el.src.indexOf('stripe') > -1) { + window.document.body.removeChild(el); + } + }); + } + + changeMethod() { + if (this.method !== 'paypal') { + this.btInstance = null; + return; + } + + window.setTimeout(() => { + (window as any).braintree.dropin.create({ + authorization: this.platformUtilsService.isDev() ? Keys.btSandbox : Keys.btProduction, + container: '#bt-dropin-container', + paymentOptionPriority: ['paypal'], + paypal: { + flow: 'vault', + buttonStyle: { + label: 'pay', + size: 'medium', + shape: 'pill', + color: 'blue', + }, + }, + }, (createErr: any, instance: any) => { + if (createErr != null) { + // tslint:disable-next-line + console.error(createErr); + return; + } + this.btInstance = instance; + }); + }, 250); + } + + createPaymentToken(): Promise { + return new Promise((resolve, reject) => { + if (this.method === 'paypal') { + this.btInstance.requestPaymentMethod().then((payload: any) => { + resolve(payload.nonce); + }).catch((err: any) => { + reject(err.message); + }); + } else { + (window as any).Stripe.card.createToken(this.card, (status: number, response: any) => { + if (status === 200 && response.id != null) { + resolve(response.id); + } else if (response.error != null) { + reject(response.error.message); + } else { + reject(); + } + }); + } + }); + } + + getCountry(): string { + return this.card.address_country; + } +} diff --git a/src/app/settings/premium.component.html b/src/app/settings/premium.component.html index 2492042ce2..802c831123 100644 --- a/src/app/settings/premium.component.html +++ b/src/app/settings/premium.component.html @@ -1,331 +1,21 @@
-

{{'addons' | i18n}}

+

{{'addons' | i18n}}

{{'additionalStorageDesc' | i18n : (storageGbPrice | currency:'USD')}}
-

{{'summary' | i18n}}

+

{{'summary' | i18n}}

{{'premiumMembership' | i18n}}: {{premiumPrice | currency:'USD':'$'}}
{{'additionalStorageGb' | i18n}}: {{additionalStorage || 0}} GB × {{storageGbPrice | currency:'USD'}} = {{additionalStorageTotal | currency:'USD':'$'}}
{{'total' | i18n}}: USD {{total | currency:'USD'}} /{{'year' | i18n}}
- {{'cardChargedAnnually' | i18n}} -

{{'paymentInformation' | i18n}}

-
-
- - -
-
- - -
-
- -
-
- - - {{'creditCardAllBrandsAccepted' | i18n}} -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- -
-
- {{'paypalClickSubmit' | i18n}} -
-
+ {{'paymentChargedAnnually' | i18n}} +

{{'paymentInformation' | i18n}}

+ + diff --git a/src/app/tools/breach-report.component.html b/src/app/tools/breach-report.component.html index 8f56e4d284..3bebc6d0b3 100644 --- a/src/app/tools/breach-report.component.html +++ b/src/app/tools/breach-report.component.html @@ -19,7 +19,7 @@
-

{{a.title}}

+

{{a.title}}

{{'compromisedData' | i18n}}:

    diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 52e547ec62..e90d563df2 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1132,6 +1132,9 @@ "twoFactorRecoveryYourCode": { "message": "Your Bitwarden two-step login recovery code" }, + "twoFactorRecoveryNoCode": { + "message": "You have not enabled any two-step login providers yet. After you have enabled a two-step login provider you can check back here for your recovery code." + }, "printCode": { "message": "Print Code", "description": "Print 2FA recovery code" @@ -1225,11 +1228,11 @@ "month": { "message": "month" }, - "cardChargedAnnually": { - "message": "Your card will be charged immediately and on a recurring basis each year. You may cancel at any time." + "paymentChargedAnnually": { + "message": "Your payment method will be charged immediately and on a recurring basis each year. You may cancel at any time." }, - "cardChargedMonthly": { - "message": "Your card will be charged immediately and on a recurring basis each month. You may cancel at any time." + "paymentChargedMonthly": { + "message": "Your payment method will be charged immediately and on a recurring basis each month. You may cancel at any time." }, "paymentInformation": { "message": "Payment Information" diff --git a/src/scss/styles.scss b/src/scss/styles.scss index ece25e1e38..b183cf54a9 100644 --- a/src/scss/styles.scss +++ b/src/scss/styles.scss @@ -25,6 +25,7 @@ $h4-font-size: 1rem; $h5-font-size: 1rem; $h6-font-size: 1rem; +$font-size-lg: 1.18rem; $code-font-size: 100%; $navbar-padding-y: .75rem; @@ -84,7 +85,7 @@ body { } } -.secondary-header { +.secondary-header, .spaced-header { margin-top: 4rem; } @@ -286,6 +287,10 @@ label:not(.form-check-label) { } } +.text-lg { + font-size: $font-size-lg; +} + app-vault-groupings { .card { #search { @@ -378,18 +383,6 @@ app-avatar { } } -app-two-factor-recovery { - code { - font-size: $font-size-lg; - } -} - -app-breach-report { - .list-group-item h3 { - font-size: $font-size-lg; - } -} - #duo-frame { background: url('../images/loading.svg') 0 0 no-repeat; height: 330px;