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

Skip to content

Conversation

@TWilson023
Copy link
Collaborator

@TWilson023 TWilson023 commented Sep 19, 2025

  • Fall back to default group in UI when groupId is null
  • For rejected applications, don't update group immediately when changed via the Change Group modal. Wait until approval to persist.
  • (Bonus) Disable deleting other users' partner comments

Summary by CodeRabbit

  • New Features

    • Set or adjust a partner’s group during approval, including for previously rejected applications.
    • Preview/change a partner’s group before persisting changes; change can be intercepted.
  • UX Improvements

    • Change Group button enables only after a group is selected; auto-selects when only one option exists.
    • Approval section shown for both pending and rejected statuses.
    • Reject action hidden when an application is already rejected.
    • “No rewards” message shown when a group has no rewards.
  • Bug Fixes

    • Only comment authors see and can edit or delete their comments.

@vercel
Copy link
Contributor

vercel bot commented Sep 19, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Sep 19, 2025 9:11pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Comment ownership enforcement
apps/web/lib/actions/partners/delete-partner-comment.ts, apps/web/ui/partners/partner-comments.tsx
Server delete now filters by userId from ctx; comment action popover renders only for the comment author; Edit/Delete remain available to owners.
Controlled group selection & modal interception
apps/web/ui/partners/change-group-modal.tsx, apps/web/ui/partners/partner-info-group.tsx, apps/web/ui/partners/partner-info-cards.tsx
Adds optional onChangeGroup callback to intercept/potentially block persistence; components accept selectedGroupId and setSelectedGroupId to support external control; modal and group resolution use controlled value with DEFAULT_PARTNER_GROUP fallback.
Partner application adjustments
apps/web/ui/partners/partner-application-sheet.tsx
Adds selectedGroupId state/useEffect for rejected partners; renders PartnerApproval for pending and rejected; includes groupId in approve payload; hides Reject button when already rejected.
SWR hook extensibility
apps/web/lib/swr/use-group.ts
useGroup gains optional swrOpts?: SWRConfiguration parameter and spreads it into useSWR options; fetch and behavior otherwise unchanged.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • steven-tey

Poem

Hop, hop—groups pick up and dance,
Only authors get the comment chance.
SWR lends options, quiet and spry,
Approvals carry group IDs by.
I nibble code and wiggle my ear—changes landed, carrots near! 🥕🐇

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 "Application group updates" concisely and accurately captures the PR's primary scope—changes to group handling in application flows (default-group fallback and deferred persistence of group changes for rejected applications) while being short and clear; it does not call out the minor comment-deletion tweak but remains a correct summary of the main change.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch application-group-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 fc423dc and 6ddd6b1.

📒 Files selected for processing (1)
  • apps/web/ui/partners/partner-application-sheet.tsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/ui/partners/partner-application-sheet.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: 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 PartnerComment doesn’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 workspaceId capture in the useCallback.

-  }, [changeGroup, selectedGroupId, partners]);
+  }, [changeGroup, selectedGroupId, partners, workspaceId]);

149-153: Minor: stale closure risk for onChangeGroup in hook.

Add onChangeGroup to the useCallback deps.

-  }, [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 accept null to 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 be undefined. If not guaranteed, we should render a non-linked “Default Group” label when fallback is missing.


37-44: Potential stale onChangeGroup in useChangeGroupModal.
The hook memoizes a callback that captures onChangeGroup, 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.
Match PartnerInfoGroup by widening the setter type to accept null.

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: false is 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) and PartnerInfoGroup (useGroups) fetch group data. Consider passing group down to PartnerInfoGroup (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.
If discount can be 0 (or another falsey but valid value), the current truthiness check will render “No rewards”. Compare against null/undefined instead.

Apply this diff:

-            group.clickReward ||
-            group.leadReward ||
-            group.saleReward ||
-            group.discount ? (
+            group.clickReward ||
+            group.leadReward ||
+            group.saleReward ||
+            (group.discount != null) ? (

If discount is 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

📥 Commits

Reviewing files that changed from the base of the PR and between b4b77a1 and fc423dc.

📒 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 user from ctx and scoping the delete by userId is 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 swrOpts is 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 default

approvePartnerAction 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.

onChangeGroup passed 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.
If selectedGroupId is provided without setSelectedGroupId, 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 both selectedGroupId and setSelectedGroupId are provided together when you want non‑persisting, controlled behavior. Otherwise, the modal will persist changes.

@steven-tey steven-tey merged commit 5e9cbd2 into main Sep 19, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the application-group-updates branch September 19, 2025 21:16
This was referenced Sep 23, 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