Skip to content

Issue 181 sponsor bounty creation#312

Closed
Danitello123 wants to merge 4 commits into
boundlessfi:mainfrom
Danitello123:issue-181-sponsor-bounty-creation
Closed

Issue 181 sponsor bounty creation#312
Danitello123 wants to merge 4 commits into
boundlessfi:mainfrom
Danitello123:issue-181-sponsor-bounty-creation

Conversation

@Danitello123

@Danitello123 Danitello123 commented Jun 28, 2026

Copy link
Copy Markdown

closes #181

Summary by CodeRabbit

  • New Features

    • Added a new multi-step “Create Bounty” experience with review before submission.
    • Expanded access so eligible sponsors and organization members can create bounties from the app and navigation.
    • Added EURC as an available budget currency.
  • Bug Fixes

    • Improved authorization handling so the create page only shows for permitted users and redirects otherwise.
    • Added success/error feedback and automatic navigation after bounty creation.

User and others added 4 commits June 24, 2026 02:34
- Remove unused useUserRole import and userRole variable from
  �pp/bounty/create/page.tsx (no-unused-vars)
- Remove unused useUserRole import and userRole variable from
  components/global-navbar.tsx (no-unused-vars)
- Add missing 
ole field to ExtendedUser interface in both files
  so user.role access is type-safe without runtime cast
- Replace error: any in hooks/use-create-bounty.ts onError handler
  with unknown + instanceof Error guard (@typescript-eslint/no-explicit-any)
- Replace four orm as any casts in �ounty-create-form.tsx with
  typed UseFormReturn<FieldValues> casts (@typescript-eslint/no-explicit-any)
- Update currency picker from AQUA → EURC in �udget-input.tsx and
  schemas.ts to match issue spec (XLM, USDC, EURC)
- Add unit tests for use-create-bounty hook covering success/error
  paths, cache invalidation, redirect, and toast notifications
@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

@Danitello123 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 Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a multi-step bounty creation flow: a new BountyCreateForm component with Zod validation and three steps (basic info, rewards/deadlines, review), a useCreateBounty hook wiring the GraphQL mutation with cache invalidation and routing, EURC asset support replacing AQUA, and session-based authorization gating in the navbar and create page.

Bounty Creation Flow

Layer / File(s) Summary
EURC asset and budget schema
components/bounty/forms/budget-input.tsx, components/bounty/forms/schemas.ts
Replaces AQUA with EURC in the BudgetInput assets list and budgetSchema z.enum allowed values.
useCreateBounty mutation hook
hooks/use-create-bounty.ts, hooks/__tests__/use-create-bounty.test.tsx
Adds useCreateBounty wrapping useCreateBountyMutation with onSuccess (cache invalidation, success toast, redirect to /bounty/{id}) and onError (error toast) handling, plus createBounty/createBountyAsync wrappers. Tests are currently skipped.
BountyCreateForm multi-step UI
components/bounty/bounty-create-form.tsx
Defines Zod schema with conditional superRefine rules by bounty type (Fixed Price, Competition, Milestone Based), react-hook-form wiring, step-based validation/navigation, and three-step UI: basic info (title/type/org/project/GitHub URL/description), rewards/deadlines (amount/dates/lightning window/milestones), and a review/confirmation panel.
Session-based authorization gating
components/global-navbar.tsx, app/bounty/create/page.tsx
Replaces useUserRole() with session-derived isSponsorOrOrgMember (sponsor role or non-empty organizations) in both the navbar "Create Bounty" link visibility and the create page redirect/gate logic. The create page now renders BountyCreateForm.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • boundlessfi/bounties#73: Updates BudgetInput/budgetSchema asset options, which this PR modifies by switching AQUA to EURC.
  • boundlessfi/bounties#254: Targets the same app/bounty/create/page.tsx and components/global-navbar.tsx authorization gating, but gates strictly on role === "sponsor" rather than sponsor-or-org-member.
  • boundlessfi/bounties#246: Playwright suite exercising the multi-step bounty creation UI and CreateBounty GraphQL submission/redirect flow introduced by this PR.

Suggested reviewers

  • Benjtalkshow
  • 0xdevcollins

🐇 Hop hop, a bounty form appears,
Three steps to glory, no more fears!
EURC gleams where AQUA once stood,
Org members create — as they should.
The rabbit stamps "LGTM" with cheer! 🎉

