diff --git a/apps/web/pages/teams/index.tsx b/apps/web/pages/teams/index.tsx
index a179513181..846698cb8d 100644
--- a/apps/web/pages/teams/index.tsx
+++ b/apps/web/pages/teams/index.tsx
@@ -1,41 +1,47 @@
-import { serverSideTranslations } from "next-i18next/serverSideTranslations";
+import type { GetServerSidePropsContext } from "next";
import { TeamsListing } from "@calcom/features/ee/teams/components";
import Shell from "@calcom/features/shell/Shell";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui";
import { Plus } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
+import { ssrInit } from "@server/lib/ssr";
+
function Teams() {
const { t } = useLocale();
+ const [user] = trpc.viewer.me.useSuspenseQuery();
+
return (
- {t("new")}
-
+ (!user.organizationId || user.organization.isOrgAdmin) && (
+
+ )
}>
);
}
-export const getStaticProps = async () => {
- return {
- props: {
- ...(await serverSideTranslations("en", ["common"])),
- },
- };
+export const getServerSideProps = async (context: GetServerSidePropsContext) => {
+ const ssr = await ssrInit(context);
+ await ssr.viewer.me.prefetch();
+
+ return { props: { trpcState: ssr.dehydrate() } };
};
Teams.requiresLicense = false;
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 4f3d962afb..ac9de759b0 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -1923,6 +1923,7 @@
"crm": "CRM",
"messaging": "Messaging",
"sender_id_info": "Name or number shown as the sender of an SMS (some countries do not allow alphanumeric sender IDs)",
+ "org_admins_can_create_new_teams": "Only the admin of your organization can create new teams",
"google_new_spam_policy": "Google’s new spam policy could prevent you from receiving any email and calendar notifications about this booking.",
"resolve": "Resolve",
"no_organization_slug": "There was an error creating teams for this organization. Missing URL slug.",
diff --git a/packages/features/ee/teams/components/TeamsListing.tsx b/packages/features/ee/teams/components/TeamsListing.tsx
index 01ed46f185..24998c44ed 100644
--- a/packages/features/ee/teams/components/TeamsListing.tsx
+++ b/packages/features/ee/teams/components/TeamsListing.tsx
@@ -1,10 +1,10 @@
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
-import { WEBAPP_URL, APP_NAME } from "@calcom/lib/constants";
+import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
-import { Alert, Button, ButtonGroup, Label, showToast } from "@calcom/ui";
+import { Alert, Label, showToast, ButtonGroup, Button } from "@calcom/ui";
import { EyeOff, Mail, RefreshCcw, UserPlus, Users, Video } from "@calcom/ui/components/icon";
import { UpgradeTip } from "../../../tips";
@@ -26,6 +26,8 @@ export function TeamsListing() {
},
});
+ const { data: user } = trpc.viewer.me.useQuery();
+
const { mutate: inviteMemberByToken } = trpc.viewer.teams.inviteMemberByToken.useMutation({
onSuccess: (teamName) => {
trpcContext.viewer.teams.list.invalidate();
@@ -102,16 +104,20 @@ export function TeamsListing() {
features={features}
background="/tips/teams"
buttons={
-
-
-
-
-
-
+ !user?.organizationId || user?.organization.isOrgAdmin ? (
+
+
+
+
+
+
+ ) : (
+ {t("org_admins_can_create_new_teams")}
+ )
}>
{teams.length > 0 ? : <>>}
diff --git a/packages/features/settings/layouts/SettingsLayout.tsx b/packages/features/settings/layouts/SettingsLayout.tsx
index 2c5a583ff3..3381ffb3fa 100644
--- a/packages/features/settings/layouts/SettingsLayout.tsx
+++ b/packages/features/settings/layouts/SettingsLayout.tsx
@@ -284,6 +284,9 @@ const SettingsSidebarContainer = ({
{teams &&
teamMenuState &&
teams.map((team, index: number) => {
+ if (!teamMenuState[index]) {
+ return null;
+ }
if (teamMenuState.some((teamState) => teamState.teamId === team.id))
return (
{
+ const { user } = ctx;
const { slug, name, logo } = input;
- const currentOrgId = ctx.user.organization?.id;
+ const isOrgChildTeam = !!user.organizationId;
+
+ // For orgs we want to create teams under the org
+ if (user.organizationId && !user.organization.isOrgAdmin) {
+ throw new TRPCError({ code: "FORBIDDEN", message: "org_admins_can_create_new_teams" });
+ }
+
const slugCollisions = await prisma.team.findFirst({
where: {
slug: slug,
- parentId: currentOrgId
- ? {
- equals: currentOrgId,
- }
- : null,
+ // If this is under an org, check that the team doesn't already exist
+ ...(isOrgChildTeam && { parentId: user.organizationId }),
},
});
if (slugCollisions) throw new TRPCError({ code: "BAD_REQUEST", message: "team_url_taken" });
+ if (user.organizationId) {
+ const nameCollisions = await prisma.user.findFirst({
+ where: {
+ organizationId: user.organization.id,
+ username: slug,
+ },
+ });
+
+ if (nameCollisions) throw new TRPCError({ code: "BAD_REQUEST", message: "team_slug_exists_as_user" });
+ }
+
// Ensure that the user is not duplicating a requested team
const duplicatedRequest = await prisma.team.findFirst({
where: {
@@ -51,25 +64,6 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
return duplicatedRequest;
}
- let parentId: number | null = null;
- // If the user in session is part of an org. check permissions
- if (ctx.user.organization?.id) {
- if (!isOrganisationAdmin(ctx.user.id, ctx.user.organization.id)) {
- throw new TRPCError({ code: "UNAUTHORIZED" });
- }
-
- const nameCollisions = await prisma.user.findFirst({
- where: {
- organizationId: ctx.user.organization.id,
- username: slug,
- },
- });
-
- if (nameCollisions) throw new TRPCError({ code: "BAD_REQUEST", message: "team_slug_exists_as_user" });
-
- parentId = ctx.user.organization.id;
- }
-
const createTeam = await prisma.team.create({
data: {
name,
@@ -84,10 +78,7 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
metadata: {
requestedSlug: slug,
},
- ...(!IS_TEAM_BILLING_ENABLED && { slug }),
- parent: {
- connect: parentId ? { id: parentId } : undefined,
- },
+ ...(isOrgChildTeam && { parentId: user.organizationId }),
},
});