diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 56b4198c88..a046499160 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -1085,6 +1085,7 @@
"make_team_private": "Make team private",
"make_team_private_description": "Your team members won't be able to see other team members when this is turned on.",
"you_cannot_see_team_members": "You cannot see all the team members of a private team.",
+ "you_cannot_see_teams_of_org":"You cannot see teams of a private organization.",
"allow_booker_to_select_duration": "Allow booker to select duration",
"impersonate_user_tip": "All uses of this feature is audited.",
"impersonating_user_warning": "Impersonating username \"{{user}}\".",
@@ -1620,6 +1621,7 @@
"test_routing": "Test Routing",
"payment_app_disabled": "An admin has disabled a payment app",
"edit_event_type": "Edit event type",
+ "only_admin_can_see_members_of_org": "This Organization is private, and only the organization's admin or owner can view its members.",
"collective_scheduling": "Collective Scheduling",
"make_it_easy_to_book": "Make it easy to book your team when everyone is available.",
"find_the_best_person": "Find the best person available and cycle through your team.",
@@ -2297,6 +2299,8 @@
"field_identifiers_as_variables": "Use field identifiers as variables for your custom event redirect",
"field_identifiers_as_variables_with_example": "Use field identifiers as variables for your custom event redirect (e.g. {{variable}})",
"account_already_linked": "Account is already linked",
+ "privacy_organization_description": "Manage privacy settings for your organization",
+ "privacy": "Privacy",
"team_will_be_under_org": "New teams will be under your organization",
"add_group_name": "Add group name",
"group_name": "Group Name",
diff --git a/packages/features/ee/organizations/pages/settings/members.tsx b/packages/features/ee/organizations/pages/settings/members.tsx
index e247b459ca..a20bb8f4b9 100644
--- a/packages/features/ee/organizations/pages/settings/members.tsx
+++ b/packages/features/ee/organizations/pages/settings/members.tsx
@@ -1,7 +1,6 @@
"use client";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
-import MakeTeamPrivateSwitch from "@calcom/features/ee/teams/components/MakeTeamPrivateSwitch";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { UserListTable } from "@calcom/features/users/components/UserTable/UserListTable";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -11,29 +10,24 @@ import { Meta } from "@calcom/ui";
const MembersView = () => {
const { t } = useLocale();
- const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery();
+ const { data: currentOrg, isPending } = trpc.viewer.organizations.listCurrent.useQuery();
- const isInviteOpen = !currentOrg?.user.accepted;
const isOrgAdminOrOwner =
currentOrg &&
(currentOrg.user.role === MembershipRole.OWNER || currentOrg.user.role === MembershipRole.ADMIN);
+ const canLoggedInUserSeeMembers =
+ (currentOrg?.isPrivate && isOrgAdminOrOwner) || isOrgAdminOrOwner || !currentOrg?.isPrivate;
+
return (
-
- {((currentOrg?.isPrivate && isOrgAdminOrOwner) || isOrgAdminOrOwner || !currentOrg?.isPrivate) && (
-
- )}
- {currentOrg && isOrgAdminOrOwner && (
-
- )}
-
+ {!isPending && canLoggedInUserSeeMembers && }
+ {!canLoggedInUserSeeMembers && (
+
+
{t("only_admin_can_see_members_of_org")}
+
+ )}
);
};
diff --git a/packages/features/ee/organizations/pages/settings/privacy.tsx b/packages/features/ee/organizations/pages/settings/privacy.tsx
new file mode 100644
index 0000000000..f68da3ab91
--- /dev/null
+++ b/packages/features/ee/organizations/pages/settings/privacy.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
+import MakeTeamPrivateSwitch from "@calcom/features/ee/teams/components/MakeTeamPrivateSwitch";
+import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
+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 PrivacyView = () => {
+ const { t } = useLocale();
+ const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery();
+ const isOrgAdminOrOwner =
+ currentOrg &&
+ (currentOrg.user.role === MembershipRole.OWNER || currentOrg.user.role === MembershipRole.ADMIN);
+ const isInviteOpen = !currentOrg?.user.accepted;
+
+ const isDisabled = isInviteOpen || !isOrgAdminOrOwner;
+
+ if (!currentOrg) return null;
+
+ return (
+
+
+
+
+
+
+ );
+};
+PrivacyView.getLayout = getLayout;
+
+export default PrivacyView;
diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx
index 48e60d7ccc..08314496fa 100644
--- a/packages/features/settings/layouts/SettingsLayout.tsx
+++ b/packages/features/settings/layouts/SettingsLayout.tsx
@@ -92,6 +92,10 @@ const tabs: VerticalTabItemProps[] = [
name: "members",
href: "/settings/organizations/members",
},
+ {
+ name: "privacy",
+ href: "/settings/organizations/privacy",
+ },
{
name: "appearance",
href: "/settings/organizations/appearance",
diff --git a/packages/lib/server/queries/teams/index.ts b/packages/lib/server/queries/teams/index.ts
index 7fd897f070..96ac66d5eb 100644
--- a/packages/lib/server/queries/teams/index.ts
+++ b/packages/lib/server/queries/teams/index.ts
@@ -99,6 +99,8 @@ export async function getTeamWithMembers(args: {
id: true,
slug: true,
name: true,
+ isPrivate: true,
+ isOrganization: true,
},
},
children: {
diff --git a/packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts b/packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts
index 2e881d02c4..679ee8f38b 100644
--- a/packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts
+++ b/packages/trpc/server/routers/viewer/organizations/listMembers.handler.ts
@@ -20,6 +20,16 @@ export const listMembersHandler = async ({ ctx, input }: GetOptions) => {
throw new TRPCError({ code: "NOT_FOUND", message: "User is not part of any organization." });
}
+ if (ctx.user.organization.isPrivate && !ctx.user.organization.isOrgAdmin) {
+ return {
+ canUserGetMembers: false,
+ rows: [],
+ meta: {
+ totalRowCount: 0,
+ },
+ };
+ }
+
const { cursor, limit } = input;
const getTotalMembers = await prisma.membership.count({
diff --git a/packages/trpc/server/routers/viewer/teams/get.handler.ts b/packages/trpc/server/routers/viewer/teams/get.handler.ts
index 21f8edccad..501a40c35e 100644
--- a/packages/trpc/server/routers/viewer/teams/get.handler.ts
+++ b/packages/trpc/server/routers/viewer/teams/get.handler.ts
@@ -1,5 +1,6 @@
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
+import { MembershipRole } from "@calcom/prisma/enums";
import { TRPCError } from "@trpc/server";
@@ -31,9 +32,20 @@ export const getHandler = async ({ ctx, input }: GetOptions) => {
if (!membership) {
throw new TRPCError({ code: "NOT_FOUND", message: "Not a member of this team." });
}
+ const { members, ...restTeam } = team;
+
+ // Hide Members of team when 1) Org is private and logged in user is not admin or owner
+ // OR
+ // 2)Team is private and logged in user is not admin or owner of team or Organization's admin or owner
+ const hideMembers =
+ (ctx.user.profile?.organization?.isPrivate && !ctx.user.organization?.isOrgAdmin) ||
+ (team.isPrivate &&
+ !(membership.role === MembershipRole.OWNER || membership.role === MembershipRole.ADMIN) &&
+ !ctx.user.organization?.isOrgAdmin);
return {
- ...team,
+ ...restTeam,
+ members: hideMembers ? [] : members,
safeBio: markdownToSafeHTML(team.bio),
membership: {
role: membership.role,