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 3, 2025

Summary by CodeRabbit

  • New Features

    • Tiered analytics: partners meeting a new commissions threshold now see detailed analytics; others see simplified summaries.
  • Improvements

    • Customer links and analytics content now toggle based on program performance.
    • Increased maximum additional partner links from 20 to 100.
    • Analytics and events pages simplified for more consistent rendering.
  • Chores

    • Removed an internal aggregation proof-of-concept script.
  • Style

    • Default program name in partner deactivated emails changed to "Acme".

@vercel
Copy link
Contributor

vercel bot commented Nov 3, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 3, 2025 1:20am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

This 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

Cohort / File(s) Change Summary
API Access Control
apps/web/app/(ee)/api/partner-profile/programs/[programId]/analytics/route.ts, 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
Import LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS, retrieve totalCommissions from enrollment, and extend access checks to require both a specific program id and totalCommissions < threshold for restricted analytics paths.
Hook & Constants
apps/web/lib/swr/use-program-enrollment.ts, apps/web/lib/partners/constants.ts
Added showDetailedAnalytics to the hook's return shape; compute it from programSlug and totalCommissions vs LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS (added constant, value 500000).
Dashboard — Pages (redirect removal)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/analytics/page.tsx, apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx
Removed async params/redirect logic and converted components to simple synchronous renderers that always return PageContent wrapping respective UI.
Dashboard — Components (analytics visibility)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx, 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/partner-link-card.tsx, apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/earnings/earnings-table.tsx
Replaced hardcoded programSlug === "perplexity" checks with showDetailedAnalytics from useProgramEnrollment to toggle detailed vs simplified analytics UI, link behavior, and chart/badge rendering.
Navigation / Sidebar
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx
Added optional showDetailedAnalytics to SidebarNavData, pass it into NAV_AREAS and SidebarNav payload to conditionally show Analytics/Events menu items.
Validation & Scripts & Templates
apps/web/lib/zod/schemas/groups.ts, apps/web/scripts/partners/aggregate-stats.ts, packages/email/src/templates/partner-deactivated.tsx
Increased MAX_ADDITIONAL_PARTNER_LINKS from 20 → 100; deleted PoC script aggregate-stats.ts; changed default program.name in partner-deactivated email from "Perplexity" → "Acme".

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Attention areas:
    • Confirm threshold constant value (500000) and usage consistency across API checks.
    • Verify useProgramEnrollment handles missing/undefined totalCommissions and server/hydration edge cases.
    • Ensure removed async/redirect logic in page components doesn't affect route parameter propagation elsewhere.
    • Check sidebar/menu behavior for parity with main dashboard components.

Possibly related PRs

Suggested reviewers

  • TWilson023

Poem

🐰 A hop, a twitch, a tiny analytical cheer,
Thresholds set so rabbit friends can peer,
Five hundred thousand cents draws the line,
Detailed charts appear when the numbers shine,
Hooray — the dashboard blooms, nibble the vine! 🌿

Pre-merge checks and finishing touches

✅ 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 showDetailedAnalytics flag" accurately reflects a central architectural change in this pull request. The showDetailedAnalytics flag is added to the useProgramEnrollment hook and propagated throughout the codebase to replace hardcoded program slug checks ("perplexity") with data-driven conditional logic. This flag affects at least 11 files across components, hooks, and the sidebar navigation, making it the most widespread and significant change in the changeset. The title is concise, clear, and specific enough that a teammate reviewing the history would understand the primary purpose of the PR without needing extensive explanation.
✨ 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 show-detailed-analytics

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.

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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 showDetailedAnalytics permission 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 in customers/[customerId]/page-client.tsx:

if (!showDetailedAnalytics) {
  redirect(`/programs/${programSlug}`);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7636951 and fe32212.

📒 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.ts
  • packages/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.tsx
  • apps/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.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/events/page.tsx
  • 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)/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.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx
  • packages/email/src/templates/partner-deactivated.tsx
  • apps/web/lib/partners/constants.ts
  • apps/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.tsx
  • apps/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 showDetailedAnalytics field to the SidebarNavData type is well-typed and follows existing patterns.


