fix: Important Booking Page fixes (#10130)

This commit is contained in:
Hariom Balhara
2023-07-13 19:38:41 +05:30
committed by GitHub
parent 2918a34e1a
commit 9444f925e5
17 changed files with 113 additions and 123 deletions
+2 -2
View File
@@ -18,8 +18,8 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
const router = useRouter();
const isBioEmpty = !member.bio || !member.bio.replace("<p><br></p>", "").length;
// slug is a route parameter, we don't want to forward it to the next route
const { slug: _slug, ...queryParamsToForward } = router.query;
// We don't want to forward orgSlug and user which are route params to the next route
const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = router.query;
return (
<Link key={member.id} href={{ pathname: `/${member.username}`, query: queryParamsToForward }}>
+23 -3
View File
@@ -17,7 +17,7 @@ import PageWrapper from "@components/PageWrapper";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden, rescheduleUid }: PageProps) {
export default function Type({ slug, user, booking, away, isBrandingHidden, rescheduleUid, org }: PageProps) {
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
return (
<main className={classNames("flex h-full items-center justify-center", !isEmbed && "min-h-[100dvh]")}>
@@ -26,6 +26,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden, resc
eventSlug={slug}
rescheduleUid={rescheduleUid ?? undefined}
hideBranding={isBrandingHidden}
org={org}
/>
<Booker
username={user}
@@ -33,6 +34,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden, resc
bookingData={booking}
isAway={away}
hideBranding={isBrandingHidden}
org={org}
/>
</main>
);
@@ -46,12 +48,18 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const users = await prisma.user.findMany({
where: {
username: {
in: usernames,
},
organization: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: {
allowDynamicBooking: true,
@@ -63,6 +71,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
notFound: true,
};
}
const org = isValidOrgDomain ? currentOrgDomain : null;
let booking: GetBookingType | null = null;
if (rescheduleUid) {
@@ -73,7 +82,11 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({ username: usernames.join("+"), eventSlug: slug });
const eventData = await ssr.viewer.public.event.fetch({
username: usernames.join("+"),
eventSlug: slug,
org,
});
if (!eventData) {
return {
@@ -83,6 +96,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
return {
props: {
org,
booking,
user: usernames.join("+"),
slug,
@@ -132,9 +146,14 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
booking = await getBookingForSeatedEvent(`${bookingUid}`);
}
const org = isValidOrgDomain ? currentOrgDomain : null;
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug });
const eventData = await ssr.viewer.public.event.fetch({
username,
eventSlug: slug,
org,
});
if (!eventData) {
return {
@@ -148,6 +167,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
away: user?.away,
user: username,
slug,
org,
trpcState: ssr.dehydrate(),
isBrandingHidden: user?.hideBranding,
themeBasis: username,
+13 -1
View File
@@ -18,7 +18,7 @@ const querySchema = z
async function getIdentityData(req: NextApiRequest) {
const { username, teamname } = querySchema.parse(req.query);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.host ?? "");
const org = isValidOrgDomain ? currentOrgDomain : null;
if (username) {
const user = await prisma.user.findFirst({
where: {
@@ -35,6 +35,7 @@ async function getIdentityData(req: NextApiRequest) {
name: username,
email: user?.email,
avatar: user?.avatar,
org,
};
}
if (teamname) {
@@ -50,6 +51,7 @@ async function getIdentityData(req: NextApiRequest) {
select: { logo: true },
});
return {
org,
name: teamname,
email: null,
avatar: team?.logo || getPlaceholderAvatar(null, teamname),
@@ -62,6 +64,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const img = identity?.avatar;
// If image isn't set or links to this route itself, use default avatar
if (!img) {
if (identity?.org) {
res.setHeader("x-cal-org", identity.org);
}
res.writeHead(302, {
Location: defaultAvatarSrc({
md5: crypto
@@ -70,16 +75,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
.digest("hex"),
}),
});
return res.end();
}
if (!img.includes("data:image")) {
if (identity.org) {
res.setHeader("x-cal-org", identity.org);
}
res.writeHead(302, { Location: img });
return res.end();
}
const decoded = img.toString().replace("data:image/png;base64,", "").replace("data:image/jpeg;base64,", "");
const imageResp = Buffer.from(decoded, "base64");
if (identity.org) {
res.setHeader("x-cal-org", identity.org);
}
res.writeHead(200, {
"Content-Type": "image/png",
"Content-Length": imageResp.length,
+6 -2
View File
@@ -15,7 +15,7 @@ import PageWrapper from "@components/PageWrapper";
type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent }: PageProps) {
export default function Type({ slug, user, booking, away, isBrandingHidden, isTeamEvent, org }: PageProps) {
return (
<main className="flex h-full min-h-[100dvh] items-center justify-center">
<BookerSeo
@@ -23,6 +23,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden, isTe
eventSlug={slug}
rescheduleUid={booking?.uid}
hideBranding={isBrandingHidden}
org={org}
/>
<Booker
username={user}
@@ -31,6 +32,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden, isTe
isAway={away}
hideBranding={isBrandingHidden}
isTeamEvent={isTeamEvent}
org={org}
/>
</main>
);
@@ -42,6 +44,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
const { link, slug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const org = isValidOrgDomain ? currentOrgDomain : null;
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
@@ -107,7 +110,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent });
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent, org });
if (!eventData) {
return {
@@ -117,6 +120,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
return {
props: {
org,
booking,
away: user?.away,
user: username,
@@ -55,8 +55,8 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
type Props = UserTypePageProps | TeamTypePageProps;
export default function Page(props: Props) {
if ((props as TeamTypePageProps)?.teamId) return TeamTypePage(props as TeamTypePageProps);
return UserTypePage(props as UserTypePageProps);
if ((props as TeamTypePageProps)?.teamId) return <TeamTypePage {...(props as TeamTypePageProps)} />;
return <UserTypePage {...(props as UserTypePageProps)} />;
}
Page.PageWrapper = PageWrapper;
@@ -33,8 +33,8 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
type Props = UserPageProps | TeamPageProps;
export default function Page(props: Props) {
if ((props as TeamPageProps)?.team) return TeamPage(props as TeamPageProps);
return UserPage(props as UserPageProps);
if ((props as TeamPageProps)?.team) return <TeamPage {...(props as TeamPageProps)} />;
return <UserPage {...(props as UserPageProps)} />;
}
Page.PageWrapper = PageWrapper;
+1 -1
View File
@@ -64,7 +64,7 @@ function TeamPage({ team, isUnpublished, markdownStrippedBio, isValidOrgDomain }
}
// slug is a route parameter, we don't want to forward it to the next route
const { slug: _slug, ...queryParamsToForward } = router.query;
const { slug: _slug, orgSlug: _orgSlug, user: _user, ...queryParamsToForward } = router.query;
const EventTypes = () => (
<ul className="border-subtle rounded-md border">
+15 -3
View File
@@ -5,6 +5,7 @@ import { Booker } from "@calcom/atoms";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { classNames } from "@calcom/lib";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
@@ -15,7 +16,7 @@ import PageWrapper from "@components/PageWrapper";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
export default function Type({ slug, user, booking, away, isBrandingHidden, org }: PageProps) {
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
return (
<main className={classNames("flex h-full items-center justify-center", !isEmbed && "min-h-[100dvh]")}>
@@ -25,6 +26,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
rescheduleUid={booking?.uid}
hideBranding={isBrandingHidden}
isTeamEvent
org={org}
/>
<Booker
username={user}
@@ -33,6 +35,7 @@ export default function Type({ slug, user, booking, away, isBrandingHidden }: Pa
isAway={away}
hideBranding={isBrandingHidden}
isTeamEvent
org={org}
/>
</main>
);
@@ -53,10 +56,16 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const { rescheduleUid } = context.query;
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req.headers.host ?? "");
const team = await prisma.team.findFirst({
where: {
slug: teamSlug,
parent: isValidOrgDomain
? {
slug: currentOrgDomain,
}
: null,
},
select: {
id: true,
@@ -67,7 +76,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
if (!team) {
return {
notFound: true,
};
} as const;
}
let booking: GetBookingType | null = null;
@@ -75,22 +84,25 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
booking = await getBookingForReschedule(`${rescheduleUid}`);
}
const org = isValidOrgDomain ? currentOrgDomain : null;
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({
username: teamSlug,
eventSlug: meetingSlug,
isTeamEvent: true,
org,
});
if (!eventData) {
return {
notFound: true,
};
} as const;
}
return {
props: {
org,
booking,
away: false,
user: teamSlug,
+4 -101
View File
@@ -1,104 +1,7 @@
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import withEmbedSsr from "@lib/withEmbedSsr";
import { Booker } from "@calcom/atoms";
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
import { getBookingForReschedule } from "@calcom/features/bookings/lib/get-booking";
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
import { classNames } from "@calcom/lib";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import { getServerSideProps as _getServerSideProps } from "../[type]";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
export { default } from "../[type]";
import PageWrapper from "@components/PageWrapper";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
export default function Type({ slug, user, booking, away, isBrandingHidden }: PageProps) {
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
return (
<main className={classNames("flex h-full items-center justify-center", !isEmbed && "min-h-[100dvh]")}>
<BookerSeo
username={user}
eventSlug={slug}
rescheduleUid={booking?.uid}
hideBranding={isBrandingHidden}
isTeamEvent
/>
<Booker
username={user}
eventSlug={slug}
bookingData={booking}
isAway={away}
hideBranding={isBrandingHidden}
isTeamEvent
/>
</main>
);
}
Type.PageWrapper = PageWrapper;
const paramsSchema = z.object({
type: z.string().transform((s) => slugify(s)),
slug: z.string().transform((s) => slugify(s)),
});
// Booker page fetches a tiny bit of data server side:
// 1. Check if team exists, to show 404
// 2. If rescheduling, get the booking details
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { slug: teamSlug, type: meetingSlug } = paramsSchema.parse(context.params);
const { rescheduleUid } = context.query;
const { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
const team = await prisma.team.findFirst({
where: {
slug: teamSlug,
},
select: {
id: true,
hideBranding: true,
},
});
if (!team) {
return {
notFound: true,
};
}
let booking: GetBookingType | null = null;
if (rescheduleUid) {
booking = await getBookingForReschedule(`${rescheduleUid}`);
}
// We use this to both prefetch the query on the server,
// as well as to check if the event exist, so we c an show a 404 otherwise.
const eventData = await ssr.viewer.public.event.fetch({
username: teamSlug,
eventSlug: meetingSlug,
isTeamEvent: true,
});
if (!eventData) {
return {
notFound: true,
};
}
return {
props: {
booking,
away: false,
user: teamSlug,
teamId: team.id,
slug: meetingSlug,
trpcState: ssr.dehydrate(),
isBrandingHidden: team?.hideBranding,
themeBasis: null,
},
};
};
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
@@ -37,6 +37,7 @@ const BookerComponent = ({
bookingData,
hideBranding = false,
isTeamEvent,
org,
}: BookerProps) => {
const isMobile = useMediaQuery("(max-width: 768px)");
const isTablet = useMediaQuery("(max-width: 1024px)");
@@ -96,6 +97,7 @@ const BookerComponent = ({
bookingData,
layout: defaultLayout,
isTeamEvent,
org,
});
useEffect(() => {
+18 -1
View File
@@ -25,6 +25,7 @@ type StoreInitializeType = {
bookingData?: GetBookingType | null | undefined;
rescheduleUid?: string | null;
seatReferenceUid?: string;
org?: string | null;
};
type SeatedEventData = {
@@ -105,6 +106,7 @@ export type BookerStore = {
* both the slug and the event slug.
*/
isTeamEvent: boolean;
org?: string | null;
seatedEventData: SeatedEventData;
setSeatedEventData: (seatedEventData: SeatedEventData) => void;
};
@@ -183,6 +185,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
bookingData = null,
layout,
isTeamEvent,
org,
}: StoreInitializeType) => {
const selectedDateInStore = get().selectedDate;
@@ -201,6 +204,7 @@ export const useBookerStore = create<BookerStore>((set, get) => ({
username,
eventSlug,
eventId,
org,
rescheduleUid,
bookingUid,
bookingData,
@@ -250,6 +254,7 @@ export const useInitializeBookerStore = ({
bookingData = null,
layout,
isTeamEvent,
org,
}: StoreInitializeType) => {
const initializeStore = useBookerStore((state) => state.initialize);
useEffect(() => {
@@ -262,6 +267,18 @@ export const useInitializeBookerStore = ({
bookingData,
layout,
isTeamEvent,
org,
});
}, [initializeStore, username, eventSlug, month, eventId, rescheduleUid, bookingData, layout, isTeamEvent]);
}, [
initializeStore,
org,
username,
eventSlug,
month,
eventId,
rescheduleUid,
bookingData,
layout,
isTeamEvent,
]);
};
@@ -5,6 +5,8 @@ import type { GetBookingType } from "../lib/get-booking";
export interface BookerProps {
eventSlug: string;
username: string;
// Make it optional later, once we figure out where we can possibly need to set org
org: string | null;
/**
* If month is NOT set as a prop on the component, we expect a query parameter
@@ -17,9 +17,10 @@ import { useBookerStore } from "../store";
export const useEvent = () => {
const [username, eventSlug] = useBookerStore((state) => [state.username, state.eventSlug], shallow);
const isTeamEvent = useBookerStore((state) => state.isTeamEvent);
const org = useBookerStore((state) => state.org);
return trpc.viewer.public.event.useQuery(
{ username: username ?? "", eventSlug: eventSlug ?? "", isTeamEvent },
{ username: username ?? "", eventSlug: eventSlug ?? "", isTeamEvent, org: org ?? null },
{ refetchOnWindowFocus: false, enabled: Boolean(username) && Boolean(eventSlug) }
);
};
@@ -8,13 +8,14 @@ interface BookerSeoProps {
rescheduleUid: string | undefined;
hideBranding?: boolean;
isTeamEvent?: boolean;
org: string | null;
}
export const BookerSeo = (props: BookerSeoProps) => {
const { eventSlug, username, rescheduleUid, hideBranding, isTeamEvent } = props;
const { eventSlug, username, rescheduleUid, hideBranding, isTeamEvent, org } = props;
const { t } = useLocale();
const { data: event } = trpc.viewer.public.event.useQuery(
{ username, eventSlug, isTeamEvent },
{ username, eventSlug, isTeamEvent, org },
{ refetchOnWindowFocus: false }
);
@@ -81,10 +81,15 @@ export const getPublicEvent = async (
username: string,
eventSlug: string,
isTeamEvent: boolean | undefined,
org: string | null,
prisma: PrismaClient
) => {
const usernameList = getUsernameList(username);
const orgQuery = org
? {
slug: org ?? null,
}
: null;
// In case of dynamic group event, we fetch user's data and use the default event.
if (usernameList.length > 1) {
const users = await prisma.user.findMany({
@@ -92,6 +97,7 @@ export const getPublicEvent = async (
username: {
in: usernameList,
},
organization: orgQuery,
},
select: {
username: true,
@@ -151,12 +157,14 @@ export const getPublicEvent = async (
? {
team: {
slug: username,
parent: orgQuery,
},
}
: {
users: {
some: {
username,
organization: orgQuery,
},
},
team: null,
@@ -192,6 +200,7 @@ export const getPublicEvent = async (
// Sets user data on profile object for easier access
profile: getProfileFromEvent(event),
users,
orgDomain: org,
};
};
@@ -10,6 +10,12 @@ interface EventHandlerOptions {
}
export const eventHandler = async ({ ctx, input }: EventHandlerOptions) => {
const event = await getPublicEvent(input.username, input.eventSlug, input.isTeamEvent, ctx.prisma);
const event = await getPublicEvent(
input.username,
input.eventSlug,
input.isTeamEvent,
input.org,
ctx.prisma
);
return event;
};
@@ -4,6 +4,7 @@ export const ZEventInputSchema = z.object({
username: z.string(),
eventSlug: z.string(),
isTeamEvent: z.boolean().optional(),
org: z.string().nullable(),
});
export type TEventInputSchema = z.infer<typeof ZEventInputSchema>;