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

Skip to content

Conversation

@TWilson023
Copy link
Collaborator

@TWilson023 TWilson023 commented Oct 20, 2025

Summary by CodeRabbit

  • New Features

    • Partner Program Marketplace: browse marketplace with search, filters (category, reward type, status), sorting, pagination, and featured carousel
    • Program detail pages with dynamic header, rewards, categories and apply/accept actions
    • Program settings modal and category selector
  • UX

    • New program cards, skeletons, empty states, promoted ordering, enrollment/status badges, and keyboard shortcuts for actions
  • UI/Style

    • Minor spacing and layout refinements across forms and listings

✏️ Tip: You can customize this high-level summary in your review settings.

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: 0

🧹 Nitpick comments (1)
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1)

104-109: Consider simplifying the isActive logic (optional).

The current implementation using .every() is functionally correct and maintainable if more exclusions are added later. However, for just two exclusions, a direct boolean expression might be slightly more readable:

isActive: (pathname, href) =>
- pathname.startsWith(href) &&
- ["invitations", "marketplace"].every(
-   (k) => !pathname.startsWith(`${href}/${k}`),
- ),
+ pathname.startsWith(href) &&
+ !pathname.startsWith(`${href}/invitations`) &&
+ !pathname.startsWith(`${href}/marketplace`),

The current approach is fine if you anticipate adding more exclusions in the future.

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 25987da and b3aab93.

πŸ“’ Files selected for processing (6)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/layout.tsx (1 hunks)
  • apps/web/app/api/user/referrals-token/route.ts (2 hunks)
  • apps/web/lib/middleware/utils/partners-redirect.ts (1 hunks)
  • apps/web/lib/network/get-discoverability-requirements.ts (4 hunks)
  • apps/web/lib/network/throw-if-partner-cannot-view-marketplace.ts (1 hunks)
  • apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/layout.tsx
  • apps/web/lib/network/throw-if-partner-cannot-view-marketplace.ts
  • apps/web/app/api/user/referrals-token/route.ts
  • apps/web/lib/middleware/utils/partners-redirect.ts
🧰 Additional context used
🧠 Learnings (4)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š 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/ui/layout/sidebar/partners-sidebar-nav.tsx
πŸ“š Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/ui/layout/sidebar/partners-sidebar-nav.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/lib/network/get-discoverability-requirements.ts
🧬 Code graph analysis (2)
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (3)
packages/ui/src/icons/nucleo/shop.tsx (1)
  • Shop (3-65)
apps/web/lib/swr/use-partner-profile.ts (1)
  • usePartnerProfile (6-30)
apps/web/lib/network/get-discoverability-requirements.ts (1)
  • partnerCanViewMarketplace (34-51)
apps/web/lib/network/get-discoverability-requirements.ts (3)
apps/web/lib/constants/partner-profile.ts (1)
  • PARTNER_NETWORK_MIN_COMMISSIONS_CENTS (15-15)
apps/web/lib/types.ts (2)
  • PartnerProps (453-456)
  • EnrolledPartnerProps (465-465)
packages/utils/src/functions/currency-formatter.ts (1)
  • currencyFormatter (7-22)
⏰ 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 (10)
apps/web/lib/network/get-discoverability-requirements.ts (5)

2-10: LGTM!

The import consolidation and path updates are clean refactoring changes that align with the new constant usage pattern.


44-46: Document the @dub.co email bypass rationale.

The bypass grants unconditional marketplace access to any partner whose email ends with "@dub.co". While this appears intentional for internal testing, consider:

  1. Adding a code comment explaining this is for Dub employees
  2. Confirming that the email address is validated by the authentication layer to prevent spoofing
  3. If this is temporary for testing, consider using a feature flag instead

Is this bypass intended for production, or should it be gated behind a development/staging environment check?


107-107: LGTM!

The currency formatting correctly uses the new constant and will display the commission threshold cleanly (e.g., "$10" instead of "$10.00").


23-23: Change is consistent and correct.

The constant PARTNER_NETWORK_MIN_COMMISSIONS_CENTS is properly centralized in apps/web/lib/constants/partner-profile.ts and used consistently across all commission threshold checks. Both get-discoverability-requirements.ts and throw-if-partner-cannot-view-marketplace.ts reference the same constant ($10), and no hardcoded commission values exist elsewhere in the codebase.


