diff --git a/frontend/package.json b/frontend/package.json index 9748d193..192753ea 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,6 +50,7 @@ "pretty-ms": "^7.0.1", "query-string": "^8.1.0", "regex-colorize": "^0.0.3", + "slugify": "^1.6.6", "style-loader": "^3.3.0", "tailwindcss": "^3.2.7", "ts-loader": "^9.2.6", diff --git a/frontend/src/pages/org/index.ts b/frontend/src/pages/org/index.ts index deccbb72..d6d4ba1d 100644 --- a/frontend/src/pages/org/index.ts +++ b/frontend/src/pages/org/index.ts @@ -31,7 +31,7 @@ import "./components/new-collection-dialog"; import "./components/new-workflow-dialog"; import type { Member, - OrgNameChangeEvent, + OrgInfoChangeEvent, UserRoleChangeEvent, OrgRemoveMemberEvent, } from "./settings"; @@ -104,7 +104,7 @@ export class Org extends LiteElement { private org?: OrgData | null; @state() - private isSavingOrgName = false; + private isSavingOrgInfo = false; get userOrg() { if (!this.userInfo) return null; @@ -535,8 +535,8 @@ export class Org extends LiteElement { .orgId=${this.orgId} activePanel=${activePanel} ?isAddingMember=${isAddingMember} - ?isSavingOrgName=${this.isSavingOrgName} - @org-name-change=${this.onOrgNameChange} + ?isSavingOrgName=${this.isSavingOrgInfo} + @org-info-change=${this.onOrgInfoChange} @org-user-role-change=${this.onUserRoleChange} @org-remove-member=${this.onOrgRemoveMember} >`; @@ -554,18 +554,17 @@ export class Org extends LiteElement { return data; } - - private async onOrgNameChange(e: OrgNameChangeEvent) { - this.isSavingOrgName = true; + private async onOrgInfoChange(e: OrgInfoChangeEvent) { + this.isSavingOrgInfo = true; try { await this.apiFetch(`/orgs/${this.org!.id}/rename`, this.authState!, { method: "POST", - body: JSON.stringify({ name: e.detail.value }), + body: JSON.stringify(e.detail), }); this.notify({ - message: msg("Updated organization name."), + message: msg("Updated organization."), variant: "success", icon: "check2-circle", }); @@ -577,13 +576,13 @@ export class Org extends LiteElement { this.notify({ message: e.isApiError ? e.message - : msg("Sorry, couldn't update organization name at this time."), + : msg("Sorry, couldn't update organization at this time."), variant: "danger", icon: "exclamation-octagon", }); } - this.isSavingOrgName = false; + this.isSavingOrgInfo = false; } private async onOrgRemoveMember(e: OrgRemoveMemberEvent) { diff --git a/frontend/src/pages/org/settings.ts b/frontend/src/pages/org/settings.ts index ef24f9dc..3212ce8a 100644 --- a/frontend/src/pages/org/settings.ts +++ b/frontend/src/pages/org/settings.ts @@ -3,6 +3,8 @@ import { ifDefined } from "lit/directives/if-defined.js"; import { msg, localized, str } from "@lit/localize"; import { when } from "lit/directives/when.js"; import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js"; +import slugify from "slugify"; +import type { SlInput } from "@shoelace-style/shoelace"; import type { AuthState } from "../../utils/AuthService"; import LiteElement, { html } from "../../utils/LiteElement"; @@ -24,8 +26,9 @@ type Invite = User & { export type Member = User & { name: string; }; -export type OrgNameChangeEvent = CustomEvent<{ - value: string; +export type OrgInfoChangeEvent = CustomEvent<{ + name: string; + slug?: string; }>; export type UserRoleChangeEvent = CustomEvent<{ user: Member; @@ -48,7 +51,7 @@ export type OrgRemoveMemberEvent = CustomEvent<{ * ``` * * @events - * org-name-change + * org-info-change * org-user-role-change * org-remove-member */ @@ -84,14 +87,17 @@ export class OrgSettings extends LiteElement { @state() private isSubmittingInvite = false; - private get tabLabels() { + @state() + private slugValue = ""; + + private get tabLabels(): Record { return { - information: msg("Org Information"), + information: msg("General"), members: msg("Members"), }; } - private validateOrgNameMax = maxLengthValidator(50); + private validateOrgNameMax = maxLengthValidator(40); async willUpdate(changedProperties: Map) { if (changedProperties.has("isAddingMember") && this.isAddingMember) { @@ -164,28 +170,80 @@ export class OrgSettings extends LiteElement { } private renderInformation() { - return html`
-
- - ${msg("Save Changes")} + return html`
+ +
+
+ +
+
+
+ +
+
+ ${msg( + "Name of your organization that is visible to all org members." + )} +
+
+
+ { + const input = e.target as SlInput; + this.slugValue = input.value; + }} + > + +
+ +
+
+ +
+
+ ${msg("Unique URL for this organization.")} +
+
+
+
+ ${msg("Save Changes")} +
`; } @@ -362,6 +420,12 @@ export class OrgSettings extends LiteElement { `; } + private slugify(value: string) { + return slugify(value, { + strict: true, + }); + } + private async checkFormValidity(formEl: HTMLFormElement) { await this.updateComplete; return !formEl.querySelector("[data-invalid]"); @@ -390,16 +454,23 @@ export class OrgSettings extends LiteElement { } } - private async onOrgNameSubmit(e: SubmitEvent) { + private async onOrgInfoSubmit(e: SubmitEvent) { e.preventDefault(); const formEl = e.target as HTMLFormElement; if (!(await this.checkFormValidity(formEl))) return; const { orgName } = serialize(formEl); + const orgSlug = this.slugify(this.slugValue); + const detail: any = { name: orgName }; + + if (orgSlug !== this.org.slug) { + detail.slug = orgSlug; + } + this.dispatchEvent( - new CustomEvent("org-name-change", { - detail: { value: orgName }, + new CustomEvent("org-info-change", { + detail, }) ); } diff --git a/frontend/src/types/org.ts b/frontend/src/types/org.ts index 61b74b23..2ae86191 100644 --- a/frontend/src/types/org.ts +++ b/frontend/src/types/org.ts @@ -11,6 +11,7 @@ export const AccessCode: Record = { export type OrgData = { id: string; name: string; + slug: string; quotas: Record; bytesStored: number; users?: { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a9e9b1ad..50aea536 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -6191,6 +6191,11 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" +slugify@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b" + integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw== + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce"