-
Notifications
You must be signed in to change notification settings - Fork 10.4k
feat: add report bookings feature #22709
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@husniabad is attempting to deploy a commit to the cal Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds 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
Assessment against linked issues: Out-of-scope changes
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
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. |
There was a problem hiding this 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 keysKeys 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 parameterThe
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 behaviorThe 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 checksThe 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 checkInstead 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 conventionThe 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:
- The conditional logic could be more readable
- 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
📒 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 typereason
using native enum ensures type safetydescription
as optional string provides flexibility for additional contextcancelBooking
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 logicThe
reports
subquery is always selected in the booking resolver (returning an empty array when there are no reports), andBookingReport.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 tofalse
already cover all edge cases here.
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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.
0880cc0
to
1020be3
Compare
Hi @Devanshusharma2005 all suggestions and changes addressed, could you please check again |
There was a problem hiding this 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
📒 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
Thebooking.startTime
value is normalized to UTC (seehandleNewBooking.ts
usingdayjs(reqBody.start).utc()
), and stored/passed as an ISO timestamp. Bothnew Date(booking.startTime)
andnew Date()
operate on the same UTC‐based millisecond epoch, so the>
comparison correctly reflects absolute time regardless of client timezone. No changes needed here.
There was a problem hiding this 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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
// 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const hasBeenReportedByUserField = (booking as any).hasBeenReportedByUser; | |
const hasBeenReportedByUserField = booking.hasBeenReportedByUser; |
don't think we need this here
7b99439
to
076cb4b
Compare
Hi @CarinaWolli and @kart1ka I have simplified the logic as you requested and addressed other changes |
@@ -818,6 +840,7 @@ model Booking { | |||
tracking Tracking? | |||
routingFormResponses RoutingFormResponseDenormalized[] | |||
expenseLogs CreditExpenseLog[] | |||
reports BookingReport[] |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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")} |
There was a problem hiding this comment.
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
where: { | ||
reportedById: user.id, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where: { | |
reportedById: user.id, | |
}, |
We would need to remove this then too
})), | ||
}); | ||
|
||
const bookingReport = { id: reportedBookingIds[0] }; // For backward compatibility |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
076cb4b
to
d354d23
Compare
There was a problem hiding this 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 withvi.setSystemTime(new Date("2025-01-01T12:00:00Z"))
in abeforeAll
and restore inafterAll
.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
usesbooking.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
orbooking.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)
inbookingActions.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
supportstype="button"
(you set it) but it renders interactive content. Ensure it doesn’t render an anchor withouthref
(component-dependent). If it does, prefer a button to avoidhref="#"
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 includearia-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.
📒 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 useinclude
, always useselect
Ensure thecredential.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.
d354d23
to
58ca42a
Compare
There was a problem hiding this 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.
📒 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 useinclude
, always useselect
Ensure thecredential.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
: Verifyreports.reason
is selected inbookings.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 expectsbooking.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 minimalselect
onreports.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.
58ca42a
to
b3821b1
Compare
There was a problem hiding this 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 checkAfter digging into the backend handler, each booking’s
reports
array is populated only with reports whosebookingId
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 organizationIdUse 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() callisTeamAdmin() 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 bookingYou 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 onlyYou 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.errorAlign 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.
📒 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 useinclude
, always useselect
Ensure thecredential.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 correctIncludes 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 goodThe any-report check matches the “cannot be reported again” requirement. No action needed.
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
Outdated
Show resolved
Hide resolved
packages/trpc/server/routers/viewer/bookings/reportBooking.handler.ts
Outdated
Show resolved
Hide resolved
b3821b1
to
fc8e83e
Compare
There was a problem hiding this 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 booleanThe 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 cacheutils.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 reportsMinor 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.
📒 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 useinclude
, always useselect
Ensure thecredential.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 usageThe 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 appropriatebookingReports on User allows “my reports” queries without backtracking from Booking. All good.
845-846
: Relation on Booking is consistent with UI needsreports 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 LGTMLocal dialog state is appropriate here.
409-412
: Memoization is acceptableMemo on booking object is fine given you invalidate/refetch on change. No concerns.
413-429
: Action factory reads well; consistent labels/iconsThe helper avoids duplication between inline button and dropdown item. Good use of t().
744-759
: Dropdown action wiring LGTMAction is appended after existing sections and respects disabled state when already reported.
765-779
: Inline icon action LGTMGood 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 guidelineUsing select (not include) and limiting to necessary fields is aligned with our TS/Prisma guidelines.
137-154
: Series-level duplicate guard is finePreventing a second report on the same recurring series matches the one-report-per-series intention.
172-183
: Good concurrency handlingcreateMany with skipDuplicates avoids unique-constraint races and you correctly use result.count in the response.
209-223
: Clear, user-facing messagingGood job reflecting series vs single and surfacing cancellation failure without blocking the report.
{(() => { | ||
// 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> | ||
) | ||
); | ||
})()} |
There was a problem hiding this comment.
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 incommon.json
for each locale underapps/web/public/static/locales/*/common.json
to ensuret("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).
- Wrap the fallback string in
{(() => {
- // 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.
{(() => { | |
// 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.
fc8e83e
to
6ae7041
Compare
There was a problem hiding this 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 statusIncluding 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 bookingsFiltering by startTime >= the selected instance and ordering asc matches “report remaining occurrences” reliably.
171-181
: Good: createMany with skipDuplicates to handle concurrencyThis prevents duplicate reports under concurrent submissions.
208-221
: Good: response messaging uses actual inserted count and surfaces cancellation errorsAccurately 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 costYou 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-insensitiveEmail 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 orgsFetching 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 outcomeReports 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 reductionMinor, 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.errorRoute logs through the project’s logging facility (if available) for consistency and observability.
183-184
: Nit: avoid temporary object for reportedBookingYou 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.
📒 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 useinclude
, always useselect
Ensure thecredential.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 intentCurrent 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 effectiveskipDuplicates 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 requirementsBypassing a cancellation reason and letting the standard cancellation email send (without mentioning “reported”) meets the product requirement.
Hi @CarinaWolli I have addresses all your requests as following:
Also I have addressed coderabbitai suggestions |
select: { | ||
id: true, | ||
}, | ||
}) |
There was a problem hiding this comment.
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"} |
There was a problem hiding this comment.
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, | ||
}, | ||
}); | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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); |
There was a problem hiding this comment.
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, | ||
}, | ||
}); | ||
|
There was a problem hiding this comment.
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.
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.
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):
report booking feature - Watch Video
Image Demo (if applicable):
Mandatory Tasks (DO NOT REMOVE)
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.