34-51: Breaking API change is complete across all call sites.

The function signature change from single parameter to destructured object has been properly implemented. All three call sites (partners-sidebar-nav.tsx:344, referrals-token/route.ts:40, and marketplace/layout.tsx:15) have been updated to use the new { partner, programEnrollments } signature.

The @dub.co email bypass is an intentional pattern for internal employee access, consistent with similar checks found in throw-if-partner-cannot-view-marketplace.ts and e2e test cleanup logic elsewhere in the codebase. Since the partner object originates from an authenticated session via the usePartnerProfile hook, email spoofing is mitigated at the authentication layer.

apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (5)

3-21: LGTM!

The new imports are well-organized and all are utilized within the component for the marketplace feature.


46-46: LGTM!

The optional showMarketplace field appropriately extends the data model for conditional marketplace visibility.


110-119: LGTM!

The conditional marketplace navigation item is well-implemented with appropriate icon, badge, and placement. The spread operator pattern ensures clean conditional rendering.


288-310: LGTM!

The hooks are properly used following React rules, and the data is appropriately integrated into the visibility logic. The undefined fallback handling is safe.


344-347: LGTM!

The call to partnerCanViewMarketplace correctly uses the new signature and appropriately handles undefined data with safe fallbacks.

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: 0

♻️ Duplicate comments (1)
apps/web/ui/partners/program-marketplace/program-category.tsx (1)

16-19: Use replaceAll to replace all underscores in the label.

category.replace("_", " ") only replaces the first occurrence. Categories like "food_and_beverage" would render as "food and_beverage".

   const { icon: Icon, label } = categoryData ?? {
     icon: CircleInfo,
-    label: category.replace("_", " "),
+    label: category.replaceAll("_", " "),
   };
🧹 Nitpick comments (3)
apps/web/ui/partners/program-marketplace/program-rewards-display.tsx (1)

99-106: Consider applying iconClassName in single-item case for consistency.

In the multi-item case (line 131), iconClassName is passed to ProgramRewardIcon, but in the single-item case here, it's not applied to the icon container. This inconsistency may cause styling differences depending on the number of items.

             <div
               className={cn(
                 "text-content-default flex size-6 items-center justify-center rounded-md",
                 isDarkImage && "text-content-inverted",
+                iconClassName,
               )}
             >
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/featured-program-card.tsx (2)

16-16: Consider a more robust dark image detection mechanism.

The current approach relies on the presence of "-dark" in the URL string, which is fragile and error-prone. If the naming convention changes or isn't consistently followed, the dark mode styling will break.

Consider storing a boolean flag in the program data model or using a more explicit configuration approach.


115-132: Simplify first category rendering.

Using slice(0, 1).map() creates an unnecessary single-item array and then maps over it. Since you're only rendering the first category, consider accessing it directly for cleaner, more efficient code.

Apply this diff:

-                {program.categories.slice(0, 1)?.map((category) => (
+                {program.categories[0] && (
                  <ProgramCategory
-                    key={category}
-                    category={category}
+                    key={program.categories[0]}
+                    category={program.categories[0]}
                    onClick={() =>
                      queryParams({
                        set: {
-                          category,
+                          category: program.categories[0],
                        },
                        del: "page",
                      })
                    }
                    className={cn(
                      "hover:bg-bg-default/10 active:bg-bg-default/20",
                      isDarkImage && "text-content-inverted",
                    )}
                  />
-                ))}
+                )}
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between b3aab93 and 580c8e4.

πŸ“’ Files selected for processing (4)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/featured-program-card.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/program-card.tsx (1 hunks)
  • apps/web/ui/partners/program-marketplace/program-category.tsx (1 hunks)
  • apps/web/ui/partners/program-marketplace/program-rewards-display.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/program-card.tsx
🧰 Additional context used
🧠 Learnings (4)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š 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/ui/partners/program-marketplace/program-rewards-display.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/marketplace/featured-program-card.tsx
πŸ“š Learning: 2025-11-24T16:36:36.196Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:36:36.196Z
Learning: Applies to packages/hubspot-app/app/cards/**/*.{js,jsx,ts,tsx} : Only components exported from the `hubspot/ui-extensions` npm package can be used in card components

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/featured-program-card.tsx
🧬 Code graph analysis (3)
apps/web/ui/partners/program-marketplace/program-rewards-display.tsx (3)
apps/web/lib/types.ts (2)
  • RewardProps (556-556)
  • DiscountProps (483-483)
