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

Skip to content

Conversation

marcusljf
Copy link
Collaborator

@marcusljf marcusljf commented Oct 7, 2025

The provide relevant information to owners about the partners, the tooltip has been modified on specific pages.

Updated on:

  • Applications
  • All partners
  • Analytics
CleanShot 2025-10-06 at 22 00 48@2x

Hover removed from:

  • Rejected applicants (unnecessary to show anything since the application is rejected)

No change to:

  • Payouts
  • Commissions
  • Bounties
  • All financial related, since the payout connection is relevant.

Summary by CodeRabbit

  • New Features

    • Added a rewards tooltip on partner rows showing click, lead, sale and discount rewards with clear duration context; rewards now appear in relevant partner lists.
  • UI/UX

    • Rejected Applications now shows a compact avatar + name in the Applicant column for improved readability.
    • Payout badge is hidden when the rewards tooltip is displayed to reduce visual clutter.

To show relevant information based on page content.
@vercel
Copy link
Contributor

vercel bot commented Oct 7, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Oct 8, 2025 8:32pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 7, 2025

Walkthrough

Adds a rewards tooltip feature: new PartnerRewardsTooltip component, a showRewardsTooltip prop on PartnerRowItem, groups API augmented with rewards data, tables wired to enable the tooltip, and one table changed to inline avatar/name rendering. No data-fetch control-flow changes beyond group rewards enrichment.

Changes

