intuita codemod: app-directory-boilerplate-calcom
This commit is contained in:
parent
0bdc45a1a5
commit
6860ee67f0
|
@ -2,16 +2,20 @@ import type { GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
|
||||||
export const withAppDir =
|
export const withAppDir =
|
||||||
(getServerSideProps: GetServerSideProps) => async (context: GetServerSidePropsContext) => {
|
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
|
||||||
|
async (context: GetServerSidePropsContext): Promise<T> => {
|
||||||
const ssrResponse = await getServerSideProps(context);
|
const ssrResponse = await getServerSideProps(context);
|
||||||
|
|
||||||
if ("redirect" in ssrResponse) {
|
if ("redirect" in ssrResponse) {
|
||||||
redirect(ssrResponse.redirect.destination);
|
redirect(ssrResponse.redirect.destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("notFound" in ssrResponse) {
|
if ("notFound" in ssrResponse) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ssrResponse.props;
|
return {
|
||||||
|
...ssrResponse.props,
|
||||||
|
// includes dehydratedState required for future page trpcPropvider
|
||||||
|
...("trpcState" in ssrResponse.props && { dehydratedState: ssrResponse.props.trpcState }),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import LegacyPage from "@pages/[user]/[type]";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getPageProps } from "../page";
|
||||||
|
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getPageProps);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
|
@ -0,0 +1,208 @@
|
||||||
|
import LegacyPage, { type PageProps } from "@pages/[user]/[type]";
|
||||||
|
import { withAppDir } from "app/AppDirSSRHOC";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { headers, cookies } from "next/headers";
|
||||||
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||||
|
import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking";
|
||||||
|
import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
|
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||||
|
import slugify from "@calcom/lib/slugify";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import { RedirectType } from "@calcom/prisma/client";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
||||||
|
const pageProps = await getPageProps(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
const { eventData, booking, user, slug } = pageProps;
|
||||||
|
const rescheduleUid = booking?.uid;
|
||||||
|
const { trpc } = await import("@calcom/trpc/react");
|
||||||
|
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||||
|
{ username: user, eventSlug: slug, isTeamEvent: false, org: eventData.entity.orgSlug ?? null },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const profileName = event?.profile?.name ?? "";
|
||||||
|
const title = event?.title ?? "";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||||
|
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramsSchema = z.object({
|
||||||
|
type: z.string().transform((s) => slugify(s)),
|
||||||
|
user: z.string().transform((s) => getUsernameList(s)),
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||||
|
const session = await getServerSession(context);
|
||||||
|
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
|
||||||
|
const { rescheduleUid, bookingUid } = context.query;
|
||||||
|
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||||
|
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
where: {
|
||||||
|
username: {
|
||||||
|
in: usernames,
|
||||||
|
},
|
||||||
|
organization: isValidOrgDomain
|
||||||
|
? {
|
||||||
|
slug: currentOrgDomain,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
allowDynamicBooking: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!users.length) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||||
|
|
||||||
|
let booking: GetBookingType | null = null;
|
||||||
|
if (rescheduleUid) {
|
||||||
|
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||||
|
} else if (bookingUid) {
|
||||||
|
booking = await getBookingForSeatedEvent(`${bookingUid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
org,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!eventData) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
eventData: {
|
||||||
|
entity: eventData.entity,
|
||||||
|
length: eventData.length,
|
||||||
|
metadata: {
|
||||||
|
...eventData.metadata,
|
||||||
|
multipleDuration: [15, 30, 60],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
booking,
|
||||||
|
user: usernames.join("+"),
|
||||||
|
slug,
|
||||||
|
away: false,
|
||||||
|
dehydratedState: ssr.dehydrate(),
|
||||||
|
isBrandingHidden: false,
|
||||||
|
isSEOIndexable: true,
|
||||||
|
themeBasis: null,
|
||||||
|
bookingUid: bookingUid ? `${bookingUid}` : null,
|
||||||
|
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||||
|
const session = await getServerSession(context);
|
||||||
|
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
|
||||||
|
const username = usernames[0];
|
||||||
|
const { rescheduleUid, bookingUid } = context.query;
|
||||||
|
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||||
|
|
||||||
|
const isOrgContext = currentOrgDomain && isValidOrgDomain;
|
||||||
|
|
||||||
|
if (!isOrgContext) {
|
||||||
|
const redirectObj = await getTemporaryOrgRedirect({
|
||||||
|
slug: usernames[0],
|
||||||
|
redirectType: RedirectType.User,
|
||||||
|
eventTypeSlug: slug,
|
||||||
|
currentQuery: context.query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (redirectObj) {
|
||||||
|
return redirect(redirectObj.redirect.destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
username,
|
||||||
|
organization: userOrgQuery(context.req, context.params?.orgSlug),
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
away: true,
|
||||||
|
hideBranding: true,
|
||||||
|
allowSEOIndexing: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
let booking: GetBookingType | null = null;
|
||||||
|
if (rescheduleUid) {
|
||||||
|
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||||
|
} else if (bookingUid) {
|
||||||
|
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 can show a 404 otherwise.
|
||||||
|
const eventData = await ssr.viewer.public.event.fetch({
|
||||||
|
username,
|
||||||
|
eventSlug: slug,
|
||||||
|
org,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!eventData) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
booking,
|
||||||
|
eventData: {
|
||||||
|
entity: eventData.entity,
|
||||||
|
length: eventData.length,
|
||||||
|
metadata: eventData.metadata,
|
||||||
|
},
|
||||||
|
away: user?.away,
|
||||||
|
user: username,
|
||||||
|
slug,
|
||||||
|
dehydratedState: ssr.dehydrate(),
|
||||||
|
isBrandingHidden: user?.hideBranding,
|
||||||
|
isSEOIndexable: user?.allowSEOIndexing,
|
||||||
|
themeBasis: username,
|
||||||
|
bookingUid: bookingUid ? `${bookingUid}` : null,
|
||||||
|
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getPageProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const { user } = paramsSchema.parse(context.params);
|
||||||
|
const isDynamicGroup = user.length > 1;
|
||||||
|
|
||||||
|
return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context);
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error arg
|
||||||
|
export const getData = withAppDir<PageProps>(getPageProps);
|
||||||
|
|
||||||
|
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
|
|
@ -0,0 +1,10 @@
|
||||||
|
import LegacyPage from "@pages/[user]/[type]";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getPageProps } from "../page";
|
||||||
|
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getPageProps);
|
||||||
|
|
||||||
|
// @ts-expect-error arg
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
|
@ -0,0 +1,202 @@
|
||||||
|
import LegacyPage, { type UserPageProps } from "@pages/[user]";
|
||||||
|
import { withAppDir } from "app/AppDirSSRHOC";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { encode } from "querystring";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-user";
|
||||||
|
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
|
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
|
import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants";
|
||||||
|
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||||
|
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
|
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import { RedirectType } from "@calcom/prisma/client";
|
||||||
|
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
|
import { getEventTypesWithHiddenFromDB } from "@lib/[user]/getServerSideProps";
|
||||||
|
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("workflows"),
|
||||||
|
(t) => t("workflows_to_automate_notifications")
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getPageProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||||
|
const usernameList = getUsernameList(context.query.user as string);
|
||||||
|
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
||||||
|
const dataFetchStart = Date.now();
|
||||||
|
let outOfOffice = false;
|
||||||
|
|
||||||
|
if (usernameList.length === 1) {
|
||||||
|
const result = await handleUserRedirection({ username: usernameList[0] });
|
||||||
|
if (result && result.outOfOffice) {
|
||||||
|
outOfOffice = true;
|
||||||
|
}
|
||||||
|
if (result && result.redirect?.destination) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersWithoutAvatar = await prisma.user.findMany({
|
||||||
|
where: {
|
||||||
|
username: {
|
||||||
|
in: usernameList,
|
||||||
|
},
|
||||||
|
organization: isOrgContext ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
bio: true,
|
||||||
|
metadata: true,
|
||||||
|
brandColor: true,
|
||||||
|
darkBrandColor: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
organizationId: true,
|
||||||
|
organization: {
|
||||||
|
select: {
|
||||||
|
slug: true,
|
||||||
|
name: true,
|
||||||
|
metadata: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
theme: true,
|
||||||
|
away: true,
|
||||||
|
verified: true,
|
||||||
|
allowDynamicBooking: true,
|
||||||
|
allowSEOIndexing: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDynamicGroup = usersWithoutAvatar.length > 1;
|
||||||
|
if (isDynamicGroup) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `/${usernameList.join("+")}/dynamic`,
|
||||||
|
},
|
||||||
|
} as {
|
||||||
|
redirect: {
|
||||||
|
permanent: false;
|
||||||
|
destination: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = usersWithoutAvatar.map((user) => ({
|
||||||
|
...user,
|
||||||
|
organization: {
|
||||||
|
...user.organization,
|
||||||
|
metadata: user.organization?.metadata ? teamMetadataSchema.parse(user.organization.metadata) : null,
|
||||||
|
},
|
||||||
|
avatar: `/${user.username}/avatar.png`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!isOrgContext) {
|
||||||
|
const redirect = await getTemporaryOrgRedirect({
|
||||||
|
slug: usernameList[0],
|
||||||
|
redirectType: RedirectType.User,
|
||||||
|
eventTypeSlug: null,
|
||||||
|
currentQuery: context.query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
return redirect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [user] = users; //to be used when dealing with single user, not dynamic group
|
||||||
|
|
||||||
|
const profile = {
|
||||||
|
name: user.name || user.username || "",
|
||||||
|
image: user.avatar,
|
||||||
|
theme: user.theme,
|
||||||
|
brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
|
||||||
|
avatarUrl: user.avatarUrl,
|
||||||
|
darkBrandColor: user.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR,
|
||||||
|
allowSEOIndexing: user.allowSEOIndexing ?? true,
|
||||||
|
username: user.username,
|
||||||
|
organization: {
|
||||||
|
id: user.organizationId,
|
||||||
|
slug: user.organization?.slug ?? null,
|
||||||
|
requestedSlug: user.organization?.metadata?.requestedSlug ?? null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventTypesWithHidden = await getEventTypesWithHiddenFromDB(user.id);
|
||||||
|
const dataFetchEnd = Date.now();
|
||||||
|
if (context.query.log === "1") {
|
||||||
|
context.res.setHeader("X-Data-Fetch-Time", `${dataFetchEnd - dataFetchStart}ms`);
|
||||||
|
}
|
||||||
|
const eventTypesRaw = eventTypesWithHidden.filter((evt) => !evt.hidden);
|
||||||
|
|
||||||
|
const eventTypes = eventTypesRaw.map((eventType) => ({
|
||||||
|
...eventType,
|
||||||
|
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
|
||||||
|
descriptionAsSafeHTML: markdownToSafeHTML(eventType.description),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// if profile only has one public event-type, redirect to it
|
||||||
|
if (eventTypes.length === 1 && context.query.redirect !== "false" && !outOfOffice) {
|
||||||
|
// Redirect but don't change the URL
|
||||||
|
const urlDestination = `/${user.username}/${eventTypes[0].slug}`;
|
||||||
|
const { query } = context;
|
||||||
|
const urlQuery = new URLSearchParams(encode(query));
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `${urlDestination}?${urlQuery}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeBio = markdownToSafeHTML(user.bio) || "";
|
||||||
|
|
||||||
|
const markdownStrippedBio = stripMarkdown(user?.bio || "");
|
||||||
|
const org = usersWithoutAvatar[0].organization;
|
||||||
|
|
||||||
|
return {
|
||||||
|
users: users.map((user) => ({
|
||||||
|
name: user.name,
|
||||||
|
username: user.username,
|
||||||
|
bio: user.bio,
|
||||||
|
avatarUrl: user.avatarUrl,
|
||||||
|
away: usernameList.length === 1 ? outOfOffice : user.away,
|
||||||
|
verified: user.verified,
|
||||||
|
})),
|
||||||
|
entity: {
|
||||||
|
isUnpublished: org?.slug === null,
|
||||||
|
orgSlug: currentOrgDomain,
|
||||||
|
name: org?.name ?? null,
|
||||||
|
},
|
||||||
|
eventTypes,
|
||||||
|
safeBio,
|
||||||
|
profile,
|
||||||
|
// Dynamic group has no theme preference right now. It uses system theme.
|
||||||
|
themeBasis: user.username,
|
||||||
|
dehydratedState: ssr.dehydrate(),
|
||||||
|
markdownStrippedBio,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error arg
|
||||||
|
export const getData = withAppDir<UserPageProps>(getPageProps);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout, getData, Page: LegacyPage })<"P">;
|
|
@ -1,7 +1,7 @@
|
||||||
import LegacyPage from "@pages/apps/categories/index";
|
import LegacyPage from "@pages/apps/categories/index";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import AppsPage from "@pages/apps";
|
import AppsPage from "@pages/apps";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import LegacyPage from "@pages/getting-started/[[...step]]";
|
import LegacyPage from "@pages/getting-started/[[...step]]";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
import { cookies, headers } from "next/headers";
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
@ -10,10 +9,7 @@ import prisma from "@calcom/prisma";
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
const getData = async (ctx: GetServerSidePropsContext) => {
|
||||||
const req = { headers: headers(), cookies: cookies() };
|
const session = await getServerSession({ req: ctx.req });
|
||||||
|
|
||||||
//@ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest
|
|
||||||
const session = await getServerSession({ req });
|
|
||||||
|
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
return redirect("/auth/login");
|
return redirect("/auth/login");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import OldPage from "@pages/teams/index";
|
import OldPage from "@pages/teams/index";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
|
|
@ -2,7 +2,7 @@ import OldPage from "@pages/video/[uid]";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import MarkdownIt from "markdown-it";
|
import MarkdownIt from "markdown-it";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import { handleTypeRedirection } from "@calcom/features/booking-redirect/handle-type";
|
||||||
|
import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking";
|
||||||
|
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||||
|
import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
|
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||||
|
import slugify from "@calcom/lib/slugify";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import { RedirectType } from "@calcom/prisma/client";
|
||||||
|
|
||||||
|
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
|
||||||
|
|
||||||
|
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||||
|
const session = await getServerSession(context);
|
||||||
|
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
|
||||||
|
const { rescheduleUid, bookingUid } = context.query;
|
||||||
|
|
||||||
|
const { ssrInit } = await import("@server/lib/ssr");
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||||
|
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
where: {
|
||||||
|
username: {
|
||||||
|
in: usernames,
|
||||||
|
},
|
||||||
|
organization: isValidOrgDomain
|
||||||
|
? {
|
||||||
|
slug: currentOrgDomain,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
allowDynamicBooking: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!users.length) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
const org = isValidOrgDomain ? currentOrgDomain : null;
|
||||||
|
|
||||||
|
let booking: GetBookingType | null = null;
|
||||||
|
if (rescheduleUid) {
|
||||||
|
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||||
|
} else if (bookingUid) {
|
||||||
|
booking = await getBookingForSeatedEvent(`${bookingUid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
org,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!eventData) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
eventData: {
|
||||||
|
entity: eventData.entity,
|
||||||
|
length: eventData.length,
|
||||||
|
metadata: {
|
||||||
|
...eventData.metadata,
|
||||||
|
multipleDuration: [15, 30, 60],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
booking,
|
||||||
|
user: usernames.join("+"),
|
||||||
|
slug,
|
||||||
|
away: false,
|
||||||
|
trpcState: ssr.dehydrate(),
|
||||||
|
isBrandingHidden: false,
|
||||||
|
isSEOIndexable: true,
|
||||||
|
themeBasis: null,
|
||||||
|
bookingUid: bookingUid ? `${bookingUid}` : null,
|
||||||
|
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||||
|
const session = await getServerSession(context);
|
||||||
|
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
|
||||||
|
const username = usernames[0];
|
||||||
|
const { rescheduleUid, bookingUid } = context.query;
|
||||||
|
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||||
|
let outOfOffice = false;
|
||||||
|
const isOrgContext = currentOrgDomain && isValidOrgDomain;
|
||||||
|
|
||||||
|
if (!isOrgContext) {
|
||||||
|
const redirect = await getTemporaryOrgRedirect({
|
||||||
|
slug: usernames[0],
|
||||||
|
redirectType: RedirectType.User,
|
||||||
|
eventTypeSlug: slug,
|
||||||
|
currentQuery: context.query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
return redirect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ssrInit } = await import("@server/lib/ssr");
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
username,
|
||||||
|
organization: userOrgQuery(context.req, context.params?.orgSlug),
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
hideBranding: true,
|
||||||
|
allowSEOIndexing: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
// If user is found, quickly verify bookingRedirects
|
||||||
|
const result = await handleTypeRedirection({
|
||||||
|
userId: user.id,
|
||||||
|
username,
|
||||||
|
slug,
|
||||||
|
});
|
||||||
|
if (result && result.outOfOffice) {
|
||||||
|
outOfOffice = true;
|
||||||
|
}
|
||||||
|
if (result && result.redirect?.destination) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let booking: GetBookingType | null = null;
|
||||||
|
if (rescheduleUid) {
|
||||||
|
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
||||||
|
} else if (bookingUid) {
|
||||||
|
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 can show a 404 otherwise.
|
||||||
|
const eventData = await ssr.viewer.public.event.fetch({
|
||||||
|
username,
|
||||||
|
eventSlug: slug,
|
||||||
|
org,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!eventData) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
booking,
|
||||||
|
eventData: {
|
||||||
|
entity: eventData.entity,
|
||||||
|
length: eventData.length,
|
||||||
|
metadata: eventData.metadata,
|
||||||
|
},
|
||||||
|
away: outOfOffice,
|
||||||
|
user: username,
|
||||||
|
slug,
|
||||||
|
trpcState: ssr.dehydrate(),
|
||||||
|
isBrandingHidden: user?.hideBranding,
|
||||||
|
isSEOIndexable: user?.allowSEOIndexing,
|
||||||
|
themeBasis: username,
|
||||||
|
bookingUid: bookingUid ? `${bookingUid}` : null,
|
||||||
|
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Booker page fetches a tiny bit of data server side, to determine early
|
||||||
|
// whether the page should show an away state or dynamic booking not allowed.
|
||||||
|
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const { user } = paramsSchema.parse(context.params);
|
||||||
|
const isDynamicGroup = user.length > 1;
|
||||||
|
|
||||||
|
return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramsSchema = z.object({
|
||||||
|
type: z.string().transform((s) => slugify(s)),
|
||||||
|
user: z.string().transform((s) => getUsernameList(s)),
|
||||||
|
});
|
|
@ -0,0 +1,291 @@
|
||||||
|
import type { DehydratedState } from "@tanstack/react-query";
|
||||||
|
import type { GetServerSideProps } from "next";
|
||||||
|
import { encode } from "querystring";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
|
import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-user";
|
||||||
|
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
|
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
|
import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants";
|
||||||
|
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||||
|
import logger from "@calcom/lib/logger";
|
||||||
|
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||||
|
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import { RedirectType, type EventType, type User } from "@calcom/prisma/client";
|
||||||
|
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||||
|
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
|
||||||
|
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
|
||||||
|
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
export type UserPageProps = {
|
||||||
|
trpcState: DehydratedState;
|
||||||
|
profile: {
|
||||||
|
name: string;
|
||||||
|
image: string;
|
||||||
|
theme: string | null;
|
||||||
|
brandColor: string;
|
||||||
|
darkBrandColor: string;
|
||||||
|
organization: {
|
||||||
|
requestedSlug: string | null;
|
||||||
|
slug: string | null;
|
||||||
|
id: number | null;
|
||||||
|
};
|
||||||
|
allowSEOIndexing: boolean;
|
||||||
|
username: string | null;
|
||||||
|
};
|
||||||
|
users: Pick<User, "away" | "name" | "username" | "bio" | "verified" | "avatarUrl">[];
|
||||||
|
themeBasis: string | null;
|
||||||
|
markdownStrippedBio: string;
|
||||||
|
safeBio: string;
|
||||||
|
entity: {
|
||||||
|
isUnpublished?: boolean;
|
||||||
|
orgSlug?: string | null;
|
||||||
|
name?: string | null;
|
||||||
|
};
|
||||||
|
eventTypes: ({
|
||||||
|
descriptionAsSafeHTML: string;
|
||||||
|
metadata: z.infer<typeof EventTypeMetaDataSchema>;
|
||||||
|
} & Pick<
|
||||||
|
EventType,
|
||||||
|
| "id"
|
||||||
|
| "title"
|
||||||
|
| "slug"
|
||||||
|
| "length"
|
||||||
|
| "hidden"
|
||||||
|
| "lockTimeZoneToggleOnBookingPage"
|
||||||
|
| "requiresConfirmation"
|
||||||
|
| "requiresBookerEmailVerification"
|
||||||
|
| "price"
|
||||||
|
| "currency"
|
||||||
|
| "recurringEvent"
|
||||||
|
>)[];
|
||||||
|
} & EmbedProps;
|
||||||
|
|
||||||
|
export const getEventTypesWithHiddenFromDB = async (userId: number) => {
|
||||||
|
const eventTypes = await prisma.eventType.findMany({
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
teamId: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
users: {
|
||||||
|
some: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
orderBy: [
|
||||||
|
{
|
||||||
|
position: "desc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "asc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
select: {
|
||||||
|
...baseEventTypeSelect,
|
||||||
|
metadata: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// map and filter metadata, exclude eventType entirely when faulty metadata is found.
|
||||||
|
// report error to exception so we don't lose the error.
|
||||||
|
return eventTypes.reduce<typeof eventTypes>((eventTypes, eventType) => {
|
||||||
|
const parsedMetadata = EventTypeMetaDataSchema.safeParse(eventType.metadata);
|
||||||
|
if (!parsedMetadata.success) {
|
||||||
|
logger.error(parsedMetadata.error);
|
||||||
|
return eventTypes;
|
||||||
|
}
|
||||||
|
eventTypes.push({
|
||||||
|
...eventType,
|
||||||
|
metadata: parsedMetadata.data,
|
||||||
|
});
|
||||||
|
return eventTypes;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps<UserPageProps> = async (context) => {
|
||||||
|
const ssr = await ssrInit(context);
|
||||||
|
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
||||||
|
const usernameList = getUsernameList(context.query.user as string);
|
||||||
|
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
||||||
|
const dataFetchStart = Date.now();
|
||||||
|
let outOfOffice = false;
|
||||||
|
|
||||||
|
if (usernameList.length === 1) {
|
||||||
|
const result = await handleUserRedirection({ username: usernameList[0] });
|
||||||
|
if (result && result.outOfOffice) {
|
||||||
|
outOfOffice = true;
|
||||||
|
}
|
||||||
|
if (result && result.redirect?.destination) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersWithoutAvatar = await prisma.user.findMany({
|
||||||
|
where: {
|
||||||
|
username: {
|
||||||
|
in: usernameList,
|
||||||
|
},
|
||||||
|
organization: isOrgContext ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
email: true,
|
||||||
|
name: true,
|
||||||
|
bio: true,
|
||||||
|
metadata: true,
|
||||||
|
brandColor: true,
|
||||||
|
darkBrandColor: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
organizationId: true,
|
||||||
|
organization: {
|
||||||
|
select: {
|
||||||
|
slug: true,
|
||||||
|
name: true,
|
||||||
|
metadata: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
theme: true,
|
||||||
|
away: true,
|
||||||
|
verified: true,
|
||||||
|
allowDynamicBooking: true,
|
||||||
|
allowSEOIndexing: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDynamicGroup = usersWithoutAvatar.length > 1;
|
||||||
|
if (isDynamicGroup) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `/${usernameList.join("+")}/dynamic`,
|
||||||
|
},
|
||||||
|
} as {
|
||||||
|
redirect: {
|
||||||
|
permanent: false;
|
||||||
|
destination: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = usersWithoutAvatar.map((user) => ({
|
||||||
|
...user,
|
||||||
|
organization: {
|
||||||
|
...user.organization,
|
||||||
|
metadata: user.organization?.metadata ? teamMetadataSchema.parse(user.organization.metadata) : null,
|
||||||
|
},
|
||||||
|
avatar: `/${user.username}/avatar.png`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!isOrgContext) {
|
||||||
|
const redirect = await getTemporaryOrgRedirect({
|
||||||
|
slug: usernameList[0],
|
||||||
|
redirectType: RedirectType.User,
|
||||||
|
eventTypeSlug: null,
|
||||||
|
currentQuery: context.query,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
return redirect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
} as {
|
||||||
|
notFound: true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [user] = users; //to be used when dealing with single user, not dynamic group
|
||||||
|
|
||||||
|
const profile = {
|
||||||
|
name: user.name || user.username || "",
|
||||||
|
image: user.avatar,
|
||||||
|
theme: user.theme,
|
||||||
|
brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
|
||||||
|
avatarUrl: user.avatarUrl,
|
||||||
|
darkBrandColor: user.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR,
|
||||||
|
allowSEOIndexing: user.allowSEOIndexing ?? true,
|
||||||
|
username: user.username,
|
||||||
|
organization: {
|
||||||
|
id: user.organizationId,
|
||||||
|
slug: user.organization?.slug ?? null,
|
||||||
|
requestedSlug: user.organization?.metadata?.requestedSlug ?? null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventTypesWithHidden = await getEventTypesWithHiddenFromDB(user.id);
|
||||||
|
const dataFetchEnd = Date.now();
|
||||||
|
if (context.query.log === "1") {
|
||||||
|
context.res.setHeader("X-Data-Fetch-Time", `${dataFetchEnd - dataFetchStart}ms`);
|
||||||
|
}
|
||||||
|
const eventTypesRaw = eventTypesWithHidden.filter((evt) => !evt.hidden);
|
||||||
|
|
||||||
|
const eventTypes = eventTypesRaw.map((eventType) => ({
|
||||||
|
...eventType,
|
||||||
|
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
|
||||||
|
descriptionAsSafeHTML: markdownToSafeHTML(eventType.description),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// if profile only has one public event-type, redirect to it
|
||||||
|
if (eventTypes.length === 1 && context.query.redirect !== "false" && !outOfOffice) {
|
||||||
|
// Redirect but don't change the URL
|
||||||
|
const urlDestination = `/${user.username}/${eventTypes[0].slug}`;
|
||||||
|
const { query } = context;
|
||||||
|
const urlQuery = new URLSearchParams(encode(query));
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: `${urlDestination}?${urlQuery}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeBio = markdownToSafeHTML(user.bio) || "";
|
||||||
|
|
||||||
|
const markdownStrippedBio = stripMarkdown(user?.bio || "");
|
||||||
|
const org = usersWithoutAvatar[0].organization;
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
users: users.map((user) => ({
|
||||||
|
name: user.name,
|
||||||
|
username: user.username,
|
||||||
|
bio: user.bio,
|
||||||
|
avatarUrl: user.avatarUrl,
|
||||||
|
away: usernameList.length === 1 ? outOfOffice : user.away,
|
||||||
|
verified: user.verified,
|
||||||
|
})),
|
||||||
|
entity: {
|
||||||
|
isUnpublished: org?.slug === null,
|
||||||
|
orgSlug: currentOrgDomain,
|
||||||
|
name: org?.name ?? null,
|
||||||
|
},
|
||||||
|
eventTypes,
|
||||||
|
safeBio,
|
||||||
|
profile,
|
||||||
|
// Dynamic group has no theme preference right now. It uses system theme.
|
||||||
|
themeBasis: user.username,
|
||||||
|
trpcState: ssr.dehydrate(),
|
||||||
|
markdownStrippedBio,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,11 +1,10 @@
|
||||||
import type { DehydratedState } from "@tanstack/react-query";
|
"use client";
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import type { GetServerSideProps, InferGetServerSidePropsType } from "next";
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { encode } from "querystring";
|
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
import type { z } from "zod";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
sdkActionManager,
|
sdkActionManager,
|
||||||
|
@ -13,34 +12,20 @@ import {
|
||||||
useEmbedStyles,
|
useEmbedStyles,
|
||||||
useIsEmbed,
|
useIsEmbed,
|
||||||
} from "@calcom/embed-core/embed-iframe";
|
} from "@calcom/embed-core/embed-iframe";
|
||||||
import { handleUserRedirection } from "@calcom/features/booking-redirect/handle-user";
|
|
||||||
import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/orgDomains";
|
|
||||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
|
||||||
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
|
||||||
import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
|
import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
|
||||||
import { DEFAULT_DARK_BRAND_COLOR, DEFAULT_LIGHT_BRAND_COLOR } from "@calcom/lib/constants";
|
|
||||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
|
||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||||
import logger from "@calcom/lib/logger";
|
|
||||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
|
||||||
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
|
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
import { RedirectType, type EventType, type User } from "@calcom/prisma/client";
|
|
||||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
|
||||||
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
|
||||||
import { HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
import { HeadSeo, UnpublishedEntity } from "@calcom/ui";
|
||||||
import { UserAvatar } from "@calcom/ui";
|
import { UserAvatar } from "@calcom/ui";
|
||||||
import { Verified, ArrowRight } from "@calcom/ui/components/icon";
|
import { Verified, ArrowRight } from "@calcom/ui/components/icon";
|
||||||
|
|
||||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
import { getServerSideProps, type UserPageProps } from "@lib/[user]/getServerSideProps";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
export { getServerSideProps, UserPageProps };
|
||||||
|
|
||||||
import { getTemporaryOrgRedirect } from "../lib/getTemporaryOrgRedirect";
|
|
||||||
|
|
||||||
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||||
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
|
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
|
||||||
|
@ -215,273 +200,4 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
|
||||||
UserPage.isBookingPage = true;
|
UserPage.isBookingPage = true;
|
||||||
UserPage.PageWrapper = PageWrapper;
|
UserPage.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
const getEventTypesWithHiddenFromDB = async (userId: number) => {
|
|
||||||
const eventTypes = await prisma.eventType.findMany({
|
|
||||||
where: {
|
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
teamId: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
users: {
|
|
||||||
some: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
position: "desc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "asc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
select: {
|
|
||||||
...baseEventTypeSelect,
|
|
||||||
metadata: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// map and filter metadata, exclude eventType entirely when faulty metadata is found.
|
|
||||||
// report error to exception so we don't lose the error.
|
|
||||||
return eventTypes.reduce<typeof eventTypes>((eventTypes, eventType) => {
|
|
||||||
const parsedMetadata = EventTypeMetaDataSchema.safeParse(eventType.metadata);
|
|
||||||
if (!parsedMetadata.success) {
|
|
||||||
logger.error(parsedMetadata.error);
|
|
||||||
return eventTypes;
|
|
||||||
}
|
|
||||||
eventTypes.push({
|
|
||||||
...eventType,
|
|
||||||
metadata: parsedMetadata.data,
|
|
||||||
});
|
|
||||||
return eventTypes;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UserPageProps = {
|
|
||||||
trpcState: DehydratedState;
|
|
||||||
profile: {
|
|
||||||
name: string;
|
|
||||||
image: string;
|
|
||||||
theme: string | null;
|
|
||||||
brandColor: string;
|
|
||||||
darkBrandColor: string;
|
|
||||||
organization: {
|
|
||||||
requestedSlug: string | null;
|
|
||||||
slug: string | null;
|
|
||||||
id: number | null;
|
|
||||||
};
|
|
||||||
allowSEOIndexing: boolean;
|
|
||||||
username: string | null;
|
|
||||||
};
|
|
||||||
users: Pick<User, "away" | "name" | "username" | "bio" | "verified" | "avatarUrl">[];
|
|
||||||
themeBasis: string | null;
|
|
||||||
markdownStrippedBio: string;
|
|
||||||
safeBio: string;
|
|
||||||
entity: {
|
|
||||||
isUnpublished?: boolean;
|
|
||||||
orgSlug?: string | null;
|
|
||||||
name?: string | null;
|
|
||||||
};
|
|
||||||
eventTypes: ({
|
|
||||||
descriptionAsSafeHTML: string;
|
|
||||||
metadata: z.infer<typeof EventTypeMetaDataSchema>;
|
|
||||||
} & Pick<
|
|
||||||
EventType,
|
|
||||||
| "id"
|
|
||||||
| "title"
|
|
||||||
| "slug"
|
|
||||||
| "length"
|
|
||||||
| "hidden"
|
|
||||||
| "lockTimeZoneToggleOnBookingPage"
|
|
||||||
| "requiresConfirmation"
|
|
||||||
| "requiresBookerEmailVerification"
|
|
||||||
| "price"
|
|
||||||
| "currency"
|
|
||||||
| "recurringEvent"
|
|
||||||
>)[];
|
|
||||||
} & EmbedProps;
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps<UserPageProps> = async (context) => {
|
|
||||||
const ssr = await ssrInit(context);
|
|
||||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
|
||||||
const usernameList = getUsernameList(context.query.user as string);
|
|
||||||
const isOrgContext = isValidOrgDomain && currentOrgDomain;
|
|
||||||
const dataFetchStart = Date.now();
|
|
||||||
let outOfOffice = false;
|
|
||||||
|
|
||||||
if (usernameList.length === 1) {
|
|
||||||
const result = await handleUserRedirection({ username: usernameList[0] });
|
|
||||||
if (result && result.outOfOffice) {
|
|
||||||
outOfOffice = true;
|
|
||||||
}
|
|
||||||
if (result && result.redirect?.destination) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const usersWithoutAvatar = await prisma.user.findMany({
|
|
||||||
where: {
|
|
||||||
username: {
|
|
||||||
in: usernameList,
|
|
||||||
},
|
|
||||||
organization: isOrgContext ? getSlugOrRequestedSlug(currentOrgDomain) : null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
name: true,
|
|
||||||
bio: true,
|
|
||||||
metadata: true,
|
|
||||||
brandColor: true,
|
|
||||||
darkBrandColor: true,
|
|
||||||
avatarUrl: true,
|
|
||||||
organizationId: true,
|
|
||||||
organization: {
|
|
||||||
select: {
|
|
||||||
slug: true,
|
|
||||||
name: true,
|
|
||||||
metadata: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
theme: true,
|
|
||||||
away: true,
|
|
||||||
verified: true,
|
|
||||||
allowDynamicBooking: true,
|
|
||||||
allowSEOIndexing: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const isDynamicGroup = usersWithoutAvatar.length > 1;
|
|
||||||
if (isDynamicGroup) {
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
permanent: false,
|
|
||||||
destination: `/${usernameList.join("+")}/dynamic`,
|
|
||||||
},
|
|
||||||
} as {
|
|
||||||
redirect: {
|
|
||||||
permanent: false;
|
|
||||||
destination: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const users = usersWithoutAvatar.map((user) => ({
|
|
||||||
...user,
|
|
||||||
organization: {
|
|
||||||
...user.organization,
|
|
||||||
metadata: user.organization?.metadata ? teamMetadataSchema.parse(user.organization.metadata) : null,
|
|
||||||
},
|
|
||||||
avatar: `/${user.username}/avatar.png`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!isOrgContext) {
|
|
||||||
const redirect = await getTemporaryOrgRedirect({
|
|
||||||
slug: usernameList[0],
|
|
||||||
redirectType: RedirectType.User,
|
|
||||||
eventTypeSlug: null,
|
|
||||||
currentQuery: context.query,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (redirect) {
|
|
||||||
return redirect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!users.length || (!isValidOrgDomain && !users.some((user) => user.organizationId === null))) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
} as {
|
|
||||||
notFound: true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const [user] = users; //to be used when dealing with single user, not dynamic group
|
|
||||||
|
|
||||||
const profile = {
|
|
||||||
name: user.name || user.username || "",
|
|
||||||
image: user.avatar,
|
|
||||||
theme: user.theme,
|
|
||||||
brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
|
|
||||||
avatarUrl: user.avatarUrl,
|
|
||||||
darkBrandColor: user.darkBrandColor ?? DEFAULT_DARK_BRAND_COLOR,
|
|
||||||
allowSEOIndexing: user.allowSEOIndexing ?? true,
|
|
||||||
username: user.username,
|
|
||||||
organization: {
|
|
||||||
id: user.organizationId,
|
|
||||||
slug: user.organization?.slug ?? null,
|
|
||||||
requestedSlug: user.organization?.metadata?.requestedSlug ?? null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventTypesWithHidden = await getEventTypesWithHiddenFromDB(user.id);
|
|
||||||
const dataFetchEnd = Date.now();
|
|
||||||
if (context.query.log === "1") {
|
|
||||||
context.res.setHeader("X-Data-Fetch-Time", `${dataFetchEnd - dataFetchStart}ms`);
|
|
||||||
}
|
|
||||||
const eventTypesRaw = eventTypesWithHidden.filter((evt) => !evt.hidden);
|
|
||||||
|
|
||||||
const eventTypes = eventTypesRaw.map((eventType) => ({
|
|
||||||
...eventType,
|
|
||||||
metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}),
|
|
||||||
descriptionAsSafeHTML: markdownToSafeHTML(eventType.description),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// if profile only has one public event-type, redirect to it
|
|
||||||
if (eventTypes.length === 1 && context.query.redirect !== "false" && !outOfOffice) {
|
|
||||||
// Redirect but don't change the URL
|
|
||||||
const urlDestination = `/${user.username}/${eventTypes[0].slug}`;
|
|
||||||
const { query } = context;
|
|
||||||
const urlQuery = new URLSearchParams(encode(query));
|
|
||||||
|
|
||||||
return {
|
|
||||||
redirect: {
|
|
||||||
permanent: false,
|
|
||||||
destination: `${urlDestination}?${urlQuery}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const safeBio = markdownToSafeHTML(user.bio) || "";
|
|
||||||
|
|
||||||
const markdownStrippedBio = stripMarkdown(user?.bio || "");
|
|
||||||
const org = usersWithoutAvatar[0].organization;
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
users: users.map((user) => ({
|
|
||||||
name: user.name,
|
|
||||||
username: user.username,
|
|
||||||
bio: user.bio,
|
|
||||||
avatarUrl: user.avatarUrl,
|
|
||||||
away: usernameList.length === 1 ? outOfOffice : user.away,
|
|
||||||
verified: user.verified,
|
|
||||||
})),
|
|
||||||
entity: {
|
|
||||||
isUnpublished: org?.slug === null,
|
|
||||||
orgSlug: currentOrgDomain,
|
|
||||||
name: org?.name ?? null,
|
|
||||||
},
|
|
||||||
eventTypes,
|
|
||||||
safeBio,
|
|
||||||
profile,
|
|
||||||
// Dynamic group has no theme preference right now. It uses system theme.
|
|
||||||
themeBasis: user.username,
|
|
||||||
trpcState: ssr.dehydrate(),
|
|
||||||
markdownStrippedBio,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UserPage;
|
export default UserPage;
|
||||||
|
|
|
@ -1,28 +1,19 @@
|
||||||
import type { GetServerSidePropsContext } from "next";
|
"use client";
|
||||||
|
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { Booker } from "@calcom/atoms";
|
import { Booker } from "@calcom/atoms";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import { handleTypeRedirection } from "@calcom/features/booking-redirect/handle-type";
|
|
||||||
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
|
||||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||||
import { getBookingForReschedule, getBookingForSeatedEvent } from "@calcom/features/bookings/lib/get-booking";
|
|
||||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
|
||||||
import { orgDomainConfig, userOrgQuery } from "@calcom/features/ee/organizations/lib/orgDomains";
|
|
||||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
|
||||||
import slugify from "@calcom/lib/slugify";
|
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
import { RedirectType } from "@calcom/prisma/client";
|
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@lib/[user]/[type]/getServerSideProps";
|
||||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
import type { EmbedProps } from "@lib/withEmbedSsr";
|
import type { EmbedProps } from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect";
|
export type PageProps = Omit<inferSSRProps<typeof getServerSideProps>, "trpcState"> & EmbedProps;
|
||||||
|
export { getServerSideProps };
|
||||||
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
|
|
||||||
|
|
||||||
export const getMultipleDurationValue = (
|
export const getMultipleDurationValue = (
|
||||||
multipleDurationConfig: number[] | undefined,
|
multipleDurationConfig: number[] | undefined,
|
||||||
|
@ -81,191 +72,3 @@ export default function Type({
|
||||||
|
|
||||||
Type.isBookingPage = true;
|
Type.isBookingPage = true;
|
||||||
Type.PageWrapper = PageWrapper;
|
Type.PageWrapper = PageWrapper;
|
||||||
|
|
||||||
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
|
||||||
const session = await getServerSession(context);
|
|
||||||
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
|
|
||||||
const { rescheduleUid, bookingUid } = context.query;
|
|
||||||
|
|
||||||
const { ssrInit } = await import("@server/lib/ssr");
|
|
||||||
const ssr = await ssrInit(context);
|
|
||||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
|
||||||
|
|
||||||
const users = await prisma.user.findMany({
|
|
||||||
where: {
|
|
||||||
username: {
|
|
||||||
in: usernames,
|
|
||||||
},
|
|
||||||
organization: isValidOrgDomain
|
|
||||||
? {
|
|
||||||
slug: currentOrgDomain,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
allowDynamicBooking: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!users.length) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
|
||||||
|
|
||||||
let booking: GetBookingType | null = null;
|
|
||||||
if (rescheduleUid) {
|
|
||||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
|
||||||
} else if (bookingUid) {
|
|
||||||
booking = await getBookingForSeatedEvent(`${bookingUid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
org,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!eventData) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
eventData: {
|
|
||||||
entity: eventData.entity,
|
|
||||||
length: eventData.length,
|
|
||||||
metadata: {
|
|
||||||
...eventData.metadata,
|
|
||||||
multipleDuration: [15, 30, 60],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
booking,
|
|
||||||
user: usernames.join("+"),
|
|
||||||
slug,
|
|
||||||
away: false,
|
|
||||||
trpcState: ssr.dehydrate(),
|
|
||||||
isBrandingHidden: false,
|
|
||||||
isSEOIndexable: true,
|
|
||||||
themeBasis: null,
|
|
||||||
bookingUid: bookingUid ? `${bookingUid}` : null,
|
|
||||||
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getUserPageProps(context: GetServerSidePropsContext) {
|
|
||||||
const session = await getServerSession(context);
|
|
||||||
const { user: usernames, type: slug } = paramsSchema.parse(context.params);
|
|
||||||
const username = usernames[0];
|
|
||||||
const { rescheduleUid, bookingUid } = context.query;
|
|
||||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
|
|
||||||
let outOfOffice = false;
|
|
||||||
const isOrgContext = currentOrgDomain && isValidOrgDomain;
|
|
||||||
|
|
||||||
if (!isOrgContext) {
|
|
||||||
const redirect = await getTemporaryOrgRedirect({
|
|
||||||
slug: usernames[0],
|
|
||||||
redirectType: RedirectType.User,
|
|
||||||
eventTypeSlug: slug,
|
|
||||||
currentQuery: context.query,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (redirect) {
|
|
||||||
return redirect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { ssrInit } = await import("@server/lib/ssr");
|
|
||||||
const ssr = await ssrInit(context);
|
|
||||||
const user = await prisma.user.findFirst({
|
|
||||||
where: {
|
|
||||||
username,
|
|
||||||
organization: userOrgQuery(context.req, context.params?.orgSlug),
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
hideBranding: true,
|
|
||||||
allowSEOIndexing: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
// If user is found, quickly verify bookingRedirects
|
|
||||||
const result = await handleTypeRedirection({
|
|
||||||
userId: user.id,
|
|
||||||
username,
|
|
||||||
slug,
|
|
||||||
});
|
|
||||||
if (result && result.outOfOffice) {
|
|
||||||
outOfOffice = true;
|
|
||||||
}
|
|
||||||
if (result && result.redirect?.destination) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
let booking: GetBookingType | null = null;
|
|
||||||
if (rescheduleUid) {
|
|
||||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
|
||||||
} else if (bookingUid) {
|
|
||||||
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 can show a 404 otherwise.
|
|
||||||
const eventData = await ssr.viewer.public.event.fetch({
|
|
||||||
username,
|
|
||||||
eventSlug: slug,
|
|
||||||
org,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!eventData) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
booking,
|
|
||||||
eventData: {
|
|
||||||
entity: eventData.entity,
|
|
||||||
length: eventData.length,
|
|
||||||
metadata: eventData.metadata,
|
|
||||||
},
|
|
||||||
away: outOfOffice,
|
|
||||||
user: username,
|
|
||||||
slug,
|
|
||||||
trpcState: ssr.dehydrate(),
|
|
||||||
isBrandingHidden: user?.hideBranding,
|
|
||||||
isSEOIndexable: user?.allowSEOIndexing,
|
|
||||||
themeBasis: username,
|
|
||||||
bookingUid: bookingUid ? `${bookingUid}` : null,
|
|
||||||
rescheduleUid: rescheduleUid ? `${rescheduleUid}` : null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const paramsSchema = z.object({
|
|
||||||
type: z.string().transform((s) => slugify(s)),
|
|
||||||
user: z.string().transform((s) => getUsernameList(s)),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Booker page fetches a tiny bit of data server side, to determine early
|
|
||||||
// whether the page should show an away state or dynamic booking not allowed.
|
|
||||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
|
||||||
const { user } = paramsSchema.parse(context.params);
|
|
||||||
const isDynamicGroup = user.length > 1;
|
|
||||||
|
|
||||||
return isDynamicGroup ? await getDynamicGroupPageProps(context) : await getUserPageProps(context);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
"use client";
|
||||||
|
|
||||||
import { getServerSideProps as _getServerSideProps } from "../[type]";
|
import { getServerSideProps as _getServerSideProps } from "@lib/[user]/[type]/getServerSideProps";
|
||||||
|
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
export { default } from "../[type]";
|
export { default } from "../[type]";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
"use client";
|
||||||
|
|
||||||
import { getServerSideProps as _getServerSideProps } from "../[user]";
|
import { getServerSideProps as _getServerSideProps } from "@lib/[user]/getServerSideProps";
|
||||||
|
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
export { default } from "../[user]";
|
export { default } from "../[user]";
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user