apps/web/ui/partners/format-reward-description.ts (1)
  • formatRewardDescription (4-32)
apps/web/ui/partners/format-discount-description.ts (1)
  • formatDiscountDescription (5-34)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/featured-program-card.tsx (6)
apps/web/lib/types.ts (1)
  • NetworkProgramProps (473-473)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/program-status-badge.tsx (1)
  • ProgramStatusBadge (20-39)
apps/web/ui/partners/program-marketplace/program-rewards-display.tsx (1)
  • ProgramRewardsDisplay (28-136)
apps/web/ui/partners/program-marketplace/program-category.tsx (1)
  • ProgramCategory (6-43)
packages/ui/src/tooltip.tsx (1)
  • Tooltip (68-119)
apps/web/ui/partners/program-marketplace/program-category.tsx (2)
apps/web/lib/network/program-categories.ts (1)
  • PROGRAM_CATEGORIES_MAP (79-83)
packages/ui/src/icons/index.tsx (1)
  • Icon (82-82)
⏰ 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 (6)
apps/web/ui/partners/program-marketplace/program-category.tsx (1)

6-43: Component structure looks good.

The dynamic element pattern (As = onClick ? "button" : "div") and event handling with preventDefault/stopPropagation are consistent with the ProgramRewardIcon component pattern. The fallback for unmapped categories is a sensible defensive approach.

apps/web/ui/partners/program-marketplace/program-rewards-display.tsx (3)

10-26: Type definitions are well-structured.

The RewardItem internal type and ProgramRewardsDisplayProps interface are clean with appropriate optional markers.


122-135: Multi-item rendering looks good.

Clean delegation to ProgramRewardIcon with proper key assignment and consistent styling. The isDarkImage theming is correctly applied via the className prop.


42-51: No action needed. The code is type-safe.

Line 46 accesses REWARD_EVENTS[reward.event].icon safely because reward.event is typed as EventType (a Prisma enum with exactly three values: click, lead, sale), and REWARD_EVENTS has all three as keys. The RewardSchema Zod validation using z.nativeEnum(EventType) ensures only valid enum values reach this code, preventing any runtime errors.

Likely an incorrect or invalid review comment.

apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/marketplace/featured-program-card.tsx (2)

174-202: LGTM!

The skeleton component properly mirrors the structure of the actual card with appropriate loading states and animations. The comments explaining sizing choices are helpful for maintainability.


25-29: Standard <img> tag usage is consistent across all marketplace components in the partners section for external/partner-provided images. This is an intentional pattern rather than an optimization gapβ€”Next.js Image component is not used in the partners directory for external URLs with unknown dimensions. The established approach is appropriate for partner-provided marketplace images.

Likely an incorrect or invalid review comment.

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

♻️ Duplicate comments (1)
apps/web/lib/api/programs/get-program-or-throw.ts (1)

33-43: Refine typing to avoid @ts-ignore on categories transformation

The runtime behavior looks right, but the // @ts-ignore masks type issues around program.categories. It’d be better to model the two cases explicitly so TS understands when categories exists, e.g. by branching before parse:

-  return ProgramSchema.extend({
-    inviteEmailData: programInviteEmailDataSchema,
-  }).parse(
-    includeCategories
-      ? {
-          ...program,
-          // @ts-ignore conditionally including categories
-          categories: program.categories?.map(({ category }) => category) ?? [],
-        }
-      : program,
-  );
+  const BaseProgramSchema = ProgramSchema.extend({
+    inviteEmailData: programInviteEmailDataSchema,
+  });
+
+  if (includeCategories && "categories" in program) {
+    return BaseProgramSchema.parse({
+      ...program,
+      categories: program.categories?.map(({ category }) => category) ?? [],
+    });
+  }
+
+  return BaseProgramSchema.parse(program);

You may need to fine‑tune the narrowing (e.g. a small helper type for program when categories are included), but eliminating the blanket @ts-ignore will make future type issues easier to catch.

