-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Partner profile updates #2872
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
Partner profile updates #2872
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
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: 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 concernThe 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 deletionThe error message on line 224 mentions "email" but the
deleteStripeAccountIfRequiredfunction 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 navReturning
nullforcurrentAreaon routes starting with/messages,/profile, or/payoutshides 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 />(sinceisEnrolledProgramPageis false on/messages/*). If that UX is intended, all good; otherwise we should refine the check to keepcurrentArea="program"for program-specific message threads.
- Extract the top-level group paths to a constant to centralize and reduce churn.
- Nit:
programSlugin theuseMemodeps is redundant givenisEnrolledProgramPagealready 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-throughPassing 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 churnDefaulting 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 detectionThe current implementation sends
image: nullwhen 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 patternThe undefined check for
countryuses a different pattern thanprofileType(line 196). Also, the code allows setting country tonullbut then compares withundefined.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 validationThe condition
newEmail !== undefinedmeans 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
📒 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.tsapps/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 settingsUsing 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 validatingcompanyNameconsistency when checking dirty fieldsWhen checking
basicInfoForm.formState.dirtyFields.companyNameon line 104, this doesn't account for the scenario where a user switchesprofileTypefrom "company" to "individual". ThecompanyNamefield would becomenull(per line 38 in update-partner-profile.ts), butdirtyFields.companyNamemight 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 filteringThe payout status filtering includes both "sent" and "completed". Verify this matches the backend logic to avoid discrepancies in determining when fields should be disabled.
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/about-you-form.tsx
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/industry-interests-modal.tsx
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx
Show resolved
Hide resolved
apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.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
🧹 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 groupBoth 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 outernameand improve readability in map callbacksThe callback param
nameshadows the outernamefield. Rename tovalue(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:errorisunknownAccessing
error.messagedirectly can throw iferrorisn’t anError. 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
📒 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 orderPlacing {...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 orderApple 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 addedThe 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 5000MAX_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?
Summary by CodeRabbit