-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Application group updates #2862
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.
|
WalkthroughAdds user-scoped deletion for partner comments, restricts comment action UI to comment authors, introduces controlled group selection and an onChangeGroup interception across partner components, extends useGroup to accept SWR options, and adjusts approval/rejection flows to include groupId and conditional UI. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant UI as ChangeGroupModal
participant H as Host/Hook (optional)
participant API as Server
U->>UI: Select group and click "Change Group"
UI->>H: call onChangeGroup(selectedGroupId)?
alt onChangeGroup provided
H-->>UI: return false (intercept)
UI-->>U: close modal (no API call)
else not provided
UI->>API: mutate -> update partner group
API-->>UI: success/failure
UI-->>U: reflect result and close
end
sequenceDiagram
autonumber
participant U as User (author)
participant C as Comments UI
participant S as delete-partner-comment
participant DB as Prisma
U->>C: Click delete on my comment
C->>S: request delete(commentId) with ctx.user
S->>DB: delete where id=commentId AND userId=ctx.user.id
alt record deleted
DB-->>S: deleted
S-->>C: success
C-->>U: remove comment from UI
else not found / no ownership
DB-->>S: no rows
S-->>C: error
C-->>U: show error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 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: 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/lib/actions/partners/delete-partner-comment.ts (1)
17-23: Prisma delete likely needs a unique filter; use deleteMany + count check unless you have a composite unique.If
PartnerCommentdoesn’t declare a composite unique on(id, programId, userId),delete({ where: { id, programId, userId } })won’t type‑check/run. Safer pattern:- await prisma.partnerComment.delete({ - where: { - id: commentId, - programId, - userId: user.id, - }, - }); + const { count } = await prisma.partnerComment.deleteMany({ + where: { id: commentId, programId, userId: user.id }, + }); + if (count === 0) { + throw new Error("Comment not found or not authorized to delete"); + }
🧹 Nitpick comments (9)
apps/web/ui/partners/change-group-modal.tsx (2)
131-139: Minor: include workspaceId in handleChangeGroup deps.Avoid stale
workspaceIdcapture in theuseCallback.- }, [changeGroup, selectedGroupId, partners]); + }, [changeGroup, selectedGroupId, partners, workspaceId]);
149-153: Minor: stale closure risk for onChangeGroup in hook.Add
onChangeGroupto theuseCallbackdeps.- }, [showChangeGroupModal, setShowChangeGroupModal, partners]); + }, [showChangeGroupModal, setShowChangeGroupModal, partners, onChangeGroup]);Also applies to: 161-164
apps/web/ui/partners/partner-info-group.tsx (3)
23-25: Allow clearing selection to “default” explicitly.
Consider letting the setter acceptnullto reset to the default group.Apply this diff:
- setSelectedGroupId?: (groupId: string) => void; + setSelectedGroupId?: (groupId: string | null) => void;
31-34: Prefer nullish coalescing to avoid empty‑string pitfalls; keep fallback robust.
||treats""as falsy. If an empty string ever flows in, we’ll erroneously fall back. Use??.Apply this diff:
- const group = groups - ? groups.find((g) => g.id === (selectedGroupId || partner.groupId)) || - groups.find((g) => g.slug === DEFAULT_PARTNER_GROUP.slug) - : undefined; + const group = groups + ? groups.find((g) => g.id === (selectedGroupId ?? partner.groupId)) ?? + groups.find((g) => g.slug === DEFAULT_PARTNER_GROUP.slug) + : undefined;Also, please confirm every workspace always has a
"default"group; otherwise this can still beundefined. If not guaranteed, we should render a non-linked “Default Group” label when fallback is missing.
37-44: Potential staleonChangeGroupinuseChangeGroupModal.
The hook memoizes a callback that capturesonChangeGroup, but its dependency list (in the hook file) omits it. Passing a new handler here might be ignored by the modal until the next dependency change.Apply this diff in
apps/web/ui/partners/change-group-modal.tsx:- const ChangeGroupModalCallback = useCallback(() => { + const ChangeGroupModalCallback = useCallback(() => { return ( <ChangeGroupModal showChangeGroupModal={showChangeGroupModal} setShowChangeGroupModal={setShowChangeGroupModal} partners={partners} onChangeGroup={onChangeGroup} /> ); - }, [showChangeGroupModal, setShowChangeGroupModal, partners]); + }, [showChangeGroupModal, setShowChangeGroupModal, partners, onChangeGroup]); - return useMemo( + return useMemo( () => ({ setShowChangeGroupModal, ChangeGroupModal: ChangeGroupModalCallback, }), - [setShowChangeGroupModal, ChangeGroupModalCallback], + [setShowChangeGroupModal, ChangeGroupModalCallback], );apps/web/ui/partners/partner-info-cards.tsx (4)
42-45: Symmetry with child component: allow clearing to default.
MatchPartnerInfoGroupby widening the setter type to acceptnull.Apply this diff:
- setSelectedGroupId?: (groupId: string) => void; + setSelectedGroupId?: (groupId: string | null) => void;
61-68: Use nullish coalescing and verify SWR option override intent.
- Prefer
??so empty strings don’t trigger fallback.- Double‑check that
keepPreviousData: falseis intentional (it will increase UI skeleton flicker on selection change).Apply this diff:
- const { group } = useGroup( - { - groupIdOrSlug: partner - ? selectedGroupId || partner.groupId || DEFAULT_PARTNER_GROUP.slug - : undefined, - }, - { keepPreviousData: false }, - ); + const { group } = useGroup( + { + groupIdOrSlug: partner + ? selectedGroupId ?? partner.groupId ?? DEFAULT_PARTNER_GROUP.slug + : undefined, + }, + { keepPreviousData: false }, + );
192-194: Avoid duplicate fetches (optional).
Both this component (useGroup) andPartnerInfoGroup(useGroups) fetch group data. Consider passinggroupdown toPartnerInfoGroup(and letting it skip its SWR call when provided) to cut one network request and re-render path.
206-209: Reward presence check: handle zero/falsey discount.
Ifdiscountcan be0(or another falsey but valid value), the current truthiness check will render “No rewards”. Compare againstnull/undefinedinstead.Apply this diff:
- group.clickReward || - group.leadReward || - group.saleReward || - group.discount ? ( + group.clickReward || + group.leadReward || + group.saleReward || + (group.discount != null) ? (If
discountis an object, keep this; if it’s a number/percentage, you’re covered too.Also applies to: 223-225
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/lib/actions/partners/delete-partner-comment.ts(2 hunks)apps/web/lib/swr/use-group.ts(2 hunks)apps/web/ui/partners/change-group-modal.tsx(4 hunks)apps/web/ui/partners/partner-application-sheet.tsx(6 hunks)apps/web/ui/partners/partner-comments.tsx(1 hunks)apps/web/ui/partners/partner-info-cards.tsx(6 hunks)apps/web/ui/partners/partner-info-group.tsx(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-26T15:38:48.173Z
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/lib/api/bounties/get-bounty-or-throw.ts:53-63
Timestamp: 2025-08-26T15:38:48.173Z
Learning: In bounty-related code, getBountyOrThrow returns group objects with { id } field (transformed from BountyGroup.groupId), while other routes working directly with BountyGroup Prisma records use the actual groupId field. This is intentional - getBountyOrThrow abstracts the join table details.
Applied to files:
apps/web/ui/partners/partner-info-cards.tsx
🧬 Code graph analysis (5)
apps/web/ui/partners/partner-comments.tsx (1)
packages/ui/src/popover.tsx (1)
Popover(25-102)
apps/web/ui/partners/partner-application-sheet.tsx (2)
apps/web/ui/partners/partner-info-cards.tsx (1)
PartnerInfoCards(34-274)apps/web/lib/types.ts (1)
EnrolledPartnerProps(434-434)
apps/web/ui/partners/partner-info-cards.tsx (2)
apps/web/lib/swr/use-group.ts (1)
useGroup(7-37)apps/web/lib/zod/schemas/groups.ts (1)
DEFAULT_PARTNER_GROUP(12-16)
apps/web/ui/partners/partner-info-group.tsx (4)
apps/web/lib/types.ts (1)
EnrolledPartnerProps(434-434)apps/web/lib/swr/use-groups.ts (1)
useGroups(10-38)apps/web/lib/zod/schemas/groups.ts (1)
DEFAULT_PARTNER_GROUP(12-16)apps/web/ui/partners/change-group-modal.tsx (1)
useChangeGroupModal(149-173)
apps/web/ui/partners/change-group-modal.tsx (1)
apps/web/lib/types.ts (1)
EnrolledPartnerProps(434-434)
⏰ 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 (18)
apps/web/lib/actions/partners/delete-partner-comment.ts (2)
12-12: Good: owner-scoped delete is enforced server-side.Pulling
userfromctxand scoping the delete byuserIdis correct defense-in-depth with the UI guard.Also applies to: 21-22
17-23: Verify schema uniqueness or switch to deleteMany
- Prisma's delete requires a unique identifier (@id or @@unique). The call in apps/web/lib/actions/partners/delete-partner-comment.ts (lines 17–23) uses { id: commentId, programId, userId } — confirm the PartnerComment model in packages/prisma/schema/schema.prisma declares a matching composite unique (@@unique([id, programId, userId]) or composite @id).
- If no such constraint exists, either use prisma.partnerComment.deleteMany({ where: { id: commentId, programId, userId } }) or delete by id only if id is the primary key.
apps/web/ui/partners/partner-comments.tsx (2)
224-235: Edit button placement looks good.Always showing “Edit” inside the owner-only popover is consistent with the new guard.
220-283: Approve — ownership enforced on server.Both updatePartnerCommentAction and deletePartnerCommentAction include userId in the Prisma where clause, so only the comment owner can update/delete (apps/web/lib/actions/partners/update-partner-comment.ts, delete-partner-comment.ts).
apps/web/lib/swr/use-group.ts (2)
7-10: Non-breaking hook signature extension: looks good.Optional
swrOptsis a clean way to allow per-call overrides.
25-28: Config merge order is correct.Defaults first, caller overrides via spread — as intended.
apps/web/ui/partners/partner-application-sheet.tsx (5)
99-105: Controlled group selection only for rejected: good.Props are conditionally passed as intended to avoid persisting changes pre‑approval.
251-259: PartnerApproval now accepts optional groupId: good.Keeps the approval payload source-of-truth centralized.
299-307: Hide Reject for already rejected: good UX.Prevents redundant actions.
285-289: Approve — server validates groupId and uses program default when omitted.approvePartnerAction forwards groupId; approvePartnerEnrollment falls back to program.defaultGroupId when groupId is null (throws if neither) and getGroupOrThrow enforces the group belongs to the program. Files: apps/web/lib/actions/partners/approve-partner.ts, apps/web/lib/partners/approve-partner-enrollment.ts, apps/web/lib/api/groups/get-group-or-throw.ts.
126-133: Approve — backend honors provided groupId and falls back to program defaultapprovePartnerAction forwards parsed groupId to approvePartnerEnrollment; approvePartnerEnrollment uses groupId || program.defaultGroupId and only throws if neither exists, so the UI passing selectedGroupId/partner.groupId is correct (apps/web/lib/actions/partners/approve-partner.ts, apps/web/lib/partners/approve-partner-enrollment.ts).
apps/web/ui/partners/change-group-modal.tsx (3)
18-26: onChangeGroup interception is a solid extensibility point.Allows non-persistent selection flows where needed.
31-33: Prop plumbed correctly.
onChangeGrouppassed through to the modal.
37-41: Nice: sensible default when single partner.Preselecting the current group reduces clicks.
apps/web/ui/partners/partner-info-group.tsx (2)
4-4: Import aligns with fallback behavior — looks good.
Matches the PR goal to fallback to the default group.
15-17: Controlled vs. uncontrolled usage — clarify contract.
IfselectedGroupIdis provided withoutsetSelectedGroupId, the Change Group action will persist to the server (uncontrolled). Please confirm this is intended and document it in the prop JSDoc.apps/web/ui/partners/partner-info-cards.tsx (2)
11-11: Import for default group — good alignment with fallback logic.
36-39: Controlled props added — confirm intended usage.
Ensure bothselectedGroupIdandsetSelectedGroupIdare provided together when you want non‑persisting, controlled behavior. Otherwise, the modal will persist changes.
groupIdisnullSummary by CodeRabbit
New Features
UX Improvements
Bug Fixes