-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Updated the partner hover state on specific pages #2940
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
base: main
Are you sure you want to change the base?
Conversation
To show relevant information based on page content.
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ 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 |
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
🧹 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 iniconMap
, 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
whenshowRewardsTooltip
is false. While this works (the hook checkspartnerId
truthiness), passingnull
orundefined
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 theany
types.The
reward
anddiscount
fields useany
, 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
📒 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:
- Show rewards tooltip when
showRewardsTooltip
is true- Show payouts tooltip when
showPayoutsEnabled
is true- 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
andpartnerId
are present- Disables revalidation on focus/reconnect (appropriate for relatively static rewards data)
- Returns a clean API with data, loading, and error states
apps/web/app/(ee)/api/partners/[partnerId]/rewards-and-discounts/route.ts
Outdated
Show resolved
Hide resolved
Optimized from O(n) API calls to O(1) by leveraging existing useGroups data. Fixed getGroups API to include reward relationships.
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
🧹 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:
- Using Prisma.sql joins in the main query to fetch reward data alongside group metrics in a single round-trip
- 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 emptyIN
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 formaxDuration === 1
.The duration logic skips
maxDuration === 1
in both sale rewards (lines 75-80) and discounts (lines 102-107). WhenmaxDuration
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
whengroup
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
📒 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 thegroups
array,partnerGroup
will beundefined
. ThePartnerRewardsTooltip
component handles this by returningnull
, 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (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
} 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}`; |
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.
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.
The provide relevant information to owners about the partners, the tooltip has been modified on specific pages.
Updated on:
Hover removed from:
No change to:
Summary by CodeRabbit
New Features
UI/UX