Skip to content

feat: add frontend hooks for submission mutations - #117

Merged
0xdevcollins merged 6 commits into
boundlessfi:mainfrom
Shadow-MMN:feature/submission-hooks
Feb 25, 2026
Merged

0xdevcollins merged 6 commits into
boundlessfi:mainfrom
Shadow-MMN:feature/submission-hooks

Conversation

@Shadow-MMN

@Shadow-MMN Shadow-MMN commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

Changes

  • Added hooks: useSubmitToBounty, useReviewSubmission, useMarkSubmissionPaid
  • Hooks invalidate bounty detail cache on success
  • Submission query key factory added to lib/query/query-keys.ts
  • Integrated hooks into bounty detail page's submission section

Acceptance Criteria

  • Users can submit PRs via GraphQL
  • Org members can review and mark submissions as paid
  • Bounty detail cache refreshes after actions

Closes #104

Summary by CodeRabbit

  • New Features
    • Submission management added to bounty detail pages with a dedicated submissions card.
    • Submit PR links with optional comments via an in-page dialog (URL validation and loading states).
    • View submissions list with status badges, reviewer details, and payment status.
    • Organization members can review submissions, change status, and mark approved items as paid (enter transaction reference).
    • Empty-state messaging shown when no submissions exist for open bounties.

- Create useSubmitToBounty, useReviewSubmission, useMarkSubmissionPaid hooks
- Hooks call corresponding backend mutations
- Invalidate bounty detail query on success
- Add submission query key factory in lib/query/query-keys.ts
- Wire hooks into bounty detail page UI
@vercel

vercel Bot commented Feb 24, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a submissions feature: new BountyDetailSubmissionsCard UI rendered in bounty detail, three submission mutation hooks with cache invalidation, a submission query-keys factory, and a typing refinement for bounty detail to allow submission-related fields.

Changes

