Feature/Embed/Add Embed ui instruction config to hide event type description (#5951)
* Add embed ui instruction to hide event type description * Add EventTypeDetails option in Embed Snippet Generator
This commit is contained in:
@@ -44,6 +44,7 @@ type PreviewState = {
|
||||
palette: {
|
||||
brandColor: string;
|
||||
};
|
||||
hideEventTypeDetails: boolean;
|
||||
};
|
||||
const queryParamsForDialog = ["embedType", "embedTabName", "embedUrl"];
|
||||
|
||||
@@ -125,10 +126,12 @@ const getEmbedUIInstructionString = ({
|
||||
apiName,
|
||||
theme,
|
||||
brandColor,
|
||||
hideEventTypeDetails,
|
||||
}: {
|
||||
apiName: string;
|
||||
theme?: string;
|
||||
brandColor: string;
|
||||
hideEventTypeDetails: boolean;
|
||||
}) => {
|
||||
theme = theme !== "auto" ? theme : undefined;
|
||||
return getInstructionString({
|
||||
@@ -141,6 +144,7 @@ const getEmbedUIInstructionString = ({
|
||||
brandColor,
|
||||
},
|
||||
},
|
||||
hideEventTypeDetails: hideEventTypeDetails,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -253,18 +257,21 @@ const getEmbedTypeSpecificString = ({
|
||||
apiName: string;
|
||||
theme: PreviewState["theme"];
|
||||
brandColor: string;
|
||||
hideEventTypeDetails: boolean;
|
||||
};
|
||||
if (embedFramework === "react") {
|
||||
uiInstructionStringArg = {
|
||||
apiName: "cal",
|
||||
theme: previewState.theme,
|
||||
brandColor: previewState.palette.brandColor,
|
||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||
};
|
||||
} else {
|
||||
uiInstructionStringArg = {
|
||||
apiName: "Cal",
|
||||
theme: previewState.theme,
|
||||
brandColor: previewState.palette.brandColor,
|
||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||
};
|
||||
}
|
||||
if (!frameworkCodes[embedType]) {
|
||||
@@ -689,6 +696,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
theme: Theme.auto,
|
||||
floatingPopup: {},
|
||||
elementClick: {},
|
||||
hideEventTypeDetails: false,
|
||||
palette: {
|
||||
brandColor: "#000000",
|
||||
},
|
||||
@@ -753,6 +761,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
name: "ui",
|
||||
arg: {
|
||||
theme: previewState.theme,
|
||||
hideEventTypeDetails: previewState.hideEventTypeDetails,
|
||||
styles: {
|
||||
branding: {
|
||||
...previewState.palette,
|
||||
@@ -811,7 +820,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
{embed.title}
|
||||
</h3>
|
||||
<hr className={classNames("mt-4", embedType === "element-click" ? "hidden" : "")} />
|
||||
<div className="max-h-97 flex flex-col overflow-y-auto">
|
||||
<div className="flex flex-col overflow-y-auto">
|
||||
<div className={classNames("mt-4 font-medium", embedType === "element-click" ? "hidden" : "")}>
|
||||
<Collapsible
|
||||
open={isEmbedCustomizationOpen}
|
||||
@@ -838,7 +847,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
<div className="text-sm">Embed Window Sizing</div>
|
||||
<div className="justify-left flex items-center">
|
||||
<TextField
|
||||
name="width"
|
||||
labelProps={{ className: "hidden" }}
|
||||
required
|
||||
value={previewState.inline.width}
|
||||
@@ -860,7 +868,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
<span className="p-2">×</span>
|
||||
<TextField
|
||||
labelProps={{ className: "hidden" }}
|
||||
name="height"
|
||||
value={previewState.inline.height}
|
||||
required
|
||||
onChange={(e) => {
|
||||
@@ -888,7 +895,6 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
<div className="mb-2 text-sm">Button Text</div>
|
||||
{/* Default Values should come from preview iframe */}
|
||||
<TextField
|
||||
name="buttonText"
|
||||
labelProps={{ className: "hidden" }}
|
||||
onChange={(e) => {
|
||||
setPreviewState((previewState) => {
|
||||
@@ -1004,6 +1010,20 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="mt-6 text-sm">
|
||||
<div className="mb-4 flex items-center justify-start space-x-2">
|
||||
<Switch
|
||||
checked={previewState.hideEventTypeDetails}
|
||||
onCheckedChange={(checked) => {
|
||||
setPreviewState((previewState) => {
|
||||
return {
|
||||
...previewState,
|
||||
hideEventTypeDetails: checked,
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="text-sm">{t("hide_eventtype_details")}</div>
|
||||
</div>
|
||||
<Label className="">
|
||||
<div className="mb-2">Theme</div>
|
||||
<Select
|
||||
|
||||
@@ -13,6 +13,7 @@ import dayjs, { Dayjs } from "@calcom/dayjs";
|
||||
import {
|
||||
useEmbedNonStylesConfig,
|
||||
useEmbedStyles,
|
||||
useEmbedUiConfig,
|
||||
useIsBackgroundTransparent,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
@@ -253,6 +254,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
useTheme(profile.theme);
|
||||
const { t } = useLocale();
|
||||
const availabilityDatePickerEmbedStyles = useEmbedStyles("availabilityDatePicker");
|
||||
//TODO: Plan to remove shouldAlignCentrallyInEmbed config
|
||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||
const isBackgroundTransparent = useIsBackgroundTransparent();
|
||||
@@ -289,7 +291,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
);
|
||||
}
|
||||
}, [telemetry]);
|
||||
|
||||
const embedUiConfig = useEmbedUiConfig();
|
||||
// get dynamic user list here
|
||||
const userList = eventType.users ? eventType.users.map((user) => user.username).filter(notEmpty) : [];
|
||||
|
||||
@@ -302,6 +304,8 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
const rawSlug = profile.slug ? profile.slug.split("/") : [];
|
||||
if (rawSlug.length > 1) rawSlug.pop(); //team events have team name as slug, but user events have [user]/[type] as slug.
|
||||
|
||||
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
|
||||
|
||||
// Define conditional gates here
|
||||
const gates = [
|
||||
// Rainbow gate is only added if the event has both a `blockchainId` and a `smartContractAddress`
|
||||
@@ -335,7 +339,7 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
<div>
|
||||
<main
|
||||
className={classNames(
|
||||
"flex-col md:mx-4 lg:flex",
|
||||
"flex flex-col md:mx-4",
|
||||
shouldAlignCentrally ? "items-center" : "items-start",
|
||||
!isEmbed && classNames("mx-auto my-0 ease-in-out md:my-24")
|
||||
)}>
|
||||
@@ -350,54 +354,55 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
isEmbed && "mx-auto"
|
||||
)}>
|
||||
<div className="overflow-hidden md:flex">
|
||||
<div
|
||||
className={classNames(
|
||||
"sm:dark:border-darkgray-200 flex flex-col border-gray-200 p-5 sm:border-r",
|
||||
"min-w-full md:w-[230px] md:min-w-[230px]",
|
||||
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
|
||||
)}>
|
||||
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
|
||||
{!rescheduleUid && eventType.recurringEvent && (
|
||||
<div className="flex items-start text-sm font-medium">
|
||||
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />
|
||||
<div>
|
||||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
||||
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
|
||||
</p>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max={eventType.recurringEvent.count}
|
||||
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
|
||||
defaultValue={eventType.recurringEvent.count}
|
||||
onChange={(event) => {
|
||||
setRecurringEventCount(parseInt(event?.target.value));
|
||||
}}
|
||||
/>
|
||||
<p className="inline">
|
||||
{t("occurrence", {
|
||||
count: recurringEventCount,
|
||||
})}
|
||||
</p>
|
||||
{showEventTypeDetails && (
|
||||
<div
|
||||
className={classNames(
|
||||
"sm:dark:border-darkgray-200 flex flex-col border-gray-200 p-5 sm:border-r",
|
||||
"min-w-full md:w-[230px] md:min-w-[230px]",
|
||||
recurringEventCount && "xl:w-[380px] xl:min-w-[380px]"
|
||||
)}>
|
||||
<BookingDescription profile={profile} eventType={eventType} rescheduleUid={rescheduleUid}>
|
||||
{!rescheduleUid && eventType.recurringEvent && (
|
||||
<div className="flex items-start text-sm font-medium">
|
||||
<Icon.FiRefreshCcw className="float-left mr-[10px] mt-[7px] ml-[2px] inline-block h-4 w-4 " />
|
||||
<div>
|
||||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
||||
{getRecurringFreq({ t, recurringEvent: eventType.recurringEvent })}
|
||||
</p>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max={eventType.recurringEvent.count}
|
||||
className="w-15 dark:bg-darkgray-200 h-7 rounded-sm border-gray-300 bg-white text-sm font-medium [appearance:textfield] ltr:mr-2 rtl:ml-2 dark:border-gray-500"
|
||||
defaultValue={eventType.recurringEvent.count}
|
||||
onChange={(event) => {
|
||||
setRecurringEventCount(parseInt(event?.target.value));
|
||||
}}
|
||||
/>
|
||||
<p className="inline">
|
||||
{t("occurrence", {
|
||||
count: recurringEventCount,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{stripeAppData.price > 0 && (
|
||||
<p className="-ml-2 px-2 text-sm font-medium">
|
||||
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={stripeAppData.price / 100.0}
|
||||
style="currency"
|
||||
currency={stripeAppData.currency.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
</p>
|
||||
)}
|
||||
{timezoneDropdown}
|
||||
</BookingDescription>
|
||||
)}
|
||||
{stripeAppData.price > 0 && (
|
||||
<p className="-ml-2 px-2 text-sm font-medium">
|
||||
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={stripeAppData.price / 100.0}
|
||||
style="currency"
|
||||
currency={stripeAppData.currency.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
</p>
|
||||
)}
|
||||
{timezoneDropdown}
|
||||
</BookingDescription>
|
||||
|
||||
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
|
||||
{/* Temporarily disabled - booking?.startTime && rescheduleUid && (
|
||||
<div>
|
||||
<p
|
||||
className="mt-4 mb-3 text-gray-600 dark:text-darkgray-600"
|
||||
@@ -410,7 +415,8 @@ const AvailabilityPage = ({ profile, eventType, ...restProps }: Props) => {
|
||||
</p>
|
||||
</div>
|
||||
)*/}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<SlotPicker
|
||||
weekStart={
|
||||
typeof profile.weekStart === "string"
|
||||
|
||||
@@ -26,6 +26,7 @@ import { LocationObject, LocationType } from "@calcom/core/location";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import {
|
||||
useEmbedNonStylesConfig,
|
||||
useEmbedUiConfig,
|
||||
useIsBackgroundTransparent,
|
||||
useIsEmbed,
|
||||
} from "@calcom/embed-core/embed-iframe";
|
||||
@@ -89,6 +90,7 @@ const BookingPage = ({
|
||||
const { t, i18n } = useLocale();
|
||||
const { duration: queryDuration } = useRouterQuery("duration");
|
||||
const isEmbed = useIsEmbed(restProps.isEmbed);
|
||||
const embedUiConfig = useEmbedUiConfig();
|
||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||
const router = useRouter();
|
||||
@@ -422,6 +424,7 @@ const BookingPage = ({
|
||||
}
|
||||
});
|
||||
}
|
||||
const showEventTypeDetails = (isEmbed && !embedUiConfig.hideEventTypeDetails) || !isEmbed;
|
||||
const rainbowAppData = getEventTypeAppData(eventType, "rainbow") || {};
|
||||
|
||||
// Define conditional gates here
|
||||
@@ -455,7 +458,7 @@ const BookingPage = ({
|
||||
className={classNames(
|
||||
shouldAlignCentrally ? "mx-auto" : "",
|
||||
isEmbed ? "" : "sm:my-24",
|
||||
"my-0 max-w-3xl "
|
||||
"my-0 max-w-3xl"
|
||||
)}>
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -464,95 +467,97 @@ const BookingPage = ({
|
||||
"dark:border-darkgray-300 rounded-md sm:border"
|
||||
)}>
|
||||
<div className="sm:flex">
|
||||
<div className="sm:dark:border-darkgray-300 dark:text-darkgray-600 flex flex-col px-6 pt-6 pb-0 text-gray-600 sm:w-1/2 sm:border-r sm:pb-6">
|
||||
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
||||
{stripeAppData.price > 0 && (
|
||||
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
||||
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={stripeAppData.price / 100.0}
|
||||
style="currency"
|
||||
currency={stripeAppData.currency.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
</p>
|
||||
)}
|
||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
||||
<div className="items-start text-sm font-medium text-gray-600 dark:text-white">
|
||||
<Icon.FiRefreshCw className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
|
||||
<p className="-ml-2 inline-block items-center px-2">
|
||||
{getEveryFreqFor({
|
||||
t,
|
||||
recurringEvent: eventType.recurringEvent,
|
||||
recurringCount: recurringEventCount,
|
||||
})}
|
||||
{showEventTypeDetails && (
|
||||
<div className="sm:dark:border-darkgray-300 dark:text-darkgray-600 flex flex-col px-6 pt-6 pb-0 text-gray-600 sm:w-1/2 sm:border-r sm:pb-6">
|
||||
<BookingDescription isBookingPage profile={profile} eventType={eventType}>
|
||||
{stripeAppData.price > 0 && (
|
||||
<p className="text-bookinglight -ml-2 px-2 text-sm ">
|
||||
<Icon.FiCreditCard className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
||||
<IntlProvider locale="en">
|
||||
<FormattedNumber
|
||||
value={stripeAppData.price / 100.0}
|
||||
style="currency"
|
||||
currency={stripeAppData.currency.toUpperCase()}
|
||||
/>
|
||||
</IntlProvider>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-bookinghighlight flex items-start text-sm">
|
||||
<Icon.FiCalendar className="mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4" />
|
||||
<div className="text-sm font-medium">
|
||||
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
|
||||
{!rescheduleUid &&
|
||||
eventType.recurringEvent?.freq &&
|
||||
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
|
||||
return <p key={key}>{timeFormatted}</p>;
|
||||
})}
|
||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
|
||||
<div className="flex">
|
||||
<Tooltip
|
||||
content={recurringStrings.slice(5).map((timeFormatted, key) => (
|
||||
<p key={key}>{timeFormatted}</p>
|
||||
))}>
|
||||
<p className="dark:text-darkgray-600 text-sm">
|
||||
+ {t("plus_more", { count: recurringStrings.length - 5 })}
|
||||
</p>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{booking?.startTime && rescheduleUid && (
|
||||
<div>
|
||||
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
|
||||
{t("former_time")}
|
||||
</p>
|
||||
<p className="line-through ">
|
||||
<Icon.FiCalendar className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
||||
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!!eventType.seatsPerTimeSlot && (
|
||||
)}
|
||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringEventCount && (
|
||||
<div className="items-start text-sm font-medium text-gray-600 dark:text-white">
|
||||
<Icon.FiRefreshCw className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
|
||||
<p className="-ml-2 inline-block items-center px-2">
|
||||
{getEveryFreqFor({
|
||||
t,
|
||||
recurringEvent: eventType.recurringEvent,
|
||||
recurringCount: recurringEventCount,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-bookinghighlight flex items-start text-sm">
|
||||
<Icon.FiUser
|
||||
className={`mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4 ${
|
||||
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
|
||||
? "text-rose-600"
|
||||
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
|
||||
? "text-yellow-500"
|
||||
: "text-bookinghighlight"
|
||||
}`}
|
||||
/>
|
||||
<p
|
||||
className={`${
|
||||
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
|
||||
? "text-rose-600"
|
||||
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
|
||||
? "text-yellow-500"
|
||||
: "text-bookinghighlight"
|
||||
} mb-2 font-medium`}>
|
||||
{booking
|
||||
? eventType.seatsPerTimeSlot - booking.attendees.length
|
||||
: eventType.seatsPerTimeSlot}{" "}
|
||||
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
|
||||
</p>
|
||||
<Icon.FiCalendar className="mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4" />
|
||||
<div className="text-sm font-medium">
|
||||
{(rescheduleUid || !eventType.recurringEvent?.freq) && `${parseDate(date, i18n)}`}
|
||||
{!rescheduleUid &&
|
||||
eventType.recurringEvent?.freq &&
|
||||
recurringStrings.slice(0, 5).map((timeFormatted, key) => {
|
||||
return <p key={key}>{timeFormatted}</p>;
|
||||
})}
|
||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
|
||||
<div className="flex">
|
||||
<Tooltip
|
||||
content={recurringStrings.slice(5).map((timeFormatted, key) => (
|
||||
<p key={key}>{timeFormatted}</p>
|
||||
))}>
|
||||
<p className="dark:text-darkgray-600 text-sm">
|
||||
+ {t("plus_more", { count: recurringStrings.length - 5 })}
|
||||
</p>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</BookingDescription>
|
||||
</div>
|
||||
<div className="p-6 sm:w-1/2">
|
||||
{booking?.startTime && rescheduleUid && (
|
||||
<div>
|
||||
<p className="mt-8 mb-2 text-sm " data-testid="former_time_p">
|
||||
{t("former_time")}
|
||||
</p>
|
||||
<p className="line-through ">
|
||||
<Icon.FiCalendar className="mr-[10px] ml-[2px] -mt-1 inline-block h-4 w-4" />
|
||||
{typeof booking.startTime === "string" && parseDate(dayjs(booking.startTime), i18n)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{!!eventType.seatsPerTimeSlot && (
|
||||
<div className="text-bookinghighlight flex items-start text-sm">
|
||||
<Icon.FiUser
|
||||
className={`mr-[10px] ml-[2px] mt-[2px] inline-block h-4 w-4 ${
|
||||
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
|
||||
? "text-rose-600"
|
||||
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
|
||||
? "text-yellow-500"
|
||||
: "text-bookinghighlight"
|
||||
}`}
|
||||
/>
|
||||
<p
|
||||
className={`${
|
||||
booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.5
|
||||
? "text-rose-600"
|
||||
: booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
|
||||
? "text-yellow-500"
|
||||
: "text-bookinghighlight"
|
||||
} mb-2 font-medium`}>
|
||||
{booking
|
||||
? eventType.seatsPerTimeSlot - booking.attendees.length
|
||||
: eventType.seatsPerTimeSlot}{" "}
|
||||
/ {eventType.seatsPerTimeSlot} {t("seats_available")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</BookingDescription>
|
||||
</div>
|
||||
)}
|
||||
<div className={classNames("p-6", showEventTypeDetails ? "sm:w-1/2" : "w-full")}>
|
||||
<Form form={bookingForm} handleSubmit={bookEvent}>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||
|
||||
@@ -1438,5 +1438,6 @@
|
||||
"add_an_option": "Add an option",
|
||||
"radio": "Radio",
|
||||
"event_type_duplicate_copy_text": "{{slug}}-copy",
|
||||
"set_as_default": "Set as default"
|
||||
"set_as_default": "Set as default",
|
||||
"hide_eventtype_details": "Hide EventType Details"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Embed Playground</title>
|
||||
<!-- <link rel="prerender" href="http://localhost:3000/free"> -->
|
||||
<!-- <script src="./src/embed.ts" type="module"></script> -->
|
||||
<script>
|
||||
@@ -9,8 +10,8 @@
|
||||
(()=> {
|
||||
const url = new URL(document.URL);
|
||||
// Only run the example specified by only=, avoids distraction and faster to test.
|
||||
const only = url.searchParams.get("only");
|
||||
const elementIdentifier = only ? only.replace("ns:",""): null
|
||||
const only = window.only = url.searchParams.get("only");
|
||||
const elementIdentifier = only !== "all" ? only.replace("ns:",""): null
|
||||
if (elementIdentifier) {
|
||||
location.hash="#cal-booking-place-" + elementIdentifier + "-iframe"
|
||||
}
|
||||
@@ -56,9 +57,10 @@
|
||||
rgba(148, 232, 249, 1) 100%
|
||||
);
|
||||
}
|
||||
.debug {
|
||||
.inline-embed-container {
|
||||
/* border: 1px solid black; */
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid
|
||||
}
|
||||
|
||||
.loader {
|
||||
@@ -70,19 +72,26 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h3>This page has a non responsive version accessible <a href="?nonResponsive">here</a></h3>
|
||||
<h3>Pre-render test page available at <a href="?only=prerender-test">here</a></h3>
|
||||
<span style="display: block;" ><a href="?nonResponsive">Non responsive version of this page here</a></span>
|
||||
<span style="display: block;" ><a href="?only=prerender-test">Go to Pre-render test page only</a><small></small></span>
|
||||
<div>
|
||||
<button data-cal-namespace="prerendertestLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="free?light&popup">Book with Free User[Light Theme]</button>
|
||||
<div>
|
||||
<i
|
||||
>Corresponding Cal Link is being preloaded. Assuming that it would take you some time to click this
|
||||
as you are reading this text, it would open up super fast[If you are running a production build on
|
||||
local]. Try switching to slow 3G or create a custom Network configuration which is impossibly
|
||||
slow</i
|
||||
>
|
||||
</div>
|
||||
<h2>Other Popup Examples</h2>
|
||||
<script>
|
||||
if (only === "all" || only === "prerender-test") {
|
||||
document.write(`
|
||||
<button data-cal-namespace="prerendertestLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="free?light&popup">Book with Free User[Light Theme]</button>
|
||||
<i
|
||||
>Corresponding Cal Link is being preloaded. Assuming that it would take you some time to click this
|
||||
as you are reading this text, it would open up super fast[If you are running a production build on
|
||||
local]. Try switching to slow 3G or create a custom Network configuration which is impossibly
|
||||
slow</i
|
||||
>`)
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<span style="display: block;" ><a href="?only=all">Render All embeds together</a><small> - It would be slow to load</small></span>
|
||||
<div>
|
||||
|
||||
<h2>Popup Examples</h2>
|
||||
<button data-cal-namespace="popupAutoTheme" data-cal-link="free">Book with Free User[Auto Theme]</button>
|
||||
<button data-cal-namespace="popupDarkTheme" data-cal-config='{"theme":"dark"}' data-cal-link="free">Book with Free User[Dark Theme]</button>
|
||||
<button data-cal-namespace="popupTeamLinkLightTheme" data-cal-config='{"theme":"light"}' data-cal-link="team/seeded-team/collective-seeded-team-event">Book with Test Team[Light Theme]</button>
|
||||
@@ -90,35 +99,28 @@
|
||||
<button data-cal-namespace="popupTeamLinksList" data-cal-link="team/seeded-team/">See Team Links [Auto Theme]</button>
|
||||
<button data-cal-namespace="popupReschedule" data-cal-link="reschedule/qm3kwt3aTnVD7vmP9tiT2f">Reschedule Event[Auto Theme]</button>
|
||||
<button data-cal-namespace="popupPaidEvent" data-cal-link="pro/paid">Book Paid Event [Auto Theme]</button>
|
||||
<button data-cal-namespace="popupHideEventTypeDetails" data-cal-link="free/30min">Book Free Event [Auto Theme][uiConfig.hideEventTypeDetails=true]</button>
|
||||
<button data-cal-namespace="routingFormAuto" data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">Book through Routing Form [Auto Theme]</button>
|
||||
<button data-cal-namespace="routingFormDark" data-cal-config='{"theme":"dark"}' data-cal-link="forms/948ae412-d995-4865-875a-48302588de03">Book through Routing Form [Dark Theme]</button>
|
||||
<div>
|
||||
<!-- <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>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<h2>Inline Embed Examples</h2>
|
||||
<div id="namespaces-test">
|
||||
<div class="debug" id="cal-booking-place-default">
|
||||
<h2>
|
||||
Default Namespace(Cal)<i>[Dark Theme][inline][Guests(janedoe@example.com and test@example.com)]</i>
|
||||
</h2>
|
||||
<div>
|
||||
<i><a href="?only=ns:default">Test in Zen Mode</a></i>
|
||||
</div>
|
||||
<div class="inline-embed-container" id="cal-booking-place-default">
|
||||
<h3>
|
||||
<a href="?only=ns:default">[Dark Theme][Guests(janedoe@example.com and test@example.com)]</a>
|
||||
</h3>
|
||||
<i class="last-action"> You would see last Booking page action in my place </i>
|
||||
<div >
|
||||
<div class="place" style="width:100%;"></div>
|
||||
<div class="loader" id="cal-booking-loader-">Loading .....</div>
|
||||
<div class="loader" id="cal-booking-loader-"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="debug" id="cal-booking-place-second">
|
||||
<h2>Namespace "second"(Cal.ns.second)[Custom Styling][inline]</h2>
|
||||
<div>
|
||||
<i><a href="?only=ns:second">Test in Zen Mode</a></i>
|
||||
</div>
|
||||
<i class="last-action">
|
||||
<i>You would see last Booking page action in my place</i>
|
||||
</i>
|
||||
<div class="inline-embed-container" id="cal-booking-place-second">
|
||||
<h3><a href="?only=ns:second">[Custom Styling]</a></h3>
|
||||
<div class="place">
|
||||
<div>If you render booking embed in me, I won't restrict you. The entire page is yours. Content is by default aligned center</div>
|
||||
<button
|
||||
@@ -143,71 +145,48 @@
|
||||
Change Date Button Color
|
||||
</button>
|
||||
|
||||
<div class="loader" id="cal-booking-loader-second">Loading .....</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="debug" id="cal-booking-place-third">
|
||||
<h2>Namespace "third"(Cal.ns.third)[inline][Custom Styling - Transparent Background]</h2>
|
||||
<div>
|
||||
<i><a href="?only=ns:third">Test in Zen Mode</a></i>
|
||||
</div>
|
||||
<i class="last-action">
|
||||
<i>You would see last Booking page action in my place</i>
|
||||
</i>
|
||||
<div class="inline-embed-container" id="cal-booking-place-third">
|
||||
<h3><a href="?only=ns:third">[Custom Styling - Transparent Background]</a></h3>
|
||||
<div style="width: 30%" class="place">
|
||||
<div>If you render booking embed in me, I would not let you be more than 30% wide</div>
|
||||
<div class="loader" id="cal-booking-loader-third">Loading .....</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="debug" id="cal-booking-place-fourth">
|
||||
<h2>Namespace "fourth"(Cal.ns.fourth)[Team Event Test][inline taking entire width]</h2>
|
||||
<div>
|
||||
<i><a href="?only=ns:fourth">Test in Zen Mode</a></i>
|
||||
</div>
|
||||
<i class="last-action">
|
||||
<i>You would see last Booking page action in my place</i>
|
||||
</i>
|
||||
<div class="inline-embed-container" id="cal-booking-place-fourth">
|
||||
<h3><a href="?only=ns:fourth">[Team Event Test][inline taking entire width]</a></h3>
|
||||
<div style="width: 30%" class="place">
|
||||
<div>If you render booking embed in me, I would not let you be more than 30% wide</div>
|
||||
<div class="loader" id="cal-booking-loader-third">Loading .....</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="debug" id="cal-booking-place-fifth">
|
||||
<h2>Namespace "fifth"(Cal.ns.fifth)[Team Event Test][inline along with some content]</h2>
|
||||
<div>
|
||||
<i><a href="?only=ns:fifth">Test in Zen Mode</a></i>
|
||||
</div>
|
||||
<i class="last-action">
|
||||
<i>You would see last Booking page action in my place</i>
|
||||
</i>
|
||||
<div class="inline-embed-container" id="cal-booking-place-fifth">
|
||||
<h3><a href="?only=ns:fifth">[Team Event Test][inline along with some content]</a></h3>
|
||||
<div style="display:flex;align-items: center;">
|
||||
<h2 style="width: 30%">
|
||||
<h4 style="width: 30%">
|
||||
On the right side you can book a team meeting =>
|
||||
</h2>
|
||||
</h4>
|
||||
<div style="width: 70%" class="place">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="debug" id="cal-booking-place-inline-routing-form">
|
||||
<h2>Inline Routing Form</h2>
|
||||
<div>
|
||||
<i><a href="?only=inline-routing-form">Test in Zen Mode</a></i>
|
||||
</div>
|
||||
<i class="last-action">
|
||||
<i>You would see last Booking page action in my place</i>
|
||||
</i>
|
||||
<div class="inline-embed-container" id="cal-booking-place-inline-routing-form">
|
||||
<h3><a href="?only=inline-routing-form">Inline Routing Form</a></h3>
|
||||
<div style="display:flex;align-items: center;">
|
||||
<h2 style="width: 30%">
|
||||
<h4 style="width: 30%">
|
||||
On the right side you can book a team meeting =>
|
||||
</h2>
|
||||
</h4>
|
||||
<div style="width: 70%" class="place">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-embed-container" id="cal-booking-place-hideEventTypeDetails">
|
||||
<h3><a href="?only=hideEventTypeDetails">Hide EventType Details Test</a></h3>
|
||||
<div class="place">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
const callback = function (e) {
|
||||
@@ -224,8 +203,8 @@
|
||||
);
|
||||
};
|
||||
const searchParams = new URL(document.URL).searchParams;
|
||||
const only = searchParams.get("only");
|
||||
if (!only || only === "ns:default") {
|
||||
let only = window.only
|
||||
if (only === "all" || only === "ns:default") {
|
||||
Cal("init", {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
@@ -251,7 +230,7 @@
|
||||
callback,
|
||||
});
|
||||
}
|
||||
if (!only || only === "ns:second") {
|
||||
if (only === "all" || only === "ns:second") {
|
||||
// Create a namespace "second". It can be accessed as Cal.ns.second with the exact same API as Cal
|
||||
Cal("init", "second", {
|
||||
debug: 1,
|
||||
@@ -278,8 +257,7 @@
|
||||
callback,
|
||||
});
|
||||
}
|
||||
|
||||
if (!only || only === "ns:third") {
|
||||
if (only === "all" || only === "ns:third") {
|
||||
// Create a namespace "third". It can be accessed as Cal.ns.second with the exact same API as Cal
|
||||
Cal("init", "third", {
|
||||
debug: 1,
|
||||
@@ -329,8 +307,7 @@
|
||||
callback,
|
||||
});
|
||||
}
|
||||
|
||||
if (!only || only === "ns:fourth") {
|
||||
if (only === "all" || only === "ns:fourth") {
|
||||
Cal("init", "fourth", {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
@@ -372,7 +349,7 @@
|
||||
callback,
|
||||
});
|
||||
}
|
||||
if (!only || only === "ns:fifth") {
|
||||
if (only === "all" || only === "ns:fifth") {
|
||||
Cal("init", "fifth", {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
@@ -396,7 +373,7 @@
|
||||
callback,
|
||||
});
|
||||
}
|
||||
if (!only || only === "prerender-test") {
|
||||
if (only === "all" || only === "prerender-test") {
|
||||
Cal('init', 'prerendertestLightTheme', {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
@@ -406,7 +383,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (!only || only === "inline-routing-form") {
|
||||
if (only === "all" || only === "inline-routing-form") {
|
||||
Cal('init', 'inline-routing-form', {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
@@ -427,11 +404,45 @@
|
||||
);
|
||||
}
|
||||
|
||||
if (only === "all" || only === "hideEventTypeDetails") {
|
||||
let identifier = "hideEventTypeDetails"
|
||||
Cal('init', identifier, {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
})
|
||||
|
||||
Cal.ns.hideEventTypeDetails([
|
||||
['inline', {
|
||||
elementOrSelector: `#cal-booking-place-${identifier} .place`,
|
||||
calLink: "free/30min",
|
||||
config: {
|
||||
iframeAttrs: {
|
||||
id: `cal-booking-place-${identifier}-iframe`
|
||||
},
|
||||
}
|
||||
}],
|
||||
[
|
||||
'ui', {
|
||||
hideEventTypeDetails:true
|
||||
}
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
Cal('init', 'popupDarkTheme', {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
})
|
||||
|
||||
Cal('init', 'popupHideEventTypeDetails', {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
});
|
||||
|
||||
Cal.ns.popupHideEventTypeDetails('ui', {
|
||||
hideEventTypeDetails:true
|
||||
});
|
||||
|
||||
Cal('init', 'popupReschedule', {
|
||||
debug: 1,
|
||||
origin: "http://localhost:3000",
|
||||
@@ -481,7 +492,7 @@
|
||||
})
|
||||
|
||||
|
||||
if (!only || only == "ns:floatingButton") {
|
||||
if (only === "all" || only == "ns:floatingButton") {
|
||||
Cal.ns.floatingButton("floatingButton", {
|
||||
calLink: "pro"
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useState, useEffect, CSSProperties } from "react";
|
||||
import { sdkActionManager } from "./sdk-event";
|
||||
|
||||
export interface UiConfig {
|
||||
hideEventTypeDetails?: boolean;
|
||||
theme?: "dark" | "light" | "auto";
|
||||
styles?: EmbedStyles;
|
||||
}
|
||||
@@ -39,7 +40,9 @@ const embedStore = {
|
||||
parentInformedAboutContentHeight: boolean;
|
||||
windowLoadEventFired: boolean;
|
||||
theme?: UiConfig["theme"];
|
||||
setTheme: (arg0: string) => void;
|
||||
uiConfig?: Omit<UiConfig, "styles" | "theme">;
|
||||
setTheme?: (arg0: string) => void;
|
||||
setUiConfig?: (arg0: UiConfig) => void;
|
||||
};
|
||||
|
||||
let isSafariBrowser = false;
|
||||
@@ -146,6 +149,12 @@ export const useEmbedTheme = () => {
|
||||
return theme === "auto" ? null : theme;
|
||||
};
|
||||
|
||||
export const useEmbedUiConfig = () => {
|
||||
const [uiConfig, setUiConfig] = useState(embedStore.uiConfig || {});
|
||||
embedStore.setUiConfig = setUiConfig;
|
||||
return uiConfig;
|
||||
};
|
||||
|
||||
// TODO: Make it usable as an attribute directly instead of styles value. It would allow us to go beyond styles e.g. for debugging we can add a special attribute indentifying the element on which UI config has been applied
|
||||
export const useEmbedStyles = (elementName: keyof EmbedStyles) => {
|
||||
const [styles, setStyles] = useState({} as EmbedStyles);
|
||||
@@ -258,7 +267,15 @@ export const methods = {
|
||||
|
||||
if (uiConfig.theme) {
|
||||
embedStore.theme = uiConfig.theme as UiConfig["theme"];
|
||||
embedStore.setTheme(uiConfig.theme);
|
||||
if (embedStore.setTheme) {
|
||||
embedStore.setTheme(uiConfig.theme);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the value here so that if setUiConfig state isn't available and later it's defined,it uses this value
|
||||
embedStore.uiConfig = uiConfig;
|
||||
if (embedStore.setUiConfig) {
|
||||
embedStore.setUiConfig(uiConfig);
|
||||
}
|
||||
|
||||
setEmbedStyles(stylesConfig || {});
|
||||
|
||||
Reference in New Issue
Block a user