feat:implement user dashboard-my claims#87
Conversation
|
@KingFRANKHOOD is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis PR introduces a "My Claims" tab to the user profile page that displays claimed bounties organized by status (Active, In Review, Completed). It adds a new MyClaims component, supporting utilities and types, integrates infinite bounty fetching, and makes widespread formatting adjustments across the codebase including quote style standardization and error handling simplification. Changes
Sequence DiagramsequenceDiagram
participant ProfilePage as Profile Page
participant Hook as useInfiniteBounties
participant API as Bounty API
participant Util as flattenBountyPages
participant Component as MyClaims Component
ProfilePage->>Hook: Initialize hook with userId
ProfilePage->>Hook: Call fetchNextPage if nextPage exists
Hook->>API: Fetch bounties (paginated)
API-->>Hook: Return bounty page data
ProfilePage->>Util: Process infinite pages
Util-->>ProfilePage: Return flattened bounties
ProfilePage->>ProfilePage: Filter by claimedBy, normalize status → myClaims[]
ProfilePage->>Component: Pass claims array
Component->>Component: Group claims by status via getClaimsBySection
Component-->>ProfilePage: Render sectioned claims UI
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
components/reputation/my-claims.tsx (2)
35-43:getClaimsBySectionis O(sections × statuses × claims) — fine now, but note the quadratic shape.Each call normalizes every section status string on every claim iteration. For the current small dataset this is negligible. If the claim list ever grows large, consider pre-normalizing the section statuses into a
Setonce.♻️ Pre-normalize section statuses
export function getClaimsBySection(claims: MyClaim[]) { return CLAIM_SECTIONS.map((section) => ({ section, - claims: claims.filter((claim) => { - const normalizedClaimStatus = normalizeStatus(claim.status); - return section.statuses.some((status) => normalizeStatus(status) === normalizedClaimStatus); - }), + claims: (() => { + const normalizedStatuses = new Set(section.statuses.map(normalizeStatus)); + return claims.filter((claim) => normalizedStatuses.has(normalizeStatus(claim.status))); + })(), })); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/reputation/my-claims.tsx` around lines 35 - 43, getClaimsBySection currently calls normalizeStatus repeatedly for every section status inside the claims.filter loop, causing O(sections × statuses × claims) work; to fix it, inside getClaimsBySection precompute a Set of normalized statuses for each section (use CLAIM_SECTIONS and normalizeStatus) once before filtering (e.g., const normalizedStatuses = new Set(section.statuses.map(normalizeStatus))) and then change the inner filter to check membership with normalizedStatuses.has(normalizedClaimStatus) so each status is normalized only once per section.
19-23: Redundant entries inCLAIM_SECTIONSafter normalization.
"in-review"and"in review"both normalize to"in-review", making one redundant. Same applies if any other pairs collapse. SincegetClaimsBySectionnormalizes both sides, you only need the canonical (already-normalized) form in eachstatusesarray.♻️ Suggested cleanup
export const CLAIM_SECTIONS: { title: string; statuses: string[] }[] = [ { title: "Active Claims", statuses: ["active", "claimed", "in-progress"] }, - { title: "In Review", statuses: ["in-review", "in review", "review", "pending", "under-review"] }, + { title: "In Review", statuses: ["in-review", "review", "pending", "under-review"] }, { title: "Completed", statuses: ["completed", "closed", "accepted", "done"] }, ];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/reputation/my-claims.tsx` around lines 19 - 23, CLAIM_SECTIONS contains duplicate-equivalent status strings because getClaimsBySection normalizes values; update CLAIM_SECTIONS so each statuses array uses only the canonical, normalized forms (e.g., keep "in-review" and remove "in review") to avoid redundant entries, reviewing all entries in CLAIM_SECTIONS and ensuring they match the normalization logic used in getClaimsBySection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/profile/`[userId]/page.tsx:
- Around line 45-69: myClaims currently treats bountyResponse?.data as empty
which hides loading/error states; update the logic around the useMemo in
page.tsx to respect bountyResponse.isLoading and bountyResponse.error (or
equivalent) instead of silently returning an empty array: detect isLoading and
make the component render a spinner/placeholder for the "My Claims" tab, detect
error and render an error message, and only compute myClaims from
bountyResponse.data when not loading and no error; reference the useMemo hook
named myClaims, the bountyResponse object, and the UI path that renders the "My
Claims" tab to wire the loading/error states into the rendered output.
- Around line 53-59: The frontend branch that maps bounty.status +
claimExpiresAt to "in-review" is out of sync with backend semantics: remove the
heuristic in app/profile/[userId]/page.tsx that sets status = "in-review" (the
code inspecting bounty.status and bounty.claimExpiresAt) and instead rely on the
backend-normalized status (or a single canonical mapper that consumes
BountyLogic.processBountyStatus() output). Ask backend team whether expired
claims are auto-reverted to "open" or should map to a review UI state; then
either (a) accept only the backend statuses ('open'|'claimed'|'closed') and
derive UI labels from them, or (b) implement a documented mapping function
(e.g., mapBackendBountyStatusToUI) that explicitly translates backend statuses
to UI states and uses server-provided processed status rather than computing
"in-review" locally.
- Line 20: The component currently calls useBounties(), which only returns the
first page and omits claims on subsequent pages; replace useBounties() with
useInfiniteBounties(), then call flattenBountyPages() on the returned pages to
produce a single list before applying the claimedBy filter (e.g., where you
currently filter bountyResponse.data by claimedBy). Update any variable names as
needed (bountyResponse → infiniteBounties/pages) and ensure you handle
loading/hasNext/fetchNext logic from useInfiniteBounties() if required.
---
Nitpick comments:
In `@components/reputation/my-claims.tsx`:
- Around line 35-43: getClaimsBySection currently calls normalizeStatus
repeatedly for every section status inside the claims.filter loop, causing
O(sections × statuses × claims) work; to fix it, inside getClaimsBySection
precompute a Set of normalized statuses for each section (use CLAIM_SECTIONS and
normalizeStatus) once before filtering (e.g., const normalizedStatuses = new
Set(section.statuses.map(normalizeStatus))) and then change the inner filter to
check membership with normalizedStatuses.has(normalizedClaimStatus) so each
status is normalized only once per section.
- Around line 19-23: CLAIM_SECTIONS contains duplicate-equivalent status strings
because getClaimsBySection normalizes values; update CLAIM_SECTIONS so each
statuses array uses only the canonical, normalized forms (e.g., keep "in-review"
and remove "in review") to avoid redundant entries, reviewing all entries in
CLAIM_SECTIONS and ensuring they match the normalization logic used in
getClaimsBySection.
Benjtalkshow
left a comment
There was a problem hiding this comment.
-
CI failed because ESLint/TypeScript reported 7 errors—primarily '@typescript-eslint/no-explicit-any' in several files and a 'react-hooks/set-state-in-effect' error in components/wallet/withdrawal-section.tsx—plus multiple 'no-unused-vars' warnings and a missing useEffect dependency list.
-
Fix by replacing any with explicit types, move or defer the synchronous setState out of the effect (or refactor the effect to avoid immediate state updates), add the missing dependencies to the useEffect array, remove unused variables, then commit and re-run the workflow.
-
Fix the coderabbit correction. See sample:
|
@Benjtalkshow Alright. Fixing them now |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/compliance/limits-display.tsx (1)
89-93:⚠️ Potential issue | 🟡 MinorAdd lower-bound guard to prevent negative width values.
The backend's
getRemainingLimits()calculation uses(usage / limits) * 100and is designed to prevent negative percentages; usage values start at 0 and are only incremented or reset. However, the frontend should defensively clamp both directions to protect against potential upstream anomalies or future changes. Currently,Math.min(100, percent)only guards against overflow; a negativepercentwould still produce invalid CSS (width: -N%), which browsers silently treat as 0 but represents an unintended state.Proposed fix
- style={{ width: `${Math.min(100, percent)}%` }} + style={{ width: `${Math.min(100, Math.max(0, percent))}%` }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/compliance/limits-display.tsx` around lines 89 - 93, The progress-bar width currently uses Math.min(100, percent) which prevents overflow but allows negative percent values; update the inline style in the component that renders the div with className "bg-primary h-2 rounded-full transition-all" to clamp percent into the 0–100 range (e.g., replace Math.min(100, percent) with a clamp such as Math.max(0, Math.min(100, percent)) or a small helper like clampPercent(percent)) so the rendered width never becomes negative or exceeds 100.
🧹 Nitpick comments (3)
lib/services/appeal.ts (1)
39-43:getUserAppealsreturns raw internal references, inconsistent with the defensive-copy pattern insubmitAppeal/getAppeal.
submitAppeal(line 29) andgetAppeal(line 36) both return{ ...appeal }to prevent callers from mutating internal state.getUserAppealsreturns the raw objects fromMOCK_APPEALSdirectly, so a caller that mutates a returned item will silently corrupt the in-memory store.♻️ Proposed fix — map to shallow copies for consistency
static async getUserAppeals(userId: string): Promise<VerificationAppeal[]> { return Object.values(MOCK_APPEALS) .filter(a => a.userId === userId) - .sort((a, b) => new Date(b.submittedAt).getTime() - new Date(a.submittedAt).getTime()); + .sort((a, b) => new Date(b.submittedAt).getTime() - new Date(a.submittedAt).getTime()) + .map(a => ({ ...a })); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/appeal.ts` around lines 39 - 43, getUserAppeals currently returns references to the objects stored in MOCK_APPEALS (unlike submitAppeal and getAppeal which return defensive shallow copies), so callers can mutate the in-memory store; update getUserAppeals to map the filtered/sorted results to shallow copies (e.g., return items via {...a} or equivalent) before returning to maintain the same defensive-copy pattern and protect MOCK_APPEALS from external mutation.lib/services/geo-restriction.ts (1)
62-62: Use the_ipnaming convention instead ofvoid ip;to suppress the unused-parameter warning.
void ip;is non-idiomatic in TypeScript/JavaScript. The conventional way to declare an intentionally unused parameter is to prefix it with_:♻️ Proposed refactor
- static async detectProxy(ip: string): Promise<boolean> { + static async detectProxy(_ip: string): Promise<boolean> { // In production: check proxy databases or use APIs // For now, use similar logic as VPN detection - void ip; return false; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/services/geo-restriction.ts` at line 62, Replace the non-idiomatic "void ip;" statement by renaming the unused parameter "ip" to "_ip" in the function/method in geo-restriction.ts (remove the "void ip;" line); i.e., change the parameter declaration from ip to _ip so the unused-parameter warning is suppressed idiomatically and no inline void statement is needed.app/api/withdrawal/submit/route.ts (1)
24-30: Error type narrowing and safe message extraction look good.One existing (pre-PR) issue worth noting: the catch block always returns HTTP 400 regardless of whether the error is a client mistake (bad input) or an unexpected server-side failure (e.g., service/DB unavailability). A server-side failure should return 500. Consider separating the two cases:
💡 Suggested status-code differentiation
- } catch (error: unknown) { + } catch (error: unknown) { console.error("Error submitting withdrawal:", error); const message = error instanceof Error ? error.message : "Withdrawal failed"; + // Use 400 only for client-caused errors; fall back to 500 for unexpected failures + const isClientError = error instanceof Error && (error as any).statusCode === 400; return NextResponse.json( { error: message }, - { status: 400 } + { status: isClientError ? 400 : 500 } ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/withdrawal/submit/route.ts` around lines 24 - 30, The catch block in the route handler in app/api/withdrawal/submit/route.ts currently always returns 400; change it to return 400 only for client/validation errors and 500 for unexpected server failures: detect client errors by checking known signatures (e.g. error instanceof Error && (error.name === 'ValidationError' || (error as any).status === 400 || (error as any).statusCode === 400)) or by using your app's BadRequest/ValidationError class if available, and call NextResponse.json({ error: message }, { status: 400 }) for those cases, otherwise log the error and return NextResponse.json({ error: message }, { status: 500 }) for server-side failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/api/compliance/terms/route.ts`:
- Around line 9-11: The catch block in the GET handler in
app/api/compliance/terms/route.ts currently swallows errors; update the catch to
log the caught error (include the Error object and a clear message) before
returning the 500 response so failures inside
TermsService.getCurrentTermsVersion() are recorded (match the pattern used in
compliance/status/route.ts, e.g., console.error("Failed to fetch terms:",
error)). Ensure you reference the same handler function where
TermsService.getCurrentTermsVersion() is called and keep the response behavior
unchanged.
In `@app/api/compliance/upgrade/route.ts`:
- Around line 39-41: The GET handler in app/api/compliance/upgrade/route.ts
currently uses a bare catch that swallows exceptions from
VerificationService.getVerificationStatus(); update the catch to accept the
error (e.g., "err") and log the error with the existing logger (or
console.error) before returning the 500 NextResponse so failures are visible;
reference the GET handler and VerificationService.getVerificationStatus() when
locating the change and ensure the logged message provides context (e.g.,
"Failed to fetch verification status") along with the error object.
In `@components/compliance/appeal-dialog.tsx`:
- Around line 33-35: The catch in the AppealDialog component that handles
AppealService.submitAppeal currently forwards error.message verbatim to alert();
instead sanitize and limit what is shown by: normalize the caught error in the
catch block for AppealService.submitAppeal, extract message only if it matches a
safe whitelist/regex or otherwise use a generic "Failed to submit appeal"
fallback, strip newlines/stack traces, and truncate to a short max length (e.g.,
150–200 chars) before calling alert; update the catch (error: unknown) handling
in AppealDialog to perform these checks and transformations so internal server
details are never shown.
In `@components/compliance/document-upload.tsx`:
- Around line 43-45: The catch block that narrows error as Error and passes
error.message to setError can surface unsafe server-originated text in the UI;
change the catch in the upload flow (the try/catch around the onUpload call that
calls setError) to avoid rendering raw error.message by mapping known, safe
error cases to user-friendly messages and otherwise using a stable fallback like
"Upload failed" (while logging the original error for debugging via
console.error or a logger); update the code paths that call setError (and any UI
that reads that state) to use only the sanitized message.
In `@components/wallet/withdrawal-section.tsx`:
- Around line 46-48: The current validationError expression short-circuits when
isAmountWithinBalance is false, so users get no message when amount > balance;
add a new balanceError (e.g. const balanceError = !isAmountWithinBalance ?
'Amount exceeds available balance' : null) and keep validationError based on
validationData as-is, then render balanceError wherever validationError is shown
and include it in any combined error/Alert used by the withdrawal UI
(referencing validationError, balanceError, isAmountWithinBalance,
validationData and canWithdraw to locate the logic and button state).
- Around line 40-44: The effect calling validateWithdrawal on every keystroke
causes race conditions and hides over-balance errors; debounce parsedAmount
(e.g., with a useDebounce hook or lodash debounce) and use the debounced value
in the effect and as the argument to validateWithdrawal (update the effect deps
to [debouncedAmount, isValidAmount, isAmountWithinBalance, validateWithdrawal])
so mutations fire only after the user pauses, and change the error rendering
logic (where isValidAmount and isAmountWithinBalance are checked) to also show a
specific error when isAmountWithinBalance is false so users see why the button
is disabled; key symbols: parsedAmount, validateWithdrawal (from
useValidateWithdrawal), isValidAmount, isAmountWithinBalance.
In `@lib/services/geo-restriction.ts`:
- Around line 59-64: detectProxy currently returns false silently which hides a
compliance gap; create a tracking issue in the repo for "implement proxy
detection for compliance (OFAC risk)" and update the detectProxy function to
reflect its unimplemented status by adding an explicit TODO comment and a clear
runtime signal (e.g., log a warning and either throw a NotImplemented/Runtime
error or return null/undefined) so callers in GeoRestrictionService
(detectProxy) must handle the unknown result; include brief notes in the TODO
referencing recommended providers (IPHub, ip-api.com proxy field, IP2Proxy) and
update any caller logic to treat a non-boolean result as "unknown / require
manual review".
---
Outside diff comments:
In `@components/compliance/limits-display.tsx`:
- Around line 89-93: The progress-bar width currently uses Math.min(100,
percent) which prevents overflow but allows negative percent values; update the
inline style in the component that renders the div with className "bg-primary
h-2 rounded-full transition-all" to clamp percent into the 0–100 range (e.g.,
replace Math.min(100, percent) with a clamp such as Math.max(0, Math.min(100,
percent)) or a small helper like clampPercent(percent)) so the rendered width
never becomes negative or exceeds 100.
---
Nitpick comments:
In `@app/api/withdrawal/submit/route.ts`:
- Around line 24-30: The catch block in the route handler in
app/api/withdrawal/submit/route.ts currently always returns 400; change it to
return 400 only for client/validation errors and 500 for unexpected server
failures: detect client errors by checking known signatures (e.g. error
instanceof Error && (error.name === 'ValidationError' || (error as any).status
=== 400 || (error as any).statusCode === 400)) or by using your app's
BadRequest/ValidationError class if available, and call NextResponse.json({
error: message }, { status: 400 }) for those cases, otherwise log the error and
return NextResponse.json({ error: message }, { status: 500 }) for server-side
failures.
In `@lib/services/appeal.ts`:
- Around line 39-43: getUserAppeals currently returns references to the objects
stored in MOCK_APPEALS (unlike submitAppeal and getAppeal which return defensive
shallow copies), so callers can mutate the in-memory store; update
getUserAppeals to map the filtered/sorted results to shallow copies (e.g.,
return items via {...a} or equivalent) before returning to maintain the same
defensive-copy pattern and protect MOCK_APPEALS from external mutation.
In `@lib/services/geo-restriction.ts`:
- Line 62: Replace the non-idiomatic "void ip;" statement by renaming the unused
parameter "ip" to "_ip" in the function/method in geo-restriction.ts (remove the
"void ip;" line); i.e., change the parameter declaration from ip to _ip so the
unused-parameter warning is suppressed idiomatically and no inline void
statement is needed.
lib/services/geo-restriction.ts
Outdated
| static async detectProxy(ip: string): Promise<boolean> { | ||
| // In production: check proxy databases or use APIs | ||
| // For now, use similar logic as VPN detection | ||
| void ip; | ||
| return false; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check git history for the file
echo "=== Recent commits affecting geo-restriction.ts ==="
git log --oneline --all -20 -- lib/services/geo-restriction.ts
echo -e "\n=== Current detectProxy method ==="
sed -n '59,64p' lib/services/geo-restriction.ts
echo -e "\n=== Check for VPN_INDICATORS in current file ==="
rg "VPN_INDICATORS" lib/services/geo-restriction.ts || echo "VPN_INDICATORS not found in current file"
echo -e "\n=== Check entire codebase for VPN_INDICATORS ==="
rg "VPN_INDICATORS" --type ts --type js || echo "VPN_INDICATORS not found in codebase"Repository: boundlessfi/bounties
Length of output: 669
🏁 Script executed:
#!/bin/bash
# Check the most recent changes to understand context
echo "=== Recent diff for geo-restriction.ts ==="
git diff HEAD~5 HEAD -- lib/services/geo-restriction.ts | head -150
echo -e "\n=== Full content of geo-restriction.ts ==="
wc -l lib/services/geo-restriction.tsRepository: boundlessfi/bounties
Length of output: 232
🏁 Script executed:
#!/bin/bash
# Check the recent commits in detail
echo "=== Commit 6d6fe4c (KYC/compliance changes) ==="
git show 6d6fe4c --stat | head -50
echo -e "\n=== Diff of geo-restriction.ts in commit 6d6fe4c ==="
git show 6d6fe4c -- lib/services/geo-restriction.ts | head -200
echo -e "\n=== Check git log with patch for VPN_INDICATORS ==="
git log --all -p -- lib/services/geo-restriction.ts | grep -A 5 -B 5 "VPN_INDICATORS" | head -100Repository: boundlessfi/bounties
Length of output: 8359
🏁 Script executed:
#!/bin/bash
# Read full file to see context
echo "=== Full geo-restriction.ts file ==="
cat -n lib/services/geo-restriction.tsRepository: boundlessfi/bounties
Length of output: 4795
detectProxy is a non-functional stub in a compliance-sensitive service — track this as a known gap.
This method always returns false, meaning users routing through proxies or anonymizing services will never be flagged as isProxy = true. For a service enforcing OFAC sanctions (Cuba, Iran, North Korea, Syria), an undetected proxy is a realistic bypass vector.
The stub implementation is concerning precisely because it remains incomplete without any heuristic fallback. Recommend opening a tracking issue so this gap doesn't get lost, and consider adding an explicit TODO comment that makes the stub status clear rather than allowing it to appear as working logic. In production, services like IPHub, ip-api.com (proxy field), or IP2Proxy can fill this role.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/services/geo-restriction.ts` around lines 59 - 64, detectProxy currently
returns false silently which hides a compliance gap; create a tracking issue in
the repo for "implement proxy detection for compliance (OFAC risk)" and update
the detectProxy function to reflect its unimplemented status by adding an
explicit TODO comment and a clear runtime signal (e.g., log a warning and either
throw a NotImplemented/Runtime error or return null/undefined) so callers in
GeoRestrictionService (detectProxy) must handle the unknown result; include
brief notes in the TODO referencing recommended providers (IPHub, ip-api.com
proxy field, IP2Proxy) and update any caller logic to treat a non-boolean result
as "unknown / require manual review".
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
app/api/compliance/upgrade/route.ts (1)
20-26:error.messagefrom the service layer is still forwarded to the client in the POST error response.The errors explicitly thrown in
VerificationService.createVerificationRequestare user-friendly domain errors ("You already have a pending verification request", "Target tier must be higher than current tier"). However, this pattern is vulnerable to exposure of internal error details from downstream service calls likeComplianceService.getUserCompliance()orComplianceService.updateVerificationStatus(), or from unexpected async failures. For consistency withappeal-dialog.tsx, which uses a fixed fallback message without exposingerror.message, consider using the safer pattern:🛡️ Suggested safer pattern
- } catch (error: unknown) { - console.error("Error creating verification request:", error); - const message = error instanceof Error ? error.message : "Failed to create verification request"; - return NextResponse.json( - { error: message }, - { status: 400 } - ); + } catch (error: unknown) { + console.error("Error creating verification request:", error); + return NextResponse.json( + { error: "Failed to create verification request" }, + { status: 400 } + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/compliance/upgrade/route.ts` around lines 20 - 26, The catch block currently returns error.message to the client for failures from VerificationService.createVerificationRequest (and downstream calls like ComplianceService.getUserCompliance / ComplianceService.updateVerificationStatus), which can leak internal details; change it to always respond with a fixed, user-safe message (e.g., "Failed to create verification request") while keeping the full error logged server-side (console.error or processLogger) for debugging, and only expose specific domain errors if you have a safe explicit whitelist/type (e.g., a custom DomainError type) — update the catch in route.ts around the POST handler to implement this pattern for VerificationService.createVerificationRequest and downstream failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/profile/`[userId]/page.tsx:
- Around line 59-72: The "In Review" section is unreachable because myClaims
(built in the useMemo in page.tsx) only maps backend statuses to "open",
"claimed", or "completed", which never normalizes to the "in-review" variants
expected by CLAIM_SECTIONS/normalizeStatus; update the mapping in the myClaims
creation (the mapping inside useMemo that uses
flattenBountyPages(infiniteBounties?.pages)) to emit an "in-review" status when
appropriate (for example map a backend review indicator or a specific bounty
field such as bounty.reviewStatus or a combination like bounty.status ===
"claimed" && bounty.isUnderReview to "in-review"), or alternatively remove the
"In Review" entry from CLAIM_SECTIONS in components/reputation/my-claims.tsx so
the UI and backend statuses align. Ensure you reference myClaims,
flattenBountyPages, infiniteBounties, CLAIM_SECTIONS, and normalizeStatus when
making the change.
- Around line 21-34: The current logic auto-pages through all bounties
client-side by calling useInfiniteBounties() with no filters and then filtering
claimedBy === userId, causing unbounded fetches; update the code to avoid
fetching every page: either (a) modify useInfiniteBounties()/the underlying
query to accept a claimedBy/userId parameter so server-side filtering returns
only the user's claimed bounties (update any BountyListParams and server handler
accordingly), or if server changes aren’t yet possible, implement a client-side
safety cap—stop auto-fetching after N pages or remove the automatic
fetchNextPage in useEffect and fetch only a single page or limited pages
(useInfiniteBounties, fetchNextPage, hasNextPage, isFetchingNextPage) to prevent
unbounded pagination and the full skeleton blocking the UI.
In `@components/wallet/withdrawal-section.tsx`:
- Around line 80-84: canWithdraw can become true before the new debounced amount
is validated; update the canWithdraw logic to require the debounce to be settled
and an explicit successful validation result (and no validation in-flight)
before enabling submit. Specifically, modify the canWithdraw predicate that
currently references isValidAmount, isAmountWithinBalance, validationError, and
complianceData to also require debounceSettled && validationData?.valid === true
&& !validationMutation?.isLoading (or the equivalent isValidating flag) so the
button stays disabled during the debounce window and while a validation request
is pending.
---
Duplicate comments:
In `@app/api/compliance/upgrade/route.ts`:
- Around line 30-42: The GET route's previous issues are resolved: the handler
uses a no-argument signature GET() and the catch now logs the error; approve and
merge as-is. No code changes required—keep the GET() function and the
console.error("Error fetching verification status:", error) in the catch block,
and proceed to merge the change.
In `@components/compliance/appeal-dialog.tsx`:
- Around line 33-34: The catch block after calling AppealService.submitAppeal
swallows errors and only shows an alert; update the catch in the submit handler
for AppealDialog to log the error for developer observability by calling
console.error with a clear context string (e.g., "AppealService.submitAppeal
failed") and the caught error object before showing the existing alert, ensuring
you reference the submit handler that calls AppealService.submitAppeal so the
log is colocated with the existing alert.
In `@components/wallet/withdrawal-section.tsx`:
- Line 17: The debounce fix using useDebounce on parsedAmount and wiring the
effect to the debounced-derived booleans (so walletInfo.balance changes are
picked up) looks correct and addresses the stale mutation/validationData race;
keep validateWithdrawal triggered from the effect using the debounced value (and
ensure any mutation handlers guard against overwriting validationData by
validating request identity if present). Also remove the duplicated
review/comment block that was repeated for lines 40-48 so the file has only one
approval comment.
In `@lib/services/geo-restriction.ts`:
- Around line 59-65: The detectProxy method currently uses a non-idiomatic "void
ip" to silence the unused parameter and silently returns false; change the
parameter name from ip to _ip (e.g., detectProxy(_ip: string)) to indicate it is
intentionally unused and remove the void statement, and add a runtime warning
(e.g., console.warn('[GeoRestriction] detectProxy: not implemented — defaulting
to false')) inside detectProxy so callers/operators see that proxy detection is
not yet implemented while preserving the Promise<boolean> return of false.
---
Nitpick comments:
In `@app/api/compliance/upgrade/route.ts`:
- Around line 20-26: The catch block currently returns error.message to the
client for failures from VerificationService.createVerificationRequest (and
downstream calls like ComplianceService.getUserCompliance /
ComplianceService.updateVerificationStatus), which can leak internal details;
change it to always respond with a fixed, user-safe message (e.g., "Failed to
create verification request") while keeping the full error logged server-side
(console.error or processLogger) for debugging, and only expose specific domain
errors if you have a safe explicit whitelist/type (e.g., a custom DomainError
type) — update the catch in route.ts around the POST handler to implement this
pattern for VerificationService.createVerificationRequest and downstream
failures.
app/profile/[userId]/page.tsx
Outdated
| const { | ||
| data: infiniteBounties, | ||
| isLoading: isBountiesLoading, | ||
| isError: isBountiesError, | ||
| fetchNextPage, | ||
| hasNextPage, | ||
| isFetchingNextPage, | ||
| } = useInfiniteBounties(); | ||
|
|
||
| useEffect(() => { | ||
| if (hasNextPage && !isFetchingNextPage) { | ||
| void fetchNextPage(); | ||
| } | ||
| }, [fetchNextPage, hasNextPage, isFetchingNextPage]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "BountyListParams|claimedBy|claimed_by" --type ts -A 15 -B 2Repository: boundlessfi/bounties
Length of output: 25951
Fetching all bounties client-side to filter by user is a major performance issue.
useInfiniteBounties() is called without parameters, so the useEffect waterfall fetches every page of every bounty in the system and then client-side-filters by claimedBy === userId (line 63). This makes the "My Claims" tab's load time and memory footprint grow linearly with the total bounty count, and the full skeleton loading state (line 193) blocks the UI until the last page lands.
Unfortunately, BountyListParams does not currently support a claimedBy or user-filter parameter—the interface only supports status, type, difficulty, projectId, tags, and search. Server-side filtering by claimed bounties would require extending the API before this can be optimized. As a workaround, consider implementing pagination limits or a request timeout to prevent the load from becoming prohibitive as the bounty dataset grows.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/profile/`[userId]/page.tsx around lines 21 - 34, The current logic
auto-pages through all bounties client-side by calling useInfiniteBounties()
with no filters and then filtering claimedBy === userId, causing unbounded
fetches; update the code to avoid fetching every page: either (a) modify
useInfiniteBounties()/the underlying query to accept a claimedBy/userId
parameter so server-side filtering returns only the user's claimed bounties
(update any BountyListParams and server handler accordingly), or if server
changes aren’t yet possible, implement a client-side safety cap—stop
auto-fetching after N pages or remove the automatic fetchNextPage in useEffect
and fetch only a single page or limited pages (useInfiniteBounties,
fetchNextPage, hasNextPage, isFetchingNextPage) to prevent unbounded pagination
and the full skeleton blocking the UI.
app/profile/[userId]/page.tsx
Outdated
| const myClaims = useMemo<MyClaim[]>(() => { | ||
| const bounties = flattenBountyPages(infiniteBounties?.pages); | ||
|
|
||
| return bounties | ||
| .filter((bounty) => bounty.claimedBy === userId) | ||
| .map((bounty) => { | ||
| return { | ||
| bountyId: bounty.id, | ||
| title: bounty.issueTitle, | ||
| status: bounty.status === "closed" ? "completed" : bounty.status, | ||
| rewardAmount: bounty.rewardAmount ?? undefined, | ||
| }; | ||
| }); | ||
| }, [infiniteBounties?.pages, userId]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "getClaimsBySection|normalizeStatus|CLAIM_SECTIONS" --type ts --type tsx -A 15 -B 2Repository: boundlessfi/bounties
Length of output: 91
🏁 Script executed:
rg -n "getClaimsBySection|normalizeStatus|CLAIM_SECTIONS" -A 15 -B 2Repository: boundlessfi/bounties
Length of output: 7811
The "In Review" section will be unreachable with the current status mapping.
The code in page.tsx (lines 59-72) transforms bounty statuses with only one rule: "closed" → "completed". Since backend statuses are limited to 'open' | 'claimed' | 'closed', the resulting MyClaim statuses will be "open", "claimed", or "completed".
However, CLAIM_SECTIONS in components/reputation/my-claims.tsx defines the "In Review" section with expected statuses: ["in-review", "in review", "review", "pending", "under-review"]. The normalizeStatus() function will normalize the page.tsx statuses to "open", "claimed", and "completed" — none of which match the "In Review" section's expected values.
This means the "In Review" section will always render empty. Either:
- Add status transformations in page.tsx to produce
"in-review"(e.g., map a backend status or field to this value), or - Remove "In Review" from
CLAIM_SECTIONSif it's not needed for the current backend structure.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/profile/`[userId]/page.tsx around lines 59 - 72, The "In Review" section
is unreachable because myClaims (built in the useMemo in page.tsx) only maps
backend statuses to "open", "claimed", or "completed", which never normalizes to
the "in-review" variants expected by CLAIM_SECTIONS/normalizeStatus; update the
mapping in the myClaims creation (the mapping inside useMemo that uses
flattenBountyPages(infiniteBounties?.pages)) to emit an "in-review" status when
appropriate (for example map a backend review indicator or a specific bounty
field such as bounty.reviewStatus or a combination like bounty.status ===
"claimed" && bounty.isUnderReview to "in-review"), or alternatively remove the
"In Review" entry from CLAIM_SECTIONS in components/reputation/my-claims.tsx so
the UI and backend statuses align. Ensure you reference myClaims,
flattenBountyPages, infiniteBounties, CLAIM_SECTIONS, and normalizeStatus when
making the change.
|
Hi @Benjtalkshow, Please review for any corrections |
Benjtalkshow
left a comment
There was a problem hiding this comment.
Hi @KingFRANKHOOD ,
Thanks for the modification. There are just a few things to take care of:
- Please fix the merge conflicts.
- Make sure your code builds successfully and is lint-error free (I’ve updated the codebase, so it now compiles fully).
- Fix coderabbit corrections
- Attach screenshot of your implementation
- Pull the latest changes from
mainbefore pushing your updates.Thanks again for your contribution — I really appreciate your efforts!
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
hooks/Use-bounty-detail.ts (1)
1-3: FilenameUse-bounty-detail.tsdeviates from the lowercase hook convention and creates a portability hazard.All sibling hooks in
hooks/follow theuse-*lowercase naming convention (e.g.,use-bounties.ts,use-bounty-mutations.ts). The capitalUin this filename is an exception. While the current import incomponents/bounty-detail/bounty-detail-client.tsxcorrectly matches the actual filename casing, this deviation is risky: a developer expecting the convention might writeuse-bounty-detail(lowercase), which would fail on case-sensitive Linux filesystems (CI/Docker) while succeeding on macOS, causing hard-to-debug build failures.Rename the file to
use-bounty-detail.tsto align with the established convention and update the one import accordingly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/Use-bounty-detail.ts` around lines 1 - 3, The file name Use-bounty-detail.ts uses a capital U and breaks the lowercase hook naming convention which can cause case-sensitive filesystem failures; rename the file to use-bounty-detail.ts and update its import in components/bounty-detail/bounty-detail-client.tsx to match the new lowercase filename so that the exported hook (used via bountyKeys and bountiesApi/Bounty in the file) is resolved consistently across platforms.lib/mock-bounty.ts (1)
44-79: Add mock entries for"in-review"and"completed"statuses to cover all My Claims sections.Only bounty id
"2"is associated with"user-1"and it only populates the "Active Claims" bucket (status: "claimed"). The "In Review" and "Completed" dashboard sections have no testable data for this user, which is consistent with the empty-state screenshot but limits local development and visual testing of those panels.♻️ Suggested mock additions
Add two more entries to
mockBounties— one for each missing section — so all three My Claims panels can be exercised locally:{ id: "2", ... status: "claimed", claimedBy: "user-1", ... }, + { + id: "12", + type: "feature", + issueTitle: "Implement notification preferences", + projectId: "boundless", + projectName: "Boundless", + projectLogoUrl: "/logo.svg", + githubRepo: "boundlessfi/bounties", + githubIssueUrl: "https://github.com/boundlessfi/bounties/issues/12", + issueNumber: 12, + description: "Add user notification preference settings.", + rewardAmount: 250, + rewardCurrency: "USDC", + claimingModel: "single-claim", + difficulty: "beginner", + tags: ["notifications", "settings"], + status: "in-review", + claimedBy: "user-1", + claimedAt: "2026-01-20T00:00:00Z", + createdAt: "2026-01-10T08:00:00Z", + updatedAt: "2026-02-01T11:00:00Z", + }, + { + id: "13", + type: "bug", + issueTitle: "Fix broken avatar upload flow", + projectId: "boundless", + projectName: "Boundless", + projectLogoUrl: "/logo.svg", + githubRepo: "boundlessfi/bounties", + githubIssueUrl: "https://github.com/boundlessfi/bounties/issues/13", + issueNumber: 13, + description: "Avatar upload silently fails on large files.", + rewardAmount: 175, + rewardCurrency: "USDC", + claimingModel: "single-claim", + difficulty: "beginner", + tags: ["bug", "upload", "avatar"], + status: "completed", + claimedBy: "user-1", + claimedAt: "2025-12-01T00:00:00Z", + createdAt: "2025-11-15T08:00:00Z", + updatedAt: "2026-01-10T11:00:00Z", + },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/mock-bounty.ts` around lines 44 - 79, Add two additional objects to the mockBounties array so My Claims covers all panels: create one entry with id "3" (status: "in-review", claimedBy: "user-1", claimedAt set to a recent ISO date) and one entry with id "4" (status: "completed", claimedBy: "user-1", claimedAt and a completed/closed or updatedAt ISO date). Keep other fields (type, projectId/projectName, issueTitle, rewardAmount, difficulty, tags, githubIssueUrl, issueNumber, etc.) consistent with existing entries so components rendering mockBounties can display data for the "In Review" and "Completed" sections.components/bounty/sponsor-review-dashboard.tsx (1)
27-34:useEffectpermanently suppresses server-authoritative status updates for existing items.
currIdMap.get(sub.submissionId) ?? subunconditionally preserves the locally-held item regardless of what the incomingsubmissionsprop says. If the parent re-fetches and supplies a server-confirmed status that differs from the current local (possibly stale) state, it will be silently discarded. This is fine for preventing flicker during an in-flight action, but it also means the component can drift permanently out of sync with the server once any item enters its local state map.Consider restricting local preference to items that are actually in a loading state:
♻️ Proposed refactor
React.useEffect(() => { setItems((curr) => { const currIdMap = new Map(curr.map((it) => [it.submissionId, it])); + const inflightIds = new Set(Object.keys(loadingIds)); return submissions.map( - (sub) => (currIdMap.get(sub.submissionId) ?? sub) as ReviewSubmission, + (sub) => + (inflightIds.has(sub.submissionId) + ? currIdMap.get(sub.submissionId) + : undefined) ?? sub, ); }); }, [submissions]);Note:
loadingIdswould need to be added to the dependency array if used inside the effect.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty/sponsor-review-dashboard.tsx` around lines 27 - 34, The current useEffect in which setItems merges submissions into local state (using currIdMap.get(sub.submissionId) ?? sub) always favors the local item and thus can permanently ignore server-updated statuses; change the merge so it prefers the incoming server-provided sub unless the local item is actively loading — i.e., when mapping submissions, check currIdMap.get(sub.submissionId) and only return the local item when its submissionId is present in a loadingIds set/array, otherwise return the server sub (and add loadingIds to the effect dependency array); update references to useEffect, setItems, currIdMap, submissions, loadingIds, and ReviewSubmission accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Around line 38-56: The updater is mutating external variable prevItem inside
the setItems updater, which breaks rollback in React Strict Mode; instead,
capture the original item from the current items snapshot before calling
setItems (e.g., const prevItem = items.find(it => it.submissionId === id)), then
call setItems with a pure updater that only returns the new array (no side
effects). Use that captured prevItem for the catch rollback logic so rollback
restores the true previous ReviewSubmission, and reference the existing symbols
id, action, setItems, items, and ReviewSubmission to locate and update the code.
---
Nitpick comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Around line 27-34: The current useEffect in which setItems merges submissions
into local state (using currIdMap.get(sub.submissionId) ?? sub) always favors
the local item and thus can permanently ignore server-updated statuses; change
the merge so it prefers the incoming server-provided sub unless the local item
is actively loading — i.e., when mapping submissions, check
currIdMap.get(sub.submissionId) and only return the local item when its
submissionId is present in a loadingIds set/array, otherwise return the server
sub (and add loadingIds to the effect dependency array); update references to
useEffect, setItems, currIdMap, submissions, loadingIds, and ReviewSubmission
accordingly.
In `@hooks/Use-bounty-detail.ts`:
- Around line 1-3: The file name Use-bounty-detail.ts uses a capital U and
breaks the lowercase hook naming convention which can cause case-sensitive
filesystem failures; rename the file to use-bounty-detail.ts and update its
import in components/bounty-detail/bounty-detail-client.tsx to match the new
lowercase filename so that the exported hook (used via bountyKeys and
bountiesApi/Bounty in the file) is resolved consistently across platforms.
In `@lib/mock-bounty.ts`:
- Around line 44-79: Add two additional objects to the mockBounties array so My
Claims covers all panels: create one entry with id "3" (status: "in-review",
claimedBy: "user-1", claimedAt set to a recent ISO date) and one entry with id
"4" (status: "completed", claimedBy: "user-1", claimedAt and a completed/closed
or updatedAt ISO date). Keep other fields (type, projectId/projectName,
issueTitle, rewardAmount, difficulty, tags, githubIssueUrl, issueNumber, etc.)
consistent with existing entries so components rendering mockBounties can
display data for the "In Review" and "Completed" sections.
| let prevItem: ReviewSubmission | undefined; | ||
|
|
||
| setItems(curr => curr.map(it => { | ||
| if (it.submissionId === id) { | ||
| prevItem = it | ||
| return { | ||
| ...it, | ||
| status: action === 'approve' ? 'approved' : action === 'reject' ? 'rejected' : 'revision_requested' | ||
| setItems((curr) => | ||
| curr.map((it) => { | ||
| if (it.submissionId === id) { | ||
| prevItem = it; | ||
| return { | ||
| ...it, | ||
| status: | ||
| action === "approve" | ||
| ? "approved" | ||
| : action === "reject" | ||
| ? "rejected" | ||
| : "revision_requested", | ||
| }; | ||
| } | ||
| } | ||
| return it | ||
| })) | ||
| return it; | ||
| }), | ||
| ); |
There was a problem hiding this comment.
Side effect inside setItems updater breaks rollback in React Strict Mode.
prevItem is captured inside the setItems updater on Line 43. In React 19 Strict Mode (development), React invokes updater functions twice to detect side effects — the second invocation receives the result of the first (already-optimistically-mutated) state, so prevItem ends up holding the updated item rather than the original. The catch branch then "rolls back" to the optimistically-updated value, making error recovery silently a no-op in development.
Fix by reading prevItem from the items state snapshot captured in the closure before setItems is called:
🛡️ Proposed fix
const handleAction = async (id: string, action: Action) => {
setLoadingIds((s) => ({ ...s, [id]: true }));
- let prevItem: ReviewSubmission | undefined;
+ const prevItem = items.find((it) => it.submissionId === id);
- setItems((curr) =>
- curr.map((it) => {
- if (it.submissionId === id) {
- prevItem = it;
- return {
- ...it,
- status:
- action === "approve"
- ? "approved"
- : action === "reject"
- ? "rejected"
- : "revision_requested",
- };
- }
- return it;
- }),
- );
+ setItems((curr) =>
+ curr.map((it) =>
+ it.submissionId === id
+ ? {
+ ...it,
+ status:
+ action === "approve"
+ ? "approved"
+ : action === "reject"
+ ? "rejected"
+ : "revision_requested",
+ }
+ : it,
+ ),
+ );📝 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.
| let prevItem: ReviewSubmission | undefined; | |
| setItems(curr => curr.map(it => { | |
| if (it.submissionId === id) { | |
| prevItem = it | |
| return { | |
| ...it, | |
| status: action === 'approve' ? 'approved' : action === 'reject' ? 'rejected' : 'revision_requested' | |
| setItems((curr) => | |
| curr.map((it) => { | |
| if (it.submissionId === id) { | |
| prevItem = it; | |
| return { | |
| ...it, | |
| status: | |
| action === "approve" | |
| ? "approved" | |
| : action === "reject" | |
| ? "rejected" | |
| : "revision_requested", | |
| }; | |
| } | |
| } | |
| return it | |
| })) | |
| return it; | |
| }), | |
| ); | |
| const prevItem = items.find((it) => it.submissionId === id); | |
| setItems((curr) => | |
| curr.map((it) => | |
| it.submissionId === id | |
| ? { | |
| ...it, | |
| status: | |
| action === "approve" | |
| ? "approved" | |
| : action === "reject" | |
| ? "rejected" | |
| : "revision_requested", | |
| } | |
| : it, | |
| ), | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/bounty/sponsor-review-dashboard.tsx` around lines 38 - 56, The
updater is mutating external variable prevItem inside the setItems updater,
which breaks rollback in React Strict Mode; instead, capture the original item
from the current items snapshot before calling setItems (e.g., const prevItem =
items.find(it => it.submissionId === id)), then call setItems with a pure
updater that only returns the new array (no side effects). Use that captured
prevItem for the catch rollback logic so rollback restores the true previous
ReviewSubmission, and reference the existing symbols id, action, setItems,
items, and ReviewSubmission to locate and update the code.
I believe I have fixed it all, Please review and I'm open to correct whatever needs to be corrected |
Implement User Dashboard – My Claims Closes #83
Dashboard section for tracking claimed work.
TASKS
Implementations
Screenshot
Summary by CodeRabbit
New Features
Bug Fixes
Chores