From 020c9dc1b86112d15655fbe268ff76198f8aba61 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Wed, 10 Jul 2024 17:02:00 -0700 Subject: [PATCH] feat: Allow superadmins to delete org (#1788) Resolves https://github.com/webrecorder/browsertrix/issues/1453 ### Changes Allows super-admins to delete an org via UI --- frontend/src/components/orgs-list.ts | 165 ++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/orgs-list.ts b/frontend/src/components/orgs-list.ts index f7cb9775..493b4ec3 100644 --- a/frontend/src/components/orgs-list.ts +++ b/frontend/src/components/orgs-list.ts @@ -1,8 +1,8 @@ import { localized, msg, str } from "@lit/localize"; -import { - // type SlChangeEvent, - type SlInput, - // type SlMenuItem, +import type { + SlButton, + SlChangeEvent, + SlInput, } from "@shoelace-style/shoelace"; import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js"; import { css, html, nothing } from "lit"; @@ -52,6 +52,12 @@ export class OrgsList extends TailwindElement { @query("#orgReadOnlyDialog") private readonly orgReadOnlyDialog?: Dialog | null; + @query("#orgDeleteDialog") + private readonly orgDeleteDialog?: Dialog | null; + + @query("#orgDeleteButton") + private readonly orgDeleteButton?: SlButton | null; + private readonly api = new APIController(this); private readonly navigate = new NavigateController(this); private readonly notify = new NotifyController(this); @@ -86,6 +92,7 @@ export class OrgsList extends TailwindElement { ${this.renderOrgQuotas()} ${this.renderOrgReadOnly()} + ${this.renderOrgDelete()} `; } @@ -157,7 +164,7 @@ export class OrgsList extends TailwindElement {

${msg( html`Are you sure you want to make - ${org.name} + ${org.name} read-only? Members will no longer be able to crawl, upload files, create browser profiles, or create collections.`, )} @@ -208,6 +215,119 @@ export class OrgsList extends TailwindElement { `; } + private renderOrgDelete() { + return html` + (this.currOrg = null)} + > + ${when(this.currOrg, (org) => { + const confirmationStr = msg(str`Delete ${org.name}`); + return html` +

+ ${msg( + html`Are you sure you want to delete + ${org.name}? This + cannot be undone.`, + )} +

+ +

+ ${msg( + html`Deleting an org will delete all + + + + of data associated with the org.`, + )} +

+ + + { + const { value } = e.target as SlInput; + this.orgDeleteButton!.disabled = value !== confirmationStr; + }} + > + + ${msg(str`Type "${confirmationStr}" to confirm`)} + + +
+ void this.orgDeleteDialog?.hide()} + > + ${msg("Cancel")} + + { + await this.deleteOrg(org); + void this.orgDeleteDialog?.hide(); + }} + > + ${msg("Delete Org")} + +
+ `; + })} + + `; + } + private onUpdateQuota(e: CustomEvent) { const inputEl = e.target as SlInput; const quotas = this.currOrg?.quotas; @@ -288,6 +408,30 @@ export class OrgsList extends TailwindElement { } } + private async deleteOrg(org: OrgData) { + try { + await this.api.fetch(`/orgs/${org.id}`, this.authState!, { + method: "DELETE", + }); + + this.orgList = this.orgList?.filter((o) => o.id !== org.id); + + this.notify.toast({ + message: msg(str`Org "${org.name}" has been deleted.`), + variant: "success", + icon: "check2-circle", + }); + } catch (e) { + console.debug(e); + + this.notify.toast({ + message: msg("Sorry, couldn't delete org at this time."), + variant: "danger", + icon: "exclamation-octagon", + }); + } + } + private readonly renderOrg = (org: OrgData) => { if (!this.userInfo) return; @@ -419,6 +563,17 @@ export class OrgsList extends TailwindElement { ${msg("Make Read-Only")} `} + + { + this.currOrg = org; + void this.orgDeleteDialog?.show(); + }} + > + + ${msg("Delete Org")} +