browsertrix/frontend/src/components/invite-form.ts

162 lines
3.9 KiB
TypeScript

import { state, property } from "lit/decorators.js";
import { msg, localized } from "@lit/localize";
import sortBy from "lodash/fp/sortBy";
import type { AuthState } from "../utils/AuthService";
import LiteElement, { html } from "../utils/LiteElement";
import { OrgData } from "../types/org";
const sortByName = sortBy("name");
/**
* @event success
*/
@localized()
export class InviteForm extends LiteElement {
@property({ type: Object })
authState?: AuthState;
@property({ type: Array })
orgs: OrgData[] = [];
@property({ type: Object })
defaultOrg: OrgData | null = null;
@state()
private isSubmitting: boolean = false;
@state()
private serverError?: string;
@state()
private selectedOrgId?: string;
willUpdate(changedProperties: Map<string, any>) {
if (
changedProperties.has("defaultOrg") &&
this.defaultOrg &&
!this.selectedOrgId
) {
this.selectedOrgId = this.defaultOrg.id;
}
}
render() {
let formError;
if (this.serverError) {
formError = html`
<div class="mb-5">
<btrix-alert id="formError" variant="danger"
>${this.serverError}</btrix-alert
>
</div>
`;
}
const sortedOrgs = sortByName(this.orgs) as any as OrgData[];
return html`
<form
class="max-w-md"
@submit=${this.onSubmit}
aria-describedby="formError"
>
<div class="mb-5">
<sl-select
label=${msg("Organization")}
value=${this.defaultOrg ? this.defaultOrg.id : sortedOrgs[0]?.id}
@sl-change=${(e: Event) => {
this.selectedOrgId = (e.target as HTMLSelectElement).value;
}}
?disabled=${sortedOrgs.length === 1}
required
>
${sortedOrgs.map(
(org) => html`
<sl-option value=${org.id}>${org.name}</sl-option>
`
)}
</sl-select>
</div>
<div class="mb-5">
<sl-input
id="inviteEmail"
name="inviteEmail"
type="email"
label=${msg("Email")}
placeholder=${msg("person@email.com", {
desc: "Placeholder text for email to invite",
})}
required
>
</sl-input>
</div>
${formError}
<div class="text-right">
<sl-button
variant="primary"
size="small"
type="submit"
?loading=${this.isSubmitting}
?disabled=${!this.selectedOrgId || this.isSubmitting}
>${msg("Invite")}</sl-button
>
</div>
</form>
`;
}
async onSubmit(event: SubmitEvent) {
event.preventDefault();
if (!this.authState || !this.selectedOrgId) return;
const formEl = event.target as HTMLFormElement;
if (!(await this.checkFormValidity(formEl))) return;
this.serverError = undefined;
this.isSubmitting = true;
const formData = new FormData(event.target as HTMLFormElement);
const inviteEmail = formData.get("inviteEmail") as string;
try {
const data = await this.apiFetch(
`/orgs/${this.selectedOrgId}/invite`,
this.authState,
{
method: "POST",
body: JSON.stringify({
email: inviteEmail,
role: 10,
}),
}
);
this.dispatchEvent(
new CustomEvent("success", {
detail: {
inviteEmail,
isExistingUser: data.invited === "existing_user",
},
})
);
} catch (e: any) {
if (e?.isApiError) {
this.serverError = e?.message;
} else {
this.serverError = msg("Something unexpected went wrong");
}
}
this.isSubmitting = false;
}
async checkFormValidity(formEl: HTMLFormElement) {
await this.updateComplete;
return !formEl.querySelector("[data-invalid]");
}
}