feat: Bounty Hunt 2 Implementation & Playwright tests - #245
Conversation
- Moved all mock data to lib/mock/ - Exported factories for building fixtures - Cleaned up ProjectCard and app/discover types - Deleted old lib/mock-*.ts files
… search - Move mock data into `lib/mock` directory using factory patterns - Extract `SidebarCTA` and `MobileCTA` into isolated components via `useBountyCTAState` hook - Enhance `search-command` to query cached bounties and fuzzy-search projects and pages - Verified no type or lint errors remaining
…unties - Add useAdvanceMilestone, useRemoveFromSlot, useReleaseMilestonePayment hooks - Wire Advance, Remove, Release Payment, Message, View Submissions actions - Optimistic cache updates for advance and remove operations - Toast feedback for all actions (success/error/info) - Add aria-labels to icon-only buttons for accessibility - Remove all [Coming soon] stubs from button text and tooltips - Pass bountyId prop from bounty-detail-client to dashboard Closes #205
… panel - Add useRequestRevisions hook with optimistic status update - Replace Coming Soon placeholder with interactive revision form - Textarea for reviewer feedback with cancel/submit controls - Toast notifications for success/error states - Surface latest revision feedback on contributor submit work panel - Show submit panel for REVISION_REQUESTED status - Button text adapts: 'Submit' vs 'Resubmit' based on revision state Closes #202
- Extended user session type with role: 'sponsor' | 'contributor' - Added 'Switch to Sponsor' toggle on /settings - Gated Create Bounty link in navbar to only show for sponsors - Created /bounty/create page that redirects non-sponsors Closes #207
- Added useRaiseDispute mutation - Created DisputeDialog with validation for reason and description - Wired dialog into mobile and sidebar CTAs - Replaced 'Coming Soon' placeholder with fully functional redirect Closes #203
- Added 'latestRevisionFeedback' to Bounty type in types/bounty.ts - Replaced scattered 'as any' and verbose casts in hooks/use-bounty-application.ts with typed BountyQuery intersection - Cleaned up ad-hoc cast for latestRevisionFeedback in bounty-detail-client.tsx Closes #211
… dev tools button
|
@Ishant5436 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis PR consolidates bounty platform infrastructure and implements major application workflows. It reorganizes mock data into a centralized directory with factory helpers, consolidates skeleton components into a shared UI module, introduces user role-based access control (sponsor/contributor), implements a complete 3-step bounty creation wizard with form validation, refactors bounty detail CTA logic into reusable hooks and split components, adds new mutation workflows for managing applicants and revisions, restructures the notifications system with grouped/dated display, enhances multi-category search, and adds E2E test coverage for the bounty creation flow. ChangesBounty Platform Feature Consolidation & Application Workflows
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (4)
components/global-navbar.tsx (1)
33-36: 💤 Low valueConsider improving type safety at the auth client level.
The double type assertion (
as Record<string, unknown>→as string | undefined) works defensively but suggests thatsession.userdoesn't have a properly typedrolefield. If the auth client's session type can be extended to includerole, it would eliminate the need for these assertions.♻️ Potential improvement
If you control the auth client types, consider extending the user type:
// In auth-client.ts or types interface User { id: string; name: string; email: string; image: string | null; walletAddress: string; role?: string; // Add this }Then the code simplifies to:
- const userRole = (session?.user as Record<string, unknown> | undefined) - ?.role as string | undefined; + const userRole = session?.user?.role;🤖 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/global-navbar.tsx` around lines 33 - 36, The code is using double type assertions around session.user to access role (authClient.useSession -> session.user -> userRole -> isSponsor); instead, update the auth client's user/session types to include an optional role property (e.g., extend the User interface used by authClient) so that session.user.role is strongly typed and you can remove the casts, then update components/global-navbar.tsx to read session?.user?.role directly and compute isSponsor from that typed value.e2e/bounty-creation.spec.ts (2)
68-78: 💤 Low valueConsider making cookie domain configurable for different test environments.
The cookie domain is hardcoded to
"localhost". In CI/CD environments or when using different base URLs, this might need to be dynamic. Consider using Playwright'sbaseURLconfiguration or an environment variable.♻️ Suggested improvement
+const baseURL = new URL(page.url()); await page.context().addCookies([ { name: "boundless_auth.session_token", value: "fake-sponsor-token", - domain: "localhost", + domain: baseURL.hostname, path: "/", httpOnly: false, secure: false, sameSite: "Lax", }, ]);🤖 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 `@e2e/bounty-creation.spec.ts` around lines 68 - 78, The test hardcodes the cookie domain ("localhost") when calling page.context().addCookies for "boundless_auth.session_token", which breaks in other environments; change the domain to be computed from the test runtime configuration (e.g., read Playwright's baseURL or an environment variable like TEST_BASE_URL/COOKIE_DOMAIN) and use that value when constructing the cookie before calling page.context().addCookies so the spec works across local, CI, and alternate hosts.
92-92: 💤 Low valueConsider extracting repeated selector to reduce duplication.
The "Next" button selector appears three times. While not a major issue, extracting it to a constant would make future updates easier and more maintainable.
♻️ Suggested refactor
+const NEXT_BUTTON = { name: "Next", exact: true }; + test("validation path: prevents advancing with empty title", async ({ page, }) => { await page.goto("/bounty/create"); // Attempt to proceed without filling anything - await page.getByRole("button", { name: "Next", exact: true }).click(); + await page.getByRole("button", NEXT_BUTTON).click(); // Should stay on step 1 and show an error for title await expect(page.getByText(/Title is required/i)).toBeVisible();Also applies to: 122-122, 132-132
🤖 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 `@e2e/bounty-creation.spec.ts` at line 92, The "Next" button selector is duplicated; extract it into a single reusable constant (e.g., const nextButton = page.getByRole("button", { name: "Next", exact: true })) at the top of the test or inside the relevant describe/it block and replace each page.getByRole(...).click() call with nextButton.click(); update all occurrences referenced in this spec (the three places calling page.getByRole("button", { name: "Next", exact: true }).click()) to use the constant to reduce duplication and ease future updates.hooks/use-bounty-search.ts (1)
146-148: ⚡ Quick winCache lookup key matches
bountyKeysroot; only consider de-duplication
hooks/use-bounty-search.tsusesqueryClient.getQueriesData({ queryKey: ["Bounties"] }), which aligns withbountyKeys’ list roots (bountyKeys.allListKeys[0]is["Bounties"], and list keys are["Bounties", ...]). This shouldn’t miss cached entries due to casing/root mismatch.
Optional: reusebountyKeys.allListKeys[0](or the key factory) instead of hard-coding["Bounties"]to prevent future drift.🤖 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/use-bounty-search.ts` around lines 146 - 148, The cache lookup using queryClient.getQueriesData({ queryKey: ["Bounties"] }) in hooks/use-bounty-search.ts is correct relative to bountyKeys (so it won't miss entries), but to avoid future drift and make intent explicit replace the hard-coded key with the canonical key from bountyKeys (e.g., use bountyKeys.allListKeys[0] or the bountyKeys list key factory) when constructing cachedQueries via queryClient.getQueriesData, and ensure the logic around de-duplication still only considers entries under that root.
🤖 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 `@app/bounty/create/page.tsx`:
- Around line 28-31: Remove the redundant nested CardContent wrapper that
duplicates padding around the BountyCreationWizard; locate the outer CardContent
and the inner CardContent elements in the create page (around
BountyCreationWizard) and replace them with a single CardContent that directly
contains <BountyCreationWizard /> so the duplicated container/padding is
eliminated.
In `@app/bounty/create/wizard.tsx`:
- Around line 28-30: handleNextStep2 currently advances unconditionally which
allows Number(rewardAmount) to be 0 or NaN; update the handler (and the similar
handler around the other step at lines 47-50) to validate required fields before
calling setStep. Specifically, in handleNextStep2 check that rewardAmount is not
empty and that Number(rewardAmount) is a finite number > 0 (and any other
required inputs for that step are present); if validation fails, set an error
state or prevent progression and surface a user-facing error message instead of
calling setStep(3). Ensure you reference and update the same logic used in the
other next/submit handler so invalid bounty payloads cannot be submitted.
- Around line 33-57: The handleCreate network call currently assumes success;
update the fetch handling in the CreateBounty flow (the POST to "/api/graphql"
in wizard.tsx / the handleCreate function) to explicitly check res.ok and handle
GraphQL errors: after awaiting fetch, if !res.ok read and surface the response
body (or status) as an error, then parse JSON, check for a top-level errors
array (response.errors) and handle/log those before using data.createBounty;
only call router.push when data?.createBounty?.id exists and otherwise set/throw
a clear error or update component error state so failures aren’t silent. Ensure
numeric conversion of rewardAmount remains and include contextual messages when
logging/reporting failures.
In `@components/bounty-detail/bounty-detail-client.tsx`:
- Around line 201-204: The current prop assignment
contributorAddress={walletAddress || session.user.id} can pass a non-wallet
identifier into on-chain flows; change usages so MilestoneSubmissionCard only
receives a real wallet address (pass contributorAddress={walletAddress} or
undefined) and handle the missing-wallet case by disabling/hiding the component
or showing a connect-wallet prompt; update MilestoneSubmissionCard (and any
downstream functions that assume a contributorAddress, e.g., onSubmit handlers
or contract call helpers) to explicitly validate/abort if contributorAddress is
falsy rather than accepting session.user.id.
- Around line 153-155: The current gating expression in bounty-detail-client.tsx
wrongly treats every non-creator as allowed to submit while a bounty is
IN_PROGRESS; remove the fallback check "(!isCreator && bounty.status ===
'IN_PROGRESS')" and tighten the condition so only the actual assignee or someone
who has already submitted can access the submit flow. Update the expression that
uses bounty?.assignedContributorId, bounty?.submissions, session?.user?.id and
isCreator so it only returns true when bounty?.assignedContributorId ===
session?.user?.id OR bounty?.submissions?.some(s => s.submittedBy ===
session?.user?.id); do not rely on bounty.status alone to grant submit access.
In `@components/bounty-detail/milestone-submission-card.tsx`:
- Around line 52-59: The code trims workCid for validation but then passes the
original untrimmed workCid to submitWork; change the payload to use the
normalized value by assigning const normalizedCid = workCid.trim() (or reuse the
trimmed variable) and pass normalizedCid as workCid in the submitWork call
(keeping bountyId and contributorAddress unchanged) so the mutation always
receives the trimmed CID.
In `@components/bounty-detail/mobile-cta.tsx`:
- Around line 112-119: The icon-only cancel buttons in the Mobile CTA component
(the Button elements rendering XCircle and the other two icon-only Buttons that
call setCancelDialogOpen) lack accessible names; add an aria-label (or a
visually hidden text alternative) such as aria-label="Cancel" (or a localized
equivalent) to each icon-only Button to provide a screen-reader-friendly name,
e.g., update the Button components that render <XCircle /> and the other
icon-only icons to include aria-label="Cancel" while preserving their onClick
handlers (setCancelDialogOpen) and existing classes.
- Around line 154-160: The onClick handler currently calls
window.open(bounty.githubIssueUrl, "_blank", "noopener,noreferrer") without
validating the dynamic URL; update the onClick for the element that references
bounty.githubIssueUrl to first validate the string using the URL constructor (or
a small validator) and ensure the protocol is either "http:" or "https:"; if
validation passes, call window.open with the same target and rel options,
otherwise do nothing (or show a safe fallback) and avoid opening
malformed/non-HTTP(S) URLs to prevent client-side security risks.
In `@components/bounty-detail/model4-maintainer-dashboard.tsx`:
- Around line 62-66: The code advances to the "nextMilestone" without handling
the case where currentMilestoneId isn't found (findIndex returns -1); update the
logic around currentIndex/currentMilestoneId in the milestone advancement block
to guard for currentIndex === -1 (or < 0) and bail out early
(return/throw/no-op/report error) instead of using index 0, only proceed to
compute nextMilestone when currentIndex >= 0 and currentIndex <
milestones.length - 1 so you don't accidentally advance to the first milestone.
In `@components/bounty-detail/sidebar-cta.tsx`:
- Around line 175-186: The Button's onClick currently calls window.open even if
bounty.githubIssueUrl is falsy; update the onClick handler (the Button with
props className, disabled={!canAct}, size="lg") to only call window.open when
both canAct and bounty.githubIssueUrl are truthy (e.g., guard with canAct &&
bounty.githubIssueUrl && window.open(...)); this prevents opening a blank tab
when githubIssueUrl is missing and keeps existing behavior when the URL exists.
In `@components/bounty/application-submit-work-panel.tsx`:
- Around line 35-42: The code trims workCid for validation but then submits the
original untrimmed value; change to compute a normalized value (e.g., const
trimmedWorkCid = workCid.trim()) and use that for both the emptiness check and
in the submitWork payload (replace workCid with the trimmedWorkCid identifier)
so the value sent by the submitWork call is the normalized string.
In `@components/bounty/submission-approval-panel.tsx`:
- Around line 79-83: The payload is sending bounty.id as submissionId (see keys
bountyId and submissionId and the value revisionFeedback.trim()), which is
incorrect; change the submissionId value to the actual submission record's
identifier (e.g., use submission.id or selectedSubmission.id instead of
bounty.id) so the revision request targets the submission entity rather than the
bounty. Ensure the variable you use exists in the component scope and update any
call sites that construct this payload in the submission-approval-panel
component.
In `@components/settings/notifications-tab.tsx`:
- Line 85: The mentions filter is dead because NotificationType (in
hooks/use-notifications.ts) never includes "mentions" and producers never emit
it; either add "mentions" to the NotificationType union and update all
notification producers (the code paths that call useNotifications or create
notification objects) to set type: "mentions", then change the filter in
components/settings/notifications-tab.tsx to compare against the typed literal
(n.type === "mentions"), or if the feature isn't implemented remove the mentions
UI/prefs and the .filter((n) => n.type === ("mentions" as string)) line; update
useNotifications, NotificationType, and any producer functions consistently so
the types and runtime values match.
In `@lib/mock/bounties.ts`:
- Around line 279-282: The factory makeMockBounty currently does a shallow clone
using {...mockBounties[0]}, which reuses nested object references and can leak
state between tests; update makeMockBounty to produce a deep copy of
mockBounties[0] (e.g., use structuredClone(mockBounties[0]) or a deep-clone
utility like lodash's cloneDeep) and then apply overrides so nested objects are
not shared—i.e., deep-clone mockBounties[0], merge/assign overrides, and return
that new object.
In `@lib/server-auth.ts`:
- Around line 94-104: The current fake-token bypass (checking sessionCookie ===
"fake-sponsor-token" or "fake-e2e-token") must be restricted to test-only
execution to avoid a production auth bypass: modify the condition around that
block to additionally require a test-only guard such as process.env.NODE_ENV ===
"test" or a dedicated flag like process.env.ENABLE_E2E_BYPASS === "true" so the
block in lib/server-auth.ts (the sessionCookie check that returns id
"e2e-tester" and role based on "fake-sponsor-token") only runs when the test
guard is present; keep the returned object unchanged but ensure the
environment/flag check is evaluated before returning the fake user.
In `@lib/services/withdrawal.ts`:
- Around line 25-30: The validation currently only checks balance; add
pre-checks to reject non-positive and fee-too-small withdrawals: in the same
validation function where amount, fee, result, and mockWalletWithAssets are used
(e.g., around the existing block checking mockWalletWithAssets.balance), first
check if amount <= 0 and set result.valid = false, push an error like "Amount
must be greater than 0" and set a blocker (e.g., invalidAmount); then check if
amount <= fee and set result.valid = false, push an error like "Amount must be
greater than fee" and set a blocker (e.g., amountNotCoveringFee). Keep these
checks before the balance check and before computing netAmount so netAmount
cannot become non-positive. Ensure the same validation logic guards the
netAmount computation later.
In `@scripts/refactor_mocks.py`:
- Around line 18-29: The script currently appends a factory block into `content`
unconditionally which causes duplicate exported factories on re-runs; before
adding the factory string (the block built using factory_name, type_name,
array_name) check whether `content` already contains the export signature (e.g.
"export const {factory_name}") or the exact factory block and skip appending if
found, or otherwise rebuild `content` from scratch to include only one factory;
ensure you still write to `dst` (the same file) and keep the existing print of
src -> dst.
---
Nitpick comments:
In `@components/global-navbar.tsx`:
- Around line 33-36: The code is using double type assertions around
session.user to access role (authClient.useSession -> session.user -> userRole
-> isSponsor); instead, update the auth client's user/session types to include
an optional role property (e.g., extend the User interface used by authClient)
so that session.user.role is strongly typed and you can remove the casts, then
update components/global-navbar.tsx to read session?.user?.role directly and
compute isSponsor from that typed value.
In `@e2e/bounty-creation.spec.ts`:
- Around line 68-78: The test hardcodes the cookie domain ("localhost") when
calling page.context().addCookies for "boundless_auth.session_token", which
breaks in other environments; change the domain to be computed from the test
runtime configuration (e.g., read Playwright's baseURL or an environment
variable like TEST_BASE_URL/COOKIE_DOMAIN) and use that value when constructing
the cookie before calling page.context().addCookies so the spec works across
local, CI, and alternate hosts.
- Line 92: The "Next" button selector is duplicated; extract it into a single
reusable constant (e.g., const nextButton = page.getByRole("button", { name:
"Next", exact: true })) at the top of the test or inside the relevant
describe/it block and replace each page.getByRole(...).click() call with
nextButton.click(); update all occurrences referenced in this spec (the three
places calling page.getByRole("button", { name: "Next", exact: true }).click())
to use the constant to reduce duplication and ease future updates.
In `@hooks/use-bounty-search.ts`:
- Around line 146-148: The cache lookup using queryClient.getQueriesData({
queryKey: ["Bounties"] }) in hooks/use-bounty-search.ts is correct relative to
bountyKeys (so it won't miss entries), but to avoid future drift and make intent
explicit replace the hard-coded key with the canonical key from bountyKeys
(e.g., use bountyKeys.allListKeys[0] or the bountyKeys list key factory) when
constructing cachedQueries via queryClient.getQueriesData, and ensure the logic
around de-duplication still only considers entries under that root.
🪄 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: ce385814-f120-4313-9a14-9c278cc83f3b
📒 Files selected for processing (57)
app/api/leaderboard/route.tsapp/api/leaderboard/top/route.tsapp/api/leaderboard/user/[userId]/route.tsapp/bounty/create/page.tsxapp/bounty/create/wizard.tsxapp/bounty/page.tsxapp/discover/page.tsxapp/projects/[id]/page.tsxapp/projects/page.tsxapp/saved/saved-client.tsxapp/wallet/page.tsxcomponents/bounty-detail/bounty-detail-bounty-detail-skeleton.tsxcomponents/bounty-detail/bounty-detail-client.tsxcomponents/bounty-detail/bounty-detail-sidebar-cta.tsxcomponents/bounty-detail/dispute-dialog.tsxcomponents/bounty-detail/milestone-submission-card.tsxcomponents/bounty-detail/mobile-cta.tsxcomponents/bounty-detail/model4-maintainer-dashboard.tsxcomponents/bounty-detail/sidebar-cta.tsxcomponents/bounty-detail/types.tscomponents/bounty-detail/use-bounty-cta-state.tscomponents/bounty/application-review-dashboard.tsxcomponents/bounty/application-submit-work-panel.tsxcomponents/bounty/bounty-card-skeleton.tsxcomponents/bounty/bounty-grid.tsxcomponents/bounty/bounty-list.tsxcomponents/bounty/submission-approval-panel.tsxcomponents/cards/project-card.tsxcomponents/global-navbar.tsxcomponents/leaderboard/leaderboard-table.tsxcomponents/mode-toggle.tsxcomponents/projects/project-bounties.tsxcomponents/search-command.tsxcomponents/settings/notifications-tab.tsxcomponents/settings/profile-tab.tsxcomponents/ui/skeleton-loaders.tsxe2e/bounty-creation.spec.tshooks/use-bounty-application.tshooks/use-bounty-search.tshooks/use-competition-join-state.tshooks/use-user-mutations.tslib/auth-client.tslib/graphql/generated.tslib/graphql/schema.graphqllib/mock-data.tslib/mock-wallet.tslib/mock/bounties.tslib/mock/index.tslib/mock/leaderboard.tslib/mock/model4.tslib/mock/projects.tslib/mock/wallet.tslib/server-auth.tslib/services/withdrawal.tslib/store.tsscripts/refactor_mocks.pytypes/bounty.ts
💤 Files with no reviewable changes (5)
- lib/mock-data.ts
- components/bounty/bounty-card-skeleton.tsx
- components/bounty-detail/bounty-detail-bounty-detail-skeleton.tsx
- lib/mock-wallet.ts
- components/bounty-detail/bounty-detail-sidebar-cta.tsx
| const handleNextStep2 = () => { | ||
| setStep(3); | ||
| }; |
There was a problem hiding this comment.
Add required-field validation before advancing/submitting.
Step 2 advances unconditionally, and Number(rewardAmount) can become 0 (empty input) or NaN. This can submit invalid bounty payloads.
Proposed fix
const handleNextStep2 = () => {
+ const parsedReward = Number(rewardAmount);
+ if (
+ !bountyType ||
+ !currency ||
+ !deadline ||
+ !Number.isFinite(parsedReward) ||
+ parsedReward <= 0
+ ) {
+ setError("Please complete all required reward fields with valid values");
+ return;
+ }
+ setError("");
setStep(3);
};Also applies to: 47-50
🤖 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 `@app/bounty/create/wizard.tsx` around lines 28 - 30, handleNextStep2 currently
advances unconditionally which allows Number(rewardAmount) to be 0 or NaN;
update the handler (and the similar handler around the other step at lines
47-50) to validate required fields before calling setStep. Specifically, in
handleNextStep2 check that rewardAmount is not empty and that
Number(rewardAmount) is a finite number > 0 (and any other required inputs for
that step are present); if validation fails, set an error state or prevent
progression and surface a user-facing error message instead of calling
setStep(3). Ensure you reference and update the same logic used in the other
next/submit handler so invalid bounty payloads cannot be submitted.
| const res = await fetch("/api/graphql", { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify({ | ||
| operationName: "CreateBounty", | ||
| variables: { | ||
| input: { | ||
| title, | ||
| description, | ||
| organization, | ||
| githubUrl, | ||
| bountyType, | ||
| rewardAmount: Number(rewardAmount), | ||
| currency, | ||
| deadline, | ||
| }, | ||
| }, | ||
| }), | ||
| }); | ||
| const { data } = await res.json(); | ||
| if (data?.createBounty?.id) { | ||
| router.push(`/bounty/${data.createBounty.id}`); | ||
| } |
There was a problem hiding this comment.
Handle non-OK and GraphQL error responses explicitly.
handleCreate assumes success and can fail silently on network/GraphQL errors.
Proposed fix
const handleCreate = async () => {
- const res = await fetch("/api/graphql", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- operationName: "CreateBounty",
- variables: {
- input: {
- title,
- description,
- organization,
- githubUrl,
- bountyType,
- rewardAmount: Number(rewardAmount),
- currency,
- deadline,
- },
- },
- }),
- });
- const { data } = await res.json();
- if (data?.createBounty?.id) {
- router.push(`/bounty/${data.createBounty.id}`);
- }
+ try {
+ const res = await fetch("/api/graphql", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ operationName: "CreateBounty",
+ variables: {
+ input: {
+ title,
+ description,
+ organization,
+ githubUrl,
+ bountyType,
+ rewardAmount: Number(rewardAmount),
+ currency,
+ deadline,
+ },
+ },
+ }),
+ });
+
+ const payload = await res.json();
+ if (!res.ok || payload?.errors?.length) {
+ setError(payload?.errors?.[0]?.message ?? "Failed to create bounty");
+ return;
+ }
+
+ const id = payload?.data?.createBounty?.id;
+ if (!id) {
+ setError("Failed to create bounty");
+ return;
+ }
+ router.push(`/bounty/${id}`);
+ } catch {
+ setError("Failed to create bounty");
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const res = await fetch("/api/graphql", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify({ | |
| operationName: "CreateBounty", | |
| variables: { | |
| input: { | |
| title, | |
| description, | |
| organization, | |
| githubUrl, | |
| bountyType, | |
| rewardAmount: Number(rewardAmount), | |
| currency, | |
| deadline, | |
| }, | |
| }, | |
| }), | |
| }); | |
| const { data } = await res.json(); | |
| if (data?.createBounty?.id) { | |
| router.push(`/bounty/${data.createBounty.id}`); | |
| } | |
| try { | |
| const res = await fetch("/api/graphql", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| operationName: "CreateBounty", | |
| variables: { | |
| input: { | |
| title, | |
| description, | |
| organization, | |
| githubUrl, | |
| bountyType, | |
| rewardAmount: Number(rewardAmount), | |
| currency, | |
| deadline, | |
| }, | |
| }, | |
| }), | |
| }); | |
| const payload = await res.json(); | |
| if (!res.ok || payload?.errors?.length) { | |
| setError(payload?.errors?.[0]?.message ?? "Failed to create bounty"); | |
| return; | |
| } | |
| const id = payload?.data?.createBounty?.id; | |
| if (!id) { | |
| setError("Failed to create bounty"); | |
| return; | |
| } | |
| router.push(`/bounty/${id}`); | |
| } catch { | |
| setError("Failed to create bounty"); | |
| } |
🤖 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 `@app/bounty/create/wizard.tsx` around lines 33 - 57, The handleCreate network
call currently assumes success; update the fetch handling in the CreateBounty
flow (the POST to "/api/graphql" in wizard.tsx / the handleCreate function) to
explicitly check res.ok and handle GraphQL errors: after awaiting fetch, if
!res.ok read and surface the response body (or status) as an error, then parse
JSON, check for a top-level errors array (response.errors) and handle/log those
before using data.createBounty; only call router.push when
data?.createBounty?.id exists and otherwise set/throw a clear error or update
component error state so failures aren’t silent. Ensure numeric conversion of
rewardAmount remains and include contextual messages when logging/reporting
failures.
| bounty?.assignedContributorId === session?.user?.id || | ||
| bounty?.submissions?.some((s) => s.submittedBy === session?.user?.id) || | ||
| (!isCreator && bounty.status === "IN_PROGRESS"); |
There was a problem hiding this comment.
Tighten assigned-applicant gating to avoid exposing submit flow to unrelated users.
The fallback (!isCreator && bounty.status === "IN_PROGRESS") marks every non-creator as assigned while in progress, which can surface submit-work UI to users who were never assigned.
Proposed fix
- const isAssignedApplicant =
- bounty?.assignedContributorId === session?.user?.id ||
- bounty?.submissions?.some((s) => s.submittedBy === session?.user?.id) ||
- (!isCreator && bounty.status === "IN_PROGRESS");
+ const isAssignedApplicant =
+ bounty?.assignedContributorId === session?.user?.id ||
+ bounty?.submissions?.some((s) => s.submittedBy === session?.user?.id);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| bounty?.assignedContributorId === session?.user?.id || | |
| bounty?.submissions?.some((s) => s.submittedBy === session?.user?.id) || | |
| (!isCreator && bounty.status === "IN_PROGRESS"); | |
| bounty?.assignedContributorId === session?.user?.id || | |
| bounty?.submissions?.some((s) => s.submittedBy === session?.user?.id) |
🤖 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-detail/bounty-detail-client.tsx` around lines 153 - 155,
The current gating expression in bounty-detail-client.tsx wrongly treats every
non-creator as allowed to submit while a bounty is IN_PROGRESS; remove the
fallback check "(!isCreator && bounty.status === 'IN_PROGRESS')" and tighten the
condition so only the actual assignee or someone who has already submitted can
access the submit flow. Update the expression that uses
bounty?.assignedContributorId, bounty?.submissions, session?.user?.id and
isCreator so it only returns true when bounty?.assignedContributorId ===
session?.user?.id OR bounty?.submissions?.some(s => s.submittedBy ===
session?.user?.id); do not rely on bounty.status alone to grant submit access.
| <MilestoneSubmissionCard | ||
| bountyId={bounty.id} | ||
| contributorAddress={walletAddress || session.user.id} | ||
| milestones={milestones} |
There was a problem hiding this comment.
Avoid falling back to session.user.id for on-chain contributor address.
contributorAddress={walletAddress || session.user.id} can pass a non-wallet identifier into downstream contract-related flows.
Proposed fix
const myProgress = contributorProgress.find(
(c) => c.userId === session.user.id,
);
- if (!myProgress) return null;
+ if (!myProgress || !walletAddress) return null;
return (
<MilestoneSubmissionCard
bountyId={bounty.id}
- contributorAddress={walletAddress || session.user.id}
+ contributorAddress={walletAddress}
milestones={milestones}
contributorProgress={myProgress}
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <MilestoneSubmissionCard | |
| bountyId={bounty.id} | |
| contributorAddress={walletAddress || session.user.id} | |
| milestones={milestones} | |
| const myProgress = contributorProgress.find( | |
| (c) => c.userId === session.user.id, | |
| ); | |
| if (!myProgress || !walletAddress) return null; | |
| return ( | |
| <MilestoneSubmissionCard | |
| bountyId={bounty.id} | |
| contributorAddress={walletAddress} | |
| milestones={milestones} | |
| contributorProgress={myProgress} | |
| /> |
🤖 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-detail/bounty-detail-client.tsx` around lines 201 - 204,
The current prop assignment contributorAddress={walletAddress ||
session.user.id} can pass a non-wallet identifier into on-chain flows; change
usages so MilestoneSubmissionCard only receives a real wallet address (pass
contributorAddress={walletAddress} or undefined) and handle the missing-wallet
case by disabling/hiding the component or showing a connect-wallet prompt;
update MilestoneSubmissionCard (and any downstream functions that assume a
contributorAddress, e.g., onSubmit handlers or contract call helpers) to
explicitly validate/abort if contributorAddress is falsy rather than accepting
session.user.id.
| .filter((n) => n.type === "submission-reviewed") | ||
| .slice(0, 3); | ||
| const mentions = notifications | ||
| .filter((n) => n.type === ("mentions" as string)) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect notification type source of truth and usage sites.
fd -i "use-notifications.ts" --exec sed -n '1,260p' {}
rg -nP --type=ts --type=tsx '\b(type|notificationType)\b.*(mention|mentions|new-application|submission-reviewed|bounty-updated|saved-bounty-updated)'Repository: boundlessfi/bounties
Length of output: 7825
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the exact line in the target file
fd -a "notifications-tab.tsx" --exec sh -c 'echo "---- $1"; nl -ba "$1" | sed -n "70,110p"' sh {}
# 2) Find the mentions discriminator and where it’s defined
rg -n --hidden --no-ignore-vcs --type-add 'tsx:*.tsx' --type tsx --type ts \
'mentions"\s*as\s*string|mentions"\s*as\s*string|\btype\b.*mentions|\bnotificationType\b.*mentions' .
# 3) Locate the notification type source-of-truth in the codebase
rg -n --hidden --no-ignore-vcs --type-add 'tsx:*.tsx' --type tsx --type ts \
'export (type|enum)\s+Notification(Type|TypeName)?|NotificationType\s*=|notification type|discriminator' \
components lib src . 2>/dev/null || true
# 4) Re-run search in use-notifications.ts for mention/mentions
fd -i "use-notifications.ts" --exec sh -c 'echo "---- $1"; rg -n --type ts --type tsx "\b(type|notificationType)\b.*mentions" "$1" || true' {}Repository: boundlessfi/bounties
Length of output: 12384
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Show the exact code around the flagged line
fd -a "notifications-tab.tsx" --exec sh -c 'echo "---- $1"; sed -n "70,100p" "$1"' sh {}
# 2) Inspect NotificationType in the notifications hook
fd -a "use-notifications.ts" --exec sh -c 'echo "---- $1"; sed -n "1,120p" "$1"' sh {}
# 3) Find all mentions discriminator usage sites (typed or not)
rg -n --hidden --no-ignore-vcs --type ts --type tsx '"mentions"' .
rg -n --hidden --no-ignore-vcs --type ts --type tsx 'mentions"\s+as\s+string|mentions"\s+as\s+const|===\s*["'\'']mentions["'\'']' .Repository: boundlessfi/bounties
Length of output: 4479
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for any "mentions" notification type production/typing
rg -n --hidden --no-ignore-vcs --type ts --type tsx --type graphql --type md '"mentions"' .
# Inspect remainder of use-notifications.ts for mention events
fd -a "use-notifications.ts" --exec sh -c 'echo "---- $1"; sed -n "120,260p" "$1"; sed -n "260,520p" "$1"' sh {}
# Inspect imports/types in notifications-tab.tsx to see what NotificationType/NotificationItem is used there
fd -a "notifications-tab.tsx" --exec sh -c 'echo "---- $1"; sed -n "1,130p" "$1"' sh {}Repository: boundlessfi/bounties
Length of output: 92
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the top of the notifications tab to see the types it uses
fd -a "notifications-tab.tsx" --exec sh -c 'echo "---- $1"; sed -n "1,140p" "$1"' sh {}
# 2) Search for mentions/mention production and any discriminator keys
rg -n --hidden --no-ignore-vcs --glob '*.{ts,tsx}' '"mentions"|\bmentions\b|\bmention\b' .
# 3) Search for any mention GraphQL subscriptions or event keys
rg -n --hidden --no-ignore-vcs --glob '*.{ts,tsx}' 'MENTION|Mention|mention|mentions' .
# 4) Inspect more of use-notifications.ts for mention-related logic
fd -a "use-notifications.ts" --exec sh -c 'echo "---- $1"; sed -n "120,320p" "$1"; sed -n "320,620p" "$1"' sh {}Repository: boundlessfi/bounties
Length of output: 12414
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for any actual mention notification discriminator usage in code (not just UI prefs)
rg -n --hidden --no-ignore-vcs --glob '*.{ts,tsx}' 'type:\s*["'\'']mentions["'\'']|"\s*mentions\s*"\s*as\s*string|notification(s)?\s*.*mentions|ON_.*MENTION|MENTION_' .
# Search for any mentions/mention GraphQL subscription constants in code (including lib/graphql/subscriptions)
rg -n --hidden --no-ignore-vcs --glob '*.{ts,tsx}' 'MENTION|Mention|mentions' lib hooks components .
# Locate all NotificationType occurrences to see whether union is missing mentions
rg -n --hidden --no-ignore-vcs --glob '*.{ts,tsx}' '\bNotificationType\b' .Repository: boundlessfi/bounties
Length of output: 1984
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '230,310p' components/settings/notifications-tab.tsxRepository: boundlessfi/bounties
Length of output: 2330
Fix mentions filtering: NotificationType doesn’t include "mentions"
components/settings/notifications-tab.tsxfilters withn.type === ("mentions" as string), buthooks/use-notifications.ts’sNotificationTypeunion does not define"mentions", anduseNotificationsnever produces mention notifications—so the mentions digest will never match (except for any stale/unvalidated localStorage contents).- Add
"mentions"toNotificationTypeand ensure the producer(s) settype: "mentions"(then compare with the typed literal), or remove the mentions UI/prefs if the feature isn’t implemented.
// components/settings/notifications-tab.tsx:85
.filter((n) => n.type === ("mentions" as string))🤖 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/settings/notifications-tab.tsx` at line 85, The mentions filter is
dead because NotificationType (in hooks/use-notifications.ts) never includes
"mentions" and producers never emit it; either add "mentions" to the
NotificationType union and update all notification producers (the code paths
that call useNotifications or create notification objects) to set type:
"mentions", then change the filter in components/settings/notifications-tab.tsx
to compare against the typed literal (n.type === "mentions"), or if the feature
isn't implemented remove the mentions UI/prefs and the .filter((n) => n.type ===
("mentions" as string)) line; update useNotifications, NotificationType, and any
producer functions consistently so the types and runtime values match.
| export const makeMockBounty = (overrides?: Partial<Bounty>): Bounty => ({ | ||
| ...mockBounties[0], | ||
| ...overrides, | ||
| }); |
There was a problem hiding this comment.
Avoid shared nested references in makeMockBounty.
This factory is a shallow clone; nested objects from mockBounties[0] are reused and can cause cross-test/state mutation leaks.
Proposed fix
-export const makeMockBounty = (overrides?: Partial<Bounty>): Bounty => ({
- ...mockBounties[0],
- ...overrides,
-});
+export const makeMockBounty = (overrides?: Partial<Bounty>): Bounty => ({
+ ...structuredClone(mockBounties[0]),
+ ...overrides,
+});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const makeMockBounty = (overrides?: Partial<Bounty>): Bounty => ({ | |
| ...mockBounties[0], | |
| ...overrides, | |
| }); | |
| export const makeMockBounty = (overrides?: Partial<Bounty>): Bounty => ({ | |
| ...structuredClone(mockBounties[0]), | |
| ...overrides, | |
| }); |
🤖 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 `@lib/mock/bounties.ts` around lines 279 - 282, The factory makeMockBounty
currently does a shallow clone using {...mockBounties[0]}, which reuses nested
object references and can leak state between tests; update makeMockBounty to
produce a deep copy of mockBounties[0] (e.g., use
structuredClone(mockBounties[0]) or a deep-clone utility like lodash's
cloneDeep) and then apply overrides so nested objects are not shared—i.e.,
deep-clone mockBounties[0], merge/assign overrides, and return that new object.
| // E2E Test Bypass for Server Components | ||
| if ( | ||
| sessionCookie === "fake-sponsor-token" || | ||
| sessionCookie === "fake-e2e-token" | ||
| ) { | ||
| return { | ||
| id: "e2e-tester", | ||
| name: "E2E Tester", | ||
| role: sessionCookie === "fake-sponsor-token" ? "sponsor" : "contributor", | ||
| }; | ||
| } |
There was a problem hiding this comment.
Gate the fake-token auth bypass to test-only execution.
Any client able to set the fake cookie can currently obtain an authenticated user (including sponsor role), which is a production auth bypass risk.
Proposed fix
- // E2E Test Bypass for Server Components
- if (
- sessionCookie === "fake-sponsor-token" ||
- sessionCookie === "fake-e2e-token"
- ) {
+ // E2E test bypass: explicitly gated
+ const isFakeE2EToken =
+ sessionCookie === "fake-sponsor-token" ||
+ sessionCookie === "fake-e2e-token";
+ const allowE2EBypass =
+ process.env.NODE_ENV === "test" ||
+ process.env.ENABLE_E2E_AUTH_BYPASS === "true";
+
+ if (isFakeE2EToken && allowE2EBypass) {
return {
id: "e2e-tester",
name: "E2E Tester",
role: sessionCookie === "fake-sponsor-token" ? "sponsor" : "contributor",
};
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // E2E Test Bypass for Server Components | |
| if ( | |
| sessionCookie === "fake-sponsor-token" || | |
| sessionCookie === "fake-e2e-token" | |
| ) { | |
| return { | |
| id: "e2e-tester", | |
| name: "E2E Tester", | |
| role: sessionCookie === "fake-sponsor-token" ? "sponsor" : "contributor", | |
| }; | |
| } | |
| // E2E test bypass: explicitly gated | |
| const isFakeE2EToken = | |
| sessionCookie === "fake-sponsor-token" || | |
| sessionCookie === "fake-e2e-token"; | |
| const allowE2EBypass = | |
| process.env.NODE_ENV === "test" || | |
| process.env.ENABLE_E2E_AUTH_BYPASS === "true"; | |
| if (isFakeE2EToken && allowE2EBypass) { | |
| return { | |
| id: "e2e-tester", | |
| name: "E2E Tester", | |
| role: sessionCookie === "fake-sponsor-token" ? "sponsor" : "contributor", | |
| }; | |
| } |
🤖 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 `@lib/server-auth.ts` around lines 94 - 104, The current fake-token bypass
(checking sessionCookie === "fake-sponsor-token" or "fake-e2e-token") must be
restricted to test-only execution to avoid a production auth bypass: modify the
condition around that block to additionally require a test-only guard such as
process.env.NODE_ENV === "test" or a dedicated flag like
process.env.ENABLE_E2E_BYPASS === "true" so the block in lib/server-auth.ts (the
sessionCookie check that returns id "e2e-tester" and role based on
"fake-sponsor-token") only runs when the test guard is present; keep the
returned object unchanged but ensure the environment/flag check is evaluated
before returning the fake user.
| // Check balance | ||
| if (amount > mockWalletWithAssets.balance) { | ||
| result.valid = false; | ||
| result.errors.push("Insufficient balance"); | ||
| result.blockers.insufficientBalance = true; | ||
| } |
There was a problem hiding this comment.
Block non-positive and fee-below amounts during validation.
Line 26 only checks balance. A zero/negative amount (or amount ≤ fee) can still pass and create invalid netAmount at Line 93.
Proposed fix
export class WithdrawalService {
+ private static readonly WITHDRAWAL_FEE = 2.5;
+
static async validate(
userId: string,
amount: number,
ip: string,
): Promise<WithdrawalValidationResult> {
@@
+ if (!Number.isFinite(amount) || amount <= 0) {
+ result.valid = false;
+ result.errors.push("Amount must be greater than zero");
+ result.blockers.invalidAmount = true;
+ return result;
+ }
+
+ if (amount <= this.WITHDRAWAL_FEE) {
+ result.valid = false;
+ result.errors.push("Amount must be greater than withdrawal fee");
+ result.blockers.invalidAmount = true;
+ return result;
+ }
+
@@
- fee: 2.5,
- netAmount: amount - 2.5,
+ fee: this.WITHDRAWAL_FEE,
+ netAmount: amount - this.WITHDRAWAL_FEE,Also applies to: 92-93
🤖 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 `@lib/services/withdrawal.ts` around lines 25 - 30, The validation currently
only checks balance; add pre-checks to reject non-positive and fee-too-small
withdrawals: in the same validation function where amount, fee, result, and
mockWalletWithAssets are used (e.g., around the existing block checking
mockWalletWithAssets.balance), first check if amount <= 0 and set result.valid =
false, push an error like "Amount must be greater than 0" and set a blocker
(e.g., invalidAmount); then check if amount <= fee and set result.valid = false,
push an error like "Amount must be greater than fee" and set a blocker (e.g.,
amountNotCoveringFee). Keep these checks before the balance check and before
computing netAmount so netAmount cannot become non-positive. Ensure the same
validation logic guards the netAmount computation later.
| factory = f""" | ||
|
|
||
| export const {factory_name} = (overrides?: Partial<{type_name}>): {type_name} => ({{ | ||
| ...{array_name}[0], | ||
| ...overrides, | ||
| }}); | ||
| """ | ||
| content += factory | ||
|
|
||
| with open(dst, "w") as f: | ||
| f.write(content) | ||
| print(f"Processed {src} -> {dst}") |
There was a problem hiding this comment.
Make generation idempotent to prevent duplicate exported factories.
Re-running this script appends the same export const <factory_name> repeatedly, which can create duplicate symbol errors in generated mock files.
Proposed fix
- # Add factory
- factory = f"""
+ # Add factory once (idempotent)
+ factory = f"""
export const {factory_name} = (overrides?: Partial<{type_name}>): {type_name} => ({{
...{array_name}[0],
...overrides,
}});
"""
- content += factory
+ if f"export const {factory_name} =" not in content:
+ content += factory🤖 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 `@scripts/refactor_mocks.py` around lines 18 - 29, The script currently appends
a factory block into `content` unconditionally which causes duplicate exported
factories on re-runs; before adding the factory string (the block built using
factory_name, type_name, array_name) check whether `content` already contains
the export signature (e.g. "export const {factory_name}") or the exact factory
block and skip appending if found, or otherwise rebuild `content` from scratch
to include only one factory; ensure you still write to `dst` (the same file) and
keep the existing print of src -> dst.
…mponents (#182) - Decompose monolithic notification-center.tsx into notification-bell, notification-list, and notification-item components - Add per-type Lucide icons with distinct color coding per notification type - Add resourceUrl to NotificationItem for click-to-navigate behavior - Implement date grouping (Today / Yesterday / Earlier) with sticky headers - Add clearAll function to reset notifications and localStorage - Extend NotificationType with dispute-raised and payment-received - Gate NotificationCenter on authenticated session in global navbar - Replace notification-center.tsx with thin backward-compatible re-export Closes #182
|
Settlement Information:
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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-wizard.tsx`:
- Around line 153-165: The onSubmit handler is validating step-2 fields
(deadline, startDate, milestones) but is not passing them into the createBounty
mutation, causing data loss; update the onSubmit function to include these
fields in the createBounty input (deadline, startDate, milestones) or
gate/remove the step-2 inputs until backend support exists. Specifically, modify
onSubmit to map FormData.deadline, FormData.startDate, and FormData.milestones
into the createBounty({ input: { ... } }) payload (or disable/hide the UI that
sets those fields), and if you choose to include them ensure the
CreateBountyInput type/schema is extended to accept these keys so the
createBounty call and TypeScript types (CreateBountyInput) remain consistent.
In `@components/notifications/notification-list.tsx`:
- Around line 78-88: The icon-only action Button components (e.g., the Button
with onClick={onMarkAllAsRead}, disabled={unreadCount === 0} and child
CheckCheck) lack accessible names on small screens because the visible <span
className="hidden sm:inline"> label is hidden; fix by adding an explicit
accessible label (aria-label or aria-labelledby) to the Button and ensure the
same pattern is applied to the other icon-only button around lines 90-100 so
screen readers get a descriptive name while keeping the visual hidden text for
mobile.
🪄 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: 594082d8-0ca3-4e11-9fc8-5d4c52f0606e
📒 Files selected for processing (11)
app/bounty/create/page.tsxcomponents/bounty/bounty-create-wizard.tsxcomponents/global-navbar.tsxcomponents/notifications/notification-bell.tsxcomponents/notifications/notification-center.tsxcomponents/notifications/notification-item.tsxcomponents/notifications/notification-list.tsxe2e/bounty-creation.spec.tshooks/use-create-bounty.tshooks/use-notifications.tslib/server-graphql.ts
✅ Files skipped from review due to trivial changes (1)
- lib/server-graphql.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- app/bounty/create/page.tsx
- e2e/bounty-creation.spec.ts
| const onSubmit = async (data: FormData) => { | ||
| try { | ||
| const response = await createBounty({ | ||
| input: { | ||
| title: data.title, | ||
| description: data.description, | ||
| organizationId: data.organizationId, | ||
| githubIssueUrl: data.githubIssueUrl, | ||
| type: data.type, | ||
| rewardAmount: data.rewardAmount, | ||
| rewardCurrency: data.rewardCurrency, | ||
| // Note: Omitting startDate, deadline, milestones from GQL as they are not natively supported in CreateBountyInput yet. | ||
| }, |
There was a problem hiding this comment.
Step-2 data is validated but never persisted in create mutation.
deadline, startDate, and milestones are required/validated in the wizard, but they are intentionally omitted from createBounty input. That breaks the competition/milestone flows by creating bounties without the timeline/distribution data users just entered.
Please either (a) extend CreateBountyInput and pass these fields through now, or (b) temporarily remove/gate these inputs until backend support lands to avoid silent data loss.
🤖 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-wizard.tsx` around lines 153 - 165, The
onSubmit handler is validating step-2 fields (deadline, startDate, milestones)
but is not passing them into the createBounty mutation, causing data loss;
update the onSubmit function to include these fields in the createBounty input
(deadline, startDate, milestones) or gate/remove the step-2 inputs until backend
support exists. Specifically, modify onSubmit to map FormData.deadline,
FormData.startDate, and FormData.milestones into the createBounty({ input: { ...
} }) payload (or disable/hide the UI that sets those fields), and if you choose
to include them ensure the CreateBountyInput type/schema is extended to accept
these keys so the createBounty call and TypeScript types (CreateBountyInput)
remain consistent.
| <Button | ||
| type="button" | ||
| variant="ghost" | ||
| size="sm" | ||
| className="h-7 gap-1 px-2 text-xs" | ||
| onClick={onMarkAllAsRead} | ||
| disabled={unreadCount === 0} | ||
| > | ||
| <CheckCheck className="h-3.5 w-3.5" /> | ||
| <span className="hidden sm:inline">Read all</span> | ||
| </Button> |
There was a problem hiding this comment.
Add accessible names for mobile icon-only action buttons.
At Line 78 and Line 90, the visible labels are hidden on small screens, leaving unlabeled icon buttons for assistive tech.
Suggested fix
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 gap-1 px-2 text-xs"
onClick={onMarkAllAsRead}
disabled={unreadCount === 0}
+ aria-label="Mark all notifications as read"
>
- <CheckCheck className="h-3.5 w-3.5" />
- <span className="hidden sm:inline">Read all</span>
+ <CheckCheck className="h-3.5 w-3.5" aria-hidden="true" />
+ <span className="sr-only sm:not-sr-only">Read all</span>
</Button>
@@
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 gap-1 px-2 text-xs text-destructive hover:text-destructive"
onClick={onClearAll}
disabled={notifications.length === 0}
+ aria-label="Clear notifications"
>
- <Trash2 className="h-3.5 w-3.5" />
- <span className="hidden sm:inline">Clear</span>
+ <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
+ <span className="sr-only sm:not-sr-only">Clear</span>
</Button>Also applies to: 90-100
🤖 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/notifications/notification-list.tsx` around lines 78 - 88, The
icon-only action Button components (e.g., the Button with
onClick={onMarkAllAsRead}, disabled={unreadCount === 0} and child CheckCheck)
lack accessible names on small screens because the visible <span
className="hidden sm:inline"> label is hidden; fix by adding an explicit
accessible label (aria-label or aria-labelledby) to the Button and ensure the
same pattern is applied to the other icon-only button around lines 90-100 so
screen readers get a descriptive name while keeping the visual hidden text for
mobile.
Implementation
Summary by CodeRabbit
New Features
Improvements
Bug Fixes