feat: Minor improvements to superadmin view (#1971)

Resolves https://github.com/webrecorder/browsertrix/issues/1951

### Changes

- Shows date org was created in superadmin org list
- Visually differentiates unnamed org ID
- Adds "Admin" badge to app header to make current login more apparent
- Fixes logic to show "create org" dialog if there are no orgs in an
instance
- Refactors `btrix-home` to remove unused references to non-superadmin
org list


---------
Co-authored-by: Henry Wilkinson <henry@wilkinson.graphics>
This commit is contained in:
sua yoo 2024-07-25 18:47:40 -04:00 committed by GitHub
parent 94e985ae13
commit daeb7448f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 55 deletions

View File

@ -16,7 +16,7 @@ import { NavigateController } from "@/controllers/navigate";
import { NotifyController } from "@/controllers/notify"; import { NotifyController } from "@/controllers/notify";
import type { CurrentUser } from "@/types/user"; import type { CurrentUser } from "@/types/user";
import type { AuthState } from "@/utils/AuthService"; import type { AuthState } from "@/utils/AuthService";
import { formatNumber } from "@/utils/localization"; import { formatNumber, getLocale } from "@/utils/localization";
import type { OrgData } from "@/utils/orgs"; import type { OrgData } from "@/utils/orgs";
/** /**
@ -27,7 +27,7 @@ import type { OrgData } from "@/utils/orgs";
export class OrgsList extends TailwindElement { export class OrgsList extends TailwindElement {
static styles = css` static styles = css`
btrix-table { btrix-table {
grid-template-columns: min-content [clickable-start] 50ch auto auto [clickable-end] min-content; grid-template-columns: min-content [clickable-start] 50ch auto auto auto [clickable-end] min-content;
} }
`; `;
@ -76,6 +76,9 @@ export class OrgsList extends TailwindElement {
<btrix-table-header-cell class="px-2"> <btrix-table-header-cell class="px-2">
${msg("Name")} ${msg("Name")}
</btrix-table-header-cell> </btrix-table-header-cell>
<btrix-table-header-cell class="px-2">
${msg("Created")}
</btrix-table-header-cell>
<btrix-table-header-cell class="px-2"> <btrix-table-header-cell class="px-2">
${msg("Members")} ${msg("Members")}
</btrix-table-header-cell> </btrix-table-header-cell>
@ -500,7 +503,7 @@ export class OrgsList extends TailwindElement {
</btrix-table-cell> </btrix-table-cell>
<btrix-table-cell class="p-2" rowClickTarget="a"> <btrix-table-cell class="p-2" rowClickTarget="a">
<a <a
class=${org.readOnly ? "text-neutral-400" : "text-neutral-900"} class=${org.readOnly ? "text-neutral-500" : "text-neutral-900"}
href="/orgs/${org.slug}" href="/orgs/${org.slug}"
@click=${this.navigate.link} @click=${this.navigate.link}
aria-disabled="${!isUserOrg}" aria-disabled="${!isUserOrg}"
@ -508,9 +511,22 @@ export class OrgsList extends TailwindElement {
${org.default ${org.default
? html`<btrix-tag class="mr-1">${msg("Default")}</btrix-tag>` ? html`<btrix-tag class="mr-1">${msg("Default")}</btrix-tag>`
: nothing} : nothing}
${org.name} ${org.name === org.id
? html`<code class="text-neutral-400">${org.id}</code>`
: org.name}
</a> </a>
</btrix-table-cell> </btrix-table-cell>
<btrix-table-cell class="p-2">
<sl-format-date
lang=${getLocale()}
class="truncate"
date=${`${org.created}Z`}
month="2-digit"
day="2-digit"
year="2-digit"
></sl-format-date>
</btrix-table-cell>
<btrix-table-cell class="p-2"> <btrix-table-cell class="p-2">
${memberCount ? formatNumber(memberCount) : none} ${memberCount ? formatNumber(memberCount) : none}
</btrix-table-cell> </btrix-table-cell>

View File

@ -292,6 +292,7 @@ export class App extends LiteElement {
class="mx-auto box-border flex h-12 items-center justify-between px-3 xl:pl-6" class="mx-auto box-border flex h-12 items-center justify-between px-3 xl:pl-6"
> >
<a <a
class="items-between flex gap-2"
aria-label="home" aria-label="home"
href=${homeHref} href=${homeHref}
@click=${(e: MouseEvent) => { @click=${(e: MouseEvent) => {
@ -302,6 +303,9 @@ export class App extends LiteElement {
}} }}
> >
<img class="h-6" alt="Browsertrix logo" src=${brandLockupColor} /> <img class="h-6" alt="Browsertrix logo" src=${brandLockupColor} />
${isSuperAdmin
? html`<btrix-tag>${msg("Admin")}</btrix-tag>`
: nothing}
</a> </a>
${isSuperAdmin ${isSuperAdmin
? html` ? html`

View File

@ -1,7 +1,7 @@
import { localized, msg, str } from "@lit/localize"; import { localized, msg, str } from "@lit/localize";
import type { SlInput, SlInputEvent } from "@shoelace-style/shoelace"; import type { SlInput, SlInputEvent } from "@shoelace-style/shoelace";
import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js"; import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js";
import { type PropertyValues, type TemplateResult } from "lit"; import { type PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import type { InviteSuccessDetail } from "@/features/accounts/invite-form"; import type { InviteSuccessDetail } from "@/features/accounts/invite-form";
@ -15,6 +15,11 @@ import type { OrgData } from "@/utils/orgs";
import slugifyStrict from "@/utils/slugify"; import slugifyStrict from "@/utils/slugify";
/** /**
* Home page when org is not selected.
* Currently, only visible to superadmins--redirects to user's org, otherwise
*
* TODO Refactor out superadmin UI
*
* @fires btrix-update-user-info * @fires btrix-update-user-info
*/ */
@localized() @localized()
@ -62,27 +67,24 @@ export class Home extends LiteElement {
this.navTo(`/orgs/${this.slug}`); this.navTo(`/orgs/${this.slug}`);
} else if (changedProperties.has("userInfo") && this.userInfo) { } else if (changedProperties.has("userInfo") && this.userInfo) {
if (this.userInfo.isSuperAdmin) { if (this.userInfo.isSuperAdmin) {
if (this.userInfo.orgs.length) {
void this.fetchOrgs(); void this.fetchOrgs();
} else {
this.isAddingOrg = true;
this.isAddOrgFormVisible = true;
}
} else { } else {
this.navTo(`/account/settings`); this.navTo(`/account/settings`);
} }
} }
} }
async updated( render() {
changedProperties: PropertyValues<this> & Map<string, unknown>, if (!this.userInfo || !this.userInfo.isSuperAdmin) {
) { return;
const orgListUpdated = changedProperties.has("orgList") && this.orgList;
const userInfoUpdated = changedProperties.has("userInfo") && this.userInfo;
if (orgListUpdated || userInfoUpdated) {
if (this.userInfo?.isSuperAdmin && this.orgList && !this.orgList.length) {
this.isAddingOrg = true;
}
}
} }
render() { if (this.userInfo.orgs.length && !this.orgList) {
if (!this.userInfo || !this.orgList) {
return html` return html`
<div class="my-24 flex items-center justify-center text-3xl"> <div class="my-24 flex items-center justify-center text-3xl">
<sl-spinner></sl-spinner> <sl-spinner></sl-spinner>
@ -90,29 +92,19 @@ export class Home extends LiteElement {
`; `;
} }
let title: string | undefined;
let content: TemplateResult<1> | undefined;
if (this.userInfo.isSuperAdmin) {
title = msg("Welcome");
content = this.renderAdminOrgs();
} else {
title = msg("Organizations");
content = this.renderLoggedInNonAdmin();
}
return html` return html`
<div class="bg-white"> <div class="bg-white">
<header <header
class="mx-auto box-border w-full max-w-screen-desktop px-3 py-4 md:py-8" class="mx-auto box-border w-full max-w-screen-desktop px-3 py-4 md:py-8"
> >
<h1 class="text-xl font-medium">${title}</h1> <h1 class="text-xl font-medium">${msg("Welcome")}</h1>
</header> </header>
<hr /> <hr />
</div> </div>
<main class="mx-auto box-border w-full max-w-screen-desktop px-3 py-4"> <main class="mx-auto box-border w-full max-w-screen-desktop px-3 py-4">
${content} ${this.renderAdminOrgs()}
</main> </main>
${this.renderAddOrgDialog()}
`; `;
} }
@ -182,8 +174,6 @@ export class Home extends LiteElement {
</section> </section>
</div> </div>
</div> </div>
${this.renderAddOrgDialog()}
`; `;
} }
@ -277,24 +267,6 @@ export class Home extends LiteElement {
`; `;
} }
private renderLoggedInNonAdmin() {
if (this.orgList && !this.orgList.length) {
return html`<div class="rounded-lg border bg-white p-4 md:p-8">
<p class="text-center text-neutral-400">
${msg("You don't have any organizations.")}
</p>
</div>`;
}
return html`
<btrix-orgs-list
.userInfo=${this.userInfo}
.orgList=${this.orgList}
?skeleton=${!this.orgList}
></btrix-orgs-list>
`;
}
private renderInvite() { private renderInvite() {
return html` return html`
<btrix-invite-form <btrix-invite-form
@ -384,9 +356,6 @@ export class Home extends LiteElement {
// Update user info since orgs are checked against userInfo.orgs // Update user info since orgs are checked against userInfo.orgs
this.dispatchEvent(new CustomEvent("btrix-update-user-info")); this.dispatchEvent(new CustomEvent("btrix-update-user-info"));
await this.updateComplete;
void this.fetchOrgs();
this.notify({ this.notify({
message: msg(str`Created new org named "${params.name}".`), message: msg(str`Created new org named "${params.name}".`),
variant: "success", variant: "success",

View File

@ -31,6 +31,7 @@ export type OrgQuotas = {
export type OrgData = { export type OrgData = {
id: string; id: string;
name: string; name: string;
created: string;
slug: string; slug: string;
default: boolean; default: boolean;
quotas: OrgQuotas; quotas: OrgQuotas;