mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-27 17:18:04 +01: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:
parent
8242989b9d
commit
e103ddf02f
@ -6,6 +6,7 @@
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
*ngIf="loaded"
|
||||
>
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="deleteOrganizationTitle">{{ "deleteOrganization" | i18n }}</h2>
|
||||
@ -19,8 +20,30 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ descriptionKey | i18n }}</p>
|
||||
<app-callout type="warning">{{ "deleteOrganizationWarning" | i18n }}</app-callout>
|
||||
<app-callout type="warning">{{
|
||||
"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>
|
||||
</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 { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.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 { 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";
|
||||
|
||||
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({
|
||||
selector: "app-delete-organization",
|
||||
templateUrl: "delete-organization.component.html",
|
||||
})
|
||||
export class DeleteOrganizationComponent {
|
||||
export class DeleteOrganizationComponent implements OnInit {
|
||||
organizationId: string;
|
||||
descriptionKey = "deleteOrganizationDesc";
|
||||
loaded: boolean;
|
||||
deleteOrganizationRequestType: "InvalidFamiliesForEnterprise" | "RegularDelete" = "RegularDelete";
|
||||
organizationName: string;
|
||||
organizationContentSummary: OrganizationContentSummary = new OrganizationContentSummary();
|
||||
@Output() onSuccess: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
masterPassword: Verification;
|
||||
@ -24,9 +63,15 @@ export class DeleteOrganizationComponent {
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
private logService: LogService
|
||||
private logService: LogService,
|
||||
private cipherService: CipherService,
|
||||
private organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.load();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
this.formPromise = this.userVerificationService
|
||||
@ -43,4 +88,44 @@ export class DeleteOrganizationComponent {
|
||||
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,
|
||||
(comp) => {
|
||||
comp.organizationId = organizationId;
|
||||
comp.descriptionKey = "orgCreatedSponsorshipInvalid";
|
||||
comp.deleteOrganizationRequestType = "InvalidFamiliesForEnterprise";
|
||||
comp.onSuccess.subscribe(() => {
|
||||
this.router.navigate(["/"]);
|
||||
});
|
||||
|
@ -300,6 +300,18 @@
|
||||
"typeSecureNote": {
|
||||
"message": "Secure Note"
|
||||
},
|
||||
"typeLoginPlural": {
|
||||
"message": "Logins"
|
||||
},
|
||||
"typeCardPlural": {
|
||||
"message": "Cards"
|
||||
},
|
||||
"typeIdentityPlural": {
|
||||
"message": "Identities"
|
||||
},
|
||||
"typeSecureNotePlural": {
|
||||
"message": "Secure Notes"
|
||||
},
|
||||
"folders": {
|
||||
"message": "Folders"
|
||||
},
|
||||
@ -2782,11 +2794,26 @@
|
||||
"deleteOrganization": {
|
||||
"message": "Delete Organization"
|
||||
},
|
||||
"deleteOrganizationDesc": {
|
||||
"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. "
|
||||
"deletingOrganizationContentWarning": {
|
||||
"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": {
|
||||
"message": "Deleting the organization is permanent. It cannot be undone."
|
||||
"deletingOrganizationActiveUserAccountsWarning": {
|
||||
"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": {
|
||||
"message": "Organization Deleted"
|
||||
|
Loading…
Reference in New Issue
Block a user