Skip to content

feat: github repo marketplace#882

Open
codebestia wants to merge 1 commit intoSolFoundry:mainfrom
codebestia:feat/github-rep-marketplace
Open

feat: github repo marketplace#882
codebestia wants to merge 1 commit intoSolFoundry:mainfrom
codebestia:feat/github-rep-marketplace

Conversation

@codebestia
Copy link
Copy Markdown
Contributor

@codebestia codebestia commented Apr 4, 2026

Description

Closes #857

Builds the open-source project funding marketplace at /repos — a public page where developers can discover GitHub repos, fund specific features or maintenance goals, and track payment distribution across contributors. Repo maintainers can register their repos with a USDC funding goal; funders can contribute in one click; payment splits are tracked per contributor wallet.

Solana Wallet for Payout

Wallet: 4QhseKvBuaCQhdkP248iXoUxohPzVC5m8pE9hAv4nMYw

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • 🎨 Style/UI update
  • ♻️ Code refactoring
  • ⚡ Performance improvement
  • ✅ Test addition/update

Checklist

  • Code is clean and follows the issue spec exactly
  • One PR per bounty (no multiple bounties in one PR)
  • Tests included for new functionality
  • All existing tests pass
  • No console.log or debugging code left behind
  • No hardcoded secrets or API keys

Testing

  • Manual testing performed
  • Unit tests added/updated
  • Integration tests added/updated

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

📝 Walkthrough

Walkthrough

