-
- ×
-
-
{{ (add ? "addStorage" : "removeStorage") | i18n }}
-
-
-
- {{ "total" | i18n }}: {{ storageAdjustment || 0 }} GB ×
- {{ storageGbPrice | currency: "$" }} = {{ adjustedStorageTotal | currency: "$" }} /{{
- interval | i18n
- }}
-
-
-
- {{ "submit" | i18n }}
-
-
- {{ "cancel" | i18n }}
-
-
- {{ (add ? "storageAddNote" : "storageRemoveNote") | i18n }}
-
-
+
+
+
+ {{ "submit" | i18n }}
+
+
+ {{ "cancel" | i18n }}
+
+
+
diff --git a/apps/web/src/app/billing/shared/adjust-storage.component.ts b/apps/web/src/app/billing/shared/adjust-storage.component.ts
index 25462c2829..fcdbc3437d 100644
--- a/apps/web/src/app/billing/shared/adjust-storage.component.ts
+++ b/apps/web/src/app/billing/shared/adjust-storage.component.ts
@@ -1,4 +1,6 @@
-import { Component, EventEmitter, Input, Output, ViewChild } from "@angular/core";
+import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
+import { Component, Inject, ViewChild } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -8,27 +10,45 @@ 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 { PaymentComponent } from "./payment.component";
+export interface AdjustStorageDialogData {
+ storageGbPrice: number;
+ add: boolean;
+ organizationId?: string;
+ interval?: string;
+}
+
+export enum AdjustStorageDialogResult {
+ Adjusted = "adjusted",
+ Cancelled = "cancelled",
+}
+
@Component({
- selector: "app-adjust-storage",
templateUrl: "adjust-storage.component.html",
})
export class AdjustStorageComponent {
- @Input() storageGbPrice = 0;
- @Input() add = true;
- @Input() organizationId: string;
- @Input() interval = "year";
- @Output() onAdjusted = new EventEmitter
();
- @Output() onCanceled = new EventEmitter();
+ storageGbPrice: number;
+ add: boolean;
+ organizationId: string;
+ interval: string;
@ViewChild(PaymentComponent, { static: true }) paymentComponent: PaymentComponent;
- storageAdjustment = 0;
- formPromise: Promise;
+ protected DialogResult = AdjustStorageDialogResult;
+ protected formGroup = new FormGroup({
+ storageAdjustment: new FormControl(0, [
+ Validators.required,
+ Validators.min(0),
+ Validators.max(99),
+ ]),
+ });
constructor(
+ private dialogRef: DialogRef,
+ @Inject(DIALOG_DATA) protected data: AdjustStorageDialogData,
private apiService: ApiService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
@@ -36,69 +56,74 @@ export class AdjustStorageComponent {
private activatedRoute: ActivatedRoute,
private logService: LogService,
private organizationApiService: OrganizationApiServiceAbstraction,
- ) {}
+ ) {
+ this.storageGbPrice = data.storageGbPrice;
+ this.add = data.add;
+ this.organizationId = data.organizationId;
+ this.interval = data.interval || "year";
+ }
- async submit() {
- try {
- const request = new StorageRequest();
- request.storageGbAdjustment = this.storageAdjustment;
- if (!this.add) {
- request.storageGbAdjustment *= -1;
- }
-
- let paymentFailed = false;
- const action = async () => {
- let response: Promise;
- if (this.organizationId == null) {
- response = this.formPromise = this.apiService.postAccountStorage(request);
- } else {
- response = this.formPromise = this.organizationApiService.updateStorage(
- this.organizationId,
- request,
- );
- }
- const result = await response;
- if (result != null && result.paymentIntentClientSecret != null) {
- try {
- await this.paymentComponent.handleStripeCardPayment(
- result.paymentIntentClientSecret,
- null,
- );
- } catch {
- paymentFailed = true;
- }
- }
- };
- this.formPromise = action();
- await this.formPromise;
- this.onAdjusted.emit(this.storageAdjustment);
- if (paymentFailed) {
- this.platformUtilsService.showToast(
- "warning",
- null,
- 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()),
- );
- }
- } catch (e) {
- this.logService.error(e);
+ submit = async () => {
+ const request = new StorageRequest();
+ request.storageGbAdjustment = this.formGroup.value.storageAdjustment;
+ if (!this.add) {
+ request.storageGbAdjustment *= -1;
}
- }
- cancel() {
- this.onCanceled.emit();
- }
+ let paymentFailed = false;
+ const action = async () => {
+ let response: Promise;
+ if (this.organizationId == null) {
+ response = this.apiService.postAccountStorage(request);
+ } else {
+ response = this.organizationApiService.updateStorage(this.organizationId, request);
+ }
+ const result = await response;
+ if (result != null && result.paymentIntentClientSecret != null) {
+ try {
+ await this.paymentComponent.handleStripeCardPayment(
+ result.paymentIntentClientSecret,
+ null,
+ );
+ } catch {
+ paymentFailed = true;
+ }
+ }
+ };
+ await action();
+ this.dialogRef.close(AdjustStorageDialogResult.Adjusted);
+ if (paymentFailed) {
+ this.platformUtilsService.showToast(
+ "warning",
+ null,
+ 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()),
+ );
+ }
+ };
get adjustedStorageTotal(): number {
- return this.storageGbPrice * this.storageAdjustment;
+ return this.storageGbPrice * this.formGroup.value.storageAdjustment;
}
}
+
+/**
+ * Strongly typed helper to open an AdjustStorageDialog
+ * @param dialogService Instance of the dialog service that will be used to open the dialog
+ * @param config Configuration for the dialog
+ */
+export function openAdjustStorageDialog(
+ dialogService: DialogService,
+ config: DialogConfig,
+) {
+ return dialogService.open(AdjustStorageComponent, config);
+}
diff --git a/apps/web/src/app/billing/shared/billing-history.component.html b/apps/web/src/app/billing/shared/billing-history.component.html
index 56a8a990d4..1719a59076 100644
--- a/apps/web/src/app/billing/shared/billing-history.component.html
+++ b/apps/web/src/app/billing/shared/billing-history.component.html
@@ -1,65 +1,72 @@
-{{ "invoices" | i18n }}
-{{ "noInvoices" | i18n }}
-
-
-{{ "noTransactions" | i18n }}
-
-
-
- {{ t.createdDate | date: "mediumDate" }}
-
-
- {{ "chargeNoun" | i18n }}
-
- {{ "refundNoun" | i18n }}
-
-
-
- {{ t.details }}
-
-
- {{ t.amount | currency: "$" }}
-
-
-
-
-* {{ "chargesStatement" | i18n: "BITWARDEN" }}
+ {{ t.amount | currency: "$" }}
+
+
+
+
+ * {{ "chargesStatement" | i18n: "BITWARDEN" }}
+
diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts
index 2f773870aa..65a651b73d 100644
--- a/apps/web/src/app/billing/shared/billing-shared.module.ts
+++ b/apps/web/src/app/billing/shared/billing-shared.module.ts
@@ -4,7 +4,7 @@ import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared";
import { AddCreditComponent } from "./add-credit.component";
-import { AdjustPaymentComponent } from "./adjust-payment.component";
+import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog.component";
import { AdjustStorageComponent } from "./adjust-storage.component";
import { BillingHistoryComponent } from "./billing-history.component";
import { OffboardingSurveyComponent } from "./offboarding-survey.component";
@@ -18,7 +18,7 @@ import { UpdateLicenseComponent } from "./update-license.component";
imports: [SharedModule, PaymentComponent, TaxInfoComponent, HeaderModule],
declarations: [
AddCreditComponent,
- AdjustPaymentComponent,
+ AdjustPaymentDialogComponent,
AdjustStorageComponent,
BillingHistoryComponent,
PaymentMethodComponent,
diff --git a/apps/web/src/app/billing/shared/payment-method.component.html b/apps/web/src/app/billing/shared/payment-method.component.html
index cfe98178b0..5f78294fa6 100644
--- a/apps/web/src/app/billing/shared/payment-method.component.html
+++ b/apps/web/src/app/billing/shared/payment-method.component.html
@@ -15,7 +15,7 @@
@@ -102,23 +102,9 @@
{{ paymentSource.description }}
-
+
{{ (paymentSource ? "changePaymentMethod" : "addPaymentMethod") | i18n }}
-
-
{{ "paymentChargedWithUnpaidSubscription" | i18n }}
diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts
index d2b65968c3..fee97cb912 100644
--- a/apps/web/src/app/billing/shared/payment-method.component.ts
+++ b/apps/web/src/app/billing/shared/payment-method.component.ts
@@ -1,6 +1,7 @@
import { Component, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, FormControl, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
+import { lastValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
@@ -14,6 +15,10 @@ 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 {
+ AdjustPaymentDialogResult,
+ openAdjustPaymentDialog,
+} from "./adjust-payment-dialog.component";
import { TaxInfoComponent } from "./tax-info.component";
@Component({
@@ -25,7 +30,6 @@ export class PaymentMethodComponent implements OnInit {
loading = false;
firstLoaded = false;
- showAdjustPayment = false;
showAddCredit = false;
billing: BillingPaymentResponse;
org: OrganizationSubscriptionResponse;
@@ -120,18 +124,18 @@ export class PaymentMethodComponent implements OnInit {
}
}
- changePayment() {
- this.showAdjustPayment = true;
- }
-
- closePayment(load: boolean) {
- this.showAdjustPayment = false;
- if (load) {
- // 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();
+ changePayment = async () => {
+ const dialogRef = openAdjustPaymentDialog(this.dialogService, {
+ data: {
+ organizationId: this.organizationId,
+ currentType: this.paymentSource !== null ? this.paymentSource.type : null,
+ },
+ });
+ const result = await lastValueFrom(dialogRef.closed);
+ if (result === AdjustPaymentDialogResult.Adjusted) {
+ await this.load();
}
- }
+ };
async verifyBank() {
if (this.loading || !this.forOrganization) {
diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts
index e5c2f353c0..066ed5db10 100644
--- a/apps/web/src/app/oss-routing.module.ts
+++ b/apps/web/src/app/oss-routing.module.ts
@@ -13,6 +13,7 @@ import { flagEnabled, Flags } from "../utils/flags";
import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/sponsorships/accept-family-sponsorship.component";
import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component";
+import { VerifyRecoverDeleteProviderComponent } from "./admin-console/providers/verify-recover-delete-provider.component";
import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component";
import { SponsoredFamiliesComponent } from "./admin-console/settings/sponsored-families.component";
import { AcceptOrganizationComponent } from "./auth/accept-organization.component";
@@ -156,6 +157,12 @@ const routes: Routes = [
canActivate: [UnauthGuard],
data: { titleId: "deleteAccount" },
},
+ {
+ path: "verify-recover-delete-provider",
+ component: VerifyRecoverDeleteProviderComponent,
+ canActivate: [UnauthGuard],
+ data: { titleId: "deleteAccount" },
+ },
{
path: "send/:sendId/:key",
component: AccessComponent,
diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts
index 586f207962..8f6a1eaedc 100644
--- a/apps/web/src/app/shared/loose-components.module.ts
+++ b/apps/web/src/app/shared/loose-components.module.ts
@@ -13,6 +13,7 @@ import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } f
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../admin-console/organizations/tools/unsecured-websites-report.component";
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../admin-console/organizations/tools/weak-passwords-report.component";
import { ProvidersComponent } from "../admin-console/providers/providers.component";
+import { VerifyRecoverDeleteProviderComponent } from "../admin-console/providers/verify-recover-delete-provider.component";
import { SponsoredFamiliesComponent } from "../admin-console/settings/sponsored-families.component";
import { SponsoringOrgRowComponent } from "../admin-console/settings/sponsoring-org-row.component";
import { AcceptOrganizationComponent } from "../auth/accept-organization.component";
@@ -184,6 +185,7 @@ import { SharedModule } from "./shared.module";
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
+ VerifyRecoverDeleteProviderComponent,
LowKdfComponent,
],
exports: [
@@ -261,6 +263,7 @@ import { SharedModule } from "./shared.module";
VerifyEmailComponent,
VerifyEmailTokenComponent,
VerifyRecoverDeleteComponent,
+ VerifyRecoverDeleteProviderComponent,
LowKdfComponent,
HeaderModule,
DangerZoneComponent,
diff --git a/apps/web/src/app/vault/core/collection-admin.service.ts b/apps/web/src/app/vault/core/collection-admin.service.ts
index 74f825e1ac..7f78ab214a 100644
--- a/apps/web/src/app/vault/core/collection-admin.service.ts
+++ b/apps/web/src/app/vault/core/collection-admin.service.ts
@@ -124,6 +124,9 @@ export class CollectionAdminService {
view.groups = c.groups;
view.users = c.users;
view.assigned = c.assigned;
+ view.readOnly = c.readOnly;
+ view.hidePasswords = c.hidePasswords;
+ view.manage = c.manage;
}
return view;
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index f28bff066a..c8dfa14c8b 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -7905,5 +7905,44 @@
},
"unassignedItemsBannerSelfHost": {
"message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible."
+ },
+ "restrictedGroupAccessDesc": {
+ "message": "You cannot add yourself to a group."
+ },
+ "deleteProvider": {
+ "message": "Delete provider"
+ },
+ "deleteProviderConfirmation": {
+ "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data."
+ },
+ "deleteProviderName": {
+ "message": "Cannot delete $ID$",
+ "placeholders": {
+ "id": {
+ "content": "$1",
+ "example": "John Smith"
+ }
+ }
+ },
+ "deleteProviderWarningDesc": {
+ "message": "You must unlink all clients before you can delete $ID$",
+ "placeholders": {
+ "id": {
+ "content": "$1",
+ "example": "John Smith"
+ }
+ }
+ },
+ "providerDeleted": {
+ "message": "Provider deleted"
+ },
+ "providerDeletedDesc": {
+ "message": "The Provider and all associated data has been deleted."
+ },
+ "deleteProviderRecoverConfirmDesc": {
+ "message": "You have requested to delete this Provider. Use the button below to confirm."
+ },
+ "deleteProviderWarning": {
+ "message": "Deleting your provider is permanent. It cannot be undone."
}
}
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
index 81cc7c2919..0d75973712 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
@@ -8,6 +8,7 @@ import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vau
import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
+import { DangerZoneComponent } from "../../../../../../apps/web/src/app/auth/settings/account/danger-zone.component";
import { ManageClientOrganizationSubscriptionComponent } from "../../billing/providers/clients/manage-client-organization-subscription.component";
import { ManageClientOrganizationsComponent } from "../../billing/providers/clients/manage-client-organizations.component";
@@ -40,6 +41,7 @@ import { SetupComponent } from "./setup/setup.component";
ProvidersLayoutComponent,
PaymentMethodWarningsModule,
TaxInfoComponent,
+ DangerZoneComponent,
],
declarations: [
AcceptProviderComponent,
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.html
index ea634e5ebc..10f6d14425 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.html
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.html
@@ -1,51 +1,58 @@
-
-
-
- {{ "loading" | i18n }}
-
-