Skip to content

Comments

feat: ✨ lock member default availability#22136

Draft
shaun-ak wants to merge 89 commits intocalcom:mainfrom
shaun-ak:feat-lock-member-default-availability
Draft

feat: ✨ lock member default availability#22136
shaun-ak wants to merge 89 commits intocalcom:mainfrom
shaun-ak:feat-lock-member-default-availability

Conversation

@shaun-ak
Copy link
Contributor

@shaun-ak shaun-ak commented Jun 30, 2025

What does this PR do?

Video Demo (if applicable):

Screen.Recording.2025-07-01.at.8.57.37.AM.mp4

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

Summary by cubic

Added an organization-only team setting to lock default availability, preventing members from editing, deleting, or changing the timezone of their default schedule unless they are an admin or owner. Members can still create additional schedules.

  • New Features
    • UI toggle in Team Settings (admins only; shown for teams within organizations).
    • When locked, default schedule actions are disabled with clear error/toast and a warning banner.
    • Timezone updates skip the default schedule when locked (profile updates and cron timezone changes).

Written for commit 923b7b2. Summary will update on new commits.

@shaun-ak shaun-ak requested a review from a team June 30, 2025 04:08
@shaun-ak shaun-ak marked this pull request as draft June 30, 2025 04:08
@vercel
Copy link

vercel bot commented Jun 30, 2025

@shaun-ak is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@graphite-app graphite-app bot requested a review from a team June 30, 2025 04:08
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Jun 30, 2025
@github-actions github-actions bot added the ❗️ migrations contains migration files label Jun 30, 2025
@dosubot dosubot bot added the ✨ feature New feature or request label Jun 30, 2025
@graphite-app
Copy link

graphite-app bot commented Jun 30, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (06/30/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add community label" took an action on this PR • (06/30/25)

1 label was added to this PR based on Keith Williams's automation.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic found 3 issues across 13 files. Review them in cubic.dev

React with 👍 or 👎 to teach cubic. Tag @cubic-dev-ai to give specific feedback.

Copy link
Contributor

@Devanshusharma2005 Devanshusharma2005 left a comment

Choose a reason for hiding this comment

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

Hey can you please address the issues suggested by the cubic

@github-actions github-actions bot added consumer teams area: teams, round robin, collective, managed event-types labels Jul 1, 2025
@shaun-ak shaun-ak marked this pull request as ready for review July 1, 2025 04:03
@dosubot dosubot bot added the bookings area: bookings, availability, timezones, double booking label Jul 1, 2025
@shaun-ak
Copy link
Contributor Author

shaun-ak commented Jul 1, 2025

hey @Devanshusharma2005 , I have addressed the review comments by cubic.

Also added a video for reference.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic reviewed 13 files and found no issues. Review PR in cubic.dev.

Copy link
Contributor

@Devanshusharma2005 Devanshusharma2005 left a comment

Choose a reason for hiding this comment

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

okay . Lets wait for @anikdhabal review too.

Copy link
Contributor

@kart1ka kart1ka left a comment

Choose a reason for hiding this comment

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

Left a comment. Need clarification from the team. The rest of the code looks good to me.

Copy link
Contributor

@kart1ka kart1ka left a comment

Choose a reason for hiding this comment

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

In a team inside an org, when a team admin/owner tries to update their own default availability, no error is shown. Default Availability does not update but on the frontend no error is shown.

https://cap.link/sca2b78tdqjecpr

@shaun-ak
Copy link
Contributor Author

shaun-ak commented Jul 3, 2025

Hey @kart1ka , I was trying to set up an organization following this Guide so that I could recreate this issue, seems like I need a License key to set up an org and I think one is not available for open source.

Can you please help me with this.

Thanks!

@kart1ka
Copy link
Contributor

kart1ka commented Jul 3, 2025

@shaun-ak you can check the docs here: https://cal.com/docs/self-hosting/license-key

@shaun-ak
Copy link
Contributor Author

shaun-ak commented Jul 4, 2025

Hey @kart1ka , I am trying to use the key from docs but getting "Invalid License Key" error.

cap.link/hzkkj76x5693ahn

@shaun-ak shaun-ak marked this pull request as ready for review October 27, 2025 03:26
});

