From e103ddf02ffa390206e370a7a17523d48fa73eec Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Fri, 4 Mar 2022 09:03:48 -0500 Subject: [PATCH] [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. --- .../delete-organization.component.html | 27 +++++- .../settings/delete-organization.component.ts | 93 ++++++++++++++++++- ...families-for-enterprise-setup.component.ts | 2 +- src/locales/en/messages.json | 35 ++++++- 4 files changed, 146 insertions(+), 11 deletions(-) diff --git a/src/app/organizations/settings/delete-organization.component.html b/src/app/organizations/settings/delete-organization.component.html index 17af9539be0..2c3cae47a35 100644 --- a/src/app/organizations/settings/delete-organization.component.html +++ b/src/app/organizations/settings/delete-organization.component.html @@ -6,6 +6,7 @@ (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate + *ngIf="loaded" > diff --git a/src/app/organizations/settings/delete-organization.component.ts b/src/app/organizations/settings/delete-organization.component.ts index 6bdb6fbb768..799d916a8b4 100644 --- a/src/app/organizations/settings/delete-organization.component.ts +++ b/src/app/organizations/settings/delete-organization.component.ts @@ -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 = 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 { + 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 { + 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`); + } } diff --git a/src/app/organizations/sponsorships/families-for-enterprise-setup.component.ts b/src/app/organizations/sponsorships/families-for-enterprise-setup.component.ts index 36b92b61fe1..ee99d19eede 100644 --- a/src/app/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/src/app/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -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(["/"]); }); diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 147f78340b8..612bf822f25 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -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"