Skip to content

feat: Bounty Hunt 2 Implementation & Playwright tests - #245

Closed
Ishant5436 wants to merge 14 commits into
boundlessfi:mainfrom
Ishant5436:feature/bounty-hunt-2
Closed

feat: Bounty Hunt 2 Implementation & Playwright tests#245
Ishant5436 wants to merge 14 commits into
boundlessfi:mainfrom
Ishant5436:feature/bounty-hunt-2

Conversation

@Ishant5436

@Ishant5436 Ishant5436 commented May 28, 2026

Copy link
Copy Markdown

Implementation

Summary by CodeRabbit

  • New Features

    • Bounty creation wizard for sponsors to create and manage bounties
    • Dispute raising functionality for bounty-related conflicts
    • Sponsor role toggle in profile settings
    • Revision request capability for bounty submissions
    • Cross-app search now includes projects and pages
    • Email digest preview in notification settings
  • Improvements

    • Enhanced bounty detail sidebar with contextual actions
    • Mobile-optimized bounty action panel
    • Refined loading states across bounty, wallet, and leaderboard pages
    • Simplified project card display with clearer status indicators
    • Updated notification center with bell icon and organized lists
  • Bug Fixes

    • Improved empty state messaging for saved bounties

Review Change Stack

Ishant5436 added 12 commits May 28, 2026 01:22
- 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
@vercel

vercel Bot commented May 28, 2026

Copy link
Copy Markdown

@Ishant5436 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 May 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This 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.

Changes

Bounty Platform Feature Consolidation & Application Workflows

