Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
0762d3f
Setup UI for global booking limits
asadath1395 Mar 28, 2024
1c81a92
Refactor code to reuse existing code block
asadath1395 Mar 28, 2024
5be79a2
Add new table GlobalSettings, setup API to save the values from UI to DB
asadath1395 Mar 29, 2024
b2aee6f
Added modal to sync event settings with global settings and its relat…
asadath1395 Apr 4, 2024
0d8d828
Add falling back to global settings for booking limits when its not s…
asadath1395 Apr 5, 2024
dc3109e
Add ability to sync future booking with global settings
asadath1395 Apr 5, 2024
5f9157b
Fix all future booking event types returned even when global setting …
asadath1395 Apr 8, 2024
f0014ca
Revert global settings taken into consideration when limits where not…
asadath1395 Apr 9, 2024
646b6f0
Add ability to disable global limit settings
asadath1395 Apr 9, 2024
a98e1fb
Fix type errors
asadath1395 Apr 9, 2024
de1fe21
Fix type errors
asadath1395 Apr 9, 2024
60d98ee
Merge branch 'booking-global-limits' of https://github.com/asadath139…
asadath1395 Apr 12, 2024
a55fd2c
Merge with upstream
asadath1395 Apr 16, 2024
95df261
Fix failing to sync events with global settings
asadath1395 Apr 16, 2024
cec6253
Fix type check
asadath1395 Apr 16, 2024
3ebc5e0
Fix values not getting synced with global limits in individual event …
asadath1395 Apr 17, 2024
79aa566
Merge branch 'main' into booking-global-limits
asadath1395 Apr 17, 2024
1885e6a
Remove global future bookings related code
asadath1395 Apr 20, 2024
543f960
Merge branch 'booking-global-limits' of https://github.com/asadath139…
asadath1395 Apr 20, 2024
a099a39
Add global booking limit check while booking a event type
asadath1395 Apr 22, 2024
d9e6963
Improve UI to match design
asadath1395 Apr 22, 2024
8b16e30
Remove global settings and move bookingLimits to user table
asadath1395 Apr 25, 2024
b361282
Merge branch 'main' into booking-global-limits
asadath1395 Apr 25, 2024
021dd76
Fix type error
asadath1395 Apr 25, 2024
a7ee1da
Merge with upstream
asadath1395 Apr 30, 2024
d13ab8b
Fix availablity showing slots as open even when global booking limits…
asadath1395 Sep 13, 2024
aa5a782
Fix global booking limits to be applied to only individual and manage…
asadath1395 Sep 23, 2024
0aa2883
Merge with upstream
asadath1395 Sep 25, 2024
f78862b
Remove yarn.lock changes
asadath1395 Sep 25, 2024
820d316
Merge with upstream
asadath1395 Sep 25, 2024
8d8635b
Address PR comments
asadath1395 Sep 25, 2024
bb6749a
Add tests
asadath1395 Sep 25, 2024
6cf0a97
Merge with upstream
asadath1395 Oct 8, 2024
ad4d4ed
Merge branch 'main' into booking-global-limits
asadath1395 Oct 14, 2024
760a797
Discard changes to apps/web/modules/signup-view.tsx
zomars Oct 14, 2024
2bc097d
Merge branch 'main' into pr/14243
zomars Oct 14, 2024
ed5e17d
Rename bookingLimits.tsx to booking-limits.tsx
zomars Oct 14, 2024
214dd52
Fixes
zomars Oct 14, 2024
c37daf6
Refactoring
zomars Oct 14, 2024
f7f9abe
type fix
zomars Oct 14, 2024
bc11784
Fix available slots shown when global booking limits is set and user …
asadath1395 Oct 16, 2024
1393a86
Merge branch 'main' into booking-global-limits
asadath1395 Oct 16, 2024
d8936e9
Merge branch 'main' into booking-global-limits
asadath1395 Oct 16, 2024
3f21f51
feat: Add last login to user table and display that in orgs members view
asadath1395 Oct 18, 2024
34de56e
Merge branch 'main' into booking-global-limits
asadath1395 Oct 22, 2024
11ac965
Merge with upstream and resolve merge conflicts
asadath1395 Oct 28, 2024
24b8b76
Merge branch 'booking-global-limits' of https://github.com/asadath139…
asadath1395 Oct 28, 2024
66ab828
Merge branch 'main' into booking-global-limits
anikdhabal Oct 28, 2024
3b607cf
Merge with upstream and resolve conflicts
asadath1395 Nov 7, 2024
a4fb4c1
Merge branch 'booking-global-limits' of https://github.com/asadath139…
asadath1395 Nov 7, 2024
991b174
Merge branch 'main' into booking-global-limits
asadath1395 Nov 8, 2024
753e025
Merge branch 'main' into booking-global-limits
asadath1395 Nov 11, 2024
a655d7c
Merge branch 'main' into booking-global-limits
asadath1395 Nov 12, 2024
990a8af
Merge branch 'main' into booking-global-limits
asadath1395 Nov 19, 2024
b7e9cfe
Merge branch 'main' of https://github.com/calcom/cal.com into booking…
asadath1395 Nov 24, 2024
139e3c3
Merge branch 'main' into booking-global-limits
asadath1395 Nov 24, 2024
d4e195e
Merge branch 'booking-global-limits' of https://github.com/asadath139…
asadath1395 Dec 4, 2024
7697e72
Merge with upstream
asadath1395 Dec 4, 2024
63c2fb8
Merge with upstream and fix merge conflicts
asadath1395 Dec 16, 2024
fa40128
Merge branch 'main' into booking-global-limits
asadath1395 Dec 17, 2024
628d73f
Merge branch 'main' of https://github.com/calcom/cal.com into booking…
asadath1395 Mar 13, 2025
9cce11e
Revert unintended changes
asadath1395 Mar 13, 2025
ffd2dc0
Merge branch 'main' into booking-global-limits
asadath1395 Mar 13, 2025
620cd5b
Merge branch 'main' into booking-global-limits
asadath1395 Mar 14, 2025
03a7391
Fix type error
asadath1395 Mar 14, 2025
e930229
Merge branch 'booking-global-limits' of https://github.com/asadath139…
asadath1395 Mar 14, 2025
b88a3e5
Merge branch 'main' of https://github.com/calcom/cal.com into booking…
asadath1395 Mar 15, 2025
0e13b39
Merge branch 'main' of https://github.com/calcom/cal.com into booking…
asadath1395 Apr 10, 2025
c4406b9
Merge branch 'main' of https://github.com/calcom/cal.com into booking…
asadath1395 Apr 11, 2025
63e561e
Address PR review comments
asadath1395 Apr 11, 2025
404d39c
Fix tests
asadath1395 Apr 11, 2025
7652dbb
Merge branch 'main' into booking-global-limits
asadath1395 Apr 15, 2025
568f704
Merge branch 'main' of https://github.com/calcom/cal.com into booking…
asadath1395 Apr 22, 2025
4bc552c
Merge branch 'booking-global-limits' of https://github.com/asadath139…
asadath1395 Apr 22, 2025
1c06a4c
Merge with upstream
asadath1395 Jul 2, 2025
8405d52
Address cubic-dev-ai review comments
asadath1395 Jul 2, 2025
9fac49d
Merge with upstream
asadath1395 Jul 9, 2025
04fdafb
Revert unintended changes
asadath1395 Jul 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions apps/web/components/settings/GlobalBookingLimitsController.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"use client";

