-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Add showDetailedAnalytics flag #3053
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.
|
WalkthroughThis PR introduces a LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS threshold and a showDetailedAnalytics flag. API routes and UI now gate detailed analytics based on program identity and totalCommissions; multiple dashboard components, the sidebar, a hook, and constants were updated. A PoC script was removed and one constant value increased. Changes
Sequence Diagram(s)sequenceDiagram
participant User as Partner User
participant Frontend as Dashboard UI
participant Hook as useProgramEnrollment
participant API as Partner API
participant DB as Database
User->>Frontend: Open program dashboard
Frontend->>Hook: request enrollment (programSlug)
Hook->>API: fetch enrollment (includes program + totalCommissions)
API->>DB: query enrollment by programId/partnerId
DB-->>API: return enrollment with totalCommissions
API-->>Hook: return enrollment
Note right of Hook: compute showDetailedAnalytics = (programSlug ≠ "perplexity") OR (totalCommissions ≥ threshold)
Hook-->>Frontend: { programEnrollment, showDetailedAnalytics }
alt showDetailedAnalytics == true
Frontend->>Frontend: render detailed StatCard, charts, customer links, show Analytics/Events
else showDetailedAnalytics == false
Frontend->>Frontend: render simplified stats, hide customer links, hide Analytics/Events
end
Frontend-->>User: display conditional UI
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (2 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 |
|
@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: 2
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)/events/page.tsx (1)
4-10: Add access control check to Events page to match customers page pattern.The Events page lacks the
showDetailedAnalyticspermission check that the customers page enforces. While the sidebar conditionally hides the "Events" link, users can bypass this by accessing the URL directly. Add a redirect in the Events component or page wrapper to enforce this permission, matching the pattern used incustomers/[customerId]/page-client.tsx:if (!showDetailedAnalytics) { redirect(`/programs/${programSlug}`); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/route.ts(2 hunks)apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts(3 hunks)apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts(3 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/analytics/page.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx(2 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx(2 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx(2 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx(2 hunks)apps/web/lib/partners/constants.ts(1 hunks)apps/web/lib/swr/use-program-enrollment.ts(2 hunks)apps/web/lib/zod/schemas/groups.ts(1 hunks)apps/web/scripts/partners/aggregate-stats.ts(0 hunks)apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx(5 hunks)packages/email/src/templates/partner-deactivated.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- apps/web/scripts/partners/aggregate-stats.ts
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 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/app/(ee)/api/partner-profile/programs/[programId]/events/route.tspackages/email/src/templates/partner-deactivated.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)/analytics/page.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx
📚 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)/analytics/page.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsxapps/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)/customers/[customerId]/page-client.tsx
📚 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)/earnings/earnings-table.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsxpackages/email/src/templates/partner-deactivated.tsxapps/web/lib/partners/constants.tsapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
📚 Learning: 2025-08-18T02:31:22.282Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2756
File: apps/web/ui/webhooks/webhook-header.tsx:20-20
Timestamp: 2025-08-18T02:31:22.282Z
Learning: The Next.js redirect() function can be used in both Server Components and Client Components, as well as Route Handlers and Server Actions. It is not server-only as previously thought.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx
📚 Learning: 2025-08-18T02:31:22.282Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2756
File: apps/web/ui/webhooks/webhook-header.tsx:20-20
Timestamp: 2025-08-18T02:31:22.282Z
Learning: The Next.js redirect() function can be used in both Server Components and Client Components, as well as Route Handlers and Server Actions, according to the official Next.js documentation. It is not server-only.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx
📚 Learning: 2025-08-25T21:03:24.285Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsx:1-1
Timestamp: 2025-08-25T21:03:24.285Z
Learning: In Next.js App Router, Server Components that use hooks can work without "use client" directive if they are only imported by Client Components, as they get "promoted" to run on the client side within the Client Component boundary.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
📚 Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsxapps/web/lib/swr/use-program-enrollment.ts
🧬 Code graph analysis (9)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts (1)
apps/web/lib/partners/constants.ts (1)
LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS(10-10)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx (1)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-42)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx (1)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-42)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (1)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-42)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-42)
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-42)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts (1)
apps/web/lib/partners/constants.ts (1)
LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS(10-10)
apps/web/lib/swr/use-program-enrollment.ts (1)
apps/web/lib/partners/constants.ts (1)
LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS(10-10)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/route.ts (4)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts (1)
GET(14-112)apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts (1)
GET(15-102)apps/web/lib/api/programs/get-program-enrollment-or-throw.ts (1)
getProgramEnrollmentOrThrow(6-67)apps/web/lib/partners/constants.ts (1)
LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS(10-10)
⏰ 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 (7)
packages/email/src/templates/partner-deactivated.tsx (1)
23-23: LGTM! Good use of a generic placeholder.Using "Acme" as a default example is better than referencing a real company name like "Perplexity". This change has no impact on production since actual values are passed at runtime.
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (3)
41-41: LGTM!The addition of the optional
showDetailedAnalyticsfield to theSidebarNavDatatype is well-typed and follows existing patterns.
191-206: LGTM!The conditional rendering of Analytics and Events navigation items based on
showDetailedAnalyticsis clean and follows the existing pattern for conditional menu items.
269-269: LGTM!Properly destructures
showDetailedAnalyticsfrom the hook to use in the navigation data.apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx (2)
73-73: LGTM!Properly destructures
showDetailedAnalyticsfrom the hook for use in conditional rendering.
239-251: LGTM!The conditional rendering between
StatCard(detailed analytics with charts) andStatCardSimple(simple stats) based onshowDetailedAnalyticsis well-implemented. Both component types properly handle all three metrics (clicks, leads, sales) and are correctly wrapped inNumberFlowGroupfor animation coordination.apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/analytics/page.tsx (1)
4-10: Access control is properly enforced; the review comment is incorrect.The Analytics page is protected by
ProgramEnrollmentAuthin the shared(enrolled)layout, which redirects unenrolled users to the application page. This provides the same access control as the previous implementation but at the layout level, which is actually better architecture since it's centralized for all enrolled routes. The API endpoints additionally enforce access viawithPartnerProfilemiddleware andgetProgramEnrollmentOrThrow()validation.Users cannot access the Analytics page directly via URL without being enrolled in the program—they will be redirected to
/programs/[programSlug]/apply.Likely an incorrect or invalid review comment.
|
|
||
| export function ProgramCustomerPageClient() { | ||
| const { programEnrollment } = useProgramEnrollment(); | ||
| const { programEnrollment, showDetailedAnalytics } = useProgramEnrollment(); |
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.
🧩 Analysis chain
Verify the loading state before using showDetailedAnalytics.
The showDetailedAnalytics flag depends on programEnrollment.totalCommissions, which loads asynchronously. The useProgramEnrollment hook returns a loading state, but it's not being destructured or checked here. Consider destructuring the loading state to avoid potential race conditions.
Check if the redirect logic at line 30 should also wait for the program enrollment data to finish loading:
🏁 Script executed:
#!/bin/bash
# Check how other components use the loading state from useProgramEnrollment
rg -nP -C3 'useProgramEnrollment.*loading' --type=tsx --type=tsLength of output: 27
🏁 Script executed:
#!/bin/bash
# Check how other components use useProgramEnrollment hook
rg -nP -C3 'useProgramEnrollment' --type=tsLength of output: 40447
🏁 Script executed:
#!/bin/bash
# Find the useProgramEnrollment hook definition
fd -e ts -e tsx | xargs grep -l 'useProgramEnrollment' | head -5Length of output: 225
🏁 Script executed:
#!/bin/bash
# Check the redirect logic around line 30 in the page-client file
rg -nP -B5 -A5 'redirect' apps/web/app/\(ee\)/partners.dub.co/\(dashboard\)/programs/\[programSlug\]/\(enrolled\)/customers/\[customerId\]/page-client.tsxLength of output: 879
🏁 Script executed:
#!/bin/bash
# Find and display the useProgramEnrollment hook implementation
cat apps/web/lib/swr/use-program-enrollment.tsLength of output: 1117
Check the loading state from useProgramEnrollment before redirecting based on showDetailedAnalytics.
The useProgramEnrollment hook returns a loading state, but the redirect condition at line 30 only checks isLoading from usePartnerCustomer. During initial load, programEnrollment is undefined, so totalCommissions defaults to 0, which can make showDetailedAnalytics false prematurely for the "perplexity" program. This causes a redirect before the enrollment data finishes loading.
Destructure loading from useProgramEnrollment and update the redirect condition to avoid redirecting while enrollment data is still loading:
const { programEnrollment, showDetailedAnalytics, loading } = useProgramEnrollment();
Then update line 30 to:
if ((!customer && !isLoading) || (!showDetailedAnalytics && !loading)) {
redirect(`/programs/${programSlug}`);
}
🤖 Prompt for AI Agents
In
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
around line 20, the redirect logic uses showDetailedAnalytics before program
enrollment finishes loading; destructure loading from useProgramEnrollment
(const { programEnrollment, showDetailedAnalytics, loading } =
useProgramEnrollment()) and update the redirect condition to guard against
redirecting while enrollment is loading (i.e., only redirect when (!customer &&
!isLoading) || (!showDetailedAnalytics && !loading)), ensuring you don't
redirect for the "perplexity" program prematurely.
| if ((!customer && !isLoading) || !showDetailedAnalytics) { | ||
| redirect(`/programs/${programSlug}`); | ||
| } |
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.
Potential race condition in redirect logic.
The redirect condition checks !showDetailedAnalytics, but this flag depends on programEnrollment.totalCommissions which may not be loaded yet. During the loading state, totalCommissions defaults to 0, which could trigger a premature redirect for "perplexity" program users before their commission data loads.
Consider checking the loading state from useProgramEnrollment before redirecting:
- const { programEnrollment, showDetailedAnalytics } = useProgramEnrollment();
+ const { programEnrollment, showDetailedAnalytics, loading: isProgramLoading } = useProgramEnrollment();
const { programSlug, customerId } = useParams<{
programSlug: string;
customerId: string;
}>();
const { data: customer, isLoading } = usePartnerCustomer({
customerId,
});
- if ((!customer && !isLoading) || !showDetailedAnalytics) {
+ if ((!customer && !isLoading) || (!showDetailedAnalytics && !isProgramLoading)) {
redirect(`/programs/${programSlug}`);
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx
around lines 30-32, the redirect uses showDetailedAnalytics which depends on
programEnrollment.totalCommissions that may be 0 while enrollment data is still
loading, causing a premature redirect; update the condition to also wait for the
enrollment loading flag (e.g., isLoading from useProgramEnrollment or an
explicit enrollmentLoading) so the redirect only runs when both customer and
enrollment data have finished loading and showDetailedAnalytics is definitively
false.
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
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)/api/partner-profile/programs/[programId]/events/route.ts (1)
27-35: Extract hardcoded program ID to a constant—it's duplicated across 3 files.The program ID
"prog_1K0QHV7MP3PR05CJSCF5VN93X"is hardcoded in 3 API route files with identical access control logic:
apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts(lines 28-29)apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts(lines 28-29)apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/route.ts(lines 47-48)Add to
apps/web/lib/partners/constants.ts:export const DETAILED_ANALYTICS_PROGRAM_ID = "prog_1K0QHV7MP3PR05CJSCF5VN93X";Update all 3 files to import and use this constant instead of the hardcoded string.
♻️ Duplicate comments (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1)
30-32: Race condition in redirect logic (duplicate of previous review).The redirect condition uses
!showDetailedAnalyticswithout waiting forprogramEnrollmentto finish loading. During the loading state,showDetailedAnalyticsincorrectly evaluates tofalsefor "perplexity" program users (sincetotalCommissionsdefaults to 0), causing premature redirects before the enrollment data is available.The previous review comments correctly identified this issue and suggested destructuring the
loadingstate fromuseProgramEnrollment()and updating the redirect condition to:if ((!customer && !isLoading) || (!showDetailedAnalytics && !loading)) { redirect(`/programs/${programSlug}`); }This issue should be addressed as recommended in the previous review.
🧹 Nitpick comments (3)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx (1)
69-69: Consider handling the loading state forshowDetailedAnalytics.During initial load,
programEnrollmentis undefined, causingtotalCommissionsto default to 0. For the "perplexity" program, this makesshowDetailedAnalyticstemporarily false until enrollment data loads, briefly showingStatsBadgeinstead ofStatsCharts. While this is less critical than redirect logic (as seen in other files), it may cause a brief UI flicker.Consider destructuring
loadingfromuseProgramEnrollment()and displaying a loading state when data is not yet available.apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1)
191-206: Consider showing loading state for conditional menu items.When
programEnrollmentis still loading,showDetailedAnalyticsmay be incorrectlyfalsefor "perplexity" program users, causing Analytics and Events menu items to be hidden briefly until data loads. This could be confusing to users who expect to see these items.Consider showing a loading indicator or skeleton for the Insights section while
loadingis true, similar to how the loading state is handled in other parts of the application.apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx (1)
142-145: Consider handling loading state for conditional customer links.When
programEnrollmentis still loading,showDetailedAnalyticsmay be incorrectlyfalsefor "perplexity" program users, making customer links non-clickable (href=undefined) until data loads. While less critical than redirect issues, this could briefly confuse users.Consider checking the
loadingstate fromuseProgramEnrollment()and either:
- Show a loading indicator in the customer cell during load, or
- Default to enabling the link during loading (optimistic approach)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/route.ts(2 hunks)apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts(3 hunks)apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts(3 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/analytics/page.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx(2 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx(2 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx(2 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx(2 hunks)apps/web/lib/partners/constants.ts(1 hunks)apps/web/lib/swr/use-program-enrollment.ts(2 hunks)apps/web/lib/zod/schemas/groups.ts(1 hunks)apps/web/scripts/partners/aggregate-stats.ts(0 hunks)apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx(5 hunks)packages/email/src/templates/partner-deactivated.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- apps/web/scripts/partners/aggregate-stats.ts
🧰 Additional context used
🧠 Learnings (9)
📚 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)/events/page.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsxapps/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)/analytics/page.tsx
📚 Learning: 2025-08-18T02:31:22.282Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2756
File: apps/web/ui/webhooks/webhook-header.tsx:20-20
Timestamp: 2025-08-18T02:31:22.282Z
Learning: The Next.js redirect() function can be used in both Server Components and Client Components, as well as Route Handlers and Server Actions. It is not server-only as previously thought.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx
📚 Learning: 2025-08-18T02:31:22.282Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2756
File: apps/web/ui/webhooks/webhook-header.tsx:20-20
Timestamp: 2025-08-18T02:31:22.282Z
Learning: The Next.js redirect() function can be used in both Server Components and Client Components, as well as Route Handlers and Server Actions, according to the official Next.js documentation. It is not server-only.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 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/app/(ee)/api/partner-profile/programs/[programId]/events/route.tspackages/email/src/templates/partner-deactivated.tsx
📚 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)/page-client.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsxpackages/email/src/templates/partner-deactivated.tsxapps/web/lib/partners/constants.ts
📚 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
📚 Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.
Applied to files:
apps/web/lib/swr/use-program-enrollment.tsapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
📚 Learning: 2025-08-25T21:03:24.285Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/bounty-card.tsx:1-1
Timestamp: 2025-08-25T21:03:24.285Z
Learning: In Next.js App Router, Server Components that use hooks can work without "use client" directive if they are only imported by Client Components, as they get "promoted" to run on the client side within the Client Component boundary.
Applied to files:
apps/web/lib/swr/use-program-enrollment.tsapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.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)/analytics/page.tsx
🔇 Additional comments (10)
packages/email/src/templates/partner-deactivated.tsx (1)
23-23: Clarify if this change is intentional.The default
program.namevalue was changed from "Perplexity" to "Acme". While "Acme" is a more generic placeholder, this change appears unrelated to the PR's stated objective of adding ashowDetailedAnalyticsflag.Is this an intentional cleanup/standardization, or was it accidentally included from separate work?
apps/web/lib/zod/schemas/groups.ts (1)
22-22: Verify the scope and business justification for increasing MAX_ADDITIONAL_PARTNER_LINKS from 20 to 100.The increase is correctly implemented and consistently propagated throughout the codebase—it validates the schema, gates UI controls, and shows error messages appropriately. However, this feature change for partner link limits appears orthogonal to the
showDetailedAnalyticsflag implementation (which gates access based on commission thresholds).Please confirm this constant increase is intentional and within the PR scope by reviewing:
- The original PR description or commit message
- Any linked issues or business requirements
- Performance/storage implications for 100 vs. 20 links
apps/web/lib/partners/constants.ts (1)
10-10: Threshold constant aligns with usage.Value and naming look consistent with the downstream gating checks. 👍
apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/route.ts (1)
12-20: Gating to count-only analytics looks solid.Pulling
totalCommissionshere and switching to the aggregate query under the threshold keeps the sensitive timeseries hidden while still letting the composite event through. Nicely done.Also applies to: 47-50
apps/web/app/(ee)/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts (1)
17-30: Customer detail gating mirrors analytics guard.Re-using the threshold for this route keeps the exposure consistent with analytics. Looks good.
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx (1)
74-74: UI toggle matches the new flag.Pulling
showDetailedAnalyticsfrom the hook and swapping to the simplified cards when it’s false keeps the front end perfectly in sync with the API guardrails.Also applies to: 239-250
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx (1)
4-10: LGTM! Simplified component structure.The removal of async params handling and redirect logic makes this component cleaner. Access control is now handled via the
showDetailedAnalyticsflag consumed by parent components or navigation logic.apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/analytics/page.tsx (1)
4-10: LGTM! Consistent with the simplified component pattern.Similar to the Events page, this removes async params handling and redirect logic, making the component cleaner. Access control is handled via navigation and parent component logic using the
showDetailedAnalyticsflag.apps/web/app/(ee)/api/partner-profile/programs/[programId]/events/route.ts (2)
6-6: LGTM!The import is correctly added and necessary for the new access control logic.
17-17: No verification needed—totalCommissions is properly available in the return type.The
totalCommissionsfield is a base field on the ProgramEnrollment model (defined inpackages/prisma/schema/program.prisma), not a relation. Base fields are always included in Prisma query results regardless of theincludeparameter. Theincludeobject only specifies which relations to eagerly load (program and links in this case). The destructuring at line 17 is correct.
| showDetailedAnalytics: | ||
| programSlug !== "perplexity" || | ||
| (programEnrollment?.totalCommissions ?? 0) >= | ||
| LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS, |
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.
Race condition: showDetailedAnalytics evaluates incorrectly during loading.
When programEnrollment is still loading, it's undefined, so totalCommissions defaults to 0. For the "perplexity" program, this makes showDetailedAnalytics false during the loading state, even if the user should have access once data loads.
Components that use this flag for redirects (e.g., page-client.tsx at line 30) will redirect prematurely before enrollment data finishes loading. This causes users to be redirected away from pages they should have access to.
Consider one of these solutions:
Solution 1: Return showDetailedAnalytics as true during loading to avoid premature redirects:
return {
programEnrollment,
showDetailedAnalytics:
+ status === "loading" ||
programSlug !== "perplexity" ||
(programEnrollment?.totalCommissions ?? 0) >=
LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS,
error,
loading: status === "loading" || isLoading,
};Solution 2: Make consumers check the loading state before using showDetailedAnalytics. However, this is error-prone as it requires every consumer to remember to check loading.
📝 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.
| showDetailedAnalytics: | |
| programSlug !== "perplexity" || | |
| (programEnrollment?.totalCommissions ?? 0) >= | |
| LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS, | |
| showDetailedAnalytics: | |
| status === "loading" || | |
| programSlug !== "perplexity" || | |
| (programEnrollment?.totalCommissions ?? 0) >= | |
| LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS, |
🤖 Prompt for AI Agents
In apps/web/lib/swr/use-program-enrollment.ts around lines 35 to 38,
showDetailedAnalytics currently treats an undefined programEnrollment as
totalCommissions=0 which causes premature false during loading; change the logic
so that while enrollment data is loading you return true for
showDetailedAnalytics (e.g., detect loading/undefined enrollment and, for
programSlug "perplexity", short-circuit to true) so consumers don’t redirect
before data arrives; ensure once the real enrollment is loaded the original
commission threshold check is applied.
Summary by CodeRabbit
New Features
Improvements
Chores
Style