This PR adds a new GitHub repository marketplace feature to the frontend. It introduces a new /repos route with a RepoMarketplacePage component that enables users to discover open-source projects, filter by language and minimum stars, and interact with funding workflows. The implementation includes modals for viewing repository details with contributor distribution data, funding repositories, and registering new repositories with funding goals. The changes also include a comprehensive test suite covering component rendering, API integration with mocked endpoints, filtering and sorting functionality, modal workflows, funding submission, and repository registration flows.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: github repo marketplace' directly and clearly summarizes the main change, which is the addition of a new GitHub repo marketplace feature at the /repos route.
Description check ✅ Passed The description is well-related to the changeset, explaining the marketplace implementation, key features (repo discovery, funding, contributor tracking), and explicitly referencing the closed issue #857.
Linked Issues check ✅ Passed The PR implementation fully addresses issue #857 requirements: includes repo discovery with language/stars filtering, funding goal setup and progress tracking, and payment distribution to contributors via the new RepoMarketplacePage component and supporting modals.
Out of Scope Changes check ✅ Passed All changes are in-scope: App.tsx route addition, test suite creation, and the main RepoMarketplacePage component directly support the marketplace feature objectives with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/__tests__/repo-marketplace.test.tsx`:
- Around line 336-395: Add tests that use the existing mockFetch and failJson
helper to exercise error states: (1) a test for GET /api/repos returning
failJson(...) to assert the repo list shows the error UI (use renderPage and
expect the component's error test id or message), (2) a test where POST
/api/repos/repo-001/fund returns failJson(500) to assert the fund modal displays
a failure message or error test id after clicking confirm-fund, and (3) a test
where POST /api/repos (registration) returns failJson(...) to assert the
registration flow surfaces an error; for each test reuse the existing patterns
(userEvent.setup(), renderPage(), mockFetch implementation branching on
url.includes('/api/repos') and method === 'POST') and assert the appropriate
error element appears instead of the success path.

In `@frontend/src/pages/RepoMarketplacePage.tsx`:
- Around line 301-306: The modal components (DetailModal, FundModal,
RegisterModal) currently only close on backdrop click and do not trap focus,
handle Escape, or restore focus to the trigger; update each modal to use a
proper focus-trap/Dialog (e.g., replace the current markup with
`@headlessui/react` Dialog or wrap the content in focus-trap-react) so keyboard
focus is contained while open, add a global onKeyDown or Dialog onClose handler
that closes on Escape (calling the existing onClose prop), and capture the
element that opened the modal (store document.activeElement before opening) then
restore focus to that element after onClose returns. Ensure the backdrop click
logic (e.target === e.currentTarget && onClose()) remains but is augmented by
the focus trap/Dialog behavior and Escape handling for full keyboard
accessibility.
- Around line 327-333: The close buttons are missing accessible labels; update
the button that uses the × character (the one with
data-testid="close-detail-modal" which calls onClose) and the similar close
button in FundModal to include an aria-label like aria-label="Close" (or
aria-label={`Close ${context}`} if more context needed) so screen readers
announce the purpose; keep the visible × but add the aria-label attribute to the
button elements and ensure no other click handlers are changed.
- Around line 413-425: The registration mutation in RegisterModal using
useMutation (mutationFn -> postRegister) has no onError handler; add an onError
callback to the useMutation config that surfaces the failure to the user (e.g.,
show a toast or set an error state) and prevents calling onClose so the user can
correct input; keep the existing onSuccess logic
(qc.invalidateQueries(['repos']) and onClose) intact and ensure the onError
receives the error param to include error.message/details in the user-visible
feedback.
- Around line 43-55: The functions fetchRepos and fetchContributors silently
swallow non-OK HTTP responses by returning empty results; change both to throw
an error when res.ok is false (include status and response text/body for
context) instead of returning { items: [], total: 0 } so the error propagates to
React Query and triggers isError; preserve existing successful-path behavior
(return res.json()) and ensure any caller can catch/display the thrown error.
- Around line 408-411: The client-side wallet check using
ownerWallet.trim().length >= 32 is too weak; update the validation used by
isValid to trim ownerWallet, ensure its length is between 32 and 44, and verify
it decodes as valid base58 (rejecting characters outside the base58 alphabet) —
e.g., import or use a base58 decoder (bs58 or equivalent) to try decoding
ownerWallet.trim() inside a try/catch and only accept if decode succeeds and the
decoded byte length is 32; reference and change the isValid expression and any
place that reads ownerWallet in RepoMarketplacePage.tsx to use this stricter
validation.
- Around line 206-218: The funding mutation (useMutation with mutationFn calling
postFund) lacks error handling; add an onError callback to the mutation to
capture the thrown error, set local error state or call your existing error
handler (e.g., setError or a toast/notification) and ensure you stop any loading
UI there. Then update the component render to check mutation.isError (or your
error state) and display a user-facing error message (and optionally the
error.message) near the form so users get feedback when postFund fails;
reference the useMutation invocation, postFund, mutation.isError and any
setSuccess/setError state setters to implement this.
- Around line 207-212: The current mutationFn in RepoMarketplacePage (calling
postFund with tx_signature: `sig-${Date.now()}`) uses a placeholder signature
and a plain text wallet field; replace this with real Solana wallet integration:
add `@solana/wallet-adapter` and web3 packages, connect a WalletProvider and use
the connected wallet (replace manual wallet input usage of wallet) to sign and
send a transaction via web3 (use connection.sendTransaction /
wallet.signTransaction or adapter helper), obtain the on-chain
confirmation/signature and pass that actual tx signature into postFund instead
of the mock `sig-${Date.now()}`, and add error handling and UI feedback around
the send/sign/confirmation steps used by mutationFn and the component that
manages amount/repo.id.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a430e302-c1e8-45ed-b474-ae0b45a80d22

📥 Commits

Reviewing files that changed from the base of the PR and between f418700 and 1e86b62.

📒 Files selected for processing (3)
  • frontend/src/App.tsx
  • frontend/src/__tests__/repo-marketplace.test.tsx
  • frontend/src/pages/RepoMarketplacePage.tsx

Comment on lines +336 to +395
describe('Fund workflow', () => {
it('opens fund modal from detail modal', async () => {
const u = userEvent.setup();
renderPage();
await waitFor(() => expect(screen.getByTestId('repo-card-repo-001')).toBeInTheDocument());
await u.click(screen.getByTestId('repo-card-repo-001'));
await u.click(screen.getByTestId('open-fund-modal'));
expect(screen.getByTestId('fund-modal')).toBeInTheDocument();
});

it('confirm-fund button is disabled until amount entered', async () => {
const u = userEvent.setup();
renderPage();
await waitFor(() => expect(screen.getByTestId('repo-card-repo-001')).toBeInTheDocument());
await u.click(screen.getByTestId('repo-card-repo-001'));
await u.click(screen.getByTestId('open-fund-modal'));
expect(screen.getByTestId('confirm-fund')).toBeDisabled();
});

it('confirm-fund button enables after amount entered', async () => {
const u = userEvent.setup();
renderPage();
await waitFor(() => expect(screen.getByTestId('repo-card-repo-001')).toBeInTheDocument());
await u.click(screen.getByTestId('repo-card-repo-001'));
await u.click(screen.getByTestId('open-fund-modal'));
await u.type(screen.getByTestId('fund-amount-input'), '100');
expect(screen.getByTestId('confirm-fund')).not.toBeDisabled();
});

it('submits fund and shows success message', async () => {
mockFetch.mockImplementation((urlArg: unknown, opts?: unknown) => {
const url = String(urlArg ?? '');
const method = (opts as RequestInit | undefined)?.method ?? 'GET';
if (method === 'POST' && url.includes('/api/repos/repo-001/fund')) {
return Promise.resolve(
okJson({ id: 'fund-abc', repo_id: 'repo-001', amount: 100, tx_signature: 'sig123' }),
);
}
if (url.includes('/api/repos') && !url.includes('/contributors')) {
return Promise.resolve(okJson({ items: REPOS, total: REPOS.length }));
}
if (url.includes('/contributors')) {
return Promise.resolve(okJson({ contributors: CONTRIBUTORS }));
}
return Promise.resolve(okJson({ items: [], total: 0 }));
});

const u = userEvent.setup();
renderPage();
await waitFor(() => expect(screen.getByTestId('repo-card-repo-001')).toBeInTheDocument());
await u.click(screen.getByTestId('repo-card-repo-001'));
await u.click(screen.getByTestId('open-fund-modal'));
await u.type(screen.getByTestId('fund-amount-input'), '100');
await u.click(screen.getByTestId('confirm-fund'));

await waitFor(() =>
expect(screen.getByTestId('fund-success')).toBeInTheDocument(),
);
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Missing test coverage for error states and mutation failures.

The test suite thoroughly covers happy-path scenarios but lacks coverage for:

  1. API error handling: No tests verify behavior when /api/repos or /api/repos/{repoId}/fund returns a non-2xx status. The component silently degrades (shows empty state) rather than displaying an error—this should be tested.

  2. Fund mutation failure: No test for when POST /api/repos/{repoId}/fund fails (e.g., using failJson(500)). Users would not see feedback if the request fails, and this gap is not validated.

  3. Register mutation failure: Similarly, no test covers a failed POST /api/repos for registration.

Consider adding tests using the existing failJson helper (lines 105-110) to verify error feedback is displayed to users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/__tests__/repo-marketplace.test.tsx` around lines 336 - 395, Add
tests that use the existing mockFetch and failJson helper to exercise error
states: (1) a test for GET /api/repos returning failJson(...) to assert the repo
list shows the error UI (use renderPage and expect the component's error test id
or message), (2) a test where POST /api/repos/repo-001/fund returns
failJson(500) to assert the fund modal displays a failure message or error test
id after clicking confirm-fund, and (3) a test where POST /api/repos
(registration) returns failJson(...) to assert the registration flow surfaces an
error; for each test reuse the existing patterns (userEvent.setup(),
renderPage(), mockFetch implementation branching on url.includes('/api/repos')
and method === 'POST') and assert the appropriate error element appears instead
of the success path.

