-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Messaging unenrolled partners #3098
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 enrollment-centered queries with direct Prisma partner/program queries across message APIs and actions; adds an Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant PageClient
participant API_Route
participant Prisma
Note right of PageClient `#F0F7FF`: derive enrolledProgram/enrolledPartner\nfrom programEnrollment OR messages payload
Browser->>PageClient: open messages page (programSlug/partnerId)
PageClient->>API_Route: fetch messages payload (partners|programs)
API_Route->>Prisma: query partners/programs with OR filters\n(discoverable || enrolled || has messages)
Prisma-->>API_Route: return entities with limited, ordered messages
API_Route-->>PageClient: return shaped payload ({ partner|program, messages })
PageClient->>Browser: render UI (gate right-panel on enrolledProgram/enrolledPartner)
Estimated code review effortπ― 4 (Complex) | β±οΈ ~45 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touchesβ Failed checks (1 warning)
β Passed checks (2 passed)
β¨ Finishing touches
π§ͺ Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (1)
28-271: Missing messaging enabled check contradicts PR objectives.The AI summary states "messaging disabled checks are relocated to page-client," but there is no check for
program.messagingEnabledAtor similar in this file. TheMessagesPaneland send message functionality are always active regardless of whether messaging is enabled for the program.Based on learnings, the team uses
messagingEnabledAttimestamps to control messaging availability. This check should be implemented to prevent messages from being sent when messaging is disabled.Consider adding a check like this before rendering the MessagesPanel:
const isMessagingEnabled = program?.messagingEnabledAt !== null; // Then in the render: <MessagesPanel messages={messages && user ? messages : undefined} error={errorMessages} currentUserType="user" currentUserId={user?.id || ""} program={program} partner={partner} disabled={!isMessagingEnabled} onSendMessage={!isMessagingEnabled ? undefined : async (message) => { // existing implementation }} />Would you like me to open an issue to track implementing the messaging enabled check?
π§Ή Nitpick comments (3)
apps/web/lib/actions/partners/message-program.ts (1)
21-48: Verify the security implications of allowing continued messaging via message history.The second OR condition (lines 39-45) allows partners to continue messaging a program indefinitely if they have any message history, even if:
- They've been unenrolled from the program
- Messaging has been disabled (
messagingEnabledAtset tonull)While this supports conversation continuity, it effectively bypasses both enrollment and messaging-enabled checks for partners with prior messages. Please confirm this is the intended behavior.
Additionally, consider improving error handling. When
findFirstOrThrowfails, it throws a generic Prisma error that may not clearly communicate to the user why they cannot message (e.g., "not enrolled", "messaging disabled", or "program not found"). Consider catching the error and returning a more specific message:try { const program = await prisma.program.findFirstOrThrow({ // ... existing query }); } catch (error) { throw new Error( "You must be enrolled in this program with messaging enabled, or have an existing conversation." ); }apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsx (1)
33-33: Remove unusedprogramvariable.The
programvariable is extracted but never used inCapableLayout. According to the AI summary, themessagingEnabledAtcheck that previously used this value has been removed from this file.Apply this diff to remove the unused variable:
function CapableLayout({ children }: { children: ReactNode }) { const { slug: workspaceSlug } = useWorkspace(); const { partnerId } = useParams() as { partnerId?: string }; - const { program } = useProgram(); const router = useRouter();apps/web/lib/actions/partners/message-partner.ts (1)
38-64: Consider providing more specific error messages.The
findFirstOrThrowcall will throw a generic Prisma error ("Record not found") for both non-existent partners and partners that don't meet the access criteria.For better UX, consider catching the error and providing specific messages like "Partner not found" vs "You don't have access to message this partner."
Apply this diff to add specific error handling:
- // Make sure partner is either discoverable, enrolled in the program, or already has a message with the program - await prisma.partner.findFirstOrThrow({ + const partner = await prisma.partner.findFirst({ where: { id: partnerId, OR: [ { discoverableAt: { not: null, }, }, { programs: { some: { programId, }, }, }, { messages: { some: { programId, }, }, }, ], }, }); + + if (!partner) { + const partnerExists = await prisma.partner.findUnique({ + where: { id: partnerId }, + select: { id: true }, + }); + + throw new DubApiError({ + code: "not_found", + message: partnerExists + ? "You don't have access to message this partner. They must be discoverable, enrolled in your program, or already in conversation with you." + : "Partner not found.", + }); + }
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (13)
apps/web/app/(ee)/api/messages/route.ts(1 hunks)apps/web/app/(ee)/api/partner-profile/messages/route.ts(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx(6 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx(4 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsx(1 hunks)apps/web/lib/actions/partners/mark-program-messages-read.ts(1 hunks)apps/web/lib/actions/partners/message-partner.ts(1 hunks)apps/web/lib/actions/partners/message-program.ts(2 hunks)apps/web/lib/swr/use-program-enrollment.ts(2 hunks)apps/web/lib/zod/schemas/messages.ts(1 hunks)apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx(1 hunks)
π§° Additional context used
π§ Learnings (7)
π Common learnings
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:254-254
Timestamp: 2025-05-29T04:49:42.842Z
Learning: In the Dub codebase, it's acceptable to keep `partners.dub.co` hardcoded rather than making it configurable for different environments.
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.
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: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/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsxapps/web/app/(ee)/api/messages/route.tsapps/web/lib/actions/partners/message-program.tsapps/web/lib/zod/schemas/messages.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsxapps/web/lib/actions/partners/mark-program-messages-read.tsapps/web/lib/actions/partners/message-partner.tsapps/web/ui/layout/sidebar/partners-sidebar-nav.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsxapps/web/app/(ee)/api/partner-profile/messages/route.ts
π Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.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/layout/sidebar/partners-sidebar-nav.tsx
𧬠Code graph analysis (11)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsx (2)
apps/web/lib/swr/use-program.ts (1)
useProgram(6-40)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsx (1)
MessagesDisabled(8-42)
apps/web/app/(ee)/api/messages/route.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/zod/schemas/messages.ts (1)
PartnerMessagesSchema(36-45)
apps/web/lib/actions/partners/message-program.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/api/create-id.ts (1)
createId(66-71)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsx (2)
packages/ui/src/icons/nucleo/msgs-dotted.tsx (1)
MsgsDotted(3-35)packages/ui/src/button.tsx (1)
buttonVariants(7-28)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (5)
apps/web/lib/swr/use-partner.ts (1)
usePartner(6-29)apps/web/ui/layout/page-content/toggle-side-panel-button.tsx (1)
ToggleSidePanelButton(4-66)apps/web/ui/partners/partner-info-section.tsx (1)
PartnerInfoSection(7-71)apps/web/ui/partners/partner-info-stats.tsx (1)
PartnerInfoStats(6-79)apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(6-46)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsx (4)
apps/web/lib/swr/use-program.ts (1)
useProgram(6-40)apps/web/lib/plan-capabilities.ts (1)
getPlanCapabilities(4-21)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-upsell.tsx (1)
MessagesUpsell(8-70)apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(6-46)
apps/web/lib/actions/partners/mark-program-messages-read.ts (1)
packages/prisma/index.ts (1)
prisma(3-9)
apps/web/lib/actions/partners/message-partner.ts (1)
packages/prisma/index.ts (1)
prisma(3-9)
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-45)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (2)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-45)apps/web/ui/layout/page-content/toggle-side-panel-button.tsx (1)
ToggleSidePanelButton(4-66)
apps/web/app/(ee)/api/partner-profile/messages/route.ts (1)
apps/web/lib/zod/schemas/messages.ts (1)
ProgramMessagesSchema(73-84)
β° 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 (15)
apps/web/lib/actions/partners/message-program.ts (2)
50-64: LGTM!The message creation correctly uses
program.idfrom the validated program, consistent with the refactored validation logic.
66-76: LGTM!The notification payload correctly uses
program.id, maintaining consistency with the refactored approach.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsx (2)
18-28: LGTM! Clean feature gating logic.The loading state and plan capability checks provide appropriate gates for the messaging feature.
18-28: Verification confirmed:messagingEnabledAtvalidation is properly relocated to page-client.The check has been moved to
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsx(line 12), where it returns<MessagesDisabled />whenprogram?.messagingEnabledAt === null. This prevents users from accessing messaging when the feature is disabled, while the layout correctly relies on the plan capability check (canMessagePartners).apps/web/lib/actions/partners/message-partner.ts (1)
38-64: Code is correct; discoverable partner messaging is intentional and properly gated.The verification confirms this change implements the intended behavior. Messaging access is controlled at three levels: (1) plan capability (
canMessagePartners), (2) program-level enablement (messagingEnabledAt), and (3) partner accessibility (discoverable, enrolled, or in-conversation). The discoverable flag is opt-in by partners, and this same pattern is consistently deployed across the messaging system. No additional access controls are needed.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (5)
35-38: Good addition of retry skip for 404 errors.The
shouldRetryOnErroroption prevents unnecessary retries when a partner is not enrolled, improving performance and reducing API load. The renamed variables (enrolledPartner,enrolledPartnerError) also better convey the enrollment-specific nature of this data.
70-70: LGTM!Deriving partner data from messages provides a sensible fallback for non-enrolled partners, ensuring the UI can still display basic partner information when direct enrollment data is unavailable.
78-78: Verify redirect logic handles all error scenarios.The redirect now only triggers on
errorMessages, but what happens ifenrolledPartnerErrorexists buterrorMessagesis undefined? The page may render in an incomplete state. Consider whether a redirect should also occur when partner data fails to load without message errors.
273-298: LGTM!The
ViewPartnerButtoncomponent correctly routes to the appropriate profile page based on enrollment status. The implementation is clean and uses proper Next.js Link patterns with external tab opening.
121-128: Review comment verification complete β concern is invalid.The error handling in this code correctly distinguishes non-enrollment (404) from other error types. Here's why:
The
usePartnerhook is configured withshouldRetryOnError: (err) => err.status !== 404, which means:
- 404 errors: Stop retrying immediately (error becomes permanent)
- Non-404 errors (network, permission, etc.): Continue retrying indefinitely
The API returns 404 only when
getPartnerForProgramfinds no enrollment matching thepartnerId_programIdcomposite key, and this error code maps to HTTP 404 status.Therefore,
enrolledPartnerErroris truthy exclusively when the partner is not enrolled in the program. Other error types will continue to be retried by SWR and won't become permanent, so the conditional logic at lines 121β128 is correct.apps/web/lib/zod/schemas/messages.ts (1)
75-81: Expose messagingEnabledAt for consumer gating.Thanks for threading the field through this schema so downstream messaging flows can respect disabled programs.
apps/web/lib/actions/partners/mark-program-messages-read.ts (1)
18-47: Guard program lookup by partner/message relationship.Nice pivot away from enrollment-only checks; this keeps the authorization tight while letting historical conversations stay readable.
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1)
276-279: Gate SWR fetch behind enrolled pages.Appreciate the
enabledflag so we skip the enrollment fetch when weβre outside the program views.apps/web/lib/swr/use-program-enrollment.ts (1)
9-26:enabledhook option keeps API flexible.Optional flag keeps legacy callers untouched while letting consumers defer the network work when desired.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsx (1)
12-40: Simpler empty state layout reads well.The streamlined copy and CTA make the βenable messagingβ path obvious.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
Show resolved
Hide resolved
β¦nto message-unenrolled-partners
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
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (2)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1)
166-178: Useprograminstead ofenrolledProgramfor support email.For unenrolled partners who have existing messages but messaging is now disabled,
enrolledProgramwill be undefined, preventing them from seeing the support email button. Sinceprogram(from message data) should also contain the support email, use it instead for consistency.Apply this diff:
- {enrolledProgram?.supportEmail && ( + {program?.supportEmail && ( <Link - href={`mailto:${enrolledProgram.supportEmail}`} + href={`mailto:${program.supportEmail}`} target="_blank" >apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (1)
96-118: Avatar/name button has confusing behavior for non-enrolled partners.The onClick handler at line 98 toggles
isRightPanelOpenunconditionally, but the right panel only renders whenenrolledPartnerexists (line 215). For non-enrolled partners, clicking the avatar/name appears to do nothing because the panel never renders.Apply this diff to conditionally enable the onClick based on enrollment status:
<button type="button" - onClick={() => setIsRightPanelOpen((o) => !o)} + onClick={enrolledPartner ? () => setIsRightPanelOpen((o) => !o) : undefined} className="-mx-2 -my-1 flex items-center gap-2 rounded-lg px-2 py-1 transition-colors duration-100 hover:bg-black/5 active:bg-black/10" + disabled={!enrolledPartner} >Alternatively, consider navigating to the partner's network profile page when clicked for non-enrolled partners.
β»οΈ Duplicate comments (1)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (1)
215-249: Critical: Right panel doesn't render for non-enrolled partners.The right panel is wrapped in
{enrolledPartner && (...)}, which prevents it from rendering entirely when the partner is not enrolled. According to the AI summary and past review comments, the panel should render with a limited/pending view for non-enrolled partners.The past review comment suggested changing the outer conditional to allow the panel to render for both enrolled and non-enrolled partners:
- {enrolledPartner && ( + {(enrolledPartner || partner) && (However, this would require additional conditional rendering inside the panel, as components like
PartnerInfoStatsexpect fullEnrolledPartnerProps. Consider either:
- Implementing the limited view for non-enrolled partners as intended, OR
- Confirming that the current behavior (no right panel for non-enrolled partners) is the desired design
The
ViewPartnerButtonin the header (line 126) currently serves as the only way to access non-enrolled partner info, which may be sufficient if that's the intended UX.
π§Ή Nitpick comments (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1)
85-85: Consider adding a fallback for enrolled partners with no messages yet.If a partner is enrolled but hasn't messaged the program yet,
programwill be undefined whileenrolledProgramis defined. This could cause the UI to show loading states unnecessarily.Consider using a fallback pattern:
- const program = programMessages?.[0]?.program; + const program = programMessages?.[0]?.program ?? enrolledProgram;This ensures program data is always available when the partner is enrolled, regardless of message history.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (2)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx(7 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx(5 hunks)
π§° Additional context used
π§ Learnings (8)
π Common learnings
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.
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: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)/messages/[programSlug]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π 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/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.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/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π Learning: 2025-10-08T21:33:23.553Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2936
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/analytics/add-hostname-modal.tsx:28-34
Timestamp: 2025-10-08T21:33:23.553Z
Learning: In the dub/ui Button component, when the `disabledTooltip` prop is set to a non-undefined value (e.g., a string), the button is automatically disabled. Therefore, it's not necessary to also add the same condition to the `disabled` propβsetting `disabledTooltip={permissionsError || undefined}` is sufficient to disable the button when there's a permissions error.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
𧬠Code graph analysis (2)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (2)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-45)apps/web/ui/layout/page-content/toggle-side-panel-button.tsx (1)
ToggleSidePanelButton(4-66)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (6)
apps/web/lib/swr/use-partner.ts (1)
usePartner(6-29)apps/web/ui/layout/page-content/toggle-side-panel-button.tsx (1)
ToggleSidePanelButton(4-66)apps/web/ui/partners/partner-info-section.tsx (1)
PartnerInfoSection(7-71)apps/web/ui/partners/partner-info-group.tsx (1)
PartnerInfoGroup(11-86)apps/web/ui/partners/partner-info-stats.tsx (1)
PartnerInfoStats(6-79)apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(6-46)
β° 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 (9)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (6)
48-54: LGTM! Good error handling for unenrolled partners.The
shouldRetryOnErrorlogic correctly avoids retrying 404 errors, which now represent a valid state (unenrolled partner with existing messages).
93-98: LGTM! Redirect logic correctly supports unenrolled messaging.The condition properly allows unenrolled partners to access the page when they have existing messages, while redirecting when neither enrollment nor message history exists.
143-150: LGTM! Appropriate conditional rendering based on enrollment status.The conditional logic correctly shows different actions based on whether the partner is enrolled, handling the loading state gracefully by rendering
null.
152-155: LGTM! Correctly implements continued messaging for existing conversations.The condition properly allows existing conversations to continue even when
messagingEnabledAtis null, while preventing new conversations. This aligns with the PR objective.
270-299: LGTM! Appropriate gating of enrollment-specific panel.Conditionally rendering the right panel only for enrolled partners is correct, as it displays enrollment-specific information like referral links and rewards that wouldn't be relevant for unenrolled partners.
470-480: LGTM! Clean component extraction.Nice extraction of the ViewProgramButton into a reusable component, following DRY principles.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (3)
34-77: LGTM!The data fetching logic is well-structured:
shouldRetryOnErrorcorrectly prevents infinite retries on 404 responses for non-enrolled partners- Partner data is appropriately sourced from the messages API response
- Error handling redirects to the messages index when message fetching fails
120-127: LGTM!The conditional rendering logic for header buttons is well-designed:
- Shows the toggle button for enrolled partners
- Shows a view button for non-enrolled partners (when error indicates 404)
- Shows nothing while loading (prevents layout shift)
254-279: LGTM!The
ViewPartnerButtoncomponent is well-implemented:
- Correctly routes enrolled partners to their profile page
- Routes non-enrolled partners to the network page
- Uses appropriate button text for each case
- Opens in a new tab to preserve the messages context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
π§Ή Nitpick comments (2)
apps/web/ui/partners/partner-network/network-partner-sheet.tsx (1)
78-88: Consider adding screen reader indication for new tab behavior.While the implementation is clean and functional, screen reader users won't be informed that the link opens in a new tab. This is a minor accessibility enhancement.
<Link href={`/${workspaceSlug}/program/messages/${partner.id}`} target="_blank" + aria-label="Message partner (opens in new tab)" > <Button variant="secondary" text="Message" icon={<Msgs className="size-4 shrink-0" />} className="hidden h-9 rounded-lg px-4 sm:flex" /> </Link>apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (1)
120-127: Consider adding a loading state placeholder.When
enrolledPartneris still loading (neither exists nor errored), the header rendersnull, creating a visual gap. While this is functional, showing a loading skeleton would improve the user experience.Apply this diff to add a loading indicator:
{enrolledPartner ? ( <ToggleSidePanelButton isOpen={isRightPanelOpen} onClick={() => setIsRightPanelOpen((o) => !o)} /> ) : enrolledPartnerError ? ( <ViewPartnerButton partnerId={partnerId} isEnrolled={false} /> - ) : null} + ) : ( + <div className="size-9 animate-pulse rounded-lg bg-neutral-200" /> + )}
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (2)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx(5 hunks)apps/web/ui/partners/partner-network/network-partner-sheet.tsx(3 hunks)
π§° Additional context used
π§ Learnings (6)
π Common learnings
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.
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: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/ui/partners/partner-network/network-partner-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
𧬠Code graph analysis (2)
apps/web/ui/partners/partner-network/network-partner-sheet.tsx (1)
apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(6-46)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (3)
apps/web/lib/swr/use-partner.ts (1)
usePartner(6-29)apps/web/ui/layout/page-content/toggle-side-panel-button.tsx (1)
ToggleSidePanelButton(4-66)apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(6-46)
β° 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 (4)
apps/web/ui/partners/partner-network/network-partner-sheet.tsx (2)
6-6: LGTM: Imports for messaging feature.The new imports (
useWorkspace,Msgs,Link) are appropriate and necessary for the messaging button implementation.Also applies to: 14-14, 21-21
43-43: Add null check forworkspaceSlug.The
useWorkspacehook can returnundefinedfor workspace properties during loading or error states. UsingworkspaceSlugwithout validation (line 79) will create an invalid href like/undefined/program/messages/.... The existing code pattern at line 224 demonstrates checking workspace properties before use.Consider one of these approaches:
Option 1: Conditionally render the button
const { slug: workspaceSlug } = useWorkspace(); + const [currentTabId, setCurrentTabId] = useState<string>("about");Then wrap the Link in a conditional:
<div className="flex items-center gap-4"> - <Link - href={`/${workspaceSlug}/program/messages/${partner.id}`} - target="_blank" - > - <Button - variant="secondary" - text="Message" - icon={<Msgs className="size-4 shrink-0" />} - className="hidden h-9 rounded-lg px-4 sm:flex" - /> - </Link> + {workspaceSlug && ( + <Link + href={`/${workspaceSlug}/program/messages/${partner.id}`} + target="_blank" + > + <Button + variant="secondary" + text="Message" + icon={<Msgs className="size-4 shrink-0" />} + className="hidden h-9 rounded-lg px-4 sm:flex" + /> + </Link> + )} <div className="flex items-center">Option 2: Provide a fallback or disable the button
<Link - href={`/${workspaceSlug}/program/messages/${partner.id}`} + href={workspaceSlug ? `/${workspaceSlug}/program/messages/${partner.id}` : '#'} target="_blank" > <Button variant="secondary" text="Message" icon={<Msgs className="size-4 shrink-0" />} className="hidden h-9 rounded-lg px-4 sm:flex" + disabled={!workspaceSlug} /> </Link>β Skipped due to learnings
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.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.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (2)
34-37: LGTM! Error handling correctly differentiates enrollment status.The
shouldRetryOnErrorconfiguration prevents retrying on 404 errors, allowing the component to distinguish between enrolled and non-enrolled partners. When a partner is not enrolled,enrolledPartnerErrorwill be set, enabling the conditional rendering logic at lines 125-126 to show the appropriate fallback UI.
215-249: Right panel correctly restricted to enrolled partners.The conditional rendering
{enrolledPartner && (...)}ensures the profile panel only displays for enrolled partners, which aligns with the PR objectives. For non-enrolled partners, users can view basic info via the "View partner" button in the header (line 126), while the messaging functionality remains available in the left panel.Note: The past review comment about unreachable code at lines 250-264 is no longer applicable to this version of the code.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
Show resolved
Hide resolved
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 (5)
apps/web/app/(ee)/api/messages/route.ts(1 hunks)apps/web/app/(ee)/api/partner-profile/messages/route.ts(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx(8 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx(6 hunks)apps/web/lib/actions/partners/message-program.ts(2 hunks)
π§° Additional context used
π§ Learnings (10)
π Common learnings
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.
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: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)/messages/[programSlug]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π 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/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsxapps/web/app/(ee)/api/partner-profile/messages/route.tsapps/web/lib/actions/partners/message-program.tsapps/web/app/(ee)/api/messages/route.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.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/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π Learning: 2025-10-08T21:33:23.553Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2936
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/analytics/add-hostname-modal.tsx:28-34
Timestamp: 2025-10-08T21:33:23.553Z
Learning: In the dub/ui Button component, when the `disabledTooltip` prop is set to a non-undefined value (e.g., a string), the button is automatically disabled. Therefore, it's not necessary to also add the same condition to the `disabled` propβsetting `disabledTooltip={permissionsError || undefined}` is sufficient to disable the button when there's a permissions error.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π Learning: 2025-06-18T20:23:38.835Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx:50-82
Timestamp: 2025-06-18T20:23:38.835Z
Learning: Internal links within the same application that use target="_blank" may not require rel="noopener noreferrer" according to the team's security standards, even though it's generally considered a best practice for any target="_blank" link.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π Learning: 2025-09-24T15:50:16.414Z
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.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
𧬠Code graph analysis (5)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1)
apps/web/lib/swr/use-program-enrollment.ts (1)
useProgramEnrollment(8-45)
apps/web/app/(ee)/api/partner-profile/messages/route.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/zod/schemas/messages.ts (1)
ProgramMessagesSchema(73-84)
apps/web/lib/actions/partners/message-program.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/api/create-id.ts (1)
createId(66-71)
apps/web/app/(ee)/api/messages/route.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/zod/schemas/messages.ts (1)
PartnerMessagesSchema(36-45)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (6)
apps/web/lib/swr/use-partner.ts (1)
usePartner(6-29)apps/web/ui/layout/page-content/toggle-side-panel-button.tsx (1)
ToggleSidePanelButton(4-66)apps/web/ui/partners/partner-info-section.tsx (1)
PartnerInfoSection(7-71)apps/web/ui/partners/partner-info-group.tsx (1)
PartnerInfoGroup(11-86)apps/web/ui/partners/partner-info-stats.tsx (1)
PartnerInfoStats(6-79)apps/web/lib/swr/use-workspace.ts (1)
useWorkspace(6-46)
β° 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)
apps/web/app/(ee)/api/messages/route.ts (2)
24-59: Query logic correctly enables messaging unenrolled partners.The partner-based query structure properly implements the PR objective of allowing programs to message partners not currently enrolled. The OR conditions appropriately cover discoverable partners, enrolled partners, and partners with existing message threads.
63-76: Data transformation correctly aligns with new query structure.The sorting and mapping logic properly handles the restructured data, using the
messagespath directly and producing the expected{partner, messages}format.apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (3)
34-37: Partner data fetching correctly handles enrollment status.The combination of
usePartnerwith 404 error handling and deriving basic partner info from messages enables the UI to gracefully handle both enrolled and non-enrolled partners.Also applies to: 69-69
77-77: Redirect logic appropriately handles error states.The redirect only triggers on message errors, allowing the page to render even when enrollment data is unavailable (404). This aligns with the PR objective of enabling messaging to non-enrolled partners.
121-128: Enrollment-aware UI correctly restricts profile access.The implementation appropriately gates detailed profile information (stats, group, actions) to enrolled partners only, while providing a navigation path to view non-enrolled partners in the network. This aligns with the access control model for partner data.
Also applies to: 216-250, 255-280
apps/web/app/(ee)/api/partner-profile/messages/route.ts (2)
20-97: Query logic correctly implements partner-side message access.The program-based query properly enforces access control:
- Ensures partners are not banned (lines 22-28)
- Allows enrolled partners to access program messages
- Allows non-enrolled partners to access messages if the program initiated contact
- Filters results appropriately based on whether a specific program is requested
101-114: Data transformation aligns with schema expectations.The sorting by most recent message and mapping to
{program, messages}format correctly produces the structure expected byProgramMessagesSchema.apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (4)
48-54: Program data fetching correctly handles enrollment status.The dual-source approach (enrollment data for enrolled partners, message-derived data for non-enrolled) enables the UI to gracefully handle both scenarios while maintaining appropriate access control.
Also applies to: 85-85
94-98: Redirect logic appropriately balances access control.The conditional redirect allows non-enrolled partners to access existing message threads while preventing access when there's no enrollment and no message history. This correctly implements the messaging access model.
153-180: External support fallback correctly uses enrollment data.The conditional rendering for external support appropriately checks enrollment status and messaging availability, using
enrolledProgram.supportEmailto provide contact information when messaging is disabled.
144-151: Enrollment-aware UI correctly restricts program details.The implementation appropriately gates detailed program information (performance stats, rewards, referral links) to enrolled partners, while providing a navigation path for non-enrolled partners to view the public program page.
Also applies to: 271-300, 471-481
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
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (7)
apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts(0 hunks)apps/web/app/(ee)/api/messages/route.ts(1 hunks)apps/web/app/(ee)/api/partner-profile/messages/route.ts(1 hunks)apps/web/lib/actions/partners/message-partner.ts(2 hunks)apps/web/lib/actions/partners/message-program.ts(2 hunks)apps/web/lib/api/partners/get-network-invites-usage.ts(1 hunks)packages/prisma/schema/network.prisma(1 hunks)
π€ Files with no reviewable changes (1)
- apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts
π§° Additional context used
π§ Learnings (5)
π Common learnings
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.390Z
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.
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.
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.390Z
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.390Z
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/lib/actions/partners/message-program.tsapps/web/app/(ee)/api/messages/route.tsapps/web/lib/actions/partners/message-partner.tsapps/web/app/(ee)/api/partner-profile/messages/route.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/message-program.tsapps/web/app/(ee)/api/messages/route.tsapps/web/lib/api/partners/get-network-invites-usage.tsapps/web/lib/actions/partners/message-partner.tsapps/web/app/(ee)/api/partner-profile/messages/route.ts
π Learning: 2025-09-24T15:50:16.414Z
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.
Applied to files:
apps/web/lib/actions/partners/message-program.ts
π Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.
Applied to files:
apps/web/lib/api/partners/get-network-invites-usage.tsapps/web/lib/actions/partners/message-partner.ts
𧬠Code graph analysis (4)
apps/web/lib/actions/partners/message-program.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/api/create-id.ts (1)
createId(66-71)
apps/web/app/(ee)/api/messages/route.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/zod/schemas/messages.ts (1)
PartnerMessagesSchema(36-45)
apps/web/lib/actions/partners/message-partner.ts (4)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/api/partners/get-network-invites-usage.ts (1)
getNetworkInvitesUsage(5-30)apps/web/lib/api/errors.ts (1)
DubApiError(58-75)apps/web/lib/api/create-id.ts (1)
createId(66-71)
apps/web/app/(ee)/api/partner-profile/messages/route.ts (2)
packages/prisma/index.ts (1)
prisma(3-9)apps/web/lib/zod/schemas/messages.ts (1)
ProgramMessagesSchema(73-84)
β° 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
|
@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: 0
β»οΈ Duplicate comments (2)
apps/web/app/(ee)/api/partner-profile/messages/route.ts (1)
31-52: Reinstate the messaging-disabled spam guard.The
partners.somebranch still returns programs for any enrolled partner even whenmessagingEnabledAtis null. That was the path we explicitly closed to stop partners from pinging programs that turned messaging off unless the thread was program-initiated (senderPartnerId: null). Please keep enrollment-only access gated to active messaging and rely on the second branch for conversations the program started.{ partners: { some: { partnerId: partner.id, }, }, + messagingEnabledAt: { + not: null, + }, },Based on learnings
apps/web/lib/actions/partners/message-partner.ts (1)
88-93: Guard unlimited network invites before comparing.
workspace.networkInvitesLimitisnullfor unlimited workspaces, andnetworkInvitesUsage >= nullcoercesnullto0, so every outreach attempt immediately throws the βlimit reachedβ error. Please reintroduce the null-check before comparing so unlimited plans keep working.- if (networkInvitesUsage >= workspace.networkInvitesLimit) { + if ( + workspace.networkInvitesLimit != null && + networkInvitesUsage >= workspace.networkInvitesLimit + ) { throw new DubApiError({ code: "forbidden", message: "You have reached your partner network invitations limit.", }); }
π§Ή Nitpick comments (1)
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (1)
167-169: Add fallback toprogram?.supportEmailfor robust edge case coverage.The component already has access to the
programobject from the message payload (used for logo, name, and messagingEnabledAt checks). Based on codebase patterns, this object includessupportEmailas a standard field.Currently, the email link only renders when
enrolledProgram?.supportEmailexists. In the narrow edge case where a non-enrolled user has an empty conversation with messaging disabled,enrolledProgramwould be undefined and the support email wouldn't display, even though the program object would have it available.Change line 167 to:
{(enrolledProgram?.supportEmail ?? program?.supportEmail) && ( <Link href={`mailto:${enrolledProgram?.supportEmail ?? program?.supportEmail}`}This improves robustness without affecting the common enrollment paths.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
π Files selected for processing (19)
apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts(0 hunks)apps/web/app/(ee)/api/messages/route.ts(1 hunks)apps/web/app/(ee)/api/partner-profile/messages/route.ts(1 hunks)apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx(8 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsx(0 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx(6 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsx(1 hunks)apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsx(1 hunks)apps/web/lib/actions/partners/mark-program-messages-read.ts(1 hunks)apps/web/lib/actions/partners/message-partner.ts(2 hunks)apps/web/lib/actions/partners/message-program.ts(2 hunks)apps/web/lib/api/partners/get-network-invites-usage.ts(1 hunks)apps/web/lib/api/workflows/execute-send-campaign-workflow.ts(0 hunks)apps/web/lib/swr/use-program-enrollment.ts(2 hunks)apps/web/lib/zod/schemas/messages.ts(1 hunks)apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx(1 hunks)apps/web/ui/partners/partner-network/network-partner-sheet.tsx(3 hunks)packages/prisma/schema/network.prisma(1 hunks)
π€ Files with no reviewable changes (3)
- apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/campaigns/[campaignId]/campaign-editor.tsx
- apps/web/lib/api/workflows/execute-send-campaign-workflow.ts
- apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts
π§° Additional context used
π§ Learnings (16)
π Common learnings
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.390Z
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.
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.
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.390Z
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.390Z
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/ui/partners/partner-network/network-partner-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsxapps/web/lib/zod/schemas/messages.tsapps/web/lib/actions/partners/message-program.tsapps/web/lib/actions/partners/message-partner.tsapps/web/app/(ee)/api/partner-profile/messages/route.tsapps/web/app/(ee)/api/messages/route.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsxapps/web/lib/actions/partners/mark-program-messages-read.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.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/ui/partners/partner-network/network-partner-sheet.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsxapps/web/lib/zod/schemas/messages.tsapps/web/lib/actions/partners/message-program.tsapps/web/lib/swr/use-program-enrollment.tsapps/web/lib/actions/partners/message-partner.tsapps/web/app/(ee)/api/partner-profile/messages/route.tsapps/web/app/(ee)/api/messages/route.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsxapps/web/lib/api/partners/get-network-invites-usage.tsapps/web/lib/actions/partners/mark-program-messages-read.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/layout.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π Learning: 2025-09-18T16:33:17.719Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2858
File: apps/web/ui/partners/partner-application-tabs.tsx:1-1
Timestamp: 2025-09-18T16:33:17.719Z
Learning: When a React component in Next.js App Router uses non-serializable props (like setState functions), adding "use client" directive can cause serialization warnings. If the component is only imported by Client Components, it's better to omit the "use client" directive to avoid these warnings while still getting client-side execution through promotion.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/page-client.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/messages-disabled.tsx
π Learning: 2025-09-24T15:50:16.414Z
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.
Applied to files:
apps/web/lib/actions/partners/message-program.tsapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π Learning: 2025-10-06T15:48:14.205Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: apps/web/lib/actions/partners/invite-partner-from-network.ts:21-28
Timestamp: 2025-10-06T15:48:14.205Z
Learning: For the network invites limit check in apps/web/lib/actions/partners/invite-partner-from-network.ts, the team accepts that concurrent invites may bypass the limit due to race conditions. Perfect atomicity is not required for this feature.
Applied to files:
apps/web/lib/actions/partners/message-partner.tsapps/web/lib/api/partners/get-network-invites-usage.ts
π Learning: 2025-06-16T19:21:23.506Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2519
File: apps/web/ui/analytics/utils.ts:35-37
Timestamp: 2025-06-16T19:21:23.506Z
Learning: In the `useAnalyticsFilterOption` function in `apps/web/ui/analytics/utils.ts`, the pattern `options?.context ?? useContext(AnalyticsContext)` is intentionally designed as a complete replacement strategy, not a merge. When `options.context` is provided, it should contain all required fields (`baseApiPath`, `queryString`, `selectedTab`, `requiresUpgrade`) and completely replace the React context, not be merged with it. This is used for dependency injection or testing scenarios.
Applied to files:
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx
π 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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx
π Learning: 2025-06-18T20:23:38.835Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2538
File: apps/web/ui/partners/overview/blocks/traffic-sources-block.tsx:50-82
Timestamp: 2025-06-18T20:23:38.835Z
Learning: Internal links within the same application that use target="_blank" may not require rel="noopener noreferrer" according to the team's security standards, even though it's generally considered a best practice for any target="_blank" link.
Applied to files:
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.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/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsxapps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π Learning: 2025-09-24T15:47:40.293Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx:152-157
Timestamp: 2025-09-24T15:47:40.293Z
Learning: The Button component from dub/ui automatically adds type="button" when an onClick prop is passed, preventing accidental form submissions without requiring explicit type specification. The implementation uses: type={onClick ? "button" : type}
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π Learning: 2025-10-08T21:33:23.553Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2936
File: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/analytics/add-hostname-modal.tsx:28-34
Timestamp: 2025-10-08T21:33:23.553Z
Learning: In the dub/ui Button component, when the `disabledTooltip` prop is set to a non-undefined value (e.g., a string), the button is automatically disabled. Therefore, it's not necessary to also add the same condition to the `disabled` propβsetting `disabledTooltip={permissionsError || undefined}` is sufficient to disable the button when there's a permissions error.
Applied to files:
apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx
π Additional comments (11)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/messages/[partnerId]/page-client.tsx (5)
34-37: Excellent error handling for non-enrolled partners.The
shouldRetryOnErrorcallback that skips retries on 404 allows the UI to quickly determine enrollment status and show appropriate controls for non-enrolled partners. This is a clean approach.
69-69: Smart derivation of partner from message payload.Deriving partner data from the messages API response allows the UI to display partner information for both enrolled and non-enrolled partners without requiring a separate enrolled-partner check for basic display data.
121-128: Clear enrollment-aware header controls.The conditional rendering provides appropriate actions for each state: toggle panel for enrolled partners, navigation to network/profile page for non-enrolled partners, and clean loading state. The logic is straightforward and handles all cases.
216-250: Right panel rendering correctly simplified.The previous critical issue about unreachable fallback code has been resolved. The current implementation cleanly limits the right panel to enrolled partners only, while non-enrolled partners use the
ViewPartnerButtonin the header to navigate to the network page for partner details. This is a clearer separation of concerns.
255-280: Clean navigation component for enrollment-aware routing.The
ViewPartnerButtoncomponent cleanly routes to the appropriate page based on enrollment status. The implementation is straightforward and provides good UX for viewing partner details in a new tab.apps/web/app/(ee)/partners.dub.co/(dashboard)/messages/[programSlug]/page-client.tsx (6)
48-54: Good error handling for enrollment fetch.The retry logic correctly distinguishes between "not found" (404, an expected state for unenrolled partners) and transient errors that should be retried. This aligns well with the PR's goal of supporting messaging for unenrolled partners.
85-85: Smart derivation of program from messages.Deriving
programfrom the message payload allows the UI to display program information even for unenrolled partners who have ongoing conversations. This is a key enabler for the feature.
94-98: Redirect logic correctly supports the new feature.The condition allows unenrolled partners to view conversations (enrollment error but messages exist) while redirecting only when there's nothing to display. This properly implements the requirement that programs can message unenrolled partners.
121-122: Well-designed conditional rendering for enrollment states.The header button gating is thoughtfully implemented:
- Disabled when not enrolled (since the right panel shows enrollment-specific details)
- Shows the toggle button when enrolled
- Shows a ViewProgramButton when not enrolled but has messages (provides alternate action)
- Shows nothing during loading (acceptable UX)
Also applies to: 144-151
271-300: Right panel gating is correctly implemented.The panel displays enrollment-specific information (performance stats, referral links, rewards), so gating it behind
enrolledProgramexistence is the right approach. The inclusion of ViewProgramButton in the header provides users with a clear path to the full program page.
471-481: Clean implementation of ViewProgramButton.The component is simple, reusable, and follows good UX patterns by opening the program page in a new tab. Nice addition to provide navigation for unenrolled users.
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
Behavior Change