191-206: LGTM!

The conditional rendering of Analytics and Events navigation items based on showDetailedAnalytics is clean and follows the existing pattern for conditional menu items.


269-269: LGTM!

Properly destructures showDetailedAnalytics from 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 showDetailedAnalytics from the hook for use in conditional rendering.


239-251: LGTM!

The conditional rendering between StatCard (detailed analytics with charts) and StatCardSimple (simple stats) based on showDetailedAnalytics is well-implemented. Both component types properly handle all three metrics (clicks, leads, sales) and are correctly wrapped in NumberFlowGroup for 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 ProgramEnrollmentAuth in 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 via withPartnerProfile middleware and getProgramEnrollmentOrThrow() 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();
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

🧩 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=ts

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check how other components use useProgramEnrollment hook
rg -nP -C3 'useProgramEnrollment' --type=ts

Length of output: 40447


🏁 Script executed:

#!/bin/bash
# Find the useProgramEnrollment hook definition
fd -e ts -e tsx | xargs grep -l 'useProgramEnrollment' | head -5

Length 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.tsx

Length of output: 879


🏁 Script executed:

#!/bin/bash
# Find and display the useProgramEnrollment hook implementation
cat apps/web/lib/swr/use-program-enrollment.ts

Length 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.

Comment on lines +30 to +32
if ((!customer && !isLoading) || !showDetailedAnalytics) {
redirect(`/programs/${programSlug}`);
}
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 | 🟠 Major

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.

@steven-tey steven-tey merged commit e6937ab into main Nov 3, 2025
9 checks passed
@steven-tey steven-tey deleted the show-detailed-analytics branch November 3, 2025 01:28
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

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 !showDetailedAnalytics without waiting for programEnrollment to finish loading. During the loading state, showDetailedAnalytics incorrectly evaluates to false for "perplexity" program users (since totalCommissions defaults to 0), causing premature redirects before the enrollment data is available.

The previous review comments correctly identified this issue and suggested destructuring the loading state from useProgramEnrollment() 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 for showDetailedAnalytics.

During initial load, programEnrollment is undefined, causing totalCommissions to default to 0. For the "perplexity" program, this makes showDetailedAnalytics temporarily false until enrollment data loads, briefly showing StatsBadge instead of StatsCharts. While this is less critical than redirect logic (as seen in other files), it may cause a brief UI flicker.

Consider destructuring loading from useProgramEnrollment() 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 programEnrollment is still loading, showDetailedAnalytics may be incorrectly false for "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 loading is 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 programEnrollment is still loading, showDetailedAnalytics may be incorrectly false for "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 loading state from useProgramEnrollment() and either:

  1. Show a loading indicator in the customer cell during load, or
  2. Default to enabling the link during loading (optimistic approach)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7636951 and fe32212.

📒 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.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/page-client.tsx
  • 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)/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.ts
  • packages/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.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/links/partner-link-card.tsx
  • packages/email/src/templates/partner-deactivated.tsx
  • apps/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.ts
  • apps/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.ts
  • apps/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.name value 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 a showDetailedAnalytics flag.

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 showDetailedAnalytics flag 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 totalCommissions here 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 showDetailedAnalytics from 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 showDetailedAnalytics flag 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 showDetailedAnalytics flag.

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 totalCommissions field is a base field on the ProgramEnrollment model (defined in packages/prisma/schema/program.prisma), not a relation. Base fields are always included in Prisma query results regardless of the include parameter. The include object only specifies which relations to eagerly load (program and links in this case). The destructuring at line 17 is correct.

Comment on lines +35 to +38
showDetailedAnalytics:
programSlug !== "perplexity" ||
(programEnrollment?.totalCommissions ?? 0) >=
LARGE_PROGRAM_MIN_TOTAL_COMMISSIONS_CENTS,
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 | 🟠 Major

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.

Suggested change
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.

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