Skip to content

Feat: implementation logic for single claim model#111

Merged
0xdevcollins merged 3 commits intoboundlessfi:mainfrom
Bosun-Josh121:feat/logic-implemention-for-single-claim-model
Feb 23, 2026
Merged

Feat: implementation logic for single claim model#111
0xdevcollins merged 3 commits intoboundlessfi:mainfrom
Bosun-Josh121:feat/logic-implemention-for-single-claim-model

Conversation

@Bosun-Josh121
Copy link
Contributor

@Bosun-Josh121 Bosun-Josh121 commented Feb 23, 2026

Description

This PR implements the Single Claim Model logic for the bounty details page, bridging the gap between an open bounty and a claimed state.

Closes #79

Key Changes

  • Data Types & Schemas: Added the ClaimInfo type to the Bounty interface (types/bounty.ts) and updated the Zod schema (lib/api/bounties.ts) to validate and pass the new data structure.
  • Claimant Profile UI: Created a new ClaimantProfile component in the sidebar (bounty-detail-sidebar-cta.tsx) that displays the avatar, username, and timestamp of the user who claimed the bounty.
  • Button State Logic: Updated the main CTA button to automatically disable and display "Already Claimed" when the bounty status is no longer open.
  • Mock Data: Updated mockBounties to include a fully mocked claimInfo object for UI testing.

Acceptance Criteria Met

  • Show claimant profile when claimed
  • Disable claim button if already claimed
  • Display "Already Claimed" state
  • Cannot claim if already claimed

Testing Steps

  1. Navigate to an open bounty (e.g., /bounty/1).
  2. Click Claim Bounty. Verify the success toast appears, the button disables to "Already Claimed", and the Claimant Profile card appears below it.
  3. Navigate to an already claimed bounty (e.g., /bounty/2). Verify the Claimant Profile renders correctly on load.
bounty

Summary by CodeRabbit

  • Style
    • Reorganized bounty detail sidebar for clearer visual hierarchy and added separators between Reward, Meta, and CTA sections.
    • Cleaned up code formatting and minor markup structure; no user-facing behavior changes.

@vercel
Copy link

vercel bot commented Feb 23, 2026

@Bosun-Josh121 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent 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 9239ed5 and f97656d.

📒 Files selected for processing (1)
  • hooks/use-bounty-mutations.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • hooks/use-bounty-mutations.ts

📝 Walkthrough

Walkthrough

Reorders and restructures the bounty detail sidebar CTA markup (adds outer spacing container, separators, and moves Meta after Reward) and applies formatting/indentation edits to bounty mutation hooks; no public API/signature changes and no behavioral changes to CTA handlers or mutation logic.

Changes

Cohort / File(s) Summary
CTA Component Restructuring
components/bounty-detail/bounty-detail-sidebar-cta.tsx
Rewraps CTA content in an outer space-y-4 container, reorders sections so Meta appears after Reward, and adds Separator elements to delineate blocks. No changes to click handlers, CTA enablement, or copy-to-clipboard logic.
Mutation Hook Formatting
hooks/use-bounty-mutations.ts
Formatting and import quote style adjustments; useClaimBounty's onSuccess now uses an explicit generic in setQueryData and includes a comment—no change in runtime behavior or cache invalidation policy.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • Benjtalkshow

Poem

🐰 In the sidebar's tidy row I hop,
I add a separator, then stop,
Reward first, then meta in line,
Mutations tidy, all looks fine,
A tiny twitch, then onward I hop! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat: implementation logic for single claim model' accurately describes the main objective of adding Single Claim Model logic to the bounty details page, directly matching the linked issue #79.
Linked Issues check ✅ Passed The PR implements all requirements from issue #79: adds ClaimInfo type to Bounty interface, creates ClaimantProfile UI component, disables CTA when claimed, and updates mock data—meeting all acceptance criteria for the Single Claim Model.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the Single Claim Model as defined in issue #79; no unrelated modifications detected in the UI restructuring, hook updates, or mock data changes.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 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.

