From 1e2a590fe6ccb656774128356281277aa5f8a3a0 Mon Sep 17 00:00:00 2001 From: sua yoo Date: Mon, 27 Jan 2025 13:48:38 -0800 Subject: [PATCH 01/24] feat: Update references to org public profile -> gallery (#2330) - Renames public URL prefix to `explore` - Updates org settings sections - Removes or renames references to "org profile" --------- Co-authored-by: Ilya Kreymer Co-authored-by: Henry Wilkinson --- frontend/docs/docs/user-guide/org-settings.md | 15 +- .../collections/collection-metadata-dialog.ts | 7 +- .../features/collections/collections-grid.ts | 2 +- .../collections/select-collection-access.ts | 2 +- .../features/collections/share-collection.ts | 11 +- frontend/src/index.ts | 6 +- frontend/src/pages/index.ts | 1 + frontend/src/pages/org/dashboard.ts | 6 +- frontend/src/pages/org/index.ts | 13 +- .../pages/org/settings/components/profile.ts | 158 ------------ .../org/settings/components/visibility.ts | 128 ++++++++++ frontend/src/pages/org/settings/settings.ts | 226 +++++++++++++----- frontend/src/pages/public/index.ts | 1 + .../pages/{org/profile.ts => public/org.ts} | 14 +- frontend/src/routes.ts | 4 +- frontend/src/strings/collections/alerts.ts | 7 + frontend/src/strings/orgs/alerts.ts | 6 + 17 files changed, 351 insertions(+), 256 deletions(-) delete mode 100644 frontend/src/pages/org/settings/components/profile.ts create mode 100644 frontend/src/pages/org/settings/components/visibility.ts create mode 100644 frontend/src/pages/public/index.ts rename frontend/src/pages/{org/profile.ts => public/org.ts} (95%) create mode 100644 frontend/src/strings/collections/alerts.ts create mode 100644 frontend/src/strings/orgs/alerts.ts diff --git a/frontend/docs/docs/user-guide/org-settings.md b/frontend/docs/docs/user-guide/org-settings.md index 5c5f50d37a..a2a6a46b86 100644 --- a/frontend/docs/docs/user-guide/org-settings.md +++ b/frontend/docs/docs/user-guide/org-settings.md @@ -4,17 +4,20 @@ Settings that apply to the entire organization are found in the **Settings** pag ## General -### Name and URL +### Name, URL, and other basic information -Choose a display name for your org that's unique and memorable, like the name of your company, organization, or personal project. This name will be visible in the org's [public profile](#profile), if that page is enabled. +Your org name appears throughout the web application and in email notifications. Choose a display name for your org that's unique and memorable, like the name of your company, organization, or personal project. -The org URL is where you and other org members will go to view the dashboard, configure org settings, and manage all other org-related activities. Changing this URL will also update the URL of your org's public profile, if enabled. +The org URL is where you and other org members will go to view the dashboard, configure org settings, and manage all other org-related activities. If your org has a [public collections gallery](#public-collections-gallery) enabled, changing this will also update the URL to the gallery. -Org name and URLs are unique to each Browsertrix instance (for example, on `app.browsertrix.com`) and you may be prompted to change the org name or URL identifier if either are already in use by another org. +Org name and URLs are unique to each Browsertrix instance (for example, on `app.browsertrix.com`) and you may be prompted to change the org name or URL if either are already in use by another org. -### Profile +??? info "What information will be visible to the public?" + All org information is private until you make the org visible. Once your org is made visible to the public, the org name, description, and website will appear on the org's public gallery page. You can preview how information appears to the public by clicking **View as public**. -Enable and configure a public profile page for your org. Once enabled, anyone on the internet with a link to your org's profile page will be able to view public information like the org name, description, and public collections. +### Public Collections Gallery + +Enable a homepage for your public collections to easily share all public collections in your org. Once enabled, anyone on the internet with a link to your org's public collections gallery will be able to browse public collections and view general information like the org name, description, and website. ## Billing diff --git a/frontend/src/features/collections/collection-metadata-dialog.ts b/frontend/src/features/collections/collection-metadata-dialog.ts index cd54308c1d..7c3d4ae012 100644 --- a/frontend/src/features/collections/collection-metadata-dialog.ts +++ b/frontend/src/features/collections/collection-metadata-dialog.ts @@ -16,6 +16,7 @@ import { DEFAULT_THUMBNAIL } from "./collection-thumbnail"; import { BtrixElement } from "@/classes/BtrixElement"; import type { Dialog } from "@/components/ui/dialog"; import type { SelectCollectionAccess } from "@/features/collections/select-collection-access"; +import { alerts } from "@/strings/collections/alerts"; import { CollectionAccess, type Collection } from "@/types/collection"; import { isApiError } from "@/utils/api"; import { maxLengthValidator } from "@/utils/form"; @@ -170,12 +171,10 @@ export class CollectionMetadataDialog extends BtrixElement { ${org.enablePublicProfile ? msg( - "This collection will be visible on the org public profile, even without archived items. You may want to set visibility to 'Unlisted' until archived items have been added.", + "This collection will be visible in the org public collection gallery, even without archived items. You may want to set visibility to 'Unlisted' until archived items have been added.", ) : html` - ${msg( - "This collection will be visible on the org profile page, which isn't public yet. To make the org profile and this collection visible to the public, update org profile settings.", - )} + ${alerts.orgNotPublicWarning} - ${msg("Visit Public Page")} + ${msg("Visit Public Collections Gallery")} diff --git a/frontend/src/features/collections/select-collection-access.ts b/frontend/src/features/collections/select-collection-access.ts index 7547298ca2..2e8c9d5df9 100644 --- a/frontend/src/features/collections/select-collection-access.ts +++ b/frontend/src/features/collections/select-collection-access.ts @@ -28,7 +28,7 @@ export class SelectCollectionAccess extends BtrixElement { [CollectionAccess.Public]: { label: msg("Public"), icon: "globe2", - detail: msg("Anyone can view on the org's public profile"), + detail: msg("Anyone can view from the org public collections gallery"), }, }; diff --git a/frontend/src/features/collections/share-collection.ts b/frontend/src/features/collections/share-collection.ts index 1e97db062f..2d1ec3d55e 100644 --- a/frontend/src/features/collections/share-collection.ts +++ b/frontend/src/features/collections/share-collection.ts @@ -20,6 +20,7 @@ import { SelectCollectionAccess } from "./select-collection-access"; import { BtrixElement } from "@/classes/BtrixElement"; import { ClipboardController } from "@/controllers/clipboard"; import { RouteNamespace } from "@/routes"; +import { alerts } from "@/strings/collections/alerts"; import { AnalyticsTrackEvent } from "@/trackEvents"; import { CollectionAccess, @@ -246,7 +247,7 @@ export class ShareCollection extends BtrixElement { @sl-after-hide=${() => { this.tabGroup?.show(Tab.Link); }} - class="[--body-spacing:0] [--width:40rem]" + class="[--width:40rem] [--body-spacing:0]" > html` - ${msg( - "The org profile page isn't public yet. To make the org profile and this collection visible to the public, update profile visibility in org settings.", - )} + ${alerts.orgNotPublicWarning} `, )} @@ -311,9 +310,7 @@ export class ShareCollection extends BtrixElement {
${msg("Thumbnail")} diff --git a/frontend/src/index.ts b/frontend/src/index.ts index e1d2dbce6d..dfc1693359 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -820,11 +820,11 @@ export class App extends BtrixElement { >`; } - case "publicOrgProfile": - return html``; + >`; case "publicCollection": { const { collectionSlug, collectionTab } = this.viewState.params; diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts index 142bce53f1..bf3410b96a 100644 --- a/frontend/src/pages/index.ts +++ b/frontend/src/pages/index.ts @@ -11,3 +11,4 @@ import(/* webpackChunkName: "users-invite" */ "./users-invite"); import(/* webpackChunkName: "accept-invite" */ "./invite/accept"); import(/* webpackChunkName: "account-settings" */ "./account-settings"); import(/* webpackChunkName: "collections" */ "./collections"); +import(/* webpackChunkName: "public" */ "./public"); diff --git a/frontend/src/pages/org/dashboard.ts b/frontend/src/pages/org/dashboard.ts index 2d3b06f762..af3ad16e72 100644 --- a/frontend/src/pages/org/dashboard.ts +++ b/frontend/src/pages/org/dashboard.ts @@ -309,8 +309,8 @@ export class Dashboard extends BtrixElement { > ${this.org?.enablePublicProfile - ? msg("Visit Public Profile") - : msg("Preview Public Profile")} + ? msg("Visit Public Collections Gallery") + : msg("Preview Public Collections Gallery")} ${when(this.org, (org) => org.enablePublicProfile @@ -330,7 +330,7 @@ export class Dashboard extends BtrixElement { }} > - ${msg("Copy Link to Profile")} + ${msg("Copy Link to Public Gallery")} ` : this.appState.isAdmin diff --git a/frontend/src/pages/org/index.ts b/frontend/src/pages/org/index.ts index e27e4d09d6..cc68523188 100644 --- a/frontend/src/pages/org/index.ts +++ b/frontend/src/pages/org/index.ts @@ -12,6 +12,7 @@ import type { Tab as CollectionTab } from "./collection-detail"; import type { Member, OrgRemoveMemberEvent, + UpdateOrgDetail, UserRoleChangeEvent, } from "./settings/settings"; @@ -41,7 +42,6 @@ import "./browser-profiles-detail"; import "./browser-profiles-list"; import "./settings/settings"; import "./dashboard"; -import "./profile"; import(/* webpackChunkName: "org" */ "./archived-item-qa/archived-item-qa"); import(/* webpackChunkName: "org" */ "./workflows-new"); @@ -628,6 +628,17 @@ export class Org extends BtrixElement { return html`) => { + e.stopPropagation(); + + // Optimistic update + AppStateService.partialUpdateOrg({ + id: this.orgId, + ...e.detail, + }); + + void this.updateOrg(); + }} @org-user-role-change=${this.onUserRoleChange} @org-remove-member=${this.onOrgRemoveMember} >`; diff --git a/frontend/src/pages/org/settings/components/profile.ts b/frontend/src/pages/org/settings/components/profile.ts deleted file mode 100644 index fc1708fb3c..0000000000 --- a/frontend/src/pages/org/settings/components/profile.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { localized, msg } from "@lit/localize"; -import { serialize } from "@shoelace-style/shoelace/dist/utilities/form.js"; -import { html } from "lit"; -import { customElement } from "lit/decorators.js"; - -import { BtrixElement } from "@/classes/BtrixElement"; -import { columns, type Cols } from "@/layouts/columns"; -import { RouteNamespace } from "@/routes"; -import { formValidator, maxLengthValidator } from "@/utils/form"; - -@localized() -@customElement("btrix-org-settings-profile") -export class OrgSettingsProfile extends BtrixElement { - private readonly validateDescriptionMax = maxLengthValidator(150); - - render() { - const orgBaseUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ""}`; - - const cols: Cols = [ - [ - html` - -
- - ${msg("Allow anyone to view org")} - -
- `, - msg( - "If enabled, anyone will be able to view your org's profile page and public collections.", - ), - ], - [ - html` - - `, - msg("Write a short description to introduce your organization."), - ], - [ - html` - - `, - msg("Link to your organization's (or your personal) website."), - ], - [ - html` -
- -
- `, - html` - ${msg( - html`To customize this URL, -
${msg("update your Org URL in General settings")}.`, - )} - `, - ], - ]; - - return html` -

${msg("Profile")}

- -
-
-
${columns(cols)}
-
- - ${msg("Preview public profile page")} - - - ${msg("Save")} - -
-
-
- `; - } - - private readonly checkFormValidity = formValidator(this); - - private async onSubmit(e: SubmitEvent) { - e.preventDefault(); - - const form = e.currentTarget as HTMLFormElement; - - if (!(await this.checkFormValidity(form))) return; - - const { enablePublicProfile, publicDescription, publicUrl } = - serialize(form); - - try { - const data = await this.api.fetch<{ updated: boolean }>( - `/orgs/${this.orgId}/public-profile`, - { - method: "POST", - body: JSON.stringify({ - enablePublicProfile: enablePublicProfile === "on", - publicDescription, - publicUrl, - }), - }, - ); - - if (!data.updated) { - throw new Error(); - } - - this.notify.toast({ - message: msg("Org profile has been updated."), - variant: "success", - icon: "check2-circle", - }); - } catch (err) { - console.debug(err); - - this.notify.toast({ - message: msg("Sorry, couldn't update org at this time."), - variant: "danger", - icon: "exclamation-octagon", - }); - } - } -} diff --git a/frontend/src/pages/org/settings/components/visibility.ts b/frontend/src/pages/org/settings/components/visibility.ts new file mode 100644 index 0000000000..aadbbd0216 --- /dev/null +++ b/frontend/src/pages/org/settings/components/visibility.ts @@ -0,0 +1,128 @@ +import { localized, msg } from "@lit/localize"; +import type { SlChangeEvent, SlSwitch } from "@shoelace-style/shoelace"; +import { html } from "lit"; +import { customElement } from "lit/decorators.js"; + +import { UPDATED_STATUS_TOAST_ID, type UpdateOrgDetail } from "../settings"; + +import { BtrixElement } from "@/classes/BtrixElement"; +import { columns, type Cols } from "@/layouts/columns"; +import { RouteNamespace } from "@/routes"; +import { alerts } from "@/strings/orgs/alerts"; + +/** + * @fires btrix-update-org + */ +@localized() +@customElement("btrix-org-settings-visibility") +export class OrgSettingsVisibility extends BtrixElement { + render() { + const orgBaseUrl = `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ""}`; + + const cols: Cols = [ + [ + html` + +
+ + ${msg("Enable gallery of public collections")} + +
+ `, + msg( + "If enabled, anyone on the Internet will be able to browse this org's public collections and view general org information.", + ), + ], + [ + html` +
+ +
+ `, + html` + ${msg( + html`To customize this URL, + ${msg("update your Org URL in General settings")}.`, + )} + `, + ], + ]; + + return html` +

+ ${msg("Public Collections Gallery")} +

+ +
+
${columns(cols)}
+
+ `; + } + + private readonly onVisibilityChange = async (e: SlChangeEvent) => { + const checked = (e.target as SlSwitch).checked; + + if (checked === this.org?.enablePublicProfile) { + return; + } + + try { + const data = await this.api.fetch<{ updated: boolean }>( + `/orgs/${this.orgId}/public-profile`, + { + method: "POST", + body: JSON.stringify({ + enablePublicProfile: checked, + }), + }, + ); + + if (!data.updated) { + throw new Error("`data.updated` is not true"); + } + + this.dispatchEvent( + new CustomEvent("btrix-update-org", { + detail: { + enablePublicProfile: checked, + }, + bubbles: true, + composed: true, + }), + ); + + this.notify.toast({ + message: msg("Updated public collections gallery visibility."), + variant: "success", + icon: "check2-circle", + id: UPDATED_STATUS_TOAST_ID, + }); + } catch (err) { + console.debug(err); + + this.notify.toast({ + message: alerts.settingsUpdateFailure, + variant: "danger", + icon: "exclamation-octagon", + id: UPDATED_STATUS_TOAST_ID, + }); + } + }; +} diff --git a/frontend/src/pages/org/settings/settings.ts b/frontend/src/pages/org/settings/settings.ts index f0b744bb21..ea5c6ef625 100644 --- a/frontend/src/pages/org/settings/settings.ts +++ b/frontend/src/pages/org/settings/settings.ts @@ -20,10 +20,11 @@ import type { APIUser } from "@/index"; import { columns } from "@/layouts/columns"; import { pageHeader } from "@/layouts/pageHeader"; import { RouteNamespace } from "@/routes"; +import { alerts } from "@/strings/orgs/alerts"; import type { APIPaginatedList } from "@/types/api"; import { isApiError } from "@/utils/api"; import { formValidator, maxLengthValidator } from "@/utils/form"; -import { AccessCode, isAdmin, isCrawler } from "@/utils/orgs"; +import { AccessCode, isAdmin, isCrawler, type OrgData } from "@/utils/orgs"; import slugifyStrict from "@/utils/slugify"; import { AppStateService } from "@/utils/state"; import { tw } from "@/utils/tailwind"; @@ -31,7 +32,7 @@ import { formatAPIUser } from "@/utils/user"; import "./components/billing"; import "./components/crawling-defaults"; -import "./components/profile"; +import "./components/visibility"; const styles = unsafeCSS(stylesheet); @@ -55,6 +56,10 @@ export type OrgRemoveMemberEvent = CustomEvent<{ member: Member; }>; +export type UpdateOrgDetail = Partial; + +export const UPDATED_STATUS_TOAST_ID = "org-updated-status" as const; + /** * Usage: * ```ts @@ -64,6 +69,7 @@ export type OrgRemoveMemberEvent = CustomEvent<{ * > * ``` * + * @fires btrix-update-org * @fires org-user-role-change * @fires org-remove-member */ @@ -103,6 +109,7 @@ export class OrgSettings extends BtrixElement { } private readonly validateOrgNameMax = maxLengthValidator(40); + private readonly validateDescriptionMax = maxLengthValidator(150); async willUpdate(changedProperties: PropertyValues) { if (changedProperties.has("isAddingMember") && this.isAddingMember) { @@ -171,7 +178,7 @@ export class OrgSettings extends BtrixElement { ${this.renderPanelHeader({ title: msg("General") })} ${this.renderInformation()} - + ${this.renderApi()} @@ -294,7 +301,7 @@ export class OrgSettings extends BtrixElement { html` -
- ${window.location.hostname}${window.location.port - ? `:${window.location.port}` - : ""}/${RouteNamespace.PrivateOrgs}/ -
+
/
`, - msg( - "Customize your org's Browsertrix URL. This will also apply to the URL to your org's public profile page, if you've enabled it.", - ), + msg("Customize your org's Browsertrix URL."), + ], + [ + html` + + `, + msg("Write a short description to introduce your organization."), + ], + [ + html` + + `, + msg("Link to your organization's (or your personal) website."), ], ])}
-