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 }), }, });