// Update all schedules to the new timezone (excluding default if locked)
const schedulesToUpdate = hasLockedAvailability
Copy link
Contributor Author

@shaun-ak shaun-ak Oct 27, 2025

Choose a reason for hiding this comment

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

While local testing, I found that if we update timezone or travel schedule timezone from general > settings, then not only the timezone of default availability but also the timezones of other schedules are not getting updated, that should not be the case, the schedulesToUpdate code change fixes that.

Note: in case of locked default availability, only the timezone of default availability should not be updated on profile timezone change.

This was working previously, but maybe some new code change in main branch caused this side effect.

CC: @keithwillcode @volnei @CarinaWolli

Copy link
Contributor Author

@shaun-ak shaun-ak Oct 28, 2025

Choose a reason for hiding this comment

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

Checked in main branch, all other availability timezones do not get updated and only the default one gets updated.

I checked in this branch so thought that default availability was not getting updated.

What should actually happen, I am confused here.

@CarinaWolli

@shaun-ak shaun-ak requested a review from dhairyashiil October 27, 2025 03:33
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 26 files

Prompt for AI agents (all 4 issues)

Understand the root cause of the following 4 issues and fix them.


<file name="apps/web/app/api/cron/changeTimeZone/route.ts">

<violation number="1" location="apps/web/app/api/cron/changeTimeZone/route.ts:102">
If the stored defaultScheduleId points to a deleted schedule, this new update() call will throw and halt the cron job. A schedule-less findUnique returns null, the guard treats it as falsy, and update() now raises instead of no-op. Consider using updateMany or checking for null before updating.</violation>
</file>

<file name="apps/api/v1/pages/api/teams/[teamId]/_patch.ts">

<violation number="1" location="apps/api/v1/pages/api/teams/[teamId]/_patch.ts:102">
Enabling lockDefaultAvailability should permit the same request that assigns a parent team—otherwise legitimate updates (setting parentId and enabling the lock together) fail because the guard only inspects the current team.parentId.</violation>
</file>

<file name="packages/lib/lockedDefaultAvailability.test.ts">

<violation number="1" location="packages/lib/lockedDefaultAvailability.test.ts:15">
The mock for &quot;@calcom/prisma&quot; only provides a default export, so the named prisma import used by the implementation is undefined; this causes the tested functions to throw before assertions run.</violation>
</file>

<file name="packages/trpc/server/routers/viewer/me/updateProfile.handler.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/me/updateProfile.handler.ts:284">
Updating all schedules&#39; timeZone here overrides any non-default schedule that was intentionally in a different timezone (the old code only touched the default schedule). Please restrict the timezone change to the default schedule and skip it when the lock is active.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

shaun-ak and others added 4 commits October 27, 2025 09:35
# Conflicts:
#	apps/web/public/static/locales/en/common.json
#	packages/features/schedules/repositories/ScheduleRepository.ts
#	packages/lib/schedules/updateSchedule.ts
@shaun-ak
Copy link
Contributor Author

2nd.test.1.1.mp4

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 27 files

