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 ? (
<>
-
+
>
) : (
{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 (