Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Oct 22, 2025

Summary by CodeRabbit

  • New Features

    • Partners can now control customer data sharing in advanced settings; customer email addresses are automatically masked when data sharing is disabled.
  • Refactor

    • Consolidated analytics export functionality for improved consistency across analytics and events views.

@vercel
Copy link
Contributor

vercel bot commented Oct 22, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Oct 22, 2025 3:08am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

Introduces customer data sharing control flag to partner program enrollments with conditional email masking in API responses. Refactors analytics export components from default to named exports and consolidates analytics/events export logic by removing redundant event-options component.

Changes

Cohort / File(s) Summary
Customer Data Sharing: Database & Schema
packages/prisma/schema/program.prisma, apps/web/lib/zod/schemas/partners.ts, apps/web/lib/zod/schemas/partner-profile.ts
Added optional customerDataSharingEnabledAt DateTime field to ProgramEnrollment model. Extended EnrolledPartnerSchemaExtended and PartnerProfileCustomerSchema with nullable date field; email is now picked and exposed directly in partner-profile schema.
Customer Data Sharing: API & Backend
apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts, apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts, apps/web/lib/actions/partners/update-partner-enrollment.ts
Retrieves customerDataSharingEnabledAt from program enrollment and applies conditional email masking (transforms to masked format when sharing disabled). Updated partner enrollment action to accept and persist customerDataSharingEnabledAt in database payload.
Customer Data Sharing: UI
apps/web/ui/partners/partner-advanced-settings-modal.tsx
Added customer data sharing toggle switch to partner advanced settings modal. Wired toggle to form state, dirty tracking, and validation. Updated partner prop type from EnrolledPartnerProps to EnrolledPartnerExtendedProps.
Analytics Export Refactoring
apps/web/ui/analytics/analytics-export-button.tsx, apps/web/ui/analytics/events/events-export-button.tsx, apps/web/ui/analytics/analytics-options.tsx, apps/web/ui/analytics/events/events-options.tsx, apps/web/ui/analytics/toggle.tsx
Converted export button components from default to named exports (AnalyticsExportButton, EventsExportButton). Consolidated separate analytics and events options into single AnalyticsOptions component with page prop-based rendering. Deleted redundant events-options.tsx. Updated toggle to import named AnalyticsOptions and render with page context.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes span database schema, API logic, UI components, and internal refactoring. While individual edits are straightforward (conditional email masking, component export changes, form state wiring), the mix of heterogeneous changes across multiple layers requires separate reasoning for each cohort. No dense logic or complex control flow, but moderate file spread and feature scope.

Possibly related PRs

  • Add messagingEnabled props #2840: Modifies partner-profile program enrollment surfaces and getProgramEnrollmentOrThrow usage to include additional per-enrollment flags, similar pattern to customerDataSharingEnabledAt addition.
  • Add list partners to OpenAPI spec #2677: Updates partners Zod schemas (apps/web/lib/zod/schemas/partners.ts), same file modified here for adding customerDataSharingEnabledAt field.
  • Discount code attribution tracking #2620: Modifies partner-profile API routes (program customer/events handlers) and program enrollment retrieval logic, overlapping endpoint changes.

Suggested reviewers

  • devkiran

Poem

🐰 A rabbit's tale of sharing care,
With data shields and toggles fair,
Email masked when sharing's nay,
Export buttons find new way,
Consolidated exports shine,
Partner settings now align! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add customerDataSharingEnabledAt feature" directly and accurately describes the primary objective of the changeset. The PR implements a new feature by adding a customerDataSharingEnabledAt field across multiple layers: the database schema (Prisma), validation schemas (Zod), API routes, UI components, and server actions. The title is concise, clear, and specific—it avoids vague terminology and accurately conveys the main change from the developer's perspective. The secondary refactoring of export button components (changing to named exports) is supporting infrastructure rather than the primary focus.
✨ 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 customer-data-sharing

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcde650 and 545ff35.

