-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Reward modifier display #2734
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
Reward modifier display #2734
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis set of changes introduces support for reward modifiers throughout the application, enhancing components and logic to handle and display multiple reward amounts and their conditions. New props and types are added to relevant components, a tooltip for modifier details is introduced, and operator label mappings are centralized. Minor style and prop formatting adjustments are also included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ProgramRewardList
participant ProgramRewardDescription
participant ProgramRewardModifiersTooltip
User->>ProgramRewardList: View reward list
ProgramRewardList->>ProgramRewardDescription: Render each reward (with showModifiers)
ProgramRewardDescription->>ProgramRewardModifiersTooltip: (If showModifiers & modifiers exist) Render tooltip
ProgramRewardModifiersTooltip-->>ProgramRewardDescription: Tooltip with modifier details
ProgramRewardDescription-->>ProgramRewardList: Render reward row with modifiers info
ProgramRewardList-->>User: Display rewards with modifier tooltips
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–20 minutes Possibly related PRs
Poem
Note 🔌 MCP (Model Context Protocol) integration is now available in Early Access!Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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: 6
🔭 Outside diff range comments (2)
apps/web/ui/partners/rewards/rewards-logic.tsx (2)
262-270: Reset dependent fields when the entity changesSwitching
entitywithout clearingattribute,operator, andvaluecan leave the UI in an invalid state (e.g. a sale attribute hanging on a customer entity).
Update the assignment to wipe the dependent fields:- setValue(conditionKey, { entity: value as keyof typeof ENTITIES }, { … }); + setValue( + conditionKey, + { entity: value as keyof typeof ENTITIES }, + { shouldDirty: true, shouldValidate: true }, + ); + // Immediately clear downstream fields + setValue(`${conditionKey}.attribute`, undefined, { shouldDirty: true }); + setValue(`${conditionKey}.operator`, undefined, { shouldDirty: true }); + setValue(`${conditionKey}.value`, undefined, { shouldDirty: true });This mirrors the UX requirement captured in previous PR #2673.
255-256: Guard against undefinedoperator
operatorcan beundefineduntil the user makes a selection, yetoperator.toLowerCase()is called unconditionally.
Add a null-safe guard to avoid a run-time crash:-{conditionIndex === 0 ? "If" : capitalize(operator.toLowerCase())}{" "} +{conditionIndex === 0 + ? "If" + : operator + ? capitalize(operator.toLowerCase()) + : "—"}{" "}
🧹 Nitpick comments (4)
packages/ui/src/sheet.tsx (1)
39-46: Optional: Add WebkitUserSelect for older Safari; confirm override intent.
- If you need to support older iOS/Safari/WebView edge cases, consider also setting WebkitUserSelect: "auto".
- Current spread order lets consumer-provided contentProps.style override userSelect. If the goal is to guarantee selectable content regardless of consumer style, invert the spread.
Proposed diff:
style={ // 8px between edge of screen and drawer { "--initial-transform": "calc(100% + 8px)", - userSelect: "auto", // Override default user-select: none from Vaul + userSelect: "auto", // Override default user-select: none from Vaul + WebkitUserSelect: "auto", ...contentProps?.style, } as React.CSSProperties }apps/web/lib/zod/schemas/rewards.ts (1)
36-43: Add a compile-time exhaustiveness check for operator labels
CONDITION_OPERATOR_LABELScan silently drift out of sync when new operators are added.
Leverage TypeScript 4.9’ssatisfiesoperator so the compiler flags missing/extraneous keys:-export const CONDITION_OPERATOR_LABELS = { +export const CONDITION_OPERATOR_LABELS = { equals_to: "is", not_equals: "is not", starts_with: "starts with", ends_with: "ends with", in: "is one of", not_in: "is not one of", -} as const; +} satisfies Record<(typeof CONDITION_OPERATORS)[number], string>;This keeps the run-time object unchanged while giving full type safety.
apps/web/app/api/og/program/route.tsx (1)
141-151: Use.modifiers?.lengthto avoid single-elementamountswhen modifiers is an empty array.Empty arrays are truthy, producing
amounts: [reward.amount]. Prefer checking length.- ...(rewards[0].modifiers + ...(rewards[0].modifiers?.length ? { amounts: [ rewards[0].amount, ...rewards[0].modifiers.map(({ amount }) => amount), ], } : { amount: rewards[0].amount }),apps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsx (1)
24-33: Prefer.modifiers?.lengthover a truthy check to avoid single-elementamounts.- ...(reward.modifiers + ...(reward.modifiers?.length ? { amounts: [ reward.amount, ...reward.modifiers.map(({ amount }) => amount), ], } : { amount: reward.amount }),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
apps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx(1 hunks)apps/web/app/api/og/program/route.tsx(1 hunks)apps/web/lib/api/sales/construct-reward-amount.ts(1 hunks)apps/web/lib/partners/approve-partner-enrollment.ts(1 hunks)apps/web/lib/partners/bulk-approve-partners.ts(1 hunks)apps/web/lib/zod/schemas/rewards.ts(1 hunks)apps/web/ui/partners/program-reward-description.tsx(3 hunks)apps/web/ui/partners/program-reward-list.tsx(3 hunks)apps/web/ui/partners/program-reward-modifiers-tooltip.tsx(1 hunks)apps/web/ui/partners/rewards/rewards-logic.tsx(3 hunks)packages/ui/src/sheet.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: TWilson023
PR: dubinc/dub#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.
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.
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
PR: dubinc/dub#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/lib/partners/bulk-approve-partners.tsapps/web/app/api/og/program/route.tsxapps/web/lib/partners/approve-partner-enrollment.tsapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsxapps/web/ui/partners/rewards/rewards-logic.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/ui/partners/program-reward-description.tsxapps/web/lib/zod/schemas/rewards.tsapps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsxapps/web/ui/partners/program-reward-list.tsxapps/web/lib/api/sales/construct-reward-amount.ts
📚 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/partners/bulk-approve-partners.tsapps/web/app/api/og/program/route.tsxapps/web/lib/partners/approve-partner-enrollment.tsapps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsxapps/web/ui/partners/rewards/rewards-logic.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/ui/partners/program-reward-description.tsxapps/web/lib/zod/schemas/rewards.tsapps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsxapps/web/ui/partners/program-reward-list.tsxapps/web/lib/api/sales/construct-reward-amount.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
PR: dubinc/dub#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/lib/partners/bulk-approve-partners.tsapps/web/lib/partners/approve-partner-enrollment.tsapps/web/ui/partners/rewards/rewards-logic.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsx
📚 Learning: 2025-07-09T20:52:56.592Z
Learnt from: TWilson023
PR: dubinc/dub#2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/ui/partners/program-reward-list.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
🔇 Additional comments (11)
packages/ui/src/sheet.tsx (1)
43-44: Correct React style key (LGTM).Switching to userSelect fixes the React.CSSProperties typing and applies as intended. Good catch.
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1)
70-75: Prop addition looks goodExplicitly disabling modifier display (
showModifiers={false}) keeps the tooltip concise for this view.apps/web/lib/partners/bulk-approve-partners.ts (1)
84-88: Email copy uses the new flag correctlyPassing
showModifiers: falsematches the simplified reward description intended for approval emails.apps/web/lib/partners/approve-partner-enrollment.ts (1)
186-188: Email still may show ranged amounts from modifiers despiteshowModifiers: false.
showModifierscurrently only hides the tooltip; the amount string is still built from modifiers. If the intent is to fully suppress modifier display in emails (amount ranges + tooltip), update ProgramRewardDescription to respectshowModifierswhen constructingamounts.See suggested fix in ProgramRewardDescription comment.
Likely an incorrect or invalid review comment.
apps/web/ui/partners/program-reward-list.tsx (3)
7-7: LGTM: Modifier tooltip import added where used.
13-19: LGTM:showModifiersprop (default true) adds needed control in different contexts.
68-73: LGTM: Tooltip correctly gated byshowModifiersand non-empty modifiers.apps/web/ui/partners/program-reward-description.tsx (3)
11-12: LGTM:showModifiersprop surfaces control to callers. Types align with usage.Also applies to: 20-21
15-16: LGTM: Extendingrewardpick to includemodifiersis correct.
65-71: LGTM: Tooltip rendering is correctly gated byshowModifiersand non-empty modifiers.apps/web/ui/partners/program-reward-modifiers-tooltip.tsx (1)
20-20: Guard clause for empty modifiers — LGTMStraightforward and prevents rendering unnecessary markup.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🔭 Outside diff range comments (2)
apps/web/ui/partners/rewards/rewards-logic.tsx (2)
256-259: Fix potential crash: avoid calling capitalize on undefinedWhen entity is unset, capitalize(condition.entity) will throw. Use a guard.
- <InlineBadgePopover - text={capitalize(condition.entity) || "Select item"} - invalid={!condition.entity} - > + <InlineBadgePopover + text={condition.entity ? capitalize(condition.entity) : "Select item"} + invalid={!condition.entity} + >
260-271: Reset dependent fields when entity changes to prevent invalid statePer prior learning, changing entity should clear attribute/operator/value to avoid mismatches with the new entity’s attribute set.
- onSelect={(value) => - setValue( - conditionKey, - { entity: value as keyof typeof ENTITIES }, - { - shouldDirty: true, - }, - ) - } + onSelect={(value) => + setValue( + conditionKey, + { + entity: value as keyof typeof ENTITIES, + attribute: undefined, + operator: undefined, + value: undefined, + }, + { shouldDirty: true }, + ) + }
♻️ Duplicate comments (6)
apps/web/lib/api/sales/construct-reward-amount.ts (1)
18-21: Percentage range string fix is correct (previous issue resolved)Both ends now include “%” (e.g.,
10% - 12%). Thanks for addressing this.apps/web/ui/partners/program-reward-list.tsx (1)
33-43: Guard on modifiers length to avoid single-element amountsUse reward.modifiers?.length to only build ranges when there are actual modifiers.
- {constructRewardAmount({ - ...(reward.modifiers + {constructRewardAmount({ + ...(reward.modifiers?.length ? { amounts: [ reward.amount, ...reward.modifiers.map(({ amount }) => amount), ], } : { amount: reward.amount }), type: reward.type, })}{" "}apps/web/ui/partners/program-reward-description.tsx (1)
30-37: HonorshowModifiersTooltipwhen constructing the amount; also check.modifiers?.lengthWithout gating by
showModifiersTooltip, ranged amounts will show even when the tooltip is hidden (e.g., emails). Gate by both flags to avoid unintended ranged display.- {constructRewardAmount({ - ...(reward.modifiers?.length - ? { - amounts: [ - reward.amount, - ...reward.modifiers.map(({ amount }) => amount), - ], - } - : { amount: reward.amount }), - type: reward.type, - })}{" "} + {constructRewardAmount({ + ...(showModifiersTooltip && reward.modifiers?.length + ? { + amounts: [ + reward.amount, + ...reward.modifiers.map(({ amount }) => amount), + ], + } + : { amount: reward.amount }), + type: reward.type, + })}{" "}apps/web/ui/partners/program-reward-modifiers-tooltip.tsx (3)
42-47: Handle undefinedmaxDurationto avoid “undefined NaN months”Treat undefined/null/Infinity consistently as lifetime.
- {reward.maxDuration === 0 - ? "one time" - : reward.maxDuration === Infinity || - reward.maxDuration === null - ? "for the customer's lifetime" - : `for ${reward.maxDuration} ${pluralize("month", Number(reward.maxDuration))}`} + {reward.maxDuration === 0 + ? "one time" + : reward.maxDuration == null || reward.maxDuration === Infinity + ? "for the customer's lifetime" + : `for ${reward.maxDuration} ${pluralize("month", Number(reward.maxDuration))}`}
53-55: Do not useamountas React key
amountisn’t guaranteed unique; use a stable unique id if available or fall back to index.- ).map(({ amount, operator, conditions }) => ( - <Fragment key={amount}> + ).map(({ amount, operator, conditions }, i) => ( + <Fragment key={`${amount}-${i}`}>
71-78: Preserve falsy values and add safe fallbacks for operator and labels
- Default
operatorbefore.toLowerCase().- Provide fallback for
CONDITION_OPERATOR_LABELS.- Don’t drop 0/false in
condition.value.- {idx === 0 - ? "If" - : capitalize(operator.toLowerCase())} + {idx === 0 + ? "If" + : capitalize((operator ?? "and").toLowerCase())} {` ${condition.entity}`} {` ${condition.attribute}`} - {` ${CONDITION_OPERATOR_LABELS[condition.operator]}`} - {` ${condition.value && truncate(Array.isArray(condition.value) ? condition.value.join(", ") : condition.value.toString(), 16)}`} + {` ${CONDITION_OPERATOR_LABELS[condition.operator] ?? condition.operator}`} + {condition.value !== undefined && condition.value !== null + ? ` ${truncate( + Array.isArray(condition.value) + ? condition.value.join(", ") + : String(condition.value), + 16 + )}` + : ""}
🧹 Nitpick comments (8)
apps/web/lib/zod/schemas/rewards.ts (1)
36-43: Make operator labels mapping immutable and type-checked against CONDITION_OPERATORSPrevent accidental runtime mutation and enforce one-to-one coverage at compile-time.
Apply this diff:
-export const CONDITION_OPERATOR_LABELS = { - equals_to: "is", - not_equals: "is not", - starts_with: "starts with", - ends_with: "ends with", - in: "is one of", - not_in: "is not one of", -} as const; +export const CONDITION_OPERATOR_LABELS = Object.freeze({ + equals_to: "is", + not_equals: "is not", + starts_with: "starts with", + ends_with: "ends with", + in: "is one of", + not_in: "is not one of", +} satisfies Record<(typeof CONDITION_OPERATORS)[number], string>);apps/web/lib/api/sales/construct-reward-amount.ts (1)
14-21: Collapse range when all amounts are equalIf multiple amounts are passed but min === max, showing
10% - 10%(or$10 - $10) is noisy; render a single value.Apply this diff:
// Range of amounts if (amounts && amounts.length > 1) { const min = Math.min(...amounts); const max = Math.max(...amounts); + if (min === max) { + return type === "percentage" ? `${min}%` : formatCurrency(min); + } return type === "percentage" ? `${min}% - ${max}%` : `${formatCurrency(min)} - ${formatCurrency(max)}`; }apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/rewards/rewards.tsx (1)
324-327: Avoid passing non-DOM props to a div when As === "div"
hrefandscrollend up as unknown attributes on a div. Conditionally spread these props only for Link.Apply this diff:
- <As - href={reward ? `/${slug}/program/rewards?rewardId=${reward.id}` : ""} - scroll={false} - className="flex cursor-pointer items-center gap-4 rounded-lg border border-neutral-200 p-4 transition-all hover:border-neutral-300" - > + <As + {...(reward + ? { + href: `/${slug}/program/rewards?rewardId=${reward.id}`, + scroll: false, + } + : {})} + className="flex cursor-pointer items-center gap-4 rounded-lg border border-neutral-200 p-4 transition-all hover:border-neutral-300" + >apps/web/ui/partners/rewards/rewards-logic.tsx (1)
293-305: Optional: also reset operator/value when attribute changesDifferent attributes may imply different operator/value semantics (e.g., list vs string). Clearing them reduces invalid combos and simplifies validation.
- onSelect={(value) => - setValue( - conditionKey, - { - entity: condition.entity, - attribute: value as (typeof CONDITION_ATTRIBUTES)[number], - }, - { - shouldDirty: true, - }, - ) - } + onSelect={(value) => + setValue( + conditionKey, + { + entity: condition.entity, + attribute: value as (typeof CONDITION_ATTRIBUTES)[number], + operator: undefined, + value: undefined, + }, + { shouldDirty: true }, + ) + }apps/web/app/api/og/program/route.tsx (2)
141-151: Guard on modifiers length to avoid single-element amountsIf modifiers is an empty array, passing amounts: [base] is redundant and may format differently than amount.
- {constructRewardAmount({ - ...(rewards[0].modifiers + {constructRewardAmount({ + ...(rewards[0].modifiers?.length ? { amounts: [ rewards[0].amount, ...rewards[0].modifiers.map(({ amount }) => amount), ], } : { amount: rewards[0].amount }), type: rewards[0].type, })}
154-161: Handle Infinity maxDuration to avoid “Infinity months” in OGSome flows use Infinity to represent lifetime; render a lifetime string instead of “Infinity months”.
- {rewards[0].maxDuration === null ? ( + {rewards[0].maxDuration === null ? ( "for the customer's lifetime" - ) : rewards[0].maxDuration && rewards[0].maxDuration > 1 ? ( + ) : rewards[0].maxDuration === Infinity ? ( + "for the customer's lifetime" + ) : rewards[0].maxDuration && rewards[0].maxDuration > 1 ? ( <> , and again every month for {rewards[0].maxDuration} months </> ) : null}apps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsx (1)
24-31: Prefer checking.modifiers?.lengthto gate passingamountsUsing a truthy check on
reward.modifierswill also pass an array when it's empty (harmless but inconsistent). Gate on length for clarity and parity with other call sites.- ...(reward.modifiers + ...(reward.modifiers?.length ? { amounts: [ reward.amount, ...reward.modifiers.map(({ amount }) => amount), ], } : { amount: reward.amount }),apps/web/ui/partners/program-reward-description.tsx (1)
11-11: Prop naming/contract clarity
showModifiersTooltipnow implicitly controls both tooltip visibility and amount formatting (after applying the above change). Consider either documenting this dual purpose in the prop comment or splitting intoshowModifiers(amount formatting) andshowModifiersTooltip(UI) if you foresee divergent behavior.Also applies to: 20-20
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
apps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsx(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx(1 hunks)apps/web/app/api/og/program/route.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/rewards/rewards.tsx(1 hunks)apps/web/lib/api/sales/construct-reward-amount.ts(1 hunks)apps/web/lib/partners/approve-partner-enrollment.ts(1 hunks)apps/web/lib/partners/bulk-approve-partners.ts(1 hunks)apps/web/lib/zod/schemas/rewards.ts(1 hunks)apps/web/ui/partners/program-reward-description.tsx(3 hunks)apps/web/ui/partners/program-reward-list.tsx(3 hunks)apps/web/ui/partners/program-reward-modifiers-tooltip.tsx(1 hunks)apps/web/ui/partners/rewards/rewards-logic.tsx(3 hunks)packages/ui/src/sheet.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: TWilson023
PR: dubinc/dub#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.
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.
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
PR: dubinc/dub#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.tsxapps/web/lib/partners/bulk-approve-partners.tsapps/web/lib/partners/approve-partner-enrollment.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/rewards/rewards.tsxapps/web/ui/partners/rewards/rewards-logic.tsxapps/web/app/api/og/program/route.tsxapps/web/ui/partners/program-reward-list.tsxapps/web/lib/zod/schemas/rewards.tsapps/web/lib/api/sales/construct-reward-amount.tsapps/web/ui/partners/program-reward-description.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsx
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
PR: dubinc/dub#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)/customers/[customerId]/page-client.tsxapps/web/lib/partners/bulk-approve-partners.tsapps/web/lib/partners/approve-partner-enrollment.tsapps/web/ui/partners/rewards/rewards-logic.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsx
📚 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/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsxapps/web/lib/partners/bulk-approve-partners.tsapps/web/lib/partners/approve-partner-enrollment.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/rewards/rewards.tsxapps/web/ui/partners/rewards/rewards-logic.tsxapps/web/app/api/og/program/route.tsxapps/web/ui/partners/program-reward-list.tsxapps/web/lib/zod/schemas/rewards.tsapps/web/lib/api/sales/construct-reward-amount.tsapps/web/ui/partners/program-reward-description.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsxapps/web/app/(ee)/app.dub.co/embed/referrals/faq.tsx
📚 Learning: 2025-07-09T20:52:56.592Z
Learnt from: TWilson023
PR: dubinc/dub#2614
File: apps/web/ui/partners/design/previews/lander-preview.tsx:181-181
Timestamp: 2025-07-09T20:52:56.592Z
Learning: In apps/web/ui/partners/design/previews/lander-preview.tsx, the ellipsis wave animation delay calculation `3 - i * -0.15` is intentionally designed to create negative delays that offset each dot's animation cycle. This pattern works correctly for the intended ellipsis effect and should not be changed to positive incremental delays.
Applied to files:
apps/web/lib/partners/approve-partner-enrollment.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/rewards/rewards.tsxapps/web/ui/partners/program-reward-list.tsxapps/web/lib/api/sales/construct-reward-amount.tsapps/web/ui/partners/program-reward-description.tsxapps/web/ui/partners/program-reward-modifiers-tooltip.tsx
📚 Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
PR: dubinc/dub#2177
File: apps/web/lib/api/links/bulk-create-links.ts:66-84
Timestamp: 2025-06-06T07:59:03.120Z
Learning: In apps/web/lib/api/links/bulk-create-links.ts, the team accepts the risk of potential undefined results from links.find() operations when building invalidLinks arrays, because existing links are fetched from the database based on the input links, so matches are expected to always exist.
Applied to files:
apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
📚 Learning: 2025-06-18T20:31:51.779Z
Learnt from: TWilson023
PR: dubinc/dub#2538
File: apps/web/ui/partners/overview/blocks/conversion-block.tsx:85-85
Timestamp: 2025-06-18T20:31:51.779Z
Learning: In React components where arrays are defined with useMemo using static array literals (not filtered or dynamically populated), the array will always have the same number of elements regardless of the data values. Using .at(-1)! on such arrays is safe because the array structure is constant, even if individual property values within the array elements may be 0 or falsy.
Applied to files:
apps/web/ui/partners/program-reward-modifiers-tooltip.tsx
🔇 Additional comments (8)
packages/ui/src/sheet.tsx (1)
43-45: Correct React style key for user-select; inline override is appropriateSwitching to userSelect fixes the React style key and correctly overrides Vaul’s default. Looks good.
apps/web/lib/api/sales/construct-reward-amount.ts (1)
28-37: Good extraction of currency formatting helperThis removes duplication and preserves formatting semantics (fixed 2 decimals only when needed). LGTM.
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page-client.tsx (1)
73-74: Explicitly disabling modifiers tooltip here is sensiblePrevents nested/competing tooltips within the existing Tooltip content. LGTM.
apps/web/lib/partners/bulk-approve-partners.ts (1)
83-89: Email-safe: disable modifiers tooltip in reward description — LGTMExplicitly passing showModifiersTooltip: false for emails makes sense and matches usage elsewhere. No concerns.
apps/web/lib/partners/approve-partner-enrollment.ts (1)
185-189: Consistent tooltip suppression — LGTMshowModifiersTooltip: false is appropriate for email context and consistent with bulk approval flow.
apps/web/ui/partners/rewards/rewards-logic.tsx (1)
8-13: Centralized operator labels — good moveSwitching to CONDITION_OPERATOR_LABELS improves consistency across UI and schema. Usage at both the badge and menu looks correct.
Also applies to: 320-323, 347-351
apps/web/ui/partners/program-reward-list.tsx (1)
68-73: Conditional tooltip rendering — LGTMTooltip is correctly gated by showModifiersTooltip and actual modifiers presence.
apps/web/ui/partners/program-reward-description.tsx (1)
65-71: Good conditional rendering of modifiers tooltipTooltip is properly gated by
showModifiersTooltipand the presence of modifiers.
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes
Configuration