import { localized, msg, str } from "@lit/localize"; import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js"; import { type PropertyValues, type TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import type { InviteSuccessDetail } from "@/features/accounts/invite-form"; import type { APIPaginatedList } from "@/types/api"; import type { CurrentUser } from "@/types/user"; import { isApiError } from "@/utils/api"; import type { AuthState } from "@/utils/AuthService"; import { maxLengthValidator } from "@/utils/form"; import LiteElement, { html } from "@/utils/LiteElement"; import type { OrgData } from "@/utils/orgs"; /** * @fires btrix-update-user-info */ @localized() @customElement("btrix-home") export class Home extends LiteElement { @property({ type: Object }) authState?: AuthState; @property({ type: Object }) userInfo?: CurrentUser; @property({ type: String }) slug?: string; @state() private orgList?: OrgData[]; @state() private isAddingOrg = false; @state() private isAddOrgFormVisible = false; @state() private isSubmittingNewOrg = false; private readonly validateOrgNameMax = maxLengthValidator(50); connectedCallback() { if (this.authState) { super.connectedCallback(); } else { this.navTo("/log-in"); } } willUpdate(changedProperties: PropertyValues) { if (changedProperties.has("slug") && this.slug) { this.navTo(`/orgs/${this.slug}`); } else if (changedProperties.has("authState") && this.authState) { void this.fetchOrgs(); } } async updated( changedProperties: PropertyValues & Map, ) { 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) { this.isAddingOrg = true; } } } render() { if (!this.userInfo || !this.orgList) { return html`
`; } let title: string | undefined; let content: TemplateResult<1> | undefined; if (this.userInfo.isAdmin) { title = msg("Welcome"); content = this.renderAdminOrgs(); } else { title = msg("Organizations"); content = this.renderLoggedInNonAdmin(); } return html`

${title}


${content}
`; } private renderAdminOrgs() { return html`
{ const formData = new FormData(e.target as HTMLFormElement); const id = formData.get("crawlId"); this.navTo(`/crawls/crawl/${id?.toString()}`); }} >
${msg("Go to Crawl")}
${msg("Go")}

${msg("All Organizations")}

(this.isAddingOrg = true)} > ${msg("New Organization")}

${msg("Invite User to Org")}

${this.renderInvite()}
{ // Disable closing if there are no orgs if (this.orgList?.length) { this.isAddingOrg = false; } else { e.preventDefault(); } }} @sl-show=${() => (this.isAddOrgFormVisible = true)} @sl-after-hide=${() => (this.isAddOrgFormVisible = false)} > ${this.isAddOrgFormVisible ? html`
(this.isAddingOrg = false)} @submit=${this.onSubmitNewOrg} >
${this.orgList?.length ? html` ${msg("Cancel")} ` : ""} ${msg("Create Org")}
` : ""}
`; } private renderLoggedInNonAdmin() { if (this.orgList && !this.orgList.length) { return html`

${msg("You don't have any organizations.")}

`; } return html` `; } private renderInvite() { return html` ) => { const org = this.orgList?.find(({ id }) => id === e.detail.orgId); this.notify({ message: html` ${msg("Invite sent!")}
${msg("View org members")} `, variant: "success", icon: "check2-circle", }); }} >
`; } private async fetchOrgs() { this.orgList = await this.getOrgs(); } private async getOrgs() { const data = await this.apiFetch>( "/orgs", this.authState!, ); return data.items; } private async onSubmitNewOrg(e: SubmitEvent) { e.preventDefault(); const formEl = e.target as HTMLFormElement; if (!(await this.checkFormValidity(formEl))) return; const params = serialize(formEl); this.isSubmittingNewOrg = true; try { await this.apiFetch(`/orgs/create`, this.authState!, { method: "POST", body: JSON.stringify(params), }); // Update user info since orgs are checked against userInfo.orgs this.dispatchEvent(new CustomEvent("btrix-update-user-info")); await this.updateComplete; void this.fetchOrgs(); this.notify({ message: msg(str`Created new org named "${params.name}".`), variant: "success", icon: "check2-circle", duration: 8000, }); this.isAddingOrg = false; } catch (e) { this.notify({ message: isApiError(e) ? e.message : msg("Sorry, couldn't create organization at this time."), variant: "danger", icon: "exclamation-octagon", }); } this.isSubmittingNewOrg = false; } async onUpdateOrgQuotas(e: CustomEvent) { const org = e.detail as OrgData; await this.apiFetch(`/orgs/${org.id}/quotas`, this.authState!, { method: "POST", body: JSON.stringify(org.quotas), }); } async checkFormValidity(formEl: HTMLFormElement) { await this.updateComplete; return !formEl.querySelector("[data-invalid]"); } }