Cohort / File(s) Summary
Submission UI
components/bounty-detail/bounty-detail-client.tsx, components/bounty-detail/bounty-detail-submissions-card.tsx
Inserted BountyDetailSubmissionsCard into bounty detail layout; new component implements PR submission flow, submissions list, review dialog, and "mark as paid" dialog with validation, loading states, and org-member action controls.
Submission Mutation Hooks
hooks/use-submission-mutations.ts
New hooks: useSubmitToBounty, useReviewSubmission, useMarkSubmissionPaid wrapping generated GraphQL mutations; each invalidates bounty-related query keys on success.
Query Key Utilities
lib/query/query-keys.ts
Added submissionKeys factory and SubmissionQueryKey type (all, lists, list(bountyId), detail(id), byBounty(bountyId)).
Bounty Detail Typing
hooks/use-bounty-detail.ts
Widened the returned bounty typing to `BountyFieldsFragment & Partial<BountyQuery["bounty"]>
Type Declarations Formatting
types/shims.d.ts
Formatting/quoting and semicolon normalization in module declarations (@tabler/icons-react, motion/react); no runtime behavior changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Client as BountyDetailSubmissionsCard
    participant Mut as useSubmitToBounty
    participant Cache as Query Cache (bountyKeys)
    participant Server as GraphQL Backend

    User->>Client: Open "Submit PR" dialog and submit PR URL + comments
    Client->>Client: Validate input, set loading state
    Client->>Mut: call mutate({ input: CreateSubmissionInput })
    Mut->>Server: submitToBounty mutation
    Server-->>Mut: return created Submission
    Mut->>Cache: invalidate bounty detail & lists
    Cache->>Client: refetch updated bounty & submissions
    Client-->>User: close dialog, show updated submissions
Loading
sequenceDiagram
    participant OrgMember
    participant Client as BountyDetailSubmissionsCard
    participant ReviewMut as useReviewSubmission
    participant PayMut as useMarkSubmissionPaid
    participant Cache as Query Cache (bountyKeys)
    participant Server as GraphQL Backend

    OrgMember->>Client: Open review dialog or "Mark as Paid"
    Client->>Client: Collect status/comments or tx hash
    alt Review submission
        Client->>ReviewMut: call mutate({ input: ReviewSubmissionInput })
        ReviewMut->>Server: reviewSubmission mutation
        Server-->>ReviewMut: return updated submission
        ReviewMut->>Cache: invalidate bounty queries
    else Mark as paid
        Client->>PayMut: call mutate({ submissionId, transactionHash })
        PayMut->>Server: markSubmissionPaid mutation
        Server-->>PayMut: return updated payment status
        PayMut->>Cache: invalidate bounty queries
    end
    Cache->>Client: refetch submission data
    Client-->>OrgMember: display updated status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • Issue #104 — Implements the three submission mutation hooks and UI wiring requested by this issue.
  • Issue #102 — Changes to bounty detail typing/UI to surface submissions align with this issue's migration and submissions objectives.

Possibly related PRs

  • PR #113 — Adds/updates GraphQL mutation/query fragments and generated hooks that these new submission hooks depend on.
  • PR #114 — Also modifies use-bounty-detail.ts; typing changes are directly related.
  • PR #88 — Prior bounty-detail client changes; this PR extends that UI by adding the submissions card.

Suggested reviewers

  • Benjtalkshow

Poem

🐇
I hopped through code with tiny paws,
Added PR boxes, checks, and laws,
Dialogs open, statuses bright,
Cache refreshed to show the right,
A crunchy carrot for each merged cause 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning All changes are directly related to issue #104 requirements. The type formatting updates in shims.d.ts appear to be minor formatting adjustments unrelated to submission mutations. Remove or justify the formatting-only changes in types/shims.d.ts (quote style, semicolon updates) as they are out of scope for submission mutation hooks.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding frontend hooks for submission mutations, which is the primary focus of this PR.
Linked Issues check ✅ Passed The PR successfully implements all required tasks from issue #104: three custom hooks (useSubmitToBounty, useReviewSubmission, useMarkSubmissionPaid) with cache invalidation, submission query keys, and UI integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Line 52: The current isOrgMember check uses session?.user?.id &&
bounty.organizationId which is truthy for any logged-in user; replace it with a
real membership/role check—e.g., consult session claims or org membership data
instead of just presence of IDs. Update the isOrgMember assignment to verify
that the session contains membership/role info for bounty.organizationId (for
example session.user.organizations includes bounty.organizationId, or
session.user.roles[bounty.organizationId] contains an admin/owner/reviewer
role), or call an existing helper like isUserOrgMember(userId, orgId) and use
its result; ensure the UI gating (isOrgMember) uses that boolean so only actual
org members see org-only actions.
- Around line 233-273: The Cancel button in the Mark as Paid dialog doesn't
close the dialog because it lacks a DialogClose wrapper; update the Cancel
Button to use the DialogClose component (e.g., <DialogClose asChild> around the
Button) or switch the dialog to controlled state and call the close handler on
click so that clicking Cancel closes the Dialog; locate the dialog block
containing Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle,
DialogDescription and replace the plain Cancel Button with a DialogClose-wrapped
Button (keeping existing props) so it properly closes the dialog without
changing handleMarkPaid, transactionHash, setTransactionHash, or
markSubmissionPaid behavior.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f1fcb62 and e4158c9.

📒 Files selected for processing (5)
  • components/bounty-detail/bounty-detail-client.tsx
  • components/bounty-detail/bounty-detail-submissions-card.tsx
  • hooks/use-bounty-detail.ts
  • hooks/use-submission-mutations.ts
  • lib/query/query-keys.ts

Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx Outdated
Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
components/bounty-detail/bounty-detail-submissions-card.tsx (1)

244-271: ⚠️ Potential issue | 🟡 Minor

Cancel doesn’t close the Mark as Paid dialog.
Wrap the Cancel button in DialogClose (or control open state) so it closes consistently.

✅ Suggested fix
 import {
   Dialog,
+  DialogClose,
   DialogContent,
   DialogDescription,
   DialogHeader,
   DialogTitle,
   DialogTrigger,
 } from "@/components/ui/dialog";
@@
-                            <Button variant="outline">Cancel</Button>
+                            <DialogClose asChild>
+                              <Button variant="outline">Cancel</Button>
+                            </DialogClose>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty-detail/bounty-detail-submissions-card.tsx` around lines 244
- 271, The Cancel button inside the Dialog does not close the modal because it
is a plain <Button>; wrap that button in the DialogClose component (e.g.,
<DialogClose asChild> around the Cancel <Button>) or wire it to the dialog open
state so it triggers close; locate the Dialog instance and the Cancel <Button
variant="outline"> in bounty-detail-submissions-card.tsx and replace/wrap that
Button with DialogClose (or call the dialog close handler) to ensure the dialog
closes consistently when Cancel is clicked.
🧹 Nitpick comments (1)
components/bounty-detail/bounty-detail-submissions-card.tsx (1)