Copy link

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/mock-bounty.ts (1)

186-193: ⚠️ Potential issue | 🟡 Minor

Bounty "6" has status: "claimed" but no claimInfo.

Bounty "6" is also in a "claimed" state, but unlike bounty "2", it lacks a claimInfo object. This means ClaimantProfile will not render for this bounty while the CTA still shows "Already Claimed." Consider adding mock claimInfo here too for testing consistency, or leaving a comment explaining why it's intentionally omitted (e.g., different claiming model).

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

In `@lib/mock-bounty.ts` around lines 186 - 193, Bounty object with id "6" has
status "claimed" but is missing the claimInfo used by ClaimantProfile; update
the bounty (the object for "6" in mock-bounty.ts) to include a mock claimInfo
structure matching the shape used by ClaimantProfile (e.g., claimant
id/name/avatar/claimedAt/amount or the same fields used in bounty "2"), or if
intentional, add an inline comment on that bounty explaining why claimInfo is
omitted (e.g., different claiming model) so tests and developers aren’t
confused.
🧹 Nitpick comments (3)
components/bounty-detail/bounty-detail-sidebar-cta.tsx (2)

186-188: ClaimantProfile rendered based on status === "claimed" but claimInfo may be absent.

The guard here checks bounty.status === "claimed" to render ClaimantProfile, but ClaimantProfile itself returns null when claimInfo is missing (line 276). This works safely — no crash — but the user sees a "claimed" status with no claimant info at all (no card, no message). For bounties like mock bounty "6" (status "claimed", no claimInfo), this could be confusing.

Consider rendering a minimal fallback message (e.g., "Claimed by an unknown contributor") when claimInfo is absent but the bounty is claimed.

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

In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 186 -
188, The component currently renders <ClaimantProfile
claimInfo={bounty.claimInfo} /> whenever bounty.status === "claimed", but
<ClaimantProfile> returns null if claimInfo is missing, leaving no UI for
claimed bounties with missing data; update the render logic in
bounty-detail-sidebar-cta so that when bounty.status === "claimed" you check
bounty.claimInfo and render <ClaimantProfile claimInfo={bounty.claimInfo} /> if
present, otherwise render a minimal fallback UI (e.g., a small message or card
reading "Claimed by an unknown contributor") so users still see that the bounty
is claimed even without claimInfo.

210-266: MobileCTA duplicates handleAction and label logic from SidebarCTA; both also instantiate separate useClaimBounty hooks.

