From 8c4e481bd35b2032ff1f3c0822e7b8923233d0e9 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Tue, 23 Jul 2024 16:51:28 -0700 Subject: [PATCH] feat: Improve UX when user doesn't belong to any orgs (#1953) Directs user that doesn't belong to any orgs to account settings page, with banner. Also contains some minor out-of-scope changes: - Refactors `isAdmin` key to `isSuperAdmin` for more legibility on whether current user is superadmin or regular user without orgs - Adds "cancel" button to change password form --- .../src/features/accounts/account-settings.ts | 11 +++- frontend/src/index.test.ts | 2 +- frontend/src/index.ts | 55 +++++++++++++++---- frontend/src/pages/home.ts | 12 ++-- frontend/src/pages/org/index.ts | 13 +---- frontend/src/types/user.ts | 2 +- frontend/src/utils/form.ts | 4 +- frontend/src/utils/user.ts | 2 +- 8 files changed, 69 insertions(+), 32 deletions(-) diff --git a/frontend/src/features/accounts/account-settings.ts b/frontend/src/features/accounts/account-settings.ts index 7b9c277e..665ace5e 100644 --- a/frontend/src/features/accounts/account-settings.ts +++ b/frontend/src/features/accounts/account-settings.ts @@ -257,6 +257,15 @@ export class AccountSettings extends LiteElement { str`Choose a strong password between ${PASSWORD_MINLENGTH}-${PASSWORD_MAXLENGTH} characters.`, )}

+ (this.isChangingPassword = false)} + > + ${msg("Cancel")} + `, () => html` -
+

${msg("Password")}

