From bafc96ac94317f71ca873e13a1452246577a9129 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Mon, 15 Jul 2024 12:05:19 -0700 Subject: [PATCH] check org slug --- frontend/src/pages/home.ts | 79 +++++++++++++++++++++++++++++++++-- frontend/src/utils/slugify.ts | 7 ++++ 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 frontend/src/utils/slugify.ts diff --git a/frontend/src/pages/home.ts b/frontend/src/pages/home.ts index 27f0e497..fc751e7a 100644 --- a/frontend/src/pages/home.ts +++ b/frontend/src/pages/home.ts @@ -1,4 +1,5 @@ import { localized, msg, str } from "@lit/localize"; +import type { SlInput, SlInputEvent } from "@shoelace-style/shoelace"; 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"; @@ -11,6 +12,7 @@ import type { AuthState } from "@/utils/AuthService"; import { maxLengthValidator } from "@/utils/form"; import LiteElement, { html } from "@/utils/LiteElement"; import type { OrgData } from "@/utils/orgs"; +import slugifyStrict from "@/utils/slugify"; /** * @fires btrix-update-user-info @@ -30,6 +32,9 @@ export class Home extends LiteElement { @state() private orgList?: OrgData[]; + @state() + private orgSlugs: string[] = []; + @state() private isAddingOrg = false; @@ -39,6 +44,9 @@ export class Home extends LiteElement { @state() private isSubmittingNewOrg = false; + @state() + private isOrgNameValid: boolean | null = null; + private readonly validateOrgNameMax = maxLengthValidator(40); connectedCallback() { @@ -171,6 +179,29 @@ export class Home extends LiteElement { + ${this.renderAddOrgDialog()} + `; + } + + private renderAddOrgDialog() { + let orgNameStatusLabel = msg("Start typing to see availability"); + let orgNameStatusIcon = html` + + `; + + if (this.isOrgNameValid) { + orgNameStatusLabel = msg("This org name is available"); + orgNameStatusIcon = html` + + `; + } else if (this.isOrgNameValid === false) { + orgNameStatusLabel = msg("This org name is taken"); + orgNameStatusIcon = html` + + `; + } + + return html` (this.isAddOrgFormVisible = true)} - @sl-after-hide=${() => (this.isAddOrgFormVisible = false)} + @sl-after-hide=${() => { + this.isAddOrgFormVisible = false; + this.isOrgNameValid = null; + }} > ${this.isAddOrgFormVisible ? html` @@ -201,8 +235,17 @@ export class Home extends LiteElement { autocomplete="off" required help-text=${this.validateOrgNameMax.helpText} - @sl-input=${this.validateOrgNameMax.validate} + @sl-input=${this.onOrgNameInput} > + e.stopPropagation()} + @sl-after-hide=${(e: CustomEvent) => e.stopPropagation()} + hoist + > + ${orgNameStatusIcon} + @@ -277,7 +320,12 @@ export class Home extends LiteElement { } private async fetchOrgs() { - this.orgList = await this.getOrgs(); + try { + this.orgList = await this.getOrgs(); + this.orgSlugs = await this.getOrgSlugs(); + } catch (e) { + console.debug(e); + } } private async getOrgs() { @@ -289,6 +337,31 @@ export class Home extends LiteElement { return data.items; } + private async getOrgSlugs() { + const data = await this.apiFetch<{ slugs: string[] }>( + "/orgs/slugs", + this.authState!, + ); + + return data.slugs; + } + + private async onOrgNameInput(e: SlInputEvent) { + this.validateOrgNameMax.validate(e); + + const input = e.target as SlInput; + const slug = slugifyStrict(input.value); + const isInvalid = this.orgSlugs.includes(slug); + + if (isInvalid) { + input.setCustomValidity(msg("This org name is already taken.")); + } else { + input.setCustomValidity(""); + } + + this.isOrgNameValid = !isInvalid; + } + private async onSubmitNewOrg(e: SubmitEvent) { e.preventDefault(); diff --git a/frontend/src/utils/slugify.ts b/frontend/src/utils/slugify.ts new file mode 100644 index 00000000..da3c0feb --- /dev/null +++ b/frontend/src/utils/slugify.ts @@ -0,0 +1,7 @@ +import slugify from "slugify"; + +import { getLocale } from "./localization"; + +export default function slugifyStrict(value: string) { + return slugify(value, { strict: true, lower: true, locale: getLocale() }); +}