-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Support fractional reward percentages #2998
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.
|
WalkthroughReplaces single reward Changes
Sequence Diagram(s)sequenceDiagram
participant Cron as Aggregate Clicks Cron
participant DB as Prisma DB
participant S as serializeReward
participant G as getRewardAmount
participant API as Prisma: Commission.createMany
Cron->>DB: fetch links + rewards
DB-->>Cron: reward records
Cron->>S: serializeReward(reward)
S-->>Cron: serializedReward
Cron->>G: getRewardAmount(serializedReward)
G-->>Cron: numericAmount
Cron->>Cron: earnings = numericAmount * clicks
Cron->>API: bulk create commissions with earnings
API-->>Cron: created commissions
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas to focus during review:
Possibly related PRs
Suggested reviewers
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 |
|
@coderabbitai final review |
|
@devkiran: I'll perform a final review of the PR now. I'll examine all the changes including the new fractional reward percentage support, validation logic, schema updates, and the migration strategy. ✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/lib/fetchers/get-program.ts(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/lib/fetchers/get-program.ts (2)
apps/web/lib/types.ts (1)
RewardProps(533-533)apps/web/lib/api/partners/serialize-reward.ts (1)
serializeReward(5-13)
⏰ 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 (1)
apps/web/lib/fetchers/get-program.ts (1)
4-4: LGTM!The import is correctly placed and necessary for the reward serialization logic below.
|
/bug0 run |
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
♻️ Duplicate comments (2)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx (2)
297-306: Prior validation concerns remain unaddressed.The previous review identified several issues that are still present:
Field naming inconsistency:
amountInCentsstores dollars, not cents (converted on line 86), making the name misleading.Zero values allowed:
min: 0permits $0 or 0% rewards, which don't make business sense. Considermin: 0.01for both types.Backend schema validation gap: The frontend enforces
max: 1000for flat rewards, but the backend schema may lack this constraint, allowing direct API calls to bypass the limit.Refer to the previous review for detailed recommendations on addressing these concerns.
97-98: Improved null check, but zero values still permitted.The explicit
amount == nullcheck correctly distinguishes between missing values and zero, addressing the prior concern about falsy checks. However, this now permits the button to be enabled whenamountis0, which—combined withmin: 0validation—allows submission of $0 or 0% rewards that don't make business sense.Consider changing the validation to
min: 0.01and updating the disabled check to explicitly reject zero:-const buttonDisabled = amount == null || !type || !defaultRewardType; +const buttonDisabled = amount == null || amount === 0 || !type || !defaultRewardType;
🧹 Nitpick comments (2)
apps/web/scripts/migrations/migrate-reward-amounts.ts (2)
23-25: Preferidfor deterministic ordering.The re-query-without-skip pagination approach is correct. However, ordering by
createdAtmay cause issues:
- Non-unique timestamps can lead to non-deterministic ordering
- May not be indexed, causing performance degradation
- Past review specifically suggested
orderBy: { id: "asc" }Apply this diff:
- orderBy: { - createdAt: "asc", - }, + orderBy: { + id: "asc", + },
33-43: Handle edge cases for reward type and null amounts.The current logic has potential gaps:
- If
reward.typeis neither "flat" nor "percentage", both new fields remainnullwith no warning- Defaulting
nullamounts to0(line 34) may not be semantically correctConsider adding validation and logging:
const amount = reward.amount ?? 0; let amountInCents: number | null = null; let amountInPercentage: number | null = null; // Migrate main amount field if (reward.type === "flat") { amountInCents = amount; } else if (reward.type === "percentage") { amountInPercentage = amount; + } else { + console.warn( + `Reward ${reward.id} has unknown type "${reward.type}", skipping amount migration` + ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx(6 hunks)apps/web/scripts/migrations/migrate-reward-amounts.ts(1 hunks)apps/web/ui/partners/lander/blocks/earnings-calculator-block.tsx(3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T17:31:10.548Z
Learnt from: devkiran
PR: dubinc/dub#2833
File: apps/web/lib/actions/partners/approve-bounty-submission.ts:53-61
Timestamp: 2025-09-12T17:31:10.548Z
Learning: In approve-bounty-submission.ts, the logic `bounty.rewardAmount ?? rewardAmount` is intentional. Bounties with preset reward amounts should use those fixed amounts, and the rewardAmount override parameter is only used when bounty.rewardAmount is null/undefined (for custom reward bounties). This follows the design pattern where bounties are either "flat rate" (fixed amount) or "custom" (variable amount set during approval).
Applied to files:
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx
🧬 Code graph analysis (3)
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx (1)
apps/web/app/api/domains/[domain]/verify/route.ts (1)
maxDuration(10-10)
apps/web/ui/partners/lander/blocks/earnings-calculator-block.tsx (2)
apps/web/lib/partners/get-reward-amount.ts (1)
getRewardAmount(3-11)apps/web/ui/partners/format-reward-description.ts (1)
formatRewardDescription(4-32)
apps/web/scripts/migrations/migrate-reward-amounts.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/types.ts (1)
RewardConditions(554-554)
⏰ 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 (8)
apps/web/scripts/migrations/migrate-reward-amounts.ts (1)
1-4: LGTM!The imports are appropriate for the migration task.
apps/web/app/(ee)/app.dub.co/(new-program)/[slug]/program/new/rewards/form.tsx (3)
43-55: LGTM: Watch statement correctly updated for separate amount fields.The refactor to watch
amountInCentsandamountInPercentageseparately aligns with the new field structure and supports fractional values correctly.
84-91: Conversion logic is correct.The null checks and type guards ensure only the appropriate field is converted and sent to the backend. The
Math.roundon line 86 correctly handles floating-point precision when converting dollars to cents.
261-263: LGTM: UI terminology improvements.The updates from "Payout" to "Reward amount" and "Payout model" to "Reward structure" improve clarity and consistency throughout the form.
Also applies to: 272-273, 286-286
apps/web/ui/partners/lander/blocks/earnings-calculator-block.tsx (4)
3-3: LGTM!The import correctly reflects the new helper function used in this component.
31-31: LGTM!The refactoring correctly uses the
getRewardAmounthelper to retrieve the appropriate amount based on reward type, maintaining null safety by defaulting to 0.
72-72: LGTM!The changes correctly implement the refactoring:
- Line 72: The function call now passes
rewarddirectly, matching the updatedformatRewardDescriptionsignature.- Lines 84-88: The earnings calculations properly handle both reward types and support fractional percentages as intended by this PR:
- Flat rewards: converts cents to dollars (
rewardAmount / 100)- Percentage rewards: applies percentage to revenue (
rewardAmount / 100for fractional support, e.g., 10.5% → 0.105)Also applies to: 84-88
28-30: No issues found - this change is correct and improves the component logic.The behavioral change from
sortRewardsByEventOrder(program.rewards)[0]toprogram.rewards.find((r) => r.event === "sale")is appropriate for this component:
- The component is named
EarningsCalculatorBlockand displays sale-based earnings, so it should specifically use the sale reward, not the first reward by priority order.- The old code would have returned a click or lead reward if one existed before a sale reward, which would produce incorrect earnings calculations for this use case.
- This change aligns with similar patterns elsewhere in the codebase (e.g.,
apps/web/app/(ee)/api/workflows/partner-approved/route.ts:242).- The defensive null check
if (!reward) return null;is appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/web/lib/zod/schemas/rewards.ts (1)
132-137: Verify the 99.99% maximum is intentional.The max percentage is set to 99.99% rather than 100%. This effectively prevents partners from receiving the entire sale amount. If this is a business rule to ensure the platform always retains at least 0.01%, it's working as intended.
Additionally, consider adding decimal precision validation:
The schema currently accepts any number between 0 and 99.99, including values with many decimal places (e.g., 10.123456789%). If percentage rewards should be limited to 2 decimal places, apply this diff:
export const PERCENTAGE_REWARD_AMOUNT_SCHEMA = z .number() .min(0, { message: "Reward percentage amount cannot be less than 0%" }) .max(99.99, { message: "Reward percentage amount cannot be greater than 99.99%", - }); + }) + .refine( + (val) => Number.isInteger(val * 100), + { message: "Reward percentage amount must have at most 2 decimal places" } + );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/lib/zod/schemas/program-onboarding.ts(2 hunks)apps/web/lib/zod/schemas/rewards.ts(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/lib/zod/schemas/program-onboarding.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.
Applied to files:
apps/web/lib/zod/schemas/rewards.ts
🧬 Code graph analysis (1)
apps/web/lib/zod/schemas/rewards.ts (1)
packages/prisma/client.ts (1)
RewardStructure(29-29)
⏰ 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/zod/schemas/rewards.ts (5)
139-145: LGTM!The flat reward schema correctly validates amounts in cents with an integer constraint and reasonable bounds (0 to $999,999.99). The user-friendly error messages are clear.
147-154: LGTM!The dual optional fields (
amountInCentsandamountInPercentage) correctly allow partial modifier configuration during form editing, with business logic validation ensuring exactly one field is set based ontypeat submission time. This aligns with the two-stage validation pattern used elsewhere in the codebase.Based on learnings.
160-164: LGTM!The transformer correctly handles null/undefined/empty strings while preserving
0values. The fix from the previous review (changing from falsy check to explicit null check) ensures 0% rewards work correctly.
171-172: LGTM!The read schema appropriately uses lenient validation (no min/max constraints) to accommodate existing database records, while maintaining type safety with
.int()for cents and thedecimalToNumbertransformer for percentage values.
177-187: LGTM!The write schema correctly uses the exported validation schemas for both amount fields. As confirmed in previous reviews, mutual exclusivity validation between
amountInCentsandamountInPercentagebased ontypeis appropriately handled in thevalidate-rewardmethod rather than at the Zod schema level, following the two-stage validation pattern.
…ntage types
Summary by CodeRabbit
New Features
Bug Fixes
Chores