Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions src/components/bounty/ClaimButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,48 @@ describe("ClaimButton", () => {
expect(reset.mock.invocationCallOrder[0]).toBeLessThan(claim.mock.invocationCallOrder[0]);
});

it("renders the already-claimed view instead of a button when status is claimed", () => {
mockHooks({}, { status: "claimed", bounty: { id: "b1", status: "claimed", claimedBy: "bob" } });
it("renders the already-claimed view instead of a button when status is claimed by someone else", () => {
mockHooks({}, { status: "claimed", bounty: { id: "b1", status: "claimed", claimedBy: "bob", claimedById: "user-2" } });
render(<ClaimButton bountyId="b1" />);
expect(screen.queryByRole("button")).not.toBeInTheDocument();
expect(screen.getByText(/This bounty has already been/)).toBeInTheDocument();
expect(screen.getByText("Claimed by: bob")).toBeInTheDocument();
});

it("renders the working-on-this state with PR instructions when claimed by current user (#699)", () => {
mockHooks({}, {
status: "claimed",
bounty: {
id: "b1",
status: "claimed",
claimedById: "user-1",
issueNumber: 42,
},
});
render(<ClaimButton bountyId="b1" />);
expect(screen.queryByRole("button")).not.toBeInTheDocument();
expect(screen.getByText("You're working on this bounty")).toBeInTheDocument();
expect(screen.getByText(/Open a pull request that says/)).toBeInTheDocument();
expect(screen.getByText("Fixes #42")).toBeInTheDocument();
expect(screen.queryByText(/This bounty has already been/)).not.toBeInTheDocument();
});

it("identifies claimant by username when claimedById is absent (#699)", () => {
mockAuth({ id: "user-1", username: "alice", stellarAddress: "GSTELLAR" });
mockHooks({}, {
status: "claimed",
bounty: {
id: "b1",
status: "claimed",
claimedBy: "alice",
issueNumber: 101,
},
});
render(<ClaimButton bountyId="b1" />);
expect(screen.getByText("You're working on this bounty")).toBeInTheDocument();
expect(screen.getByText("Fixes #101")).toBeInTheDocument();
});

it("omits the claimant line when claimedBy is missing", () => {
mockHooks({}, { status: "claimed", bounty: { id: "b1", status: "claimed" } });
render(<ClaimButton bountyId="b1" />);
Expand Down
29 changes: 29 additions & 0 deletions src/components/bounty/ClaimButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,35 @@ export function ClaimButton({
const needsPayoutWallet = !!user && !user.stellarAddress;

if (status === 'claimed') {
const isClaimedByMe =
!!user &&
((bounty?.claimedById && bounty.claimedById === user.id) ||
(bounty?.claimedBy && bounty.claimedBy === user.username));

if (isClaimedByMe) {
const issueRef = bounty?.issueNumber
? `Fixes #${bounty.issueNumber}`
: 'Fixes #<issue-number>';

return (
<div
className={`p-4 bg-emerald-50 border border-emerald-200 rounded-lg dark:bg-emerald-950/20 dark:border-emerald-800 ${className}`}
role="status"
>
<h4 className="text-emerald-800 dark:text-emerald-300 font-semibold">
You&apos;re working on this bounty
</h4>
<p className="text-emerald-700 dark:text-emerald-400 text-sm mt-1">
Open a pull request that says{' '}
<code className="font-mono font-semibold bg-emerald-100 dark:bg-emerald-900/40 px-1 py-0.5 rounded text-xs">
{issueRef}
</code>{' '}
to link your work. Payout is released automatically when your pull request is merged.
</p>
</div>
);
}

return (
<div className={`p-4 bg-gray-50 border border-gray-200 rounded-lg ${className}`}>
<p className="text-gray-600">
Expand Down