From dd6c33a59d1d29e5b9c35593e207a644b82a90ac Mon Sep 17 00:00:00 2001 From: sua yoo Date: Thu, 25 Jul 2024 16:57:02 -0400 Subject: [PATCH] feat: Show details of invalid invite (#1970) Resolves https://github.com/webrecorder/browsertrix/issues/1912 ### Changes - Show support email, if available, in invalid invite error message - Separate error message for invite email that doesn't match current user's --- frontend/src/index.ts | 1 + frontend/src/pages/invite/accept.ts | 64 ++++++++++++++++++++++------- frontend/src/pages/invite/join.ts | 33 ++++++++++----- frontend/src/types/app.ts | 1 + 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 67f517d0..e25085e8 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -188,6 +188,7 @@ export class App extends LiteElement { maxScale: 0, billingEnabled: false, salesEmail: "", + supportEmail: "", }; } } diff --git a/frontend/src/pages/invite/accept.ts b/frontend/src/pages/invite/accept.ts index b79459e9..106c7b3e 100644 --- a/frontend/src/pages/invite/accept.ts +++ b/frontend/src/pages/invite/accept.ts @@ -1,6 +1,6 @@ import { localized, msg, str } from "@lit/localize"; import { Task } from "@lit/task"; -import { html } from "lit"; +import { html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { renderInviteMessage } from "./ui/inviteMessage"; @@ -15,7 +15,7 @@ import { ROUTES } from "@/routes"; import type { UserOrg, UserOrgInviteInfo } from "@/types/user"; import { isApiError } from "@/utils/api"; import type { Auth, AuthState } from "@/utils/AuthService"; -import { AppStateService } from "@/utils/state"; +import appState, { AppStateService, use } from "@/utils/state"; import { formatAPIUser } from "@/utils/user"; import "./ui/org-form"; @@ -32,6 +32,9 @@ export class AcceptInvite extends TailwindElement { @property({ type: String }) email?: string; + @use() + appState = appState; + @state() private serverError?: string; @@ -49,9 +52,7 @@ export class AcceptInvite extends TailwindElement { }); get _isLoggedIn(): boolean { - return Boolean( - this.authState && this.email && this.authState.username === this.email, - ); + return Boolean(this.authState && this.email); } readonly _api = new APIController(this); @@ -69,7 +70,7 @@ export class AcceptInvite extends TailwindElement { firstUpdated() { if (!this._isLoggedIn) { this._notify.toast({ - message: msg("Log in to continue."), + message: msg("Please log in to accept this invite."), variant: "warning", icon: "exclamation-triangle", }); @@ -152,13 +153,17 @@ export class AcceptInvite extends TailwindElement { error: (err) => html`
${err instanceof Error ? err.message : err}
- - ${msg("Go to home page")} - + ${this.authState && this.authState.username !== this.email + ? nothing + : html` + + ${msg("Go to home page")} + + `}
`, })} @@ -181,7 +186,38 @@ export class AcceptInvite extends TailwindElement { ); } catch (e) { console.debug(e); - throw new Error(msg("This invitation is not valid.")); + + const status = isApiError(e) ? e.statusCode : null; + + switch (status) { + case 404: + throw new Error( + msg( + "This invite doesn't exist or has expired. Please ask the organization administrator to resend an invitation.", + ), + ); + case 400: { + if (auth.username === this.email) { + throw new Error( + msg( + str`This is not a valid invite, or it may have expired. If you believe this is an error, please contact ${this.appState.settings?.supportEmail || msg("your Browsertrix administrator")} for help.`, + ), + ); + } else { + throw new Error( + msg( + str`This invitation is for ${this.email}. You are currently logged in as ${auth.username}. Please log in with the correct email to access this invite.`, + ), + ); + } + } + default: + throw new Error( + msg( + str`Something unexpected went wrong retrieving this invite. Please contact ${this.appState.settings?.supportEmail || msg("your Browsertrix administrator")} for help.`, + ), + ); + } } } diff --git a/frontend/src/pages/invite/join.ts b/frontend/src/pages/invite/join.ts index 9f19f053..592d7085 100644 --- a/frontend/src/pages/invite/join.ts +++ b/frontend/src/pages/invite/join.ts @@ -1,4 +1,4 @@ -import { localized, msg } from "@lit/localize"; +import { localized, msg, str } from "@lit/localize"; import { Task } from "@lit/task"; import { customElement, property, state } from "lit/decorators.js"; @@ -132,16 +132,27 @@ export class Join extends LiteElement { `/api/users/invite/${token}?email=${encodeURIComponent(email)}`, ); - if (resp.status === 200) { - return (await resp.json()) as UserOrgInviteInfo; - } else if (resp.status === 404) { - throw new Error( - msg( - "This invite doesn't exist or has expired. Please ask the organization administrator to resend an invitation.", - ), - ); - } else { - throw new Error(msg("This invitation is not valid.")); + switch (resp.status) { + case 200: + return (await resp.json()) as UserOrgInviteInfo; + case 404: + throw new Error( + msg( + "This invite doesn't exist or has expired. Please ask the organization administrator to resend an invitation.", + ), + ); + case 400: + throw new Error( + msg( + str`This is not a valid invite, or it may have expired. If you believe this is an error, please contact ${this.appState.settings?.supportEmail || msg("your Browsertrix administrator")} for help.`, + ), + ); + default: + throw new Error( + msg( + str`Something unexpected went wrong retrieving this invite. Please contact ${this.appState.settings?.supportEmail || msg("your Browsertrix administrator")} for help.`, + ), + ); } } diff --git a/frontend/src/types/app.ts b/frontend/src/types/app.ts index 639f6d7c..5844ed02 100644 --- a/frontend/src/types/app.ts +++ b/frontend/src/types/app.ts @@ -7,4 +7,5 @@ export type AppSettings = { maxScale: number; billingEnabled: boolean; salesEmail: string; + supportEmail: string; };