import { useForm, Controller } from "react-hook-form";

import { IntervalLimitsManager } from "@calcom/features/eventtypes/components/tabs/limits/EventLimitsTab";
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { parseBookingLimit } from "@calcom/lib/intervalLimits/isBookingLimits";
import { validateIntervalLimitOrder } from "@calcom/lib/intervalLimits/validateIntervalLimitOrder";
import { trpc } from "@calcom/trpc/react";
import type { IntervalLimit } from "@calcom/types/Calendar";
import classNames from "@calcom/ui/classNames";
import { Button } from "@calcom/ui/components/button";
import { Form, SettingsToggle } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/components/toast";

const GlobalBookingLimitsController = ({
bookingLimits,
}: {
bookingLimits: IntervalLimit | null | undefined;
}) => {
const { t } = useLocale();
const safeBookingLimits = bookingLimits ?? {};
const bookingsLimitFormMethods = useForm({
defaultValues: {
bookingLimits: safeBookingLimits,
},
});

const utils = trpc.useUtils();
const updateProfileMutation = trpc.viewer.me.updateProfile.useMutation({
onSuccess: async () => {
await utils.viewer.me.invalidate();
bookingsLimitFormMethods.reset(bookingsLimitFormMethods.getValues());
showToast(t("booking_limits_updated_successfully"), "success");
},
onError: () => {
showToast(t("failed_to_save_global_settings"), "error");
},
});

const handleSubmit = async (values: { bookingLimits: IntervalLimit }) => {
const { bookingLimits } = values;
const parsedBookingLimits = parseBookingLimit(bookingLimits) || {};
if (bookingLimits) {
const isValid = validateIntervalLimitOrder(parsedBookingLimits);
if (!isValid) throw new Error(t("event_setup_booking_limits_error"));
}
updateProfileMutation.mutate({ ...values, bookingLimits: parsedBookingLimits });
};

return (
<Form form={bookingsLimitFormMethods} handleSubmit={handleSubmit}>
<Controller
name="bookingLimits"
render={({ field: { value } }) => {
const isChecked = Object.keys(value ?? {}).length > 0;
return (
<SettingsToggle
toggleSwitchAtTheEnd={true}
title={t("limit_booking_frequency")}
description={t("global_limit_booking_frequency_description")}
checked={isChecked}
onCheckedChange={(active) => {
if (active) {
bookingsLimitFormMethods.setValue("bookingLimits", {
PER_DAY: 1,
});
} else {
bookingsLimitFormMethods.setValue("bookingLimits", {});
}
handleSubmit(bookingsLimitFormMethods.getValues());
}}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
isChecked && "rounded-b-none"
)}
childrenClassName="lg:ml-0">
<div className="border-subtle border border-y-0 p-6">
<IntervalLimitsManager propertyName="bookingLimits" defaultLimit={1} step={1} />
</div>
<SectionBottomActions align="end">
<Button
color="primary"
type="submit"
loading={updateProfileMutation.isPending}
disabled={!bookingsLimitFormMethods.formState.dirtyFields.bookingLimits}>
{t("update")}
</Button>
</SectionBottomActions>
</SettingsToggle>
);
}}
/>
</Form>
);
};