The handleAction, ctaLabel/label, and claimMutation setup in MobileCTA (lines 212–241) is nearly identical to SidebarCTA (lines 19, 35–63). Since both components are mounted simultaneously (CSS lg:hidden / lg:block doesn't unmount), you get two independent mutation instances — their isPending states won't stay in sync across a breakpoint change.

Consider extracting the shared claim logic (mutation, handler, label) into a custom hook (e.g., useClaimAction(bounty)) that both components consume, ensuring a single mutation instance and eliminating duplication.

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

In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 210 -
266, Extract the duplicated claim logic from MobileCTA and SidebarCTA into a new
custom hook (e.g., useClaimAction) that wraps useClaimBounty and exposes the
shared symbols: claimMutation (or mutate/claim), isPending, a unified
handleAction(bounty → opens dialog or calls mutate), and a label/ctaLabel
function; then replace the internal claimMutation, handleAction, and label
implementations in both MobileCTA and SidebarCTA to call useClaimAction(bounty)
so both components share one mutation instance and identical label/behavior
(keep SubmissionDialog state handling in each component if needed, but use the
hook for mutation/label/handler).
lib/api/bounties.ts (1)

54-56: Redundant claim fields: old flat claimedAt/claimedBy coexist with new nested claimInfo.

Lines 54–55 retain the legacy flat claimedAt and claimedBy fields while line 56 introduces the new claimInfo object that encapsulates the same data in a richer form. The UI components now read from claimInfo, making the flat fields effectively dead weight in the schema. This dual representation creates ambiguity about which is the source of truth.

Consider deprecating or removing the flat fields if the backend will migrate to the claimInfo structure, or document why both are needed during a transition period.

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

In `@lib/api/bounties.ts` around lines 54 - 56, The schema includes redundant flat
fields claimedAt and claimedBy alongside the new claimInfo object; remove (or
mark deprecated) the flat fields from the Zod schema and update any code that
reads claimedAt/claimedBy to use claimInfo (or migrate values into claimInfo on
input). Specifically, edit the Zod schema declarations in lib/api/bounties.ts to
drop claimedAt and claimedBy (or leave them only in a transitional comment),
ensure parsing/validation and any callers of the bounty schema (e.g.,
create/update handlers and downstream consumers) read
claimInfo.{claimedAt,claimedBy,...} instead, and add a migration/compatibility
step if you must accept legacy payloads by mapping claimedAt/claimedBy into
claimInfo before validating.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/use-bounty-mutations.ts`:
- Around line 108-125: The fallback branch in the update logic uses a brittle
validity check (data && data.projectName) and patches the cache with hardcoded
mock claimant values; update the check to verify the actual claim outcome (e.g.,
data?.claimInfo?.claimedBy or data?.status === "claimed") and remove or replace
the hardcoded mock values in the fallback path (the object merged into old with
status: "claimed" and claimInfo.claimedBy) — if you must keep a local fallback,
add a clear TODO comment above the fallback that warns to replace mock
userId/username/avatarUrl before production, otherwise drop the fallback and let
the real API response (data) determine the cached value.

In `@types/bounty.ts`:
- Around line 8-13: The TypeScript union ClaimingModel and the Zod enum
claimingModelSchema are out of sync because ClaimingModel includes "milestone"
but claimingModelSchema does not; update the Zod schema in lib/api/bounties.ts
(claimingModelSchema) to include "milestone" in its enum values so validation
accepts the same set as the ClaimingModel type (alternatively remove "milestone"
from the ClaimingModel type if the feature is intentionally unsupported).

---

Outside diff comments:
In `@lib/mock-bounty.ts`:
- Around line 186-193: Bounty object with id "6" has status "claimed" but is
missing the claimInfo used by ClaimantProfile; update the bounty (the object for
"6" in mock-bounty.ts) to include a mock claimInfo structure matching the shape
used by ClaimantProfile (e.g., claimant id/name/avatar/claimedAt/amount or the
same fields used in bounty "2"), or if intentional, add an inline comment on
that bounty explaining why claimInfo is omitted (e.g., different claiming model)
so tests and developers aren’t confused.

---

Nitpick comments:
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 186-188: The component currently renders <ClaimantProfile
claimInfo={bounty.claimInfo} /> whenever bounty.status === "claimed", but
<ClaimantProfile> returns null if claimInfo is missing, leaving no UI for
claimed bounties with missing data; update the render logic in
bounty-detail-sidebar-cta so that when bounty.status === "claimed" you check
bounty.claimInfo and render <ClaimantProfile claimInfo={bounty.claimInfo} /> if
present, otherwise render a minimal fallback UI (e.g., a small message or card
reading "Claimed by an unknown contributor") so users still see that the bounty
is claimed even without claimInfo.
- Around line 210-266: Extract the duplicated claim logic from MobileCTA and
SidebarCTA into a new custom hook (e.g., useClaimAction) that wraps
useClaimBounty and exposes the shared symbols: claimMutation (or mutate/claim),
isPending, a unified handleAction(bounty → opens dialog or calls mutate), and a
label/ctaLabel function; then replace the internal claimMutation, handleAction,
and label implementations in both MobileCTA and SidebarCTA to call
useClaimAction(bounty) so both components share one mutation instance and
identical label/behavior (keep SubmissionDialog state handling in each component
if needed, but use the hook for mutation/label/handler).

In `@lib/api/bounties.ts`:
- Around line 54-56: The schema includes redundant flat fields claimedAt and
claimedBy alongside the new claimInfo object; remove (or mark deprecated) the
flat fields from the Zod schema and update any code that reads
claimedAt/claimedBy to use claimInfo (or migrate values into claimInfo on
input). Specifically, edit the Zod schema declarations in lib/api/bounties.ts to
drop claimedAt and claimedBy (or leave them only in a transitional comment),
ensure parsing/validation and any callers of the bounty schema (e.g.,
create/update handlers and downstream consumers) read
claimInfo.{claimedAt,claimedBy,...} instead, and add a migration/compatibility
step if you must accept legacy payloads by mapping claimedAt/claimedBy into
claimInfo before validating.

Copy link

@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

🧹 Nitpick comments (1)
hooks/use-bounty-mutations.ts (1)

99-124: Consider adding onSettled to re-validate the detail cache.

The hook has no onSettled handler, so the detail cache is only updated via the setQueryData call in onSuccess. If the claim API response is ever partial or the cache is not pre-populated (the if (!old) return old guard silently no-ops), the detail entry won't be refreshed. Adding an onSettled invalidation keeps the detail in sync consistently, matching the pattern already used in useUpdateBounty.

♻️ Proposed addition
     onSuccess: (data, id) => {
       queryClient.setQueryData<Bounty>(bountyKeys.detail(id), data);
       queryClient.invalidateQueries({ queryKey: bountyKeys.lists() });
     },
+    onSettled: (_data, _err, id) => {
+      queryClient.invalidateQueries({ queryKey: bountyKeys.detail(id) });
+    },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-bounty-mutations.ts` around lines 99 - 124, The useClaimBounty hook
only updates the detail cache in onSuccess via queryClient.setQueryData and
skips when old is missing, so add an onSettled handler to always revalidate the
bounty detail; implement onSettled: call queryClient.invalidateQueries({
queryKey: bountyKeys.detail(id) }) (or pass the id param from the mutation
variables) and also keep the existing queryClient.invalidateQueries({ queryKey:
bountyKeys.lists() }) behavior to ensure the detail cache is refreshed whether
the mutation succeeds or fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/use-bounty-mutations.ts`:
- Around line 104-122: The onSuccess handler contains dead fallback logic: since
mutationFn returns Promise<Bounty>, data is always a non-null Bounty, so remove
the unreachable if (data) { ... } else { ... } branch and simplify the update to
always setQueryData<Bounty>(bountyKeys.detail(id), () => data); keep the
subsequent queryClient.invalidateQueries({ queryKey: bountyKeys.lists() }) call;
update references are in the onSuccess callback, queryClient.setQueryData,
bountyKeys.detail and bountyKeys.lists, and the Bounty/mutationFn contract.

---

Nitpick comments:
In `@hooks/use-bounty-mutations.ts`:
- Around line 99-124: The useClaimBounty hook only updates the detail cache in
onSuccess via queryClient.setQueryData and skips when old is missing, so add an
onSettled handler to always revalidate the bounty detail; implement onSettled:
call queryClient.invalidateQueries({ queryKey: bountyKeys.detail(id) }) (or pass
the id param from the mutation variables) and also keep the existing
queryClient.invalidateQueries({ queryKey: bountyKeys.lists() }) behavior to
ensure the detail cache is refreshed whether the mutation succeeds or fails.

ℹ️ 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 9c19e5b and 9239ed5.

📒 Files selected for processing (2)
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • hooks/use-bounty-mutations.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx

@0xdevcollins 0xdevcollins merged commit 41f520e into boundlessfi:main Feb 23, 2026
1 of 2 checks passed
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.

Implement Single Claim Model Logic

2 participants