Layer / File(s) Summary
Mock Data Reorganization & Factories
scripts/refactor_mocks.py, lib/mock/, lib/mock-data.ts (removed), lib/mock-wallet.ts (removed)
Migrate all scattered mock data files into centralized lib/mock/ directory with factory helper functions (makeMockBounty, makeMockProject, makeMockLeaderboard, makeMockWallet, etc.) and barrel exports for cleaner imports.
Update Import Paths Across Codebase
app/api/*, app/bounty/, app/discover/, app/projects/, app/wallet/, components/bounty-detail/, components/bounty/, lib/services/withdrawal.ts, lib/store.ts
Switch all mock data imports from scattered lib/mock-*.ts modules to unified @/lib/mock entry point.
Centralize Skeleton Components
components/ui/skeleton-loaders.tsx (new), components/bounty/bounty-card-skeleton.tsx (removed), components/bounty-detail/bounty-detail-bounty-detail-skeleton.tsx (removed)
Create unified skeleton component library exporting BountyCardSkeleton, BountyListSkeleton, LeaderboardSkeleton, WalletSkeleton, BountyDetailSkeleton. Update app/bounty/page.tsx, app/wallet/page.tsx, app/saved/saved-client.tsx, components/bounty/bounty-grid.tsx, components/bounty/bounty-list.tsx, components/leaderboard/leaderboard-table.tsx to import from new location.
Update Bounty Types & Enums
lib/graphql/schema.graphql, lib/graphql/generated.ts, types/bounty.ts
Add MultiWinnerMilestone enum value to BountyType, extend Bounty interface with applications, claimCount, maxParticipants, assignedContributorId, latestRevisionFeedback fields, and simplify type casting in app/bounty/page.tsx, components/projects/project-bounties.tsx.
User Authentication & Role System
lib/auth-client.ts, lib/server-auth.ts, hooks/use-user-mutations.ts, components/settings/profile-tab.tsx
Add optional role field (sponsor/contributor) to User type with auth client configuration, implement server-side role derivation with E2E test bypass (fake-sponsor-token), and wire role updates through user mutations and profile UI settings.
Bounty Detail CTA & Dispute Workflows
components/bounty-detail/types.ts, components/bounty-detail/use-bounty-cta-state.ts, components/bounty-detail/sidebar-cta.tsx, components/bounty-detail/mobile-cta.tsx, components/bounty-detail/dispute-dialog.tsx, components/bounty-detail/bounty-detail-client.tsx, components/bounty-detail/bounty-detail-sidebar-cta.tsx (removed)
Split monolithic sidebar CTA into reusable useBountyCtaState hook and separate SidebarCTA/MobileCTA components, add new DisputeDialog for raising bounty disputes with validation, and refactor BountyDetailClient to pass new props to updated subcomponents.
Application & Submission Workflows
hooks/use-bounty-application.ts, components/bounty/application-review-dashboard.tsx, components/bounty/submission-approval-panel.tsx, components/bounty/application-submit-work-panel.tsx, components/bounty-detail/milestone-submission-card.tsx, components/bounty-detail/model4-maintainer-dashboard.tsx
Add new mutation hooks (useApplyForSlot, useDeclineApplicant, useAdvanceMilestone, useRemoveFromSlot, useReleaseMilestonePayment, useRequestRevisions, useRaiseDispute) with optimistic cache updates; integrate Decline/Revise/Advance buttons into application and submission UIs with loading states and toasts.
Bounty Creation Wizard & Page
app/bounty/create/page.tsx, components/bounty/bounty-create-wizard.tsx, hooks/use-create-bounty.ts
Create /bounty/create page with sponsor role check and redirect, implement BountyCreateWizard 3-step form (Step 1: basic info; Step 2: rewards/milestones; Step 3: review), wire useCreateBounty hook for submission with success navigation to created bounty.
Notifications System Refactoring
components/notifications/notification-bell.tsx, components/notifications/notification-list.tsx, components/notifications/notification-item.tsx, components/notifications/notification-center.tsx (re-exported), hooks/use-notifications.ts, components/settings/notifications-tab.tsx
Refactor from monolithic NotificationCenter to split structure: NotificationBell trigger, NotificationList for grouped/dated display, NotificationItem for individual rendering; add resourceUrl navigation support and email digest preview dialog.
Multi-Category Search & Discovery
hooks/use-bounty-search.ts, components/search-command.tsx, app/discover/page.tsx
Enhance useBountySearch to merge cached + API bounties with deduping and add projectResults/pageResults; split SearchCommand results into distinct Pages/Projects/Bounties sections with category icons and navigation; update project search to match name instead of title.
Global Navigation & Navbar Updates
components/global-navbar.tsx
Read user session role and conditionally render /bounty/create link for sponsors, gate NotificationCenter on authenticated session, add accessibility aria-label attributes.
Project Card & Other UI Updates
components/cards/project-card.tsx
Switch Project type import to @/types/project, rewrite status mapping (Active/Ended/Draft), remove progress bar, update metadata to show creation date + creator name.
E2E Test Coverage
e2e/bounty-creation.spec.ts
Add Playwright tests covering bounty creation validation (empty title error) and happy path (Steps 1-3 form fill + submission with redirect assertion).
Remaining Refactors & Utilities
components/mode-toggle.tsx, lib/store.ts, lib/services/withdrawal.ts, lib/server-graphql.ts
Lint/formatting updates, restructure React Query hooks to use destructured params with inline types, update withdrawal service import path, rename query normalization variable.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Benjtalkshow

Poem

🐰 A Rabbit's Ode to the Great Refactor

From scattered mocks to one true home,
With bounty wizards we now roam!
Role-based sponsors now take flight,
While notifications group just right.
~*

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

🧹 Nitpick comments (4)
components/global-navbar.tsx (1)

33-36: 💤 Low value

Consider improving type safety at the auth client level.

The double type assertion (as Record<string, unknown>as string | undefined) works defensively but suggests that session.user doesn't have a properly typed role field. If the auth client's session type can be extended to include role, 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 value

Consider 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's baseURL configuration 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 value

Consider 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 win

Cache lookup key matches bountyKeys root; only consider de-duplication

hooks/use-bounty-search.ts uses queryClient.getQueriesData({ queryKey: ["Bounties"] }), which aligns with bountyKeys’ list roots (bountyKeys.allListKeys[0] is ["Bounties"], and list keys are ["Bounties", ...]). This shouldn’t miss cached entries due to casing/root mismatch.
Optional: reuse bountyKeys.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

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa81cb and bbfdcf1.

📒 Files selected for processing (57)
  • app/api/leaderboard/route.ts
  • app/api/leaderboard/top/route.ts
  • app/api/leaderboard/user/[userId]/route.ts
  • app/bounty/create/page.tsx
  • app/bounty/create/wizard.tsx
  • app/bounty/page.tsx
  • app/discover/page.tsx
  • app/projects/[id]/page.tsx
  • app/projects/page.tsx
  • app/saved/saved-client.tsx
  • app/wallet/page.tsx
  • components/bounty-detail/bounty-detail-bounty-detail-skeleton.tsx
  • components/bounty-detail/bounty-detail-client.tsx
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • components/bounty-detail/dispute-dialog.tsx
  • components/bounty-detail/milestone-submission-card.tsx
  • components/bounty-detail/mobile-cta.tsx
  • components/bounty-detail/model4-maintainer-dashboard.tsx
  • components/bounty-detail/sidebar-cta.tsx
  • components/bounty-detail/types.ts
  • components/bounty-detail/use-bounty-cta-state.ts
  • components/bounty/application-review-dashboard.tsx
  • components/bounty/application-submit-work-panel.tsx
  • components/bounty/bounty-card-skeleton.tsx
  • components/bounty/bounty-grid.tsx
  • components/bounty/bounty-list.tsx
  • components/bounty/submission-approval-panel.tsx
  • components/cards/project-card.tsx
  • components/global-navbar.tsx
  • components/leaderboard/leaderboard-table.tsx
  • components/mode-toggle.tsx
  • components/projects/project-bounties.tsx
  • components/search-command.tsx
  • components/settings/notifications-tab.tsx
  • components/settings/profile-tab.tsx
  • components/ui/skeleton-loaders.tsx
  • e2e/bounty-creation.spec.ts
  • hooks/use-bounty-application.ts
  • hooks/use-bounty-search.ts
  • hooks/use-competition-join-state.ts
  • hooks/use-user-mutations.ts
  • lib/auth-client.ts
  • lib/graphql/generated.ts
  • lib/graphql/schema.graphql
  • lib/mock-data.ts
  • lib/mock-wallet.ts
  • lib/mock/bounties.ts
  • lib/mock/index.ts
  • lib/mock/leaderboard.ts
  • lib/mock/model4.ts
  • lib/mock/projects.ts
  • lib/mock/wallet.ts
  • lib/server-auth.ts
  • lib/services/withdrawal.ts
  • lib/store.ts
  • scripts/refactor_mocks.py
  • types/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

Comment thread app/bounty/create/page.tsx
Comment thread app/bounty/create/wizard.tsx Outdated
Comment on lines +28 to +30
const handleNextStep2 = () => {
setStep(3);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Comment thread app/bounty/create/wizard.tsx Outdated
Comment on lines +33 to +57
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}`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

Comment on lines +153 to 155
bounty?.assignedContributorId === session?.user?.id ||
bounty?.submissions?.some((s) => s.submittedBy === session?.user?.id) ||
(!isCreator && bounty.status === "IN_PROGRESS");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

Comment on lines 201 to 204
<MilestoneSubmissionCard
bountyId={bounty.id}
contributorAddress={walletAddress || session.user.id}
milestones={milestones}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
<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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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.tsx

Repository: boundlessfi/bounties

Length of output: 2330


Fix mentions filtering: NotificationType doesn’t include "mentions"

  • components/settings/notifications-tab.tsx filters with n.type === ("mentions" as string), but hooks/use-notifications.ts’s NotificationType union does not define "mentions", and useNotifications never produces mention notifications—so the mentions digest will never match (except for any stale/unvalidated localStorage contents).
  • Add "mentions" to NotificationType and ensure the producer(s) set type: "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.

Comment thread lib/mock/bounties.ts
Comment on lines +279 to +282
export const makeMockBounty = (overrides?: Partial<Bounty>): Bounty => ({
...mockBounties[0],
...overrides,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

Comment thread lib/server-auth.ts
Comment on lines +94 to +104
// 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",
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

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.

Suggested change
// 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.

Comment on lines +25 to 30
// Check balance
if (amount > mockWalletWithAssets.balance) {
result.valid = false;
result.errors.push("Insufficient balance");
result.blockers.insufficientBalance = true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

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.

Comment thread scripts/refactor_mocks.py
Comment on lines +18 to +29
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}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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
@Ishant5436

Copy link
Copy Markdown
Author

Settlement Information:

  • Solana: 2WktXRjaQ4GKhj6FJhUSndTBLVjxrk43TQwyywehneDA

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

📥 Commits

Reviewing files that changed from the base of the PR and between bbfdcf1 and 1cb847c.

📒 Files selected for processing (11)
  • app/bounty/create/page.tsx
  • components/bounty/bounty-create-wizard.tsx
  • components/global-navbar.tsx
  • components/notifications/notification-bell.tsx
  • components/notifications/notification-center.tsx
  • components/notifications/notification-item.tsx
  • components/notifications/notification-list.tsx
  • e2e/bounty-creation.spec.ts
  • hooks/use-create-bounty.ts
  • hooks/use-notifications.ts
  • lib/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

Comment on lines +153 to +165
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.
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

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.

Comment on lines +78 to +88
<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>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

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.

2 participants