-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Improvements to the partners dashboard #3061
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds 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 Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
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
showDetailedAnalyticsflips), this effect fires on every SWR revalidation becauselinksis a new array reference. That immediately forcesdisplayOptionback 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 optionalname, please extend the inferred customer type/Zod schema to include this field (as optional) and read it viacustomer.name. That keeps the UI type-safe without resorting to string indexing.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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
| ...(customerDataSharingEnabledAt && { name: z.string().nullish() }), | ||
| }).parse({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| ...(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.
Summary by CodeRabbit