🧹 Nitpick comments (3)
apps/web/ui/modals/application-settings-modal.tsx (1)

62-79: Simplify error handling and remove redundant assertion.

Line 63 already guards against missing workspaceId, making the non-null assertion on line 66 redundant. Additionally, lines 70-78 set both a form error and show a toast using the same message, which may be redundant since the onError callback (line 52-54) already displays server errors via toast.

 const onSubmit = handleSubmit(async (data) => {
   if (!workspaceId) return;

   const result = await executeAsync({
-    workspaceId: workspaceId!,
+    workspaceId,
     ...data,
   });

   if (result?.serverError || result?.validationErrors) {
-    setError("root.serverError", {
-      message: "Failed to update application settings",
-    });
     toast.error(
       parseActionError(result, "Failed to update application settings"),
     );
     return;
   }
 });
apps/web/lib/api/programs/get-program-or-throw.ts (1)

15-23: Optionally narrow the Prisma include to only the needed category field

Right now include: { categories: true } will load the full relation objects, but you only consume the category property later. You could trim payload size a bit by selecting just that field:

-    ...(includeCategories && {
-      include: {
-        categories: true,
-      },
-    }),
+    ...(includeCategories && {
+      include: {
+        categories: {
+          select: { category: true },
+        },
+      },
+    }),

This is non-blocking but can help keep the object shape and data volume tighter.

apps/web/lib/zod/schemas/programs.ts (1)

10-15: New ProgramSchema fields (description, marketplace timestamp, categories) look consistent; confirm update paths

Importing Category and modeling:

  • description: z.string().nullish()
  • addedToMarketplaceAt: z.date().nullish()
  • categories: z.array(z.nativeEnum(Category)).nullish()

fits well with the new marketplace/program metadata and with how getProgramOrThrow maps categories into an enum array. One thing to confirm: these fields are not present in updateProgramSchema, so they appear read‑only from that API. If they’re meant to be editable in the same β€œupdate program” surface, you’ll want to mirror them there (and in any corresponding forms); if they’re managed via a separate marketplace‑specific flow, the current separation is fine.

Also applies to: 26-42

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 580c8e4 and b2823fe.

πŸ“’ Files selected for processing (5)
  • apps/web/lib/actions/partners/update-application-settings.ts (1 hunks)
  • apps/web/lib/api/programs/get-program-or-throw.ts (2 hunks)
  • apps/web/lib/zod/schemas/program-network.ts (1 hunks)
  • apps/web/lib/zod/schemas/programs.ts (2 hunks)
  • apps/web/ui/modals/application-settings-modal.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/lib/zod/schemas/program-network.ts
🧰 Additional context used
🧠 Learnings (12)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/ui/modals/application-settings-modal.tsx
πŸ“š Learning: 2025-11-24T16:36:36.196Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:36:36.196Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Only components exported from the `hubspot/ui-extensions` npm package can be used in settings components

Applied to files:

  • apps/web/ui/modals/application-settings-modal.tsx
πŸ“š Learning: 2025-11-24T16:36:36.196Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-11-24T16:36:36.196Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : React components from `hubspot/ui-extensions/crm` cannot be used in settings components

Applied to files:

  • apps/web/ui/modals/application-settings-modal.tsx
πŸ“š 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/ui/modals/application-settings-modal.tsx
πŸ“š Learning: 2025-10-28T19:17:28.085Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2985
File: apps/web/app/(ee)/api/network/programs/[programSlug]/route.ts:20-27
Timestamp: 2025-10-28T19:17:28.085Z
Learning: In this codebase, Prisma's extendedWhereUnique feature is enabled (or Prisma 5.x is used), allowing findUniqueOrThrow to accept additional non-unique filters alongside unique fields like slug. This pattern is valid and acceptable.

Applied to files:

  • apps/web/lib/api/programs/get-program-or-throw.ts
πŸ“š Learning: 2025-08-25T17:42:13.600Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2736
File: apps/web/lib/api/get-workspace-users.ts:76-83
Timestamp: 2025-08-25T17:42:13.600Z
Learning: Business rule confirmed: Each workspace has exactly one program. The code should always return workspace.programs[0] since there's only one program per workspace.

Applied to files:

  • apps/web/lib/api/programs/get-program-or-throw.ts
