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
This commit is contained in:
parent
a02f7a6826
commit
8c4e481bd3
@ -257,6 +257,15 @@ export class AccountSettings extends LiteElement {
|
||||
str`Choose a strong password between ${PASSWORD_MINLENGTH}-${PASSWORD_MAXLENGTH} characters.`,
|
||||
)}
|
||||
</p>
|
||||
<sl-button
|
||||
type="reset"
|
||||
size="small"
|
||||
variant="text"
|
||||
class="mx-2"
|
||||
@click=${() => (this.isChangingPassword = false)}
|
||||
>
|
||||
${msg("Cancel")}
|
||||
</sl-button>
|
||||
<sl-button
|
||||
type="submit"
|
||||
size="small"
|
||||
@ -270,7 +279,7 @@ export class AccountSettings extends LiteElement {
|
||||
</form>
|
||||
`,
|
||||
() => html`
|
||||
<div class="flex items-center justify-between px-4 py-3">
|
||||
<div class="flex items-center justify-between px-4 py-2.5">
|
||||
<h2 class="text-lg font-semibold leading-none">
|
||||
${msg("Password")}
|
||||
</h2>
|
||||
|
@ -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",
|
||||
|
@ -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`
|
||||
<div class="min-w-screen flex min-h-screen flex-col">
|
||||
${this.renderNavBar()}
|
||||
${this.renderNavBar()} ${this.renderAlertBanner()}
|
||||
<main class="relative flex flex-auto">${this.renderPage()}</main>
|
||||
<div class="border-t border-neutral-100">${this.renderFooter()}</div>
|
||||
</div>
|
||||
@ -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`
|
||||
<div class="border-b bg-slate-100 py-5">
|
||||
<div class="mx-auto box-border w-full max-w-screen-desktop px-3">
|
||||
<sl-alert variant="warning" open>
|
||||
<sl-icon slot="icon" name="exclamation-triangle-fill"></sl-icon>
|
||||
<strong class="block font-semibold">
|
||||
${msg("Your account isn't quite set up yet")}
|
||||
</strong>
|
||||
${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.`,
|
||||
)}
|
||||
</sl-alert>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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 {
|
||||
>
|
||||
<img class="h-6" alt="Browsertrix logo" src=${brandLockupColor} />
|
||||
</a>
|
||||
${isAdmin
|
||||
${isSuperAdmin
|
||||
? html`
|
||||
<div
|
||||
class="grid grid-flow-col items-center gap-3 text-xs md:gap-5 md:text-sm"
|
||||
@ -316,7 +347,7 @@ export class App extends LiteElement {
|
||||
<sl-icon slot="prefix" name="gear"></sl-icon>
|
||||
${msg("Account Settings")}
|
||||
</sl-menu-item>
|
||||
${this.appState.userInfo?.isAdmin
|
||||
${this.appState.userInfo?.isSuperAdmin
|
||||
? html` <sl-menu-item
|
||||
@click=${() => this.navigate(ROUTES.usersInvite)}
|
||||
>
|
||||
@ -387,7 +418,7 @@ export class App extends LiteElement {
|
||||
}}
|
||||
>
|
||||
${when(
|
||||
this.appState.userInfo.isAdmin,
|
||||
this.appState.userInfo.isSuperAdmin,
|
||||
() => html`
|
||||
<sl-menu-item
|
||||
type="checkbox"
|
||||
@ -415,7 +446,7 @@ export class App extends LiteElement {
|
||||
|
||||
private renderMenuUserInfo() {
|
||||
if (!this.appState.userInfo) return;
|
||||
if (this.appState.userInfo.isAdmin) {
|
||||
if (this.appState.userInfo.isSuperAdmin) {
|
||||
return html`
|
||||
<div class="mb-2">
|
||||
<sl-tag class="uppercase" variant="primary" size="small"
|
||||
@ -614,7 +645,7 @@ export class App extends LiteElement {
|
||||
|
||||
case "usersInvite": {
|
||||
if (this.appState.userInfo) {
|
||||
if (this.appState.userInfo.isAdmin) {
|
||||
if (this.appState.userInfo.isSuperAdmin) {
|
||||
return html`<btrix-users-invite
|
||||
class="mx-auto box-border w-full max-w-screen-desktop p-2 md:py-8"
|
||||
.authState="${this.authService.authState}"
|
||||
@ -631,7 +662,7 @@ export class App extends LiteElement {
|
||||
case "crawls":
|
||||
case "crawl": {
|
||||
if (this.appState.userInfo) {
|
||||
if (this.appState.userInfo.isAdmin) {
|
||||
if (this.appState.userInfo.isSuperAdmin) {
|
||||
return html`<btrix-crawls
|
||||
class="w-full"
|
||||
@notify=${this.onNotify}
|
||||
|
@ -60,8 +60,12 @@ export class Home extends LiteElement {
|
||||
willUpdate(changedProperties: PropertyValues<this>) {
|
||||
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 {
|
||||
|
@ -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"),
|
||||
|
@ -30,6 +30,6 @@ export type CurrentUser = {
|
||||
email: string;
|
||||
name: string;
|
||||
isVerified: boolean;
|
||||
isAdmin: boolean;
|
||||
isSuperAdmin: boolean;
|
||||
orgs: UserOrg[];
|
||||
};
|
||||
|
@ -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.`,
|
||||
)
|
||||
: "",
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user