📒 Files selected for processing (2)
  • apps/web/lib/zod/schemas/partners.ts (1 hunks)
  • apps/web/ui/partners/partner-advanced-settings-modal.tsx (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/ui/partners/partner-advanced-settings-modal.tsx (1)
apps/web/lib/types.ts (1)
  • EnrolledPartnerExtendedProps (460-462)
⏰ 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: build
🔇 Additional comments (8)
apps/web/ui/partners/partner-advanced-settings-modal.tsx (7)

5-9: LGTM!

Import additions are correct and necessary for the customer data sharing feature.


22-25: LGTM!

FormData type correctly extended with the new field.


34-40: LGTM!

The component prop type is correctly updated to EnrolledPartnerExtendedProps, and the local state initialization properly converts the timestamp to a boolean.


63-69: Verify timestamp behavior when re-enabling.

When toggling customer data sharing on, the handler always sets a new Date(), which means the original timestamp is lost if a user toggles off and back on without saving. For example, if the feature was originally enabled on 2025-01-15, toggling it off and on again would replace that with the current date.

Consider whether this is intentional (to track "last enabled at") or if you should preserve the original timestamp when re-enabling. If the latter, you could store the original value and restore it:

+const originalTimestamp = partner.customerDataSharingEnabledAt;
+
 const handleCustomerDataSharingToggle = (checked: boolean) => {
   setHasCustomerDataSharing(checked);
-  setValue("customerDataSharingEnabledAt", checked ? new Date() : null, {
+  setValue("customerDataSharingEnabledAt", checked ? (originalTimestamp || new Date()) : null, {
     shouldDirty: true,
     shouldValidate: true,
   });
 };

88-88: LGTM!

The submit payload correctly includes the new field.


130-150: LGTM!

The UI section is well-structured with clear labeling and proper Switch integration. The explanatory text helps users understand the feature's purpose.


164-164: LGTM!

Disabling the Save button when the form is not dirty provides good UX by preventing unnecessary submissions.

apps/web/lib/zod/schemas/partners.ts (1)

405-405: LGTM! Implementation follows established patterns.

The customerDataSharingEnabledAt field is correctly implemented with the appropriate type (z.date().nullish()) and naming convention. The exclusion from exportPartnerColumns is intentional and consistent with the pattern for internal timestamp tracking fields like lastLeadAt and lastConversionAt, which are also not exported. Only user-facing business timestamps (createdAt, payoutsEnabledAt) are included in exports.


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
apps/web/lib/zod/schemas/programs.ts (1)

126-131: Schema looks good; prefer nullable over nullish for stable API shape.

Switch to .nullable() to avoid “undefined” creeping into consumers; DB returns null anyway.

-  customerDataSharingEnabledAt: z
-    .date()
-    .nullish()
+  customerDataSharingEnabledAt: z
+    .date()
+    .nullable()
     .describe(
       "The date when customer data sharing was enabled for this partner.",
     ),
apps/web/ui/partners/partner-advanced-settings-modal.tsx (1)

60-66: Avoid overwriting an existing enabledAt when toggling back on.

If “enabledAt” represents the first enable time, preserve the prior timestamp instead of always writing a fresh Date.

- setValue("customerDataSharingEnabledAt", checked ? new Date() : null, {
+ setValue(
+   "customerDataSharingEnabledAt",
+   checked
+     ? (getValues?.("customerDataSharingEnabledAt") ?? new Date())
+     : null,
+   {
      shouldDirty: true,
      shouldValidate: true,
-   });
+   },
+ );

If “last enabled” is intended, ignore this.

apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts (1)

80-87: Deduplicate masking logic via a shared helper.

Inline Zod with regex appears in multiple places; extract to a util to ensure one consistent policy (and simplify tests).

-  PartnerProfileCustomerSchema.extend({
-    email: customerDataSharingEnabledAt
-      ? z.string()
-      : z
-          .string()
-          .transform((email) => email.replace(/(?<=^.).+(?=.@)/, "****")),
-  }).parse({
+  PartnerProfileCustomerSchema.parse({
+    ...transformCustomer({
+      ...customer,
+      email:
+        maskEmail(
+          customer.email || customer.name || generateRandomName(),
+          Boolean(customerDataSharingEnabledAt),
+        ),
+    }),

Utility (new file, e.g., lib/privacy/email.ts):

export function maskEmail(email: string, share: boolean) {
  if (share) return email;
  return email.includes("@")
    ? email.replace(/(?<=^.).+(?=@)/, "****")
    : "****";
}

Note: Regex adjusted to (?=@) to avoid leaking the last local-part char.

apps/web/lib/actions/partners/update-partner-enrollment.ts (1)

54-56: Optional: guard against future timestamps and improve audit trail.

  • Reject future dates to prevent clock skews.
  • Include old/new values in audit log for traceability.
-  customerDataSharingEnabledAt,
+  customerDataSharingEnabledAt,

Add validation to the schema:

-  customerDataSharingEnabledAt: z.coerce.date().nullable(),
+  customerDataSharingEnabledAt: z.coerce
+    .date()
+    .nullable()
+    .refine((d) => !d || d.getTime() <= Date.now(), "enabledAt cannot be in the future"),

Enhance audit log payload (after update):

- description: `Partner ${partnerId} enrollment updated`,
+ description: `Partner ${partnerId} enrollment updated`,
+ metadata: {
+   changes: {
+     tenantId,
+     customerDataSharingEnabledAt: programEnrollment.customerDataSharingEnabledAt,
+   },
+ },
apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts (1)

65-75: Use shared email-masking helper to keep policy consistent.

Same masking logic as customers route; prefer a single helper (e.g., maskEmail) and avoid inline regex.

-  customer: z
-    .object({
-      id: z.string(),
-      email: customerDataSharingEnabledAt
-        ? z.string()
-        : z
-            .string()
-            .transform((email) =>
-              email.replace(/(?<=^.).+(?=.@)/, "****"),
-            ),
-    })
-    .parse(event.customer),
+  customer: z
+    .object({
+      id: z.string(),
+      email: z.string().transform((e) =>
+        maskEmail(e, Boolean(customerDataSharingEnabledAt)),
+      ),
+    })
+    .parse(event.customer),

Also consider updating the regex to (?=@) to avoid leaking the last local-part character.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e2a92f and b38c0c8.

📒 Files selected for processing (7)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts (2 hunks)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts (2 hunks)
  • apps/web/lib/actions/partners/update-partner-enrollment.ts (2 hunks)
  • apps/web/lib/zod/schemas/partner-profile.ts (1 hunks)
  • apps/web/lib/zod/schemas/programs.ts (1 hunks)
  • apps/web/ui/partners/partner-advanced-settings-modal.tsx (7 hunks)
  • packages/prisma/schema/program.prisma (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
PR: dubinc/dub#2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-enrollment.ts
🧬 Code graph analysis (3)
apps/web/lib/actions/partners/update-partner-enrollment.ts (1)
apps/web/lib/actions/safe-action.ts (1)
  • authActionClient (33-82)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts (3)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts (1)
  • GET (14-84)
apps/web/lib/api/programs/get-program-enrollment-or-throw.ts (1)
  • getProgramEnrollmentOrThrow (6-67)
apps/web/lib/zod/schemas/partner-profile.ts (1)
  • PartnerProfileCustomerSchema (97-104)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts (1)
apps/web/lib/api/programs/get-program-enrollment-or-throw.ts (1)
  • getProgramEnrollmentOrThrow (6-67)
⏰ 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: build
🔇 Additional comments (3)
apps/web/ui/partners/partner-advanced-settings-modal.tsx (1)

161-161: Nice UX touch with Save disabled until dirty.

Prevents no-op submissions. LGTM.

packages/prisma/schema/program.prisma (1)

98-104: Clarify semantics: field currently tracks "last enabled" not "first enabled", and consider adding an index.

The current implementation (line 62 of partner-advanced-settings-modal.tsx) overwrites the timestamp with new Date() on each toggle, making this field "last enabled at" rather than "first enabled at" despite its name. If audit history is needed, you'll need a separate solution (audit table or history).

For performance: the field is queried in multiple API routes to check if sharing is enabled (used for access control). Adding an index on this nullable column can improve query performance at scale.

apps/web/lib/zod/schemas/partner-profile.ts (1)

97-104: Email masking is already properly implemented in the sole route using PartnerProfileCustomerSchema.

Verification confirms the [customerId] route applies conditional email masking based on customerDataSharingEnabledAt: when false, emails are transformed to mask the middle characters (e.g., "u****@example.com"). Since this is the only route using the schema and masking is already in place at the edge, no unintended exposures exist. The suggested maskEmail helper extraction is not critical given the single usage.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b38c0c8 and 9d226a6.

📒 Files selected for processing (7)
  • apps/web/lib/actions/partners/update-partner-enrollment.ts (2 hunks)
  • apps/web/ui/analytics/analytics-export-button.tsx (1 hunks)
  • apps/web/ui/analytics/analytics-options.tsx (1 hunks)
  • apps/web/ui/analytics/events/events-export-button.tsx (2 hunks)
  • apps/web/ui/analytics/events/events-options.tsx (0 hunks)
  • apps/web/ui/analytics/toggle.tsx (2 hunks)
  • apps/web/ui/partners/partner-advanced-settings-modal.tsx (7 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/ui/analytics/events/events-options.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/ui/partners/partner-advanced-settings-modal.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
PR: dubinc/dub#2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.

Applied to files:

  • apps/web/ui/analytics/toggle.tsx
🧬 Code graph analysis (3)
apps/web/ui/analytics/analytics-options.tsx (2)
apps/web/ui/analytics/analytics-export-button.tsx (1)
  • AnalyticsExportButton (7-59)
apps/web/ui/analytics/events/events-export-button.tsx (1)
  • EventsExportButton (7-62)
apps/web/lib/actions/partners/update-partner-enrollment.ts (1)
apps/web/lib/actions/safe-action.ts (1)
  • authActionClient (33-82)
apps/web/ui/analytics/toggle.tsx (1)
apps/web/ui/analytics/analytics-options.tsx (1)
  • AnalyticsOptions (8-39)
⏰ 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: build
🔇 Additional comments (8)
apps/web/ui/analytics/analytics-export-button.tsx (1)

7-59: LGTM - Clean refactor to named export.

The conversion from default export to named export AnalyticsExportButton is clean and maintains the same props signature and functionality.

apps/web/ui/analytics/analytics-options.tsx (2)

5-6: LGTM - Proper named imports.

Both export button components are correctly imported as named exports.


8-21: LGTM - Clean conditional rendering pattern.

The component correctly accepts a page discriminator prop and conditionally renders the appropriate export button. Both buttons receive the same setOpenPopover prop for consistent popover control.

apps/web/ui/analytics/toggle.tsx (2)

34-34: LGTM - Updated to named import.

Correctly updated to import AnalyticsOptions as a named export, aligning with the refactored analytics-options.tsx.


273-273: LGTM - Correct prop passthrough.

The page prop is correctly passed to AnalyticsOptions, allowing it to render the appropriate export button based on whether the current view is analytics or events.

apps/web/ui/analytics/events/events-export-button.tsx (3)

3-3: LGTM - Required imports added.

Correctly added Dispatch and SetStateAction imports to support the new prop signature.


7-11: LGTM - Standardized prop signature.

The prop signature change from optional onClick callback to required setOpenPopover aligns with AnalyticsExportButton and creates a consistent control pattern across both export buttons.


58-58: LGTM - Correct popover state management.

The setOpenPopover(false) call correctly closes the popover after triggering the export, maintaining consistency with the AnalyticsExportButton implementation.

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

✅ Actions performed

Full review triggered.

@steven-tey steven-tey merged commit c03e98e into main Oct 22, 2025
7 checks passed
@steven-tey steven-tey deleted the customer-data-sharing branch October 22, 2025 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants