import { state, property } from "lit/decorators.js"; import { msg, localized, str } from "@lit/localize"; import { when } from "lit/directives/when.js"; import type { ViewState } from "../../utils/APIRouter"; import type { AuthState } from "../../utils/AuthService"; import type { CurrentUser } from "../../types/user"; import type { OrgData } from "../../utils/orgs"; import { isAdmin, isCrawler } from "../../utils/orgs"; import LiteElement, { html } from "../../utils/LiteElement"; import { needLogin } from "../../utils/auth"; import "./workflow-detail"; import "./workflows-list"; import "./workflows-new"; import "./crawl-detail"; import "./crawls-list"; import "./browser-profiles-detail"; import "./browser-profiles-list"; import "./browser-profiles-new"; import "./settings"; import type { Member, OrgNameChangeEvent, UserRoleChangeEvent, OrgRemoveMemberEvent, } from "./settings"; export type OrgTab = "crawls" | "workflows" | "browser-profiles" | "settings"; type Params = { crawlId?: string; workflowId?: string; browserProfileId?: string; browserId?: string; }; const defaultTab = "crawls"; @needLogin @localized() export class Org extends LiteElement { @property({ type: Object }) authState?: AuthState; @property({ type: Object }) userInfo?: CurrentUser; @property({ type: Object }) viewStateData?: ViewState["data"]; // Path after `/orgs/:orgId/` @property({ type: String }) orgPath!: string; @property({ type: Object }) params!: Params; @property({ type: String }) orgId!: string; @property({ type: String }) orgTab: OrgTab = defaultTab; @state() private org?: OrgData | null; @state() private isSavingOrgName = false; get userOrg() { if (!this.userInfo) return null; return this.userInfo.orgs.find(({ id }) => id === this.orgId)!; } get isAdmin() { const userOrg = this.userOrg; if (userOrg) return isAdmin(userOrg.role); return false; } get isCrawler() { const userOrg = this.userOrg; if (userOrg) return isCrawler(userOrg.role); return false; } async willUpdate(changedProperties: Map) { if (changedProperties.has("orgId") && this.orgId) { try { this.org = await this.getOrg(this.orgId); } catch { this.org = null; this.notify({ message: msg("Sorry, couldn't retrieve organization at this time."), variant: "danger", icon: "exclamation-octagon", }); } } } render() { if (this.org === null) { // TODO handle 404 and 500s return ""; } if (!this.org || !this.userInfo) { // TODO combine loading state with tab panel content return ""; } let tabPanelContent = "" as any; switch (this.orgTab) { case "crawls": tabPanelContent = this.renderCrawls(); break; case "workflows": tabPanelContent = this.renderWorkflows(); break; case "browser-profiles": tabPanelContent = this.renderBrowserProfiles(); break; case "settings": { if (this.isAdmin) { tabPanelContent = this.renderOrgSettings(); break; } } default: tabPanelContent = html``; break; } return html` ${this.renderOrgNavBar()}
${tabPanelContent}
`; } private renderOrgNavBar() { return html`

`; } private renderNavTab({ tabName, label }: { tabName: OrgTab; label: string }) { const isActive = this.orgTab === tabName; return html`
${label}
`; } private renderCrawls() { const crawlsBaseUrl = `/orgs/${this.orgId}/crawls`; if (this.params.crawlId) { return html` `; } return html``; } private renderWorkflows() { const isEditing = this.params.hasOwnProperty("edit"); const isNewResourceTab = this.params.hasOwnProperty("new"); if (this.params.workflowId) { return html` `; } if (isNewResourceTab) { const workflow = this.viewStateData?.workflow; return html` `; } return html``; } private renderBrowserProfiles() { const isNewResourceTab = this.params.hasOwnProperty("new"); if (this.params.browserProfileId) { return html``; } if (this.params.browserId) { return html``; } return html``; } private renderOrgSettings() { // const activePanel = this. const activePanel = this.orgPath.includes("/members") ? "members" : "information"; const isAddingMember = this.params.hasOwnProperty("invite"); return html``; } private async getOrg(orgId: string): Promise { const data = await this.apiFetch(`/orgs/${orgId}`, this.authState!); return data; } private async onOrgNameChange(e: OrgNameChangeEvent) { this.isSavingOrgName = true; try { await this.apiFetch(`/orgs/${this.org!.id}/rename`, this.authState!, { method: "POST", body: JSON.stringify({ name: e.detail.value }), }); this.notify({ message: msg("Updated organization name."), variant: "success", icon: "check2-circle", }); this.dispatchEvent( new CustomEvent("update-user-info", { bubbles: true }) ); } catch (e: any) { this.notify({ message: e.isApiError ? e.message : msg("Sorry, couldn't update organization name at this time."), variant: "danger", icon: "exclamation-octagon", }); } this.isSavingOrgName = false; } private async onOrgRemoveMember(e: OrgRemoveMemberEvent) { this.removeMember(e.detail.member); } private async onUserRoleChange(e: UserRoleChangeEvent) { const { user, newRole } = e.detail; try { await this.apiFetch(`/orgs/${this.orgId}/user-role`, this.authState!, { method: "PATCH", body: JSON.stringify({ email: user.email, role: newRole, }), }); this.notify({ message: msg( str`Successfully updated role for ${user.name || user.email}.` ), variant: "success", icon: "check2-circle", }); this.org = await this.getOrg(this.orgId); } catch (e: any) { console.debug(e); this.notify({ message: e.isApiError ? e.message : msg( str`Sorry, couldn't update role for ${ user.name || user.email } at this time.` ), variant: "danger", icon: "exclamation-octagon", }); } } private async removeMember(member: Member) { if (!this.org) return; const isSelf = member.email === this.userInfo!.email; if ( isSelf && !window.confirm( msg( str`Are you sure you want to remove yourself from ${this.org.name}?` ) ) ) { return; } try { await this.apiFetch(`/orgs/${this.orgId}/remove`, this.authState!, { method: "POST", body: JSON.stringify({ email: member.email, }), }); this.notify({ message: msg( str`Successfully removed ${member.name || member.email} from ${ this.org.name }.` ), variant: "success", icon: "check2-circle", }); if (isSelf) { // FIXME better UX, this is the only page currently that doesn't require org... this.navTo("/account/settings"); } else { this.org = await this.getOrg(this.orgId); } } catch (e: any) { console.debug(e); this.notify({ message: e.isApiError ? e.message : msg( str`Sorry, couldn't remove ${ member.name || member.email } at this time.` ), variant: "danger", icon: "exclamation-octagon", }); } } }