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
This commit is contained in:
sua yoo 2024-07-25 16:57:02 -04:00 committed by GitHub
parent d38abbca7f
commit dd6c33a59d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 74 additions and 25 deletions

View File

@ -188,6 +188,7 @@ export class App extends LiteElement {
maxScale: 0, maxScale: 0,
billingEnabled: false, billingEnabled: false,
salesEmail: "", salesEmail: "",
supportEmail: "",
}; };
} }
} }

View File

@ -1,6 +1,6 @@
import { localized, msg, str } from "@lit/localize"; import { localized, msg, str } from "@lit/localize";
import { Task } from "@lit/task"; import { Task } from "@lit/task";
import { html } from "lit"; import { html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { renderInviteMessage } from "./ui/inviteMessage"; import { renderInviteMessage } from "./ui/inviteMessage";
@ -15,7 +15,7 @@ import { ROUTES } from "@/routes";
import type { UserOrg, UserOrgInviteInfo } from "@/types/user"; import type { UserOrg, UserOrgInviteInfo } from "@/types/user";
import { isApiError } from "@/utils/api"; import { isApiError } from "@/utils/api";
import type { Auth, AuthState } from "@/utils/AuthService"; 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 { formatAPIUser } from "@/utils/user";
import "./ui/org-form"; import "./ui/org-form";
@ -32,6 +32,9 @@ export class AcceptInvite extends TailwindElement {
@property({ type: String }) @property({ type: String })
email?: string; email?: string;
@use()
appState = appState;
@state() @state()
private serverError?: string; private serverError?: string;
@ -49,9 +52,7 @@ export class AcceptInvite extends TailwindElement {
}); });
get _isLoggedIn(): boolean { get _isLoggedIn(): boolean {
return Boolean( return Boolean(this.authState && this.email);
this.authState && this.email && this.authState.username === this.email,
);
} }
readonly _api = new APIController(this); readonly _api = new APIController(this);
@ -69,7 +70,7 @@ export class AcceptInvite extends TailwindElement {
firstUpdated() { firstUpdated() {
if (!this._isLoggedIn) { if (!this._isLoggedIn) {
this._notify.toast({ this._notify.toast({
message: msg("Log in to continue."), message: msg("Please log in to accept this invite."),
variant: "warning", variant: "warning",
icon: "exclamation-triangle", icon: "exclamation-triangle",
}); });
@ -152,6 +153,9 @@ export class AcceptInvite extends TailwindElement {
error: (err) => error: (err) =>
html`<btrix-alert variant="danger"> html`<btrix-alert variant="danger">
<div>${err instanceof Error ? err.message : err}</div> <div>${err instanceof Error ? err.message : err}</div>
${this.authState && this.authState.username !== this.email
? nothing
: html`
<a <a
href=${ROUTES.home} href=${ROUTES.home}
@click=${this._navigate.link} @click=${this._navigate.link}
@ -159,6 +163,7 @@ export class AcceptInvite extends TailwindElement {
> >
${msg("Go to home page")} ${msg("Go to home page")}
</a> </a>
`}
</btrix-alert> `, </btrix-alert> `,
})} })}
</div> </div>
@ -181,7 +186,38 @@ export class AcceptInvite extends TailwindElement {
); );
} catch (e) { } catch (e) {
console.debug(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.`,
),
);
}
} }
} }

View File

@ -1,4 +1,4 @@
import { localized, msg } from "@lit/localize"; import { localized, msg, str } from "@lit/localize";
import { Task } from "@lit/task"; import { Task } from "@lit/task";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
@ -132,16 +132,27 @@ export class Join extends LiteElement {
`/api/users/invite/${token}?email=${encodeURIComponent(email)}`, `/api/users/invite/${token}?email=${encodeURIComponent(email)}`,
); );
if (resp.status === 200) { switch (resp.status) {
case 200:
return (await resp.json()) as UserOrgInviteInfo; return (await resp.json()) as UserOrgInviteInfo;
} else if (resp.status === 404) { case 404:
throw new Error( throw new Error(
msg( msg(
"This invite doesn't exist or has expired. Please ask the organization administrator to resend an invitation.", "This invite doesn't exist or has expired. Please ask the organization administrator to resend an invitation.",
), ),
); );
} else { case 400:
throw new Error(msg("This invitation is not valid.")); 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.`,
),
);
} }
} }

View File

@ -7,4 +7,5 @@ export type AppSettings = {
maxScale: number; maxScale: number;
billingEnabled: boolean; billingEnabled: boolean;
salesEmail: string; salesEmail: string;
supportEmail: string;
}; };