🚥 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 clearly names the sponsor bounty creation feature and matches the main change.
Linked Issues check ✅ Passed The PR implements the creation route, form, hook, validation, redirect, and navbar gating required by #181.
Out of Scope Changes check ✅ Passed The added EURC support, tests, and navbar copy all align with the bounty creation flow and issue requirements.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Warning

⚠️ This pull request shows signs of AI-generated slop (defensive_cruft). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/bounty/bounty-create-form.tsx`:
- Around line 226-229: The defaultValues initializer in bounty-create-form.tsx
is using an `any` escape hatch for `reward.amount`, which should be removed.
Since `defaultValues` is already partial, update the `defaultValues` object so
`reward.amount` is omitted entirely rather than set to `undefined as any`, and
keep the typing aligned with the `BountyCreateForm`/form defaults shape to avoid
masking type issues and lint violations.
- Around line 199-208: The bounty create form is defaulting a required mutation
field to placeholder organization IDs when `user.organizations` is empty, which
can cause fake `organizationId` values to be submitted. Update
`bounty-create-form.tsx` in the organization selection logic and the
preselection path used by the form so that sponsor users without real
organizations do not get mock options; instead, leave `organizationId` unset and
require a real selection or derive it only from actual user organizations.
- Around line 267-279: The onSubmit payload in bounty-create-form.tsx is missing
several validated fields from BountyFormValues, so include deadline, startDate,
endDate, and milestones when building the CreateBountyInput for createBounty.
Also update the githubIssueUrl handling in onSubmit so non-fixed bounty flows
still satisfy the CreateBountyInput contract instead of sending an empty string;
use the existing form values and related helpers like parseGithubIssueNumber and
reward/bountyWindow fields to keep the submission aligned with the review step.
- Around line 424-425: The SelectItem values for the “None” options are using an
empty string, which Radix Select rejects at runtime. Update the affected select
fields in bounty-create-form by replacing the empty-string option with a
non-empty sentinel value, and make the form logic map that sentinel back to
undefined when building/submitting the payload. Use the existing select handlers
and submit mapping in bounty-create-form to keep the UI behavior unchanged while
avoiding invalid SelectItem values.

In `@hooks/__tests__/use-create-bounty.test.tsx`:
- Line 87: The useCreateBounty test suite is currently disabled with
describe.skip, leaving the new hook behavior unverified. Re-enable the suite in
use-create-bounty.test.tsx by removing the skip so the existing success, error,
and cache invalidation tests for useCreateBounty actually run before merge.
- Around line 26-41: The GraphQL mock for useCreateBountyMutation is replacing
the entire `@/lib/graphql/generated` module and dropping BountyType, which breaks
MOCK_INPUT.type usage in use-create-bounty.test.tsx. Update the jest.mock for
`@/lib/graphql/generated` to include the real module via jest.requireActual and
override only useCreateBountyMutation, keeping BountyType and other exported
symbols available to the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd424473-991c-443c-8b8b-993b2160653c

📥 Commits

Reviewing files that changed from the base of the PR and between 948ff12 and c3875ee.

📒 Files selected for processing (7)
  • app/bounty/create/page.tsx
  • components/bounty/bounty-create-form.tsx
  • components/bounty/forms/budget-input.tsx
  • components/bounty/forms/schemas.ts
  • components/global-navbar.tsx
  • hooks/__tests__/use-create-bounty.test.tsx
  • hooks/use-create-bounty.ts

Comment on lines +199 to +208
// Parse user organizations & fallbacks
const user = session?.user as ExtendedUser | undefined;
const userOrgs = user?.organizations || [];
const organizations = userOrgs.length > 0
? userOrgs.map(org => ({ id: org, name: org }))
: [
{ id: "org-wallet", name: "Acme Wallet" },
{ id: "org-defi", name: "DeFi Protocol" },
{ id: "org-security", name: "Stellar Security" }
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Don't default a required mutation field to mock organization IDs.

For sponsor users without user.organizations, this falls back to placeholder options (org-wallet, org-defi, org-security) and Line 222 preselects one as the required organizationId. That means this flow can submit fake IDs even though the page intentionally authorizes sponsors without org membership.

Also applies to: 219-223

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/bounty/bounty-create-form.tsx` around lines 199 - 208, The bounty
create form is defaulting a required mutation field to placeholder organization
IDs when `user.organizations` is empty, which can cause fake `organizationId`
values to be submitted. Update `bounty-create-form.tsx` in the organization
selection logic and the preselection path used by the form so that sponsor users
without real organizations do not get mock options; instead, leave
`organizationId` unset and require a real selection or derive it only from
actual user organizations.