πŸ“š Learning: 2025-06-18T20:26:25.177Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/commissions-block.tsx:16-27
Timestamp: 2025-06-18T20:26:25.177Z
Learning: In the Dub codebase, components that use workspace data (workspaceId, defaultProgramId) are wrapped in `WorkspaceAuth` which ensures these values are always available, making non-null assertions safe. This is acknowledged as a common pattern in their codebase, though not ideal.

Applied to files:

  • apps/web/lib/api/programs/get-program-or-throw.ts
πŸ“š 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/lib/actions/partners/update-application-settings.ts
πŸ“š Learning: 2025-08-26T14:20:23.943Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/app/api/workspaces/[idOrSlug]/notification-preferences/route.ts:13-14
Timestamp: 2025-08-26T14:20:23.943Z
Learning: The updateNotificationPreference action in apps/web/lib/actions/update-notification-preference.ts already handles all notification preference types dynamically, including newBountySubmitted, through its schema validation using the notificationTypes enum and Prisma's dynamic field update pattern.

Applied to files:

  • apps/web/lib/actions/partners/update-application-settings.ts
πŸ“š 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/lib/zod/schemas/programs.ts
πŸ“š Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 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/programs.ts
🧬 Code graph analysis (3)
apps/web/lib/api/programs/get-program-or-throw.ts (1)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/actions/partners/update-application-settings.ts (2)
apps/web/lib/actions/safe-action.ts (1)
  • authActionClient (39-88)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/zod/schemas/programs.ts (1)
apps/web/lib/zod/schemas/rewards.ts (1)
  • RewardSchema (166-176)
⏰ 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 (2)
apps/web/ui/modals/application-settings-modal.tsx (1)

163-183: LGTM: Clean hook pattern.

The hook correctly uses useState, useCallback, and useMemo to expose a stable API for managing modal visibility and rendering. This pattern prevents unnecessary re-renders while maintaining encapsulation.

apps/web/lib/api/programs/get-program-or-throw.ts (1)

6-14: includeCategories flag wiring is backward‑compatible and clear

The new includeCategories parameter with a default of false keeps existing callers working while allowing new call sites to opt into categories. The type and default align correctly with how it’s used below.

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

♻️ Duplicate comments (1)
apps/web/app/(ee)/api/network/programs/route.ts (1)

64-82: Reward type multi‑select still behaves as AND, not OR

The rewardType filter still builds a single groups.some object with multiple saleRewardId/leadRewardId/clickRewardId/discountId conditions, so selecting ["sale","lead"] will only match programs whose default group has both sale and lead rewards. Marketplace users generally expect multi‑select filters to be OR (β€œsale or lead”).

You can switch this to an OR array as previously suggested:

       ...(rewardType && {
         groups: {
           some: {
             slug: DEFAULT_PARTNER_GROUP.slug,
-            ...(rewardType.includes("sale") && {
-              saleRewardId: { not: null },
-            }),
-            ...(rewardType.includes("lead") && {
-              leadRewardId: { not: null },
-            }),
-            ...(rewardType.includes("click") && {
-              clickRewardId: { not: null },
-            }),
-            ...(rewardType.includes("discount") && {
-              discountId: { not: null },
-            }),
+            OR: [
+              ...(rewardType.includes("sale")
+                ? [{ saleRewardId: { not: null } }]
+                : []),
+              ...(rewardType.includes("lead")
+                ? [{ leadRewardId: { not: null } }]
+                : []),
+              ...(rewardType.includes("click")
+                ? [{ clickRewardId: { not: null } }]
+                : []),
+              ...(rewardType.includes("discount")
+                ? [{ discountId: { not: null } }]
+                : []),
+            ],
           },
         },
       }),
🧹 Nitpick comments (3)
apps/web/ui/partners/program-marketplace/program-reward-icon.tsx (1)

37-37: Remove redundant optional chaining.

The optional chaining operator is unnecessary here since this code is already inside a conditional block that checks for onClick's existence (line 32).

Apply this diff:

-            onClick?.();
+            onClick();
apps/web/app/(ee)/api/network/programs/route.ts (2)

43-56: Search + featured filter semantics and case sensitivity