290-303: Reset review state when opening the dialog.
reviewStatus/comments can carry over from prior actions (or a canceled submit). Resetting on open avoids stale input.

♻️ Suggested tweak
                   onClick={() => {
                     setSelectedSubmission(submission);
+                    setReviewStatus("APPROVED");
+                    setComments("");
                     setReviewDialogOpen(true);
                   }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty-detail/bounty-detail-submissions-card.tsx` around lines 290
- 303, When opening the review dialog the previous reviewStatus/comments can
persist; update the click handler that calls setSelectedSubmission(submission)
and setReviewDialogOpen(true) to also reset the review form state (e.g., call
setReviewStatus(initialStatus) and setComments(initialComments) or their empty
defaults) so the dialog always shows a fresh form for the selected submission;
alternatively, reset these fields inside the effect that runs when
selectedSubmission changes (watching selectedSubmission) to ensure no stale
inputs remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Around line 244-271: The Cancel button inside the Dialog does not close the
modal because it is a plain <Button>; wrap that button in the DialogClose
component (e.g., <DialogClose asChild> around the Cancel <Button>) or wire it to
the dialog open state so it triggers close; locate the Dialog instance and the
Cancel <Button variant="outline"> in bounty-detail-submissions-card.tsx and
replace/wrap that Button with DialogClose (or call the dialog close handler) to
ensure the dialog closes consistently when Cancel is clicked.

---

Nitpick comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Around line 290-303: When opening the review dialog the previous
reviewStatus/comments can persist; update the click handler that calls
setSelectedSubmission(submission) and setReviewDialogOpen(true) to also reset
the review form state (e.g., call setReviewStatus(initialStatus) and
setComments(initialComments) or their empty defaults) so the dialog always shows
a fresh form for the selected submission; alternatively, reset these fields
inside the effect that runs when selectedSubmission changes (watching
selectedSubmission) to ensure no stale inputs remain.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e4158c9 and 17030d9.

📒 Files selected for processing (2)
  • components/bounty-detail/bounty-detail-submissions-card.tsx
  • types/shims.d.ts

Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Around line 189-257: The submissions list currently renders details but omits
org-only action controls; add per-submission buttons (visible only when
isOrgMember is true) that open the existing dialogs and call the provided
handlers: add a "Review" button that triggers the review dialog and calls
handleReviewSubmission (passing submission.id and any chosen status/comments),
and add a "Mark Paid" button that opens the payment confirmation and calls
handleMarkPaid (passing submission.id) — ensure buttons are placed near the
status badge inside the submission item, use the existing dialog state/hooks
already wired in this component, and keep the buttons hidden for non-org users
by gating them with isOrgMember.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 17030d9 and 53a0080.

📒 Files selected for processing (1)
  • components/bounty-detail/bounty-detail-submissions-card.tsx

Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
components/bounty-detail/bounty-detail-submissions-card.tsx (1)

65-72: Validate PR URL before submit.

You already have isSafeHttpUrl; use it to avoid sending invalid/unsafe URLs to the mutation and to disable the submit button when invalid.

✅ Suggested guard
-  const handleSubmitPR = async () => {
-    if (!prUrl.trim()) return;
+  const handleSubmitPR = async () => {
+    if (!prUrl.trim() || !isSafeHttpUrl(prUrl)) return;
...
-                    disabled={!prUrl.trim() || submitToBounty.isPending}
+                    disabled={!prUrl.trim() || !isSafeHttpUrl(prUrl) || submitToBounty.isPending}

Also applies to: 116-123, 173-176

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty-detail/bounty-detail-submissions-card.tsx` around lines 65
- 72, Validate PR URLs using the existing isSafeHttpUrl before calling mutations
and disable submit controls when invalid: in handleSubmitPR, replace the simple
prUrl.trim() check with a guard that trims the URL and calls
isSafeHttpUrl(trimmed) and returns early if false, then pass the trimmed URL to
submitToBounty.mutateAsync; similarly apply the same validation guard to the
other submission/update handlers referenced around lines 116-123 and 173-176
(use the corresponding functions/mutations there) and set the submit button(s)
disabled state to !isSafeHttpUrl(prUrl.trim()) so users cannot click submit with
an invalid/unsafe URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Around line 93-102: The handler handleMarkPaid currently bails out if
transactionHash is empty but there is no UI to collect it; add a modal/dialog
component (e.g., MarkPaidDialog) that opens when the Mark Paid button is
clicked, contains a controlled input bound to transactionHash state, and
validates non-empty input before calling markSubmissionPaid.mutateAsync with
submission.id and trimmed transactionHash; after success close the dialog and
clear transactionHash via setTransactionHash(""); apply the same change for the
other Mark Paid usage referenced around the other handler (lines ~251-263) so
both flows gather and validate a transaction hash before invoking
markSubmissionPaid.
- Around line 48-55: The review dialog state variables (reviewDialogOpen,
setReviewDialogOpen, selectedSubmission, setSelectedSubmission, reviewStatus,
setReviewStatus, comments, setComments) are wired but no dialog JSX exists—add a
modal/dialog component inside BountyDetailSubmissionsCard that renders when
reviewDialogOpen is true containing: a status selector bound to reviewStatus, a
text area bound to comments, Cancel button that calls setReviewDialogOpen(false)
and clears selectedSubmission, and Submit button that invokes the existing
review submission handler (or create one such as submitReview) to post
reviewStatus, comments, and transactionHash for the selectedSubmission; ensure
the Review button that currently toggles setReviewDialogOpen also sets
selectedSubmission so the modal shows the correct submission.
- Around line 60-63: The isOrgMember check uses session.user.organizations which
isn’t populated, so update the access check: either add organizations to the
session payload in your auth/session creation callback (ensure
ExtendedUser.organizations is attached when building the session) or replace the
session-based check by calling a membership API from the
BountyDetailSubmissionsCard (use session.user.id and bounty.organizationId to
fetch membership, store result in component state and use that instead of
isOrgMember); alternatively remove the org-only gating if org access isn’t
implemented.

---

Nitpick comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Around line 65-72: Validate PR URLs using the existing isSafeHttpUrl before
calling mutations and disable submit controls when invalid: in handleSubmitPR,
replace the simple prUrl.trim() check with a guard that trims the URL and calls
isSafeHttpUrl(trimmed) and returns early if false, then pass the trimmed URL to
submitToBounty.mutateAsync; similarly apply the same validation guard to the
other submission/update handlers referenced around lines 116-123 and 173-176
(use the corresponding functions/mutations there) and set the submit button(s)
disabled state to !isSafeHttpUrl(prUrl.trim()) so users cannot click submit with
an invalid/unsafe URL.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 53a0080 and c7743a0.

📒 Files selected for processing (1)
  • components/bounty-detail/bounty-detail-submissions-card.tsx

Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx
Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx
Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx
@0xdevcollins

Copy link
Copy Markdown
Contributor

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Feb 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
components/bounty-detail/bounty-detail-submissions-card.tsx (2)

308-308: ⚠️ Potential issue | 🟡 Minor

"Submit Review" button missing disabled guard during pending state.

Unlike the Submit PR and Confirm (Mark Paid) buttons, the Submit Review button has no disabled={reviewSubmission.isPending} prop, allowing multiple submissions while the request is in-flight.

🐛 Proposed fix
-              <Button onClick={handleReviewSubmission}>Submit Review</Button>
+              <Button
+                onClick={handleReviewSubmission}
+                disabled={reviewSubmission.isPending}
+              >
+                {reviewSubmission.isPending && (
+                  <Loader2 className="mr-2 size-4 animate-spin" />
+                )}
+                Submit Review
+              </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty-detail/bounty-detail-submissions-card.tsx` at line 308, The
Submit Review Button in bounty-detail-submissions-card.tsx lacks a disabled
guard allowing duplicate requests; update the Button element that calls
handleReviewSubmission to pass disabled={reviewSubmission.isPending} (matching
the pattern used for the Submit PR and Confirm buttons) so the button is
disabled while reviewSubmission.isPending is true and re-enabled afterwards;
ensure you reference the existing handleReviewSubmission handler and the
reviewSubmission object when adding this prop.

59-59: ⚠️ Potential issue | 🟠 Major

setReviewStatus is assigned but never called — remove or wire it to the status selector.

The reviewStatus state is initialised to "APPROVED" and passed to reviewSubmission.mutateAsync, but setReviewStatus is never invoked anywhere. This means reviewers can only submit "APPROVED" reviews; there is no way to reject a submission through the UI.

🐛 Proposed fix — add a status selector to the Review dialog
 // In the Review Dialog (around line 294):
 <div className="space-y-4">
+  <div className="space-y-2">
+    <Label htmlFor="review-status">Status</Label>
+    <select
+      id="review-status"
+      className="w-full rounded-md border border-gray-700 bg-gray-900 px-3 py-2 text-sm text-gray-200"
+      value={reviewStatus}
+      onChange={(e) => setReviewStatus(e.target.value)}
+    >
+      <option value="APPROVED">Approve</option>
+      <option value="REJECTED">Reject</option>
+    </select>
+  </div>
   <Textarea
     placeholder="Add review feedback..."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/bounty-detail/bounty-detail-submissions-card.tsx` at line 59, The
state reviewStatus is initialized and passed into reviewSubmission.mutateAsync
but setReviewStatus is never used, so reviewers can only send "APPROVED"; update
the Review dialog UI in bounty-detail-submissions-card.tsx to include a status
selector (e.g., radio buttons or dropdown) bound to reviewStatus and call
setReviewStatus on change, and ensure the selected reviewStatus value is what
reviewSubmission.mutateAsync sends when submitting the review; reference the
reviewStatus/setReviewStatus state and the reviewSubmission.mutateAsync call to
wire the selector into the existing submit flow.
🧹 Nitpick comments (3)
hooks/use-submission-mutations.ts (1)

20-24: invalidateQueries is not awaited in onSuccess.

The fire-and-forget pattern is common, but since onSuccess is async-capable, awaiting the invalidations ensures the cache is marked stale before the mutation settles (and before any .then() on mutateAsync in the caller resolves).

♻️ Proposed fix (apply to all three hooks)
-    onSuccess: (data) => {
+    onSuccess: async (data) => {
       const bountyId = data.submitToBounty.bountyId;
-      queryClient.invalidateQueries({ queryKey: bountyKeys.detail(bountyId) });
-      queryClient.invalidateQueries({ queryKey: bountyKeys.lists() });
+      await queryClient.invalidateQueries({ queryKey: bountyKeys.detail(bountyId) });
+      await queryClient.invalidateQueries({ queryKey: bountyKeys.lists() });
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-submission-mutations.ts` around lines 20 - 24, The onSuccess
handler in the submission mutation should await the cache invalidations so
callers waiting on mutateAsync see a settled, stale cache; change the onSuccess
callback to an async function and await the two calls to
queryClient.invalidateQueries({ queryKey: bountyKeys.detail(bountyId) }) and
queryClient.invalidateQueries({ queryKey: bountyKeys.lists() }) (apply the same
change to the other two hooks in this file) so invalidations complete before the
mutation resolves to callers of mutateAsync.
lib/query/query-keys.ts (1)

28-32: list and byBounty serve the same purpose; consolidate to avoid cache fragmentation.

Both list(bountyId) and byBounty(bountyId) key submissions by bountyId. If future query hooks use different keys for the same data, they'll maintain separate cache entries that won't invalidate each other.

Pick one shape (e.g., list) and remove byBounty, or document a clear semantic difference between them.

♻️ Proposed refactor
 export const submissionKeys = {
   all: ["Submissions"] as const,
   lists: () => [...submissionKeys.all, "lists"] as const,
   list: (bountyId: string) =>
     [...submissionKeys.lists(), { bountyId }] as const,
   detail: (id: string) => [...submissionKeys.all, "detail", id] as const,
-  byBounty: (bountyId: string) =>
-    [...submissionKeys.all, "byBounty", bountyId] as const,
 };

 export type SubmissionQueryKey =
   | ReturnType<typeof submissionKeys.list>
-  | ReturnType<typeof submissionKeys.detail>
-  | ReturnType<typeof submissionKeys.byBounty>;
+  | ReturnType<typeof submissionKeys.detail>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/query/query-keys.ts` around lines 28 - 32, The keys list(bountyId) and
byBounty(bountyId) are duplicating the same cache identity; remove byBounty and
consolidate to a single shape (use list(bountyId) as the canonical key:
[...submissionKeys.lists(), { bountyId }] as const) and update all call sites to
use submissionKeys.list(...) instead of submissionKeys.byBounty(...); also
remove the byBounty export/definition from query-keys.ts (and any tests) so only
submissionKeys.list and submissionKeys.detail remain, ensuring cache
invalidation and lookups use the single unified key shape.
hooks/use-bounty-detail.ts (1)

12-14: Consider a narrower type extension instead of the full intersection.

Partial<BountyQuery["bounty"]> re-declares every field already present in BountyFieldsFragment as optional, making the intersection noisy. Since the only new field needed is submissions, a targeted extension is cleaner and makes the intent explicit:

♻️ Proposed refactor
-    data: data?.bounty as
-      | (BountyFieldsFragment & Partial<BountyQuery["bounty"]>)
-      | undefined,
+    data: data?.bounty as
+      | (BountyFieldsFragment & { submissions?: BountyQuery["bounty"]["submissions"] })
+      | undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-bounty-detail.ts` around lines 12 - 14, The type assertion on
data?.bounty currently intersects BountyFieldsFragment with
Partial<BountyQuery["bounty"]>, which is overly broad; narrow it to only add the
missing submissions field instead. Update the assertion in use-bounty-detail
(the line assigning data: data?.bounty) to combine BountyFieldsFragment with a
targeted type that declares the submissions property (e.g., Pick or an explicit
{ submissions?: ... } matching BountyQuery["bounty"]["submissions"]) so intent
is explicit and other fields are not redundantly re-optionalized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Around line 85-97: The three handlers (handleReviewSubmission, handleSubmitPR,
handleMarkPaid) currently call mutateAsync without error handling so failures
skip the cleanup; wrap each mutateAsync call in a try/catch/finally: perform the
mutation in try, handle or surface errors in catch (e.g., show a toast or log),
and put the cleanup steps (setReviewDialogOpen(false),
setSelectedSubmission(null), setComments(""), stopping any spinner/closing
dialogs) in finally so they always run regardless of success or failure; apply
this pattern to the functions named handleReviewSubmission, handleSubmitPR, and
handleMarkPaid.
- Around line 57-58: The shared comments state causes leakage between the Submit
PR and Review flows; create two separate state variables (e.g., submitComments
with setSubmitComments and reviewComments with setReviewComments) instead of the
single comments/setComments, then update handleSubmitPR to read/clear
submitComments and update the Submit PR dialog textarea to bind to
submitComments/setSubmitComments, and update handleReviewSubmission to
read/clear reviewComments and bind the Review dialog textarea to
reviewComments/setReviewComments (leave prUrl untouched).

---

Duplicate comments:
In `@components/bounty-detail/bounty-detail-submissions-card.tsx`:
- Line 308: The Submit Review Button in bounty-detail-submissions-card.tsx lacks
a disabled guard allowing duplicate requests; update the Button element that
calls handleReviewSubmission to pass disabled={reviewSubmission.isPending}
(matching the pattern used for the Submit PR and Confirm buttons) so the button
is disabled while reviewSubmission.isPending is true and re-enabled afterwards;
ensure you reference the existing handleReviewSubmission handler and the
reviewSubmission object when adding this prop.
- Line 59: The state reviewStatus is initialized and passed into
reviewSubmission.mutateAsync but setReviewStatus is never used, so reviewers can
only send "APPROVED"; update the Review dialog UI in
bounty-detail-submissions-card.tsx to include a status selector (e.g., radio
buttons or dropdown) bound to reviewStatus and call setReviewStatus on change,
and ensure the selected reviewStatus value is what reviewSubmission.mutateAsync
sends when submitting the review; reference the reviewStatus/setReviewStatus
state and the reviewSubmission.mutateAsync call to wire the selector into the
existing submit flow.

---

Nitpick comments:
In `@hooks/use-bounty-detail.ts`:
- Around line 12-14: The type assertion on data?.bounty currently intersects
BountyFieldsFragment with Partial<BountyQuery["bounty"]>, which is overly broad;
narrow it to only add the missing submissions field instead. Update the
assertion in use-bounty-detail (the line assigning data: data?.bounty) to
combine BountyFieldsFragment with a targeted type that declares the submissions
property (e.g., Pick or an explicit { submissions?: ... } matching
BountyQuery["bounty"]["submissions"]) so intent is explicit and other fields are
not redundantly re-optionalized.

In `@hooks/use-submission-mutations.ts`:
- Around line 20-24: The onSuccess handler in the submission mutation should
await the cache invalidations so callers waiting on mutateAsync see a settled,
stale cache; change the onSuccess callback to an async function and await the
two calls to queryClient.invalidateQueries({ queryKey:
bountyKeys.detail(bountyId) }) and queryClient.invalidateQueries({ queryKey:
bountyKeys.lists() }) (apply the same change to the other two hooks in this
file) so invalidations complete before the mutation resolves to callers of
mutateAsync.

In `@lib/query/query-keys.ts`:
- Around line 28-32: The keys list(bountyId) and byBounty(bountyId) are
duplicating the same cache identity; remove byBounty and consolidate to a single
shape (use list(bountyId) as the canonical key: [...submissionKeys.lists(), {
bountyId }] as const) and update all call sites to use submissionKeys.list(...)
instead of submissionKeys.byBounty(...); also remove the byBounty
export/definition from query-keys.ts (and any tests) so only submissionKeys.list
and submissionKeys.detail remain, ensuring cache invalidation and lookups use
the single unified key shape.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f1fcb62 and b23e89d.

📒 Files selected for processing (6)
  • components/bounty-detail/bounty-detail-client.tsx
  • components/bounty-detail/bounty-detail-submissions-card.tsx
  • hooks/use-bounty-detail.ts
  • hooks/use-submission-mutations.ts
  • lib/query/query-keys.ts
  • types/shims.d.ts

Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx Outdated
Comment thread components/bounty-detail/bounty-detail-submissions-card.tsx
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.

Add submission mutation hooks (new)

2 participants