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

Skip to content

Conversation

@TWilson023
Copy link
Collaborator

@TWilson023 TWilson023 commented Sep 22, 2025

Summary by CodeRabbit

  • New Features
    • Editable profile sections: About You (description with live character counter, industry interests modal, monthly traffic), How You Work (preferred earning structures, sales channels), and enhanced Profile Details (avatar, company/individual flows, payout-aware confirmations).
  • Changes
    • Profile settings restructured into modular sub-forms; Website & Socials page removed; partner summary now shows interests, sales channels, preferred rewards, and traffic labels; UI form variants improved.
  • Backend
    • Profile fields persisted and validated to support new selections and limits.
  • Removed
    • Legacy client page for Website & Socials and related navigation entries.

@vercel
Copy link
Contributor

vercel bot commented Sep 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Sep 24, 2025 8:32pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 22, 2025

Walkthrough

Adds modular partner-profile UI (three new sub-forms and an interests modal), extends Prisma enums/relations and Zod schemas for partner profile fields, updates API/auth and updatePartnerProfile action to persist new relations/fields, adjusts related UI components, and removes the Website & Socials page.

Changes

Cohort / File(s) Summary
Profile page composition
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx
Rewrites page to compose ProfileDetailsForm, AboutYouForm, and HowYouWorkForm; removes centralized form/state, unified save flow, and legacy wrappers.
Profile forms
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx, apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx, apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx, apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/settings-row.tsx
Adds modular forms: profile details (avatar/name/email/country/type/company + Stripe gating), about (description, interests, monthlyTraffic), work preferences (preferredEarningStructures, salesChannels), and reusable SettingsRow.
Industry interests modal & shared UI
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/industry-interests-modal.tsx, apps/web/ui/shared/max-characters-counter.tsx
New IndustryInterestsModal with selection cap, save/cancel, and scroll progress; new MaxCharactersCounter for live character counts.
Profile catalogs & mappings
apps/web/lib/partners/partner-profile.ts
Adds arrays and lookup maps for industryInterests, monthlyTrafficAmounts, preferredEarningStructures, and salesChannels (ids, labels, icons).
Server action & validation
apps/web/lib/actions/partners/update-partner-profile.ts
Expands update schema via PartnerProfileSchema.partial(), adds runtime check requiring companyName for company profiles, accepts/persists monthlyTraffic and related collections, and updates relation replace logic.
Zod schemas
apps/web/lib/zod/schemas/partners.ts
Introduces PartnerProfileSchema, MAX constants (MAX_PARTNER_DESCRIPTION_LENGTH, MAX_PARTNER_INDUSTRY_INTERESTS), merges profile schema into Partner shapes, and adds PartnerWithProfileSchema.
API & auth shaping
apps/web/lib/api/partners/get-partner-for-program.ts, apps/web/lib/auth/partner.ts
API: aggregate/join new relation fields and return arrays; Auth: eager-load relations and flatten them to primitive arrays on partner objects.
Prisma schema & client exports
packages/prisma/schema/partner.prisma, packages/prisma/client.ts
Adds enums IndustryInterest, PreferredEarningStructure, SalesChannel, MonthlyTraffic; new join models for partner relations; adds monthlyTraffic field; exports new enums from client.
UI adjustments & removals
apps/web/ui/partners/online-presence-form.tsx, apps/web/ui/partners/partner-about.tsx, apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx, apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/sites/page-client.tsx, apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/sites/page.tsx
OnlinePresenceForm: adds variant-aware FormRow behavior; PartnerAbout: renders new profile facets (pills) and updates loading/error text; Sidebar: removes Profile/Payouts nav entries; Sites page & client removed (Website & Socials page deleted).
Icons
packages/ui/src/icons/nucleo/apple.tsx, packages/ui/src/icons/nucleo/index.ts, packages/ui/src/icons/nucleo/cloud.tsx
Adds new Apple and Cloud SVG icons and re-exports them.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant AYF as AboutYouForm
  participant MOD as IndustryInterestsModal
  participant ACT as updatePartnerProfileAction
  participant DB as Database
  participant T as Toast

  U->>AYF: Edit description / monthlyTraffic / open interests
  AYF->>MOD: open modal
  MOD-->>AYF: save(selectedInterests)
  AYF->>ACT: submit(formData)
  ACT->>DB: update partner + replace related relations
  DB-->>ACT: success / error
  alt success
    ACT-->>AYF: ok
    AYF-->>T: success toast
    AYF-->>AYF: reset dirty state
  else error
    ACT-->>AYF: error
    AYF-->>T: error toast
    AYF->>AYF: set form error
  end
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant PDF as ProfileDetailsForm
  participant MOD as StripeChangeConfirmModal
  participant ACT as updatePartnerProfileAction
  participant DB as Database
  participant T as Toast

  U->>PDF: Modify avatar/name/email/country/profileType/companyName
  PDF->>PDF: detect payouts & stripe connection
  alt country/profileType change requires confirmation
    PDF->>MOD: open confirmation
    alt confirm
      MOD-->>PDF: proceed
    else cancel
      MOD-->>PDF: abort
    end
  end
  PDF->>ACT: submit(formData)
  ACT->>DB: update partner + nested relations
  DB-->>ACT: success / error
  alt success
    ACT-->>PDF: ok
    PDF-->>T: show success or verification prompt
    PDF-->>PDF: reset form
  else error
    ACT-->>PDF: error
    PDF-->>T: show error / merge-accounts toast
    PDF->>PDF: set field/root errors
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • steven-tey
  • devkiran

