mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-24 21:41:33 +01:00
Merge branch 'main' into auth/pm-8111/browser-refresh-login-component
This commit is contained in:
commit
2d60073430
@ -1083,7 +1083,7 @@
|
||||
"message": "1 GB encrypted storage for file attachments."
|
||||
},
|
||||
"premiumSignUpEmergency": {
|
||||
"message": "Emergency access"
|
||||
"message": "Emergency access."
|
||||
},
|
||||
"premiumSignUpTwoStepOptions": {
|
||||
"message": "Proprietary two-step login options such as YubiKey and Duo."
|
||||
@ -1115,6 +1115,9 @@
|
||||
"premiumCurrentMemberThanks": {
|
||||
"message": "Thank you for supporting Bitwarden."
|
||||
},
|
||||
"premiumFeatures": {
|
||||
"message": "Upgrade to premium and receive:"
|
||||
},
|
||||
"premiumPrice": {
|
||||
"message": "All for just $PRICE$ /year!",
|
||||
"placeholders": {
|
||||
@ -1124,6 +1127,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"premiumPriceV2": {
|
||||
"message": "All for just $PRICE$ per year!",
|
||||
"placeholders": {
|
||||
"price": {
|
||||
"content": "$1",
|
||||
"example": "$10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"refreshComplete": {
|
||||
"message": "Refresh complete"
|
||||
},
|
||||
@ -3960,6 +3972,12 @@
|
||||
"autoFillOnPageLoad": {
|
||||
"message": "Autofill on page load?"
|
||||
},
|
||||
"cardExpiredTitle": {
|
||||
"message": "Expired card"
|
||||
},
|
||||
"cardExpiredMessage": {
|
||||
"message": "If you've renewed it, update the card's information"
|
||||
},
|
||||
"cardDetails": {
|
||||
"message": "Card details"
|
||||
},
|
||||
|
@ -9,13 +9,13 @@
|
||||
<h2 class="tw-font-bold">{{ "premiumFeatures" | i18n }}</h2>
|
||||
<bit-section>
|
||||
<bit-card>
|
||||
<div class="tw-flex tw-flex-col tw-p-3">
|
||||
<div class="tw-flex tw-flex-col tw-p-2">
|
||||
<ul class="tw-list-disc tw-pl-5 tw-space-y-2 tw-break-words">
|
||||
<li>
|
||||
{{ "ppremiumSignUpStorage" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
{{ "ppremiumSignUpTwoStepOptions" | i18n }}
|
||||
{{ "premiumSignUpTwoStepOptions" | i18n }}
|
||||
</li>
|
||||
<li>
|
||||
{{ "premiumSignUpEmergency" | i18n }}
|
||||
|
@ -74,7 +74,7 @@ export class PremiumV2Component extends BasePremiumComponent {
|
||||
const formattedPrice = this.platformUtilsService.isSafari()
|
||||
? thePrice.replace("$", "$$$")
|
||||
: thePrice;
|
||||
this.priceString = i18nService.t("premiumPrice", formattedPrice);
|
||||
this.priceString = i18nService.t("premiumPriceV2", formattedPrice);
|
||||
if (this.priceString.indexOf("%price%") > -1) {
|
||||
this.priceString = this.priceString.replace("%price%", thePrice);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
unauthGuardFn,
|
||||
} from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
|
||||
import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap";
|
||||
import {
|
||||
AnonLayoutWrapperComponent,
|
||||
AnonLayoutWrapperData,
|
||||
@ -91,7 +92,7 @@ import { SyncComponent } from "../vault/popup/settings/sync.component";
|
||||
import { VaultSettingsV2Component } from "../vault/popup/settings/vault-settings-v2.component";
|
||||
import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.component";
|
||||
|
||||
import { extensionRefreshRedirect, extensionRefreshSwap } from "./extension-refresh-route-utils";
|
||||
import { extensionRefreshRedirect } from "./extension-refresh-route-utils";
|
||||
import { debounceNavigationGuard } from "./services/debounce-navigation.service";
|
||||
import { TabsV2Component } from "./tabs-v2.component";
|
||||
import { TabsComponent } from "./tabs.component";
|
||||
|
@ -1,32 +1,9 @@
|
||||
import { inject, Type } from "@angular/core";
|
||||
import { Route, Router, Routes, UrlTree } from "@angular/router";
|
||||
import { inject } from "@angular/core";
|
||||
import { Router, UrlTree } from "@angular/router";
|
||||
|
||||
import { componentRouteSwap } from "@bitwarden/angular/utils/component-route-swap";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
/**
|
||||
* Helper function to swap between two components based on the ExtensionRefresh feature flag.
|
||||
* @param defaultComponent - The current non-refreshed component to render.
|
||||
* @param refreshedComponent - The new refreshed component to render.
|
||||
* @param options - The shared route options to apply to both components.
|
||||
*/
|
||||
export function extensionRefreshSwap(
|
||||
defaultComponent: Type<any>,
|
||||
refreshedComponent: Type<any>,
|
||||
options: Route,
|
||||
): Routes {
|
||||
return componentRouteSwap(
|
||||
defaultComponent,
|
||||
refreshedComponent,
|
||||
async () => {
|
||||
const configService = inject(ConfigService);
|
||||
return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to redirect to a new URL based on the ExtensionRefresh feature flag.
|
||||
* @param redirectUrl - The URL to redirect to if the ExtensionRefresh flag is enabled.
|
||||
|
@ -0,0 +1,192 @@
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherFormConfig, CipherFormConfigService, CipherFormMode } from "@bitwarden/vault";
|
||||
|
||||
import { BrowserFido2UserInterfaceSession } from "../../../../../autofill/fido2/services/browser-fido2-user-interface.service";
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
|
||||
import { PopupCloseWarningService } from "../../../../../popup/services/popup-close-warning.service";
|
||||
|
||||
import { AddEditV2Component } from "./add-edit-v2.component";
|
||||
|
||||
// 'qrcode-parser' is used by `BrowserTotpCaptureService` but is an es6 module that jest can't compile.
|
||||
// Mock the entire module here to prevent jest from throwing an error. I wasn't able to find a way to mock the
|
||||
// `BrowserTotpCaptureService` where jest would not load the file in the first place.
|
||||
jest.mock("qrcode-parser", () => {});
|
||||
|
||||
describe("AddEditV2Component", () => {
|
||||
let component: AddEditV2Component;
|
||||
let fixture: ComponentFixture<AddEditV2Component>;
|
||||
|
||||
const buildConfigResponse = { originalCipher: {} } as CipherFormConfig;
|
||||
const buildConfig = jest.fn((mode: CipherFormMode) =>
|
||||
Promise.resolve({ mode, ...buildConfigResponse }),
|
||||
);
|
||||
const queryParams$ = new BehaviorSubject({});
|
||||
const disable = jest.fn();
|
||||
const navigate = jest.fn();
|
||||
const back = jest.fn().mockResolvedValue(null);
|
||||
|
||||
beforeEach(async () => {
|
||||
buildConfig.mockClear();
|
||||
disable.mockClear();
|
||||
navigate.mockClear();
|
||||
back.mockClear();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AddEditV2Component],
|
||||
providers: [
|
||||
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
||||
{ provide: ConfigService, useValue: mock<ConfigService>() },
|
||||
{ provide: PopupRouterCacheService, useValue: { back } },
|
||||
{ provide: PopupCloseWarningService, useValue: { disable } },
|
||||
{ provide: Router, useValue: { navigate } },
|
||||
{ provide: ActivatedRoute, useValue: { queryParams: queryParams$ } },
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
],
|
||||
})
|
||||
.overrideProvider(CipherFormConfigService, {
|
||||
useValue: {
|
||||
buildConfig,
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AddEditV2Component);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe("query params", () => {
|
||||
describe("mode", () => {
|
||||
it("sets mode to `add` when no `cipherId` is provided", fakeAsync(() => {
|
||||
queryParams$.next({});
|
||||
|
||||
tick();
|
||||
|
||||
expect(buildConfig.mock.lastCall[0]).toBe("add");
|
||||
expect(component.config.mode).toBe("add");
|
||||
}));
|
||||
|
||||
it("sets mode to `edit` when `params.clone` is not provided", fakeAsync(() => {
|
||||
queryParams$.next({ cipherId: "222-333-444-5555", clone: "true" });
|
||||
|
||||
tick();
|
||||
|
||||
expect(buildConfig.mock.lastCall[0]).toBe("clone");
|
||||
expect(component.config.mode).toBe("clone");
|
||||
}));
|
||||
|
||||
it("sets mode to `edit` when `params.clone` is not provided", fakeAsync(() => {
|
||||
buildConfigResponse.originalCipher = { edit: true } as Cipher;
|
||||
queryParams$.next({ cipherId: "222-333-444-5555" });
|
||||
|
||||
tick();
|
||||
|
||||
expect(buildConfig.mock.lastCall[0]).toBe("edit");
|
||||
expect(component.config.mode).toBe("edit");
|
||||
}));
|
||||
|
||||
it("sets mode to `partial-edit` when `config.originalCipher.edit` is false", fakeAsync(() => {
|
||||
buildConfigResponse.originalCipher = { edit: false } as Cipher;
|
||||
queryParams$.next({ cipherId: "222-333-444-5555" });
|
||||
|
||||
tick();
|
||||
|
||||
expect(buildConfig.mock.lastCall[0]).toBe("edit");
|
||||
expect(component.config.mode).toBe("partial-edit");
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("onCipherSaved", () => {
|
||||
it("disables warning when in popout", async () => {
|
||||
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValueOnce(true);
|
||||
|
||||
await component.onCipherSaved({ id: "123-456-789" } as CipherView);
|
||||
|
||||
expect(disable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls `confirmNewCredentialResponse` when in fido2 popout", async () => {
|
||||
// @ts-expect-error - `inFido2PopoutWindow` is a private getter, mock the response here
|
||||
// for the test rather than setting up the dependencies.
|
||||
jest.spyOn(component, "inFido2PopoutWindow", "get").mockReturnValueOnce(true);
|
||||
|
||||
await component.onCipherSaved({ id: "123-456-789" } as CipherView);
|
||||
|
||||
expect(BrowserPopupUtils.inPopout).toHaveBeenCalled();
|
||||
expect(navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes single action popout", async () => {
|
||||
jest.spyOn(BrowserPopupUtils, "inSingleActionPopout").mockReturnValueOnce(true);
|
||||
jest.spyOn(BrowserPopupUtils, "closeSingleActionPopout").mockResolvedValue();
|
||||
|
||||
await component.onCipherSaved({ id: "123-456-789" } as CipherView);
|
||||
|
||||
expect(BrowserPopupUtils.closeSingleActionPopout).toHaveBeenCalled();
|
||||
expect(navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("navigates to view-cipher for new ciphers", async () => {
|
||||
component.config.mode = "add";
|
||||
|
||||
await component.onCipherSaved({ id: "123-456-789" } as CipherView);
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith(["/view-cipher"], {
|
||||
replaceUrl: true,
|
||||
queryParams: { cipherId: "123-456-789" },
|
||||
});
|
||||
expect(back).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("navigates to view-cipher for edit ciphers", async () => {
|
||||
component.config.mode = "edit";
|
||||
|
||||
await component.onCipherSaved({ id: "123-456-789" } as CipherView);
|
||||
|
||||
expect(navigate).not.toHaveBeenCalled();
|
||||
expect(back).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleBackButton", () => {
|
||||
it("disables warning and aborts fido2 popout", async () => {
|
||||
// @ts-expect-error - `inFido2PopoutWindow` is a private getter, mock the response here
|
||||
// for the test rather than setting up the dependencies.
|
||||
jest.spyOn(component, "inFido2PopoutWindow", "get").mockReturnValueOnce(true);
|
||||
jest.spyOn(BrowserFido2UserInterfaceSession, "abortPopout");
|
||||
|
||||
await component.handleBackButton();
|
||||
|
||||
expect(disable).toHaveBeenCalled();
|
||||
expect(BrowserFido2UserInterfaceSession.abortPopout).toHaveBeenCalled();
|
||||
expect(back).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes single action popout", async () => {
|
||||
jest.spyOn(BrowserPopupUtils, "inSingleActionPopout").mockReturnValueOnce(true);
|
||||
jest.spyOn(BrowserPopupUtils, "closeSingleActionPopout").mockResolvedValue();
|
||||
|
||||
await component.handleBackButton();
|
||||
|
||||
expect(BrowserPopupUtils.closeSingleActionPopout).toHaveBeenCalled();
|
||||
expect(back).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("navigates the user backwards", async () => {
|
||||
await component.handleBackButton();
|
||||
|
||||
expect(back).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@ -151,10 +151,10 @@ export class AddEditV2Component implements OnInit {
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private popupRouterCacheService: PopupRouterCacheService,
|
||||
private i18nService: I18nService,
|
||||
private addEditFormConfigService: CipherFormConfigService,
|
||||
private popupCloseWarningService: PopupCloseWarningService,
|
||||
private popupRouterCacheService: PopupRouterCacheService,
|
||||
private router: Router,
|
||||
) {
|
||||
this.subscribeToParams();
|
||||
@ -183,11 +183,7 @@ export class AddEditV2Component implements OnInit {
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates to previous view or view-cipher path
|
||||
* depending on the history length.
|
||||
*
|
||||
* This can happen when history is lost due to the extension being
|
||||
* forced into a popout window.
|
||||
* Handle back button
|
||||
*/
|
||||
async handleBackButton() {
|
||||
if (this.inFido2PopoutWindow) {
|
||||
@ -223,10 +219,18 @@ export class AddEditV2Component implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.router.navigate(["/view-cipher"], {
|
||||
replaceUrl: true,
|
||||
queryParams: { cipherId: cipher.id },
|
||||
});
|
||||
// When the cipher is in edit / partial edit, the previous page was the view-cipher page.
|
||||
// In the case of creating a new cipher, the user should go view-cipher page but we need to also
|
||||
// remove it from the history stack. This avoids the user having to click back twice on the
|
||||
// view-cipher page.
|
||||
if (this.config.mode === "edit" || this.config.mode === "partial-edit") {
|
||||
await this.popupRouterCacheService.back();
|
||||
} else {
|
||||
await this.router.navigate(["/view-cipher"], {
|
||||
replaceUrl: true,
|
||||
queryParams: { cipherId: cipher.id },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
subscribeToParams(): void {
|
||||
|
@ -14,6 +14,7 @@ import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@ -54,6 +55,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
|
||||
ssoLoginService: SsoLoginServiceAbstraction,
|
||||
dialogService: DialogService,
|
||||
kdfConfigService: KdfConfigService,
|
||||
encryptService: EncryptService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
@ -76,6 +78,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On
|
||||
ssoLoginService,
|
||||
dialogService,
|
||||
kdfConfigService,
|
||||
encryptService,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,17 +2,7 @@ import { DOCUMENT } from "@angular/common";
|
||||
import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { NavigationEnd, Router } from "@angular/router";
|
||||
import * as jq from "jquery";
|
||||
import {
|
||||
Subject,
|
||||
combineLatest,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
switchMap,
|
||||
takeUntil,
|
||||
timeout,
|
||||
timer,
|
||||
} from "rxjs";
|
||||
import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs";
|
||||
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
@ -25,8 +15,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
@ -58,7 +46,6 @@ import {
|
||||
|
||||
const BroadcasterSubscriptionId = "AppComponent";
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
const PaymentMethodWarningsRefresh = 60000; // 1 Minute
|
||||
|
||||
@Component({
|
||||
selector: "app-root",
|
||||
@ -69,7 +56,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private idleTimer: number = null;
|
||||
private isIdle = false;
|
||||
private destroy$ = new Subject<void>();
|
||||
private paymentMethodWarningsRefresh$ = timer(0, PaymentMethodWarningsRefresh);
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private document: Document,
|
||||
@ -98,7 +84,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private dialogService: DialogService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private stateEventRunnerService: StateEventRunnerService,
|
||||
private paymentMethodWarningService: PaymentMethodWarningService,
|
||||
private organizationService: InternalOrganizationServiceAbstraction,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
@ -252,25 +237,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
new DisableSendPolicy(),
|
||||
new SendOptionsPolicy(),
|
||||
]);
|
||||
|
||||
combineLatest([
|
||||
this.configService.getFeatureFlag$(FeatureFlag.ShowPaymentMethodWarningBanners),
|
||||
this.paymentMethodWarningsRefresh$,
|
||||
])
|
||||
.pipe(
|
||||
filter(([showPaymentMethodWarningBanners]) => showPaymentMethodWarningBanners),
|
||||
switchMap(() => this.organizationService.memberOrganizations$),
|
||||
switchMap(
|
||||
async (organizations) =>
|
||||
await Promise.all(
|
||||
organizations.map((organization) =>
|
||||
this.paymentMethodWarningService.update(organization.id),
|
||||
),
|
||||
),
|
||||
),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -328,7 +294,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.folderService.clear(userId),
|
||||
this.collectionService.clear(userId),
|
||||
this.biometricStateService.logout(userId),
|
||||
this.paymentMethodWarningService.clear(),
|
||||
]);
|
||||
|
||||
await this.stateEventRunnerService.handleEvent("logout", userId);
|
||||
|
@ -14,6 +14,7 @@ import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.res
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { BillingSharedModule, PaymentComponent, TaxInfoComponent } from "../../shared";
|
||||
|
||||
@ -75,6 +76,7 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
private messagingService: MessagingService,
|
||||
private organizationBillingService: OrganizationBillingService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@ -96,11 +98,11 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
const organizationId = await this.formPromise;
|
||||
const planDescription = this.getPlanDescription();
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("organizationCreated"),
|
||||
this.i18nService.t("organizationReadyToGo"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: this.i18nService.t("organizationCreated"),
|
||||
message: this.i18nService.t("organizationReadyToGo"),
|
||||
});
|
||||
|
||||
this.organizationCreated.emit({
|
||||
organizationId,
|
||||
|
@ -11,6 +11,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { PaymentComponent, TaxInfoComponent } from "../shared";
|
||||
|
||||
@ -46,6 +47,7 @@ export class PremiumComponent implements OnInit {
|
||||
private syncService: SyncService,
|
||||
private environmentService: EnvironmentService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
|
||||
@ -75,11 +77,11 @@ export class PremiumComponent implements OnInit {
|
||||
this.addonForm.markAllAsTouched();
|
||||
if (this.selfHosted) {
|
||||
if (this.licenseFile == null) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("selectFile"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("selectFile"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -87,11 +89,11 @@ export class PremiumComponent implements OnInit {
|
||||
if (this.selfHosted) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
if (!this.tokenService.getEmailVerified()) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("verifyEmailFirst"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("verifyEmailFirst"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -130,7 +132,11 @@ export class PremiumComponent implements OnInit {
|
||||
async finalizePremium() {
|
||||
await this.apiService.refreshIdentityToken();
|
||||
await this.syncService.fullSync(true);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("premiumUpdated"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("premiumUpdated"),
|
||||
});
|
||||
await this.router.navigate(["/settings/subscription/user-subscription"]);
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
AdjustStorageDialogResult,
|
||||
@ -48,6 +48,7 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
private dialogService: DialogService,
|
||||
private environmentService: EnvironmentService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
@ -94,7 +95,11 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
try {
|
||||
this.reinstatePromise = this.apiService.postReinstatePremium();
|
||||
await this.reinstatePromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("reinstated"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("reinstated"),
|
||||
});
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.load();
|
||||
|
@ -6,6 +6,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
|
||||
import { OrganizationSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-subscription-update.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "app-adjust-subscription",
|
||||
@ -33,6 +34,7 @@ export class AdjustSubscription implements OnInit, OnDestroy {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private formBuilder: FormBuilder,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -76,7 +78,11 @@ export class AdjustSubscription implements OnInit, OnDestroy {
|
||||
);
|
||||
await this.organizationApiService.updatePasswordManagerSeats(this.organizationId, request);
|
||||
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("subscriptionUpdated"),
|
||||
});
|
||||
|
||||
this.onAdjusted.emit();
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
export interface BillingSyncApiModalData {
|
||||
organizationId: string;
|
||||
@ -43,6 +43,7 @@ export class BillingSyncApiKeyComponent {
|
||||
private i18nService: I18nService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private logService: LogService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
this.organizationId = data.organizationId;
|
||||
this.hasBillingToken = data.hasBillingToken;
|
||||
@ -67,11 +68,11 @@ export class BillingSyncApiKeyComponent {
|
||||
});
|
||||
await this.load(response);
|
||||
this.showRotateScreen = false;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("billingSyncApiKeyRotated"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("billingSyncApiKeyRotated"),
|
||||
});
|
||||
} else {
|
||||
const response = await request.then((request) => {
|
||||
return this.organizationApiService.getOrCreateApiKey(this.organizationId, request);
|
||||
|
@ -37,6 +37,7 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { OrgKey } from "@bitwarden/common/types/key";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module";
|
||||
import { BillingSharedModule, secretsManagerSubscribeFormFactory } from "../shared";
|
||||
@ -150,6 +151,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
private formBuilder: FormBuilder,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private providerApiService: ProviderApiServiceAbstraction,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
@ -582,18 +584,18 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
orgId = await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]);
|
||||
}
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("organizationCreated"),
|
||||
this.i18nService.t("organizationReadyToGo"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: this.i18nService.t("organizationCreated"),
|
||||
message: this.i18nService.t("organizationReadyToGo"),
|
||||
});
|
||||
} else {
|
||||
orgId = await this.updateOrganization(orgId);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("organizationUpgraded"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("organizationUpgraded"),
|
||||
});
|
||||
}
|
||||
|
||||
await this.apiService.refreshIdentityToken();
|
||||
|
@ -16,7 +16,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
AdjustStorageDialogResult,
|
||||
@ -82,6 +82,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
private dialogService: DialogService,
|
||||
private configService: ConfigService,
|
||||
private providerService: ProviderService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -378,7 +379,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
|
||||
try {
|
||||
await this.organizationApiService.reinstate(this.organizationId);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("reinstated"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("reinstated"),
|
||||
});
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.load();
|
||||
@ -475,11 +480,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
|
||||
try {
|
||||
await this.apiService.deleteRemoveSponsorship(this.organizationId);
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("removeSponsorshipSuccess"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("removeSponsorshipSuccess"),
|
||||
});
|
||||
await this.load();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
|
@ -16,7 +16,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { BillingSyncKeyComponent } from "./billing-sync-key.component";
|
||||
|
||||
@ -84,6 +84,7 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest
|
||||
private i18nService: I18nService,
|
||||
private environmentService: EnvironmentService,
|
||||
private dialogService: DialogService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -169,7 +170,11 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest
|
||||
this.load();
|
||||
await this.loadOrganizationConnection();
|
||||
this.messagingService.send("updatedOrgLicense");
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("licenseSyncSuccess"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("licenseSyncSuccess"),
|
||||
});
|
||||
};
|
||||
|
||||
get billingSyncSetUp() {
|
||||
|
@ -6,6 +6,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
|
||||
import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
export interface SecretsManagerSubscriptionOptions {
|
||||
interval: "year" | "month";
|
||||
@ -100,6 +101,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -158,11 +160,11 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
|
||||
request,
|
||||
);
|
||||
|
||||
await this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("subscriptionUpdated"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("subscriptionUpdated"),
|
||||
});
|
||||
|
||||
this.onAdjusted.emit();
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/respon
|
||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { secretsManagerSubscribeFormFactory } from "../shared";
|
||||
|
||||
@ -33,6 +34,7 @@ export class SecretsManagerSubscribeStandaloneComponent {
|
||||
private i18nService: I18nService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationService: InternalOrganizationServiceAbstraction,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
submit = async () => {
|
||||
@ -60,11 +62,11 @@ export class SecretsManagerSubscribeStandaloneComponent {
|
||||
*/
|
||||
await this.apiService.refreshIdentityToken();
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("subscribedToSecretsManager"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("subscribedToSecretsManager"),
|
||||
});
|
||||
|
||||
this.onSubscribe.emit();
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ import { PlanSponsorshipType } from "@bitwarden/common/billing/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
interface RequestSponsorshipForm {
|
||||
selectedSponsorshipOrgId: FormControl<string>;
|
||||
@ -51,6 +52,7 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
|
||||
private organizationService: OrganizationService,
|
||||
private formBuilder: FormBuilder,
|
||||
private accountService: AccountService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
this.sponsorshipForm = this.formBuilder.group<RequestSponsorshipForm>({
|
||||
selectedSponsorshipOrgId: new FormControl("", {
|
||||
@ -118,7 +120,11 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("sponsorshipCreated"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("sponsorshipCreated"),
|
||||
});
|
||||
this.formPromise = null;
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
|
@ -7,7 +7,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "[sponsoring-org-row]",
|
||||
@ -30,6 +30,7 @@ export class SponsoringOrgRowComponent implements OnInit {
|
||||
private logService: LogService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private dialogService: DialogService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -53,7 +54,11 @@ export class SponsoringOrgRowComponent implements OnInit {
|
||||
|
||||
async resendEmail() {
|
||||
await this.apiService.postResendSponsorshipOffer(this.sponsoringOrg.id);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("emailSent"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("emailSent"),
|
||||
});
|
||||
}
|
||||
|
||||
get isSentAwaitingSync() {
|
||||
@ -73,7 +78,11 @@ export class SponsoringOrgRowComponent implements OnInit {
|
||||
}
|
||||
|
||||
await this.apiService.deleteRevokeSponsorship(this.sponsoringOrg.id);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("reclaimedFreePlan"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("reclaimedFreePlan"),
|
||||
});
|
||||
this.sponsorshipRemoved.emit();
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, ViewChild } from "@angular/core";
|
||||
import { FormGroup } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction";
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
@ -46,7 +43,6 @@ export class AdjustPaymentDialogComponent {
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private paymentMethodWarningService: PaymentMethodWarningService,
|
||||
private configService: ConfigService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
@ -78,12 +74,6 @@ export class AdjustPaymentDialogComponent {
|
||||
}
|
||||
});
|
||||
await response;
|
||||
const showPaymentMethodWarningBanners = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.ShowPaymentMethodWarningBanners),
|
||||
);
|
||||
if (this.organizationId && showPaymentMethodWarningBanners) {
|
||||
await this.paymentMethodWarningService.removeSubscriptionRisk(this.organizationId);
|
||||
}
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
|
@ -10,7 +10,7 @@ import { StorageRequest } from "@bitwarden/common/models/request/storage.request
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { PaymentComponent } from "./payment.component";
|
||||
|
||||
@ -56,6 +56,7 @@ export class AdjustStorageComponent {
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private logService: LogService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
this.storageGbPrice = data.storageGbPrice;
|
||||
this.add = data.add;
|
||||
@ -93,21 +94,21 @@ export class AdjustStorageComponent {
|
||||
await action();
|
||||
this.dialogRef.close(AdjustStorageDialogResult.Adjusted);
|
||||
if (paymentFailed) {
|
||||
this.platformUtilsService.showToast(
|
||||
"warning",
|
||||
null,
|
||||
this.i18nService.t("couldNotChargeCardPayInvoice"),
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "warning",
|
||||
title: null,
|
||||
message: this.i18nService.t("couldNotChargeCardPayInvoice"),
|
||||
timeout: 10000,
|
||||
});
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.router.navigate(["../billing"], { relativeTo: this.activatedRoute });
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3,4 +3,3 @@ export * from "./payment-method.component";
|
||||
export * from "./payment.component";
|
||||
export * from "./sm-subscribe.component";
|
||||
export * from "./tax-info.component";
|
||||
export * from "./payment-method-warnings/payment-method-warnings.module";
|
||||
|
@ -5,7 +5,7 @@ import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
type UserOffboardingParams = {
|
||||
type: "User";
|
||||
@ -88,6 +88,7 @@ export class OffboardingSurveyComponent {
|
||||
private billingApiService: BillingApiService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
submit = async () => {
|
||||
@ -106,11 +107,11 @@ export class OffboardingSurveyComponent {
|
||||
? await this.billingApiService.cancelOrganizationSubscription(this.dialogParams.id, request)
|
||||
: await this.billingApiService.cancelPremiumUserSubscription(request);
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("canceledSubscription"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("canceledSubscription"),
|
||||
});
|
||||
|
||||
this.dialogRef.close(this.ResultType.Submitted);
|
||||
};
|
||||
|
@ -1,15 +0,0 @@
|
||||
<ng-container *ngFor="let warning of warnings$ | async">
|
||||
<bit-banner
|
||||
class="-tw-m-6 tw-flex tw-flex-col tw-pb-6"
|
||||
bannerType="warning"
|
||||
(onClose)="closeWarning(warning.organizationId)"
|
||||
>
|
||||
{{ "maintainYourSubscription" | i18n: warning.organizationName }}
|
||||
<a
|
||||
bitLink
|
||||
linkType="contrast"
|
||||
[routerLink]="['/organizations', warning.organizationId, 'billing', 'payment-method']"
|
||||
>{{ "addAPaymentMethod" | i18n }}</a
|
||||
>.
|
||||
</bit-banner>
|
||||
</ng-container>
|
@ -1,33 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { map, Observable } from "rxjs";
|
||||
|
||||
import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction";
|
||||
|
||||
type Warning = {
|
||||
organizationId: string;
|
||||
organizationName: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: "app-payment-method-warnings",
|
||||
templateUrl: "payment-method-warnings.component.html",
|
||||
})
|
||||
export class PaymentMethodWarningsComponent {
|
||||
constructor(private paymentMethodWarningService: PaymentMethodWarningService) {}
|
||||
|
||||
protected warnings$: Observable<Warning[]> =
|
||||
this.paymentMethodWarningService.paymentMethodWarnings$.pipe(
|
||||
map((warnings) =>
|
||||
Object.entries(warnings ?? [])
|
||||
.filter(([_, warning]) => warning.risksSubscriptionFailure && !warning.acknowledged)
|
||||
.map(([organizationId, { organizationName }]) => ({
|
||||
organizationId,
|
||||
organizationName,
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
protected async closeWarning(organizationId: string): Promise<void> {
|
||||
await this.paymentMethodWarningService.acknowledge(organizationId);
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { BannerModule } from "@bitwarden/components";
|
||||
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { PaymentMethodWarningsComponent } from "./payment-method-warnings.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [BannerModule, SharedModule],
|
||||
declarations: [PaymentMethodWarningsComponent],
|
||||
exports: [PaymentMethodWarningsComponent],
|
||||
})
|
||||
export class PaymentMethodWarningsModule {}
|
@ -13,7 +13,7 @@ import { VerifyBankRequest } from "@bitwarden/common/models/request/verify-bank.
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component";
|
||||
import {
|
||||
@ -63,6 +63,7 @@ export class PaymentMethodComponent implements OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private formBuilder: FormBuilder,
|
||||
private dialogService: DialogService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -144,13 +145,21 @@ export class PaymentMethodComponent implements OnInit {
|
||||
request.amount1 = this.verifyBankForm.value.amount1;
|
||||
request.amount2 = this.verifyBankForm.value.amount2;
|
||||
await this.organizationApiService.verifyBank(this.organizationId, request);
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("verifiedBankAccount"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("verifiedBankAccount"),
|
||||
});
|
||||
await this.load();
|
||||
};
|
||||
|
||||
submitTaxInfo = async () => {
|
||||
await this.taxInfo.submitTaxInfo();
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("taxInfoUpdated"));
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("taxInfoUpdated"),
|
||||
});
|
||||
};
|
||||
|
||||
get isCreditBalance() {
|
||||
|
@ -13,7 +13,7 @@
|
||||
</bit-select>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div [ngClass]="trialFlow ? 'tw-col-span-5' : 'tw-col-span-3'">
|
||||
<div [ngClass]="trialFlow ? 'tw-col-span-5' : 'tw-col-span-4'">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "zipPostalCode" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="postalCode" autocomplete="postal-code" />
|
||||
|
@ -6,7 +6,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { UpdateLicenseDialogResult } from "./update-license-types";
|
||||
import { UpdateLicenseComponent } from "./update-license.component";
|
||||
@ -22,8 +22,16 @@ export class UpdateLicenseDialogComponent extends UpdateLicenseComponent {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
organizationApiService: OrganizationApiServiceAbstraction,
|
||||
formBuilder: FormBuilder,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(apiService, i18nService, platformUtilsService, organizationApiService, formBuilder);
|
||||
super(
|
||||
apiService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
organizationApiService,
|
||||
formBuilder,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
async submitLicense() {
|
||||
const result = await this.submit();
|
||||
|
@ -6,6 +6,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { UpdateLicenseDialogResult } from "./update-license-types";
|
||||
|
||||
@ -32,6 +33,7 @@ export class UpdateLicenseComponent implements OnInit {
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private formBuilder: FormBuilder,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
async ngOnInit() {
|
||||
const org = await this.organizationApiService.get(this.organizationId);
|
||||
@ -52,11 +54,11 @@ export class UpdateLicenseComponent implements OnInit {
|
||||
}
|
||||
const files = this.licenseFile;
|
||||
if (files == null) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("selectFile"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("selectFile"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const fd = new FormData();
|
||||
@ -74,11 +76,11 @@ export class UpdateLicenseComponent implements OnInit {
|
||||
});
|
||||
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("licenseUploadSuccess"),
|
||||
);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("licenseUploadSuccess"),
|
||||
});
|
||||
this.onUpdated.emit();
|
||||
return new Promise((resolve) => resolve(UpdateLicenseDialogResult.Updated));
|
||||
};
|
||||
|
@ -1,9 +1,4 @@
|
||||
<bit-layout>
|
||||
<ng-content select="app-side-nav, [slot=side-nav]" slot="side-nav"></ng-content>
|
||||
|
||||
<app-payment-method-warnings
|
||||
*ngIf="showPaymentMethodWarningBanners$ | async"
|
||||
></app-payment-method-warnings>
|
||||
|
||||
<ng-content></ng-content>
|
||||
</bit-layout>
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { LayoutComponent } from "@bitwarden/components";
|
||||
|
||||
import { PaymentMethodWarningsModule } from "../billing/shared";
|
||||
|
||||
import { ProductSwitcherModule } from "./product-switcher/product-switcher.module";
|
||||
import { ToggleWidthComponent } from "./toggle-width.component";
|
||||
|
||||
@ -14,18 +10,8 @@ import { ToggleWidthComponent } from "./toggle-width.component";
|
||||
selector: "app-layout",
|
||||
templateUrl: "web-layout.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
LayoutComponent,
|
||||
ProductSwitcherModule,
|
||||
ToggleWidthComponent,
|
||||
PaymentMethodWarningsModule,
|
||||
],
|
||||
imports: [CommonModule, LayoutComponent, ProductSwitcherModule, ToggleWidthComponent],
|
||||
})
|
||||
export class WebLayoutComponent {
|
||||
protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.ShowPaymentMethodWarningBanners,
|
||||
);
|
||||
|
||||
constructor(private configService: ConfigService) {}
|
||||
constructor() {}
|
||||
}
|
||||
|
@ -61,7 +61,6 @@ import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component"
|
||||
import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component";
|
||||
import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component";
|
||||
import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component";
|
||||
import { PaymentMethodWarningsModule } from "../billing/shared";
|
||||
import { DynamicAvatarComponent } from "../components/dynamic-avatar.component";
|
||||
import { SelectableAvatarComponent } from "../components/selectable-avatar.component";
|
||||
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
|
||||
@ -113,7 +112,6 @@ import { SharedModule } from "./shared.module";
|
||||
HeaderModule,
|
||||
OrganizationLayoutComponent,
|
||||
UserLayoutComponent,
|
||||
PaymentMethodWarningsModule,
|
||||
VerifyRecoverDeleteOrgComponent,
|
||||
VaultTimeoutInputComponent,
|
||||
],
|
||||
|
@ -23,6 +23,9 @@
|
||||
<bit-callout type="info" *ngIf="allowOwnershipAssignment() && !allowPersonal">
|
||||
{{ "personalOwnershipPolicyInEffect" | i18n }}
|
||||
</bit-callout>
|
||||
<bit-callout *ngIf="cardIsExpired" type="info" [title]="'cardExpiredTitle' | i18n">
|
||||
{{ "cardExpiredMessage" | i18n }}
|
||||
</bit-callout>
|
||||
<div class="row" *ngIf="!editMode && !viewOnly">
|
||||
<div class="col-6 form-group">
|
||||
<label for="type">{{ "whatTypeOfItem" | i18n }}</label>
|
||||
|
@ -11,6 +11,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@ -23,6 +24,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
|
||||
import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { Launchable } from "@bitwarden/common/vault/interfaces/launchable";
|
||||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
@ -43,6 +45,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||
viewingPasswordHistory = false;
|
||||
viewOnly = false;
|
||||
showPasswordCount = false;
|
||||
cardIsExpired: boolean = false;
|
||||
|
||||
protected totpInterval: number;
|
||||
protected override componentName = "app-vault-add-edit";
|
||||
@ -115,6 +118,12 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||
await this.totpTick(interval);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const extensionRefreshEnabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh),
|
||||
);
|
||||
|
||||
this.cardIsExpired = extensionRefreshEnabled && this.isCardExpiryInThePast();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -226,6 +235,24 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On
|
||||
this.viewingPasswordHistory = !this.viewingPasswordHistory;
|
||||
}
|
||||
|
||||
isCardExpiryInThePast() {
|
||||
if (this.cipher.card) {
|
||||
const { expMonth, expYear }: CardView = this.cipher.card;
|
||||
|
||||
if (expYear && expMonth) {
|
||||
// `Date` months are zero-indexed
|
||||
const parsedMonth = parseInt(expMonth) - 1;
|
||||
const parsedYear = parseInt(expYear);
|
||||
|
||||
// First day of the next month minus one, to get last day of the card month
|
||||
const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0);
|
||||
const now = new Date();
|
||||
|
||||
return cardExpiry < now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected cleanUp() {
|
||||
if (this.totpInterval) {
|
||||
window.clearInterval(this.totpInterval);
|
||||
|
@ -194,6 +194,12 @@
|
||||
"dr": {
|
||||
"message": "Dr"
|
||||
},
|
||||
"cardExpiredTitle": {
|
||||
"message": "Expired card"
|
||||
},
|
||||
"cardExpiredMessage": {
|
||||
"message": "If you've renewed it, update the card's information"
|
||||
},
|
||||
"expirationMonth": {
|
||||
"message": "Expiration month"
|
||||
},
|
||||
|
@ -7,7 +7,6 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component";
|
||||
import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vault/app/billing";
|
||||
import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
|
||||
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
|
||||
|
||||
import {
|
||||
@ -53,7 +52,6 @@ import { SetupComponent } from "./setup/setup.component";
|
||||
OrganizationPlansComponent,
|
||||
SearchModule,
|
||||
ProvidersLayoutComponent,
|
||||
PaymentMethodWarningsModule,
|
||||
TaxInfoComponent,
|
||||
DangerZoneComponent,
|
||||
ScrollingModule,
|
||||
|
@ -1,6 +1,3 @@
|
||||
<app-payment-method-warnings
|
||||
*ngIf="showPaymentMethodWarningBanners$ | async"
|
||||
></app-payment-method-warnings>
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin text-muted"
|
||||
|
@ -7,7 +7,6 @@ import { first, takeUntil } from "rxjs/operators";
|
||||
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request";
|
||||
import { TaxInformation } from "@bitwarden/common/billing/models/domain";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@ -35,12 +34,6 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
billingEmail: ["", [Validators.required, Validators.email]],
|
||||
});
|
||||
|
||||
protected readonly TaxInformation = TaxInformation;
|
||||
|
||||
protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.ShowPaymentMethodWarningBanners,
|
||||
);
|
||||
|
||||
protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.EnableConsolidatedBilling,
|
||||
);
|
||||
|
@ -21,6 +21,7 @@ import { DEFAULT_KDF_CONFIG } from "@bitwarden/common/auth/models/domain/kdf-con
|
||||
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
|
||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@ -72,6 +73,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
|
||||
private ssoLoginService: SsoLoginServiceAbstraction,
|
||||
dialogService: DialogService,
|
||||
kdfConfigService: KdfConfigService,
|
||||
private encryptService: EncryptService,
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
@ -160,7 +162,23 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
|
||||
// Existing JIT provisioned user in a MP encryption org setting first password
|
||||
// Users in this state will not already have a user asymmetric key pair so must create it for them
|
||||
// We don't want to re-create the user key pair if the user already has one (TDE user case)
|
||||
newKeyPair = await this.cryptoService.makeKeyPair(userKey[0]);
|
||||
|
||||
// in case we have a local private key, and are not sure whether it has been posted to the server, we post the local private key instead of generating a new one
|
||||
const existingUserPrivateKey = (await firstValueFrom(
|
||||
this.cryptoService.userPrivateKey$(this.userId),
|
||||
)) as Uint8Array;
|
||||
const existingUserPublicKey = await firstValueFrom(
|
||||
this.cryptoService.userPublicKey$(this.userId),
|
||||
);
|
||||
if (existingUserPrivateKey != null && existingUserPublicKey != null) {
|
||||
const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey);
|
||||
newKeyPair = [
|
||||
existingUserPublicKeyB64,
|
||||
await this.encryptService.encrypt(existingUserPrivateKey, userKey[0]),
|
||||
];
|
||||
} else {
|
||||
newKeyPair = await this.cryptoService.makeKeyPair(userKey[0]);
|
||||
}
|
||||
keysRequest = new KeysRequest(newKeyPair[0], newKeyPair[1].encryptedString);
|
||||
}
|
||||
|
||||
|
@ -123,14 +123,12 @@ import {
|
||||
BillingApiServiceAbstraction,
|
||||
BraintreeServiceAbstraction,
|
||||
OrganizationBillingServiceAbstraction,
|
||||
PaymentMethodWarningsServiceAbstraction,
|
||||
StripeServiceAbstraction,
|
||||
} from "@bitwarden/common/billing/abstractions";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
|
||||
import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service";
|
||||
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
||||
import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services/payment-method-warnings.service";
|
||||
import { BraintreeService } from "@bitwarden/common/billing/services/payment-processors/braintree.service";
|
||||
import { StripeService } from "@bitwarden/common/billing/services/payment-processors/stripe.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
@ -1201,11 +1199,6 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: BillingApiService,
|
||||
deps: [ApiServiceAbstraction, LogService, ToastService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: PaymentMethodWarningsServiceAbstraction,
|
||||
useClass: PaymentMethodWarningsService,
|
||||
deps: [BillingApiServiceAbstraction, StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: BillingAccountProfileStateService,
|
||||
useClass: DefaultBillingAccountProfileStateService,
|
||||
|
32
libs/angular/src/utils/extension-refresh-swap.ts
Normal file
32
libs/angular/src/utils/extension-refresh-swap.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Type, inject } from "@angular/core";
|
||||
import { Route, Routes } from "@angular/router";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
|
||||
import { componentRouteSwap } from "./component-route-swap";
|
||||
|
||||
/**
|
||||
* Helper function to swap between two components based on the ExtensionRefresh feature flag.
|
||||
* @param defaultComponent - The current non-refreshed component to render.
|
||||
* @param refreshedComponent - The new refreshed component to render.
|
||||
* @param options - The shared route options to apply to the default component, and to the alt component if altOptions is not provided.
|
||||
* @param altOptions - The alt route options to apply to the alt component.
|
||||
*/
|
||||
export function extensionRefreshSwap(
|
||||
defaultComponent: Type<any>,
|
||||
refreshedComponent: Type<any>,
|
||||
options: Route,
|
||||
altOptions?: Route,
|
||||
): Routes {
|
||||
return componentRouteSwap(
|
||||
defaultComponent,
|
||||
refreshedComponent,
|
||||
async () => {
|
||||
const configService = inject(ConfigService);
|
||||
return configService.getFeatureFlag(FeatureFlag.ExtensionRefresh);
|
||||
},
|
||||
options,
|
||||
altOptions,
|
||||
);
|
||||
}
|
@ -8,7 +8,6 @@ import { PaymentInformationResponse } from "@bitwarden/common/billing/models/res
|
||||
|
||||
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||
import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response";
|
||||
import { PlanResponse } from "../../billing/models/response/plan.response";
|
||||
import { ListResponse } from "../../models/response/list.response";
|
||||
import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request";
|
||||
@ -34,8 +33,6 @@ export abstract class BillingApiServiceAbstraction {
|
||||
organizationId: string,
|
||||
) => Promise<OrganizationBillingMetadataResponse>;
|
||||
|
||||
getOrganizationBillingStatus: (id: string) => Promise<OrganizationBillingStatusResponse>;
|
||||
|
||||
getPlans: () => Promise<ListResponse<PlanResponse>>;
|
||||
|
||||
getProviderClientInvoiceReport: (providerId: string, invoiceId: string) => Promise<string>;
|
||||
|
@ -1,7 +1,6 @@
|
||||
export * from "./account/billing-account-profile-state.service";
|
||||
export * from "./billilng-api.service.abstraction";
|
||||
export * from "./organization-billing.service";
|
||||
export * from "./payment-method-warnings-service.abstraction";
|
||||
export * from "./payment-processors/braintree.service.abstraction";
|
||||
export * from "./payment-processors/stripe.service.abstraction";
|
||||
export * from "./provider-billing.service.abstraction";
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { PaymentMethodWarning } from "../models/domain/payment-method-warning";
|
||||
|
||||
export abstract class PaymentMethodWarningsServiceAbstraction {
|
||||
/**
|
||||
* An {@link Observable} record in the {@link ActiveUserState} of the user's organization IDs each mapped to their respective {@link PaymentMethodWarning}.
|
||||
*/
|
||||
paymentMethodWarnings$: Observable<Record<string, PaymentMethodWarning>>;
|
||||
/**
|
||||
* Updates the {@link ActiveUserState} by setting `acknowledged` to `true` for the {@link PaymentMethodWarning} represented by the provided organization ID.
|
||||
* @param organizationId - The ID of the organization whose warning you'd like to acknowledge.
|
||||
*/
|
||||
acknowledge: (organizationId: string) => Promise<void>;
|
||||
/**
|
||||
* Updates the {@link ActiveUserState} by setting `risksSubscriptionFailure` to `false` for the {@link PaymentMethodWarning} represented by the provided organization ID.
|
||||
* @param organizationId - The ID of the organization whose subscription risk you'd like to remove.
|
||||
*/
|
||||
removeSubscriptionRisk: (organizationId: string) => Promise<void>;
|
||||
/**
|
||||
* Clears the {@link PaymentMethodWarning} record from the {@link ActiveUserState}.
|
||||
*/
|
||||
clear: () => Promise<void>;
|
||||
/**
|
||||
* Tries to retrieve the {@link PaymentMethodWarning} for the provided organization ID from the {@link ActiveUserState}.
|
||||
* If the warning does not exist, or if the warning has been in state for longer than a week, fetches the current {@link OrganizationBillingStatusResponse} for the organization
|
||||
* from the API and uses it to update the warning in state.
|
||||
* @param organizationId - The ID of the organization whose {@link PaymentMethodWarning} you'd like to update.
|
||||
*/
|
||||
update: (organizationId: string) => Promise<void>;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { BILLING_DISK, UserKeyDefinition } from "../../platform/state";
|
||||
import { PaymentMethodWarning } from "../models/domain/payment-method-warning";
|
||||
|
||||
export const PAYMENT_METHOD_WARNINGS_KEY = UserKeyDefinition.record<PaymentMethodWarning>(
|
||||
BILLING_DISK,
|
||||
"paymentMethodWarnings",
|
||||
{
|
||||
deserializer: (warnings) => ({
|
||||
...warnings,
|
||||
savedAt: new Date(warnings.savedAt),
|
||||
}),
|
||||
clearOn: ["logout"],
|
||||
},
|
||||
);
|
@ -1,5 +1,4 @@
|
||||
export * from "./bank-account";
|
||||
export * from "./masked-payment-method";
|
||||
export * from "./payment-method-warning";
|
||||
export * from "./tax-information";
|
||||
export * from "./tokenized-payment-method";
|
||||
|
@ -1,6 +0,0 @@
|
||||
export type PaymentMethodWarning = {
|
||||
organizationName: string;
|
||||
risksSubscriptionFailure: boolean;
|
||||
acknowledged: boolean;
|
||||
savedAt: Date;
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class OrganizationBillingStatusResponse extends BaseResponse {
|
||||
organizationId: string;
|
||||
organizationName: string;
|
||||
risksSubscriptionFailure: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
this.organizationId = this.getResponseProperty("OrganizationId");
|
||||
this.organizationName = this.getResponseProperty("OrganizationName");
|
||||
this.risksSubscriptionFailure = this.getResponseProperty("RisksSubscriptionFailure");
|
||||
}
|
||||
}
|
@ -12,7 +12,6 @@ import { SubscriptionCancellationRequest } from "../../billing/models/request/su
|
||||
import { TokenizedPaymentMethodRequest } from "../../billing/models/request/tokenized-payment-method.request";
|
||||
import { VerifyBankAccountRequest } from "../../billing/models/request/verify-bank-account.request";
|
||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||
import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response";
|
||||
import { PaymentInformationResponse } from "../../billing/models/response/payment-information.response";
|
||||
import { PlanResponse } from "../../billing/models/response/plan.response";
|
||||
import { ListResponse } from "../../models/response/list.response";
|
||||
@ -72,17 +71,6 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
return response as string;
|
||||
}
|
||||
|
||||
async getOrganizationBillingStatus(id: string): Promise<OrganizationBillingStatusResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + id + "/billing-status",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
return new OrganizationBillingStatusResponse(r);
|
||||
}
|
||||
|
||||
async getOrganizationBillingMetadata(
|
||||
organizationId: string,
|
||||
): Promise<OrganizationBillingMetadataResponse> {
|
||||
|
@ -1,186 +0,0 @@
|
||||
import { any, mock, MockProxy } from "jest-mock-extended";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
|
||||
import { FakeActiveUserState } from "../../../spec/fake-state";
|
||||
import { Utils } from "../../platform/misc/utils";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { BillingApiServiceAbstraction as BillingApiService } from "../abstractions/billilng-api.service.abstraction";
|
||||
import { PAYMENT_METHOD_WARNINGS_KEY } from "../models/billing-keys.state";
|
||||
import { PaymentMethodWarning } from "../models/domain/payment-method-warning";
|
||||
import { OrganizationBillingStatusResponse } from "../models/response/organization-billing-status.response";
|
||||
|
||||
import { PaymentMethodWarningsService } from "./payment-method-warnings.service";
|
||||
|
||||
describe("Payment Method Warnings Service", () => {
|
||||
let paymentMethodWarningsService: PaymentMethodWarningsService;
|
||||
let billingApiService: MockProxy<BillingApiService>;
|
||||
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let accountService: FakeAccountService;
|
||||
let stateProvider: FakeStateProvider;
|
||||
let activeUserState: FakeActiveUserState<Record<string, PaymentMethodWarning>>;
|
||||
|
||||
function getPastDate(daysAgo: number) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - daysAgo);
|
||||
return date;
|
||||
}
|
||||
|
||||
const getBillingStatusResponse = (organizationId: string) =>
|
||||
new OrganizationBillingStatusResponse({
|
||||
OrganizationId: organizationId,
|
||||
OrganizationName: "Teams Organization",
|
||||
RisksSubscriptionFailure: true,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
activeUserState = stateProvider.activeUser.getFake(PAYMENT_METHOD_WARNINGS_KEY);
|
||||
|
||||
billingApiService = mock<BillingApiService>();
|
||||
paymentMethodWarningsService = new PaymentMethodWarningsService(
|
||||
billingApiService,
|
||||
stateProvider,
|
||||
);
|
||||
});
|
||||
|
||||
it("acknowledge", async () => {
|
||||
const organizationId = "1";
|
||||
const state: Record<string, PaymentMethodWarning> = {
|
||||
[organizationId]: {
|
||||
organizationName: "Teams Organization",
|
||||
risksSubscriptionFailure: true,
|
||||
acknowledged: false,
|
||||
savedAt: getPastDate(3),
|
||||
},
|
||||
};
|
||||
activeUserState.nextState(state);
|
||||
await paymentMethodWarningsService.acknowledge(organizationId);
|
||||
expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({
|
||||
[organizationId]: {
|
||||
...state[organizationId],
|
||||
acknowledged: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("clear", async () => {
|
||||
const state: Record<string, PaymentMethodWarning> = {
|
||||
"1": {
|
||||
organizationName: "Teams Organization",
|
||||
risksSubscriptionFailure: true,
|
||||
acknowledged: false,
|
||||
savedAt: getPastDate(3),
|
||||
},
|
||||
};
|
||||
activeUserState.nextState(state);
|
||||
await paymentMethodWarningsService.clear();
|
||||
expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({});
|
||||
});
|
||||
|
||||
it("removeSubscriptionRisk", async () => {
|
||||
const organizationId = "1";
|
||||
const state: Record<string, PaymentMethodWarning> = {
|
||||
[organizationId]: {
|
||||
organizationName: "Teams Organization",
|
||||
risksSubscriptionFailure: true,
|
||||
acknowledged: false,
|
||||
savedAt: getPastDate(3),
|
||||
},
|
||||
};
|
||||
activeUserState.nextState(state);
|
||||
await paymentMethodWarningsService.removeSubscriptionRisk(organizationId);
|
||||
expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({
|
||||
[organizationId]: {
|
||||
...state[organizationId],
|
||||
risksSubscriptionFailure: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("update", () => {
|
||||
it("Does nothing if the stored payment method warning is less than a week old", async () => {
|
||||
const organizationId = "1";
|
||||
const state: Record<string, PaymentMethodWarning> = {
|
||||
[organizationId]: {
|
||||
organizationName: "Teams Organization",
|
||||
risksSubscriptionFailure: true,
|
||||
acknowledged: false,
|
||||
savedAt: getPastDate(3),
|
||||
},
|
||||
};
|
||||
activeUserState.nextState(state);
|
||||
await paymentMethodWarningsService.update(organizationId);
|
||||
expect(billingApiService.getOrganizationBillingStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Retrieves the billing status from the API and uses it to update the state if the state is null", async () => {
|
||||
const organizationId = "1";
|
||||
activeUserState.nextState(null);
|
||||
billingApiService.getOrganizationBillingStatus.mockResolvedValue(
|
||||
getBillingStatusResponse(organizationId),
|
||||
);
|
||||
await paymentMethodWarningsService.update(organizationId);
|
||||
expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({
|
||||
[organizationId]: {
|
||||
organizationName: "Teams Organization",
|
||||
risksSubscriptionFailure: true,
|
||||
acknowledged: false,
|
||||
savedAt: any(),
|
||||
},
|
||||
});
|
||||
expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("Retrieves the billing status from the API and uses it to update the state if the stored warning is null", async () => {
|
||||
const organizationId = "1";
|
||||
activeUserState.nextState({
|
||||
[organizationId]: null,
|
||||
});
|
||||
billingApiService.getOrganizationBillingStatus.mockResolvedValue(
|
||||
getBillingStatusResponse(organizationId),
|
||||
);
|
||||
await paymentMethodWarningsService.update(organizationId);
|
||||
expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({
|
||||
[organizationId]: {
|
||||
organizationName: "Teams Organization",
|
||||
risksSubscriptionFailure: true,
|
||||
acknowledged: false,
|
||||
savedAt: any(),
|
||||
},
|
||||
});
|
||||
expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("Retrieves the billing status from the API and uses it to update the state if the stored warning is older than a week", async () => {
|
||||
const organizationId = "1";
|
||||
activeUserState.nextState({
|
||||
[organizationId]: {
|
||||
organizationName: "Teams Organization",
|
||||
risksSubscriptionFailure: false,
|
||||
acknowledged: false,
|
||||
savedAt: getPastDate(10),
|
||||
},
|
||||
});
|
||||
billingApiService.getOrganizationBillingStatus.mockResolvedValue(
|
||||
new OrganizationBillingStatusResponse({
|
||||
OrganizationId: organizationId,
|
||||
OrganizationName: "Teams Organization",
|
||||
RisksSubscriptionFailure: true,
|
||||
}),
|
||||
);
|
||||
await paymentMethodWarningsService.update(organizationId);
|
||||
expect(await firstValueFrom(paymentMethodWarningsService.paymentMethodWarnings$)).toEqual({
|
||||
[organizationId]: {
|
||||
organizationName: "Teams Organization",
|
||||
risksSubscriptionFailure: true,
|
||||
acknowledged: false,
|
||||
savedAt: any(),
|
||||
},
|
||||
});
|
||||
expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,74 +0,0 @@
|
||||
import { firstValueFrom, map, Observable } from "rxjs";
|
||||
|
||||
import { ActiveUserState, StateProvider } from "../../platform/state";
|
||||
import { BillingApiServiceAbstraction as BillingApiService } from "../abstractions/billilng-api.service.abstraction";
|
||||
import { PaymentMethodWarningsServiceAbstraction } from "../abstractions/payment-method-warnings-service.abstraction";
|
||||
import { PAYMENT_METHOD_WARNINGS_KEY } from "../models/billing-keys.state";
|
||||
import { PaymentMethodWarning } from "../models/domain/payment-method-warning";
|
||||
|
||||
export class PaymentMethodWarningsService implements PaymentMethodWarningsServiceAbstraction {
|
||||
private paymentMethodWarningsState: ActiveUserState<Record<string, PaymentMethodWarning>>;
|
||||
paymentMethodWarnings$: Observable<Record<string, PaymentMethodWarning>>;
|
||||
|
||||
constructor(
|
||||
private billingApiService: BillingApiService,
|
||||
private stateProvider: StateProvider,
|
||||
) {
|
||||
this.paymentMethodWarningsState = this.stateProvider.getActive(PAYMENT_METHOD_WARNINGS_KEY);
|
||||
this.paymentMethodWarnings$ = this.paymentMethodWarningsState.state$;
|
||||
}
|
||||
|
||||
async acknowledge(organizationId: string): Promise<void> {
|
||||
await this.paymentMethodWarningsState.update((state) => {
|
||||
const current = state[organizationId];
|
||||
state[organizationId] = {
|
||||
...current,
|
||||
acknowledged: true,
|
||||
};
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
async removeSubscriptionRisk(organizationId: string): Promise<void> {
|
||||
await this.paymentMethodWarningsState.update((state) => {
|
||||
const current = state[organizationId];
|
||||
state[organizationId] = {
|
||||
...current,
|
||||
risksSubscriptionFailure: false,
|
||||
};
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
await this.paymentMethodWarningsState.update(() => ({}));
|
||||
}
|
||||
|
||||
async update(organizationId: string): Promise<void> {
|
||||
const warning = await firstValueFrom(
|
||||
this.paymentMethodWarningsState.state$.pipe(
|
||||
map((state) => (!state ? null : state[organizationId])),
|
||||
),
|
||||
);
|
||||
if (!warning || warning.savedAt < this.getOneWeekAgo()) {
|
||||
const { organizationName, risksSubscriptionFailure } =
|
||||
await this.billingApiService.getOrganizationBillingStatus(organizationId);
|
||||
await this.paymentMethodWarningsState.update((state) => {
|
||||
state ??= {};
|
||||
state[organizationId] = {
|
||||
organizationName,
|
||||
risksSubscriptionFailure,
|
||||
acknowledged: false,
|
||||
savedAt: new Date(),
|
||||
};
|
||||
return state;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getOneWeekAgo = (): Date => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 7);
|
||||
return date;
|
||||
};
|
||||
}
|
@ -7,7 +7,6 @@ export enum FeatureFlag {
|
||||
BrowserFilelessImport = "browser-fileless-import",
|
||||
ItemShare = "item-share",
|
||||
GeneratorToolsModernization = "generator-tools-modernization",
|
||||
ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners",
|
||||
EnableConsolidatedBilling = "enable-consolidated-billing",
|
||||
AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section",
|
||||
EnableDeleteProvider = "AC-1218-delete-provider",
|
||||
@ -50,7 +49,6 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.BrowserFilelessImport]: FALSE,
|
||||
[FeatureFlag.ItemShare]: FALSE,
|
||||
[FeatureFlag.GeneratorToolsModernization]: FALSE,
|
||||
[FeatureFlag.ShowPaymentMethodWarningBanners]: FALSE,
|
||||
[FeatureFlag.EnableConsolidatedBilling]: FALSE,
|
||||
[FeatureFlag.AC1795_UpdatedSubscriptionStatusSection]: FALSE,
|
||||
[FeatureFlag.EnableDeleteProvider]: FALSE,
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./dialog.service.stories";
|
||||
|
||||
<Meta title="Component Library/Dialogs" />
|
||||
|
||||
# Dialog
|
||||
@ -24,7 +26,7 @@ dialog should become scrollable.
|
||||
|
||||
A backdrop should be used to hide the content below the dialog. Use `#000000` with `30% opacity`.
|
||||
|
||||
<Story id="component-library-dialogs-service--default" />
|
||||
<Story of={stories.Default} />
|
||||
|
||||
## Accessibility
|
||||
|
||||
|
@ -1,5 +1,14 @@
|
||||
import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||
|
||||
import * as formStories from "./form.stories";
|
||||
import * as fieldStories from "../form-field/form-field.stories";
|
||||
import * as passwordToggleStories from "../form-field/password-input-toggle.stories";
|
||||
import * as searchStories from "../search/search.stories";
|
||||
import * as selectStories from "../select/select.stories";
|
||||
import * as multiSelectStories from "../form-field/multi-select.stories";
|
||||
import * as radioStories from "../radio-button/radio-button.stories";
|
||||
import * as checkboxStories from "../checkbox/checkbox.stories";
|
||||
|
||||
<Meta title="Component Library/Form" />
|
||||
|
||||
# Forms
|
||||
@ -8,9 +17,9 @@ Component Library forms should always be built using [Angular Reactive Forms][re
|
||||
[ADR-0001][adr-0001] for a background to this decision. In practice this means that forms should
|
||||
always use the native `form` element and bind a `formGroup`.
|
||||
|
||||
<Story id="component-library-form--full-example" />
|
||||
<Story of={formStories.FullExample} />
|
||||
|
||||
<Source id="component-library-form--full-example" />
|
||||
<br />
|
||||
|
||||
## Form spacing and sections
|
||||
|
||||
@ -48,25 +57,25 @@ controls like email verification, number selection, and more.
|
||||
|
||||
#### Default with required attribute
|
||||
|
||||
<Story id="component-library-form-field--default" />
|
||||
<Story of={fieldStories.Default} />
|
||||
|
||||
#### Password Toggle
|
||||
|
||||
<Story id="component-library-form-password-toggle--default" />
|
||||
<Story of={passwordToggleStories.Default} />
|
||||
|
||||
#### Search
|
||||
### Search
|
||||
|
||||
<Story id="component-library-form-search--default" />
|
||||
<Story of={searchStories.Default} />
|
||||
|
||||
### Selects
|
||||
|
||||
#### Searchable single select (default)
|
||||
|
||||
<Story id="component-library-form-select--default" />
|
||||
<Story of={selectStories.Default} />
|
||||
|
||||
#### Multi-select
|
||||
|
||||
<Story id="component-library-form-multi-select--members" />
|
||||
<Story of={multiSelectStories.Members} />
|
||||
|
||||
### Radio group
|
||||
|
||||
@ -89,14 +98,11 @@ using a radio group for more than 5 options even if the options require addition
|
||||
|
||||
#### Block
|
||||
|
||||
<Story id="component-library-form-radio-button--block" />
|
||||
<Story of={radioStories.Block} />
|
||||
|
||||
#### Inline
|
||||
|
||||
<Story id="component-library-form-radio-button--inline" />
|
||||
|
||||
[reactive]: https://angular.io/guide/reactive-forms
|
||||
[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms
|
||||
<Story of={radioStories.Inline} />
|
||||
|
||||
### Checkbox
|
||||
|
||||
@ -116,7 +122,7 @@ If a checkbox group has more than 4 options a
|
||||
|
||||
#### Single checkbox
|
||||
|
||||
<Story id="component-library-form-checkbox--default" />
|
||||
<Story of={checkboxStories.Default} />
|
||||
|
||||
## Accessibility
|
||||
|
||||
@ -176,3 +182,6 @@ the field’s label.
|
||||
Maintain a ratio of 3:1 with the form's background.
|
||||
- Error styling should not rely only on using the `danger-600`color change. Use
|
||||
<i class="bwi bwi-error"></i> as a prefix to highlight the text as error text versus helper
|
||||
|
||||
[reactive]: https://angular.io/guide/reactive-forms
|
||||
[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms
|
||||
|
@ -64,6 +64,7 @@ export default {
|
||||
skipToContent: "Skip to content",
|
||||
submenu: "submenu",
|
||||
toggleCollapse: "toggle collapse",
|
||||
toggleSideNavigation: "toggle side navigation",
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -74,6 +74,21 @@
|
||||
bitPasswordInputToggle
|
||||
[(toggled)]="showFilePassword"
|
||||
></button>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-generate"
|
||||
appStopClick
|
||||
bitSuffix
|
||||
(click)="generatePassword()"
|
||||
></button>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
[disabled]="!filePassword"
|
||||
appStopClick
|
||||
bitSuffix
|
||||
(click)="copyPasswordToClipboard()"
|
||||
></button>
|
||||
<bit-hint>{{ "exportPasswordDescription" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<tools-password-strength [password]="filePassword" [showText]="true">
|
||||
|
@ -24,6 +24,7 @@ import { EventType } from "@bitwarden/common/enums";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import {
|
||||
@ -38,6 +39,7 @@ import {
|
||||
SelectModule,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
|
||||
|
||||
import { EncryptedExportType } from "../enums/encrypted-export-type.enum";
|
||||
@ -157,6 +159,8 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
protected toastService: ToastService,
|
||||
protected exportService: VaultExportServiceAbstraction,
|
||||
protected eventCollectionService: EventCollectionService,
|
||||
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private policyService: PolicyService,
|
||||
private logService: LogService,
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
@ -272,6 +276,22 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
generatePassword = async () => {
|
||||
const [options] = await this.passwordGenerationService.getOptions();
|
||||
this.filePasswordValue = await this.passwordGenerationService.generatePassword(options);
|
||||
this.exportForm.get("filePassword").setValue(this.filePasswordValue);
|
||||
this.exportForm.get("confirmFilePassword").setValue(this.filePasswordValue);
|
||||
};
|
||||
|
||||
copyPasswordToClipboard = async () => {
|
||||
this.platformUtilsService.copyToClipboard(this.filePasswordValue);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
|
||||
});
|
||||
};
|
||||
|
||||
submit = async () => {
|
||||
if (this.isFileEncryptedExport && this.filePassword != this.confirmFilePassword) {
|
||||
this.toastService.showToast({
|
||||
|
@ -1,4 +1,8 @@
|
||||
<ng-container *ngIf="!!cipher">
|
||||
<bit-callout *ngIf="cardIsExpired" type="info" [title]="'cardExpiredTitle' | i18n">
|
||||
{{ "cardExpiredMessage" | i18n }}
|
||||
</bit-callout>
|
||||
|
||||
<!-- ITEM DETAILS -->
|
||||
<app-item-details-v2
|
||||
[cipher]="cipher"
|
||||
|
@ -8,10 +8,11 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
|
||||
import { CollectionId } from "@bitwarden/common/types/guid";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
import { SearchModule, CalloutModule } from "@bitwarden/components";
|
||||
|
||||
import { AdditionalOptionsComponent } from "./additional-options/additional-options.component";
|
||||
import { AttachmentsV2ViewComponent } from "./attachments/attachments-v2-view.component";
|
||||
@ -28,6 +29,7 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide
|
||||
templateUrl: "cipher-view.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CalloutModule,
|
||||
CommonModule,
|
||||
SearchModule,
|
||||
JslibModule,
|
||||
@ -48,6 +50,7 @@ export class CipherViewComponent implements OnInit, OnDestroy {
|
||||
folder$: Observable<FolderView>;
|
||||
collections$: Observable<CollectionView[]>;
|
||||
private destroyed$: Subject<void> = new Subject();
|
||||
cardIsExpired: boolean = false;
|
||||
|
||||
constructor(
|
||||
private organizationService: OrganizationService,
|
||||
@ -57,6 +60,8 @@ export class CipherViewComponent implements OnInit, OnDestroy {
|
||||
|
||||
async ngOnInit() {
|
||||
await this.loadCipherData();
|
||||
|
||||
this.cardIsExpired = this.isCardExpiryInThePast();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -97,4 +102,24 @@ export class CipherViewComponent implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this.destroyed$));
|
||||
}
|
||||
}
|
||||
|
||||
isCardExpiryInThePast() {
|
||||
if (this.cipher.card) {
|
||||
const { expMonth, expYear }: CardView = this.cipher.card;
|
||||
|
||||
if (expYear && expMonth) {
|
||||
// `Date` months are zero-indexed
|
||||
const parsedMonth = parseInt(expMonth) - 1;
|
||||
const parsedYear = parseInt(expYear);
|
||||
|
||||
// First day of the next month minus one, to get last day of the card month
|
||||
const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0);
|
||||
const now = new Date();
|
||||
|
||||
return cardExpiry < now;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
48
package-lock.json
generated
48
package-lock.json
generated
@ -120,7 +120,7 @@
|
||||
"@typescript-eslint/parser": "7.16.1",
|
||||
"@webcomponents/custom-elements": "1.6.0",
|
||||
"@yao-pkg/pkg": "5.12.1",
|
||||
"autoprefixer": "10.4.19",
|
||||
"autoprefixer": "10.4.20",
|
||||
"babel-loader": "9.1.3",
|
||||
"base64-loader": "1.0.0",
|
||||
"browserslist": "4.23.2",
|
||||
@ -11194,9 +11194,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.19",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
|
||||
"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
|
||||
"version": "10.4.20",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -11212,12 +11212,13 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.0",
|
||||
"caniuse-lite": "^1.0.30001599",
|
||||
"browserslist": "^4.23.3",
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"fraction.js": "^4.3.7",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"picocolors": "^1.0.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
@ -11230,6 +11231,39 @@
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer/node_modules/browserslist": {
|
||||
"version": "4.23.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
|
||||
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"electron-to-chromium": "^1.5.4",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
|
@ -82,7 +82,7 @@
|
||||
"@typescript-eslint/parser": "7.16.1",
|
||||
"@webcomponents/custom-elements": "1.6.0",
|
||||
"@yao-pkg/pkg": "5.12.1",
|
||||
"autoprefixer": "10.4.19",
|
||||
"autoprefixer": "10.4.20",
|
||||
"babel-loader": "9.1.3",
|
||||
"base64-loader": "1.0.0",
|
||||
"browserslist": "4.23.2",
|
||||
|
Loading…
Reference in New Issue
Block a user