Skip to content

Conversation

husniabad
Copy link
Contributor

What does this PR do?

This PR introduces a new feature allowing users to report suspicious bookings from their /bookings list. It adds a "Report" option to the booking actions, which opens a new dialog to collect details. Users can optionally cancel an upcoming booking directly when reporting it.

This change includes UI updates to the bookings list, the new "Report Booking" dialog component, and the necessary backend API and database models (BookingReport, ReportReason) to handle submissions.

  • Fixes Report bookings #22696 (GitHub issue number)
  • Fixes CAL-6139 (Linear issue number - should be visible at the bottom of the GitHub issue description)

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.

Image Demo (if applicable):

  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).

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.

How should this be tested?

  • Navigate to the /bookings page (upcoming, past, or cancelled).

  • Select "Report" from a booking's action menu.

  • For an upcoming booking: Fill out the dialog and click "Report", or check the "Cancel booking?" box, and click "Report and Cancel".

  • Expected: The booking is Reported and now displays a "Reported" badge, if you checked the "cancel" checkox the booking wil marked "Reported" and shift to cancelled bookings. A new entry exists in the BookingReport database table.

  • For a past or cancelled booking: Fill out the dialog and click "Report".

  • Expected: The booking now displays a "Reported" badge. The "Report" option is no longer available for this booking.

@husniabad husniabad requested a review from a team as a code owner July 24, 2025 05:21
@husniabad husniabad requested a review from a team July 24, 2025 05:21
Copy link

vercel bot commented Jul 24, 2025

@husniabad is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Jul 24, 2025

Walkthrough

Adds end-to-end booking reporting: frontend UI (ReportBookingDialog, report actions in BookingListItem, reported badge, i18n), a getReportActions helper and tests, Prisma schema + migration for BookingReport and ReportReason, TRPC schema/procedure/handler (reportBooking) that creates reports and can invoke existing cancellation flow, backend booking queries augmented to include reports, and small validation/handler updates to allow cancellations originating from reports.

Assessment against linked issues

