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

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Nov 4, 2025

Summary by CodeRabbit

  • New Features
    • Added analytics display toggle to switch between "full" and "cards" views in partner program dashboards; cards view auto-selected for many links or when detailed analytics are off.
    • Display mode is exposed to dashboard components so cards vs full controls which stats and charts are shown.
    • Customer profiles now show a nullable name when customer data sharing is enabled; email is no longer always shown publicly.

@vercel
Copy link
Contributor

vercel bot commented Nov 4, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 4, 2025 6:41pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 4, 2025

Walkthrough

Adds conditional exposure of a customer name field in the partner-profile API and shows the name in the enrolled-customer UI; introduces a "full"/"cards" display toggle for partner links, exposes displayOption on PartnerLinksContext, and uses it to control StatsBadge/StatsCharts rendering.

Changes

Cohort / File(s) Summary
Partner profile customer API schema
apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts
Replaces unconditional email extension with a conditional name: z.string().nullish() added only when customerDataSharingEnabledAt is truthy.
Enrolled customer page (UI)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
Renders customer's name (h1) when present and email (span) when present; replaces previous single-email render branch.
Partner links page & context
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
Adds ToggleGroup UI for "full" vs "cards", manages displayOption state (auto-switching based on link count / showDetailedAnalytics), and exposes displayOption in PartnerLinksContext.Provider.
Partner link card rendering
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
Uses usePartnerLinksContext().displayOption to decide showing StatsBadge (cards) vs StatsCharts (full); StatsBadge wrapper becomes Link when detailed analytics are enabled, otherwise div.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Links Page UI
  participant Context as PartnerLinksContext
  participant Card as PartnerLinkCard
  participant Router as Router/Link

  rect rgb(235,245,255)
  UI->>Context: initialize displayOption ("full"|"cards")\n(based on showDetailedAnalytics & link count)
  Context-->>UI: provide displayOption
  end

  UI->>Card: render each PartnerLinkCard (reads context)
  alt displayOption == "cards"
    Card->>Card: render StatsBadge (wrapped by Link if showDetailedAnalytics)
    Card-->>Router: optional navigate on Badge click
  else displayOption == "full"
    Card->>Card: render StatsCharts
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to the conditional schema extension in the API route (ensure runtime/typing alignment).
  • Verify displayOption propagation through PartnerLinksContext and all consumers (partner-link-card, other descendants).
  • Check auto-switching logic (link count threshold and interactions with showDetailedAnalytics) and potential accessibility/navigation changes when StatsBadge becomes a Link.

Possibly related PRs

Poem

🐰 I hopped through code to add a name and switch a view,
Cards or full — whichever suits you,
Context carries the choice along the trail,
Badges link or stay a div without fail,
A tiny rabbit cheers the merge with a happy tail.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Improvements to the partners dashboard' is vague and generic, using non-descriptive language that doesn't convey specific meaningful information about the actual changes made. Consider using a more specific title that highlights the main changes, such as 'Add customer name display and link view toggle to partners dashboard' to better communicate the primary modifications.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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 partners-improvements

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx (1)

51-119: Don’t wipe out the user’s display choice on every SWR refresh.

When there are >5 links (or showDetailedAnalytics flips), this effect fires on every SWR revalidation because links is a new array reference. That immediately forces displayOption back to "cards", so a partner who switches to the "full" view loses the charts again as soon as the list revalidates (focus change, interval, mutate, etc.). Please only apply the auto-default when the user hasn’t manually overridden it, and still force "cards" when analytics access is revoked.

One way to do that:

-import { createContext, useContext, useEffect, useState } from "react";
+import { createContext, useContext, useEffect, useRef, useState } from "react";

-const [displayOption, setDisplayOption] = useState<"full" | "cards">("full");
+const [displayOption, setDisplayOption] = useState<"full" | "cards">(
+  !showDetailedAnalytics || (links?.length ?? 0) > 5 ? "cards" : "full",
+);
+const userSelectedDisplayOptionRef = useRef(false);