Prompt for AI agents (all 4 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/app/api/cron/changeTimeZone/route.ts">

<violation number="1" location="apps/web/app/api/cron/changeTimeZone/route.ts:84">
When a locked default schedule lacks a stored timezone, this cron path writes the new travel timezone (`timeZone`) into it, so the first travel update overwrites the supposedly locked schedule. Use the user’s previous timezone instead so the lock is preserved.</violation>
</file>

<file name="apps/api/v1/pages/api/teams/[teamId]/_patch.ts">

<violation number="1" location="apps/api/v1/pages/api/teams/[teamId]/_patch.ts:102">
`lockDefaultAvailability` can’t be enabled in the same request that assigns the team to an organization because the guard checks the old `team.parentId` rather than the pending `data.parentId`. Consider validating against `data.parentId ?? team.parentId` so simultaneous updates succeed.</violation>
</file>

<file name="packages/features/schedules/components/NewScheduleButton.tsx">

<violation number="1" location="packages/features/schedules/components/NewScheduleButton.tsx:41">
Preserve the actual `lockedDefaultAvailability` flag instead of forcing it to `false`, otherwise locked teams briefly see default-changing actions for newly created schedules.</violation>
</file>

<file name="packages/platform/atoms/availability/AvailabilitySettings.tsx">

<violation number="1" location="packages/platform/atoms/availability/AvailabilitySettings.tsx:285">
Locking a default schedule only disables adding overrides, but editing/deleting existing overrides stays enabled, so the &quot;locked&quot; state can still be bypassed.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

},
},
data: {
timeZone: timeZone,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 25, 2025

Choose a reason for hiding this comment

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

When a locked default schedule lacks a stored timezone, this cron path writes the new travel timezone (timeZone) into it, so the first travel update overwrites the supposedly locked schedule. Use the user’s previous timezone instead so the lock is preserved.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/cron/changeTimeZone/route.ts, line 84:

<comment>When a locked default schedule lacks a stored timezone, this cron path writes the new travel timezone (`timeZone`) into it, so the first travel update overwrites the supposedly locked schedule. Use the user’s previous timezone instead so the lock is preserved.</comment>

<file context>
@@ -54,14 +58,58 @@ async function postHandler(request: NextRequest) {
+          },
+        },
+        data: {
+          timeZone: timeZone,
+        },
+      });
</file context>
Fix with Cubic

...schedule,
isDefault: false,
availability: [],
lockedDefaultAvailability: false,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 25, 2025

Choose a reason for hiding this comment

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

Preserve the actual lockedDefaultAvailability flag instead of forcing it to false, otherwise locked teams briefly see default-changing actions for newly created schedules.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/features/schedules/components/NewScheduleButton.tsx, line 41:

<comment>Preserve the actual `lockedDefaultAvailability` flag instead of forcing it to `false`, otherwise locked teams briefly see default-changing actions for newly created schedules.</comment>