Two small UX nits to consider:

  • featured: ...(featured && {...}) means featured=false behaves the same as omitting the param. If callers might ever send featured=false expecting β€œnon‑featured only”, that won’t happen today. If that’s not a use case, this is fine.
  • search: contains: search is case‑sensitive by default in Prisma. If you want more forgiving search, add mode: "insensitive" on each searchable field.

Example tweak for the search block:

-      ...(search && {
-        OR: [
-          { name: { contains: search } },
-          { slug: { contains: search } },
-          { domain: { contains: search } },
-          { url: { contains: search } },
-        ],
-      }),
+      ...(search && {
+        OR: [
+          { name: { contains: search, mode: "insensitive" } },
+          { slug: { contains: search, mode: "insensitive" } },
+          { domain: { contains: search, mode: "insensitive" } },
+          { url: { contains: search, mode: "insensitive" } },
+        ],
+      }),

121-142: Optional: extract PROMOTED_PROGRAM_IDS and avoid mutating programs

The promotion logic is fine functionally, but you could make it a bit cleaner:

  • Hoist PROMOTED_PROGRAM_IDS to a top‑level const so it isn’t re‑allocated on every request.
  • Instead of mutating programs with splice, consider returning a new array for clarity.

For example:

-  if (sortBy === "popularity") {
-    const PROMOTED_PROGRAM_IDS = [/* ... */];
-    const promoted = programs.filter((p) =>
-      PROMOTED_PROGRAM_IDS.includes(p.id),
-    );
-    const others = programs.filter((p) => !PROMOTED_PROGRAM_IDS.includes(p.id));
-    promoted.sort(
-      (a, b) =>
-        PROMOTED_PROGRAM_IDS.indexOf(a.id) - PROMOTED_PROGRAM_IDS.indexOf(b.id),
-    );
-    programs.splice(0, programs.length, ...promoted, ...others);
-  }
+  if (sortBy === "popularity") {
+    const promoted = programs.filter((p) =>
+      PROMOTED_PROGRAM_IDS.includes(p.id),
+    );
+    const others = programs.filter((p) => !PROMOTED_PROGRAM_IDS.includes(p.id));
+    promoted.sort(
+      (a, b) =>
+        PROMOTED_PROGRAM_IDS.indexOf(a.id) - PROMOTED_PROGRAM_IDS.indexOf(b.id),
+    );
+    programs = [...promoted, ...others];
+  }

(Adjust programs to let if you go this route.)

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 81531cf and 22ef196.

πŸ“’ Files selected for processing (2)
  • apps/web/app/(ee)/api/network/programs/route.ts (1 hunks)
  • apps/web/ui/partners/program-marketplace/program-reward-icon.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (11)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-11-12T22:23:10.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 3098
File: apps/web/lib/actions/partners/message-program.ts:49-58
Timestamp: 2025-11-12T22:23:10.414Z
Learning: In apps/web/lib/actions/partners/message-program.ts, when checking if a partner can continue messaging after messaging is disabled, the code intentionally requires `senderPartnerId: null` (program-initiated messages) to prevent partners from continuing to send junk messages. Only conversations started by the program should continue after messaging is disabled, as a spam prevention mechanism.

Applied to files:

  • apps/web/app/(ee)/api/network/programs/route.ts
πŸ“š Learning: 2025-06-06T07:59:03.120Z
Learnt from: devkiran
Repo: dubinc/dub PR: 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/app/(ee)/api/network/programs/route.ts
πŸ“š Learning: 2025-11-24T09:10:12.536Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/lib/api/fraud/fraud-rules-registry.ts:17-25
Timestamp: 2025-11-24T09:10:12.536Z
Learning: In apps/web/lib/api/fraud/fraud-rules-registry.ts, the fraud rules `partnerCrossProgramBan` and `partnerDuplicatePayoutMethod` intentionally have stub implementations that return `{ triggered: false }` because they are partner-scoped rules handled separately during partner application/onboarding flows (e.g., in detect-record-fraud-application.ts), rather than being evaluated per conversion event like other rules in the registry. The stubs exist only to satisfy the `Record<FraudRuleType, ...>` type constraint.

Applied to files:

  • apps/web/app/(ee)/api/network/programs/route.ts
πŸ“š Learning: 2025-08-16T11:14:00.667Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2754
File: apps/web/lib/partnerstack/schemas.ts:47-52
Timestamp: 2025-08-16T11:14:00.667Z
Learning: The PartnerStack API always includes the `group` field in partner responses, so the schema should use `.nullable()` rather than `.nullish()` since the field is never omitted/undefined.

