Skip to content

Commit a11ea3d

Browse files
authored
feat: Ability to set a locked timezone for event type (calcom#22531)
* feat: ability to set locked timezone * update * Update update-event-type.input.ts * Update get-event-type-public.output.ts * Update documentation.json * revert * nit * feat: locked timezone * update * fix * Update handleChildrenEventTypes.test.ts * update * fix test
1 parent b21400c commit a11ea3d

20 files changed

Lines changed: 119 additions & 26 deletions

File tree

apps/web/playwright/event-types.e2e.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,33 @@ test.describe("Event Types tests", () => {
420420
await page.click("[data-testid=vertical-tab-event_advanced_tab_title]");
421421
await expect(offerSeatsToggle).toBeDisabled();
422422
});
423+
test("should enable timezone lock in event advanced settings and verify disabled timezone selector on booking page", async ({
424+
page,
425+
users,
426+
}) => {
427+
await gotoFirstEventType(page);
428+
await expect(page.locator("[data-testid=event-title]")).toBeVisible();
429+
await page.click("[data-testid=vertical-tab-event_advanced_tab_title]");
430+
await page.click("[data-testid=lock-timezone-toggle]");
431+
await page.click("[data-testid=timezone-select]");
432+
await page.locator('[aria-label="Timezone Select"]').fill("New York");
433+
await page.keyboard.press("Enter");
434+
435+
await submitAndWaitForResponse(page, "/api/trpc/eventTypes/update?batch=1", {
436+
action: () => page.locator("[data-testid=update-eventtype]").click(),
437+
});
438+
await page.goto("/event-types");
439+
const previewLink = await page
440+
.locator("[data-testid=preview-link-button]")
441+
.first()
442+
.getAttribute("href");
443+
444+
await page.goto(previewLink ?? "");
445+
const currentTimezone = page.locator('[data-testid="event-meta-current-timezone"]');
446+
await expect(currentTimezone).toBeVisible();
447+
await expect(currentTimezone).toHaveClass(/cursor-not-allowed/);
448+
await expect(page.getByText("New York")).toBeVisible();
449+
});
423450
});
424451