export default GlobalBookingLimitsController;
2 changes: 2 additions & 0 deletions apps/web/modules/settings/my-account/general-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SettingsToggle } from "@calcom/ui/components/form";
import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton";
import { showToast } from "@calcom/ui/components/toast";

import GlobalBookingLimitsController from "@components/settings/GlobalBookingLimitsController";
import TravelScheduleModal from "@components/settings/TravelScheduleModal";

export type FormValues = {
Expand Down Expand Up @@ -395,6 +396,7 @@ const GeneralView = ({ localeProp, user, travelSchedules, revalidatePage }: Gene
setValue={formMethods.setValue}
existingSchedules={formMethods.getValues("travelSchedules") ?? []}
/>
<GlobalBookingLimitsController bookingLimits={user.bookingLimits} />
</div>
);
};
Expand Down
2 changes: 2 additions & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -2673,6 +2673,8 @@
"disconnect_account": "Disconnect connected account",
"disconnect_account_hint": "Disconnecting your connected account will change the way you log in. You will only be able to login to your account using email + password",
"cookie_consent_checkbox": "I agree to the privacy policy and cookie usage",
"global_limit_booking_frequency_description": "Limit how many bookings you allow across all your event types.",
"failed_to_save_global_settings": "Failed to save global settings",
"make_a_call": "Make a Call",
"skip_rr_assignment_label": "Skip round robin assignment if contact exists in Salesforce",
"skip_rr_description": "URL must contain the contacts email as a parameter ex. ?email=contactEmail",
Expand Down
3 changes: 3 additions & 0 deletions apps/web/test/utils/bookingScenario/bookingScenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,7 @@ export function getOrganizer({
organizationId,
metadata,
smsLockState,
bookingLimits,
completedOnboarding,
username,
locked,
Expand All @@ -1502,6 +1503,7 @@ export function getOrganizer({
teams?: InputUser["teams"];
metadata?: userMetadataType;
smsLockState?: SMSLockState;
bookingLimits?: IntervalLimit;
completedOnboarding?: boolean;
username?: string;
locked?: boolean;
Expand All @@ -1524,6 +1526,7 @@ export function getOrganizer({
profiles: [],
metadata,
smsLockState,
bookingLimits,
completedOnboarding,
locked,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSche
import { checkBookingLimits } from "@calcom/lib/intervalLimits/server/checkBookingLimits";
import { checkDurationLimits } from "@calcom/lib/intervalLimits/server/checkDurationLimits";
import { withReporting } from "@calcom/lib/sentryWrapper";
import prisma from "@calcom/prisma";

import type { NewBookingEventType } from "./getEventTypesFromDB";

type EventType = Pick<NewBookingEventType, "bookingLimits" | "durationLimits" | "id" | "schedule">;
type EventType = Pick<
NewBookingEventType,
"bookingLimits" | "durationLimits" | "id" | "schedule" | "userId" | "schedulingType"
>;

type InputProps = {
eventType: EventType;
Expand All @@ -19,11 +23,11 @@ const _checkBookingAndDurationLimits = async ({
reqBodyStart,
reqBodyRescheduleUid,
}: InputProps) => {
const startAsDate = dayjs(reqBodyStart).toDate();
if (
Object.prototype.hasOwnProperty.call(eventType, "bookingLimits") ||
Object.prototype.hasOwnProperty.call(eventType, "durationLimits")
) {
const startAsDate = dayjs(reqBodyStart).toDate();
if (eventType.bookingLimits && Object.keys(eventType.bookingLimits).length > 0) {
await checkBookingLimits(
eventType.bookingLimits as IntervalLimit,
Expand All @@ -42,6 +46,31 @@ const _checkBookingAndDurationLimits = async ({
);
}
}

// We are only interested in global booking limits for individual and managed events for which schedulingType is null
if (eventType.userId && !eventType.schedulingType) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition lacks clarity on why booking limits are only checked for users when schedulingType is falsy. Consider adding a comment explaining this logic.

const eventTypeUser = await prisma.user.findUnique({
where: {
id: eventType.userId,
},
select: {
id: true,
email: true,
bookingLimits: true,
},
});
if (eventTypeUser?.bookingLimits && Object.keys(eventTypeUser.bookingLimits).length > 0) {
await checkBookingLimits(
eventTypeUser.bookingLimits as IntervalLimit,
startAsDate,
eventType.id,
reqBodyRescheduleUid,
eventType.schedule?.timeZone,
{ id: eventTypeUser.id, email: eventTypeUser.email },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we correctly pass the user, that's why we get this error on yearly limit (slots are shown that shouldn't be available)
Screenshot 2024-10-15 at 10 13 42 AM

/* isGlobalBookingLimits */ true
);
}
}
};

export const checkBookingAndDurationLimits = withReporting(
Expand Down
Loading
Loading