Comment on lines +43 to +55
async function fetchRepos(
language: string,
minStars: string,
sort: string,
): Promise<RepoListResponse> {
const p = new URLSearchParams();
if (language) p.set('language', language);
if (minStars) p.set('min_stars', minStars);
if (sort) p.set('sort', sort);
const res = await fetch(`/api/repos?${p.toString()}`);
if (!res.ok) return { items: [], total: 0 };
return res.json();
}
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

Silent error swallowing in fetchRepos prevents user feedback.

When the API returns a non-OK response (line 53), the function returns an empty { items: [], total: 0 } instead of propagating the error. This causes:

  1. Users see "No repos found" rather than an error message when the API is down or returns 5xx
  2. React Query's isError state is never triggered, making error handling impossible at the component level

The same pattern exists in fetchContributors (line 59).

Consider propagating errors to enable proper error handling
 async function fetchRepos(
   language: string,
   minStars: string,
   sort: string,
 ): Promise<RepoListResponse> {
   const p = new URLSearchParams();
   if (language) p.set('language', language);
   if (minStars) p.set('min_stars', minStars);
   if (sort) p.set('sort', sort);
   const res = await fetch(`/api/repos?${p.toString()}`);
-  if (!res.ok) return { items: [], total: 0 };
+  if (!res.ok) throw new Error(`Failed to fetch repos: ${res.status}`);
   return res.json();
 }