Poem

I nibble code like clover sweet,
Enums and forms all hop to meet.
Interests pill, traffic shows,
Save, toast, reset — the rabbit knows.
Hooray — the profile blooms complete! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Partner profile updates” directly reflects the primary scope of the changes, which introduce new profile fields, UI components, API actions, and schema updates to enhance partner profile functionality.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch partner-profile-updates

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1be047 and e3c7e8e.

📒 Files selected for processing (1)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/lib/actions/partners/update-partner-profile.ts (2)

180-180: Error message exposure concern

The error is re-thrown with the raw error message, which might expose sensitive information. Consider sanitizing or using a generic message for unexpected errors.

Apply this fix:

} catch (error) {
  console.error(error);

-  throw new Error(error.message);
+  if (error instanceof Error && error.message.includes("Email")) {
+    throw error; // Re-throw known validation errors
+  }
+  throw new Error("Failed to update profile. Please try again.");
}

224-224: Error message mentions email but email changes don't trigger Stripe deletion

The error message on line 224 mentions "email" but the deleteStripeAccountIfRequired function doesn't check for email changes. This could be confusing to users.

Update the error message to be accurate:

throw new Error(
-  "Since you've already received payouts on Dub, you cannot change your email, country or profile type. Please contact support to update those fields.",
+  "Since you've already received payouts on Dub, you cannot change your country or profile type. Please contact support to update those fields.",
);
🧹 Nitpick comments (11)
apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1)

268-275: Confirm intent: Messages/profile/payouts routes no longer render area nav