Cohort / File(s) Summary of changes
Analytics & Partners tables: enable rewards tooltip
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-partners-table.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/page-client.tsx
Pass showRewardsTooltip={true} to PartnerRowItem in partner/applicant columns; no other control-flow or data-fetch changes.
Rejected applications: inline avatar/name
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/rejected/page-client.tsx
Replace PartnerRowItem with inline avatar + truncated name; add OG_AVATAR_URL import; UI-only change for the Applicant column.
New UI: partner rewards tooltip
apps/web/ui/partners/partner-rewards-tooltip.tsx
Add exported PartnerRewardsTooltip component to render click/lead/sale/discount rewards and duration text; returns null when no group provided.
UI: partner row updates
apps/web/ui/partners/partner-row-item.tsx
Add optional prop showRewardsTooltip?: boolean; add optional `groupId?: string
API: include rewards in groups
apps/web/lib/api/groups/get-groups.ts
After fetching groups, query rewards for those group IDs and merge reward fields (clickReward, leadReward, saleReward, discount) into each returned group object.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as User
  participant Table as Table (e.g., PartnersTable)
  participant Row as PartnerRowItem
  participant Hook as useGroups
  participant API as getGroups (+rewards)
  participant Tooltip as PartnerRewardsTooltip

  User->>Table: Open page / view table
  Table->>Row: Render row (showRewardsTooltip=true)
  Row->>Hook: useGroups() (if enabled)
  Hook->>API: fetch groups (includes rewards)
  API-->>Hook: groups with rewards
  Hook-->>Row: partnerGroup (matched by groupId)
  Row->>Tooltip: Render with partnerGroup.rewards
  Tooltip-->>User: Show rewards tooltip on hover
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • steven-tey

Poem

I hop through code with whiskers bright,
Tiny tooltips sparkle in the night.
Group rewards shimmer, concise and clear,
Hover for carrots, percentages near.
A rabbit's cheer for features new—🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly summarizes the primary change of modifying the partner hover state (tooltips) on the specific pages targeted by the pull request, matching the updates to tooltip behavior and component props without introducing unrelated details.
✨ 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 partner-hover-info

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/rejected/page-client.tsx (1)

127-140: Consider extracting avatar rendering to a shared component.

The inline avatar rendering duplicates logic from PartnerRowItem (lines 80-85 in partner-row-item.tsx). While this implementation is correct and the decision to remove the tooltip for rejected applicants is intentional, extracting the avatar rendering (image source selection, fallback, and styles) into a reusable component would improve maintainability and reduce duplication.

Example shared component:

export function PartnerAvatar({ 
  image, 
  name 
}: { 
  image?: string | null; 
  name: string 
}) {
  return (
    <img
      src={image || `${OG_AVATAR_URL}${name}`}
      alt={name}
      className="size-5 shrink-0 rounded-full"
    />
  );
}
apps/web/ui/partners/partner-rewards-tooltip.tsx (1)

46-49: Add type safety for iconMap access.

The code assumes reward.iconType exists in iconMap, but there's no runtime check. While the TypeScript types constrain this, a malformed API response could cause a runtime error.

Apply this diff for defensive coding:

   // Convert API rewards to component rewards
   const convertedRewards: RewardItem[] = rewards.map((reward) => ({
-    icon: iconMap[reward.iconType],
+    icon: iconMap[reward.iconType] ?? InvoiceDollar,
     text: reward.text,
   }));

Alternatively, add a runtime check:

   const convertedRewards: RewardItem[] = rewards
+    .filter(reward => reward.iconType in iconMap)
     .map((reward) => ({
       icon: iconMap[reward.iconType],
       text: reward.text,
     }));
apps/web/ui/partners/partner-row-item.tsx (1)

29-32: Pass null instead of empty string to skip data fetching.

The code passes an empty string to usePartnerRewardsAndDiscounts when showRewardsTooltip is false. While this works (the hook checks partnerId truthiness), passing null or undefined would be more idiomatic and explicit.

Apply this diff:

   // Fetch real reward data when showing rewards tooltip
   const { rewardsAndDiscounts } = usePartnerRewardsAndDiscounts(
-    showRewardsTooltip ? partner.id : "",
+    showRewardsTooltip ? partner.id : null,
   );

Then update the hook signature in use-partner-rewards-and-discounts.ts:

-export function usePartnerRewardsAndDiscounts(partnerId: string) {
+export function usePartnerRewardsAndDiscounts(partnerId: string | null) {
apps/web/lib/swr/use-partner-rewards-and-discounts.ts (1)

5-16: Consider tightening the any types.

The reward and discount fields use any, which reduces type safety. If these types are defined elsewhere in the codebase (e.g., in Prisma schema), consider importing and using them.

For example, if reward types are defined:

import type { ClickReward, LeadReward, SaleReward, Discount } from "@/lib/types";

type RewardsAndDiscounts = {
  rewards: Array<{
    type: "click" | "lead" | "sale";
    iconType: "CursorRays" | "UserPlus" | "InvoiceDollar";
    text: string;
    reward: ClickReward | LeadReward | SaleReward;
  }>;
  discount: {
    text: string;
    discount: Discount;
  } | null;
} | null;

If the types aren't available, consider adding a comment explaining why any is necessary.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ed46a7 and 1a77f29.

📒 Files selected for processing (9)
  • apps/web/app/(ee)/api/partners/[partnerId]/rewards-and-discounts/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-partners-table.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/page-client.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/rejected/page-client.tsx (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1 hunks)
  • apps/web/lib/api/partners/get-partner-rewards-and-discounts.ts (1 hunks)
  • apps/web/lib/swr/use-partner-rewards-and-discounts.ts (1 hunks)
  • apps/web/ui/partners/partner-rewards-tooltip.tsx (1 hunks)
  • apps/web/ui/partners/partner-row-item.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
apps/web/app/(ee)/api/partners/[partnerId]/rewards-and-discounts/route.ts (2)
apps/web/lib/auth/workspace.ts (1)
  • withWorkspace (42-436)
apps/web/lib/api/partners/get-partner-rewards-and-discounts.ts (1)
  • getPartnerRewardsAndDiscounts (5-92)
apps/web/ui/partners/partner-row-item.tsx (2)
apps/web/lib/swr/use-partner-rewards-and-discounts.ts (1)
  • usePartnerRewardsAndDiscounts (18-37)
apps/web/ui/partners/partner-rewards-tooltip.tsx (1)
  • PartnerRewardsTooltip (25-76)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)
apps/web/ui/partners/partner-row-item.tsx (1)
  • PartnerRowItem (10-109)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/page-client.tsx (1)
apps/web/ui/partners/partner-row-item.tsx (1)
  • PartnerRowItem (10-109)
apps/web/lib/swr/use-partner-rewards-and-discounts.ts (1)
apps/web/lib/swr/use-program.ts (1)
  • useProgram (6-40)
⏰ 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)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/applications/page-client.tsx (1)

173-177: LGTM!

The addition of showRewardsTooltip={true} correctly enables the rewards tooltip for pending applications, aligning with the PR objectives.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/partners-table.tsx (1)

161-165: LGTM!

The addition of showRewardsTooltip={true} correctly enables the rewards tooltip for all partners, consistent with the PR objectives.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/analytics-partners-table.tsx (1)

63-63: LGTM!

The addition of showRewardsTooltip={true} correctly enables the rewards tooltip in the analytics table, consistent with the PR objectives.

apps/web/app/(ee)/api/partners/[partnerId]/rewards-and-discounts/route.ts (1)

24-30: LGTM on error handling.

The error handling correctly:

  • Logs errors server-side for debugging
  • Returns a generic error message to avoid information leakage
  • Uses appropriate 500 status code
apps/web/ui/partners/partner-rewards-tooltip.tsx (1)

61-73: LGTM on rendering logic.

The rendering logic is clean and handles both default rewards and API-provided rewards correctly. The use of index as key is acceptable here since the list is static during render and doesn't support reordering.

apps/web/ui/partners/partner-row-item.tsx (1)

38-77: LGTM on conditional tooltip logic.

The conditional rendering correctly handles three cases:

  1. Show rewards tooltip when showRewardsTooltip is true
  2. Show payouts tooltip when showPayoutsEnabled is true
  3. No tooltip otherwise

The logic is clear despite the nesting.

apps/web/lib/swr/use-partner-rewards-and-discounts.ts (1)

18-37: LGTM on hook implementation.

The hook correctly:

  • Conditionally fetches data only when both program.id and partnerId are present
  • Disables revalidation on focus/reconnect (appropriate for relatively static rewards data)
  • Returns a clean API with data, loading, and error states

Optimized from O(n) API calls to O(1) by leveraging existing useGroups data. Fixed getGroups API to include reward relationships.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/web/lib/api/groups/get-groups.ts (1)

98-127: Consider performance optimization for reward data fetching.

The additional database query to fetch rewards runs after the main query completes. For large result sets, this could add latency. Consider:

  1. Using Prisma.sql joins in the main query to fetch reward data alongside group metrics in a single round-trip
  2. Conditionally fetching rewards only when includeExpandedFields is true (if rewards are only needed with expanded data)

Additionally, when groupIdsFromQuery is empty, the query still executes with an empty IN clause. While not incorrect, you could short-circuit and return the groups directly to avoid the extra query.

Example optimization:

  const groupIdsFromQuery = groups.map((group) => group.id);

+ // Skip rewards fetch if no groups
+ if (groupIdsFromQuery.length === 0) {
+   return groups.map((group) => ({
+     ...group,
+     partners: Number(group.partners),
+     totalClicks: Number(group.totalClicks),
+     totalLeads: Number(group.totalLeads),
+     totalSales: Number(group.totalSales),
+     totalSaleAmount: Number(group.totalSaleAmount),
+     totalConversions: Number(group.totalConversions),
+     totalCommissions: Number(group.totalCommissions),
+     netRevenue: Number(group.netRevenue),
+   }));
+ }
+
  // Fetch rewards for these groups
  const groupsWithRewards = await prisma.partnerGroup.findMany({
apps/web/ui/partners/partner-rewards-tooltip.tsx (2)

69-80: Handle the edge case for maxDuration === 1.

The duration logic skips maxDuration === 1 in both sale rewards (lines 75-80) and discounts (lines 102-107). When maxDuration is exactly 1, no duration text is appended, resulting in text like "10% per sale" instead of "10% per sale for 1 month".

Verify if this is intentional. If single-month durations should be displayed, update the condition:

- } else if (maxDuration && maxDuration > 1) {
+ } else if (maxDuration && maxDuration >= 1) {
    durationText =
      maxDuration % 12 === 0
        ? ` for ${maxDuration / 12} year${maxDuration / 12 > 1 ? "s" : ""}`
-       : ` for ${maxDuration} month${maxDuration > 1 ? "s" : ""}`;
+       : ` for ${maxDuration} month${maxDuration !== 1 ? "s" : ""}`;
  }

Also applies to: 96-107


115-132: Comment inconsistency: "always have rewards".

The comment states "partners will always have rewards through their group," but the component returns null when group is falsy (line 30). This suggests partners might not have a group or rewards.

Consider updating the comment to reflect the actual behavior:

- // Always show rewards - partners will always have rewards through their group
+ // Show rewards if group exists - partners without a group won't display tooltip
  return (
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a77f29 and b132159.

📒 Files selected for processing (3)
  • apps/web/lib/api/groups/get-groups.ts (3 hunks)
  • apps/web/ui/partners/partner-rewards-tooltip.tsx (1 hunks)
  • apps/web/ui/partners/partner-row-item.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-14T05:17:51.825Z
Learnt from: devkiran
PR: dubinc/dub#2735
File: apps/web/lib/actions/partners/delete-reward.ts:33-41
Timestamp: 2025-08-14T05:17:51.825Z
Learning: In the partner groups system, a rewardId can only belong to one group, establishing a one-to-one relationship between rewards and groups. This means using Prisma's `update` method (rather than `updateMany`) is appropriate when updating groups by rewardId.

Applied to files:

  • apps/web/lib/api/groups/get-groups.ts
🧬 Code graph analysis (1)
apps/web/ui/partners/partner-row-item.tsx (2)
apps/web/lib/swr/use-groups.ts (1)
  • useGroups (10-38)
apps/web/ui/partners/partner-rewards-tooltip.tsx (1)
  • PartnerRewardsTooltip (28-133)
⏰ 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 (5)
apps/web/lib/api/groups/get-groups.ts (1)

139-140: LGTM! Reward data integration is clean.

The spread operator correctly merges reward data from the map into each group object, providing a clean API for consumers.

apps/web/ui/partners/partner-row-item.tsx (2)

81-88: LGTM! Payout badge correctly suppressed for rewards tooltip.

The condition properly hides the payout status badge when the rewards tooltip is active, preventing visual clutter and ensuring only one tooltip type is shown at a time.


38-72: Verify behavior when partner's group is not found.

When partner.groupId exists but the corresponding group is not found in the groups array, partnerGroup will be undefined. The PartnerRewardsTooltip component handles this by returning null, which means no tooltip will be shown.

Confirm this is the intended behavior—should partners without a matched group have no tooltip, or should there be a fallback message?

Run the following script to check if there are partners with groupIds that might not match any group:

apps/web/ui/partners/partner-rewards-tooltip.tsx (2)

28-132: LGTM! Clean reward formatting logic.

The component correctly handles various reward types with appropriate icons and formatting. The duration text logic is well-structured, supporting lifetime, first-occurrence, and time-bound scenarios with proper pluralization.


39-47: Verify amount storage format assumption.

The code divides flat amounts by 100 (e.g., amount / 100), assuming amounts are stored in cents. Confirm this matches your database schema and is consistent across all reward types.

Run the following script to verify the storage format of reward amounts in the schema:

Also applies to: 51-59, 63-67, 90-94

Optimized performance by making useGroups data fetching conditional (only loads when rewards tooltips are displayed) and improved sale reward text formatting to show "Earn X% for the first sale" instead of "per sale" for one-time rewards.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b132159 and 4bd6ebd.

📒 Files selected for processing (2)
  • apps/web/ui/partners/partner-rewards-tooltip.tsx (1 hunks)
  • apps/web/ui/partners/partner-row-item.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/ui/partners/partner-row-item.tsx
⏰ 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

Comment on lines +75 to +89
} else if (maxDuration && maxDuration > 1) {
durationText =
maxDuration % 12 === 0
? ` for ${maxDuration / 12} year${maxDuration / 12 > 1 ? "s" : ""}`
: ` for ${maxDuration} month${maxDuration > 1 ? "s" : ""}`;
}

let text = "";
if (maxDuration === 0) {
// For first sale only, use "earn" instead of "per sale"
text = `Earn ${amount}${durationText}`;
} else {
// For recurring sales, use "per sale" with prefix
const prefix = group.saleReward.type === "percentage" ? "Up to " : "";
text = `${prefix}${amount} per sale${durationText}`;
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

Handle single-month durations.

When maxDuration is exactly 1, the tooltip omits the timeframe (“for 1 month”), leaving partners without crucial context. Add explicit handling so single-month rewards show the proper copy.

-    } else if (maxDuration && maxDuration > 1) {
-      durationText =
-        maxDuration % 12 === 0
-          ? ` for ${maxDuration / 12} year${maxDuration / 12 > 1 ? "s" : ""}`
-          : ` for ${maxDuration} month${maxDuration > 1 ? "s" : ""}`;
+    } else if (typeof maxDuration === "number" && maxDuration > 0) {
+      durationText =
+        maxDuration % 12 === 0
+          ? ` for ${maxDuration / 12} year${maxDuration / 12 === 1 ? "" : "s"}`
+          : ` for ${maxDuration} month${maxDuration === 1 ? "" : "s"}`;
@@
-    } else if (maxDuration && maxDuration > 1) {
-      durationText =
-        maxDuration % 12 === 0
-          ? ` for ${maxDuration / 12} year${maxDuration / 12 > 1 ? "s" : ""}`
-          : ` for ${maxDuration} month${maxDuration > 1 ? "s" : ""}`;
+    } else if (typeof maxDuration === "number" && maxDuration > 0) {
+      durationText =
+        maxDuration % 12 === 0
+          ? ` for ${maxDuration / 12} year${maxDuration / 12 === 1 ? "" : "s"}`
+          : ` for ${maxDuration} month${maxDuration === 1 ? "" : "s"}`;

Also applies to: 112-120

🤖 Prompt for AI Agents
In apps/web/ui/partners/partner-rewards-tooltip.tsx around lines 75-89 (and also
apply same fix around lines 112-120), the logic skips an explicit "for 1 month"
phrase when maxDuration === 1; update the conditional so a maxDuration of 1
renders "for 1 month" (singular) rather than falling through to no timeframe —
implement an explicit branch or adjust the ternary checks to treat 1 as a month
case and ensure pluralization uses singular when value === 1; mirror the same
change in the later block at lines 112-120.

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.

1 participant