Then handle isError in the component to show an error state alongside loading and empty states.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/RepoMarketplacePage.tsx` around lines 43 - 55, The
functions fetchRepos and fetchContributors silently swallow non-OK HTTP
responses by returning empty results; change both to throw an error when res.ok
is false (include status and response text/body for context) instead of
returning { items: [], total: 0 } so the error propagates to React Query and
triggers isError; preserve existing successful-path behavior (return res.json())
and ensure any caller can catch/display the thrown error.

Comment on lines +206 to +218
const mutation = useMutation({
mutationFn: () =>
postFund(repo.id, {
funder_wallet: wallet || 'anonymous',
amount: Number(amount),
tx_signature: `sig-${Date.now()}`,
}),
onSuccess: () => {
setSuccess(true);
qc.invalidateQueries({ queryKey: ['repos'] });
qc.invalidateQueries({ queryKey: ['contributors', repo.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

Missing error handling in fund mutation leaves users without feedback.

The useMutation for funding (lines 206-218) has no onError callback. If postFund throws (line 72), the mutation transitions to error state but:

  1. No error message is displayed to the user
  2. The form remains in its current state with no indication of failure
  3. mutation.isError is not checked in the render logic

Users who submit a fund request that fails will have no idea why nothing happened.

Add onError handler and display error state
   const mutation = useMutation({
     mutationFn: () =>
       postFund(repo.id, {
         funder_wallet: wallet || 'anonymous',
         amount: Number(amount),
         tx_signature: `sig-${Date.now()}`,
       }),
     onSuccess: () => {
       setSuccess(true);
       qc.invalidateQueries({ queryKey: ['repos'] });
       qc.invalidateQueries({ queryKey: ['contributors', repo.id] });
     },
+    onError: (error) => {
+      // Consider adding error state to display to user
+      console.error('Fund failed:', error);
+    },
   });

Then render an error message when mutation.isError is true.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/RepoMarketplacePage.tsx` around lines 206 - 218, The
funding mutation (useMutation with mutationFn calling postFund) lacks error
handling; add an onError callback to the mutation to capture the thrown error,
set local error state or call your existing error handler (e.g., setError or a
toast/notification) and ensure you stop any loading UI there. Then update the
component render to check mutation.isError (or your error state) and display a
user-facing error message (and optionally the error.message) near the form so
users get feedback when postFund fails; reference the useMutation invocation,
postFund, mutation.isError and any setSuccess/setError state setters to
implement this.

Comment on lines +207 to +212
mutationFn: () =>
postFund(repo.id, {
funder_wallet: wallet || 'anonymous',
amount: Number(amount),
tx_signature: `sig-${Date.now()}`,
}),
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if wallet adapter is already integrated elsewhere in the codebase
rg -l "wallet-adapter|useWallet|WalletProvider" --type=ts --type=tsx

Repository: SolFoundry/solfoundry

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Check if wallet adapter is integrated (fix: remove tsx type, use broader search)
rg "wallet-adapter|useWallet|WalletProvider" --type ts -l

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Also check package.json for wallet-adapter dependency
find . -name "package.json" -path "*/frontend*" -o -name "package.json" -path "*/packages*" | head -5 | xargs grep -l "wallet-adapter" 2>/dev/null

Repository: SolFoundry/solfoundry

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check the structure of RepoMarketplacePage to see current imports and hooks
head -50 frontend/src/pages/RepoMarketplacePage.tsx

Repository: SolFoundry/solfoundry

Length of output: 1339


🏁 Script executed:

#!/bin/bash
# Find frontend package.json
find . -type f -name "package.json" | grep -E "(frontend|package)" | head -10

Repository: SolFoundry/solfoundry

Length of output: 266


🏁 Script executed:

#!/bin/bash
# View the lines around 207-212 in RepoMarketplacePage
sed -n '200,220p' frontend/src/pages/RepoMarketplacePage.tsx

Repository: SolFoundry/solfoundry

