Skip to content

Comments

feat: blocklist table#24459

Merged
alishaz-polymath merged 8 commits intomainfrom
feat/blocklist-table
Oct 15, 2025
Merged

feat: blocklist table#24459
alishaz-polymath merged 8 commits intomainfrom
feat/blocklist-table

Conversation

@Udit-takkar
Copy link
Contributor

@Udit-takkar Udit-takkar commented Oct 14, 2025

What does this PR do?

Moved Booking Report Table from #24396 to this PR

Screen.Recording.2025-10-14.at.9.12.04.PM.mov

Mandatory Tasks (DO NOT REMOVE)

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

How should this be tested?

  1. Login in as org admin
  2. Then go you org settings privacy and security page
  3. Create watchlist entry

@Udit-takkar Udit-takkar requested a review from a team as a code owner October 14, 2025 17:24
@github-actions github-actions bot added the ❗️ migrations contains migration files label Oct 14, 2025
@graphite-app graphite-app bot requested a review from a team October 14, 2025 17:24
@keithwillcode keithwillcode added core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO labels Oct 14, 2025
@Udit-takkar Udit-takkar added the High priority Created by Linear-GitHub Sync label Oct 14, 2025
@Udit-takkar Udit-takkar added this to the v5.8 milestone Oct 14, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

Adds an organization watchlist/blocklist feature and renames the Settings tab/labels from "privacy" to "privacy_and_security". Introduces TRPC schemas and handlers (list/create/get/delete), a Prisma-backed Watchlist repository with audit logging and migration linking Watchlist ↔ WatchlistAudit, server-side permission checks (PBAC Resource.Watchlist and CRUD permissions), new UI components (BlocklistTable, CreateBlocklistEntryModal, BlocklistEntryDetailsSheet) integrated into the org privacy page, locale keys, and a domain-with-@ regex. Also updates settings layout tab keys and defers translation initialization in the org privacy page.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the primary new feature—the blocklist table—using a standard “feat:” prefix without extraneous details or noise. It clearly conveys the main change so that a teammate reviewing the history will immediately understand the core addition.
Description Check ✅ Passed The description, despite mistakenly referring to moving a booking report table, remains related by discussing the introduction of a table feature and providing concrete steps to test the watchlist entry functionality on the privacy and security page. It is not entirely off-topic or so generic as to be meaningless, so it meets the lenient criteria for passing this check.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/blocklist-table

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2668e50 and cf83128.

📒 Files selected for processing (2)
  • apps/web/public/static/locales/en/common.json (4 hunks)
  • packages/features/pbac/domain/types/permission-registry.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/features/pbac/domain/types/permission-registry.ts
  • apps/web/public/static/locales/en/common.json

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
Copy link

vercel bot commented Oct 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Oct 15, 2025 10:51am
cal-eu Ignored Ignored Oct 15, 2025 10:51am

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (21)
packages/features/data-table/hooks/useDebouncedWidth.ts (1)

1-3: Directive correctly added.

The "use client" directive is necessary and correctly placed for this hook, which uses client-only React features (useState, useEffect, ResizeObserver).

Optionally, the extra blank line (line 3) could be removed to follow the more common convention of a single blank line between the directive and imports:

 "use client";
 

-
 import debounce from "lodash/debounce";
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx (1)

173-180: Remove legacy “privacy” key
The "privacy" entry in organizationAdminKeys no longer corresponds to any settings route—remove it to avoid confusion.

packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.schema.ts (1)

8-8: Consider adding max length validation for searchTerm.

While the searchTerm is correctly marked as optional, it lacks a maximum length constraint. Adding a reasonable max length (e.g., .max(255)) can help prevent potential performance issues from extremely long search strings.

Apply this diff to add a max length:

-  searchTerm: z.string().optional(),
+  searchTerm: z.string().max(255).optional(),
packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.test.ts (1)

153-193: Consider adding test coverage for error handling.

The test suite comprehensively covers access control, validation, and success scenarios. However, there's no test for when deleteEntry throws an error, which should result in an INTERNAL_SERVER_ERROR response according to the handler's try-catch block.

Consider adding this test case to the "successful deletion" describe block:

it("should throw INTERNAL_SERVER_ERROR when deleteEntry fails", async () => {
  mockWatchlistRepo.findEntryWithAudit.mockResolvedValue({
    entry: mockEntry,
    auditHistory: [],
  });
  mockWatchlistRepo.deleteEntry.mockRejectedValue(new Error("Database error"));

  await expect(
    deleteWatchlistEntryHandler({
      ctx: { user: mockUser },
      input: {
        id: "entry-123",
      },
    })
  ).rejects.toMatchObject({
    code: "INTERNAL_SERVER_ERROR",
    message: "Failed to delete blocklist entry",
  });
});
packages/features/ee/organizations/pages/settings/privacy.tsx (1)

41-51: Pass a stable tableIdentifier to DataTableProvider

Avoid state collisions across navigations and ensure stable saved filters by providing an explicit tableIdentifier.

-            <DataTableProvider useSegments={useSegments} defaultPageSize={25}>
+            <DataTableProvider
+              tableIdentifier="org-watchlist"
+              useSegments={useSegments}
+              defaultPageSize={25}
+            >
packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.test.ts (1)