diff --git a/frontend/src/index.test.ts b/frontend/src/index.test.ts index a3ba9a8e..a0f0d67d 100644 --- a/frontend/src/index.test.ts +++ b/frontend/src/index.test.ts @@ -103,7 +103,7 @@ describe("browsertrix-app", () => { email: "test-user@example.com", name: "Test User", isVerified: false, - isAdmin: false, + isSuperAdmin: false, orgs: [ { id: "test_org_id", diff --git a/frontend/src/index.ts b/frontend/src/index.ts index bd7cfcc4..67f517d0 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -1,4 +1,4 @@ -import { localized, msg } from "@lit/localize"; +import { localized, msg, str } from "@lit/localize"; import type { SlDialog } from "@shoelace-style/shoelace"; import { nothing, render, type TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; @@ -149,7 +149,7 @@ export class App extends LiteElement { if ( orgs.length && - !this.appState.userInfo!.isAdmin && + !this.appState.userInfo!.isSuperAdmin && !this.appState.orgSlug ) { const firstOrg = orgs[0].slug; @@ -232,7 +232,7 @@ export class App extends LiteElement { render() { return html`
- ${this.renderNavBar()} + ${this.renderNavBar()} ${this.renderAlertBanner()}
${this.renderPage()}
${this.renderFooter()}
@@ -247,10 +247,41 @@ export class App extends LiteElement { `; } + private renderAlertBanner() { + if (this.appState.userInfo?.orgs && !this.appState.userInfo.orgs.length) { + return this.renderNoOrgsBanner(); + } + } + + private renderNoOrgsBanner() { + return html` +
+
+ + + + ${msg("Your account isn't quite set up yet")} + + ${msg( + "You must belong to at least one org in order to access Browsertrix features.", + )} + ${this.appState.settings?.salesEmail + ? msg( + str`If you haven't received an invitation to an org, please contact us at ${this.appState.settings.salesEmail}.`, + ) + : msg( + str`If you haven't received an invitation to an org, please contact your Browsertrix administrator.`, + )} + +
+
+ `; + } + private renderNavBar() { - const isAdmin = this.appState.userInfo?.isAdmin; + const isSuperAdmin = this.appState.userInfo?.isSuperAdmin; let homeHref = "/"; - if (!isAdmin && this.appState.orgSlug) { + if (!isSuperAdmin && this.appState.orgSlug) { homeHref = `/orgs/${this.appState.orgSlug}`; } @@ -263,7 +294,7 @@ export class App extends LiteElement { aria-label="home" href=${homeHref} @click=${(e: MouseEvent) => { - if (isAdmin) { + if (isSuperAdmin) { this.clearSelectedOrg(); } this.navLink(e); @@ -271,7 +302,7 @@ export class App extends LiteElement { > Browsertrix logo - ${isAdmin + ${isSuperAdmin ? html`
${msg("Account Settings")} - ${this.appState.userInfo?.isAdmin + ${this.appState.userInfo?.isSuperAdmin ? html` this.navigate(ROUTES.usersInvite)} > @@ -387,7 +418,7 @@ export class App extends LiteElement { }} > ${when( - this.appState.userInfo.isAdmin, + this.appState.userInfo.isSuperAdmin, () => html` ) { if (changedProperties.has("slug") && this.slug) { this.navTo(`/orgs/${this.slug}`); - } else if (changedProperties.has("authState") && this.authState) { - void this.fetchOrgs(); + } else if (changedProperties.has("userInfo") && this.userInfo) { + if (this.userInfo.isSuperAdmin) { + void this.fetchOrgs(); + } else { + this.navTo(`/account/settings`); + } } } @@ -71,7 +75,7 @@ export class Home extends LiteElement { const orgListUpdated = changedProperties.has("orgList") && this.orgList; const userInfoUpdated = changedProperties.has("userInfo") && this.userInfo; if (orgListUpdated || userInfoUpdated) { - if (this.userInfo?.isAdmin && this.orgList && !this.orgList.length) { + if (this.userInfo?.isSuperAdmin && this.orgList && !this.orgList.length) { this.isAddingOrg = true; } } @@ -89,7 +93,7 @@ export class Home extends LiteElement { let title: string | undefined; let content: TemplateResult<1> | undefined; - if (this.userInfo.isAdmin) { + if (this.userInfo.isSuperAdmin) { title = msg("Welcome"); content = this.renderAdminOrgs(); } else { diff --git a/frontend/src/pages/org/index.ts b/frontend/src/pages/org/index.ts index 78fecd81..c39753df 100644 --- a/frontend/src/pages/org/index.ts +++ b/frontend/src/pages/org/index.ts @@ -197,16 +197,7 @@ export class Org extends LiteElement { if (org) { this.navTo(`/orgs/${org.slug}`); } else { - // Handle edge case where user does not belong - // to any orgs but is attempting to log in - // TODO check if hosted instance and show support email if so - this.notify({ - message: msg( - "You must belong to at least one org in order to log in. Please contact your Browsertrix admin to resolve the issue.", - ), - variant: "danger", - icon: "exclamation-octagon", - }); + this.navTo(`/account/settings`); } return; @@ -376,7 +367,7 @@ export class Org extends LiteElement { path: "browser-profiles", }), )} - ${when(this.isAdmin || this.userInfo?.isAdmin, () => + ${when(this.isAdmin || this.userInfo?.isSuperAdmin, () => this.renderNavTab({ tabName: "settings", label: msg("Org Settings"), diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts index 544338fe..69b734b0 100644 --- a/frontend/src/types/user.ts +++ b/frontend/src/types/user.ts @@ -30,6 +30,6 @@ export type CurrentUser = { email: string; name: string; isVerified: boolean; - isAdmin: boolean; + isSuperAdmin: boolean; orgs: UserOrg[]; }; diff --git a/frontend/src/utils/form.ts b/frontend/src/utils/form.ts index 56788c16..6df6e62f 100644 --- a/frontend/src/utils/form.ts +++ b/frontend/src/utils/form.ts @@ -54,7 +54,9 @@ export function maxLengthValidator(maxLength: number): MaxLengthValidator { el.setCustomValidity( isInvalid - ? msg(str`Please shorten this text to ${maxLength} or fewer characters.`) + ? msg( + str`Please shorten this text to ${maxLength} or fewer characters.`, + ) : "", ); diff --git a/frontend/src/utils/user.ts b/frontend/src/utils/user.ts index 930764ab..1d238d66 100644 --- a/frontend/src/utils/user.ts +++ b/frontend/src/utils/user.ts @@ -7,7 +7,7 @@ export function formatAPIUser(userData: APIUser): CurrentUser { email: userData.email, name: userData.name, isVerified: userData.is_verified, - isAdmin: userData.is_superuser, + isSuperAdmin: userData.is_superuser, orgs: userData.orgs, }; }