Objective Addressed Explanation
Allow reporting for any booking shown in /bookings (upcoming, past, cancelled) [#22696, CAL-6139]
UI: Add "Report" to dropdown for upcoming/past; show "Report" next to Video Options for cancelled; show "Reported" badge and prevent re-reporting [#22696, CAL-6139]
Report Dialog: title/subtitle, required reason (Spam, Don’t know this person, Other), optional comments, cancel-checkbox only for upcoming, info text, and buttons reflecting checkbox state [#22696, CAL-6139]
Behavior: Persist BookingReport in DB and call existing cancellation flow when "Cancel booking" selected; permissions allow users who can see the booking to report (owner/attendee/team admin/owner) [#22696, CAL-6139]

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Added idempotencyKey: String? @unique to Booking model (packages/prisma/schema.prisma; migration present in packages/prisma/migrations/20250823231324_add_booking_report/migration.sql) This field is unrelated to the reporting feature objectives; no requirement in linked issues asks for an idempotencyKey on bookings.
Addition of getMembershipIdsWhereUserIsAdminOwner helper (packages/lib/server/queries/teams/index.ts) and its extraction used in bookings get handler (packages/trpc/server/routers/viewer/bookings/get.handler.ts) While used by report access checks, introducing a new exported helper is a broader API surface change not requested by the linked issues; reasonableness is likely, but the helper itself is beyond the stated reporting requirements.

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added bookings area: bookings, availability, timezones, double booking consumer ui area: UI, frontend, button, form, input ✨ feature New feature or request ❗️ migrations contains migration files and removed ✨ feature New feature or request bookings area: bookings, availability, timezones, double booking ui area: UI, frontend, button, form, input consumer labels Jul 24, 2025
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Jul 24, 2025
@graphite-app graphite-app bot requested a review from a team July 24, 2025 05:21
@dosubot dosubot bot added bookings area: bookings, availability, timezones, double booking ✨ feature New feature or request labels Jul 24, 2025
@github-actions github-actions bot added consumer ui area: UI, frontend, button, form, input labels Jul 24, 2025
Copy link

graphite-app bot commented Jul 24, 2025

Graphite Automations

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

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

"Add community label" took an action on this PR • (07/24/25)

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (10)
apps/web/components/booking/bookingActions.report.test.ts (2)

9-121: Well-structured mock factory with good override capability.

The createMockContext function provides comprehensive mock data and allows for flexible customization through the overrides parameter. The Date calculations are straightforward and appropriate for test scenarios.

Consider extracting the time calculations to constants for better readability:

function createMockContext(overrides: Partial<BookingActionContext> = {}): BookingActionContext {
  const now = new Date();
+  const ONE_DAY_MS = 24 * 60 * 60 * 1000;
+  const ONE_HOUR_MS = 60 * 60 * 1000;
-  const startTime = new Date(now.getTime() + 24 * 60 * 60 * 1000); // Tomorrow
-  const endTime = new Date(startTime.getTime() + 60 * 60 * 1000); // 1 hour later
+  const startTime = new Date(now.getTime() + ONE_DAY_MS); // Tomorrow
+  const endTime = new Date(startTime.getTime() + ONE_HOUR_MS); // 1 hour later

157-187: Good coverage for timing scenarios.

The tests properly verify that report actions are available for both past and upcoming bookings, ensuring the feature works across different time contexts.

For consistency with the earlier suggestion, consider extracting the time calculation constants:

    it("should return report action for past bookings", () => {
+      const ONE_DAY_MS = 24 * 60 * 60 * 1000;
+      const ONE_HOUR_MS = 60 * 60 * 1000;
-      const pastDate = new Date(Date.now() - 24 * 60 * 60 * 1000); // Yesterday
+      const pastDate = new Date(Date.now() - ONE_DAY_MS); // Yesterday
      const context = createMockContext({
        booking: {
          startTime: pastDate.toISOString(),
-          endTime: new Date(pastDate.getTime() + 60 * 60 * 1000).toISOString(),
+          endTime: new Date(pastDate.getTime() + ONE_HOUR_MS).toISOString(),
        } as any,
apps/web/public/static/locales/en/common.json (2)

3389-3404: Prefer namespacing & avoid overly-generic keys

Keys such as "report", "reason_required" or "reported" are very broad and risk clashing with future, unrelated strings.
Consider prefixing all new entries with a shared context, e.g. report_booking_*, to keep the dictionary tidy and self-describing:

-  "report": "Report",
-  "reason_required": "Reason is required",
-  "reported": "Reported",
+  "report_booking_action": "Report",
+  "report_booking_reason_required": "Reason is required",
+  "report_booking_status_reported": "Reported",

This also makes dead-key searches & i18n extraction easier.
(Other keys in this block already follow the pattern, so aligning all of them keeps things consistent.)


3399-3401: Slight wording improvement

"attendee_not_notified_report" reads awkwardly.
Suggestion:

-  "attendee_not_notified_report": "Attendee will not be notified that this booking was reported"
+  "attendee_not_notified_report": "Attendee will not be notified that this booking has been reported"

Present-perfect tense is more natural here.

apps/web/components/dialog/ReportBookingDialog.tsx (2)

34-42: Remove unused parameter

The bookingTitle parameter is renamed to _bookingTitle but never used in the component.

Either remove the parameter from the interface and props, or use it if it's intended for future use:

 export function ReportBookingDialog({
   isOpen,
   onClose,
   bookingId,
-  bookingTitle: _bookingTitle,
   isUpcoming,
   isCancelled = false,
   onSuccess,
 }: ReportBookingDialogProps) {

79-84: Consider optimizing form reset behavior

The form is reset both when closing the dialog and after successful submission. Consider resetting only on successful submission to preserve user input if they accidentally close the dialog.

 const handleClose = () => {
   if (!isSubmitting) {
     onClose();
-    form.reset();
   }
 };
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (2)

52-56: Optimize team admin/owner authorization checks

The current implementation awaits each check sequentially. Consider parallelizing these checks for better performance.

-  const isTeamAdminOrOwner =
-    booking.eventType?.teamId &&
-    ((await isTeamAdmin(user.id, booking.eventType.teamId)) ||
-      (await isTeamOwner(user.id, booking.eventType.teamId)));
+  let isTeamAdminOrOwner = false;
+  if (booking.eventType?.teamId) {
+    const [isAdmin, isOwner] = await Promise.all([
+      isTeamAdmin(user.id, booking.eventType.teamId),
+      isTeamOwner(user.id, booking.eventType.teamId)
+    ]);
+    isTeamAdminOrOwner = isAdmin || isOwner;
+  }

22-44: Consider optimizing the duplicate report check

Instead of fetching all reports and checking length, consider using a count query or exists check for better performance.

Update the booking query to only check if a report exists:

       reports: {
+        take: 1,
         where: {
           reportedById: user.id,
         },
       },

This will limit the query to fetch at most one report, which is sufficient for the existence check.

packages/prisma/migrations/20250723214119_add_booking_report/migration.sql (1)

1-2: Consider consistent enum naming convention

The enum value 'dont_know_person' uses snake_case while 'SPAM' and 'OTHER' use UPPER_CASE. Consider using consistent naming.

-CREATE TYPE "ReportReason" AS ENUM ('SPAM', 'dont_know_person', 'OTHER');
+CREATE TYPE "ReportReason" AS ENUM ('SPAM', 'DONT_KNOW_PERSON', 'OTHER');

Note: This would require updating the Prisma schema mapping as well.

apps/web/components/booking/BookingListItem.tsx (1)

403-419: Improve readability and consider edge cases.

The logic correctly adds a report action for non-cancelled bookings, but could be improved:

  1. The conditional logic could be more readable
  2. Consider extracting the report action creation into a helper function for reusability
+const createReportAction = (hasBeenReported: boolean, onReport: () => void): ActionType => {
+  return hasBeenReported
+    ? {
+        id: "report",
+        label: t("already_reported"),
+        icon: "flag",
+        disabled: true,
+      }
+    : {
+        id: "report",
+        label: t("report"),
+        icon: "flag",
+        disabled: false,
+        onClick: onReport,
+      };
+};

 if (!isCancelled) {
-  const reportAction: ActionType = hasBeenReported
-    ? {
-        id: "report",
-        label: t("already_reported"),
-        icon: "flag",
-        disabled: true,
-      }
-    : {
-        id: "report",
-        label: t("report"),
-        icon: "flag",
-        disabled: false,
-        onClick: () => setIsReportDialogOpen(true),
-      };
+  const reportAction = createReportAction(hasBeenReported, () => setIsReportDialogOpen(true));
   afterEventActions.push(reportAction);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 611f76b and 0880cc0.

📒 Files selected for processing (13)
  • apps/web/components/booking/BookingListItem.tsx (6 hunks)
  • apps/web/components/booking/bookingActions.report.test.ts (1 hunks)
  • apps/web/components/booking/bookingActions.ts (2 hunks)
  • apps/web/components/dialog/ReportBookingDialog.test.tsx (1 hunks)
  • apps/web/components/dialog/ReportBookingDialog.tsx (1 hunks)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • packages/prisma/migrations/20250723214119_add_booking_report/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/_router.tsx (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts (1 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Files:

  • packages/trpc/server/routers/viewer/bookings/get.handler.ts
  • apps/web/components/booking/bookingActions.ts
  • packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts
  • packages/trpc/server/routers/viewer/bookings/reportBooking.test.ts
  • packages/trpc/server/routers/viewer/bookings/_router.tsx
  • apps/web/components/booking/bookingActions.report.test.ts
  • apps/web/components/dialog/ReportBookingDialog.test.tsx
  • apps/web/components/dialog/ReportBookingDialog.tsx
  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
  • apps/web/components/booking/BookingListItem.tsx
🧠 Learnings (5)
packages/trpc/server/routers/viewer/bookings/get.handler.ts (2)

Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-21T13:54:11.770Z
Learning: Applies to backend/**/*.{ts,tsx} : For Prisma queries: Include selects the relationship AND all the fields of the table the relationship belongs to.

Learnt from: eunjae-lee
PR: #22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

packages/trpc/server/routers/viewer/bookings/_router.tsx (1)

Learnt from: eunjae-lee
PR: #22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

apps/web/public/static/locales/en/common.json (1)

Learnt from: bandhan-majumder
PR: #22359
File: packages/lib/server/locales/en/common.json:1336-1339
Timestamp: 2025-07-14T16:31:45.233Z
Learning: When making localization changes for new features, it's often safer to add new strings rather than modify existing ones to avoid breaking existing functionality that depends on the original strings. This approach allows for feature-specific customization while maintaining backward compatibility.

apps/web/components/dialog/ReportBookingDialog.tsx (1)

Learnt from: eunjae-lee
PR: #22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

apps/web/components/booking/BookingListItem.tsx (1)

Learnt from: eunjae-lee
PR: #22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

🧬 Code Graph Analysis (3)
packages/trpc/server/routers/viewer/bookings/_router.tsx (2)
packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts (1)
  • ZReportBookingInputSchema (5-10)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1)
  • reportBookingHandler (18-100)
apps/web/components/booking/bookingActions.report.test.ts (1)
apps/web/components/booking/bookingActions.ts (2)
  • BookingActionContext (6-34)
  • getReportActions (188-201)
apps/web/components/booking/BookingListItem.tsx (1)
apps/web/components/dialog/ReportBookingDialog.tsx (1)
  • ReportBookingDialog (34-146)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Type check / check-types
  • GitHub Check: Tests / Unit
  • GitHub Check: Linters / lint
🔇 Additional comments (25)
packages/trpc/server/routers/viewer/bookings/get.handler.ts (1)

560-573: LGTM! Clean integration of booking reports data.

The jsonArrayFrom subquery correctly fetches associated booking reports with proper field selection and ordering. The descending order by createdAt ensures the most recent reports appear first, which is appropriate for UI display.

apps/web/components/booking/bookingActions.ts (2)

188-201: LGTM! Well-structured report action implementation.

The getReportActions function follows the established pattern in the file, returning a properly structured action with appropriate icon ("flag") and localized label.


255-256: LGTM! Consistent with existing action label handling.

The report case is properly added to the switch statement, maintaining consistency with other action types.

packages/trpc/server/routers/viewer/bookings/_router.tsx (3)

11-11: LGTM! Import follows established pattern.

The schema import is consistent with other input schema imports in the file.


24-24: LGTM! Handler type cache addition follows convention.

The type definition is properly added to the handler cache, maintaining consistency with other handlers.


104-111: LGTM! Procedure implementation follows established patterns.

The reportBooking procedure correctly uses:

  • authedProcedure (appropriate for user-specific operations)
  • mutation (correct for state-changing operations)
  • Dynamic import pattern consistent with other procedures
  • Proper input validation with ZReportBookingInputSchema
packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts (2)

5-10: LGTM! Well-designed schema with appropriate field types.

The schema is thoughtfully designed:

  • bookingId as number correctly matches database ID type
  • reason using native enum ensures type safety
  • description as optional string provides flexibility for additional context
  • cancelBooking with false default prevents accidental cancellations

12-12: LGTM! Standard type inference export.

Type export follows TypeScript/zod conventions for schema inference.

packages/trpc/server/routers/viewer/bookings/reportBooking.test.ts (4)

22-49: LGTM! Well-structured test setup with comprehensive mock data.

The mock data covers all essential booking properties and user scenarios needed for thorough testing. The beforeEach cleanup ensures test isolation.


50-83: LGTM! Comprehensive happy path test.

The test validates both the success response and the correct database interaction, ensuring the handler behaves as expected for valid inputs.


85-98: LGTM! Thorough error case coverage.

The test suite properly validates:

  • Booking not found scenarios
  • Access control enforcement (user must be owner, attendee, or team admin/owner)
  • Duplicate report prevention

All error cases correctly expect TRPCError to be thrown.

Also applies to: 100-125, 127-150


152-179: LGTM! Combined functionality test is well-implemented.

The test validates the integrated reporting and cancellation workflow, ensuring both the report is created and the booking cancellation handler is invoked when requested.

apps/web/components/booking/bookingActions.report.test.ts (3)

1-8: LGTM! Clean test setup with appropriate imports.

The imports are well-organized and the mock translation function is simple but effective for testing purposes.


123-155: Comprehensive test coverage for basic functionality and status variations.

The tests effectively verify that the report action is consistently available across all booking statuses. The data-driven approach for testing multiple statuses is efficient and thorough.


189-253: Thorough coverage for different booking types.

The tests effectively verify that the report action is available for both team and individual bookings, ensuring the feature works regardless of the booking's organizational structure.

The detailed mock data setup, while verbose, clearly demonstrates the different scenarios being tested.

apps/web/components/dialog/ReportBookingDialog.test.tsx (1)

1-155: Comprehensive test coverage!

The test suite thoroughly covers the ReportBookingDialog component with well-structured tests for rendering, user interactions, form submission, and conditional behavior. The mocks are properly configured and the test cases follow best practices.

packages/prisma/migrations/20250723214119_add_booking_report/migration.sql (1)

4-27: Well-structured migration!

The migration properly defines the BookingReport table with appropriate constraints, indexes, and CASCADE rules for maintaining referential integrity.

packages/prisma/schema.prisma (2)

730-749: Well-designed schema additions!

The ReportReason enum and BookingReport model are properly structured with:

  • Appropriate field types and defaults
  • Proper foreign key relationships with CASCADE rules
  • Necessary indexes for query performance
  • Clear mapping for the enum value

423-423: Proper relation setup!

The bidirectional relations between User ↔ BookingReport and Booking ↔ BookingReport are correctly established.

Also applies to: 817-817

apps/web/components/booking/BookingListItem.tsx (6)

55-55: LGTM: Import statement is correct.

The import for ReportBookingDialog follows the established pattern and is properly placed with other dialog imports.


136-136: LGTM: State variable follows established pattern.

The isReportDialogOpen state variable is consistent with other dialog state variables in the component (e.g., isNoShowDialogOpen, rejectionDialogIsOpen).


750-764: LGTM: Cancelled booking report action rendering is well implemented.

The conditional rendering and button properties are correctly implemented:

  • Proper conditional rendering with cancelledBookingReportAction &&
  • Correct event handlers and disabled state
  • Appropriate styling and accessibility with tooltip
  • Good use of data-testid for testing

777-788: LGTM: ReportBookingDialog integration is well implemented.

The dialog integration correctly:

  • Passes all required props including booking details and state flags
  • Handles the onSuccess callback to invalidate cache and refresh data
  • Uses proper state management with isReportDialogOpen
  • Follows the established pattern for other dialogs in the component

831-835: LGTM: Reported badge implementation is correct.

The badge logic correctly:

  • Checks if reports exist and have length > 0
  • Uses appropriate red variant to indicate reporting status
  • Follows the established pattern for other badges
  • Uses proper spacing classes

400-402: No changes needed for report-checking logic

The reports subquery is always selected in the booking resolver (returning an empty array when there are no reports), and BookingReport.reportedById is non-nullable in the Prisma schema. Likewise, loggedInUser.userId is populated from the authenticated session (and guaranteed to be defined by the time this component renders). Your existing optional chaining and fallback to false already cover all edge cases here.

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.

Could you please address the coderabbit suggestions and unit tests are also failing, Marking it draft until then. Feel free to rfr.

@Devanshusharma2005 Devanshusharma2005 marked this pull request as draft July 24, 2025 05:42
@husniabad husniabad force-pushed the feature/report-bookings branch from 0880cc0 to 1020be3 Compare July 24, 2025 07:31
@husniabad
Copy link
Contributor Author

Hi @Devanshusharma2005 all suggestions and changes addressed, could you please check again

@husniabad husniabad marked this pull request as ready for review July 24, 2025 08:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1)

76-97: Improved error handling addresses previous feedback.

The cancellation error handling has been significantly improved from the previous review. The implementation now:

  • Captures cancellation errors properly
  • Provides clear error messages to users
  • Returns error details in the response

This addresses the previous concern about silent cancellation failures.

🧹 Nitpick comments (2)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (2)

22-45: Consider optimizing the database query structure.

While the query is functionally correct, the nested include structure could be optimized for better performance and maintainability.

Consider flattening the query structure and using more specific selections:

  const booking = await prisma.booking.findUnique({
    where: {
      id: bookingId,
    },
    include: {
      attendees: {
        select: {
          email: true,
        },
      },
      eventType: {
        select: {
+         teamId: true,
-         team: {
-           select: {
-             id: true,
-             name: true,
-             parentId: true,
-           },
-         },
        },
      },
      reports: {
        where: {
          reportedById: user.id,
        },
+       select: {
+         id: true,
+       },
      },
    },
  });

This reduces the data transferred and improves query performance by only selecting necessary fields.


62-64: Good duplicate prevention, but consider the user experience.

The logic correctly prevents duplicate reports, but the error message could be more user-friendly.

Consider a more informative error message:

  if (booking.reports.length > 0) {
-   throw new TRPCError({ code: "BAD_REQUEST", message: "You have already reported this booking" });
+   throw new TRPCError({ 
+     code: "BAD_REQUEST", 
+     message: "You have already reported this booking. Multiple reports are not allowed." 
+   });
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0880cc0 and 1020be3.

📒 Files selected for processing (12)
  • apps/web/components/booking/BookingListItem.tsx (6 hunks)
  • apps/web/components/booking/bookingActions.report.test.ts (1 hunks)
  • apps/web/components/booking/bookingActions.ts (2 hunks)
  • apps/web/components/dialog/ReportBookingDialog.test.tsx (1 hunks)
  • apps/web/components/dialog/ReportBookingDialog.tsx (1 hunks)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • packages/prisma/migrations/20250723214119_add_booking_report/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/_router.tsx (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts
  • apps/web/public/static/locales/en/common.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • apps/web/components/booking/bookingActions.report.test.ts
  • apps/web/components/booking/bookingActions.ts
  • packages/trpc/server/routers/viewer/bookings/_router.tsx
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts
  • apps/web/components/dialog/ReportBookingDialog.tsx
  • packages/prisma/schema.prisma
  • apps/web/components/booking/BookingListItem.tsx
  • apps/web/components/dialog/ReportBookingDialog.test.tsx
  • packages/prisma/migrations/20250723214119_add_booking_report/migration.sql
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code. Functions like .add, .diff, .isBefore, and .isAfter are slow, especially in timezone mode. Prefer .utc() for better performance. Where possible, replace with native Date and direct .valueOf() comparisons for faster execution. Recommend using native methods or Day.js .utc() consistently in hot paths like loops.

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
🧠 Learnings (1)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1)

Learnt from: CarinaWolli
PR: #22296
File: packages/lib/bookings/filterHostsBySameRoundRobinHost.ts:41-42
Timestamp: 2025-07-22T11:42:47.592Z
Learning: The filterHostsBySameRoundRobinHost function in packages/lib/bookings/filterHostsBySameRoundRobinHost.ts has a known limitation where it doesn't work correctly with fixed hosts or round robin groups. This is pre-existing technical debt that was already broken before the round robin groups feature. CarinaWolli has documented this in Linear issue CAL-6134 for future fix.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (4)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (4)

1-16: LGTM! Clean imports and type definitions.

The imports are well-organized and the type definitions follow TypeScript best practices. The handler options type properly extends the TRPC pattern.


51-60: Excellent authorization logic with comprehensive access control.

The authorization properly covers all access scenarios:

  • Booking owner access
  • Attendee access
  • Team admin/owner access for team bookings

The implementation correctly handles both team admin and team owner permissions.


99-108: Well-structured response with comprehensive error information.

The response structure properly handles all scenarios and provides clear feedback to the user about both reporting and cancellation outcomes.


78-82: Booking startTime comparison is timezone-safe
The booking.startTime value is normalized to UTC (see handleNewBooking.ts using dayjs(reqBody.start).utc()), and stored/passed as an ISO timestamp. Both new Date(booking.startTime) and new Date() operate on the same UTC‐based millisecond epoch, so the > comparison correctly reflects absolute time regardless of client timezone. No changes needed here.

@husniabad
Copy link
Contributor Author

Hi @kart1ka @emrysal any updates on this PR, is there any conflicts found?

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 few comments

Copy link
Contributor

Choose a reason for hiding this comment

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

Pls add the report booking feature on recurring booking as well.

Image

Copy link
Contributor

Choose a reason for hiding this comment

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

A user should also be able to report an unconfirmed booking.

Image

@husniabad
Copy link
Contributor Author

Hi @CarinaWolli thanks for your valuable feedback!, I fixed the issue by bypassing the new property fromReport: true to handleCancelBooking function and then add the proper exception to not throw an error due to no cancellationReason property bypassed when the cancellation arrived from report booking
Please review it again, I left few comments to explain the new logic, and I am ready for your feedback...

// This handles both individual bookings and recurring series reporting
const checkIfBookingReported = (booking: BookingItemProps): boolean => {
// Try to access the computed field from backend that considers recurring series
const hasBeenReportedByUserField = (booking as any).hasBeenReportedByUser;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const hasBeenReportedByUserField = (booking as any).hasBeenReportedByUser;
const hasBeenReportedByUserField = booking.hasBeenReportedByUser;

don't think we need this here

@kart1ka kart1ka marked this pull request as draft August 20, 2025 16:37
@husniabad husniabad force-pushed the feature/report-bookings branch from 7b99439 to 076cb4b Compare August 22, 2025 01:58
@husniabad husniabad requested a review from CarinaWolli August 22, 2025 02:06
@husniabad husniabad marked this pull request as ready for review August 22, 2025 02:06
@husniabad
Copy link
Contributor Author

Hi @CarinaWolli and @kart1ka I have simplified the logic as you requested and addressed other changes
can you please review

@@ -818,6 +840,7 @@ model Booking {
tracking Tracking?
routingFormResponses RoutingFormResponseDenormalized[]
expenseLogs CreditExpenseLog[]
reports BookingReport[]
Copy link
Member

Choose a reason for hiding this comment

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

I think the better approach would be to allow only booking report by booking

// Utility function to check if a booking has been reported by the current user
const checkIfBookingReported = (booking: BookingItemProps): boolean => {
// Check if this booking has been reported by the current user
return booking.reports?.some((report) => report.reportedById === booking.loggedInUser.userId) || false;
Copy link
Member

Choose a reason for hiding this comment

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

let's show the "reported" badge always, also when another user reported it

return (
hasAnyReport && (
<Badge variant="red" className="ltr:mr-2 rtl:ml-2">
{t("reported")}
Copy link
Member

Choose a reason for hiding this comment

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

Can we add the report reason to the badgd. Like that: Reported: Spam

Comment on lines 51 to 53
where: {
reportedById: user.id,
},
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
where: {
reportedById: user.id,
},

We would need to remove this then too

})),
});

const bookingReport = { id: reportedBookingIds[0] }; // For backward compatibility
Copy link
Member

Choose a reason for hiding this comment

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

why "backward compatibility"?

bookingReport is misleading as it's the bookingId not reportId. ReportedBooking would be better

: cancelBooking
? `${baseMessage} and cancelled successfully`
: `${baseMessage} successfully`,
reportId: bookingReport.id,
Copy link
Member

Choose a reason for hiding this comment

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

bookingReport.id is the booking id and not the report

}

// For team admins, also check if they can see this booking through team membership
// This matches the logic in get.handler.ts where team admins can see bookings from their team members
Copy link
Member

Choose a reason for hiding this comment

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

Can we reuse the code then instead of copy/pasting it?

@husniabad husniabad force-pushed the feature/report-bookings branch from 076cb4b to d354d23 Compare August 24, 2025 00:35
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/web/components/booking/BookingListItem.tsx (1)

855-867: Localize “Unknown” and guard reason key mapping.

Hardcoded “Unknown” violates the i18n guideline for TSX. Also, ensure the reason key exists before lowercasing.

-          hasAnyReport && (
-            <Badge variant="red" className="ltr:mr-2 rtl:ml-2">
-              {t("reported")}:{" "}
-              {booking.reports?.[0]?.reason ? t(booking.reports?.[0]?.reason?.toLowerCase()) : "Unknown"}
-            </Badge>
-          )
+          hasAnyReport && (
+            <Badge variant="red" className="ltr:mr-2 rtl:ml-2">
+              {t("reported")}:{" "}
+              {typeof booking.reports?.[0]?.reason === "string"
+                ? t(booking.reports[0].reason.toLowerCase())
+                : t("unknown")}
+            </Badge>
+          )

Ensure the unknown key exists in locales.

🧹 Nitpick comments (10)
apps/web/components/dialog/ReportBookingDialog.tsx (2)

38-41: Remove unused prop to reduce API surface (or use it in the UI).

bookingTitle is accepted but unused (bookingTitle: _bookingTitle). Either display it (e.g., in the dialog header/subtitle) or drop it from props.

-  bookingTitle: string;
+  // Remove if not used
+  // bookingTitle: string;

If you keep it, consider showing it under the title for context: “Report booking: {bookingTitle}”.


104-107: Use explicit required rule for react-hook-form for clarity.

Passing required: t("reason_required") is valid. Using the object form improves readability and type clarity.

-              rules={{ required: t("reason_required") }}
+              rules={{ required: { value: true, message: t("reason_required") } }}
apps/web/components/booking/bookingActions.report.test.ts (3)

125-136: Add test coverage for “already reported” (disabled) state.

The spec requires preventing re-reporting. Add a case where the booking has existing reports and assert the action is disabled or the label reflects “already_reported”.

Example (if getReportActions is enhanced to respect reported state via context):

   describe("getReportActions", () => {
+    it("should disable report action if already reported", () => {
+      const context = createMockContext({
+        booking: { reports: [{ id: 1 }] } as any,
+      });
+      const actions = getReportActions(context);
+      expect(actions).toHaveLength(1);
+      expect(actions[0].id).toBe("report");
+      expect(actions[0].disabled).toBe(true);
+    });

Note: Today getReportActions ignores reported state; either wire it up or move tests to where the logic lives (BookingListItem).


139-155: Broaden scenarios: cancelled and unconfirmed-specific expectations.

You test various statuses generically. Add explicit cases for:

  • Cancelled bookings (ensure action is present and later rendered inline in UI).
  • Unconfirmed bookings (ensure presence too).

This documents the intended behavior across tabs and prevents regressions.


9-13: Stabilize test time references to avoid flakiness.

Tests compute “tomorrow”/“yesterday” based on Date.now(). When crossing midnight on CI, assertions can flicker. Freeze time with vi.setSystemTime(new Date("2025-01-01T12:00:00Z")) in a beforeAll and restore in afterAll.

Also applies to: 14-41

apps/web/components/booking/BookingListItem.tsx (5)

86-90: Recurring-series awareness: reporting check only inspects the single booking.

checkIfBookingReported uses booking.reports.length only. If a report was filed on another occurrence and the intended UX is to show “Reported” for the whole series, this won’t reflect that unless the server populates series-level reports onto each item.

  • If the server augments each occurrence with a series flag (e.g., booking.hasBeenReportedByUser or booking.seriesReported), use that instead.
  • Otherwise, consider extending the query to return “seriesHasReports” when recurringEventId != null, or pass a prop computed at the list level.

I can generate a small PR to thread a seriesHasReports boolean through the handler and UI; let me know.


409-429: Deduplicate action construction by centralizing in getReportActions(context).

You re-implement report action logic locally. We already expose getReportActions(context) in bookingActions.ts. Move the “already reported” disabling and labels into that function and reuse it here to keep behavior consistent in all consumers.

Proposed shape for getReportActions (outside this file):

export function getReportActions(context: BookingActionContext): ActionType[] {
  const { t, booking } = context;
  const hasBeenReported = !!booking.reports?.length;
  return [
    hasBeenReported
      ? { id: "report", label: t("already_reported"), icon: "flag", disabled: true }
      : { id: "report", label: t("report"), icon: "flag", disabled: false },
  ];
}

Then, here you can map to either inline or menu presentation:

-const individualReportAction: ActionType | null = shouldShowIndividualReportButton
-  ? createReportAction(hasBeenReported, () => setIsReportDialogOpen(true))
-  : null;
+const [baseReportAction] = getReportActions(actionContext);
+const individualReportAction: ActionType | null =
+  shouldShowIndividualReportButton
+    ? { ...baseReportAction, onClick: () => setIsReportDialogOpen(true) }
+    : null;

-const reportActionForMenu: ActionType | null = shouldShowReportInThreeDotsMenu
-  ? createReportAction(hasBeenReported, () => setIsReportDialogOpen(true))
-  : null;
+const reportActionForMenu: ActionType | null =
+  shouldShowReportInThreeDotsMenu
+    ? { ...baseReportAction, onClick: () => setIsReportDialogOpen(true) }
+    : null;

744-759: Minor accessibility: menu item should be a button, not a link, when no href.

DropdownItem supports type="button" (you set it) but it renders interactive content. Ensure it doesn’t render an anchor without href (component-dependent). If it does, prefer a button to avoid href="#" anti-pattern.


765-779: Tooltip is localized, good. Consider aria-label for icon-only action.

The inline report button is icon-only. You pass tooltip, but for screen readers include aria-label={individualReportAction.label} to improve accessibility.

-                <Button
+                <Button
                   type="button"
                   variant="icon"
                   color="secondary"
                   StartIcon={individualReportAction.icon}
                   onClick={individualReportAction.onClick}
                   disabled={individualReportAction.disabled}
                   data-testid={individualReportAction.id}
                   className="h-8 w-8"
-                  tooltip={individualReportAction.label}
+                  tooltip={individualReportAction.label}
+                  aria-label={individualReportAction.label}
                 />

798-811: Avoid invalidating the entire tRPC cache; scope invalidation to bookings.

utils.invalidate() nukes all caches and can cause unnecessary network traffic/UI jank. Invalidate only the relevant queries.

         onSuccess={async () => {
           // Invalidate all booking queries to ensure UI reflects the changes
           await utils.viewer.bookings.invalidate();
-          // Also invalidate any cached booking data
-          await utils.invalidate();
+          // If needed, also invalidate specific detail queries, e.g.:
+          // await utils.viewer.bookings.get.invalidate();
         }}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7b99439 and d354d23.

📒 Files selected for processing (6)
  • apps/web/components/booking/BookingListItem.tsx (8 hunks)
  • apps/web/components/booking/bookingActions.report.test.ts (1 hunks)
  • apps/web/components/booking/bookingActions.ts (2 hunks)
  • apps/web/components/dialog/ReportBookingDialog.test.tsx (1 hunks)
  • apps/web/components/dialog/ReportBookingDialog.tsx (1 hunks)
  • apps/web/public/static/locales/en/common.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/components/booking/bookingActions.ts
  • apps/web/public/static/locales/en/common.json
  • apps/web/components/dialog/ReportBookingDialog.test.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • apps/web/components/booking/bookingActions.report.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/web/components/booking/bookingActions.report.test.ts
  • apps/web/components/dialog/ReportBookingDialog.tsx
  • apps/web/components/booking/BookingListItem.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • apps/web/components/booking/bookingActions.report.test.ts
  • apps/web/components/dialog/ReportBookingDialog.tsx
  • apps/web/components/booking/BookingListItem.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/components/dialog/ReportBookingDialog.tsx
  • apps/web/components/booking/BookingListItem.tsx
🧠 Learnings (4)
📚 Learning: 2025-07-15T12:59:34.389Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In BookingPaymentInitiatedDTO and other webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking field is a restricted structure containing only specific fields (id, eventTypeId, userId) rather than the full database booking object, so there are no security or PII leakage concerns when passing the booking object to buildEventPayload.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking fields are restricted structures containing only specific fields (id, eventTypeId, userId, and sometimes additional fields like startTime or smsReminderNumber) rather than full database booking objects, so there are no security or PII leakage concerns when using these booking objects in webhook payloads.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
🧬 Code graph analysis (3)
apps/web/components/booking/bookingActions.report.test.ts (2)
apps/web/components/booking/bookingActions.ts (2)
  • BookingActionContext (6-34)
  • getReportActions (189-202)
packages/platform/libraries/index.ts (1)
  • SchedulingType (30-30)
apps/web/components/dialog/ReportBookingDialog.tsx (2)
apps/web/app/_trpc/trpc.ts (1)
  • trpc (7-7)
packages/ui/components/form/index.ts (1)
  • Form (23-23)
apps/web/components/booking/BookingListItem.tsx (3)
apps/web/components/booking/bookingActions.ts (2)
  • shouldShowEditActions (209-212)
  • shouldShowRecurringCancelAction (214-217)
packages/ui/components/dropdown/Dropdown.tsx (5)
  • DropdownMenuItem (63-71)
  • DropdownItem (161-181)
  • DropdownMenuContent (34-51)
  • DropdownMenuPortal (31-31)
  • Dropdown (12-12)
apps/web/components/dialog/ReportBookingDialog.tsx (1)
  • ReportBookingDialog (34-156)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Tests / Unit
  • GitHub Check: Linters / lint
  • GitHub Check: Type check / check-types
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (1)
apps/web/components/booking/BookingListItem.tsx (1)

430-434: Verify UX rule: placement of Report action across tabs/states.

  • You show inline button for recurring tab, pending, and cancelled; menu otherwise.
  • PR objective: dropdown for upcoming/past; inline next to “Video Options” for cancelled.

Pending/unconfirmed inline wasn’t in the initial spec but was requested in comments. Confirm this is the final desired UX and that it doesn’t crowd the header in dense lists.

@husniabad husniabad force-pushed the feature/report-bookings branch from d354d23 to 58ca42a Compare August 24, 2025 01:08
@husniabad husniabad requested a review from CarinaWolli August 24, 2025 01:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/components/booking/BookingListItem.tsx (1)

413-442: Nice extraction of a reusable report action factory.

This addresses the earlier DRY comment around duplicated “report” action construction for different contexts.

🧹 Nitpick comments (5)
apps/web/components/booking/BookingListItem.tsx (3)

855-867: Localize the “Unknown” fallback in the Reported badge.

Direct string breaks i18n. Use t("unknown") (or add it if missing).

-              {t("reported")}:{" "}
-              {booking.reports?.[0]?.reason ? t(booking.reports?.[0]?.reason?.toLowerCase()) : "Unknown"}
+              {t("reported")}:{" "}
+              {booking.reports?.[0]?.reason
+                ? t(booking.reports?.[0]?.reason?.toLowerCase())
+                : t("unknown")}

765-779: Add an accessible name to the icon-only “Report” button.

Provide aria-label so screen readers can announce the action.

 <Button
   type="button"
   variant="icon"
   color="secondary"
   StartIcon={individualReportAction.icon}
   onClick={individualReportAction.onClick}
   disabled={individualReportAction.disabled}
   data-testid={individualReportAction.id}
   className="h-8 w-8"
-  tooltip={individualReportAction.label}
+  tooltip={individualReportAction.label}
+  aria-label={individualReportAction.label}
 />

798-812: Scope cache invalidation to bookings to avoid unnecessary refetches.

utils.invalidate() nukes all TRPC caches and can cause extra network/CPU churn. In most cases, utils.viewer.bookings.invalidate() is sufficient.

         onSuccess={async () => {
           // Invalidate all booking queries to ensure UI reflects the changes
           await utils.viewer.bookings.invalidate();
-          // Also invalidate any cached booking data
-          await utils.invalidate();
         }}
packages/prisma/schema.prisma (1)

758-777: Drop redundant non-unique index on bookingId; keep the unique one.

You already have bookingId marked @unique which creates a unique index. The additional @@index([bookingId]) is redundant overhead.

 model BookingReport {
   id           String       @id @default(uuid())
   booking      Booking      @relation(fields: [bookingId], references: [id], onDelete: Cascade)
-  bookingId    Int          @unique
+  bookingId    Int          @unique
   reportedBy   User         @relation(fields: [reportedById], references: [id], onDelete: Cascade)
   reportedById Int
   reason       ReportReason
   description  String?
   cancelled    Boolean      @default(false)
   createdAt    DateTime     @default(now())

-  @@index([bookingId])
   @@index([reportedById])
 }

Optional: if you expect sorting/filtering by recency, consider @@index([createdAt]).

packages/prisma/migrations/20250823231324_add_booking_report/migration.sql (1)

17-24: Remove redundant non-unique index on bookingId (unique already exists).

BookingReport_bookingId_key creates a unique index; BookingReport_bookingId_idx duplicates it and can be dropped.

 -- CreateIndex
-CREATE UNIQUE INDEX "BookingReport_bookingId_key" ON "BookingReport"("bookingId");
+CREATE UNIQUE INDEX "BookingReport_bookingId_key" ON "BookingReport"("bookingId");

 -- CreateIndex
-CREATE INDEX "BookingReport_bookingId_idx" ON "BookingReport"("bookingId");
+-- (removed redundant non-unique index on bookingId)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d354d23 and 58ca42a.

📒 Files selected for processing (7)
  • apps/web/components/booking/BookingListItem.tsx (8 hunks)
  • apps/web/components/dialog/ReportBookingDialog.tsx (1 hunks)
  • packages/lib/server/queries/teams/index.ts (1 hunks)
  • packages/prisma/migrations/20250823231324_add_booking_report/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts
  • apps/web/components/dialog/ReportBookingDialog.tsx
  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/server/queries/teams/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/server/queries/teams/index.ts
  • apps/web/components/booking/BookingListItem.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/server/queries/teams/index.ts
  • apps/web/components/booking/BookingListItem.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/components/booking/BookingListItem.tsx
🧠 Learnings (5)
📚 Learning: 2025-08-19T09:47:49.478Z
Learnt from: eunjae-lee
PR: calcom/cal.com#23166
File: packages/prisma/migrations/20250818151914_routing_form_response_denormalized_backfill2/migration.sql:65-66
Timestamp: 2025-08-19T09:47:49.478Z
Learning: The Booking table has a unique constraint and index on the uid column (defined as `uid String unique` in schema.prisma), so JOINs on Booking.uid have optimal performance and don't require additional indexing.

Applied to files:

  • packages/prisma/schema.prisma
📚 Learning: 2025-07-15T12:59:34.389Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In BookingPaymentInitiatedDTO and other webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking field is a restricted structure containing only specific fields (id, eventTypeId, userId) rather than the full database booking object, so there are no security or PII leakage concerns when passing the booking object to buildEventPayload.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking fields are restricted structures containing only specific fields (id, eventTypeId, userId, and sometimes additional fields like startTime or smsReminderNumber) rather than full database booking objects, so there are no security or PII leakage concerns when using these booking objects in webhook payloads.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
🧬 Code graph analysis (1)
apps/web/components/booking/BookingListItem.tsx (3)
apps/web/components/booking/bookingActions.ts (1)
  • shouldShowEditActions (209-212)
packages/ui/components/dropdown/Dropdown.tsx (5)
  • DropdownMenuItem (63-71)
  • DropdownItem (161-181)
  • DropdownMenuContent (34-51)
  • DropdownMenuPortal (31-31)
  • Dropdown (12-12)
apps/web/components/dialog/ReportBookingDialog.tsx (1)
  • ReportBookingDialog (34-163)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Tests / Unit
  • GitHub Check: Type check / check-types
  • GitHub Check: Linters / lint
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (2)
apps/web/components/booking/BookingListItem.tsx (1)

86-90: Verify reports.reason is selected in bookings.get

I didn’t see any reports field being selected in the TRPC “bookings.get” handler (packages/trpc/server/routers/viewer/bookings/get.handler.ts), yet the list component expects booking.reports?.[0]?.reason. Please manually confirm that your database query includes exactly:

select: {
  reports: {
    select: { reason: true }
  },
  // …other needed fields
}

and that you’re not using an include: { reports: true } (which pulls back full report objects).
If it’s missing or currently included in full, update the handler’s query to use a minimal select on reports.reason so you only fetch the data the UI needs.

packages/prisma/schema.prisma (1)

845-846: Relation looks good. One-report-per-booking invariant enforced at the DB level.

The reports: BookingReport[] relation on Booking aligns with the UI/handler behavior, and the unique (bookingId) enforces the “only one report per booking” product decision.

@husniabad husniabad force-pushed the feature/report-bookings branch from 58ca42a to b3821b1 Compare August 24, 2025 01:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (10)
apps/web/components/booking/BookingListItem.tsx (5)

409-412: Simplify: avoid useMemo here.

The check is O(1) and unlikely to cause re-renders on its own. Removing useMemo reduces noise.

Apply:

-const hasBeenReported = useMemo(() => {
-  return checkIfBookingReported(booking);
-}, [booking]);
+const hasBeenReported = Boolean(booking.reports?.length);

765-779: Add an accessible name to the icon-only button.

Relying on a tooltip may not set an aria-label. Use the action label for screen readers.

 <Button
   type="button"
   variant="icon"
   color="secondary"
   StartIcon={individualReportAction.icon}
   onClick={individualReportAction.onClick}
   disabled={individualReportAction.disabled}
   data-testid={individualReportAction.id}
   className="h-8 w-8"
-  tooltip={individualReportAction.label}
+  tooltip={individualReportAction.label}
+  aria-label={individualReportAction.label}
 />

798-811: Avoid double invalidation to reduce unnecessary refetches.

utils.viewer.bookings.invalidate() is sufficient; utils.invalidate() purges everything and can be costly.

         onSuccess={async () => {
           // Invalidate all booking queries to ensure UI reflects the changes
           await utils.viewer.bookings.invalidate();
-          // Also invalidate any cached booking data
-          await utils.invalidate();
         }}

855-867: i18n: remove hardcoded “Unknown” and avoid brittle toLowerCase mapping for reason.

  • Hardcoded “Unknown” violates our i18n guideline; use t().
  • Mapping reason via toLowerCase assumes enum string shapes. Use an explicit map for stability.

Apply within this block:

-        return (
-          hasAnyReport && (
-            <Badge variant="red" className="ltr:mr-2 rtl:ml-2">
-              {t("reported")}:{" "}
-              {booking.reports?.[0]?.reason ? t(booking.reports?.[0]?.reason?.toLowerCase()) : "Unknown"}
-            </Badge>
-          )
-        );
+        const reason = booking.reports?.[0]?.reason;
+        const reasonKeyMap: Record<string, string> = {
+          SPAM: "spam",
+          DONT_KNOW_THIS_PERSON: "dont_know_this_person",
+          OTHER: "other",
+        };
+        const reasonKey = reason ? reasonKeyMap[reason] : undefined;
+        return (
+          hasAnyReport && (
+            <Badge variant="red" className="ltr:mr-2 rtl:ml-2">
+              {t("reported")}: {reasonKey ? t(reasonKey) : t("unknown")}
+            </Badge>
+          )
+        );

Outside this range, ensure we have a translation key for “unknown” in common.json (or reuse an existing one). If you prefer strong typing, import ReportReason and type reasonKeyMap as Record<ReportReason, string>.

Would you like me to push a follow-up to add the “unknown” key if it’s missing in locales?


86-91: Clarify booking-vs-series reporting and tighten the Boolean check

After digging into the backend handler, each booking’s reports array is populated only with reports whose bookingId matches that booking’s own ID—there’s no series-level aggregation today. We should therefore update our comments and utility to reflect “this specific booking” rather than implying “any booking in the recurring series.” While this is non-blocking, it will prevent future confusion if series-wide reporting ever becomes a feature.

• File: apps/web/components/booking/BookingListItem.tsx
– Lines 86–91: update the utility’s comment and return statement
– Line 857: revise the inline usage comment that currently reads
“Check if this booking or any booking in the recurring series has been reported”

Suggested diff:

--- a/apps/web/components/booking/BookingListItem.tsx
+++ b/apps/web/components/booking/BookingListItem.tsx
@@ -86,9 +86,7 @@
 // Utility function to check if a booking has been reported
-const checkIfBookingReported = (booking: BookingItemProps): boolean => {
-  // Check if this booking has been reported by anyone
-  return booking.reports && booking.reports.length > 0;
-};
+// Utility: has this specific booking been reported?
+const checkIfBookingReported = (booking: BookingItemProps): boolean =>
+  Boolean(booking.reports?.length);

@@ -855,7 +853,7 @@
-       // Check if this booking or any booking in the recurring series has been reported
-        const hasAnyReport = checkIfBookingReported(booking);
+        // Check if this specific booking has been reported
+        const hasAnyReport = checkIfBookingReported(booking);
packages/lib/server/queries/teams/index.ts (1)

441-446: Remove unnecessary type assertions on organizationId

Use a simple null check to avoid the as number casts and keep the where clause cleaner.

Apply this diff:

-      ...((organizationId ?? null) !== null
-        ? {
-            OR: [{ teamId: organizationId as number }, { team: { parentId: organizationId as number } }],
-          }
-        : {}),
+      ...(organizationId != null
+        ? {
+            OR: [{ teamId: organizationId }, { team: { parentId: organizationId } }],
+          }
+        : {}),
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (4)

72-75: Drop redundant isTeamOwner() call

isTeamAdmin() already checks for role in ["ADMIN", "OWNER"]. The extra isTeamOwner() call doubles the queries with no benefit.

Apply this diff:

-    isTeamAdminOrOwner =
-      !!(await isTeamAdmin(user.id, booking.eventType.teamId)) ||
-      (await isTeamOwner(user.id, booking.eventType.teamId));
+    isTeamAdminOrOwner = !!(await isTeamAdmin(user.id, booking.eventType.teamId));

26-60: Avoid N+1 by selecting owner email with the booking

You fire a separate query to fetch the booking owner’s email. Select it in the initial booking query instead and remove the extra roundtrip.

Apply this diff:

     select: {
       id: true,
       userId: true,
+      user: { select: { email: true } },
       uid: true,
       startTime: true,
       status: true,
       recurringEventId: true,
-    let isBookingOwnerTeamMember = false;
-    if (booking.userId) {
-      const bookingOwner = await prisma.user.findUnique({
-        where: { id: booking.userId },
-        select: { email: true },
-      });
-      isBookingOwnerTeamMember = !!(bookingOwner && teamMemberEmailList.includes(bookingOwner.email));
-    }
+    const isBookingOwnerTeamMember =
+      !!booking.user?.email && teamMemberEmailList.includes(booking.user.email);

Also applies to: 118-124


139-145: Tighten query to existence only

You only need to know if any report exists for the series. Select just the id to minimize payload.

Apply this diff:

   const existingRecurringReport = await prisma.bookingReport.findFirst({
     where: {
       booking: {
         recurringEventId: booking.recurringEventId,
       },
     },
+    select: { id: true },
   });

203-205: Use the shared logger instead of console.error

Align with the codebase’s logging pattern and add context.

Apply this diff:

-      cancellationError = error;
-      console.error("Failed to cancel booking after reporting:", error);
+      cancellationError = error;
+      // import logger at top: import logger from "@calcom/lib/logger";
+      logger.error(
+        { bookingId: booking.id, uid: booking.uid, userId: user.id, error },
+        "Failed to cancel booking after reporting"
+      );
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 58ca42a and b3821b1.

📒 Files selected for processing (7)
  • apps/web/components/booking/BookingListItem.tsx (8 hunks)
  • apps/web/components/dialog/ReportBookingDialog.tsx (1 hunks)
  • packages/lib/server/queries/teams/index.ts (1 hunks)
  • packages/prisma/migrations/20250823231324_add_booking_report/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts
  • packages/prisma/migrations/20250823231324_add_booking_report/migration.sql
  • apps/web/components/dialog/ReportBookingDialog.tsx
  • packages/prisma/schema.prisma
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
  • packages/lib/server/queries/teams/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
  • packages/lib/server/queries/teams/index.ts
  • apps/web/components/booking/BookingListItem.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
  • packages/lib/server/queries/teams/index.ts
  • apps/web/components/booking/BookingListItem.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/components/booking/BookingListItem.tsx
🧠 Learnings (6)
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.ts : For Prisma queries, only select data you need; never use `include`, always use `select`

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
📚 Learning: 2025-08-22T16:38:00.182Z
Learnt from: bandhan-majumder
PR: calcom/cal.com#23192
File: packages/lib/server/service/InsightsBookingBaseService.ts:814-816
Timestamp: 2025-08-22T16:38:00.182Z
Learning: In the InsightsBookingBaseService (packages/lib/server/service/InsightsBookingBaseService.ts), when filtering for "accepted" bookings in getMembersStatsWithCount(), using `endTime <= now()` in the SQL condition should be avoided as it conflicts with existing date filtering logic. The components already handle completion filtering by setting `endDate: currentTime` in their query parameters, making additional SQL-level endTime filtering unnecessary and potentially problematic.

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
📚 Learning: 2025-07-15T12:59:34.389Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In BookingPaymentInitiatedDTO and other webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking field is a restricted structure containing only specific fields (id, eventTypeId, userId) rather than the full database booking object, so there are no security or PII leakage concerns when passing the booking object to buildEventPayload.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking fields are restricted structures containing only specific fields (id, eventTypeId, userId, and sometimes additional fields like startTime or smsReminderNumber) rather than full database booking objects, so there are no security or PII leakage concerns when using these booking objects in webhook payloads.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
🧬 Code graph analysis (2)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (2)
packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts (1)
  • TReportBookingInputSchema (12-12)
packages/lib/server/queries/teams/index.ts (3)
  • isTeamAdmin (387-407)
  • isTeamOwner (409-418)
  • getMembershipIdsWhereUserIsAdminOwner (430-452)
apps/web/components/booking/BookingListItem.tsx (2)
apps/web/components/booking/bookingActions.ts (2)
  • shouldShowEditActions (209-212)
  • shouldShowRecurringCancelAction (214-217)
apps/web/components/dialog/ReportBookingDialog.tsx (1)
  • ReportBookingDialog (34-163)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (8)
apps/web/components/booking/BookingListItem.tsx (6)

3-3: LGTM: useMemo import is appropriate.

Imported useMemo is used below for hasBeenReported; no concerns.


55-55: Good: ReportBookingDialog imported in the right layer.

Import location and usage align with our dialog composition pattern.


141-141: State wiring for report dialog looks good.

Simple and consistent with other dialog states in this component.


413-429: Nice DRY extraction for report actions.

This addresses earlier duplication and keeps the menu/inline actions in sync.


430-441: Visibility logic matches the product requirements; please re-verify pending/recurring UX.

  • Inline button for pending and cancelled bookings aligns with “unconfirmed” and “cancelled” visibility.
  • Menu item for standard upcoming/past bookings is covered via shouldShowReportInThreeDotsMenu.

Quick sanity checks to run in Storybook/E2E:

  • Unconfirmed booking: inline “Report” icon is visible and clickable.
  • Cancelled booking: inline “Report” icon is visible (as per designs “next to Video Options”).
  • Accepted upcoming/past: “Report” appears in the three-dots menu.

If you want, I can add Playwright tests for these cases.


744-759: Menu integration looks correct and respects disabled state.

Placement after the cancel block with a separator reads well in the menu grouping.

packages/lib/server/queries/teams/index.ts (1)

430-452: LGTM: accepted-membership filter and explicit orgId guard are correct

Includes accepted: true and avoids a truthy check on organizationId. This closes the privilege leakage risk and edge cases with nullish org IDs.

packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1)

132-136: Single-booking re-report prevention looks good

The any-report check matches the “cannot be reported again” requirement. No action needed.

@husniabad husniabad force-pushed the feature/report-bookings branch from b3821b1 to fc8e83e Compare August 24, 2025 01:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1)

155-171: Bug: recurring selection reports entire series, not “remaining occurrences”

The requirement is to report remaining occurrences from the selected instance onward. Current query omits the time filter and will also report past occurrences.

Apply this fix and keep results deterministic:

-  if (booking.recurringEventId) {
-    // Get all future bookings in the recurring series
-    const futureRecurringBookings = await prisma.booking.findMany({
-      where: {
-        recurringEventId: booking.recurringEventId,
-      },
-      select: {
-        id: true,
-      },
-    });
-
-    reportedBookingIds = futureRecurringBookings.map((b) => b.id);
-  }
+  if (booking.recurringEventId) {
+    // Get all remaining bookings in the series from this occurrence onward
+    const remainingRecurringBookings = await prisma.booking.findMany({
+      where: {
+        recurringEventId: booking.recurringEventId,
+        startTime: { gte: booking.startTime },
+      },
+      select: { id: true },
+      orderBy: { startTime: "asc" },
+    });
+    reportedBookingIds = remainingRecurringBookings.map((b) => b.id);
+  }
🧹 Nitpick comments (5)
packages/prisma/schema.prisma (1)

764-777: One-report-per-booking enforced; drop redundant index on bookingId

  • bookingId @unique correctly enforces “one report per booking” (as discussed in review threads).
  • @@index([bookingId]) is redundant since a unique index already exists; remove it to avoid duplicate indexes.

Apply this diff:

 model BookingReport {
   id           String       @id @default(uuid())
   booking      Booking      @relation(fields: [bookingId], references: [id], onDelete: Cascade)
   bookingId    Int          @unique
   reportedBy   User         @relation(fields: [reportedById], references: [id], onDelete: Cascade)
   reportedById Int
   reason       ReportReason
   description  String?
   cancelled    Boolean      @default(false)
   createdAt    DateTime     @default(now())

-  @@index([bookingId])
   @@index([reportedById])
 }
apps/web/components/booking/BookingListItem.tsx (3)

86-91: Helper is fine; nit: simplify boolean

The check is correct and side-effect free. Optional: coerce to boolean for readability.

Apply this diff:

-const checkIfBookingReported = (booking: BookingItemProps): boolean => {
-  // Check if this booking has been reported by anyone
-  return booking.reports && booking.reports.length > 0;
-};
+const checkIfBookingReported = (booking: BookingItemProps): boolean =>
+  !!(booking.reports && booking.reports.length > 0);

430-442: Visibility rules: confirm intended UX for “Unconfirmed” tab

  • Inline button shown for recurring tab items, Pending, or Cancelled.
  • For “Unconfirmed” tab items with status AWAITING_HOST (not PENDING), action moves to the menu.

If design wants the inline report button to show for all “Unconfirmed” tab items (including AWAITING_HOST), expand the predicate to include isTabUnconfirmed.

Apply this minimal tweak if needed:

-const shouldShowIndividualReportButton = isTabRecurring || isPending || isCancelled;
+const shouldShowIndividualReportButton = isTabRecurring || isTabUnconfirmed || isPending || isCancelled;

798-812: Avoid over-invalidating cache

utils.viewer.bookings.invalidate() is sufficient to reflect reporting changes on list items. utils.invalidate() is heavy and can regress performance.

Apply this diff:

 onSuccess={async () => {
   // Invalidate all booking queries to ensure UI reflects the changes
   await utils.viewer.bookings.invalidate();
-  // Also invalidate any cached booking data
-  await utils.invalidate();
 }}
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1)

186-207: Cancellation integration LGTM; optional: gate on created reports

Minor consideration: if another concurrent request already reported (createMany.count = 0), you may still proceed to cancel here. If product expects cancellation to occur only when a report is actually recorded by this request, gate on createdCount > 0 before calling handleCancelBooking.

-  if (
+  if (
     cancelBooking &&
     (booking.status === BookingStatus.ACCEPTED || booking.status === BookingStatus.PENDING) &&
     new Date(booking.startTime) > new Date()
   ) {
+    // Optional: only cancel if this request created at least one report
+    // if (createResult.count === 0) { /* skip cancel */ } else { ... }
     try {
       await handleCancelBooking({
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b3821b1 and fc8e83e.

📒 Files selected for processing (7)
  • apps/web/components/booking/BookingListItem.tsx (8 hunks)
  • apps/web/components/dialog/ReportBookingDialog.tsx (1 hunks)
  • packages/lib/server/queries/teams/index.ts (1 hunks)
  • packages/prisma/migrations/20250823231324_add_booking_report/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/lib/server/queries/teams/index.ts
  • apps/web/components/dialog/ReportBookingDialog.tsx
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts
  • packages/prisma/migrations/20250823231324_add_booking_report/migration.sql
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
  • apps/web/components/booking/BookingListItem.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
  • apps/web/components/booking/BookingListItem.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • apps/web/components/booking/BookingListItem.tsx
🧠 Learnings (7)
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.ts : For Prisma queries, only select data you need; never use `include`, always use `select`

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
📚 Learning: 2025-08-22T16:38:00.182Z
Learnt from: bandhan-majumder
PR: calcom/cal.com#23192
File: packages/lib/server/service/InsightsBookingBaseService.ts:814-816
Timestamp: 2025-08-22T16:38:00.182Z
Learning: In the InsightsBookingBaseService (packages/lib/server/service/InsightsBookingBaseService.ts), when filtering for "accepted" bookings in getMembersStatsWithCount(), using `endTime <= now()` in the SQL condition should be avoided as it conflicts with existing date filtering logic. The components already handle completion filtering by setting `endDate: currentTime` in their query parameters, making additional SQL-level endTime filtering unnecessary and potentially problematic.

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
📚 Learning: 2025-08-19T09:47:49.478Z
Learnt from: eunjae-lee
PR: calcom/cal.com#23166
File: packages/prisma/migrations/20250818151914_routing_form_response_denormalized_backfill2/migration.sql:65-66
Timestamp: 2025-08-19T09:47:49.478Z
Learning: The Booking table has a unique constraint and index on the uid column (defined as `uid String unique` in schema.prisma), so JOINs on Booking.uid have optimal performance and don't require additional indexing.

Applied to files:

  • packages/prisma/schema.prisma
📚 Learning: 2025-07-15T12:59:34.389Z
Learnt from: eunjae-lee
PR: calcom/cal.com#22106
File: packages/features/insights/components/FailedBookingsByField.tsx:65-71
Timestamp: 2025-07-15T12:59:34.389Z
Learning: In the FailedBookingsByField component (packages/features/insights/components/FailedBookingsByField.tsx), although routingFormId is typed as optional in useInsightsParameters, the system automatically enforces a routing form filter, so routingFormId is always present in practice. This means the data always contains only one entry, making the single-entry destructuring approach safe.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In BookingPaymentInitiatedDTO and other webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking field is a restricted structure containing only specific fields (id, eventTypeId, userId) rather than the full database booking object, so there are no security or PII leakage concerns when passing the booking object to buildEventPayload.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
📚 Learning: 2025-08-21T12:28:42.000Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#23247
File: packages/features/webhooks/lib/factory/WebhookPayloadFactory.ts:274-282
Timestamp: 2025-08-21T12:28:42.000Z
Learning: In webhook DTOs in packages/features/webhooks/lib/dto/types.ts, the booking fields are restricted structures containing only specific fields (id, eventTypeId, userId, and sometimes additional fields like startTime or smsReminderNumber) rather than full database booking objects, so there are no security or PII leakage concerns when using these booking objects in webhook payloads.

Applied to files:

  • apps/web/components/booking/BookingListItem.tsx
🧬 Code graph analysis (2)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (2)
packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts (1)
  • TReportBookingInputSchema (12-12)
packages/lib/server/queries/teams/index.ts (3)
  • isTeamAdmin (387-407)
  • isTeamOwner (409-418)
  • getMembershipIdsWhereUserIsAdminOwner (430-452)
apps/web/components/booking/BookingListItem.tsx (3)
apps/web/components/booking/bookingActions.ts (2)
  • shouldShowEditActions (209-212)
  • shouldShowRecurringCancelAction (214-217)
packages/ui/components/dropdown/Dropdown.tsx (5)
  • DropdownMenuItem (63-71)
  • DropdownItem (161-181)
  • DropdownMenuContent (34-51)
  • DropdownMenuPortal (31-31)
  • Dropdown (12-12)
apps/web/components/dialog/ReportBookingDialog.tsx (1)
  • ReportBookingDialog (34-163)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Linters / lint
  • GitHub Check: Tests / Unit
  • GitHub Check: Type check / check-types
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (12)
packages/prisma/schema.prisma (3)

758-763: Enum looks good; mapping aligns with i18n usage

The ReportReason enum and @Map for DONT_KNOW_PERSON are appropriate. This keeps DB values stable and matches UI keys like "dont_know_person".


448-449: Relation on User is appropriate

bookingReports on User allows “my reports” queries without backtracking from Booking. All good.


845-846: Relation on Booking is consistent with UI needs

reports on Booking supports UI badge logic. Even though bookingId is unique (1:1), keeping this as a list is fine given existing frontend expectations.

apps/web/components/booking/BookingListItem.tsx (5)

141-141: State for dialog LGTM

Local dialog state is appropriate here.


409-412: Memoization is acceptable

Memo on booking object is fine given you invalidate/refetch on change. No concerns.


413-429: Action factory reads well; consistent labels/icons

The helper avoids duplication between inline button and dropdown item. Good use of t().


744-759: Dropdown action wiring LGTM

Action is appended after existing sections and respects disabled state when already reported.


765-779: Inline icon action LGTM

Good use of tooltip via label; respects disabled state when already reported.

packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (4)

26-60: Good: precise Prisma select; complies with data-minimization guideline

Using select (not include) and limiting to necessary fields is aligned with our TS/Prisma guidelines.


137-154: Series-level duplicate guard is fine

Preventing a second report on the same recurring series matches the one-report-per-series intention.


172-183: Good concurrency handling

createMany with skipDuplicates avoids unique-constraint races and you correctly use result.count in the response.


209-223: Clear, user-facing messaging

Good job reflecting series vs single and surfacing cancellation failure without blocking the report.

Comment on lines +855 to +867
{(() => {
// Check if this booking or any booking in the recurring series has been reported
const hasAnyReport = checkIfBookingReported(booking);

return (
hasAnyReport && (
<Badge variant="red" className="ltr:mr-2 rtl:ml-2">
{t("reported")}:{" "}
{booking.reports?.[0]?.reason ? t(booking.reports?.[0]?.reason?.toLowerCase()) : "Unknown"}
</Badge>
)
);
})()}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Localize “Unknown”; fix misleading comment vs. logic

  • The fallback string "Unknown" is not localized; per guidelines, wrap with t().
  • The comment says “this booking or any booking in the recurring series,” but the code only checks the current booking. Either update the comment or implement series-level detection.

Apply this diff for i18n:

-            <Badge variant="red" className="ltr:mr-2 rtl:ml-2">
-              {t("reported")}:{" "}
-              {booking.reports?.[0]?.reason ? t(booking.reports?.[0]?.reason?.toLowerCase()) : "Unknown"}
-            </Badge>
+            <Badge variant="red" className="ltr:mr-2 rtl:ml-2">
+              {t("reported")}:{" "}
+              {booking.reports?.[0]?.reason
+                ? t(booking.reports?.[0]?.reason?.toLowerCase())
+                : t("unknown")}
+            </Badge>

Adjust the comment to reflect actual behavior:

-// Check if this booking or any booking in the recurring series has been reported
+// Check if this booking has been reported (series-wide reporting handled server-side)

Please ensure common.json (and other locales) include the "unknown" key to avoid rendering the key name at runtime.


🏁 Script executed:

#!/bin/bash
set -euxo pipefail

# List all 'common.json' locale files
echo "=== Common JSON Files ==="
fd --hidden --glob common.json .

# Search for the 'unknown' key in these locale files
echo -e "\n=== Searching for '\"unknown\"' in locale files ==="
for file in $(fd --hidden --glob common.json .); do
  echo "In ${file}:"
  grep -n '"unknown"' "${file}" || echo "  (none)"
done

# Locate the definition of checkIfBookingReported
echo -e "\n=== Locating checkIfBookingReported definition in TypeScript files ==="
rg -nP 'function\s+checkIfBookingReported' -g '*.ts' || true
rg -nP 'const\s+checkIfBookingReported' -g '*.ts' || true

# Show usages of checkIfBookingReported
echo -e "\n=== Finding calls to checkIfBookingReported ==="
rg -n 'checkIfBookingReported' -C2 -g '*.ts' -g '*.tsx' || true

Length of output: 15651


Localize fallback “Unknown” and correct misleading comment in BookingListItem.tsx

Please make the following mandatory updates:

  • Add an "unknown" entry in common.json for each locale under apps/web/public/static/locales/*/common.json to ensure t("unknown") renders correctly (currently none include this key).
  • In apps/web/components/booking/BookingListItem.tsx lines 855–867:
    • Wrap the fallback string in t("unknown") as shown below.
    • Update the in-line comment to accurately describe the logic (only the current booking is checked).
   {(() => {
-    // Check if this booking or any booking in the recurring series has been reported
+    // Check if this booking has been reported (series-wide reporting handled server-side)
     const hasAnyReport = checkIfBookingReported(booking);

     return (
       hasAnyReport && (
         <Badge variant="red" className="ltr:mr-2 rtl:ml-2">
-          {t("reported")}:{" "}
-          {booking.reports?.[0]?.reason ? t(booking.reports?.[0]?.reason?.toLowerCase()) : "Unknown"}
+          {t("reported")}:{" "}
+          {booking.reports?.[0]?.reason
+            ? t(booking.reports[0].reason.toLowerCase())
+            : t("unknown")}
         </Badge>
       )
     );
   })()}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{(() => {
// Check if this booking or any booking in the recurring series has been reported
const hasAnyReport = checkIfBookingReported(booking);
return (
hasAnyReport && (
<Badge variant="red" className="ltr:mr-2 rtl:ml-2">
{t("reported")}:{" "}
{booking.reports?.[0]?.reason ? t(booking.reports?.[0]?.reason?.toLowerCase()) : "Unknown"}
</Badge>
)
);
})()}
{(() => {
// Check if this booking has been reported (series-wide reporting handled server-side)
const hasAnyReport = checkIfBookingReported(booking);
return (
hasAnyReport && (
<Badge variant="red" className="ltr:mr-2 rtl:ml-2">
{t("reported")}:{" "}
{booking.reports?.[0]?.reason
? t(booking.reports[0].reason.toLowerCase())
: t("unknown")}
</Badge>
)
);
})()}
🤖 Prompt for AI Agents
In apps/web/components/booking/BookingListItem.tsx around lines 855–867, the
fallback string "Unknown" must be localized and the inline comment is
misleading: replace the literal fallback with t("unknown") and update the
comment to state that only the current booking's reports are being inspected
(not the entire recurring series). Additionally, add an "unknown" key to each
locale's apps/web/public/static/locales/*/common.json with appropriate
translated values so t("unknown") resolves for all locales.

@husniabad husniabad force-pushed the feature/report-bookings branch from fc8e83e to 6ae7041 Compare August 24, 2025 02:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (4)

128-131: Access gate correctly considers admin/owner status

Including isTeamAdminOrOwner in the final access check fixes the earlier edge case where admins could be denied if the org-filter yielded no memberships.


158-169: Good: remaining-occurrence scoping for recurring bookings

Filtering by startTime >= the selected instance and ordering asc matches “report remaining occurrences” reliably.


171-181: Good: createMany with skipDuplicates to handle concurrency

This prevents duplicate reports under concurrent submissions.


208-221: Good: response messaging uses actual inserted count and surfaces cancellation errors

Accurately reporting createdCount and exposing cancellationError improves UX and debuggability.

🧹 Nitpick comments (7)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (7)

26-60: Prune unused selections in booking.select to reduce payload and query cost

You only use eventType.teamId later; the nested team fields (id, name, parentId) are unused. Drop them to adhere to “select only what you need” and keep the payload lean.

Apply this diff:

       eventType: {
         select: {
-          teamId: true,
-          team: {
-            select: {
-              id: true,
-              name: true,
-              parentId: true,
-            },
-          },
+          teamId: true,
         },
       },

66-68: Make attendee email check case-insensitive

Email addresses are case-insensitive; strict equality can deny legitimate attendees. Compare lowercased values to avoid false negatives.

-  const isAttendee = booking.attendees.some((attendee) => attendee.email === user.email);
+  const isAttendee = booking.attendees.some(
+    (attendee) => attendee.email?.toLowerCase() === user.email?.toLowerCase()
+  );

81-126: Scope team-member email query to relevant addresses to avoid scanning large orgs

Fetching all team member emails across all admin-owned teams doesn’t scale. Limit the query to the booking owner + attendee emails; this materially reduces rows scanned and memory.

-    // Get all team member emails where user is admin/owner
-    const teamMemberEmails = await prisma.user.findMany({
-      where: {
-        teams: {
-          some: {
-            team: {
-              members: {
-                some: {
-                  id: { in: membershipIdsWhereUserIsAdminOwner },
-                },
-              },
-            },
-          },
-        },
-      },
-      select: {
-        email: true,
-      },
-    });
+    // Get the booking owner's email once
+    const bookingOwnerEmail =
+      booking.userId
+        ? (await prisma.user.findUnique({ where: { id: booking.userId }, select: { email: true } }))?.email
+        : undefined;
+
+    const candidateEmails = [
+      ...booking.attendees.map((a) => a.email),
+      ...(bookingOwnerEmail ? [bookingOwnerEmail] : []),
+    ];
+
+    // Only fetch users relevant to this booking within teams where the requester is ADMIN/OWNER
+    const teamMemberEmails = await prisma.user.findMany({
+      where: {
+        email: { in: candidateEmails },
+        teams: {
+          some: {
+            team: {
+              members: { some: { id: { in: membershipIdsWhereUserIsAdminOwner } } },
+            },
+          },
+        },
+      },
+      select: { email: true },
+    });
@@
-    // Check if booking owner is a team member where user is admin/owner
-    let isBookingOwnerTeamMember = false;
-    if (booking.userId) {
-      const bookingOwner = await prisma.user.findUnique({
-        where: { id: booking.userId },
-        select: { email: true },
-      });
-      isBookingOwnerTeamMember = !!(bookingOwner && teamMemberEmailList.includes(bookingOwner.email));
-    }
+    // Check if booking owner is a team member where user is admin/owner
+    const isBookingOwnerTeamMember = bookingOwnerEmail
+      ? teamMemberEmailList.includes(bookingOwnerEmail)
+      : false;

Note: Nice job already following the “use select, not include” guideline; this change just tightens the scope further.


185-206: Update the “cancelled” flag on BookingReport to reflect actual outcome

Reports are inserted before cancellation and persisted with cancelled: cancelBooking even if the cancellation fails. If “cancelled” is meant to represent the actual result, add a follow-up update to reflect the outcome.

   } catch (error) {
     cancellationError = error;
     console.error("Failed to cancel booking after reporting:", error);
   }
 }
+
+// Reflect actual cancellation outcome on the report(s)
+await prisma.bookingReport.updateMany({
+  where: {
+    bookingId: { in: reportedBookingIds },
+    reportedById: user.id,
+  },
+  data: {
+    cancelled: cancelBooking && !cancellationError,
+  },
+});

If “cancelled” is intended to mean “cancellation requested,” consider renaming to cancelRequested in a follow-up migration to avoid ambiguity.


186-191: Nit: compute ‘now’ once for consistent comparison and micro-alloc reduction

Minor, but avoids constructing multiple Date instances.

-  if (
+  const now = new Date();
+  if (
     cancelBooking &&
     (booking.status === BookingStatus.ACCEPTED || booking.status === BookingStatus.PENDING) &&
-    new Date(booking.startTime) > new Date()
+    new Date(booking.startTime) > now
   ) {

203-205: Prefer centralized/structured logging over console.error

Route logs through the project’s logging facility (if available) for consistency and observability.


183-184: Nit: avoid temporary object for reportedBooking

You can pass the id directly; the object wrapper isn’t needed.

-  const reportedBooking = { id: reportedBookingIds[0] };
+  const firstReportedBookingId = reportedBookingIds[0];
@@
-    bookingId: reportedBooking.id,
+    bookingId: firstReportedBookingId,

Also applies to: 219-220

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fc8e83e and 6ae7041.

📒 Files selected for processing (7)
  • apps/web/components/booking/BookingListItem.tsx (8 hunks)
  • apps/web/components/dialog/ReportBookingDialog.tsx (1 hunks)
  • packages/lib/server/queries/teams/index.ts (1 hunks)
  • packages/prisma/migrations/20250823231324_add_booking_report/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts (3 hunks)
  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/trpc/server/routers/viewer/bookings/get.handler.ts
  • apps/web/components/dialog/ReportBookingDialog.tsx
  • packages/lib/server/queries/teams/index.ts
  • packages/prisma/migrations/20250823231324_add_booking_report/migration.sql
  • apps/web/components/booking/BookingListItem.tsx
  • packages/prisma/schema.prisma
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
🧠 Learnings (3)
📚 Learning: 2025-08-21T13:44:06.784Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.784Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.ts : For Prisma queries, only select data you need; never use `include`, always use `select`

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
📚 Learning: 2025-08-22T16:38:00.182Z
Learnt from: bandhan-majumder
PR: calcom/cal.com#23192
File: packages/lib/server/service/InsightsBookingBaseService.ts:814-816
Timestamp: 2025-08-22T16:38:00.182Z
Learning: In the InsightsBookingBaseService (packages/lib/server/service/InsightsBookingBaseService.ts), when filtering for "accepted" bookings in getMembersStatsWithCount(), using `endTime <= now()` in the SQL condition should be avoided as it conflicts with existing date filtering logic. The components already handle completion filtering by setting `endDate: currentTime` in their query parameters, making additional SQL-level endTime filtering unnecessary and potentially problematic.

Applied to files:

  • packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
🧬 Code graph analysis (1)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (2)
packages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts (1)
  • TReportBookingInputSchema (12-12)
packages/lib/server/queries/teams/index.ts (3)
  • isTeamAdmin (387-407)
  • isTeamOwner (409-418)
  • getMembershipIdsWhereUserIsAdminOwner (430-452)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Tests / Unit
  • GitHub Check: Linters / lint
  • GitHub Check: Type check / check-types
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (3)
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts (3)

137-153: Series-level dedupe may be too aggressive—confirm product intent

Current check blocks reporting if any instance in the series has been reported, even if that report was for a past occurrence. The spec says “Reported bookings cannot be reported again,” but it’s ambiguous at the series level.

  • If the intent is to prevent duplicate reports only for remaining occurrences, scope the check to bookings with startTime >= the selected instance.
  • If the intent is “once per series ever,” keep as-is.

Optional change if you want “remaining occurrences only”:

-  const existingRecurringReport = await prisma.bookingReport.findFirst({
-    where: {
-      booking: {
-        recurringEventId: booking.recurringEventId,
-      },
-    },
-  });
+  const existingRecurringReport = await prisma.bookingReport.findFirst({
+    where: {
+      booking: {
+        recurringEventId: booking.recurringEventId,
+        startTime: { gte: booking.startTime },
+      },
+    },
+  });

171-181: Ensure DB has a unique index to make skipDuplicates effective

skipDuplicates only works if there’s a backing unique index (likely on bookingId if “only one report per booking” is desired). Please confirm the migration adds it; otherwise duplicates can still occur.

If needed, add a unique constraint (migration-level):

  • Unique on bookingId (series-level single report) or
  • Unique on (bookingId, reportedById) (per-reporter dedupe)

Align with product choice.


193-201: Good: fromReport + allRemainingBookings flags align with UX and email requirements

Bypassing a cancellation reason and letting the standard cancellation email send (without mentioning “reported”) meets the product requirement.

@husniabad
Copy link
Contributor Author

husniabad commented Aug 24, 2025

Hi @CarinaWolli I have addresses all your requests as following:

  • 1. Schema Change: One Report Per Booking
  • 2. Show "Reported" Badge to All Users
  • 3. Add Report Reason to Badge (Reported: Spam")
  • 4. Remove User-Specific Check in Handler
  • 5. Fix Variable Naming
  • 6. Fix Return Field Name
  • 7. Code Reuse Suggestion

Also I have addressed coderabbitai suggestions

select: {
id: true,
},
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please explain why did you removed this whole logic ?

hasAnyReport && (
<Badge variant="red" className="ltr:mr-2 rtl:ml-2">
{t("reported")}:{" "}
{booking.reports?.[0]?.reason ? t(booking.reports?.[0]?.reason?.toLowerCase()) : "Unknown"}
Copy link
Contributor

Choose a reason for hiding this comment

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

"unknown" string is hardcoded

email: true,
},
});

Copy link
Contributor

Choose a reason for hiding this comment

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

I think instead of single multiple queries . We can have a single optimized query here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd also look at TeamRepository to see if there's already a method making all of these queries.

Copy link
Contributor

@joeauyeung joeauyeung left a comment

Choose a reason for hiding this comment

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

Great work so far @husniabad. I just left a few comments.

onSuccess,
}: ReportBookingDialogProps) {
const { t } = useLocale();
const [isSubmitting, setIsSubmitting] = useState(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of using the state here we can use the reportBookingMutation.isPending() state

email: true,
},
});

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd also look at TeamRepository to see if there's already a method making all of these queries.

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 ❗️ migrations contains migration files ui area: UI, frontend, button, form, input
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Report bookings
5 participants