Comment on lines +226 to +229
reward: {
amount: undefined as any,
asset: "USDC",
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the any escape hatch from defaultValues.

defaultValues is already partial, so reward.amount can be omitted instead of forcing undefined as any. Right now this hides typing problems and still trips the lint rule at Line 227.

🧰 Tools
🪛 ESLint

[error] 227-227: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/bounty/bounty-create-form.tsx` around lines 226 - 229, The
defaultValues initializer in bounty-create-form.tsx is using an `any` escape
hatch for `reward.amount`, which should be removed. Since `defaultValues` is
already partial, update the `defaultValues` object so `reward.amount` is omitted
entirely rather than set to `undefined as any`, and keep the typing aligned with
the `BountyCreateForm`/form defaults shape to avoid masking type issues and lint
violations.

Source: Linters/SAST tools

Comment on lines +267 to +279
const onSubmit = async (values: BountyFormValues) => {
const input = {
title: values.title,
type: values.type,
description: values.description,
organizationId: values.organizationId,
projectId: values.projectId || undefined,
githubIssueUrl: values.githubIssueUrl || "",
githubIssueNumber: values.githubIssueUrl ? parseGithubIssueNumber(values.githubIssueUrl) : undefined,
rewardAmount: values.reward.amount,
rewardCurrency: values.reward.asset,
bountyWindowId: values.bountyWindowId || undefined,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The submitted payload drops most of the data collected in steps 2 and 3.

deadline, startDate, endDate, and milestones are validated and shown in review, but never sent to createBounty. At the same time, non-fixed bounties submit githubIssueUrl: "" even though the generated CreateBountyInput contract still requires that field. Competition and milestone flows will therefore either lose user input or fail at the API boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/bounty/bounty-create-form.tsx` around lines 267 - 279, The
onSubmit payload in bounty-create-form.tsx is missing several validated fields
from BountyFormValues, so include deadline, startDate, endDate, and milestones
when building the CreateBountyInput for createBounty. Also update the
githubIssueUrl handling in onSubmit so non-fixed bounty flows still satisfy the
CreateBountyInput contract instead of sending an empty string; use the existing
form values and related helpers like parseGithubIssueNumber and
reward/bountyWindow fields to keep the submission aligned with the review step.

Comment on lines +424 to +425
<SelectItem value="">None</SelectItem>
{mockProjects.map(proj => (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

wc -l components/bounty/bounty-create-form.tsx
sed -n '380,520p' components/bounty/bounty-create-form.tsx

rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' '\bSelect(Item|Trigger|Content|Value)?\b' components | head -n 200

fd -a 'select*.tsx' components
fd -a '*select*' components | head -n 50

Repository: boundlessfi/bounties

Length of output: 23631


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the Select wrapper used by the bounty form
wc -l components/ui/select.tsx
sed -n '1,240p' components/ui/select.tsx

# Inspect the form defaults and submit mapping around the bounty form
rg -n 'projectId|bountyWindowId|onSubmit|handleSubmit|undefined|__none__|SelectItem value=""' components/bounty/bounty-create-form.tsx
sed -n '1,220p' components/bounty/bounty-create-form.tsx
sed -n '520,805p' components/bounty/bounty-create-form.tsx

Repository: boundlessfi/bounties

Length of output: 28137


🌐 Web query:

Radix UI Select Item value empty string non-empty string reserved cleared state documentation

💡 Result:

As of June 2026, Radix UI Select supports using an empty string as a value for Select.Item to allow for clearing a selection [1][2][3]. Historically, Radix UI restricted the use of empty string values in Select.Item to prevent conflicts and ensure reliable state management [4][5]. However, following community feedback regarding the inability to easily clear or reset selections, updates were implemented in June 2026 to resolve this [1][2][3]. Key behaviors as of the current date include: 1. Clearable Selections: You can now include a Select.Item with value="" in your menu [1][2]. Selecting this item will reset the Select component, effectively clearing the chosen value and reverting the display to the placeholder [2]. 2. Form Submission: When the Select is used within a form, selecting an item with value="" ensures the underlying hidden input correctly submits an empty string rather than defaulting to the first available option [6][2]. 3. Controlled State: The component correctly handles transitions between a selected value and the placeholder state when the component is controlled, resolving previous issues where resetting to an empty value failed to trigger the placeholder display [7][2]. If you are encountering issues where the empty string is not behaving as expected, ensure you are using the latest version of @radix-ui/react-select, as these improvements were merged and released in June 2026 [1][7][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the Radix Select version used by this repo
rg -n '"`@radix-ui/react-select`"|`@radix-ui/react-select`' package.json pnpm-lock.yaml yarn.lock package-lock.json

Repository: boundlessfi/bounties

Length of output: 1133


🌐 Web query:

@radix-ui/react-select 2.2.6 empty string Select.Item changelog

💡 Result:

In @radix-ui/react-select version 2.2.6, the use of an empty string as a value for a Select.Item is not supported [1][2]. Historically, Radix UI explicitly prevented the use of an empty string ("") as a value for Select.Item [1][3]. The library reserved the empty string internally to represent a "cleared" state that returns the component to its placeholder [1][3]. Attempting to use <Select.Item value="" /> in version 2.2.6 would trigger an error stating that the value cannot be an empty string, as it is reserved to reset the selection [1][2]. Support for using an empty string as a valid Select.Item value for clearing selections was introduced in a later update (merged in PR #3960 on June 11, 2026) [4], which post-dates the 2.2.6 release [5]. Users requiring this functionality in version 2.2.6 typically had to manage the state via controlled props, setting the Select value to an empty string programmatically rather than via an item selection [3].

Citations:


Replace the empty-string “None” options with a sentinel (components/bounty/bounty-create-form.tsx:424, 492). @radix-ui/react-select@2.2.6 rejects <SelectItem value="">, so these items can fail at runtime; use a non-empty sentinel and map it back to undefined on submit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/bounty/bounty-create-form.tsx` around lines 424 - 425, The
SelectItem values for the “None” options are using an empty string, which Radix
Select rejects at runtime. Update the affected select fields in
bounty-create-form by replacing the empty-string option with a non-empty
sentinel value, and make the form logic map that sentinel back to undefined when
building/submitting the payload. Use the existing select handlers and submit
mapping in bounty-create-form to keep the UI behavior unchanged while avoiding
invalid SelectItem values.

Comment on lines +26 to +41
jest.mock("@/lib/graphql/generated", () => ({
useCreateBountyMutation: (options: {
onSuccess: (data: unknown) => void;
onError: (error: unknown) => void;
}) => {
capturedOnSuccess = options.onSuccess;
capturedOnError = options.onError;
return {
mutate: mockMutate,
mutateAsync: mockMutateAsync,
isPending: false,
isError: false,
isSuccess: false,
};
},
}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focus on the exact test file and any nearby helpers.
sed -n '1,220p' hooks/__tests__/use-create-bounty.test.tsx

Repository: boundlessfi/bounties

Length of output: 6453


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the generated GraphQL module to see whether BountyType is a runtime export.
for f in $(git ls-files 'lib/graphql/generated*' 'src/lib/graphql/generated*'); do
  echo "--- $f ---"
  rg -n 'BountyType|useCreateBountyMutation|export .*BountyType|enum BountyType' "$f" || true
done

Repository: boundlessfi/bounties

Length of output: 403


Preserve BountyType in the GraphQL mock. This mock replaces @/lib/graphql/generated entirely, so MOCK_INPUT.type = BountyType.FixedPrice will fail when the test file loads. Spread jest.requireActual("@/lib/graphql/generated") into the mock and override only useCreateBountyMutation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/__tests__/use-create-bounty.test.tsx` around lines 26 - 41, The GraphQL
mock for useCreateBountyMutation is replacing the entire `@/lib/graphql/generated`
module and dropping BountyType, which breaks MOCK_INPUT.type usage in
use-create-bounty.test.tsx. Update the jest.mock for `@/lib/graphql/generated` to
include the real module via jest.requireActual and override only
useCreateBountyMutation, keeping BountyType and other exported symbols available
to the test.

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe.skip("useCreateBounty", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Enable this suite before merge. describe.skip leaves the new hook path untested, so the success/error/cache invalidation cases in this PR never run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/__tests__/use-create-bounty.test.tsx` at line 87, The useCreateBounty
test suite is currently disabled with describe.skip, leaving the new hook
behavior unverified. Re-enable the suite in use-create-bounty.test.tsx by
removing the skip so the existing success, error, and cache invalidation tests
for useCreateBounty actually run before merge.

@Benjtalkshow

Copy link
Copy Markdown
Contributor

@Danitello123
There is a conflict

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.

Feature: Sponsor Bounty Creation Flow

2 participants