Returning null for currentArea on routes starting with /messages, /profile, or /payouts hides the area-specific sidebar (e.g., program nav). This means navigating to a program’s Messages page (/messages/[programSlug]) drops the program area nav and also switches the bottom slot to <PayoutStats /> (since isEnrolledProgramPage is false on /messages/*). If that UX is intended, all good; otherwise we should refine the check to keep currentArea="program" for program-specific message threads.

  • Extract the top-level group paths to a constant to centralize and reduce churn.
  • Nit: programSlug in the useMemo deps is redundant given isEnrolledProgramPage already depends on it.

Apply this diff to use a constant in the check:

-        : ["/payouts", "/profile", "/messages"].some((p) =>
-              pathname.startsWith(p),
-            )
+        : TOP_LEVEL_GROUP_PATHS.some((p) => pathname.startsWith(p))
           ? null
           : isEnrolledProgramPage
             ? "program"
             : "programs";

Add this constant near the top of the file (e.g., after the type SidebarNavData):

const TOP_LEVEL_GROUP_PATHS = ["/payouts", "/profile", "/messages"] as const;

Optionally tighten the deps:

// Current:
// }, [pathname, programSlug, isEnrolledProgramPage]);

// Suggested:
// }, [pathname, isEnrolledProgramPage]);
apps/web/ui/partners/online-presence-form.tsx (2)

233-233: LGTM on variant pass-through

Passing variant to each FormRow keeps the layout consistent across modes.

Minor: to reduce repetition, consider giving FormRow a default variant or deriving it via context so call sites don’t need to pass it every time (see next comment for a safe default-prop tweak).

Also applies to: 268-268, 299-299, 331-331, 363-363, 397-397


539-551: Make variant optional with a default to cut call-site churn

Defaulting variant to "onboarding" is safe and reduces required props. Current usages remain valid.

Apply this diff:

 function FormRow({
   label,
   input,
   property,
   prefix,
   verifiedAtField,
   icon: Icon,
   onVerifyClick,
   verifyDisabledTooltip,
-  variant,
+  variant = "onboarding",
 }: {
   label: string;
   input: ReactNode;
 
   property: keyof OnlinePresenceFormData;
   prefix?: string;
   verifiedAtField: string;
   icon: Icon;
   onVerifyClick: () => Promise<boolean>;
   verifyDisabledTooltip?: string;
-  variant: "onboarding" | "settings";
+  variant?: "onboarding" | "settings";
 }) {
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/industry-interests-modal.tsx (2)

41-43: Sync local selection when the modal opens or props change.

Initialize-once state can drift from props. Reset when opening or when interests change.

-import { Dispatch, SetStateAction, useRef, useState } from "react";
+import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
@@
   const [selectedInterests, setSelectedInterests] =
     useState<IndustryInterest[]>(interests);
+
+  useEffect(() => {
+    if (show) setSelectedInterests(interests);
+  }, [show, interests]);

51-55: Copy nits.

Polish the heading and description.

-          Add industry Interests
+          Add industry interests
@@
-          Add the industries you care and post content about.
+          Add the industries you care about and post content about.
apps/web/lib/api/partners/get-partner-for-program.ts (1)

72-90: Prefer JSON arrays over comma-joined strings for enum lists.

Use JSON_ARRAYAGG (and DISTINCT) in subqueries to avoid string parsing and ensure type-safety. Then drop .split(",") in the mapper.

-    LEFT JOIN (
-      SELECT partnerId, group_concat(industryInterest) AS industryInterests
+    LEFT JOIN (
+      SELECT partnerId, JSON_ARRAYAGG(DISTINCT industryInterest) AS industryInterests
       FROM PartnerIndustryInterest
       WHERE partnerId = ${partnerId}
       GROUP BY partnerId
     ) industryInterests ON industryInterests.partnerId = pe.partnerId
-    LEFT JOIN (
-      SELECT partnerId, group_concat(preferredEarningStructure) AS preferredEarningStructures
+    LEFT JOIN (
+      SELECT partnerId, JSON_ARRAYAGG(DISTINCT preferredEarningStructure) AS preferredEarningStructures
       FROM PartnerPreferredEarningStructure
       WHERE partnerId = ${partnerId}
       GROUP BY partnerId
     ) preferredEarningStructures ON preferredEarningStructures.partnerId = pe.partnerId
-    LEFT JOIN (
-      SELECT partnerId, group_concat(salesChannel) AS salesChannels
+    LEFT JOIN (
+      SELECT partnerId, JSON_ARRAYAGG(DISTINCT salesChannel) AS salesChannels
       FROM PartnerSalesChannel
       WHERE partnerId = ${partnerId}
       GROUP BY partnerId
     ) salesChannels ON salesChannels.partnerId = pe.partnerId
-    industryInterests: partner[0].industryInterests?.split(",") || undefined,
-    preferredEarningStructures:
-      partner[0].preferredEarningStructures?.split(",") || undefined,
-    salesChannels: partner[0].salesChannels?.split(",") || undefined,
+    industryInterests: partner[0].industryInterests || undefined,
+    preferredEarningStructures: partner[0].preferredEarningStructures || undefined,
+    salesChannels: partner[0].salesChannels || undefined,
apps/web/ui/shared/max-characters-counter.tsx (1)

15-21: Small robustness tweak and optional UX improvement.

Guard non-string values and optionally flag over-limit state.

-  const value = useWatch({ control, name });
+  const value = useWatch({ control, name });
+  const length =
+    typeof value === "string" ? value.length : value?.toString().length ?? 0;
@@
-    <span className={cn("text-content-subtle text-xs", className)}>
-      {value?.toString().length || 0}/{maxLength}
+    <span
+      className={cn(
+        "text-xs",
+        length > maxLength ? "text-red-600" : "text-content-subtle",
+        className,
+      )}
+    >
+      {length}/{maxLength}
     </span>
apps/web/ui/partners/partner-about.tsx (1)

92-101: Prefer explicit nullish check for monthlyTraffic.

Guard on defined/undefined rather than truthiness; avoids surprises if enum ever includes falsy-like values.

-      {Boolean(partner.monthlyTraffic) && (
+      {partner.monthlyTraffic != null && (
         <div className="flex flex-col gap-2">
           <h3 className="text-content-emphasis text-xs font-semibold">
             Monthly traffic
           </h3>
           <span className="text-content-default text-xs">
-            {monthlyTrafficAmountsMap[partner.monthlyTraffic!]?.label ?? "-"}
+            {monthlyTrafficAmountsMap[partner.monthlyTraffic]?.label ?? "-"}
           </span>
         </div>
       )}
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx (1)

197-202: Optimize image change detection

The current implementation sends image: null when the image hasn't changed, which could unnecessarily trigger update logic on the backend. Consider not including the image field at all when it hasn't changed.

Apply this optimization:

await executeAsync({
  ...data,
-  image: imageChanged ? data.image : null,
+  ...(imageChanged && { image: data.image }),
});
apps/web/lib/actions/partners/update-partner-profile.ts (2)

192-193: Inconsistent undefined check pattern

The undefined check for country uses a different pattern than profileType (line 196). Also, the code allows setting country to null but then compares with undefined.

For consistency and clarity:

const countryChanged =
-  input.country !== undefined &&
-  partner.country?.toLowerCase() !== input.country?.toLowerCase();
+  input.country != null &&
+  partner.country?.toLowerCase() !== input.country?.toLowerCase();

71-71: Consider stricter email validation

The condition newEmail !== undefined means an empty string would pass through. Consider also checking for non-empty strings.

-const emailChanged = newEmail !== undefined && partner.email !== newEmail;
+const emailChanged = newEmail && newEmail !== partner.email;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcd7d99 and 56d1ef5.

📒 Files selected for processing (19)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/industry-interests-modal.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/settings-row.tsx (1 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/sites/page-client.tsx (0 hunks)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/sites/page.tsx (0 hunks)
  • apps/web/lib/actions/partners/update-partner-profile.ts (6 hunks)
  • apps/web/lib/api/partners/get-partner-for-program.ts (3 hunks)
  • apps/web/lib/auth/partner.ts (2 hunks)
  • apps/web/lib/partners/partner-profile.ts (1 hunks)
  • apps/web/lib/zod/schemas/partners.ts (5 hunks)
  • apps/web/ui/layout/sidebar/partners-sidebar-nav.tsx (1 hunks)
  • apps/web/ui/partners/online-presence-form.tsx (11 hunks)
  • apps/web/ui/partners/partner-about.tsx (2 hunks)
  • apps/web/ui/shared/max-characters-counter.tsx (1 hunks)
  • packages/prisma/client.ts (1 hunks)
  • packages/prisma/schema/partner.prisma (4 hunks)
💤 Files with no reviewable changes (2)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/sites/page-client.tsx
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/sites/page.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
PR: dubinc/dub#2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/lib/partners/partner-profile.ts
  • apps/web/ui/partners/partner-about.tsx
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
PR: dubinc/dub#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-partner-profile.ts
🧬 Code graph analysis (11)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/settings-row.tsx (3)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/[groupSlug]/links/group-link-settings.tsx (1)
  • SettingsRow (273-293)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/program-settings-row.tsx (1)
  • SettingsRow (3-26)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/groups/[groupSlug]/links/group-additional-links.tsx (1)
  • SettingsRow (215-235)
apps/web/lib/auth/partner.ts (2)
apps/web/lib/partners/partner-profile.ts (3)
  • industryInterests (34-159)
  • preferredEarningStructures (196-220)
  • salesChannels (228-257)
apps/web/lib/types.ts (1)
  • PartnerProps (428-428)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx (7)
packages/prisma/client.ts (1)
  • PartnerProfileType (19-19)
apps/web/lib/types.ts (2)
  • PartnerProps (428-428)
  • PayoutsCount (458-462)
apps/web/ui/partners/online-presence-form.tsx (2)
  • useOnlinePresenceForm (70-83)
  • OnlinePresenceForm (89-414)
apps/web/ui/modals/confirm-modal.tsx (1)
  • useConfirmModal (105-118)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/settings-row.tsx (1)
  • SettingsRow (3-23)
apps/web/lib/actions/partners/update-partner-profile.ts (1)
  • updatePartnerProfileAction (42-182)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/lib/partners/partner-profile.ts (4)
packages/ui/src/icons/index.tsx (1)
  • Icon (79-79)
packages/ui/src/icons/nucleo/life-ring.tsx (1)
  • LifeRing (3-112)
packages/ui/src/icons/nucleo/trophy.tsx (1)
  • Trophy (3-48)
packages/ui/src/icons/nucleo/heart.tsx (1)
  • Heart (3-24)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx (4)
apps/web/lib/types.ts (1)
  • PartnerProps (428-428)
apps/web/lib/actions/partners/update-partner-profile.ts (1)
  • updatePartnerProfileAction (42-182)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/settings-row.tsx (1)
  • SettingsRow (3-23)
apps/web/lib/partners/partner-profile.ts (2)
  • preferredEarningStructures (196-220)
  • salesChannels (228-257)
apps/web/lib/zod/schemas/partners.ts (1)
packages/prisma/client.ts (4)
  • MonthlyTraffic (15-15)
  • IndustryInterest (13-13)
  • PreferredEarningStructure (23-23)
  • SalesChannel (28-28)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/industry-interests-modal.tsx (3)
packages/ui/src/hooks/use-scroll-progress.ts (1)
  • useScrollProgress (6-37)
apps/web/lib/zod/schemas/partners.ts (1)
  • MAX_PARTNER_INDUSTRY_INTERESTS (243-243)
apps/web/lib/partners/partner-profile.ts (1)
  • industryInterests (34-159)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (4)
apps/web/ui/layout/page-content/index.tsx (1)
  • PageContent (11-100)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx (1)
  • ProfileDetailsForm (37-116)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx (1)
  • AboutYouForm (26-202)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx (1)
  • HowYouWorkForm (22-203)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx (7)
apps/web/lib/types.ts (1)
  • PartnerProps (428-428)
apps/web/lib/actions/partners/update-partner-profile.ts (1)
  • updatePartnerProfileAction (42-182)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/settings-row.tsx (1)
  • SettingsRow (3-23)
apps/web/lib/zod/schemas/partners.ts (1)
  • MAX_PARTNER_DESCRIPTION_LENGTH (262-262)
apps/web/ui/shared/max-characters-counter.tsx (1)
  • MaxCharactersCounter (4-22)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/industry-interests-modal.tsx (1)
  • IndustryInterestsModal (15-25)
apps/web/lib/partners/partner-profile.ts (2)
  • industryInterests (34-159)
  • monthlyTrafficAmounts (167-188)
apps/web/ui/partners/partner-about.tsx (2)
apps/web/lib/partners/partner-profile.ts (4)
  • industryInterestsMap (161-165)
  • salesChannelsMap (259-261)
  • preferredEarningStructuresMap (222-226)
  • monthlyTrafficAmountsMap (190-194)
packages/ui/src/icons/index.tsx (1)
  • Icon (79-79)
apps/web/lib/actions/partners/update-partner-profile.ts (2)
apps/web/lib/zod/schemas/misc.ts (1)
  • uploadedImageSchema (117-129)
apps/web/lib/zod/schemas/partners.ts (2)
  • MAX_PARTNER_DESCRIPTION_LENGTH (262-262)
  • PartnerProfileSchema (245-260)
⏰ 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 (12)
apps/web/ui/partners/online-presence-form.tsx (1)

582-587: A11y check: visually hidden labels in settings

Using sr-only to hide labels in settings keeps an accessible name for inputs (wrapped by label). In the verified state, there’s no input, so this is mostly informational.

Confirm that OnlinePresenceCard exposes an accessible name/description for its interactive affordances (e.g., the remove button). If needed, consider wiring the label via aria-labelledby on the card and adding an id to the span.

Also applies to: 601-606

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

262-262: Confirm description limit consistency (500 vs 5000).

MAX_PARTNER_DESCRIPTION_LENGTH is 500, but PartnerSchema.description still allows 5000 “to avoid breaking changes.” Verify consumers (forms, server action) consistently enforce the intended limit to avoid UX/API divergence.

Also applies to: 286-289

packages/prisma/client.ts (1)

13-29: LGTM: new enum exports are correctly surfaced.

apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/settings-row.tsx (1)

3-23: LGTM: simple, reusable layout wrapper.

Matches existing SettingsRow patterns and improves reuse within the profile pages.

apps/web/lib/auth/partner.ts (1)

60-67: Include relations and flattening look correct.

The include shape and mapping to enum arrays aligns with Prisma models and PartnerProps.

apps/web/ui/partners/partner-about.tsx (1)

47-90: Nice addition of mapped profile facets.

Clean pill UI with safe map lookups and graceful fallbacks. Reads well and is resilient to unknown keys.

apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1)

16-31: LGTM: Good decomposition into focused sub-forms.

Simpler data flow and clearer responsibilities; controls preserved.

apps/web/lib/partners/partner-profile.ts (1)

161-165: LGTM: Maps built from arrays are clean and type-aligned.

Good use of Object.fromEntries with enum-keyed arrays; optional Partial keeps lookups safe.

Also applies to: 190-195, 222-226, 259-261

apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx (1)

192-198: Make submit intent explicit.

Same rationale as other form: set type="submit" on the primary action.

         <div className="flex items-center justify-end rounded-b-lg border-t border-neutral-200 bg-neutral-50 px-6 py-4">
           <Button
             text="Save changes"
             className="h-8 w-fit px-2.5"
             loading={isSubmitting}
+            type="submit"
           />
         </div>

Optionally verify Button’s default type:

apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/how-you-work-form.tsx (1)

193-199: No change needed — Button already becomes submit when used in a form.

The Button sets type={props.onClick ? "button" : "submit"} (packages/ui/src/button.tsx); this usage doesn't pass onClick, so it will be type="submit".

apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx (2)

100-105: Consider validating companyName consistency when checking dirty fields

When checking basicInfoForm.formState.dirtyFields.companyName on line 104, this doesn't account for the scenario where a user switches profileType from "company" to "individual". The companyName field would become null (per line 38 in update-partner-profile.ts), but dirtyFields.companyName might not be set if the field was simply hidden/removed from the form.

Consider also checking if the profile type changed from "company" to verify if company-related fields need validation.


158-161: Ensure consistent payout status filtering

The payout status filtering includes both "sent" and "completed". Verify this matches the backend logic to avoid discrepancies in determining when fields should be disabled.

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 (4)
packages/ui/src/icons/nucleo/cloud.tsx (1)

12-28: Deduplicate SVG attributes; remove redundant fill and push shared stroke to the group

Both paths repeat identical stroke and fill props, while the parent group sets an unused fill. Centralize shared attributes on the and simplify the paths.

Apply this diff:

-      <g fill="currentColor">
-        <path
-          d="M13.464,6.891c-.186-2.314-2.102-4.141-4.464-4.141-2.485,0-4.5,2.015-4.5,4.5,0,.35,.049,.686,.124,1.013-1.597,.067-2.874,1.374-2.874,2.987,0,1.657,1.343,3,3,3h7.75c2.071,0,3.75-1.679,3.75-3.75,0-1.736-1.185-3.182-2.786-3.609Z"
-          fill="none"
-          stroke="currentColor"
-          strokeLinecap="round"
-          strokeLinejoin="round"
-          strokeWidth="1.5"
-        />
-        <path
-          d="M9.705,8c.687-.767,1.684-1.25,2.795-1.25,.333,0,.657,.059,.964,.141"
-          fill="none"
-          stroke="currentColor"
-          strokeLinecap="round"
-          strokeLinejoin="round"
-          strokeWidth="1.5"
-        />
+      <g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5">
+        <path d="M13.464,6.891c-.186-2.314-2.102-4.141-4.464-4.141-2.485,0-4.5,2.015-4.5,4.5,0,.35,.049,.686,.124,1.013-1.597,.067-2.874,1.374-2.874,2.987,0,1.657,1.343,3,3,3h7.75c2.071,0,3.75-1.679,3.75-3.75,0-1.736-1.185-3.182-2.786-3.609Z" />
+        <path d="M9.705,8c.687-.767,1.684-1.25,2.795-1.25,.333,0,.657,.059,.964,.141" />
       </g>
apps/web/lib/actions/partners/update-partner-profile.ts (3)

97-122: Avoid shadowing outer name and improve readability in map callbacks

The callback param name shadows the outer name field. Rename to value (or similar) in each map to reduce confusion.

Apply this diff:

-          ...(industryInterests && {
+          ...(industryInterests && {
             industryInterests: {
               deleteMany: {},
-              create: industryInterests.map((name) => ({
-                industryInterest: name,
+              create: industryInterests.map((value) => ({
+                industryInterest: value,
               })),
             },
           }),
 
-          ...(preferredEarningStructures && {
+          ...(preferredEarningStructures && {
             preferredEarningStructures: {
               deleteMany: {},
-              create: preferredEarningStructures.map((name) => ({
-                preferredEarningStructure: name,
+              create: preferredEarningStructures.map((value) => ({
+                preferredEarningStructure: value,
               })),
             },
           }),
 
-          ...(salesChannels && {
+          ...(salesChannels && {
             salesChannels: {
               deleteMany: {},
-              create: salesChannels.map((name) => ({
-                salesChannel: name,
+              create: salesChannels.map((value) => ({
+                salesChannel: value,
               })),
             },
           }),

181-184: Harden error handling: error is unknown

Accessing error.message directly can throw if error isn’t an Error. Guard it to avoid masking the real failure.

Apply this diff:

-    } catch (error) {
-      console.error(error);
-
-      throw new Error(error.message);
-    }
+    } catch (error) {
+      console.error(error);
+      const message =
+        error instanceof Error ? error.message : "Failed to update partner profile";
+      throw new Error(message);
+    }

225-228: Align error messaging with actual constraints (include company name)

You also gate on companyName changes, but the message mentions only country/profile type. Include company name to reduce confusion.

-      "Since you've already received payouts on Dub, you cannot change your country or profile type. Please contact support to update those fields.",
+      "Since you've already received payouts on Dub, you cannot change your country, profile type, or company name. Please contact support to update those fields.",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 177b929 and e1be047.

📒 Files selected for processing (7)
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx (1 hunks)
  • apps/web/lib/actions/partners/update-partner-profile.ts (7 hunks)
  • apps/web/lib/partners/partner-profile.ts (1 hunks)
  • apps/web/lib/zod/schemas/partners.ts (5 hunks)
  • packages/prisma/schema/partner.prisma (4 hunks)
  • packages/ui/src/icons/nucleo/cloud.tsx (1 hunks)
  • packages/ui/src/icons/nucleo/index.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/prisma/schema/partner.prisma
  • apps/web/lib/partners/partner-profile.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
PR: dubinc/dub#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-partner-profile.ts
📚 Learning: 2025-07-30T15:25:13.936Z
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx:56-66
Timestamp: 2025-07-30T15:25:13.936Z
Learning: In apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx, the form schema uses partial condition objects to allow users to add empty/unconfigured condition fields without type errors, while submission validation uses strict schemas to ensure data integrity. This two-stage validation pattern improves UX by allowing progressive completion of complex forms.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: 2025-09-24T15:57:55.259Z
Learnt from: TWilson023
PR: dubinc/dub#2872
File: apps/web/lib/actions/partners/update-partner-profile.ts:97-104
Timestamp: 2025-09-24T15:57:55.259Z
Learning: In the Dub codebase, the team prefers to maintain consistency with existing patterns for database operations, even if there are theoretical improvements available. The deleteMany: {} pattern for relation updates is an accepted approach that the team considers low-risk.

Applied to files:

  • apps/web/lib/actions/partners/update-partner-profile.ts
📚 Learning: 2025-09-24T15:51:28.818Z
Learnt from: TWilson023
PR: dubinc/dub#2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:218-219
Timestamp: 2025-09-24T15:51:28.818Z
Learning: In the Dub codebase, OG_AVATAR_URL concatenation with partner names follows a consistent pattern throughout the application where names are used directly without URL encoding. This is an established codebase-wide pattern.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx
📚 Learning: 2025-07-30T15:29:54.131Z
Learnt from: TWilson023
PR: dubinc/dub#2673
File: apps/web/ui/partners/rewards/rewards-logic.tsx:268-275
Timestamp: 2025-07-30T15:29:54.131Z
Learning: In apps/web/ui/partners/rewards/rewards-logic.tsx, when setting the entity field in a reward condition, dependent fields (attribute, operator, value) should be reset rather than preserved because different entities (customer vs sale) have different available attributes. Maintaining existing fields when the entity changes would create invalid state combinations and confusing UX.

Applied to files:

  • apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx
📚 Learning: 2025-09-24T15:50:16.387Z
Learnt from: TWilson023
PR: dubinc/dub#2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.387Z
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/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx
🧬 Code graph analysis (3)
apps/web/lib/actions/partners/update-partner-profile.ts (2)
apps/web/lib/zod/schemas/misc.ts (1)
  • uploadedImageSchema (117-129)
apps/web/lib/zod/schemas/partners.ts (2)
  • MAX_PARTNER_DESCRIPTION_LENGTH (271-271)
  • PartnerProfileSchema (245-269)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx (8)
packages/prisma/client.ts (1)
  • PartnerProfileType (19-19)
apps/web/lib/types.ts (2)
  • PartnerProps (428-428)
  • PayoutsCount (458-462)
apps/web/ui/partners/online-presence-form.tsx (2)
  • useOnlinePresenceForm (70-83)
  • OnlinePresenceForm (89-414)
apps/web/ui/modals/confirm-modal.tsx (1)
  • useConfirmModal (105-118)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/settings-row.tsx (1)
  • SettingsRow (3-23)
apps/web/lib/actions/partners/update-partner-profile.ts (1)
  • updatePartnerProfileAction (42-185)
packages/utils/src/constants/misc.ts (1)
  • OG_AVATAR_URL (29-29)
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/page-client.tsx (1)
  • ProfileForm (183-463)
apps/web/lib/zod/schemas/partners.ts (1)
packages/prisma/client.ts (4)
  • MonthlyTraffic (15-15)
  • IndustryInterest (13-13)
  • PreferredEarningStructure (23-23)
  • SalesChannel (28-28)
⏰ 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)
packages/ui/src/icons/nucleo/cloud.tsx (1)

5-11: Good prop override order

Placing {...props} after explicit width/height lets consumers override defaults cleanly.

packages/ui/src/icons/nucleo/index.ts (1)

2-2: Re-exports look good and keep alphabetical order

Apple and Cloud are added in the expected spots without naming conflicts.

Also applies to: 61-61

apps/web/lib/zod/schemas/partners.ts (2)

243-269: Good: enum array validation + de-dup guards added

The uniqueness refinements on industryInterests, preferredEarningStructures, and salesChannels will prevent Prisma join unique violations and align inputs with DB constraints.


271-299: Description length mismatch: updates capped at 500 but read schema allows 5000

MAX_PARTNER_DESCRIPTION_LENGTH is 500 here, and update actions enforce it, while PartnerSchema still permits 5000 to avoid breaking reads. This can reject updates for existing partners whose description > 500 if the client sends the field unchanged.

To avoid accidental failures:

  • Ensure clients only send description when changed (undefined otherwise), or
  • Backfill/truncate existing descriptions > 500, or
  • Temporarily accept >500 server-side unless changed.

Would you like a follow-up migration plan to normalize existing descriptions > 500?

@steven-tey steven-tey merged commit dc3a6f8 into main Sep 24, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the partner-profile-updates branch September 24, 2025 20:35
This was referenced Oct 5, 2025
@coderabbitai coderabbitai bot mentioned this pull request Nov 10, 2025
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.

3 participants