Issue 181 sponsor bounty creation#312
Conversation
- 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
|
@Danitello123 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughAdds a multi-step bounty creation flow: a new Bounty Creation Flow
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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 Warning |
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
app/bounty/create/page.tsxcomponents/bounty/bounty-create-form.tsxcomponents/bounty/forms/budget-input.tsxcomponents/bounty/forms/schemas.tscomponents/global-navbar.tsxhooks/__tests__/use-create-bounty.test.tsxhooks/use-create-bounty.ts
| // 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" } | ||
| ]; |
There was a problem hiding this comment.
🗄️ 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.
| reward: { | ||
| amount: undefined as any, | ||
| asset: "USDC", | ||
| }, |
There was a problem hiding this comment.
📐 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
| 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, | ||
| }; |
There was a problem hiding this comment.
🗄️ 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.
| <SelectItem value="">None</SelectItem> | ||
| {mockProjects.map(proj => ( |
There was a problem hiding this comment.
🩺 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 50Repository: 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.tsxRepository: 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:
- 1: select: Allow empty select item value for clearing selection radix-ui/primitives#3960
- 2: https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md
- 3: radix-ui/primitives@2351d4a
- 4: Select.Item value not assignable to empty string radix-ui/primitives#3390
- 5: User is no longer able to reset optional
<Select/>value radix-ui/primitives#2706 - 6: [Select] Value of
<select>element is never empty when controlling aSelectwithin a form radix-ui/primitives#3521 - 7: Select: Fix placeholder reset radix-ui/primitives#3910
🏁 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.jsonRepository: 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:
- 1: Select.Item value not assignable to empty string radix-ui/primitives#3390
- 2: Select does not allow to add item with empty value. Actually it should. And select trigger is also not kept. radix-ui/primitives#3617
- 3: [Select] Use
""valueto reset to placeholder radix-ui/primitives#2174 - 4: select: Allow empty select item value for clearing selection radix-ui/primitives#3960
- 5: https://npmx.dev/package/@radix-ui/react-select/v/2.2.6
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.
| 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, | ||
| }; | ||
| }, | ||
| })); |
There was a problem hiding this comment.
🎯 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.tsxRepository: 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
doneRepository: 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", () => { |
There was a problem hiding this comment.
📐 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.
|
@Danitello123 |
closes #181
Summary by CodeRabbit
New Features
Bug Fixes