Length of output: 644


🏁 Script executed:

#!/bin/bash
# Search for postFund function definition
rg "postFund|function postFund|const postFund" --type ts -B 2 -A 10

Repository: SolFoundry/solfoundry

Length of output: 1944


🏁 Script executed:

#!/bin/bash
# Check all TypeScript/JavaScript files for wallet imports (no type restriction)
rg "@solana/wallet-adapter|useWallet|WalletProvider|useConnection|useAnchorWallet" -l 2>/dev/null | head -20

Repository: SolFoundry/solfoundry

Length of output: 113


🏁 Script executed:

#!/bin/bash
# Check frontend package.json for wallet-adapter dependency
cat frontend/package.json | grep -A 20 '"dependencies"'

Repository: SolFoundry/solfoundry

Length of output: 716


🏁 Script executed:

#!/bin/bash
# Look for wallet input form handling in RepoMarketplacePage
grep -n "wallet\|setWallet" frontend/src/pages/RepoMarketplacePage.tsx | head -20

Repository: SolFoundry/solfoundry

Length of output: 885


🏁 Script executed:

#!/bin/bash
# Check if there's any form/input that sets wallet value
sed -n '220,300p' frontend/src/pages/RepoMarketplacePage.tsx

Repository: SolFoundry/solfoundry

Length of output: 3417


Placeholder transaction signature must be replaced with actual wallet integration for production.

Line 211 uses a mock signature: tx_signature: 'sig-${Date.now()}'. This bypasses on-chain transaction validation entirely. The wallet input field (lines 259-265) is a plain text field accepting manual wallet addresses—no actual Solana wallet connection exists in the codebase. There are no Solana integration packages (@solana/wallet-adapter, @solana/web3.js, @project-serum/anchor) in frontend/package.json dependencies.

For a bounty submission demonstrating UI flows, this is acceptable. However, production deployment requires:

  • Integrating @solana/wallet-adapter for actual wallet connection
  • Implementing transaction signing via the user's connected wallet
  • Replacing the placeholder with a real tx_signature from on-chain transaction confirmation
  • Adding proper transaction error handling and confirmation UI
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/RepoMarketplacePage.tsx` around lines 207 - 212, The
current mutationFn in RepoMarketplacePage (calling postFund with tx_signature:
`sig-${Date.now()}`) uses a placeholder signature and a plain text wallet field;
replace this with real Solana wallet integration: add `@solana/wallet-adapter` and
web3 packages, connect a WalletProvider and use the connected wallet (replace
manual wallet input usage of wallet) to sign and send a transaction via web3
(use connection.sendTransaction / wallet.signTransaction or adapter helper),
obtain the on-chain confirmation/signature and pass that actual tx signature
into postFund instead of the mock `sig-${Date.now()}`, and add error handling
and UI feedback around the send/sign/confirmation steps used by mutationFn and
the component that manages amount/repo.id.

Comment on lines +301 to +306
return (
<>
<div
data-testid="repo-detail-modal"
className="fixed inset-0 bg-black/70 flex items-center justify-center z-40 p-4"
onClick={(e) => e.target === e.currentTarget && onClose()}
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

Modals lack focus trapping and keyboard dismissal.

The modal overlays (DetailModal line 303, FundModal line 221, RegisterModal line 428) don't:

  1. Trap focus within the modal when open (tabbing can escape to background content)
  2. Handle Escape key to close
  3. Restore focus to the trigger element when closed

This creates accessibility barriers for keyboard and screen reader users.

Consider using a modal library like @headlessui/react Dialog or implementing focus trap with focus-trap-react.

Also applies to: 220-225, 427-432

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/RepoMarketplacePage.tsx` around lines 301 - 306, The modal
components (DetailModal, FundModal, RegisterModal) currently only close on
backdrop click and do not trap focus, handle Escape, or restore focus to the
trigger; update each modal to use a proper focus-trap/Dialog (e.g., replace the
current markup with `@headlessui/react` Dialog or wrap the content in
focus-trap-react) so keyboard focus is contained while open, add a global
onKeyDown or Dialog onClose handler that closes on Escape (calling the existing
onClose prop), and capture the element that opened the modal (store
document.activeElement before opening) then restore focus to that element after
onClose returns. Ensure the backdrop click logic (e.target === e.currentTarget
&& onClose()) remains but is augmented by the focus trap/Dialog behavior and
Escape handling for full keyboard accessibility.

