feat: add report bookings feature#22709
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.
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
createMockContextfunction 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
bookingTitleparameter is renamed to_bookingTitlebut 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.tsapps/web/components/booking/bookingActions.tspackages/trpc/server/routers/viewer/bookings/reportBooking.schema.tspackages/trpc/server/routers/viewer/bookings/reportBooking.test.tspackages/trpc/server/routers/viewer/bookings/_router.tsxapps/web/components/booking/bookingActions.report.test.tsapps/web/components/dialog/ReportBookingDialog.test.tsxapps/web/components/dialog/ReportBookingDialog.tsxpackages/trpc/server/routers/viewer/bookings/reportBooking.handler.tsapps/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
getReportActionsfunction 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
ZReportBookingInputSchemapackages/trpc/server/routers/viewer/bookings/reportBooking.schema.ts (2)
5-10: LGTM! Well-designed schema with appropriate field types.The schema is thoughtfully designed:
bookingIdas number correctly matches database ID typereasonusing native enum ensures type safetydescriptionas optional string provides flexibility for additional contextcancelBookingwith 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
ReportBookingDialogfollows the established pattern and is properly placed with other dialog imports.
136-136: LGTM: State variable follows established pattern.The
isReportDialogOpenstate 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
onSuccesscallback 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
reportssubquery is always selected in the booking resolver (returning an empty array when there are no reports), andBookingReport.reportedByIdis non-nullable in the Prisma schema. Likewise,loggedInUser.userIdis populated from the authenticated session (and guaranteed to be defined by the time this component renders). Your existing optional chaining and fallback tofalsealready cover all edge cases here.
Devanshusharma2005
left a comment
There was a problem hiding this comment.
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.
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.startTimevalue is normalized to UTC (seehandleNewBooking.tsusingdayjs(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.
6ae7041 to
8caf959
Compare
8caf959 to
e890cdd
Compare
|
Hi @joeauyeung @kart1ka @Devanshusharma2005 @CarinaWolli I've addressed the feedback by creating a single, optimized query (getTeamDataForAdmin) to replace multiple DB calls, improving performance. In addition, I addressed other frontend suggestions by @Devanshusharma2005 and @joeauyeung |
|
Hi @husniabad Thanks for your contribution ! This work looks amazing and almost finished. If you don't mind, let me take over this pull request as I am going to finalize some things here and there while aligning with the the upcoming backend changes (in separate PRs) |
| export async function isTeamAdmin(userId: number, teamId: number) { | ||
| const team = await prisma.membership.findFirst({ | ||
| where: { | ||
| userId, | ||
| teamId, | ||
| accepted: true, | ||
| OR: [{ role: "ADMIN" }, { role: "OWNER" }], | ||
| }, | ||
| include: { | ||
| team: { | ||
| select: { | ||
| metadata: true, | ||
| parentId: true, | ||
| isOrganization: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| if (!team) return false; | ||
| return team; | ||
| } | ||
|
|
||
| export async function isTeamOwner(userId: number, teamId: number) { | ||
| return !!(await prisma.membership.findFirst({ | ||
| where: { | ||
| userId, | ||
| teamId, | ||
| accepted: true, | ||
| role: "OWNER", | ||
| }, | ||
| })); | ||
| } | ||
|
|
||
| export async function isTeamMember(userId: number, teamId: number) { | ||
| return !!(await prisma.membership.findFirst({ | ||
| where: { | ||
| userId, | ||
| teamId, | ||
| accepted: true, | ||
| }, | ||
| })); | ||
| } |
There was a problem hiding this comment.
@eunjae-lee would you be down to use the functions we already have for checkTeamAdmin etc - makes my life migrating to PBAC a bit easier
Hi @eunjae-lee |
- Add BOOKING_REPORT type to WatchlistType enum - Create BookingReportLog model for booking-specific metadata - Update WatchlistRepository with booking report methods - Migrate tRPC handlers to use Watchlist system - Update UI components to work with new data structure - Maintain all existing functionality while using Watchlist backend Stacks on: #22709 Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
|
Hi @kart1ka @CarinaWolli is this PR still valid with new table "BookingReport"? |
|
Hey @husniabad, Thanks for your contribution. However, we’re handling this internally as the priority of this feature request has been escalated. we’ve decided to proceed with an approach where users can report a booking and admins can review all reported bookings and add them to the watchlist by confirming on the admin dashboard and few other changes. |


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.