Applied to files:

  • apps/web/app/(ee)/api/network/programs/route.ts
πŸ“š Learning: 2025-11-24T08:55:31.332Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3089
File: apps/web/app/(ee)/api/fraud-rules/route.ts:71-87
Timestamp: 2025-11-24T08:55:31.332Z
Learning: In apps/web/app/(ee)/api/fraud-rules/route.ts, fraud rules cannot be created in a disabled state. When using prisma.fraudRule.upsert, the create branch intentionally omits the disabledAt field (defaulting to null, meaning enabled), while the update branch allows toggling enabled/disabled state via the disabledAt field. This is a business logic constraint.

Applied to files:

  • apps/web/app/(ee)/api/network/programs/route.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)/api/network/programs/route.ts
πŸ“š Learning: 2025-09-12T17:31:10.548Z
Learnt from: devkiran
Repo: dubinc/dub PR: 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)/api/network/programs/route.ts
πŸ“š Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 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)/api/network/programs/route.ts
πŸ“š 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/ui/partners/program-marketplace/program-reward-icon.tsx
πŸ“š Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.

Applied to files:

  • apps/web/ui/partners/program-marketplace/program-reward-icon.tsx
🧬 Code graph analysis (2)
apps/web/app/(ee)/api/network/programs/route.ts (4)
apps/web/lib/network/throw-if-partner-cannot-view-marketplace.ts (1)
  • throwIfPartnerCannotViewMarketplace (10-50)
apps/web/lib/zod/schemas/program-network.ts (1)
  • getNetworkProgramsQuerySchema (35-59)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/zod/schemas/groups.ts (1)
  • DEFAULT_PARTNER_GROUP (16-20)
apps/web/ui/partners/program-marketplace/program-reward-icon.tsx (1)
packages/ui/src/icons/index.tsx (1)
  • Icon (82-82)
⏰ 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 (6)
apps/web/ui/partners/program-marketplace/program-reward-icon.tsx (4)

1-3: LGTM!

The imports are appropriate for the component's functionality.


5-15: LGTM!

The component props are well-defined with appropriate types. The Icon naming pattern is consistent with the codebase conventions.


16-16: Verify TypeScript type safety with dynamic component pattern.

The dynamic component pattern (const As = onClick ? "button" : "div") may cause TypeScript issues since button and div elements accept different props. While the conditional spread on lines 32-39 attempts to handle this, TypeScript might not properly narrow the type, potentially allowing invalid prop combinations.

Consider using a type assertion or refactoring to explicit conditional rendering if type errors occur.


35-36: Verify preventDefault and stopPropagation are intentional.

The event handlers prevent default behavior and stop event bubbling, suggesting this component is used within a clickable parent element (e.g., a program card). Confirm this is the intended behavior, as it will prevent parent click handlers from executing.

apps/web/app/(ee)/api/network/programs/route.ts (2)

12-27: Auth guard + query parsing look solid

The partner access guard + throwIfPartnerCannotViewMarketplace and the Zod-based parsing of searchParams are wired correctly and give you a nicely validated input surface for the rest of the handler. No issues here.


83-93: Status filter fix correctly handles null vs undefined

The guard status !== undefined plus the inner status === null ? none : some logic looks correct and fixes the earlier bug where status === null was never applied. This should now properly distinguish β€œno enrollment yet” from β€œfilter by a specific status”.

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 (2)
apps/web/scripts/send-batch-emails.ts (2)

10-50: Add error handling for database operations.

The main function performs database queries and email queueing without error handling. If the Prisma query fails, the error will be uncaught.

