feat: add frontend hooks for submission mutations - #117
Conversation
- 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
|
Someone is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
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.
📒 Files selected for processing (5)
components/bounty-detail/bounty-detail-client.tsxcomponents/bounty-detail/bounty-detail-submissions-card.tsxhooks/use-bounty-detail.tshooks/use-submission-mutations.tslib/query/query-keys.ts
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
components/bounty-detail/bounty-detail-submissions-card.tsx (1)
244-271:⚠️ Potential issue | 🟡 MinorCancel doesn’t close the Mark as Paid dialog.
Wrap the Cancel button inDialogClose(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/commentscan 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.
📒 Files selected for processing (2)
components/bounty-detail/bounty-detail-submissions-card.tsxtypes/shims.d.ts
There was a problem hiding this comment.
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.
📒 Files selected for processing (1)
components/bounty-detail/bounty-detail-submissions-card.tsx
There was a problem hiding this comment.
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.
📒 Files selected for processing (1)
components/bounty-detail/bounty-detail-submissions-card.tsx
|
@CodeRabbit full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
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
disabledguard 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
setReviewStatusis assigned but never called — remove or wire it to the status selector.The
reviewStatusstate is initialised to"APPROVED"and passed toreviewSubmission.mutateAsync, butsetReviewStatusis 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:invalidateQueriesis not awaited inonSuccess.The fire-and-forget pattern is common, but since
onSuccessis async-capable, awaiting the invalidations ensures the cache is marked stale before the mutation settles (and before any.then()onmutateAsyncin 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:listandbyBountyserve the same purpose; consolidate to avoid cache fragmentation.Both
list(bountyId)andbyBounty(bountyId)key submissions bybountyId. 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 removebyBounty, 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 inBountyFieldsFragmentas optional, making the intersection noisy. Since the only new field needed issubmissions, 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.
📒 Files selected for processing (6)
components/bounty-detail/bounty-detail-client.tsxcomponents/bounty-detail/bounty-detail-submissions-card.tsxhooks/use-bounty-detail.tshooks/use-submission-mutations.tslib/query/query-keys.tstypes/shims.d.ts
…ntyDetailSubmissionsCard
Changes
Acceptance Criteria
Closes #104
Summary by CodeRabbit