feat: new booker embed (#9023)
* Added embed files for new booker routes. * Updated nextjs config to redirect to correct embed url. * Notify embed of booker size after animation completes. * Don't use animations and 100vh in embed. * Added booker layout toggles to embed settings (wip) * Booker header positioning tweaks * Show correct layout in embed * Don't show pricing event meta if stripe app is not installed * Support custom border booker for embed * wip on passing layout prop to iframe for previews * Only show booker layout settings in embed-create-popup if booker layout feature is enabled. * Made layout prop in preview state for embed optional * Made layout prop in preview state for embed optional * Get layout toggle working * Fixed types * Added themebasis prop * Fix potential circular import that should then fix type errors * Fixed type error by fixing casing. * Type fixes * Added translations * More translations * Remove comment * Removed uneccessary addition to package.json * Removed todo since it's solved in another way --------- Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
This commit is contained in:
1 parent
f0992b4914
commit
8331059b7c
21 files changed
+176
-36
No files matched your search
@@ -8,8 +8,11 @@ import { createRef, forwardRef, useRef, useState } from "react";
|
||||
import type { ControlProps } from "react-select";
|
||||
import { components } from "react-select";
|
||||
|
||||
import type { BookerLayout } from "@calcom/features/bookings/Booker/types";
|
||||
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
||||
import { APP_NAME, EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -41,12 +44,13 @@ type PreviewState = {
|
||||
height: string;
|
||||
};
|
||||
theme: Theme;
|
||||
floatingPopup: Record<string, string>;
|
||||
floatingPopup: Record<string, string | boolean | undefined>;
|
||||
elementClick: Record<string, string>;
|
||||
palette: {
|
||||
brandColor: string;
|
||||
};
|
||||
hideEventTypeDetails: boolean;
|
||||
layout: BookerLayouts;
|
||||
};
|
||||
const queryParamsForDialog = ["embedType", "embedTabName", "embedUrl"];
|
||||
|
||||
@@ -129,11 +133,13 @@ const getEmbedUIInstructionString = ({
|
||||
theme,
|
||||
brandColor,
|
||||
hideEventTypeDetails,
|
||||
layout,
|
||||
}: {
|
||||
apiName: string;
|
||||
theme?: string;
|
||||
brandColor: string;
|
||||
hideEventTypeDetails: boolean;
|
||||
layout?: string;
|
||||
}) => {
|
||||
theme = theme !== "auto" ? theme : undefined;
|
||||
return getInstructionString({
|
||||
@@ -147,6 +153,7 @@ const getEmbedUIInstructionString = ({
|
||||
},
|
||||
},
|
||||
hideEventTypeDetails: hideEventTypeDetails,
|
||||
layout,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -260,6 +267,7 @@ const getEmbedTypeSpecificString = ({
|
||||
theme: PreviewState["theme"];
|
||||
brandColor: string;
|
||||
hideEventTypeDetails: boolean;
|
||||
layout?: BookerLayout;
|
||||
};
|
||||
if (embedFramework === "react") {
|
||||
uiInstructionStringArg = {
|
||||
@@ -267,6 +275,7 @@ const getEmbedTypeSpecificString = ({
|
||||
theme: previewState.theme,
|
||||
brandColor: previewState.palette.brandColor,
|
||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||
layout: previewState.layout,
|
||||
};
|
||||
} else {
|
||||
uiInstructionStringArg = {
|
||||
@@ -274,6 +283,7 @@ const getEmbedTypeSpecificString = ({
|
||||
theme: previewState.theme,
|
||||
brandColor: previewState.palette.brandColor,
|
||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||
layout: previewState.layout,
|
||||
};
|
||||
}
|
||||
if (!frameworkCodes[embedType]) {
|
||||
@@ -626,6 +636,7 @@ const ThemeSelectControl = ({ children, ...props }: ControlProps<{ value: Theme;
|
||||
const ChooseEmbedTypesDialogContent = () => {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<DialogContent className="rounded-lg p-10" type="creation" size="lg">
|
||||
<div className="mb-2">
|
||||
@@ -671,6 +682,8 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
const router = useRouter();
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const dialogContentRef = useRef<HTMLDivElement>(null);
|
||||
const flags = useFlagMap();
|
||||
const isBookerLayoutsEnabled = flags["booker-layouts"] === true;
|
||||
|
||||
const s = (href: string) => {
|
||||
const searchParams = new URLSearchParams(router.asPath.split("?")[1] || "");
|
||||
@@ -691,7 +704,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
|
||||
const [isEmbedCustomizationOpen, setIsEmbedCustomizationOpen] = useState(true);
|
||||
const [isBookingCustomizationOpen, setIsBookingCustomizationOpen] = useState(true);
|
||||
const [previewState, setPreviewState] = useState({
|
||||
const [previewState, setPreviewState] = useState<PreviewState>({
|
||||
inline: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
@@ -703,6 +716,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
palette: {
|
||||
brandColor: "#000000",
|
||||
},
|
||||
layout: BookerLayouts.MONTH_VIEW,
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
@@ -765,6 +779,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
arg: {
|
||||
theme: previewState.theme,
|
||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||
layout: previewState.layout,
|
||||
styles: {
|
||||
branding: {
|
||||
...previewState.palette,
|
||||
@@ -798,6 +813,12 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
{ value: Theme.light, label: "Light Theme" },
|
||||
];
|
||||
|
||||
const layoutOptions = [
|
||||
{ value: BookerLayouts.MONTH_VIEW, label: t("bookerlayout_month_view") },
|
||||
{ value: BookerLayouts.WEEK_VIEW, label: t("bookerlayout_week_view") },
|
||||
{ value: BookerLayouts.COLUMN_VIEW, label: t("bookerlayout_column_view") },
|
||||
];
|
||||
|
||||
const FloatingPopupPositionOptions = [
|
||||
{
|
||||
value: "bottom-right",
|
||||
@@ -1070,6 +1091,27 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
</div>
|
||||
</Label>
|
||||
))}
|
||||
{isBookerLayoutsEnabled && (
|
||||
<Label className="mb-6">
|
||||
<div className="mb-2">{t("layout")}</div>
|
||||
<Select
|
||||
className="w-full"
|
||||
defaultValue={layoutOptions[0]}
|
||||
onChange={(option) => {
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
layout: option.value,
|
||||
};
|
||||
});
|
||||
}}
|
||||
options={layoutOptions}
|
||||
/>
|
||||
</Label>
|
||||
)}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
@@ -8,6 +8,7 @@ export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
|
||||
return async (context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<EmbedProps>> => {
|
||||
const ssrResponse = await getServerSideProps(context);
|
||||
const embed = context.query.embed;
|
||||
const layout = context.query.layout;
|
||||
|
||||
if ("redirect" in ssrResponse) {
|
||||
// Use a dummy URL https://base as the fallback base URL so that URL parsing works for relative URLs as well.
|
||||
@@ -18,6 +19,8 @@ export default function withEmbedSsr(getServerSideProps: GetServerSideProps) {
|
||||
destinationUrlObj.pathname +
|
||||
"/embed?" +
|
||||
destinationUrlObj.searchParams.toString() +
|
||||
"&layout=" +
|
||||
layout +
|
||||
"&embed=" +
|
||||
embed;
|
||||
|
||||
|
||||
@@ -224,12 +224,19 @@ const nextConfig = {
|
||||
destination: "/new-booker/:user/:type",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
{
|
||||
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
|
||||
destination: "/new-booker/:user/:type/embed",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
{
|
||||
source: "/team/:slug/:type",
|
||||
destination: "/new-booker/team/:slug/:type",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
{
|
||||
source: "/team/:slug/:type/embed",
|
||||
destination: "/new-booker/team/:slug/:type/embed",
|
||||
source: "/d/:link/:slug",
|
||||
destination: "/new-booker/d/:link/:slug",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Booker } from "@calcom/atoms";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
|
||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { getUsernameList } from "@calcom/lib/defaultEvents";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
@@ -15,8 +16,9 @@ import PageWrapper from "@components/PageWrapper";
|
||||
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="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<main className={classNames("flex h-full items-center justify-center", !isEmbed && "min-h-[100dvh]")}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
@@ -84,6 +86,7 @@ async function getDynamicGroupPageProps(context: GetServerSidePropsContext) {
|
||||
away: false,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: false,
|
||||
themeBasis: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -133,6 +136,7 @@ async function getUserPageProps(context: GetServerSidePropsContext) {
|
||||
slug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: user?.hideBranding,
|
||||
themeBasis: username,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[type]";
|
||||
|
||||
export { default } from "../[type]";
|
||||
|
||||
// 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);
|
||||
@@ -0,0 +1,7 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../../[user]";
|
||||
|
||||
export { default } from "../../[user]";
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
@@ -5,6 +5,7 @@ import { Booker } from "@calcom/atoms";
|
||||
import { BookerSeo } from "@calcom/features/bookings/components/BookerSeo";
|
||||
import { getBookingByUidOrRescheduleUid } from "@calcom/features/bookings/lib/get-booking";
|
||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import type { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||
@@ -14,8 +15,9 @@ import PageWrapper from "@components/PageWrapper";
|
||||
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="flex h-full min-h-[100dvh] items-center justify-center">
|
||||
<main className={classNames("flex h-full items-center justify-center", !isEmbed && "min-h-[100dvh]")}>
|
||||
<BookerSeo
|
||||
username={user}
|
||||
eventSlug={slug}
|
||||
@@ -85,6 +87,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
||||
slug: meetingSlug,
|
||||
trpcState: ssr.dehydrate(),
|
||||
isBrandingHidden: team?.hideBranding,
|
||||
themeBasis: null,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../[type]";
|
||||
|
||||
export { default } from "../[type]";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
@@ -0,0 +1,7 @@
|
||||
import withEmbedSsr from "@lib/withEmbedSsr";
|
||||
|
||||
import { getServerSideProps as _getServerSideProps } from "../../../team/[slug]";
|
||||
|
||||
export { default } from "../../../team/[slug]";
|
||||
|
||||
export const getServerSideProps = withEmbedSsr(_getServerSideProps);
|
||||
@@ -300,6 +300,7 @@
|
||||
"failed": "Failed",
|
||||
"password_has_been_reset_login": "Your password has been reset. You can now login with your newly created password.",
|
||||
"bookerlayout_title": "Layout",
|
||||
"layout": "Layout",
|
||||
"bookerlayout_default_title": "Default view",
|
||||
"bookerlayout_description": "You can select multiple and your bookers can switch views.",
|
||||
"bookerlayout_user_settings_title": "Booking layout",
|
||||
|
||||
@@ -150,6 +150,12 @@
|
||||
data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">
|
||||
Book through Routing Form [Dark Theme]
|
||||
</button>
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-config='{"layout":"week_view"}' data-cal-link="pro/paid">
|
||||
Book Paid Event - weekly view
|
||||
</button>
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-config='{"layout":"column_view"}' data-cal-link="pro/paid">
|
||||
Book Paid Event - column view
|
||||
</button>
|
||||
<!-- <div>
|
||||
<h2>Embed for Pages behind authentication</h2>
|
||||
<button data-cal-namespace="upcomingBookings" data-cal-config='{"theme":"dark"}' data-cal-link="bookings/upcoming">Show Upcoming Bookings</button>
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { CSSProperties } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import embedInit from "@calcom/embed-core/embed-iframe-init";
|
||||
import type { BookerStore } from "@calcom/features/bookings/Booker/store";
|
||||
import type { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { Message } from "./embed";
|
||||
import { sdkActionManager } from "./sdk-event";
|
||||
@@ -16,6 +18,7 @@ export type UiConfig = {
|
||||
styles?: EmbedStyles & EmbedNonStylesConfig;
|
||||
//TODO: Extract from tailwind the list of all custom variables and support them in auto-completion as well as runtime validation. Followup with listing all variables in Embed Snippet Generator UI.
|
||||
cssVarsPerTheme?: Record<Theme, Record<string, string>>;
|
||||
layout?: BookerLayouts;
|
||||
};
|
||||
|
||||
type SetStyles = React.Dispatch<React.SetStateAction<EmbedStyles>>;
|
||||
@@ -44,6 +47,7 @@ declare global {
|
||||
__logQueue?: unknown[];
|
||||
embedStore: typeof embedStore;
|
||||
applyCssVars: (cssVarsPerTheme: UiConfig["cssVarsPerTheme"]) => void;
|
||||
setLayout?: BookerStore["setLayout"];
|
||||
};
|
||||
CalComPageStatus: string;
|
||||
isEmbed?: () => boolean;
|
||||
@@ -315,6 +319,10 @@ const methods = {
|
||||
embedStore.setUiConfig(uiConfig);
|
||||
}
|
||||
|
||||
if (uiConfig.layout) {
|
||||
window.CalEmbed.setLayout?.(uiConfig.layout);
|
||||
}
|
||||
|
||||
setEmbedStyles(stylesConfig || {});
|
||||
setEmbedNonStyles(stylesConfig || {});
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LazyMotion, domAnimation, m, AnimatePresence } from "framer-motion";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useMemo } from "react";
|
||||
import StickyBox from "react-sticky-box";
|
||||
import { shallow } from "zustand/shallow";
|
||||
|
||||
@@ -20,6 +20,8 @@ import { extraDaysConfig, fadeInLeft, getBookerSizeClassNames, useBookerResizeAn
|
||||
import { useBookerStore, useInitializeBookerStore } from "./store";
|
||||
import type { BookerProps } from "./types";
|
||||
import { useEvent } from "./utils/event";
|
||||
import { validateLayout } from "./utils/layout";
|
||||
import { getQueryParam } from "./utils/query-param";
|
||||
import { useBrandColors } from "./utils/use-brand-colors";
|
||||
|
||||
const PoweredBy = dynamic(() => import("@calcom/ee/components/PoweredBy"));
|
||||
@@ -42,6 +44,9 @@ const BookerComponent = ({
|
||||
typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("rescheduleUid") : null;
|
||||
const event = useEvent();
|
||||
const [layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow);
|
||||
if (typeof window !== "undefined") {
|
||||
window.CalEmbed.setLayout = setLayout;
|
||||
}
|
||||
const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow);
|
||||
const selectedDate = useBookerStore((state) => state.selectedDate);
|
||||
const [selectedTimeslot, setSelectedTimeslot] = useBookerStore(
|
||||
@@ -52,6 +57,14 @@ const BookerComponent = ({
|
||||
const extraDays = isTablet ? extraDaysConfig[layout].tablet : extraDaysConfig[layout].desktop;
|
||||
const bookerLayouts = event.data?.profile?.bookerLayouts || defaultBookerLayoutSettings;
|
||||
const animationScope = useBookerResizeAnimation(layout, bookerState);
|
||||
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
|
||||
const isMonthView = layout === BookerLayouts.MONTH_VIEW;
|
||||
// We only want the initial url value, that's why we memo it. The embed seems to change the url, which sometimes drops
|
||||
// the layout query param.
|
||||
const layoutFromQueryParam = useMemo(() => validateLayout(getQueryParam("layout") as BookerLayouts), []);
|
||||
const defaultLayout = isEmbed
|
||||
? layoutFromQueryParam || BookerLayouts.MONTH_VIEW
|
||||
: bookerLayouts.defaultLayout;
|
||||
|
||||
useBrandColors({
|
||||
brandColor: event.data?.profile.brandColor,
|
||||
@@ -66,16 +79,16 @@ const BookerComponent = ({
|
||||
eventId: event?.data?.id,
|
||||
rescheduleUid,
|
||||
rescheduleBooking,
|
||||
layout: bookerLayouts.defaultLayout,
|
||||
layout: defaultLayout,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile && layout !== "mobile") {
|
||||
setLayout("mobile");
|
||||
} else if (!isMobile && layout === "mobile") {
|
||||
setLayout(BookerLayouts.MONTH_VIEW);
|
||||
setLayout(defaultLayout);
|
||||
}
|
||||
}, [isMobile, setLayout, layout]);
|
||||
}, [isMobile, setLayout, layout, defaultLayout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (event.isLoading) return setBookerState("loading");
|
||||
@@ -96,17 +109,21 @@ const BookerComponent = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-full w-full flex-col items-center">
|
||||
<div className="text-default flex h-full w-full flex-col items-center overflow-x-clip">
|
||||
<div
|
||||
ref={animationScope}
|
||||
className={classNames(
|
||||
// Sets booker size css variables for the size of all the columns.
|
||||
...getBookerSizeClassNames(layout, bookerState),
|
||||
"bg-default dark:bg-muted grid max-w-full items-start overflow-clip dark:[color-scheme:dark] sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row",
|
||||
layout === BookerLayouts.MONTH_VIEW && "border-subtle rounded-md border"
|
||||
"bg-default dark:bg-muted grid max-w-full auto-rows-fr items-start dark:[color-scheme:dark] sm:duration-300 sm:motion-reduce:transition-none md:flex-row",
|
||||
layout === BookerLayouts.MONTH_VIEW && "rounded-md border",
|
||||
!isEmbed && "sm:transition-[width] sm:duration-300",
|
||||
isEmbed && layout === BookerLayouts.MONTH_VIEW && "border-booker sm:border-booker-width",
|
||||
!isEmbed && layout === BookerLayouts.MONTH_VIEW && "border-subtle",
|
||||
layout === BookerLayouts.MONTH_VIEW && isEmbed && "mt-20"
|
||||
)}>
|
||||
<AnimatePresence>
|
||||
<BookerSection area="header">
|
||||
<BookerSection area="header" className={classNames(isMonthView && "fixed top-3 right-3 z-10")}>
|
||||
<Header
|
||||
enabledLayouts={bookerLayouts.enabledLayouts}
|
||||
extraDays={extraDays}
|
||||
|
||||
@@ -26,6 +26,7 @@ export function Header({
|
||||
const addToSelectedDate = useBookerStore((state) => state.addToSelectedDate);
|
||||
const isMonthView = layout === BookerLayouts.MONTH_VIEW;
|
||||
const selectedDate = dayjs(selectedDateString);
|
||||
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
|
||||
|
||||
const onLayoutToggle = useCallback(
|
||||
(newLayout: string) => {
|
||||
@@ -35,7 +36,7 @@ export function Header({
|
||||
[setLayout, layout]
|
||||
);
|
||||
|
||||
if (isMobile || !enabledLayouts) return null;
|
||||
if (isMobile || isEmbed || !enabledLayouts) return null;
|
||||
|
||||
// Only reason we create this component, is because it is used 3 times in this component,
|
||||
// and this way we can't forget to update one of the props in all places :)
|
||||
@@ -46,11 +47,7 @@ export function Header({
|
||||
// In month view we only show the layout toggle.
|
||||
if (isMonthView) {
|
||||
if (enabledLayouts.length <= 1) return null;
|
||||
return (
|
||||
<div className="fixed top-3 right-3 z-10">
|
||||
<LayoutToggleWithData />
|
||||
</div>
|
||||
);
|
||||
return <LayoutToggleWithData />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -142,7 +142,8 @@ export const getBookerSizeClassNames = (layout: BookerLayout, bookerState: Booke
|
||||
export const useBookerResizeAnimation = (layout: BookerLayout, state: BookerState) => {
|
||||
const prefersReducedMotion = useReducedMotion();
|
||||
const [animationScope, animate] = useAnimate();
|
||||
|
||||
const isEmbed = typeof window !== "undefined" && window?.isEmbed?.();
|
||||
``;
|
||||
useEffect(() => {
|
||||
const animationConfig = resizeAnimationConfig[layout][state] || resizeAnimationConfig[layout].default;
|
||||
|
||||
@@ -163,12 +164,18 @@ export const useBookerResizeAnimation = (layout: BookerLayout, state: BookerStat
|
||||
minHeight: animationConfig?.minHeight,
|
||||
};
|
||||
|
||||
// We don't animate if users has set prefers-reduced-motion,
|
||||
// or when the layout is mobile.
|
||||
if (prefersReducedMotion || layout === "mobile") {
|
||||
// In this cases we don't animate the booker at all.
|
||||
if (prefersReducedMotion || layout === "mobile" || isEmbed) {
|
||||
const styles = { ...nonAnimatedProperties, ...animatedProperties };
|
||||
Object.keys(styles).forEach((property) => {
|
||||
animationScope.current.style[property] = styles[property as keyof typeof styles];
|
||||
if (property === "height") {
|
||||
// Change 100vh to 100% in embed, since 100vh in iframe will behave weird, because
|
||||
// the iframe will constantly grow. 100% will simply make sure it grows with the iframe.
|
||||
animationScope.current.style.height =
|
||||
animatedProperties.height === "100vh" && isEmbed ? "100%" : animatedProperties.height;
|
||||
} else {
|
||||
animationScope.current.style[property] = styles[property as keyof typeof styles];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.keys(nonAnimatedProperties).forEach((property) => {
|
||||
@@ -180,7 +187,7 @@ export const useBookerResizeAnimation = (layout: BookerLayout, state: BookerStat
|
||||
ease: cubicBezier(0.4, 0, 0.2, 1),
|
||||
});
|
||||
}
|
||||
}, [animate, animationScope, layout, prefersReducedMotion, state]);
|
||||
}, [animate, isEmbed, animationScope, layout, prefersReducedMotion, state]);
|
||||
|
||||
return animationScope;
|
||||
};
|
||||
|
||||
@@ -2,10 +2,11 @@ import { useEffect } from "react";
|
||||
import { create } from "zustand";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { BookerLayouts, bookerLayoutOptions } from "@calcom/prisma/zod-utils";
|
||||
import { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import type { GetBookingType } from "../lib/get-booking";
|
||||
import type { BookerState, BookerLayout } from "./types";
|
||||
import { validateLayout } from "./utils/layout";
|
||||
import { updateQueryParam, getQueryParam, removeQueryParam } from "./utils/query-param";
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,7 @@ type StoreInitializeType = {
|
||||
layout: BookerLayout;
|
||||
};
|
||||
|
||||
type BookerStore = {
|
||||
export type BookerStore = {
|
||||
/**
|
||||
* Event details. These are stored in store for easier
|
||||
* access in child components.
|
||||
@@ -90,10 +91,6 @@ type BookerStore = {
|
||||
setFormValues: (values: Record<string, any>) => void;
|
||||
};
|
||||
|
||||
const checkLayout = (layout: BookerLayout) => {
|
||||
return bookerLayoutOptions.find((validLayout) => validLayout === layout);
|
||||
};
|
||||
|
||||
/**
|
||||
* The booker store contains the data of the component's
|
||||
* current state. This data can be reused within child components
|
||||
@@ -104,7 +101,7 @@ const checkLayout = (layout: BookerLayout) => {
|
||||
export const useBookerStore = create<BookerStore>((set, get) => ({
|
||||
state: "loading",
|
||||
setState: (state: BookerState) => set({ state }),
|
||||
layout: checkLayout(getQueryParam("layout") as BookerLayout) || BookerLayouts.MONTH_VIEW,
|
||||
layout: validateLayout(getQueryParam("layout") as BookerLayouts) || BookerLayouts.MONTH_VIEW,
|
||||
setLayout: (layout: BookerLayout) => {
|
||||
// If we switch to a large layout and don't have a date selected yet,
|
||||
// we selected it here, so week title is rendered properly.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { BookerLayouts } from "@calcom/prisma/zod-utils";
|
||||
import { bookerLayoutOptions } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export const validateLayout = (layout?: BookerLayouts | null) => {
|
||||
return bookerLayoutOptions.find((validLayout) => validLayout === layout);
|
||||
};
|
||||
@@ -40,7 +40,7 @@ export const AvailableTimes = ({
|
||||
const isToday = dayjs().isSame(date, "day");
|
||||
|
||||
return (
|
||||
<div className={classNames("text-default", className)}>
|
||||
<div className={classNames("text-default flex flex-col", className)}>
|
||||
<header className="bg-default before:bg-default dark:bg-muted dark:before:bg-muted mb-3 flex w-full flex-row items-center font-medium">
|
||||
<span
|
||||
className={classNames(
|
||||
|
||||
@@ -11,7 +11,13 @@ import { useFilterQuery } from "../lib/useFilterQuery";
|
||||
export const TeamsMemberFilter = () => {
|
||||
const { t } = useLocale();
|
||||
const session = useSession();
|
||||
const { data: query, pushItemToKey, removeItemByKeyAndValue, removeByKey, removeAllQueryParams } = useFilterQuery();
|
||||
const {
|
||||
data: query,
|
||||
pushItemToKey,
|
||||
removeItemByKeyAndValue,
|
||||
removeByKey,
|
||||
removeAllQueryParams,
|
||||
} = useFilterQuery();
|
||||
const { data } = trpc.viewer.teams.list.useQuery();
|
||||
const [dropdownTitle, setDropdownTitle] = useState<string>(t("all_bookings_filter_label"));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Fragment } from "react";
|
||||
import React from "react";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Clock, CheckSquare, RefreshCcw, CreditCard } from "@calcom/ui/components/icon";
|
||||
|
||||
@@ -145,7 +146,8 @@ export const EventDetails = ({ event, blocks = defaultEventDetailsBlocks }: Even
|
||||
);
|
||||
|
||||
case EventDetailBlocks.PRICE:
|
||||
if (event.price === 0) return null;
|
||||
const paymentAppData = getPaymentAppData(event);
|
||||
if (event.price <= 0 || paymentAppData.price <= 0) return null;
|
||||
|
||||
return (
|
||||
<EventMetaBlock key={block} icon={CreditCard}>
|
||||
|
||||
@@ -64,10 +64,10 @@ function MembersList(props: MembersListProps) {
|
||||
|
||||
const MembersView = () => {
|
||||
const { t, i18n } = useLocale();
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
const session = useSession();
|
||||
|
||||
|
||||
const utils = trpc.useContext();
|
||||
const teamId = Number(router.query.id);
|
||||
|
||||
|
||||
Reference in new issue
Block a user