// This route is reachable by // 1. /team/[slug] // 2. / (when on org domain e.g. http://calcom.cal.com/. This is through a rewrite from next.config.js) // Also the getServerSideProps and default export are reused by // 1. org/[orgSlug]/team/[slug] // 2. org/[orgSlug]/[user]/[type] import classNames from "classnames"; import type { GetServerSidePropsContext } from "next"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect } from "react"; import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe"; import { orgDomainConfig, getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains"; import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription"; import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery"; import useTheme from "@calcom/lib/hooks/useTheme"; import logger from "@calcom/lib/logger"; import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { getTeamWithMembers } from "@calcom/lib/server/queries/teams"; import slugify from "@calcom/lib/slugify"; import { stripMarkdown } from "@calcom/lib/stripMarkdown"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import prisma from "@calcom/prisma"; import { RedirectType } from "@calcom/prisma/client"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { Avatar, Button, HeadSeo, UnpublishedEntity } from "@calcom/ui"; import { ArrowRight } from "@calcom/ui/components/icon"; import { useToggleQuery } from "@lib/hooks/useToggleQuery"; import type { inferSSRProps } from "@lib/types/inferSSRProps"; import PageWrapper from "@components/PageWrapper"; import Team from "@components/team/screens/Team"; import { UserAvatarGroup } from "@components/ui/avatar/UserAvatarGroup"; import { ssrInit } from "@server/lib/ssr"; import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect"; export type PageProps = inferSSRProps; const log = logger.getSubLogger({ prefix: ["team/[slug]"] }); function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain, currentOrgDomain, }: PageProps) { useTheme(team.theme); const routerQuery = useRouterQuery(); const pathname = usePathname(); const showMembers = useToggleQuery("members"); const { t } = useLocale(); const isEmbed = useIsEmbed(); const telemetry = useTelemetry(); const teamName = team.name || "Nameless Team"; const isBioEmpty = !team.bio || !team.bio.replace("


", "").length; const metadata = teamMetadataSchema.parse(team.metadata); useEffect(() => { telemetry.event( telemetryEventTypes.pageView, collectPageParameters("/team/[slug]", { isTeamBooking: true }) ); }, [telemetry, pathname]); if (isUnpublished) { const slug = team.slug || metadata?.requestedSlug; return (
); } // slug is a route parameter, we don't want to forward it to the next route const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = routerQuery; const EventTypes = ({ eventTypes }: { eventTypes: NonNullable<(typeof team)["eventTypes"]> }) => ( ); const SubTeams = () => team.children.length ? ( ) : (

{` ${t("org_no_teams_yet")}`}

{t("org_no_teams_yet_description")}

); return ( <>

{team.parent && `${team.parent.name} `} {teamName}

{!isBioEmpty && ( <>
)}
{metadata?.isOrganization ? ( ) : ( <> {(showMembers.isOn || !team.eventTypes?.length) && (team.isPrivate ? (

{t("you_cannot_see_team_members")}

) : ( ))} {!showMembers.isOn && team.eventTypes && team.eventTypes.length > 0 && (
{/* Hide "Book a team member button when team is private or hideBookATeamMember is true" */} {!team.hideBookATeamMember && !team.isPrivate && (
)}
)} )}
); } export const getServerSideProps = async (context: GetServerSidePropsContext) => { const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug; const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug); const isOrgContext = isValidOrgDomain && currentOrgDomain; // Provided by Rewrite from next.config.js const isOrgProfile = context.query?.isOrgProfile === "1"; const flags = await getFeatureFlagMap(prisma); const isOrganizationFeatureEnabled = flags["organizations"]; log.debug("getServerSideProps", { isOrgProfile, isOrganizationFeatureEnabled, isValidOrgDomain, currentOrgDomain, }); const team = await getTeamWithMembers({ slug: slugify(slug ?? ""), orgSlug: currentOrgDomain, isTeamView: true, isOrgView: isValidOrgDomain && isOrgProfile, }); if (!isOrgContext && slug) { const redirect = await getTemporaryOrgRedirect({ slug: slug, redirectType: RedirectType.Team, eventTypeSlug: null, currentQuery: context.query, }); if (redirect) { return redirect; } } const ssr = await ssrInit(context); const metadata = teamMetadataSchema.parse(team?.metadata ?? {}); // Taking care of sub-teams and orgs if ( (!isValidOrgDomain && team?.parent) || (!isValidOrgDomain && !!metadata?.isOrganization) || !isOrganizationFeatureEnabled ) { return { notFound: true } as const; } if (!team || (team.parent && !team.parent.slug)) { const unpublishedTeam = await prisma.team.findFirst({ where: { ...(team?.parent ? { id: team.parent.id } : { metadata: { path: ["requestedSlug"], equals: slug, }, }), }, }); if (!unpublishedTeam) return { notFound: true } as const; return { props: { isUnpublished: true, team: { ...unpublishedTeam, createdAt: null }, trpcState: ssr.dehydrate(), }, } as const; } team.eventTypes = team.eventTypes?.map((type) => ({ ...type, users: type.users.map((user) => ({ ...user, avatar: `/${user.username}/avatar.png`, })), descriptionAsSafeHTML: markdownToSafeHTML(type.description), })) ?? null; const safeBio = markdownToSafeHTML(team.bio) || ""; const members = !team.isPrivate ? team.members.map((member) => { return { name: member.name, id: member.id, bio: member.bio, subteams: member.subteams, username: member.username, accepted: member.accepted, organizationId: member.organizationId, safeBio: markdownToSafeHTML(member.bio || ""), orgOrigin: getOrgFullOrigin(member.organization?.slug || ""), }; }) : []; const markdownStrippedBio = stripMarkdown(team?.bio || ""); const { inviteToken: _inviteToken, ...serializableTeam } = team; return { props: { team: { ...serializableTeam, safeBio, members, metadata }, themeBasis: serializableTeam.slug, trpcState: ssr.dehydrate(), markdownStrippedBio, isValidOrgDomain, currentOrgDomain, }, } as const; }; TeamPage.isBookingPage = true; TeamPage.PageWrapper = PageWrapper; export default TeamPage;