diff --git a/apps/web/components/team/screens/Team.tsx b/apps/web/components/team/screens/Team.tsx index 644124b7dd..cfe2748a15 100644 --- a/apps/web/components/team/screens/Team.tsx +++ b/apps/web/components/team/screens/Team.tsx @@ -18,8 +18,8 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n const router = useRouter(); const isBioEmpty = !member.bio || !member.bio.replace("


", "").length; - // slug is a route parameter, we don't want to forward it to the next route - const { slug: _slug, ...queryParamsToForward } = router.query; + // We don't want to forward orgSlug and user which are route params to the next route + const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = router.query; return ( diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx index 509ec9a83c..77c3468737 100644 --- a/apps/web/pages/[user]/[type].tsx +++ b/apps/web/pages/[user]/[type].tsx @@ -17,7 +17,7 @@ import PageWrapper from "@components/PageWrapper"; export type PageProps = inferSSRProps; -export default function Type({ slug, user, booking, away, isBrandingHidden, rescheduleUid }: PageProps) { +export default function Type({ slug, user, booking, away, isBrandingHidden, rescheduleUid, org }: PageProps) { const isEmbed = typeof window !== "undefined" && window?.isEmbed?.(); return (
@@ -26,6 +26,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden, resc eventSlug={slug} rescheduleUid={rescheduleUid ?? undefined} hideBranding={isBrandingHidden} + org={org} />
); @@ -46,12 +48,18 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { const { ssrInit } = await import("@server/lib/ssr"); const ssr = await ssrInit(context); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? ""); const users = await prisma.user.findMany({ where: { username: { in: usernames, }, + organization: isValidOrgDomain + ? { + slug: currentOrgDomain, + } + : null, }, select: { allowDynamicBooking: true, @@ -63,6 +71,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { notFound: true, }; } + const org = isValidOrgDomain ? currentOrgDomain : null; let booking: GetBookingType | null = null; if (rescheduleUid) { @@ -73,7 +82,11 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { // We use this to both prefetch the query on the server, // as well as to check if the event exist, so we c an show a 404 otherwise. - const eventData = await ssr.viewer.public.event.fetch({ username: usernames.join("+"), eventSlug: slug }); + const eventData = await ssr.viewer.public.event.fetch({ + username: usernames.join("+"), + eventSlug: slug, + org, + }); if (!eventData) { return { @@ -83,6 +96,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) { return { props: { + org, booking, user: usernames.join("+"), slug, @@ -132,9 +146,14 @@ async function getUserPageProps(context: GetServerSidePropsContext) { booking = await getBookingForSeatedEvent(`${bookingUid}`); } + const org = isValidOrgDomain ? currentOrgDomain : null; // We use this to both prefetch the query on the server, // as well as to check if the event exist, so we c an show a 404 otherwise. - const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug }); + const eventData = await ssr.viewer.public.event.fetch({ + username, + eventSlug: slug, + org, + }); if (!eventData) { return { @@ -148,6 +167,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) { away: user?.away, user: username, slug, + org, trpcState: ssr.dehydrate(), isBrandingHidden: user?.hideBranding, themeBasis: username, diff --git a/apps/web/pages/api/user/avatar.ts b/apps/web/pages/api/user/avatar.ts index 321264ffaf..4059f87900 100644 --- a/apps/web/pages/api/user/avatar.ts +++ b/apps/web/pages/api/user/avatar.ts @@ -18,7 +18,7 @@ const querySchema = z async function getIdentityData(req: NextApiRequest) { const { username, teamname } = querySchema.parse(req.query); const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.host ?? ""); - + const org = isValidOrgDomain ? currentOrgDomain : null; if (username) { const user = await prisma.user.findFirst({ where: { @@ -35,6 +35,7 @@ async function getIdentityData(req: NextApiRequest) { name: username, email: user?.email, avatar: user?.avatar, + org, }; } if (teamname) { @@ -50,6 +51,7 @@ async function getIdentityData(req: NextApiRequest) { select: { logo: true }, }); return { + org, name: teamname, email: null, avatar: team?.logo || getPlaceholderAvatar(null, teamname), @@ -62,6 +64,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const img = identity?.avatar; // If image isn't set or links to this route itself, use default avatar if (!img) { + if (identity?.org) { + res.setHeader("x-cal-org", identity.org); + } res.writeHead(302, { Location: defaultAvatarSrc({ md5: crypto @@ -70,16 +75,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) .digest("hex"), }), }); + return res.end(); } if (!img.includes("data:image")) { + if (identity.org) { + res.setHeader("x-cal-org", identity.org); + } res.writeHead(302, { Location: img }); return res.end(); } const decoded = img.toString().replace("data:image/png;base64,", "").replace("data:image/jpeg;base64,", ""); const imageResp = Buffer.from(decoded, "base64"); + if (identity.org) { + res.setHeader("x-cal-org", identity.org); + } res.writeHead(200, { "Content-Type": "image/png", "Content-Length": imageResp.length, diff --git a/apps/web/pages/d/[link]/[slug].tsx b/apps/web/pages/d/[link]/[slug].tsx index 707f94d907..6c7dd1bd53 100644 --- a/apps/web/pages/d/[link]/[slug].tsx +++ b/apps/web/pages/d/[link]/[slug].tsx @@ -15,7 +15,7 @@ import PageWrapper from "@components/PageWrapper"; type PageProps = inferSSRProps; -export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent }: PageProps) { +export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent, org }: PageProps) { return (
); @@ -42,6 +44,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) { const { link, slug } = paramsSchema.parse(context.params); const { rescheduleUid } = context.query; const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? ""); + const org = isValidOrgDomain ? currentOrgDomain : null; const { ssrInit } = await import("@server/lib/ssr"); const ssr = await ssrInit(context); @@ -107,7 +110,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) { // We use this to both prefetch the query on the server, // as well as to check if the event exist, so we c an show a 404 otherwise. - const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent }); + const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent, org }); if (!eventData) { return { @@ -117,6 +120,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) { return { props: { + org, booking, away: user?.away, user: username, diff --git a/apps/web/pages/org/[orgSlug]/[user]/[type].ts b/apps/web/pages/org/[orgSlug]/[user]/[type].tsx similarity index 89% rename from apps/web/pages/org/[orgSlug]/[user]/[type].ts rename to apps/web/pages/org/[orgSlug]/[user]/[type].tsx index 3d98769121..134dc764a3 100644 --- a/apps/web/pages/org/[orgSlug]/[user]/[type].ts +++ b/apps/web/pages/org/[orgSlug]/[user]/[type].tsx @@ -55,8 +55,8 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { type Props = UserTypePageProps | TeamTypePageProps; export default function Page(props: Props) { - if ((props as TeamTypePageProps)?.teamId) return TeamTypePage(props as TeamTypePageProps); - return UserTypePage(props as UserTypePageProps); + if ((props as TeamTypePageProps)?.teamId) return ; + return ; } Page.PageWrapper = PageWrapper; diff --git a/apps/web/pages/org/[orgSlug]/[user]/index.tsx b/apps/web/pages/org/[orgSlug]/[user]/index.tsx index 67a68e5012..ca3ef3e56e 100644 --- a/apps/web/pages/org/[orgSlug]/[user]/index.tsx +++ b/apps/web/pages/org/[orgSlug]/[user]/index.tsx @@ -33,8 +33,8 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { type Props = UserPageProps | TeamPageProps; export default function Page(props: Props) { - if ((props as TeamPageProps)?.team) return TeamPage(props as TeamPageProps); - return UserPage(props as UserPageProps); + if ((props as TeamPageProps)?.team) return ; + return ; } Page.PageWrapper = PageWrapper; diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index 8b18720fe7..abc92bcad7 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -64,7 +64,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain } } // slug is a route parameter, we don't want to forward it to the next route - const { slug: _slug, ...queryParamsToForward } = router.query; + const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = router.query; const EventTypes = () => (
    diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index d9b6590e98..a0faa0cc34 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -5,6 +5,7 @@ import { Booker } from "@calcom/atoms"; import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo"; import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking"; import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; +import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains"; import { classNames } from "@calcom/lib"; import slugify from "@calcom/lib/slugify"; import prisma from "@calcom/prisma"; @@ -15,7 +16,7 @@ import PageWrapper from "@components/PageWrapper"; export type PageProps = inferSSRProps; -export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) { +export default function Type({ slug, user, booking, away, isBrandingHidden, org }: PageProps) { const isEmbed = typeof window !== "undefined" && window?.isEmbed?.(); return (
    @@ -25,6 +26,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa rescheduleUid={booking?.uid} hideBranding={isBrandingHidden} isTeamEvent + org={org} />
    ); @@ -53,10 +56,16 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const { rescheduleUid } = context.query; const { ssrInit } = await import("@server/lib/ssr"); const ssr = await ssrInit(context); + const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? ""); const team = await prisma.team.findFirst({ where: { slug: teamSlug, + parent: isValidOrgDomain + ? { + slug: currentOrgDomain, + } + : null, }, select: { id: true, @@ -67,7 +76,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => if (!team) { return { notFound: true, - }; + } as const; } let booking: GetBookingType | null = null; @@ -75,22 +84,25 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => booking = await getBookingForReschedule(`${rescheduleUid}`); } + const org = isValidOrgDomain ? currentOrgDomain : null; // We use this to both prefetch the query on the server, // as well as to check if the event exist, so we c an show a 404 otherwise. const eventData = await ssr.viewer.public.event.fetch({ username: teamSlug, eventSlug: meetingSlug, isTeamEvent: true, + org, }); if (!eventData) { return { notFound: true, - }; + } as const; } return { props: { + org, booking, away: false, user: teamSlug, diff --git a/apps/web/pages/team/[slug]/[type]/embed.tsx b/apps/web/pages/team/[slug]/[type]/embed.tsx index d9b6590e98..5cfd1726ca 100644 --- a/apps/web/pages/team/[slug]/[type]/embed.tsx +++ b/apps/web/pages/team/[slug]/[type]/embed.tsx @@ -1,104 +1,7 @@ -import type { GetServerSidePropsContext } from "next"; -import { z } from "zod"; +import withEmbedSsr from "@lib/withEmbedSsr"; -import { Booker } from "@calcom/atoms"; -import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo"; -import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking"; -import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking"; -import { classNames } from "@calcom/lib"; -import slugify from "@calcom/lib/slugify"; -import prisma from "@calcom/prisma"; +import { getServerSideProps as _getServerSideProps } from "../[type]"; -import type { inferSSRProps } from "@lib/types/inferSSRProps"; +export { default } from "../[type]"; -import PageWrapper from "@components/PageWrapper"; - -export type PageProps = inferSSRProps; - -export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) { - const isEmbed = typeof window !== "undefined" && window?.isEmbed?.(); - return ( -
    - - -
    - ); -} - -Type.PageWrapper = PageWrapper; - -const paramsSchema = z.object({ - type: z.string().transform((s) => slugify(s)), - slug: z.string().transform((s) => slugify(s)), -}); - -// Booker page fetches a tiny bit of data server side: -// 1. Check if team exists, to show 404 -// 2. If rescheduling, get the booking details -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params); - const { rescheduleUid } = context.query; - const { ssrInit } = await import("@server/lib/ssr"); - const ssr = await ssrInit(context); - - const team = await prisma.team.findFirst({ - where: { - slug: teamSlug, - }, - select: { - id: true, - hideBranding: true, - }, - }); - - if (!team) { - return { - notFound: true, - }; - } - - let booking: GetBookingType | null = null; - if (rescheduleUid) { - booking = await getBookingForReschedule(`${rescheduleUid}`); - } - - // We use this to both prefetch the query on the server, - // as well as to check if the event exist, so we c an show a 404 otherwise. - const eventData = await ssr.viewer.public.event.fetch({ - username: teamSlug, - eventSlug: meetingSlug, - isTeamEvent: true, - }); - - if (!eventData) { - return { - notFound: true, - }; - } - - return { - props: { - booking, - away: false, - user: teamSlug, - teamId: team.id, - slug: meetingSlug, - trpcState: ssr.dehydrate(), - isBrandingHidden: team?.hideBranding, - themeBasis: null, - }, - }; -}; +export const getServerSideProps = withEmbedSsr(_getServerSideProps); diff --git a/packages/features/bookings/Booker/Booker.tsx b/packages/features/bookings/Booker/Booker.tsx index cec7296583..e0138e62a9 100644 --- a/packages/features/bookings/Booker/Booker.tsx +++ b/packages/features/bookings/Booker/Booker.tsx @@ -37,6 +37,7 @@ const BookerComponent = ({ bookingData, hideBranding = false, isTeamEvent, + org, }: BookerProps) => { const isMobile = useMediaQuery("(max-width: 768px)"); const isTablet = useMediaQuery("(max-width: 1024px)"); @@ -96,6 +97,7 @@ const BookerComponent = ({ bookingData, layout: defaultLayout, isTeamEvent, + org, }); useEffect(() => { diff --git a/packages/features/bookings/Booker/store.ts b/packages/features/bookings/Booker/store.ts index 0fdeba4e34..605b9a20a6 100644 --- a/packages/features/bookings/Booker/store.ts +++ b/packages/features/bookings/Booker/store.ts @@ -25,6 +25,7 @@ type StoreInitializeType = { bookingData?: GetBookingType | null | undefined; rescheduleUid?: string | null; seatReferenceUid?: string; + org?: string | null; }; type SeatedEventData = { @@ -105,6 +106,7 @@ export type BookerStore = { * both the slug and the event slug. */ isTeamEvent: boolean; + org?: string | null; seatedEventData: SeatedEventData; setSeatedEventData: (seatedEventData: SeatedEventData) => void; }; @@ -183,6 +185,7 @@ export const useBookerStore = create((set, get) => ({ bookingData = null, layout, isTeamEvent, + org, }: StoreInitializeType) => { const selectedDateInStore = get().selectedDate; @@ -201,6 +204,7 @@ export const useBookerStore = create((set, get) => ({ username, eventSlug, eventId, + org, rescheduleUid, bookingUid, bookingData, @@ -250,6 +254,7 @@ export const useInitializeBookerStore = ({ bookingData = null, layout, isTeamEvent, + org, }: StoreInitializeType) => { const initializeStore = useBookerStore((state) => state.initialize); useEffect(() => { @@ -262,6 +267,18 @@ export const useInitializeBookerStore = ({ bookingData, layout, isTeamEvent, + org, }); - }, [initializeStore, username, eventSlug, month, eventId, rescheduleUid, bookingData, layout, isTeamEvent]); + }, [ + initializeStore, + org, + username, + eventSlug, + month, + eventId, + rescheduleUid, + bookingData, + layout, + isTeamEvent, + ]); }; diff --git a/packages/features/bookings/Booker/types.ts b/packages/features/bookings/Booker/types.ts index 7f0ff27516..14e3ec3043 100644 --- a/packages/features/bookings/Booker/types.ts +++ b/packages/features/bookings/Booker/types.ts @@ -5,6 +5,8 @@ import type { GetBookingType } from "../lib/get-booking"; export interface BookerProps { eventSlug: string; username: string; + // Make it optional later, once we figure out where we can possibly need to set org + org: string | null; /** * If month is NOT set as a prop on the component, we expect a query parameter diff --git a/packages/features/bookings/Booker/utils/event.ts b/packages/features/bookings/Booker/utils/event.ts index 0cdb6f9b79..df8532aa3e 100644 --- a/packages/features/bookings/Booker/utils/event.ts +++ b/packages/features/bookings/Booker/utils/event.ts @@ -17,9 +17,10 @@ import { useBookerStore } from "../store"; export const useEvent = () => { const [username, eventSlug] = useBookerStore((state) => [state.username, state.eventSlug], shallow); const isTeamEvent = useBookerStore((state) => state.isTeamEvent); + const org = useBookerStore((state) => state.org); return trpc.viewer.public.event.useQuery( - { username: username ?? "", eventSlug: eventSlug ?? "", isTeamEvent }, + { username: username ?? "", eventSlug: eventSlug ?? "", isTeamEvent, org: org ?? null }, { refetchOnWindowFocus: false, enabled: Boolean(username) && Boolean(eventSlug) } ); }; diff --git a/packages/features/bookings/components/BookerSeo.tsx b/packages/features/bookings/components/BookerSeo.tsx index 0143b050ec..215527a171 100644 --- a/packages/features/bookings/components/BookerSeo.tsx +++ b/packages/features/bookings/components/BookerSeo.tsx @@ -8,13 +8,14 @@ interface BookerSeoProps { rescheduleUid: string | undefined; hideBranding?: boolean; isTeamEvent?: boolean; + org: string | null; } export const BookerSeo = (props: BookerSeoProps) => { - const { eventSlug, username, rescheduleUid, hideBranding, isTeamEvent } = props; + const { eventSlug, username, rescheduleUid, hideBranding, isTeamEvent, org } = props; const { t } = useLocale(); const { data: event } = trpc.viewer.public.event.useQuery( - { username, eventSlug, isTeamEvent }, + { username, eventSlug, isTeamEvent, org }, { refetchOnWindowFocus: false } ); diff --git a/packages/features/eventtypes/lib/getPublicEvent.ts b/packages/features/eventtypes/lib/getPublicEvent.ts index da88f674a7..c601c619de 100644 --- a/packages/features/eventtypes/lib/getPublicEvent.ts +++ b/packages/features/eventtypes/lib/getPublicEvent.ts @@ -81,10 +81,15 @@ export const getPublicEvent = async ( username: string, eventSlug: string, isTeamEvent: boolean | undefined, + org: string | null, prisma: PrismaClient ) => { const usernameList = getUsernameList(username); - + const orgQuery = org + ? { + slug: org ?? null, + } + : null; // In case of dynamic group event, we fetch user's data and use the default event. if (usernameList.length > 1) { const users = await prisma.user.findMany({ @@ -92,6 +97,7 @@ export const getPublicEvent = async ( username: { in: usernameList, }, + organization: orgQuery, }, select: { username: true, @@ -151,12 +157,14 @@ export const getPublicEvent = async ( ? { team: { slug: username, + parent: orgQuery, }, } : { users: { some: { username, + organization: orgQuery, }, }, team: null, @@ -192,6 +200,7 @@ export const getPublicEvent = async ( // Sets user data on profile object for easier access profile: getProfileFromEvent(event), users, + orgDomain: org, }; }; diff --git a/packages/trpc/server/routers/publicViewer/event.handler.ts b/packages/trpc/server/routers/publicViewer/event.handler.ts index 6daee0a968..c1a20e5a77 100644 --- a/packages/trpc/server/routers/publicViewer/event.handler.ts +++ b/packages/trpc/server/routers/publicViewer/event.handler.ts @@ -10,6 +10,12 @@ interface EventHandlerOptions { } export const eventHandler = async ({ ctx, input }: EventHandlerOptions) => { - const event = await getPublicEvent(input.username, input.eventSlug, input.isTeamEvent, ctx.prisma); + const event = await getPublicEvent( + input.username, + input.eventSlug, + input.isTeamEvent, + input.org, + ctx.prisma + ); return event; }; diff --git a/packages/trpc/server/routers/publicViewer/event.schema.ts b/packages/trpc/server/routers/publicViewer/event.schema.ts index d5c01b9248..6436d1642a 100644 --- a/packages/trpc/server/routers/publicViewer/event.schema.ts +++ b/packages/trpc/server/routers/publicViewer/event.schema.ts @@ -4,6 +4,7 @@ export const ZEventInputSchema = z.object({ username: z.string(), eventSlug: z.string(), isTeamEvent: z.boolean().optional(), + org: z.string().nullable(), }); export type TEventInputSchema = z.infer;