-useEffect(() => {
-  if ((links && links.length > 5) || !showDetailedAnalytics) {
-    setDisplayOption("cards");
-  } else {
-    setDisplayOption("full");
-  }
-}, [links, showDetailedAnalytics]);
+useEffect(() => {
+  if (!showDetailedAnalytics) {
+    userSelectedDisplayOptionRef.current = false;
+    setDisplayOption("cards");
+    return;
+  }
+  if (!userSelectedDisplayOptionRef.current) {
+    setDisplayOption((links?.length ?? 0) > 5 ? "cards" : "full");
+  }
+}, [links, showDetailedAnalytics]);

-              selectAction={(option) =>
-                setDisplayOption(option as "full" | "cards")
-              }
+              selectAction={(option) => {
+                userSelectedDisplayOptionRef.current = true;
+                setDisplayOption(option as "full" | "cards");
+              }}

That keeps the automatic defaults while respecting an explicit toggle choice.

🧹 Nitpick comments (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1)

51-59: Type the optional customer name instead of indexing the object.

customer["name"] is bypassing the schema’s type information, so TypeScript can’t help us if the contract changes. Now that the backend emits an optional name, please extend the inferred customer type/Zod schema to include this field (as optional) and read it via customer.name. That keeps the UI type-safe without resorting to string indexing.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ce3eae and 3b3692a.

📒 Files selected for processing (4)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx (5 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
📚 Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 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/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/cards/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions within card components

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
🧬 Code graph analysis (2)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (3)
apps/web/lib/swr/use-program-enrollment.ts (1)
  • useProgramEnrollment (8-42)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx (1)
  • usePartnerLinksContext (34-42)
packages/email/src/react-email.d.ts (1)
  • Link (14-14)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx (2)
apps/web/lib/swr/use-program-enrollment.ts (1)
  • useProgramEnrollment (8-42)
packages/ui/src/toggle-group.tsx (1)
  • ToggleGroup (15-85)
⏰ 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

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 4, 2025

✅ Actions performed

Full review triggered.

@steven-tey steven-tey merged commit 153378e into main Nov 4, 2025
9 checks passed
@steven-tey steven-tey deleted the partners-improvements branch November 4, 2025 18:48
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 3ce3eae and 3b3692a.

📒 Files selected for processing (4)
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx (5 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
  • apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
📚 Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 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/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/cards/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions within card components

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/page-client.tsx

Comment on lines +93 to 94
...(customerDataSharingEnabledAt && { name: z.string().nullish() }),
}).parse({
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against spreading null into the schema extension.

When customer data sharing isn’t enabled, customerDataSharingEnabledAt is null, so customerDataSharingEnabledAt && { … } yields null, and { ...(null) } throws TypeError: Cannot convert undefined or null to object. That would cause this endpoint to 500 for every program that hasn’t opted into sharing. Please gate the extension with a ternary (or hoist the schema selection) before parsing.

-  return NextResponse.json(
-    PartnerProfileCustomerSchema.extend({
-      ...(customerDataSharingEnabledAt && { name: z.string().nullish() }),
-    }).parse({
+  const customerSchema = customerDataSharingEnabledAt
+    ? PartnerProfileCustomerSchema.extend({
+        name: z.string().nullish(),
+      })
+    : PartnerProfileCustomerSchema;
+
+  return NextResponse.json(
+    customerSchema.parse({
📝 Committable suggestion

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

Suggested change
...(customerDataSharingEnabledAt && { name: z.string().nullish() }),
}).parse({
const customerSchema = customerDataSharingEnabledAt
? PartnerProfileCustomerSchema.extend({
name: z.string().nullish(),
})
: PartnerProfileCustomerSchema;
return NextResponse.json(
customerSchema.parse({
🤖 Prompt for AI Agents
In
apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts
around lines 93-94, the object spread uses ...(customerDataSharingEnabledAt && {
name: z.string().nullish() }) which evaluates to null when
customerDataSharingEnabledAt is null and causes a TypeError; change this to a
safe gate such as using a ternary (customerDataSharingEnabledAt ? { name:
z.string().nullish() } : {}) or select/compose the schema before calling parse
so you never spread null/undefined into the schema extension.

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