33-52: Align mock audit shape with repository contract (optional)

Mocks use timestamp/previousValue/newValue; repository returns changedAt/value. Not breaking here, but aligning reduces confusion and future drift.

apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx (3)

109-111: Localize date formatting

Format pattern isn’t localized. Prefer passing the user’s locale (or a shared date util) to date-fns format.

If useLocale exposes a dateFns locale, do:

const { t, dateLocale } = useLocale();
// ...
format(new Date(audit.changedAt), "PPP, p", { locale: dateLocale })

Otherwise, use the project’s date formatting utility, if available. Based on learnings


30-41: Type the action discriminator for safer variants

Narrow action to the actual union type to catch mismatches and keep Badge variants consistent.

-  const getActionVariant = (action: string) => {
+  const getActionVariant = (action: BlocklistEntry["action"]) => {
     switch (action) {
       case "BLOCK":
         return "red";
       case "REPORT":
         return "orange";
       case "ALERT":
         return "blue";
       default:
         return "gray";
     }
   };

86-89: Localize fallback placeholders

Replace hardcoded "—" with a localized placeholder (e.g., t("not_available")) for consistency with i18n guidelines.

Confirm the appropriate key (e.g., "not_available"). If none exists, I can add it across locales.

Also applies to: 106-108, 120-127

packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.handler.ts (1)

89-89: Prefer named export only (avoid default exports)

Project guideline: favor named exports for better tree-shaking and refactors. Drop the default export.

-export default listWatchlistEntriesHandler;
+// Prefer named export only

As per coding guidelines

packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.ts (1)

85-85: Prefer named export only (avoid default exports)

Follow project convention to drop default exports in TS files.

-export default getWatchlistEntryDetailsHandler;
+// Prefer named export only

As per coding guidelines

packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.ts (3)

47-59: Trim and normalize value before validation and persistence

Avoid false negatives and duplicate variants by trimming first and validating/saving the trimmed value (lowercased on save).

Apply this diff:

   const watchlistRepo = new WatchlistRepository(prisma);
 
-  if (input.type === "EMAIL" && !emailRegex.test(input.value)) {
+  const trimmedValue = input.value.trim();
+
+  if (input.type === "EMAIL" && !emailRegex.test(trimmedValue)) {
     throw new TRPCError({
       code: "BAD_REQUEST",
       message: "Invalid email address format",
     });
   }
 
-  if (input.type === "DOMAIN" && !domainWithAtRegex.test(input.value)) {
+  if (input.type === "DOMAIN" && !domainWithAtRegex.test(trimmedValue)) {
     throw new TRPCError({
       code: "BAD_REQUEST",
       message: "Invalid domain format. Domain must start with @ (e.g., @example.com)",
     });
   }
 
   try {
     const entry = await watchlistRepo.createEntry({
       type: input.type,
-      value: input.value.toLowerCase(),
+      value: trimmedValue.toLowerCase(),
       organizationId,
       action: WatchlistAction.BLOCK,
       description: input.description,
       userId: user.id,
     });

Also applies to: 61-69


76-83: Use typed/structured duplicate error instead of string matching

String contains("already exists") is brittle. Prefer a custom DuplicateWatchlistEntryError (or Prisma P2002 check) and handle by instanceof (or code).

I can draft a small custom error class and update repository + handler catch if you want.


86-86: Drop default export; keep named export only

Project guideline discourages default exports in TS. Remove the default export and use the named export everywhere.

Apply this diff:

-export default createWatchlistEntryHandler;

As per coding guidelines

packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.test.ts (1)

192-295: Add a test for duplicate mapping to BAD_REQUEST

Cover the branch that maps repository “already exists” errors to a BAD_REQUEST TRPC error.

Apply this addition near the end of the suite:

+    it("should map duplicate repository error to BAD_REQUEST", async () => {
+      mockPermissionCheckService.checkPermission.mockResolvedValue(true);
+      mockWatchlistRepo.createEntry.mockRejectedValue(
+        new Error("Watchlist entry already exists for this organization")
+      );
+
+      await expect(
+        createWatchlistEntryHandler({
+          ctx: { user: mockUser },
+          input: { type: WatchlistType.EMAIL, value: "dup@example.com" },
+        })
+      ).rejects.toMatchObject({ code: "BAD_REQUEST" });
+    });
apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx (2)

66-80: Trim input for validation and submission

Improve DX and consistency by trimming before validate and submit; optionally trim description.

Apply:

-  const onSubmit = (data: FormData) => {
+  const onSubmit = (data: FormData) => {
     createWatchlistEntry.mutate({
       type: data.type,
-      value: data.value,
-      description: data.description,
+      value: data.value.trim(),
+      description: data.description?.trim() || undefined,
     });
   };
 
-  const validateValue = (value: string) => {
-    if (!value) return t("required");
+  const validateValue = (raw: string) => {
+    const value = raw.trim();
+    if (!value) return t("field_required");
 
     if (watchType === WatchlistType.EMAIL) {
       if (!emailRegex.test(value)) {
         return t("invalid_email_address");
       }
     } else if (watchType === WatchlistType.DOMAIN) {
       if (!domainWithAtRegex.test(value)) {
         return t("invalid_domain_format");
       }
     }
 
     return true;
   };

Also applies to: 58-64


101-114: Wire labels to inputs; localize placeholders

  • Connect labels for a11y (use id/inputId).
  • Localize placeholders per .tsx i18n guideline.

Apply:

               render={({ field }) => (
-                  <Select
+                  <Select
                     {...field}
                     options={typeOptions}
+                    inputId="type"
                     onChange={(option) => {
                       if (option) {
                         field.onChange(option.value);
                         setValue("value", "");
                       }
                     }}
                     value={typeOptions.find((opt) => opt.value === field.value)}
                   />
                 render={({ field }) => (
-                  <Input
+                  <Input
                     {...field}
-                    placeholder={watchType === WatchlistType.EMAIL ? "user@example.com" : "@spammer.com"}
+                    id="value"
+                    placeholder={
+                      watchType === WatchlistType.EMAIL
+                        ? t("email_placeholder_example")
+                        : t("domain_placeholder_example")
+                    }
                   />
                 )}
               render={({ field }) => (
-                  <TextArea {...field} placeholder={t("reason_for_adding_to_blocklist")} rows={3} />
+                  <TextArea
+                    {...field}
+                    id="description"
+                    placeholder={t("reason_for_adding_to_blocklist")}
+                    rows={3}
+                  />
               )}

Please ensure the i18n keys email_placeholder_example and domain_placeholder_example exist in locales. As per coding guidelines

Also applies to: 130-136, 144-150

apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (2)

46-49: Gate query by read permission

Avoid unnecessary fetch when user can’t read.

Apply:

   const { data, isPending } = trpc.viewer.organizations.listWatchlistEntries.useQuery(
     {
       limit,
       offset,
       searchTerm,
     },
     {
       placeholderData: keepPreviousData,
+      enabled: permissions?.canRead ?? true,
     }
   );

135-143: Improve delete UX: disable while pending; clear selection on close

  • Disable delete button during mutation.
  • Clear entryToDelete when dialog closes via overlay/Escape.

Apply:

               {permissions?.canDelete && (
                 <Button
                   color="destructive"
                   variant="icon"
                   StartIcon="trash"
                   onClick={() => handleDelete(entry)}
+                  disabled={deleteWatchlistEntry.isPending}
                   tooltip={t("delete")}
                 />
               )}
-      <Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
+      <Dialog
+        open={showDeleteDialog}
+        onOpenChange={(open) => {
+          setShowDeleteDialog(open);
+          if (!open) setEntryToDelete(null);
+        }}>
         <ConfirmationDialogContent
           variety="danger"
           title={t("delete_blocklist_entry")}
           confirmBtnText={t("delete")}
           onConfirm={confirmDelete}>
           {t("delete_blocklist_entry_confirmation", { value: entryToDelete?.value })}
         </ConfirmationDialogContent>
       </Dialog>

Also applies to: 193-201

packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.ts (2)

46-60: Avoid heavy fetch for delete pre-check; select only needed fields

Using findEntryWithAudit pulls audit history unnecessarily. Add a lightweight repository method (e.g., findEntryById with select: { id, organizationId, isGlobal }) and use that here.

Example:

// repo
async findEntryById(id: string) {
  return this.prismaClient.watchlist.findUnique({
    where: { id },
    select: { id: true, organizationId: true, isGlobal: true },
  });
}

// handler
const entry = await watchlistRepo.findEntryById(input.id);

As per coding guidelines


77-77: Remove default export; use named export only

Align with project’s no-default-exports rule for TS.

Apply:

-export default deleteWatchlistEntryHandler;

As per coding guidelines

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b5585b3 and 14c4898.

📒 Files selected for processing (27)
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx (2 hunks)
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/privacy/page.tsx (2 hunks)
  • apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (1 hunks)
  • apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx (1 hunks)
  • apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx (1 hunks)
  • apps/web/public/static/locales/en/common.json (3 hunks)
  • packages/features/data-table/hooks/useDebouncedWidth.ts (1 hunks)
  • packages/features/ee/organizations/pages/settings/privacy.tsx (2 hunks)
  • packages/features/pbac/domain/types/permission-registry.ts (4 hunks)
  • packages/features/users/repositories/UserRepository.ts (1 hunks)
  • packages/lib/emailSchema.ts (1 hunks)
  • packages/lib/server/repository/watchlist.interface.ts (1 hunks)
  • packages/lib/server/repository/watchlist.repository.ts (1 hunks)
  • packages/prisma/migrations/20251014143620_add_watchlist_audit_relation/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (2 hunks)
  • packages/trpc/server/routers/viewer/organizations/_router.tsx (2 hunks)
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.test.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.schema.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.test.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.schema.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.test.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.schema.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.schema.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.ts

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

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

Files:

  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.schema.ts
  • packages/features/users/repositories/UserRepository.ts
  • packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.handler.ts
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.schema.ts
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.ts
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.test.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.schema.ts
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.test.ts
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.ts
  • packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.schema.ts
  • packages/features/data-table/hooks/useDebouncedWidth.ts
  • packages/lib/emailSchema.ts
  • packages/lib/server/repository/watchlist.interface.ts
  • packages/lib/server/repository/watchlist.repository.ts
  • packages/features/pbac/domain/types/permission-registry.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.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:

  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.schema.ts
  • packages/features/users/repositories/UserRepository.ts
  • packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.handler.ts
  • apps/web/modules/settings/organizations/privacy/blocklist-table.tsx
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.schema.ts
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.ts
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.test.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.schema.ts
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.test.ts
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.ts
  • packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.schema.ts
  • apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/privacy/page.tsx
  • packages/features/data-table/hooks/useDebouncedWidth.ts
  • packages/lib/emailSchema.ts
  • packages/features/ee/organizations/pages/settings/privacy.tsx
  • packages/lib/server/repository/watchlist.interface.ts
  • packages/lib/server/repository/watchlist.repository.ts
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx
  • packages/features/pbac/domain/types/permission-registry.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.test.ts
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.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/organizations/getWatchlistEntryDetails.schema.ts
  • packages/features/users/repositories/UserRepository.ts
  • packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.handler.ts
  • apps/web/modules/settings/organizations/privacy/blocklist-table.tsx
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.schema.ts
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.ts
  • packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.test.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.schema.ts
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.test.ts
  • packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.ts
  • packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.schema.ts
  • apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/privacy/page.tsx
  • packages/features/data-table/hooks/useDebouncedWidth.ts
  • packages/lib/emailSchema.ts
  • packages/features/ee/organizations/pages/settings/privacy.tsx
  • packages/lib/server/repository/watchlist.interface.ts
  • packages/lib/server/repository/watchlist.repository.ts
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx
  • packages/features/pbac/domain/types/permission-registry.ts
  • packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.test.ts
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx
**/*Repository.ts

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

Repository files must include Repository suffix, prefix with technology if applicable (e.g., PrismaAppRepository.ts), and use PascalCase matching the exported class

Files:

  • packages/features/users/repositories/UserRepository.ts
**/*.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/modules/settings/organizations/privacy/blocklist-table.tsx
  • apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/privacy/page.tsx
  • packages/features/ee/organizations/pages/settings/privacy.tsx
  • packages/trpc/server/routers/viewer/organizations/_router.tsx
  • apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx
  • apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx
**/*.{service,repository}.ts

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

Avoid dot-suffixes like .service.ts or .repository.ts for new files; reserve .test.ts, .spec.ts, .types.ts for their specific purposes

Files:

  • packages/lib/server/repository/watchlist.repository.ts
🧠 Learnings (1)
📚 Learning: 2025-10-03T18:51:52.952Z
Learnt from: alishaz-polymath
PR: calcom/cal.com#24246
File: packages/prisma/migrations/20251003103832_upsert_watchlist_audit/migration.sql:55-88
Timestamp: 2025-10-03T18:51:52.952Z
Learning: In the cal.com codebase, the team avoids Prisma-level relations for watchlist audit tables (WatchlistAudit, WatchlistEventAudit) to prevent nested selects and maintain explicit query control, even if it means handling referential integrity at the application layer or through database-level constraints only.

Applied to files:

  • packages/prisma/migrations/20251014143620_add_watchlist_audit_relation/migration.sql
  • packages/prisma/schema.prisma
🧬 Code graph analysis (15)
packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.handler.ts (4)
packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.schema.ts (1)
  • TListWatchlistEntriesInputSchema (16-16)
packages/features/pbac/services/permission-check.service.ts (2)
  • PermissionCheckService (19-338)
  • hasPermission (215-233)
packages/lib/server/repository/watchlist.repository.ts (1)
  • WatchlistRepository (13-180)
packages/features/users/repositories/UserRepository.ts (1)
  • UserRepository (122-1114)
apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (5)
packages/trpc/react/trpc.ts (2)
  • RouterOutputs (143-143)
  • trpc (54-138)
packages/ui/components/toast/showToast.tsx (1)
  • showToast (77-101)
apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx (1)
  • CreateBlocklistEntryModal (25-173)
apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx (1)
  • BlocklistEntryDetailsSheet (20-131)
packages/ui/components/dialog/Dialog.tsx (1)
  • Dialog (35-38)
packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.schema.ts (1)
packages/features/watchlist/lib/types.ts (1)
  • WatchlistType (3-3)
packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.ts (4)
packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.schema.ts (1)
  • TCreateWatchlistEntryInputSchema (11-11)
packages/features/pbac/services/permission-check.service.ts (2)
  • PermissionCheckService (19-338)
  • hasPermission (215-233)
packages/lib/server/repository/watchlist.repository.ts (1)
  • WatchlistRepository (13-180)
packages/lib/emailSchema.ts (2)
  • emailRegex (4-5)
  • domainWithAtRegex (12-13)
packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.test.ts (3)
packages/features/pbac/services/permission-check.service.ts (1)
  • PermissionCheckService (19-338)
packages/lib/server/repository/watchlist.repository.ts (1)
  • WatchlistRepository (13-180)
packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.handler.ts (1)
  • createWatchlistEntryHandler (19-84)
packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.test.ts (4)
packages/features/pbac/services/permission-check.service.ts (1)
  • PermissionCheckService (19-338)
packages/lib/server/repository/watchlist.repository.ts (1)
  • WatchlistRepository (13-180)
packages/features/users/repositories/UserRepository.ts (1)
  • UserRepository (122-1114)
packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.ts (1)
  • getWatchlistEntryDetailsHandler (19-83)
packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.ts (4)
packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.schema.ts (1)
  • TGetWatchlistEntryDetailsInputSchema (7-7)
packages/features/pbac/services/permission-check.service.ts (2)
  • PermissionCheckService (19-338)
  • hasPermission (215-233)
packages/lib/server/repository/watchlist.repository.ts (1)
  • WatchlistRepository (13-180)
packages/features/users/repositories/UserRepository.ts (1)
  • UserRepository (122-1114)
packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.ts (3)
packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.schema.ts (1)
  • TDeleteWatchlistEntryInputSchema (7-7)
packages/features/pbac/services/permission-check.service.ts (2)
  • PermissionCheckService (19-338)
  • hasPermission (215-233)
packages/lib/server/repository/watchlist.repository.ts (1)
  • WatchlistRepository (13-180)
apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx (1)
packages/trpc/react/trpc.ts (2)
  • RouterOutputs (143-143)
  • trpc (54-138)
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/privacy/page.tsx (3)
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/actions/validateUserHasOrg.tsx (1)
  • validateUserHasOrg (19-30)
apps/web/app/_utils.tsx (1)
  • getTranslate (15-19)
packages/features/pbac/lib/resource-permissions.ts (1)
  • getResourcePermissions (50-87)
packages/features/ee/organizations/pages/settings/privacy.tsx (3)
packages/features/data-table/DataTableProvider.tsx (1)
  • DataTableProvider (93-426)
packages/features/data-table/hooks/useSegments.ts (1)
  • useSegments (9-66)
apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (1)
  • BlocklistTable (30-204)
packages/lib/server/repository/watchlist.repository.ts (2)
packages/lib/server/repository/watchlist.interface.ts (6)
  • IWatchlistRepository (53-67)
  • CreateWatchlistInput (28-35)
  • WatchlistEntry (3-14)
  • CheckWatchlistInput (37-41)
  • FindAllEntriesInput (43-51)
  • WatchlistAuditEntry (16-26)
packages/features/watchlist/lib/repository/PrismaWatchlistAuditRepository.ts (1)
  • PrismaWatchlistAuditRepository (11-93)
packages/trpc/server/routers/viewer/organizations/_router.tsx (4)
packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.schema.ts (1)
  • ZListWatchlistEntriesInputSchema (5-14)
packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.schema.ts (1)
  • ZCreateWatchlistEntryInputSchema (5-9)
packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.schema.ts (1)
  • ZDeleteWatchlistEntryInputSchema (3-5)
packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.schema.ts (1)
  • ZGetWatchlistEntryDetailsInputSchema (3-5)
apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx (5)
packages/ui/components/toast/showToast.tsx (1)
  • showToast (77-101)
packages/lib/emailSchema.ts (2)
  • emailRegex (4-5)
  • domainWithAtRegex (12-13)
packages/ui/components/dialog/Dialog.tsx (4)
  • Dialog (35-38)
  • DialogContent (56-117)
  • DialogHeader (124-142)
  • DialogFooter (150-164)
packages/ui/components/form/select/Select.tsx (1)
  • Select (14-125)
packages/ui/components/form/inputs/TextField.tsx (1)
  • Input (55-66)
packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.test.ts (3)
packages/features/pbac/services/permission-check.service.ts (1)
  • PermissionCheckService (19-338)
packages/lib/server/repository/watchlist.repository.ts (1)
  • WatchlistRepository (13-180)
packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.handler.ts (1)
  • deleteWatchlistEntryHandler (18-75)
🔇 Additional comments (14)
packages/lib/emailSchema.ts (1)

7-13: LGTM! Well-documented domain validation regex.

The regex pattern correctly enforces the @ prefix and supports international domain characters, which aligns with the watchlist domain validation requirements.

packages/trpc/server/routers/viewer/organizations/createWatchlistEntry.schema.ts (1)

1-11: LGTM! Clean and appropriate schema definition.

The schema correctly uses z.nativeEnum for the Prisma enum, includes reasonable string length constraints, and follows Zod best practices.

packages/features/pbac/domain/types/permission-registry.ts (2)

15-15: LGTM! Watchlist resource added to enum.

The new Watchlist resource follows the established naming convention and fits well into the existing resource structure.


677-712: LGTM! Complete and consistent permission registry for Watchlist.

The permission definitions follow the established pattern for other resources:

  • Proper Organization scope
  • Correct dependency chain (CRUD operations depend on Read)
  • Consistent i18n key naming
  • Appropriate metadata structure
packages/prisma/migrations/20251014143620_add_watchlist_audit_relation/migration.sql (1)

1-2: Verify: Prisma relation contradicts previous team decision.

The migration adds a database-level foreign key, which is fine. However, the corresponding schema.prisma changes (lines 2324 and 2341-2342 in schema.prisma) introduce Prisma-level relations between Watchlist and WatchlistAudit. This appears to contradict a previous team decision to avoid Prisma-level relations for audit tables to prevent nested selects and maintain explicit query control.

Based on learnings

Please confirm whether this design change is intentional and the previous constraint no longer applies, or if the Prisma relations should be removed while keeping only the database-level foreign key constraint.

packages/features/users/repositories/UserRepository.ts (1)

1045-1056: LGTM! Clean user lookup method for enriching audit data.

The implementation correctly fetches multiple users by IDs with minimal field selection, which is appropriate for enriching watchlist audit records with user details.

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx (1)

101-101: LGTM! Tab label updated to reflect expanded scope.

The tab name change from "privacy" to "privacy_and_security" aligns with the broader watchlist/blocklist feature set being introduced.

packages/trpc/server/routers/viewer/organizations/deleteWatchlistEntry.schema.ts (1)

1-7: LGTM! Appropriate schema for delete operation.

The schema correctly validates the UUID format for the watchlist entry ID and follows the established pattern for delete operation schemas.

packages/prisma/schema.prisma (1)

2324-2324: Verify: Prisma relation contradicts previous team decision.

The Prisma-level relation between Watchlist and WatchlistAudit contradicts the retrieved learning that indicates the team avoids Prisma-level relations for audit tables to prevent nested selects and maintain explicit query control.

Based on learnings

This is the same concern raised for the migration file. Please confirm whether:

  1. This design change is intentional and the previous constraint no longer applies
  2. The relations should be removed, keeping only the database-level foreign key constraint

If the relations are intended, ensure that repository and handler code explicitly uses select (per coding guidelines) to avoid accidentally including nested audit records.

Also applies to: 2341-2342

apps/web/app/(use-page-wrapper)/settings/(settings-layout)/organizations/(org-admin-only)/privacy/page.tsx (1)

44-60: LGTM! Watchlist permissions setup is consistent.

The watchlist permissions fetch follows the same pattern as the Organization permissions and uses appropriate fallback roles (ADMIN and OWNER) that align with the TRPC endpoint authorization. The permission set (read, create, delete) is well-suited for watchlist entries.

packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.schema.ts (1)

3-5: LGTM! Input schema is well-defined.

The schema correctly validates the entry ID as a UUID, which is appropriate for database identifiers.

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

170-193: LGTM! Watchlist endpoints are properly configured.

All four watchlist endpoints correctly use authedOrgAdminProcedure for access control, appropriate operation types (query for reads, mutation for writes), and follow the established pattern of dynamic handler imports. The implementation is consistent with existing endpoints in this router.

packages/trpc/server/routers/viewer/organizations/listWatchlistEntries.handler.ts (1)

19-43: Auth and org checks look solid

Org resolution + permission check path matches PBAC service usage; error codes/messages consistent with details handler.

packages/trpc/server/routers/viewer/organizations/getWatchlistEntryDetails.handler.ts (1)

19-43: Handler flow LGTM

Org check, permission check, not-found, cross-org check, and enrichment are correct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx (1)

86-89: "Created by" still uses latest audit entry instead of earliest.

This issue was flagged in a previous review but remains unfixed. The code uses auditHistory[0] which is the latest change, not the original creator. Since audit history is returned in descending order, you need the last element.

Apply this diff to fix the issue:

               <div>
                 <label className="text-default mb-1 block text-sm font-medium">{t("created_by")}</label>
-                <p className="text-default">{data.auditHistory[0]?.changedByUser?.email || "—"}</p>
+                <p className="text-default">
+                  {data.auditHistory[data.auditHistory.length - 1]?.changedByUser?.email || "—"}
+                </p>
               </div>
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 14c4898 and e70d283.

📒 Files selected for processing (2)
  • apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx (1 hunks)
  • packages/lib/server/repository/watchlist.repository.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{service,repository}.ts

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

Avoid dot-suffixes like .service.ts or .repository.ts for new files; reserve .test.ts, .spec.ts, .types.ts for their specific purposes

Files:

  • packages/lib/server/repository/watchlist.repository.ts
**/*.ts

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

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

Files:

  • packages/lib/server/repository/watchlist.repository.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/repository/watchlist.repository.ts
  • apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.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/repository/watchlist.repository.ts
  • apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.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/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx
🧠 Learnings (2)
📚 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/lib/server/repository/watchlist.repository.ts
📚 Learning: 2025-08-07T18:42:34.081Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/lib/server/repository/PrismaPhoneNumberRepository.ts:412-417
Timestamp: 2025-08-07T18:42:34.081Z
Learning: In Cal.com codebase, the coding guideline requiring explicit `select` clauses instead of `include` for Prisma queries applies to read operations but not to update operations. Update operations don't need explicit select clauses.

Applied to files:

  • packages/lib/server/repository/watchlist.repository.ts
🧬 Code graph analysis (2)
packages/lib/server/repository/watchlist.repository.ts (2)
packages/lib/server/repository/watchlist.interface.ts (6)
  • IWatchlistRepository (53-67)
  • CreateWatchlistInput (28-35)
  • WatchlistEntry (3-14)
  • CheckWatchlistInput (37-41)
  • FindAllEntriesInput (43-51)
  • WatchlistAuditEntry (16-26)
packages/features/watchlist/lib/repository/PrismaWatchlistAuditRepository.ts (1)
  • PrismaWatchlistAuditRepository (11-93)
apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx (1)
packages/trpc/react/trpc.ts (2)
  • RouterOutputs (143-143)
  • trpc (54-138)
🔇 Additional comments (4)
packages/lib/server/repository/watchlist.repository.ts (4)

27-52: Transaction pattern correctly ensures atomicity.

The use of $transaction to wrap both the watchlist creation and audit logging ensures that both operations succeed or fail together, preventing inconsistent state. This addresses the atomicity concern from previous reviews.


88-116: Excellent use of explicit select clauses.

The query properly uses explicit select for both the watchlist fields and the nested audits relation, adhering to the coding guidelines. The orderBy: { changedAt: "desc" } correctly retrieves the most recent audit entry.


130-158: Proper use of explicit select throughout.

The method correctly uses explicit select clauses for both the entry and its audit history, avoiding the prohibited include pattern. The explicit field selection keeps payloads lean and follows repository conventions.


187-202: Transaction pattern correctly ensures atomicity.

The use of $transaction to wrap both the audit creation and deletion ensures that both operations succeed or fail together, preventing orphaned audit records or incomplete deletions. This addresses the atomicity concern from previous reviews.

Comment on lines +12 to +14
export const domainWithAtRegex =
/^@[a-zA-Z0-9\u00a1-\uffff]([a-zA-Z0-9\u00a1-\uffff-]*[a-zA-Z0-9\u00a1-\uffff])?(\.[a-zA-Z0-9\u00a1-\uffff]([a-zA-Z0-9\u00a1-\uffff-]*[a-zA-Z0-9\u00a1-\uffff])?)*$/;

Copy link
Member

Choose a reason for hiding this comment

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

Probably worth looking into packages/features/watchlist/lib/utils/normalization.ts

@dosubot dosubot bot added platform Anything related to our platform plan ✨ feature New feature or request labels Oct 14, 2025
@graphite-app graphite-app bot requested a review from a team October 14, 2025 18:14
@alishaz-polymath
Copy link
Member

I just need to test it, looks good overall, will approve once I test it in the morning 🙏

volnei
volnei previously approved these changes Oct 14, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 14, 2025

E2E results are ready!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/features/ee/organizations/pages/settings/other-team-members-view.tsx (1)

195-195: Consider clarifying the margin utilities.

The combination of my-8 and mt-6 is confusing because mt-6 overrides the top margin from my-8, resulting in asymmetric spacing (top: 1.5rem, bottom: 2rem).

Apply this diff for clarity:

-                <hr className="border-subtle my-8 mt-6" />
+                <hr className="border-subtle mb-8 mt-6" />
apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (2)

53-63: Consider adding loading state to delete button.

The delete mutation properly invalidates the query and handles errors. However, the UI provides no visual feedback during the deletion process (e.g., button disabled state, spinner).

To improve UX, you could disable the delete button while the mutation is pending:

const deleteWatchlistEntry = trpc.viewer.organizations.deleteWatchlistEntry.useMutation({
  onSuccess: async () => {
    await utils.viewer.organizations.listWatchlistEntries.invalidate();
    showToast(t("blocklist_entry_deleted"), "success");
    setShowDeleteDialog(false);
    setEntryToDelete(null);
  },
  onError: (error) => {
    showToast(error.message, "error");
  },
});

Then in the ConfirmationDialogContent (line 197-203), pass isPending={deleteWatchlistEntry.isPending} to disable the confirm button during deletion.


107-116: Simplify the complex audit type narrowing.

The type guard logic for extracting the email from audit data is overly complex and brittle. The nested conditional type checking with inline type assertions makes this code hard to maintain.

Consider simplifying the type extraction:

       cell: ({ row }) => {
-        const audit = row.original.audits?.[0] as
-          | { changedByUserId: number | null }
-          | {
-              changedByUser?: { id: number; email: string; name: string | null } | undefined;
-              changedByUserId: number | null;
-            }
-          | undefined;
-        const email =
-          (audit && "changedByUser" in audit ? audit.changedByUser?.email : undefined) ?? undefined;
+        const audit = row.original.audits?.[0];
+        const email = audit && "changedByUser" in audit 
+          ? audit.changedByUser?.email 
+          : undefined;
         return <span className="text-default">{email ?? "—"}</span>;
       },

Or extract this logic into a helper function for reusability and testability:

function getCreatorEmail(audits?: BlocklistEntry["audits"]): string | undefined {
  const audit = audits?.[0];
  if (!audit || !("changedByUser" in audit)) return undefined;
  return audit.changedByUser?.email;
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 921186d and 7387bf7.

📒 Files selected for processing (5)
  • apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (1 hunks)
  • packages/features/ee/organizations/pages/settings/other-team-members-view.tsx (2 hunks)
  • packages/features/ee/organizations/pages/settings/privacy.tsx (2 hunks)
  • packages/features/ee/teams/components/MakeTeamPrivateSwitch.tsx (0 hunks)
  • packages/features/ee/teams/pages/team-settings-view.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/features/ee/teams/components/MakeTeamPrivateSwitch.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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:

  • packages/features/ee/organizations/pages/settings/other-team-members-view.tsx
  • packages/features/ee/teams/pages/team-settings-view.tsx
  • apps/web/modules/settings/organizations/privacy/blocklist-table.tsx
  • packages/features/ee/organizations/pages/settings/privacy.tsx
**/*.{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/features/ee/organizations/pages/settings/other-team-members-view.tsx
  • packages/features/ee/teams/pages/team-settings-view.tsx
  • apps/web/modules/settings/organizations/privacy/blocklist-table.tsx
  • packages/features/ee/organizations/pages/settings/privacy.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/features/ee/organizations/pages/settings/other-team-members-view.tsx
  • packages/features/ee/teams/pages/team-settings-view.tsx
  • apps/web/modules/settings/organizations/privacy/blocklist-table.tsx
  • packages/features/ee/organizations/pages/settings/privacy.tsx
🧬 Code graph analysis (2)
apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (5)
packages/trpc/react/trpc.ts (2)
  • RouterOutputs (143-143)
  • trpc (54-138)
packages/ui/components/toast/showToast.tsx (1)
  • showToast (77-101)
apps/web/modules/settings/organizations/privacy/components/create-blocklist-entry-modal.tsx (1)
  • CreateBlocklistEntryModal (25-173)
apps/web/modules/settings/organizations/privacy/components/blocklist-entry-details-sheet.tsx (1)
  • BlocklistEntryDetailsSheet (20-131)
packages/ui/components/dialog/Dialog.tsx (1)
  • Dialog (35-38)
packages/features/ee/organizations/pages/settings/privacy.tsx (3)
packages/features/data-table/DataTableProvider.tsx (1)
  • DataTableProvider (93-426)
packages/features/data-table/hooks/useSegments.ts (1)
  • useSegments (9-66)
apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (1)
  • BlocklistTable (30-207)
🔇 Additional comments (8)
packages/features/ee/organizations/pages/settings/other-team-members-view.tsx (1)

41-41: LGTM! Minor formatting cleanup.

Removing the trailing space improves code consistency.

packages/features/ee/teams/pages/team-settings-view.tsx (1)

175-182: LGTM! Good cleanup and layout refinement.

The changes improve code quality:

  • Removing the unused useLocale hook from PrivacySettingsView (noted in the summary) eliminates dead code since this component delegates all text rendering to its children.
  • Wrapping MakeTeamPrivateSwitch in a div with mt-6 provides consistent vertical spacing in the layout.

Both changes are appropriate and maintain the component's functionality.

apps/web/modules/settings/organizations/privacy/blocklist-table.tsx (3)

40-49: LGTM: Correct React Query v5 pattern.

The use of keepPreviousData with placeholderData is the correct pattern for React Query v5, preventing jarring UI updates during pagination/filtering.


156-163: LGTM: Manual pagination correctly configured.

The table setup properly uses manualPagination mode with correct pageCount calculation. This delegates pagination to the server-side query, which is the right approach for potentially large datasets.


165-206: LGTM: Well-structured table with proper permission guards.

The component correctly:

  • Wraps table in DataTableWrapper with loading states
  • Conditionally renders create/delete actions based on permissions
  • Separates concerns with dedicated modal/sheet components
  • Provides consistent user feedback via toasts
packages/features/ee/organizations/pages/settings/privacy.tsx (3)

3-10: LGTM: Clean integration of required dependencies.

All necessary imports are added for the blocklist feature integration. The imports follow the project's conventions with proper aliasing (@calcom paths).


12-22: LGTM: Props interface cleanly extended.

The addition of optional watchlistPermissions prop maintains backward compatibility while enabling the new blocklist feature. The permission structure (canRead, canCreate, canDelete) provides proper granular access control.


41-53: LGTM: Proper conditional rendering with data table integration.

The blocklist section is correctly:

  • Gated behind watchlistPermissions?.canRead permission check
  • Wrapped in DataTableProvider with useSegments hook for state management
  • Configured with a reasonable defaultPageSize of 25
  • Using localized strings for heading and description

The BlocklistTable component handles its own create/delete permission checks internally, so only the read permission needs to be verified at this level.

Copy link
Member

@alishaz-polymath alishaz-polymath left a comment

Choose a reason for hiding this comment

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

LGTM 👏

@alishaz-polymath alishaz-polymath enabled auto-merge (squash) October 15, 2025 07:26
Copy link
Contributor

@emrysal emrysal left a comment

Choose a reason for hiding this comment

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

Re-approving Volnei's earlier approval and adding my own stamp.

@alishaz-polymath alishaz-polymath merged commit 5a59bb8 into main Oct 15, 2025
36 of 37 checks passed
@alishaz-polymath alishaz-polymath deleted the feat/blocklist-table branch October 15, 2025 11:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO ✨ feature New feature or request High priority Created by Linear-GitHub Sync ❗️ migrations contains migration files platform Anything related to our platform plan ready-for-e2e size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants