mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[feature] Refine content of the organization delete request confirmation warning (#1508)
This commit updates the organization delete request confirmation warning based on new copy from the product team. Changes are as follows: * Add a load toggle to the organization delete modal, as we now have data to collect. * Adjust how the families for enterprise error state for invalid sponserships connects with the organization delete component. Previously it just sent in a localization key to use for the description, but this commit adds a union type for identifying different delete flows and moves the FOE description localization key into the template with a condition. * Move the callout on the organization delete component to above the description text. * Adjust content of the typical organization delete request description based on copy from the product team. * This includes a list of item types in use by the organization that will be deleted and the amount of each type that exist in the organization.
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
(ngSubmit)="submit()"
|
(ngSubmit)="submit()"
|
||||||
[appApiAction]="formPromise"
|
[appApiAction]="formPromise"
|
||||||
ngNativeValidate
|
ngNativeValidate
|
||||||
|
*ngIf="loaded"
|
||||||
>
|
>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2 class="modal-title" id="deleteOrganizationTitle">{{ "deleteOrganization" | i18n }}</h2>
|
<h2 class="modal-title" id="deleteOrganizationTitle">{{ "deleteOrganization" | i18n }}</h2>
|
||||||
@@ -19,8 +20,30 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>{{ descriptionKey | i18n }}</p>
|
<app-callout type="warning">{{
|
||||||
<app-callout type="warning">{{ "deleteOrganizationWarning" | i18n }}</app-callout>
|
"deletingOrganizationIsPermanentWarning" | i18n: organizationName
|
||||||
|
}}</app-callout>
|
||||||
|
<p id="organizationDeleteDescription">
|
||||||
|
<ng-container
|
||||||
|
*ngIf="
|
||||||
|
deleteOrganizationRequestType === 'InvalidFamiliesForEnterprise';
|
||||||
|
else regularDelete
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ "orgCreatedSponsorshipInvalid" | i18n }}
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #regularDelete>
|
||||||
|
<ng-container *ngIf="organizationContentSummary.totalItemCount > 0">
|
||||||
|
{{ "deletingOrganizationContentWarning" | i18n: organizationName }}
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let type of organizationContentSummary.itemCountByType">
|
||||||
|
{{ type.count }} {{ type.localizationKey | i18n }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{ "deletingOrganizationActiveUserAccountsWarning" | i18n }}
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</p>
|
||||||
<app-verify-master-password [(ngModel)]="masterPassword" ngDefaultControl name="secret">
|
<app-verify-master-password [(ngModel)]="masterPassword" ngDefaultControl name="secret">
|
||||||
</app-verify-master-password>
|
</app-verify-master-password>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,58 @@
|
|||||||
import { Component, EventEmitter, Output } from "@angular/core";
|
import { Component, EventEmitter, OnInit, Output } from "@angular/core";
|
||||||
|
|
||||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||||
|
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||||
import { LogService } from "jslib-common/abstractions/log.service";
|
import { LogService } from "jslib-common/abstractions/log.service";
|
||||||
|
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||||
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
|
import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
|
||||||
|
import { CipherType } from "jslib-common/enums/cipherType";
|
||||||
|
import { Utils } from "jslib-common/misc/utils";
|
||||||
|
import { CipherView } from "jslib-common/models/view/cipherView";
|
||||||
import { Verification } from "jslib-common/types/verification";
|
import { Verification } from "jslib-common/types/verification";
|
||||||
|
|
||||||
|
class CountBasedLocalizationKey {
|
||||||
|
singular: string;
|
||||||
|
plural: string;
|
||||||
|
|
||||||
|
getKey(count: number) {
|
||||||
|
return count == 1 ? this.singular : this.plural;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(singular: string, plural: string) {
|
||||||
|
this.singular = singular;
|
||||||
|
this.plural = plural;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrganizationContentSummaryItem {
|
||||||
|
count: number;
|
||||||
|
get localizationKey(): string {
|
||||||
|
return this.localizationKeyOptions.getKey(this.count);
|
||||||
|
}
|
||||||
|
private localizationKeyOptions: CountBasedLocalizationKey;
|
||||||
|
constructor(count: number, localizationKeyOptions: CountBasedLocalizationKey) {
|
||||||
|
this.count = count;
|
||||||
|
this.localizationKeyOptions = localizationKeyOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrganizationContentSummary {
|
||||||
|
totalItemCount = 0;
|
||||||
|
itemCountByType: OrganizationContentSummaryItem[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-delete-organization",
|
selector: "app-delete-organization",
|
||||||
templateUrl: "delete-organization.component.html",
|
templateUrl: "delete-organization.component.html",
|
||||||
})
|
})
|
||||||
export class DeleteOrganizationComponent {
|
export class DeleteOrganizationComponent implements OnInit {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
descriptionKey = "deleteOrganizationDesc";
|
loaded: boolean;
|
||||||
|
deleteOrganizationRequestType: "InvalidFamiliesForEnterprise" | "RegularDelete" = "RegularDelete";
|
||||||
|
organizationName: string;
|
||||||
|
organizationContentSummary: OrganizationContentSummary = new OrganizationContentSummary();
|
||||||
@Output() onSuccess: EventEmitter<any> = new EventEmitter();
|
@Output() onSuccess: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
masterPassword: Verification;
|
masterPassword: Verification;
|
||||||
@@ -24,9 +63,15 @@ export class DeleteOrganizationComponent {
|
|||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private userVerificationService: UserVerificationService,
|
private userVerificationService: UserVerificationService,
|
||||||
private logService: LogService
|
private logService: LogService,
|
||||||
|
private cipherService: CipherService,
|
||||||
|
private organizationService: OrganizationService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.userVerificationService
|
this.formPromise = this.userVerificationService
|
||||||
@@ -43,4 +88,44 @@ export class DeleteOrganizationComponent {
|
|||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async load() {
|
||||||
|
this.organizationName = (await this.organizationService.get(this.organizationId)).name;
|
||||||
|
this.organizationContentSummary = await this.buildOrganizationContentSummary();
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async buildOrganizationContentSummary(): Promise<OrganizationContentSummary> {
|
||||||
|
const organizationContentSummary = new OrganizationContentSummary();
|
||||||
|
const organizationItems = (
|
||||||
|
await this.cipherService.getAllFromApiForOrganization(this.organizationId)
|
||||||
|
).filter((item) => item.deletedDate == null);
|
||||||
|
|
||||||
|
if (organizationItems.length < 1) {
|
||||||
|
return organizationContentSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
organizationContentSummary.totalItemCount = organizationItems.length;
|
||||||
|
for (const cipherType of Utils.iterateEnum(CipherType)) {
|
||||||
|
const count = this.getOrganizationItemCountByType(organizationItems, cipherType);
|
||||||
|
if (count > 0) {
|
||||||
|
organizationContentSummary.itemCountByType.push(
|
||||||
|
new OrganizationContentSummaryItem(
|
||||||
|
count,
|
||||||
|
this.getOrganizationItemLocalizationKeysByType(CipherType[cipherType])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return organizationContentSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOrganizationItemCountByType(items: CipherView[], type: CipherType) {
|
||||||
|
return items.filter((item) => item.type == type).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOrganizationItemLocalizationKeysByType(type: string): CountBasedLocalizationKey {
|
||||||
|
return new CountBasedLocalizationKey(`type${type}`, `type${type}Plural`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit {
|
|||||||
this.deleteModalRef,
|
this.deleteModalRef,
|
||||||
(comp) => {
|
(comp) => {
|
||||||
comp.organizationId = organizationId;
|
comp.organizationId = organizationId;
|
||||||
comp.descriptionKey = "orgCreatedSponsorshipInvalid";
|
comp.deleteOrganizationRequestType = "InvalidFamiliesForEnterprise";
|
||||||
comp.onSuccess.subscribe(() => {
|
comp.onSuccess.subscribe(() => {
|
||||||
this.router.navigate(["/"]);
|
this.router.navigate(["/"]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -300,6 +300,18 @@
|
|||||||
"typeSecureNote": {
|
"typeSecureNote": {
|
||||||
"message": "Secure Note"
|
"message": "Secure Note"
|
||||||
},
|
},
|
||||||
|
"typeLoginPlural": {
|
||||||
|
"message": "Logins"
|
||||||
|
},
|
||||||
|
"typeCardPlural": {
|
||||||
|
"message": "Cards"
|
||||||
|
},
|
||||||
|
"typeIdentityPlural": {
|
||||||
|
"message": "Identities"
|
||||||
|
},
|
||||||
|
"typeSecureNotePlural": {
|
||||||
|
"message": "Secure Notes"
|
||||||
|
},
|
||||||
"folders": {
|
"folders": {
|
||||||
"message": "Folders"
|
"message": "Folders"
|
||||||
},
|
},
|
||||||
@@ -2782,11 +2794,26 @@
|
|||||||
"deleteOrganization": {
|
"deleteOrganization": {
|
||||||
"message": "Delete Organization"
|
"message": "Delete Organization"
|
||||||
},
|
},
|
||||||
"deleteOrganizationDesc": {
|
"deletingOrganizationContentWarning": {
|
||||||
"message": "Proceed below to delete this organization and all associated data. Individual user accounts will remain, though they will not be associated to this organization anymore. "
|
"message": "Enter the master password to confirm deletion of $ORGANIZATION$ and all associated data. Vault data in $ORGANIZATION$ includes:",
|
||||||
|
"placeholders": {
|
||||||
|
"organization": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "My Org Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"deleteOrganizationWarning": {
|
"deletingOrganizationActiveUserAccountsWarning": {
|
||||||
"message": "Deleting the organization is permanent. It cannot be undone."
|
"message": "User accounts will remain active after deletion but will no longer be associated to this organization."
|
||||||
|
},
|
||||||
|
"deletingOrganizationIsPermanentWarning": {
|
||||||
|
"message": "Deleting $ORGANIZATION$ is permanent and irreversible.",
|
||||||
|
"placeholders": {
|
||||||
|
"organization": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "My Org Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"organizationDeleted": {
|
"organizationDeleted": {
|
||||||
"message": "Organization Deleted"
|
"message": "Organization Deleted"
|
||||||
|
|||||||
Reference in New Issue
Block a user