Apply this diff to add try-catch error handling:

 async function main() {
-  const partners = await prisma.partner.findMany({
-    where: {
-      email: "[email protected]",
-      users: {
-        some: {},
-      },
-      programs: {
-        some: {
-          programId: {
-            notIn: EXCLUDED_PROGRAM_IDS,
-          },
-          status: "approved",
-          totalCommissions: {
-            gte: PARTNER_NETWORK_MIN_COMMISSIONS_CENTS,
-          },
-        },
-        none: {
-          status: "banned",
-        },
-      },
-    },
-  });
-
-  const res = await queueBatchEmail<typeof ProgramMarketplaceAnnouncement>(
-    partners.map((partner) => ({
-      to: partner.email!,
-      subject: "Introducing the Dub Program Marketplace",
-      variant: "marketing",
-      replyTo: "noreply",
-      templateName: "ProgramMarketplaceAnnouncement",
-      templateProps: {
-        email: partner.email!,
-      },
-    })),
-  );
-
-  console.log({ res });
+  try {
+    const partners = await prisma.partner.findMany({
+      where: {
+        email: "[email protected]",
+        users: {
+          some: {},
+        },
+        programs: {
+          some: {
+            programId: {
+              notIn: EXCLUDED_PROGRAM_IDS,
+            },
+            status: "approved",
+            totalCommissions: {
+              gte: PARTNER_NETWORK_MIN_COMMISSIONS_CENTS,
+            },
+          },
+          none: {
+            status: "banned",
+          },
+        },
+      },
+    });
+
+    const res = await queueBatchEmail<typeof ProgramMarketplaceAnnouncement>(
+      partners.map((partner) => ({
+        to: partner.email!,
+        subject: "Introducing the Dub Program Marketplace",
+        variant: "marketing",
+        replyTo: "noreply",
+        templateName: "ProgramMarketplaceAnnouncement",
+        templateProps: {
+          email: partner.email!,
+        },
+      })),
+    );
+
+    console.log({ res });
+  } catch (error) {
+    console.error("Failed to send batch emails:", error);
+    throw error;
+  }
 }

10-32: Consider adding logging before the operation starts.

Adding a log statement before querying partners would help track script execution and show the partner count being processed.

 async function main() {
+  console.log("Starting batch email send for Program Marketplace announcement...");
+  
   const partners = await prisma.partner.findMany({
     where: {
       email: "[email protected]",

After the query, you could also log the count:

   });
+  
+  console.log(`Found ${partners.length} qualifying partner(s)`);
 
   const res = await queueBatchEmail<typeof ProgramMarketplaceAnnouncement>(
πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 22ef196 and b2d1af5.

πŸ“’ Files selected for processing (4)
  • apps/web/app/(ee)/api/network/programs/count/route.ts (1 hunks)
  • apps/web/app/(ee)/api/network/programs/route.ts (1 hunks)
  • apps/web/scripts/send-batch-emails.ts (1 hunks)
  • apps/web/scripts/send-batch-emails.tsx (0 hunks)
πŸ’€ Files with no reviewable changes (1)
  • apps/web/scripts/send-batch-emails.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/app/(ee)/api/network/programs/count/route.ts
  • apps/web/app/(ee)/api/network/programs/route.ts
🧰 Additional context used
🧠 Learnings (3)
πŸ““ Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
πŸ“š Learning: 2025-11-17T05:19:11.972Z
Learnt from: devkiran
Repo: dubinc/dub PR: 3113
File: apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts:65-75
Timestamp: 2025-11-17T05:19:11.972Z
Learning: In the Dub codebase, `sendBatchEmail` (implemented in packages/email/src/send-via-resend.ts) handles filtering of emails with invalid `to` addresses internally. Call sites can safely use non-null assertions on email addresses because the email sending layer will filter out any entries with null/undefined `to` values before sending. This centralized validation pattern is intentional and removes the need for filtering at individual call sites.

Applied to files:

  • apps/web/scripts/send-batch-emails.ts
πŸ“š 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/scripts/send-batch-emails.ts
🧬 Code graph analysis (1)
apps/web/scripts/send-batch-emails.ts (4)
packages/prisma/index.ts (1)
  • prisma (3-9)
apps/web/lib/constants/partner-profile.ts (2)
  • EXCLUDED_PROGRAM_IDS (10-14)
  • PARTNER_NETWORK_MIN_COMMISSIONS_CENTS (15-15)
apps/web/lib/email/queue-batch-email.ts (1)
  • queueBatchEmail (18-87)
packages/email/src/templates/program-marketplace-announcement.tsx (1)
  • ProgramMarketplaceAnnouncement (17-132)
⏰ 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

@steven-tey steven-tey merged commit c245ba7 into main Dec 10, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the program-marketplace branch December 10, 2025 02:04
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.

6 participants