chore: [app-router-migration 26] Migrate "/[user]" page (#13057)

Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
Benny Jooandzomars authored and GitHub committed 2024-01-27 10:57:43 +00:00
1 parent 9422e28598
commit c839e40a3e
17 files changed
+428 -279

No files matched your search

@@ -0,0 +1,12 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
import LegacyPage from "~/users/views/users-type-public-view";
import { getServerSideProps, type PageProps } from "~/users/views/users-type-public-view.getServerSideProps";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
@@ -0,0 +1,45 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { headers, cookies } from "next/headers";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import LegacyPage from "~/users/views/users-type-public-view";
import { getServerSideProps, type PageProps } from "~/users/views/users-type-public-view.getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Record<string, string | string[]>;
searchParams: { [key: string]: string | string[] | undefined };
}) => {
const props = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
const { eventData, booking, user, slug } = props;
const rescheduleUid = booking?.uid;
const { trpc } = await import("@calcom/trpc");
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 getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({
getData,
Page: LegacyPage,
getLayout: null,
})<"P">;
+12
View File
@@ -0,0 +1,12 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
import LegacyPage from "~/users/views/users-public-view";
import { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
+33
View File
@@ -0,0 +1,33 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { headers, cookies } from "next/headers";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
import LegacyPage from "~/users/views/users-public-view";
import { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Record<string, string | string[]>;
searchParams: { [key: string]: string | string[] | undefined };
}) => {
const props = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
const { profile, markdownStrippedBio } = props;
return await _generateMetadata(
() => profile.name,
() => markdownStrippedBio
);
};
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
export default WithLayout({ getLayout, getData, Page: LegacyPage })<"P">;
@@ -5,9 +5,10 @@ import { getSlugOrRequestedSlug } from "@calcom/features/ee/organizations/lib/or
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import { getServerSideProps as GSSUserTypePage } from "@lib/[user]/[type]/getServerSideProps";
import { getServerSideProps as GSSTeamTypePage } from "@lib/team/[slug]/[type]/getServerSideProps";
import { getServerSideProps as GSSUserTypePage } from "~/users/views/users-type-public-view.getServerSideProps";
const paramsSchema = z.object({
orgSlug: z.string().transform((s) => slugify(s)),
user: z.string(),
@@ -1,28 +1,13 @@
import type { DehydratedState } from "@tanstack/react-query";
import classNames from "classnames";
import type { GetServerSideProps, InferGetServerSidePropsType } from "next";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import type { GetServerSideProps } from "next";
import { encode } from "querystring";
import { Toaster } from "react-hot-toast";
import type { z } from "zod";
import {
sdkActionManager,
useEmbedNonStylesConfig,
useEmbedStyles,
useIsEmbed,
} 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 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 { 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 { stripMarkdown } from "@calcom/lib/stripMarkdown";
@@ -30,192 +15,57 @@ 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 { UserAvatar } from "@calcom/ui";
import { Verified, ArrowRight } from "@calcom/ui/components/icon";
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
import type { EmbedProps } from "@lib/withEmbedSsr";
import PageWrapper from "@components/PageWrapper";
import { ssrInit } from "@server/lib/ssr";
import { getTemporaryOrgRedirect } from "../lib/getTemporaryOrgRedirect";
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 function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
const searchParams = useSearchParams();
const [user] = users; //To be used when we only have a single user, not dynamic group
useTheme(profile.theme);
const { t } = useLocale();
const isBioEmpty = !user.bio || !user.bio.replace("<p><br></p>", "").length;
const isEmbed = useIsEmbed(props.isEmbed);
const eventTypeListItemEmbedStyles = useEmbedStyles("eventTypeListItem");
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const {
// So it doesn't display in the Link (and make tests fail)
user: _user,
orgSlug: _orgSlug,
redirect: _redirect,
...query
} = useRouterQuery();
const isRedirect = searchParams?.get("redirected") === "true" || false;
const fromUserNameRedirected = searchParams?.get("username") || "";
/*
const telemetry = useTelemetry();
useEffect(() => {
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/[user]"));
}
}, [telemetry, router.asPath]); */
if (entity?.isUnpublished) {
return (
<div className="flex h-full min-h-[100dvh] items-center justify-center">
<UnpublishedEntity {...entity} />
</div>
);
}
const isEventListEmpty = eventTypes.length === 0;
return (
<>
<HeadSeo
title={profile.name}
description={markdownStrippedBio}
meeting={{
title: markdownStrippedBio,
profile: { name: `${profile.name}`, image: user.avatarUrl || null },
users: [{ username: `${user.username}`, name: `${user.name}` }],
}}
nextSeoProps={{
noindex: !profile.allowSEOIndexing,
nofollow: !profile.allowSEOIndexing,
}}
/>
<div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}>
<main
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
"max-w-3xl px-4 py-24"
)}>
{isRedirect && (
<div className="mb-8 rounded-md bg-blue-100 p-4 dark:border dark:bg-transparent dark:bg-transparent">
<h2 className="text-default mb-2 text-sm font-semibold dark:text-white">
{t("user_redirect_title", {
username: fromUserNameRedirected,
})}{" "}
🏝️
</h2>
<p className="text-default text-sm">
{t("user_redirect_description", {
profile: {
username: user.username,
},
username: fromUserNameRedirected,
})}{" "}
😄
</p>
</div>
)}
<div className="mb-8 text-center">
<UserAvatar
size="xl"
user={{
organizationId: profile.organization?.id,
name: profile.name,
username: profile.username,
}}
organization={
profile.organization?.id
? {
id: profile.organization.id,
slug: profile.organization.slug,
requestedSlug: null,
}
: null
}
/>
<h1 className="font-cal text-emphasis my-1 text-3xl" data-testid="name-title">
{profile.name}
{user.verified && (
<Verified className=" mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black" />
)}
</h1>
{!isBioEmpty && (
<>
<div
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: props.safeBio }}
/>
</>
)}
</div>
<div
className={classNames("rounded-md ", !isEventListEmpty && "border-subtle border")}
data-testid="event-types">
{user.away ? (
<div className="overflow-hidden rounded-sm border ">
<div className="text-muted p-8 text-center">
<h2 className="font-cal text-default mb-2 text-3xl">😴{` ${t("user_away")}`}</h2>
<p className="mx-auto max-w-md">{t("user_away_description") as string}</p>
</div>
</div>
) : (
eventTypes.map((type) => (
<div
key={type.id}
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
className="bg-default border-subtle dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b first:rounded-t-md last:rounded-b-md last:border-b-0">
<ArrowRight className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100" />
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
<div className="block w-full p-5">
<Link
prefetch={false}
href={{
pathname: `/${user.username}/${type.slug}`,
query,
}}
passHref
onClick={async () => {
sdkActionManager?.fire("eventTypeSelected", {
eventType: type,
});
}}
data-testid="event-type-link">
<div className="flex flex-wrap items-center">
<h2 className=" text-default pr-2 text-sm font-semibold">{type.title}</h2>
</div>
<EventTypeDescription eventType={type} isPublic={true} shortenDescription />
</Link>
</div>
</div>
))
)}
</div>
{isEventListEmpty && <EmptyPage name={profile.name || "User"} />}
</main>
<Toaster position="bottom-right" />
</div>
</>
);
}
UserPage.isBookingPage = true;
UserPage.PageWrapper = PageWrapper;
const getEventTypesWithHiddenFromDB = async (userId: number) => {
export const getEventTypesWithHiddenFromDB = async (userId: number) => {
const eventTypes = await prisma.eventType.findMany({
where: {
AND: [
@@ -267,50 +117,6 @@ const getEventTypesWithHiddenFromDB = async (userId: number) => {
}, []);
};
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);
@@ -484,5 +290,3 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
},
};
};
export default UserPage;
@@ -0,0 +1,195 @@
"use client";
import classNames from "classnames";
import type { InferGetServerSidePropsType } from "next";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { Toaster } from "react-hot-toast";
import {
sdkActionManager,
useEmbedNonStylesConfig,
useEmbedStyles,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import useTheme from "@calcom/lib/hooks/useTheme";
import { HeadSeo, UnpublishedEntity, UserAvatar } from "@calcom/ui";
import { ArrowRight, Verified } from "@calcom/ui/components/icon";
import { type getServerSideProps } from "./users-public-view.getServerSideProps";
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
const searchParams = useSearchParams();
const [user] = users; //To be used when we only have a single user, not dynamic group
useTheme(profile.theme);
const { t } = useLocale();
const isBioEmpty = !user.bio || !user.bio.replace("<p><br></p>", "").length;
const isEmbed = useIsEmbed(props.isEmbed);
const eventTypeListItemEmbedStyles = useEmbedStyles("eventTypeListItem");
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
const {
// So it doesn't display in the Link (and make tests fail)
user: _user,
orgSlug: _orgSlug,
redirect: _redirect,
...query
} = useRouterQuery();
const isRedirect = searchParams?.get("redirected") === "true" || false;
const fromUserNameRedirected = searchParams?.get("username") || "";
/*
const telemetry = useTelemetry();
useEffect(() => {
if (top !== window) {
//page_view will be collected automatically by _middleware.ts
telemetry.event(telemetryEventTypes.embedView, collectPageParameters("/[user]"));
}
}, [telemetry, router.asPath]); */
if (entity?.isUnpublished) {
return (
<div className="flex h-full min-h-[100dvh] items-center justify-center">
<UnpublishedEntity {...entity} />
</div>
);
}
const isEventListEmpty = eventTypes.length === 0;
return (
<>
<HeadSeo
title={profile.name}
description={markdownStrippedBio}
meeting={{
title: markdownStrippedBio,
profile: { name: `${profile.name}`, image: user.avatarUrl || null },
users: [{ username: `${user.username}`, name: `${user.name}` }],
}}
nextSeoProps={{
noindex: !profile.allowSEOIndexing,
nofollow: !profile.allowSEOIndexing,
}}
/>
<div className={classNames(shouldAlignCentrally ? "mx-auto" : "", isEmbed ? "max-w-3xl" : "")}>
<main
className={classNames(
shouldAlignCentrally ? "mx-auto" : "",
isEmbed ? "border-booker border-booker-width bg-default rounded-md border" : "",
"max-w-3xl px-4 py-24"
)}>
{isRedirect && (
<div className="mb-8 rounded-md bg-blue-100 p-4 dark:border dark:bg-transparent dark:bg-transparent">
<h2 className="text-default mb-2 text-sm font-semibold dark:text-white">
{t("user_redirect_title", {
username: fromUserNameRedirected,
})}{" "}
🏝️
</h2>
<p className="text-default text-sm">
{t("user_redirect_description", {
profile: {
username: user.username,
},
username: fromUserNameRedirected,
})}{" "}
😄
</p>
</div>
)}
<div className="mb-8 text-center">
<UserAvatar
size="xl"
user={{
organizationId: profile.organization?.id,
name: profile.name,
username: profile.username,
}}
organization={
profile.organization?.id
? {
id: profile.organization.id,
slug: profile.organization.slug,
requestedSlug: null,
}
: null
}
/>
<h1 className="font-cal text-emphasis my-1 text-3xl" data-testid="name-title">
{profile.name}
{user.verified && (
<Verified className=" mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black" />
)}
</h1>
{!isBioEmpty && (
<>
<div
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
dangerouslySetInnerHTML={{ __html: props.safeBio }}
/>
</>
)}
</div>
<div
className={classNames("rounded-md ", !isEventListEmpty && "border-subtle border")}
data-testid="event-types">
{user.away ? (
<div className="overflow-hidden rounded-sm border ">
<div className="text-muted p-8 text-center">
<h2 className="font-cal text-default mb-2 text-3xl">😴{` ${t("user_away")}`}</h2>
<p className="mx-auto max-w-md">{t("user_away_description") as string}</p>
</div>
</div>
) : (
eventTypes.map((type) => (
<div
key={type.id}
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
className="bg-default border-subtle dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b first:rounded-t-md last:rounded-b-md last:border-b-0">
<ArrowRight className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100" />
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
<div className="block w-full p-5">
<Link
prefetch={false}
href={{
pathname: `/${user.username}/${type.slug}`,
query,
}}
passHref
onClick={async () => {
sdkActionManager?.fire("eventTypeSelected", {
eventType: type,
});
}}
data-testid="event-type-link">
<div className="flex flex-wrap items-center">
<h2 className=" text-default pr-2 text-sm font-semibold">{type.title}</h2>
</div>
<EventTypeDescription eventType={type} isPublic={true} shortenDescription />
</Link>
</div>
</div>
))
)}
</div>
{isEventListEmpty && <EmptyPage name={profile.name || "User"} />}
</main>
<Toaster position="bottom-right" />
</div>
</>
);
}
export default UserPage;
@@ -12,6 +12,10 @@ import prisma from "@calcom/prisma";
import { RedirectType } from "@calcom/prisma/client";
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
import { type inferSSRProps } from "@lib/types/inferSSRProps";
import { type EmbedProps } from "@lib/withEmbedSsr";
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
const session = await getServerSession(context);
@@ -6,14 +6,7 @@ import { Booker } from "@calcom/atoms";
import { getBookerWrapperClasses } from "@calcom/features/bookings/Booker/utils/getBookerWrapperClasses";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getServerSideProps } from "@lib/[user]/[type]/getServerSideProps";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
import PageWrapper from "@components/PageWrapper";
export type PageProps = Omit<inferSSRProps<typeof getServerSideProps>, "trpcState"> & EmbedProps;
export { getServerSideProps };
import { type PageProps } from "./users-type-public-view.getServerSideProps";
export const getMultipleDurationValue = (
multipleDurationConfig: number[] | undefined,
@@ -69,6 +62,3 @@ export default function Type({
</main>
);
}
Type.isBookingPage = true;
Type.PageWrapper = PageWrapper;
+13 -6
View File
@@ -1,11 +1,18 @@
import { type AppProps } from "@lib/app-providers";
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[type]";
import PageWrapper, { type CalPageWrapper } from "@components/PageWrapper";
export { default } from "../[type]";
import TypePage from "~/users/views/users-type-public-view";
import { getServerSideProps as _getServerSideProps } from "~/users/views/users-type-public-view.getServerSideProps";
// Somehow these types don't accept the {notFound: true} return type.
// Probably still need to fix this. I don't know why this isn't allowed yet.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
const Type = TypePage as unknown as CalPageWrapper & {
isBookingPage: AppProps["Component"]["isBookingPage"];
};
Type.isBookingPage = true;
Type.PageWrapper = PageWrapper;
export default Type;
+16
View File
@@ -0,0 +1,16 @@
import { type AppProps } from "@lib/app-providers";
import PageWrapper, { type CalPageWrapper } from "@components/PageWrapper";
import TypePage from "~/users/views/users-type-public-view";
export { getServerSideProps } from "~/users/views/users-type-public-view.getServerSideProps";
const Type = TypePage as unknown as CalPageWrapper & {
isBookingPage: AppProps["Component"]["isBookingPage"];
};
Type.isBookingPage = true;
Type.PageWrapper = PageWrapper;
export default Type;
+13 -2
View File
@@ -1,7 +1,18 @@
import { type AppProps } from "@lib/app-providers";
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[user]";
import PageWrapper, { type CalPageWrapper } from "@components/PageWrapper";
export { default } from "../[user]";
import User from "~/users/views/users-public-view";
import { getServerSideProps as _getServerSideProps } from "~/users/views/users-public-view.getServerSideProps";
const UserPage = User as unknown as CalPageWrapper & {
isBookingPage: AppProps["Component"]["isBookingPage"];
};
UserPage.isBookingPage = true;
UserPage.PageWrapper = PageWrapper;
export default UserPage;
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
+16
View File
@@ -0,0 +1,16 @@
import { type AppProps } from "@lib/app-providers";
import PageWrapper, { type CalPageWrapper } from "@components/PageWrapper";
import User from "~/users/views/users-public-view";
export { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
const UserPage = User as unknown as CalPageWrapper & {
isBookingPage: AppProps["Component"]["isBookingPage"];
};
UserPage.isBookingPage = true;
UserPage.PageWrapper = PageWrapper;
export default UserPage;
@@ -2,8 +2,8 @@
import withEmbedSsr from "@lib/withEmbedSsr";
import { getServerSideProps as _getServerSideProps } from "../[type]";
import { getServerSideProps as _getServerSideProps } from ".";
export { default, type PageProps } from "../[type]";
export { default, type PageProps } from ".";
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
@@ -2,10 +2,11 @@
import PageWrapper from "@components/PageWrapper";
import type { PageProps as UserTypePageProps } from "../../../[user]/[type]";
import UserTypePage from "../../../[user]/[type]";
import type { PageProps as TeamTypePageProps } from "../../../team/[slug]/[type]";
import TeamTypePage from "../../../team/[slug]/[type]";
import UserTypePage from "~/users/views/users-type-public-view";
import type { PageProps as UserTypePageProps } from "~/users/views/users-type-public-view.getServerSideProps";
import type { PageProps as TeamTypePageProps } from "../../../../team/[slug]/[type]";
import TeamTypePage from "../../../../team/[slug]/[type]";
export { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
@@ -2,8 +2,9 @@
import PageWrapper from "@components/PageWrapper";
import type { UserPageProps } from "../../../[user]";
import UserPage from "../../../[user]";
import UserPage from "~/users/views/users-public-view";
import { type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
import type { PageProps as TeamPageProps } from "../../../team/[slug]";
import TeamPage from "../../../team/[slug]";
+10 -9
View File
@@ -5,6 +5,7 @@ import { SchedulingType } from "@calcom/prisma/client";
import type { Schedule, TimeRange } from "@calcom/types/schedule";
import { test } from "./lib/fixtures";
import { testBothFutureAndLegacyRoutes } from "./lib/future-legacy-routes";
import {
bookFirstEvent,
bookOptinEvent,
@@ -21,7 +22,7 @@ test.afterEach(async ({ users }) => {
await users.deleteAll();
});
test.describe("free user", () => {
testBothFutureAndLegacyRoutes.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create(freeUserObj);
await page.goto(`/${free.username}`);
@@ -62,7 +63,7 @@ test.describe("free user", () => {
});
});
test.describe("pro user", () => {
testBothFutureAndLegacyRoutes.describe("pro user", () => {
test.beforeEach(async ({ page, users }) => {
const pro = await users.create();
await page.goto(`/${pro.username}`);
@@ -262,7 +263,7 @@ test.describe("pro user", () => {
});
});
test.describe("prefill", () => {
testBothFutureAndLegacyRoutes.describe("prefill", () => {
test("logged in", async ({ page, users }) => {
const prefill = await users.create({ name: "Prefill User" });
await prefill.apiLogin();
@@ -319,7 +320,7 @@ test.describe("prefill", () => {
});
});
test.describe("Booking on different layouts", () => {
testBothFutureAndLegacyRoutes.describe("Booking on different layouts", () => {
test.beforeEach(async ({ page, users }) => {
const user = await users.create();
await page.goto(`/${user.username}`);
@@ -376,7 +377,7 @@ test.describe("Booking on different layouts", () => {
});
});
test.describe("Booking round robin event", () => {
testBothFutureAndLegacyRoutes.describe("Booking round robin event", () => {
test.beforeEach(async ({ page, users }) => {
const teamMatesObj = [{ name: "teammate-1" }];
@@ -388,7 +389,7 @@ test.describe("Booking round robin event", () => {
const schedule: Schedule = [[], [dateRanges], [dateRanges], [dateRanges], [dateRanges], [dateRanges], []];
const testUser = await users.create(
{ username: "test-user", name: "Test User", email: "testuser@example.com", schedule },
{ schedule },
{
hasTeam: true,
schedulingType: SchedulingType.ROUND_ROBIN,
@@ -402,7 +403,7 @@ test.describe("Booking round robin event", () => {
test("Does not book round robin host outside availability with date override", async ({ page, users }) => {
const [testUser] = users.get();
testUser.apiLogin();
await testUser.apiLogin();
const team = await testUser.getFirstTeamMembership();
@@ -429,7 +430,7 @@ test.describe("Booking round robin event", () => {
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
const host = await page.locator('[data-testid="booking-host-name"]');
const host = page.locator('[data-testid="booking-host-name"]');
const hostName = await host.innerText();
//expect teammate-1 to be booked, test-user is not available at this time
@@ -459,7 +460,7 @@ test.describe("Booking round robin event", () => {
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
const hostSecondBooking = await page.locator('[data-testid="booking-host-name"]');
const hostSecondBooking = page.locator('[data-testid="booking-host-name"]');
const hostNameSecondBooking = await hostSecondBooking.innerText();
expect(hostNameSecondBooking).toBe("teammate-1"); // teammate-1 should be booked again
});