From 877e4199e7481a23f44135cc7bad1e231a9bc012 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Wed, 27 Nov 2024 14:53:36 +0100 Subject: [PATCH] fix: move organization members list (#17531) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Omar López --- .../organizations/[id]/members/page.tsx | 14 ++++ .../settings/organizations/members/page.tsx | 2 +- apps/web/modules/members/members-view.tsx | 26 +++++++ .../playwright/organization/booking.e2e.ts | 1 + .../playwright/organization/lib/inviteUser.ts | 8 ++- .../organization-invitation.e2e.ts | 20 +++++- .../organization/organization-privacy.e2e.ts | 4 +- packages/features/ee/components/PoweredBy.tsx | 6 +- .../pages/{settings => }/members.tsx | 1 - .../organizations/pages/settings/general.tsx | 1 - .../settings/other-team-listing-view.tsx | 1 - .../settings/other-team-profile-view.tsx | 1 - .../organizations/pages/settings/profile.tsx | 1 - .../features/ee/sso/page/orgs-sso-view.tsx | 2 +- .../appDir/SettingsLayoutAppDirClient.tsx | 4 -- .../settings/layouts/SettingsLayout.tsx | 4 -- .../features/shell/navigation/Navigation.tsx | 69 ++++++++++++------- 17 files changed, 112 insertions(+), 53 deletions(-) create mode 100644 apps/web/app/settings/organizations/[id]/members/page.tsx create mode 100644 apps/web/modules/members/members-view.tsx rename packages/features/ee/organizations/pages/{settings => }/members.tsx (97%) diff --git a/apps/web/app/settings/organizations/[id]/members/page.tsx b/apps/web/app/settings/organizations/[id]/members/page.tsx new file mode 100644 index 0000000000..0c78d970cb --- /dev/null +++ b/apps/web/app/settings/organizations/[id]/members/page.tsx @@ -0,0 +1,14 @@ +import { _generateMetadata } from "app/_utils"; +import { WithLayout } from "app/layoutHOC"; + +import MembersPage from "~/members/members-view"; + +export const generateMetadata = async () => + await _generateMetadata( + (t) => t("organization_members"), + (t) => t("organization_description") + ); + +export default WithLayout({ + Page: MembersPage, +}); diff --git a/apps/web/app/settings/organizations/members/page.tsx b/apps/web/app/settings/organizations/members/page.tsx index 2a08ef68f7..33fceacab9 100644 --- a/apps/web/app/settings/organizations/members/page.tsx +++ b/apps/web/app/settings/organizations/members/page.tsx @@ -2,7 +2,7 @@ import { _generateMetadata, getFixedT } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { getServerSessionForAppDir } from "@calcom/features/auth/lib/get-server-session-for-app-dir"; -import LegacyPage from "@calcom/features/ee/organizations/pages/settings/members"; +import LegacyPage from "@calcom/features/ee/organizations/pages/members"; import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import SettingsLayoutAppDir from "@calcom/features/settings/appDir/SettingsLayoutAppDir"; diff --git a/apps/web/modules/members/members-view.tsx b/apps/web/modules/members/members-view.tsx new file mode 100644 index 0000000000..8f89e88b16 --- /dev/null +++ b/apps/web/modules/members/members-view.tsx @@ -0,0 +1,26 @@ +"use client"; + +import MembersView from "@calcom/features/ee/organizations/pages/members"; +import Shell from "@calcom/features/shell/Shell"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { HeadSeo } from "@calcom/ui"; + +const MembersPage: React.FC = () => { + const { t } = useLocale(); + return ( + + + + + + ); +}; + +export default MembersPage; diff --git a/apps/web/playwright/organization/booking.e2e.ts b/apps/web/playwright/organization/booking.e2e.ts index c0a8eca4f4..a6dd8f8f86 100644 --- a/apps/web/playwright/organization/booking.e2e.ts +++ b/apps/web/playwright/organization/booking.e2e.ts @@ -529,6 +529,7 @@ test.describe("Bookings", () => { const { invitedUserEmail } = await inviteExistingUserToOrganization({ page, organizationId: org.id, + organizationSlug: org.slug, user: userOutsideOrganization, usersFixture: users, }); diff --git a/apps/web/playwright/organization/lib/inviteUser.ts b/apps/web/playwright/organization/lib/inviteUser.ts index 975c0bcb32..12f2b0db5d 100644 --- a/apps/web/playwright/organization/lib/inviteUser.ts +++ b/apps/web/playwright/organization/lib/inviteUser.ts @@ -6,15 +6,17 @@ import { submitAndWaitForResponse } from "../../lib/testUtils"; export const inviteUserToOrganization = async ({ page, organizationId, + organizationSlug, email, usersFixture, }: { page: Page; organizationId: number; + organizationSlug: string | null; email: string; usersFixture: ReturnType; }) => { - await page.goto("/settings/organizations/members"); + await page.goto(`/settings/organizations/${organizationSlug}/members`); const invitedUserEmail = usersFixture.trackEmail({ username: email.split("@")[0], domain: email.split("@")[1], @@ -26,17 +28,19 @@ export const inviteUserToOrganization = async ({ export const inviteExistingUserToOrganization = async ({ page, organizationId, + organizationSlug, user, usersFixture, }: { page: Page; organizationId: number; + organizationSlug: string | null; user: { email: string; }; usersFixture: ReturnType; }) => { - await page.goto("/settings/organizations/members"); + await page.goto(`/settings/organizations/${organizationSlug}/members`); await inviteAnEmail(page, user.email); return { invitedUserEmail: user.email }; }; diff --git a/apps/web/playwright/organization/organization-invitation.e2e.ts b/apps/web/playwright/organization/organization-invitation.e2e.ts index 881e4f426a..c244d23f8d 100644 --- a/apps/web/playwright/organization/organization-invitation.e2e.ts +++ b/apps/web/playwright/organization/organization-invitation.e2e.ts @@ -24,7 +24,7 @@ test.describe("Organization", () => { const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true }); const { team: org } = await orgOwner.getOrgMembership(); await orgOwner.apiLogin(); - await page.goto("/settings/organizations/members"); + await page.goto(`/settings/organizations/${org.slug}/members`); await test.step("By email", async () => { const invitedUserEmail = users.trackEmail({ username: "rick", domain: "domain.com" }); @@ -42,6 +42,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: false, @@ -61,6 +62,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -78,6 +80,7 @@ test.describe("Organization", () => { expect(dbUser?.username).toBe(usernameDerivedFromEmail); await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -119,6 +122,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: false, @@ -156,6 +160,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -184,6 +189,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -203,7 +209,7 @@ test.describe("Organization", () => { }); const { team: org } = await orgOwner.getOrgMembership(); await orgOwner.apiLogin(); - await page.goto("/settings/organizations/members"); + await page.goto(`/settings/organizations/${org.slug}/members`); await test.step("By email", async () => { const invitedUserEmail = users.trackEmail({ username: "rick", domain: "example.com" }); @@ -219,6 +225,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -238,6 +245,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -255,6 +263,7 @@ test.describe("Organization", () => { expect(dbUser?.username).toBe(usernameDerivedFromEmail); await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -346,6 +355,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -382,6 +392,7 @@ test.describe("Organization", () => { await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -411,6 +422,7 @@ test.describe("Organization", () => { }); await expectUserToBeAMemberOfOrganization({ page, + orgSlug: org.slug, username: usernameDerivedFromEmail, role: "member", isMemberShipAccepted: true, @@ -542,19 +554,21 @@ async function inviteAnEmail(page: Page, invitedUserEmail: string, teamPage?: bo async function expectUserToBeAMemberOfOrganization({ page, + orgSlug, username, email, role, isMemberShipAccepted, }: { page: Page; + orgSlug: string | null; username: string; role: string; isMemberShipAccepted: boolean; email: string; }) { // Check newly invited member is not pending anymore - await page.goto("/settings/organizations/members"); + await page.goto(`/settings/organizations/${orgSlug}/members`); await expect(page.locator(`[data-testid="member-${username}-username"]`)).toHaveText(username); await expect(page.locator(`[data-testid="member-${username}-email"]`)).toHaveText(email); expect((await page.locator(`[data-testid="member-${username}-role"]`).textContent())?.toLowerCase()).toBe( diff --git a/apps/web/playwright/organization/organization-privacy.e2e.ts b/apps/web/playwright/organization/organization-privacy.e2e.ts index af60817ab4..c94ea5eedd 100644 --- a/apps/web/playwright/organization/organization-privacy.e2e.ts +++ b/apps/web/playwright/organization/organization-privacy.e2e.ts @@ -46,7 +46,7 @@ test.describe("Organization - Privacy", () => { }); await owner.apiLogin(); - await page.goto("/settings/organizations/members"); + await page.goto(`/settings/organizations/${org.slug}/members`); await page.waitForLoadState("domcontentloaded"); const tableLocator = await page.getByTestId("user-list-data-table"); @@ -54,7 +54,7 @@ test.describe("Organization - Privacy", () => { await expect(tableLocator).toBeVisible(); await memberInOrg.apiLogin(); - await page.goto("/settings/organizations/members"); + await page.goto(`/settings/organizations/${org.slug}/members`); await page.waitForLoadState("domcontentloaded"); const userDataTable = await page.getByTestId("user-list-data-table"); const membersPrivacyWarning = await page.getByTestId("members-privacy-warning"); diff --git a/packages/features/ee/components/PoweredBy.tsx b/packages/features/ee/components/PoweredBy.tsx index d3159f459c..89379fe044 100644 --- a/packages/features/ee/components/PoweredBy.tsx +++ b/packages/features/ee/components/PoweredBy.tsx @@ -17,11 +17,7 @@ const PoweredByCal = ({ logoOnly }: { logoOnly?: boolean }) => { {!logoOnly && <>{t("powered_by")} } {APP_NAME === "Cal.com" || !hasValidLicense ? ( <> - Cal.com Logo + Cal.com Logo ) : ( {APP_NAME} diff --git a/packages/features/ee/organizations/pages/settings/members.tsx b/packages/features/ee/organizations/pages/members.tsx similarity index 97% rename from packages/features/ee/organizations/pages/settings/members.tsx rename to packages/features/ee/organizations/pages/members.tsx index c2e74a9d9f..90d912cfca 100644 --- a/packages/features/ee/organizations/pages/settings/members.tsx +++ b/packages/features/ee/organizations/pages/members.tsx @@ -5,7 +5,6 @@ import { UserListTable } from "@calcom/features/users/components/UserTable/UserL import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; -import { Meta } from "@calcom/ui"; const MembersView = () => { const { t } = useLocale(); diff --git a/packages/features/ee/organizations/pages/settings/general.tsx b/packages/features/ee/organizations/pages/settings/general.tsx index 1973a0f672..82cc86fb85 100644 --- a/packages/features/ee/organizations/pages/settings/general.tsx +++ b/packages/features/ee/organizations/pages/settings/general.tsx @@ -16,7 +16,6 @@ import { Button, Form, Label, - Meta, Select, showToast, SkeletonButton, diff --git a/packages/features/ee/organizations/pages/settings/other-team-listing-view.tsx b/packages/features/ee/organizations/pages/settings/other-team-listing-view.tsx index 4ac2a087cf..d4f88e2224 100644 --- a/packages/features/ee/organizations/pages/settings/other-team-listing-view.tsx +++ b/packages/features/ee/organizations/pages/settings/other-team-listing-view.tsx @@ -1,7 +1,6 @@ "use client"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Meta } from "@calcom/ui"; import { OtherTeamsListing } from "./../components/OtherTeamsListing"; diff --git a/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx b/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx index 7ffb7f9ca9..cb509f4241 100644 --- a/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx +++ b/packages/features/ee/organizations/pages/settings/other-team-profile-view.tsx @@ -31,7 +31,6 @@ import { ImageUploader, Label, LinkIconButton, - Meta, showToast, SkeletonContainer, SkeletonText, diff --git a/packages/features/ee/organizations/pages/settings/profile.tsx b/packages/features/ee/organizations/pages/settings/profile.tsx index c9f4276aed..737ab00501 100644 --- a/packages/features/ee/organizations/pages/settings/profile.tsx +++ b/packages/features/ee/organizations/pages/settings/profile.tsx @@ -27,7 +27,6 @@ import { ImageUploader, Label, LinkIconButton, - Meta, showToast, SkeletonAvatar, SkeletonButton, diff --git a/packages/features/ee/sso/page/orgs-sso-view.tsx b/packages/features/ee/sso/page/orgs-sso-view.tsx index 9cf310984c..15cbe6b505 100644 --- a/packages/features/ee/sso/page/orgs-sso-view.tsx +++ b/packages/features/ee/sso/page/orgs-sso-view.tsx @@ -4,7 +4,7 @@ import { useSession } from "next-auth/react"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; -import { AppSkeletonLoader as SkeletonLoader, Meta } from "@calcom/ui"; +import { AppSkeletonLoader as SkeletonLoader } from "@calcom/ui"; import SSOConfiguration from "../components/SSOConfiguration"; diff --git a/packages/features/settings/appDir/SettingsLayoutAppDirClient.tsx b/packages/features/settings/appDir/SettingsLayoutAppDirClient.tsx index 52639c6e67..a97aab7d99 100644 --- a/packages/features/settings/appDir/SettingsLayoutAppDirClient.tsx +++ b/packages/features/settings/appDir/SettingsLayoutAppDirClient.tsx @@ -78,10 +78,6 @@ const tabs: VerticalTabItemProps[] = [ name: "general", href: "/settings/organizations/general", }, - { - name: "members", - href: "/settings/organizations/members", - }, { name: "privacy", href: "/settings/organizations/privacy", diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx index 35098e1241..be6323aadd 100644 --- a/packages/features/settings/layouts/SettingsLayout.tsx +++ b/packages/features/settings/layouts/SettingsLayout.tsx @@ -77,10 +77,6 @@ const tabs: VerticalTabItemProps[] = [ name: "general", href: "/settings/organizations/general", }, - { - name: "members", - href: "/settings/organizations/members", - }, { name: "privacy", href: "/settings/organizations/privacy", diff --git a/packages/features/shell/navigation/Navigation.tsx b/packages/features/shell/navigation/Navigation.tsx index 34bd2c1795..f246f4aff6 100644 --- a/packages/features/shell/navigation/Navigation.tsx +++ b/packages/features/shell/navigation/Navigation.tsx @@ -1,7 +1,12 @@ import { useSession } from "next-auth/react"; +import { useMemo } from "react"; import { useIsEmbed } from "@calcom/embed-core/embed-iframe"; import UnconfirmedBookingBadge from "@calcom/features/bookings/UnconfirmedBookingBadge"; +import { + useOrgBranding, + type OrganizationBranding, +} from "@calcom/features/ee/organizations/context/provider"; import { KBarTrigger } from "@calcom/features/kbar/Kbar"; import { classNames } from "@calcom/lib"; @@ -11,7 +16,7 @@ import { NavigationItem, MobileNavigationItem, MobileNavigationMoreItem } from " export const MORE_SEPARATOR_NAME = "more"; -const navigation: NavigationItemType[] = [ +const getNavigationItems = (orgBranding: OrganizationBranding): NavigationItemType[] => [ { name: "event_types_page_title", href: "/event-types", @@ -29,6 +34,15 @@ const navigation: NavigationItemType[] = [ href: "/availability", icon: "clock", }, + ...(orgBranding + ? [ + { + name: "members", + href: `/settings/organizations/${orgBranding.slug}/members`, + icon: "building", + } satisfies NavigationItemType, + ] + : []), { name: "teams", href: "/teams", @@ -102,7 +116,7 @@ const navigation: NavigationItemType[] = [ }, ]; -const platformNavigation: NavigationItemType[] = [ +const platformNavigationItems: NavigationItemType[] = [ { name: "Dashboard", href: "/settings/platform/", @@ -144,33 +158,36 @@ const platformNavigation: NavigationItemType[] = [ }, ]; -export const getDesktopNavigationItems = (isPlatformNavigation = false) => { - const navigationType = !isPlatformNavigation ? navigation : platformNavigation; - const moreSeparatorIndex = navigationType.findIndex((item) => item.name === MORE_SEPARATOR_NAME); +const useNavigationItems = (isPlatformNavigation = false) => { + const orgBranding = useOrgBranding(); + return useMemo(() => { + const navigationType = !isPlatformNavigation ? getNavigationItems(orgBranding) : platformNavigationItems; + const moreSeparatorIndex = navigationType.findIndex((item) => item.name === MORE_SEPARATOR_NAME); - const { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems } = ( - !isPlatformNavigation ? navigation : platformNavigation - ).reduce>( - (items, item, index) => { - // We filter out the "more" separator in` desktop navigation - if (item.name !== MORE_SEPARATOR_NAME) items.desktopNavigationItems.push(item); - // Items for mobile bottom navigation - if (index < moreSeparatorIndex + 1 && !item.onlyDesktop) { - items.mobileNavigationBottomItems.push(item); - } // Items for the "more" menu in mobile navigation - else { - items.mobileNavigationMoreItems.push(item); - } - return items; - }, - { desktopNavigationItems: [], mobileNavigationBottomItems: [], mobileNavigationMoreItems: [] } - ); + const { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems } = ( + !isPlatformNavigation ? getNavigationItems(orgBranding) : platformNavigationItems + ).reduce>( + (items, item, index) => { + // We filter out the "more" separator in` desktop navigation + if (item.name !== MORE_SEPARATOR_NAME) items.desktopNavigationItems.push(item); + // Items for mobile bottom navigation + if (index < moreSeparatorIndex + 1 && !item.onlyDesktop) { + items.mobileNavigationBottomItems.push(item); + } // Items for the "more" menu in mobile navigation + else { + items.mobileNavigationMoreItems.push(item); + } + return items; + }, + { desktopNavigationItems: [], mobileNavigationBottomItems: [], mobileNavigationMoreItems: [] } + ); - return { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems }; + return { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems }; + }, [isPlatformNavigation, orgBranding]); }; export const Navigation = ({ isPlatformNavigation = false }: { isPlatformNavigation?: boolean }) => { - const { desktopNavigationItems } = getDesktopNavigationItems(isPlatformNavigation); + const { desktopNavigationItems } = useNavigationItems(isPlatformNavigation); return (