425452
test.describe("Interface Language Tests", () => {

apps/web/server/lib/[user]/getServerSideProps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ type UserPageProps = {
6060
| "length"
6161
| "hidden"
6262
| "lockTimeZoneToggleOnBookingPage"
63+
| "lockedTimeZone"
6364
| "requiresConfirmation"
6465
| "canSendCalVideoTranscriptionEmails"
6566
| "requiresBookerEmailVerification"

apps/web/test/lib/handleChildrenEventTypes.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,6 @@ describe("handleChildrenEventTypes", () => {
438438
schedulingType: SchedulingType.MANAGED,
439439
requiresBookerEmailVerification: false,
440440
lockTimeZoneToggleOnBookingPage: false,
441-
lockedTimeZone: "Europe/London",
442441
useEventTypeDestinationCalendarEmail: false,
443442
workflows: [],
444443
parentId: 1,

packages/features/bookings/Booker/components/EventMeta.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export const EventMeta = ({
5858
event?: Pick<
5959
BookerEvent,
6060
| "lockTimeZoneToggleOnBookingPage"
61+
| "lockedTimeZone"
6162
| "schedule"
6263
| "seatsPerTimeSlot"
6364
| "subsetOfUsers"
@@ -112,9 +113,12 @@ export const EventMeta = ({
112113
);
113114

114115
useEffect(() => {
115-
//In case the event has lockTimeZone enabled ,set the timezone to event's attached availability timezone
116-
if (event && event?.lockTimeZoneToggleOnBookingPage && event?.schedule?.timeZone) {
117-
setTimezone(event.schedule?.timeZone);
116+
//In case the event has lockTimeZone enabled ,set the timezone to event's locked timezone
117+
if (event?.lockTimeZoneToggleOnBookingPage) {
118+
const timezone = event.lockedTimeZone || event.schedule?.timeZone;
119+
if (timezone) {
120+
setTimezone(timezone);
121+
}
118122
}
119123
}, [event, setTimezone]);
120124

@@ -217,7 +221,8 @@ export const EventMeta = ({
217221
<span
218222
className={`current-timezone before:bg-subtle min-w-32 -mt-[2px] flex h-6 max-w-full items-center justify-start before:absolute before:inset-0 before:bottom-[-3px] before:left-[-30px] before:top-[-3px] before:w-[calc(100%_+_35px)] before:rounded-md before:py-3 before:opacity-0 before:transition-opacity ${
219223
event.lockTimeZoneToggleOnBookingPage ? "cursor-not-allowed" : ""
220-
}`}>
224+
}`}
225+
data-testid="event-meta-current-timezone">
221226
<TimezoneSelect
222227
timeZones={timeZones}
223228
menuPosition="absolute"
@@ -230,7 +235,11 @@ export const EventMeta = ({
230235
indicatorsContainer: () => "ml-auto",
231236
container: () => "max-w-full",
232237
}}
233-
value={event.lockTimeZoneToggleOnBookingPage ? CURRENT_TIMEZONE : timezone}
238+
value={
239+
event.lockTimeZoneToggleOnBookingPage
240+
? event.lockedTimeZone || CURRENT_TIMEZONE
241+
: timezone
242+
}
234243
onChange={({ value }) => {
235244
setTimezone(value);
236245
setBookerStoreTimezone(value);

packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => {
6565
periodDays: true,
6666
periodCountCalendarDays: true,
6767
lockTimeZoneToggleOnBookingPage: true,
68+
lockedTimeZone: true,
6869
requiresConfirmation: true,
6970
requiresConfirmationForFreeEmail: true,
7071
requiresBookerEmailVerification: true,

packages/features/bookings/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export type BookerEvent = Pick<
4646
| "price"
4747
| "currency"
4848
| "lockTimeZoneToggleOnBookingPage"
49+
| "lockedTimeZone"
4950
| "schedule"
5051
| "seatsPerTimeSlot"
5152
| "title"

packages/features/components/timezone-select/TimezoneSelect.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export function TimezoneSelectComponent({
108108
className={`${className} ${timezoneSelectCustomClassname}`}
109109
aria-label="Timezone Select"
110110
isLoading={isPending}
111+
data-testid="timezone-select"
111112
isDisabled={isPending}
112113
{...reactSelectProps}
113114
timezones={{

packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "@calcom/atoms/selected-calendars/wrappers/SelectedCalendarsSettingsWebWrapper";
1313
import getLocationsOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
1414
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
15+
import { TimezoneSelect } from "@calcom/features/components/timezone-select";
1516
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
1617
import {
1718
allowDisablingAttendeeConfirmationEmails,
@@ -1033,23 +1034,63 @@ export const EventAdvancedTab = ({
10331034
/>
10341035
<Controller
10351036
name="lockTimeZoneToggleOnBookingPage"
1036-
render={({ field: { value, onChange } }) => (
1037-
<SettingsToggle
1038-
labelClassName={classNames("text-sm", customClassNames?.timezoneLock?.label)}
1039-
descriptionClassName={customClassNames?.timezoneLock?.description}
1040-
toggleSwitchAtTheEnd={true}
1041-
switchContainerClassName={classNames(
1042-
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
1043-
customClassNames?.timezoneLock?.container
1044-
)}
1045-
title={t("lock_timezone_toggle_on_booking_page")}
1046-
{...lockTimeZoneToggleOnBookingPageLocked}
1047-
description={t("description_lock_timezone_toggle_on_booking_page")}
1048-
checked={value}
1049-
onCheckedChange={(e) => onChange(e)}
1050-
data-testid="lock-timezone-toggle"
1051-
/>
1052-
)}
1037+
render={({ field: { value, onChange } }) => {
1038+
// Calculate if we should show the selector based on current form state & handle backward compatibility
1039+
const currentLockedTimeZone = formMethods.getValues("lockedTimeZone");
1040+
const showSelector =
1041+
value &&
1042+
(!(eventType.lockTimeZoneToggleOnBookingPage && !eventType.lockedTimeZone) ||
1043+
!!currentLockedTimeZone);
1044+
1045+
return (
1046+
<SettingsToggle
1047+
labelClassName={classNames("text-sm", customClassNames?.timezoneLock?.label)}
1048+
descriptionClassName={customClassNames?.timezoneLock?.description}
1049+
toggleSwitchAtTheEnd={true}
1050+
switchContainerClassName={classNames(
1051+
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
1052+
customClassNames?.timezoneLock?.container,
1053+
showSelector && "rounded-b-none"
1054+
)}
1055+
title={t("lock_timezone_toggle_on_booking_page")}
1056+
{...lockTimeZoneToggleOnBookingPageLocked}
1057+
description={t("description_lock_timezone_toggle_on_booking_page")}
1058+
checked={value}
1059+
onCheckedChange={(e) => {
1060+
onChange(e);
1061+
const lockedTimeZone = e ? eventType.lockedTimeZone ?? "Europe/London" : null;
1062+
formMethods.setValue("lockedTimeZone", lockedTimeZone, { shouldDirty: true });
1063+
}}
1064+
data-testid="lock-timezone-toggle"
1065+
childrenClassName="lg:ml-0">
1066+
{showSelector && (
1067+
<div className="border-subtle flex flex-col gap-6 rounded-b-lg border border-t-0 p-6">
1068+
<div>
1069+
<Controller
1070+
name="lockedTimeZone"
1071+
control={formMethods.control}
1072+
render={({ field: { value } }) => (
1073+
<>
1074+
<Label className="text-default mb-2 block text-sm font-medium">
1075+
<>{t("timezone")}</>
1076+
</Label>
1077+
<TimezoneSelect
1078+
id="lockedTimeZone"
1079+
value={value ?? "Europe/London"}
1080+
onChange={(event) => {
1081+
if (event)
1082+
formMethods.setValue("lockedTimeZone", event.value, { shouldDirty: true });
1083+
}}
1084+
/>
1085+
</>
1086+
)}
1087+
/>
1088+
</div>
1089+
</div>
1090+
)}
1091+
</SettingsToggle>
1092+
);
1093+
}}
10531094
/>
10541095
<Controller
10551096
name="allowReschedulingPastBookings"

packages/features/eventtypes/lib/getPublicEvent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export const getPublicEventSelect = (fetchAllUsers: boolean) => {
6868
disableGuests: true,
6969
metadata: true,
7070
lockTimeZoneToggleOnBookingPage: true,
71+
lockedTimeZone: true,
7172
requiresConfirmation: true,
7273
autoTranslateDescriptionEnabled: true,
7374
fieldTranslations: {

packages/features/eventtypes/lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export type FormValues = {
8888
description: string;
8989
disableGuests: boolean;
9090
lockTimeZoneToggleOnBookingPage: boolean;
91+
lockedTimeZone: string | null;
9192
requiresConfirmation: boolean;
9293
requiresConfirmationWillBlockSlot: boolean;
9394
requiresConfirmationForFreeEmail: boolean;

0 commit comments

Comments
 (0)