Comment on lines +327 to +333
<button
data-testid="close-detail-modal"
onClick={onClose}
className="text-white/40 hover:text-white text-xl leading-none"
>
×
</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 | 🟡 Minor

Modal close buttons lack accessible labels.

The close button (line 332) uses × character without an aria-label. Screen reader users will hear "times" or nothing, rather than "Close". Same issue on line 241 in FundModal's close button.

Add aria-label for accessibility
             <button
               data-testid="close-detail-modal"
               onClick={onClose}
+              aria-label="Close modal"
               className="text-white/40 hover:text-white text-xl leading-none"
             >
               ×
             </button>

Also applies to: 236-241

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/RepoMarketplacePage.tsx` around lines 327 - 333, The close
buttons are missing accessible labels; update the button that uses the ×
character (the one with data-testid="close-detail-modal" which calls onClose)
and the similar close button in FundModal to include an aria-label like
aria-label="Close" (or aria-label={`Close ${context}`} if more context needed)
so screen readers announce the purpose; keep the visible × but add the
aria-label attribute to the button elements and ensure no other click handlers
are changed.

Comment on lines +408 to +411
const isValid =
githubUrl.trim().startsWith('https://github.com/') &&
Number(goalAmount) > 0 &&
ownerWallet.trim().length >= 32;
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

Weak wallet address validation may accept invalid addresses.

The validation ownerWallet.trim().length >= 32 accepts any string of 32+ characters. Solana base58-encoded public keys are 32-44 characters, but this check doesn't verify:

  1. Valid base58 character set (alphanumeric excluding 0, O, I, l)
  2. Correct checksum/format

Invalid wallet addresses would be accepted client-side, potentially causing backend errors or lost funds.

Consider adding base58 format validation
+const SOLANA_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
+
 const isValid =
   githubUrl.trim().startsWith('https://github.com/') &&
   Number(goalAmount) > 0 &&
-  ownerWallet.trim().length >= 32;
+  SOLANA_ADDRESS_REGEX.test(ownerWallet.trim());
📝 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 isValid =
githubUrl.trim().startsWith('https://github.com/') &&
Number(goalAmount) > 0 &&
ownerWallet.trim().length >= 32;
const SOLANA_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
const isValid =
githubUrl.trim().startsWith('https://github.com/') &&
Number(goalAmount) > 0 &&
SOLANA_ADDRESS_REGEX.test(ownerWallet.trim());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/RepoMarketplacePage.tsx` around lines 408 - 411, The
client-side wallet check using ownerWallet.trim().length >= 32 is too weak;
update the validation used by isValid to trim ownerWallet, ensure its length is
between 32 and 44, and verify it decodes as valid base58 (rejecting characters
outside the base58 alphabet) — e.g., import or use a base58 decoder (bs58 or
equivalent) to try decoding ownerWallet.trim() inside a try/catch and only
accept if decode succeeds and the decoded byte length is 32; reference and
change the isValid expression and any place that reads ownerWallet in
RepoMarketplacePage.tsx to use this stricter validation.

Comment on lines +413 to +425
const mutation = useMutation({
mutationFn: () =>
postRegister({
github_url: githubUrl.trim(),
goal_amount: Number(goalAmount),
owner_wallet: ownerWallet.trim(),
description: description.trim() || undefined,
}),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['repos'] });
onClose();
},
});
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

Registration mutation also lacks error feedback.

Similar to the fund mutation, RegisterModal's mutation (lines 413-425) has no onError handler. A failed registration attempt will leave users wondering why the modal didn't close.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/RepoMarketplacePage.tsx` around lines 413 - 425, The
registration mutation in RegisterModal using useMutation (mutationFn ->
postRegister) has no onError handler; add an onError callback to the useMutation
config that surfaces the failure to the user (e.g., show a toast or set an error
state) and prevents calling onClose so the user can correct input; keep the
existing onSuccess logic (qc.invalidateQueries(['repos']) and onClose) intact
and ensure the onError receives the error param to include error.message/details
in the user-visible feedback.

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.

🏭 Bounty T3: GitHub Repo Marketplace

1 participant