<file context>
@@ -34,7 +34,12 @@ export function NewScheduleButton({
+          ...schedule,
+          isDefault: false,
+          availability: [],
+          lockedDefaultAvailability: false,
+        };
         if (!data)
</file context>
Suggested change
lockedDefaultAvailability: false,
lockedDefaultAvailability: schedule.lockedDefaultAvailability ?? data?.schedules?.[0]?.lockedDefaultAvailability ?? false,
Fix with Cubic

StartIcon="plus"
data-testid="add-override">
data-testid="add-override"
disabled={disabled}>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 25, 2025

Choose a reason for hiding this comment

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

Locking a default schedule only disables adding overrides, but editing/deleting existing overrides stays enabled, so the "locked" state can still be bypassed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/platform/atoms/availability/AvailabilitySettings.tsx, line 285:

<comment>Locking a default schedule only disables adding overrides, but editing/deleting existing overrides stays enabled, so the &quot;locked&quot; state can still be bypassed.</comment>

<file context>
@@ -263,7 +281,8 @@ const DateOverride = ({
               StartIcon=&quot;plus&quot;
-              data-testid=&quot;add-override&quot;&gt;
+              data-testid=&quot;add-override&quot;
+              disabled={disabled}&gt;
               {t(&quot;add_an_override&quot;)}
             &lt;/Button&gt;
</file context>
Fix with Cubic

Copy link
Contributor

@pallava-joshi pallava-joshi left a comment

Choose a reason for hiding this comment

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

can you please check on cubic suggestions too

@pallava-joshi pallava-joshi marked this pull request as draft December 4, 2025 17:35
@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

Devin AI is resolving merge conflicts

This PR has merge conflicts with the main branch. A Devin session has been created to automatically resolve them.

View Devin Session

Devin will:

  1. Merge the latest main into this branch
  2. Resolve any conflicts intelligently
  3. Run lint/type checks to ensure validity
  4. Push the resolved changes

If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself.

Resolved conflicts in:
- apps/web/app/api/cron/changeTimeZone/route.ts
- apps/web/public/static/locales/en/common.json
- packages/features/schedules/components/ScheduleListItem.tsx
- packages/features/schedules/repositories/ScheduleRepository.ts
- packages/platform/atoms/availability/AvailabilitySettings.tsx
- packages/trpc/server/routers/viewer/availability/list.handler.ts
- packages/trpc/server/routers/viewer/availability/schedule/delete.handler.ts
- packages/trpc/server/routers/viewer/me/updateProfile.handler.ts

Conflict resolutions:
- Updated imports to use new ScheduleRepository from features package
- Updated HostRepository import to new location in features package
- Preserved locked default availability feature while adopting upstream changes
- Combined PR's locked availability checks with upstream's dialog-based delete confirmation
- Merged upstream's formHasChanges disabled state with PR's locked availability disabled state

Co-Authored-By: unknown <>
@github-actions github-actions bot added Low priority Created by Linear-GitHub Sync organizations area: organizations, orgs labels Feb 4, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

Devin AI is completing this stale PR

This PR by @shaun-ak has been marked as stale. A Devin session has been created to complete the remaining work.

View Devin Session


Devin will review the PR, address any feedback, and push updates to complete this PR.

- Fix small-screen 'Set to default' switch disabled condition by removing
  redundant 'value' check that prevented toggling when already checked
- Fix effectiveParentId check in teams API to consider data.parentId when
  setting lockDefaultAvailability in the same request
- Fix timezone change guard in updateProfile to only trigger when timeZone
  is explicitly provided in input, preventing false positives

Co-authored-by: shaun-ak <shaun-ak@users.noreply.github.com>
Co-Authored-By: unknown <>
@devin-ai-integration
Copy link
Contributor

Summary of Changes

I've addressed the following review feedback items in this PR:

Fixes Applied

  1. Small-screen "Set to default" switch disabled condition (packages/platform/atoms/availability/AvailabilitySettings.tsx)

    • Removed the redundant value check from the disabled condition that was preventing users from toggling the switch when it was already checked
    • The large-screen version didn't have this issue, so now both are consistent
  2. effectiveParentId check in teams API (apps/api/v1/pages/api/teams/[teamId]/_patch.ts)

    • Fixed the lockDefaultAvailability validation to consider data.parentId when it's being set in the same request
    • Previously, it only checked team.parentId, which would fail if a team was being moved to an organization and having lockDefaultAvailability enabled in the same API call
  3. Timezone change guard in updateProfile (packages/trpc/server/routers/viewer/me/updateProfile.handler.ts)

    • Fixed the condition to only trigger timezone-related schedule updates when input.timeZone is explicitly provided
    • Previously, the condition could fire incorrectly when timeZone wasn't in the input, potentially causing unintended behavior

CI Status

The PR requires a maintainer to add the run-ci label to trigger the full CI checks (expected behavior for external contributor PRs). The Vercel deployment failures are also expected for fork PRs.


This PR was completed by Devin on behalf of the Cal.com team. Original work by @shaun-ak.

Link to Devin run: https://app.devin.ai/sessions/e0191b1a0baf41ca80dd8f3688df8ee2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bookings area: bookings, availability, timezones, double booking community Created by Linear-GitHub Sync consumer ✨ feature New feature or request Low priority Created by Linear-GitHub Sync Medium priority Created by Linear-GitHub Sync ❗️ migrations contains migration files organizations area: organizations, orgs size/XL teams area: teams, round robin, collective, managed event-types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lock member's default availability