Feat: implement single claim model logic#109
Feat: implement single claim model logic#109Bosun-Josh121 wants to merge 2 commits intoboundlessfi:mainfrom
Conversation
|
@Bosun-Josh121 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughAdds a nested claimInfo structure and integrates single-claim logic across API, types, hooks, and UI; implements submission dialog and submission API; replaces Apollo with graphql-request/react-query client; adds completion-history API/hook and various UI guards and null-safety fixes. Changes
Sequence DiagramsequenceDiagram
participant User
participant UI as Bounty UI
participant Hook as useClaimBounty
participant API as Bounties API
participant Cache as Query Cache
User->>UI: Click "Claim"
activate UI
UI->>Hook: mutate(claim)
deactivate UI
activate Hook
Hook->>API: POST /bounties/{id}/claim (empty body)
deactivate Hook
activate API
API-->>Hook: returns either full bounty data or claimInfo
deactivate API
activate Hook
alt Full bounty returned
Hook->>Cache: replace bounty detail with API data
else
Hook->>Cache: patch bounty detail.status = "claimed", add claimInfo
end
Hook->>Cache: invalidate bounty lists
Hook-->>UI: success
deactivate Hook
UI->>UI: render ClaimantProfile, disable Claim button
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ 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: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
lib/mock-bounty.ts (1)
186-193:⚠️ Potential issue | 🟡 MinorBounty
id: "6"hasstatus: "claimed"but noclaimInfo— the sidebar will show "Already Claimed" with an empty claimant section.The
SidebarCTArenders<ClaimantProfile>whenstatus === "claimed", butClaimantProfilereturnsnullwhenclaimInfois missing. This means the "Claimed By" card silently disappears, leaving the user wondering who claimed it.Add mock
claimInfoto keep the test data consistent, or note this as an intentional edge-case test.Proposed fix
status: "claimed", + claimInfo: { + claimedBy: { + userId: "user-2", + username: "refactor_pro", + avatarUrl: "https://github.com/shadcn.png", + }, + claimedAt: "2025-01-21T16:45:00Z", + }, createdAt: "2025-01-18T08: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 186 - 193, Bounty with id "6" has status "claimed" but lacks claimInfo, causing SidebarCTA -> ClaimantProfile to render null and hide the "Claimed By" card; update the mock in lib/mock-bounty.ts for the object with id "6" to include a realistic claimInfo object (e.g., claimant name/handle/avatar, claimantAddress, claimedAt timestamp, and claimedAmount) so ClaimantProfile receives the data it expects, or explicitly mark this entry as an intentional edge-case in a comment if you want the missing-claimInfo behavior tested; reference SidebarCTA and ClaimantProfile when adding the fields to ensure shape matches what ClaimantProfile reads.lib/api/bounties.ts (1)
63-67:⚠️ Potential issue | 🔴 CriticalConsolidate bounty type definitions to eliminate dual/triple source confusion.
Three separate type definition sources now exist for
Bounty,BountyType,BountyStatus,ClaimingModel, andDifficultyLevel:
types/bounty.ts(manually defined)lib/api/bounties.ts(Zod-inferred)lib/types.ts(alternative manual definitions)Divergences confirmed:
ClaimingModel:types/bounty.tsincludes"milestone"(absent fromlib/api/bounties.tsZod schema)BountyStatus:types/bounty.tsandlib/api/bounties.tsuse"open" | "claimed" | "closed", whilelib/types.tsdefines completely different values ("open" | "in-progress" | "completed")Codebase usage shows active three-way split: hooks and query layers import from
@/lib/api, some UI components import from@/types/bounty, and legacy code imports from@/lib/types. This creates genuine import confusion risk.Choose a single authoritative source and derive all other type exports from it, or align all three definitions and designate one.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/bounties.ts` around lines 63 - 67, There are three conflicting definitions for Bounty-related types; pick one authoritative source (prefer the Zod schemas: bountySchema, bountyTypeSchema, bountyStatusSchema, difficultySchema, claimingModelSchema) and update all other exports to be derived from that source (replace manual typings Bounty, BountyType, BountyStatus, DifficultyLevel, ClaimingModel in other modules with z.infer<typeof ...> references), ensuring the claimingModelSchema is extended to include "milestone" if needed and reconciling BountyStatus values to match the chosen canonical set; update exports/imports so all code imports these unified z.infer types (or export a single re-exporting barrel) to eliminate the three-way split.
🧹 Nitpick comments (2)
components/bounty-detail/bounty-detail-sidebar-cta.tsx (1)
33-44: Duplicated claim/action logic betweenSidebarCTAandMobileCTA.
handleAction, the CTA label function, and theclaimMutationsetup are nearly identical in both components. Consider extracting into a shared hook (e.g.,useClaimAction(bounty)) that returns{ handleAction, ctaLabel, isPending }to keep them in sync and reduce maintenance burden.Also applies to: 204-214
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 33 - 44, Duplicate claim/action logic in SidebarCTA and MobileCTA should be extracted into a shared hook: create useClaimAction(bounty) that encapsulates the claimMutation setup and computes ctaLabel and isPending, and exposes handleAction; move the existing claimMutation.mutate logic and the branch on bounty.claimingModel ("single-claim" vs opening bounty.githubIssueUrl) into this hook, have SidebarCTA and MobileCTA call useClaimAction(bounty) and use the returned { handleAction, ctaLabel, isPending } to render the button and toast behavior so both components stay in sync.lib/api/bounties.ts (1)
52-54: FlatclaimedAt/claimedBycoexist with nestedclaimInfo— clarify intent.The schema keeps the legacy flat fields (
claimedAt,claimedByas a plain string) alongside the new nestedclaimInfo. If these are kept for backward compatibility, that's fine, but UI code now reads fromclaimInfoexclusively. Document or add a TODO indicating when the flat fields can be removed to avoid confusion about which is the canonical source.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/bounties.ts` around lines 52 - 54, The schema currently defines legacy flat fields claimedAt and claimedBy alongside the new nested claimInfo (claimInfoSchema), which causes ambiguity since UI code reads exclusively from claimInfo; add a clear in-code comment/TODO above this schema explaining that claimInfo is the canonical source, that claimedAt/claimedBy are kept only for backward compatibility, and include when or under what condition they can be removed (e.g., after migrating clients or a specific release/milestone); also update any related function/comment references to claimInfoSchema to state the preferred field so future maintainers know which to use.
🤖 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-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 33-43: handleAction currently calls claimMutation for
"single-claim" bounties without checking auth; use authClient.useSession() (same
hook used in use-socket-sync.ts) to verify the session at the start of
handleAction and in the MobileCTA equivalent, and if there is no authenticated
session either redirect to the login flow or show a specific toast/notification
and return early; only call claimMutation.mutate(bounty.id, ...) when the
session is present, and ensure the error path still handles API errors as a
fallback.
In `@hooks/use-bounty-mutations.ts`:
- Around line 104-126: onSuccess's fallback in queryClient.setQueryData (used in
bountyKeys.detail) writes hardcoded mock claimant data; replace that by
obtaining the real current user from the auth/session context (e.g., useAuth or
getCurrentUser) and use that user's id/username/avatar when patching claimInfo,
and validate the API response with the parseBounty Zod schema (instead of only
checking data.projectName) before deciding to return data vs patching old; if
you need a short-term marker, add a clear TODO in the onSuccess fallback noting
"replace with real user context" so it isn't left as mock data.
In `@lib/api/bounties.ts`:
- Around line 15-20: The Zod enum claimingModelSchema in lib/api/bounties.ts is
missing the "milestone" variant defined in types/bounty.ts; update
claimingModelSchema (the const named claimingModelSchema) to include "milestone"
so parseBounty and other Zod validation accepts that variant, or alternatively
remove "milestone" from the ClaimingModel union in types/bounty.ts if that
variant is not intended—make the two definitions consistent.
In `@types/bounty.ts`:
- Around line 8-13: The union type ClaimingModel declares a "milestone" variant
but claimingModelSchema in lib/api/bounties.ts does not include "milestone",
causing runtime validation to reject valid types; update claimingModelSchema to
include "milestone" as an allowed literal (or remove "milestone" from the
ClaimingModel type) so both the TypeScript type ClaimingModel and the Zod schema
claimingModelSchema match exactly; locate ClaimingModel in types/bounty.ts and
claimingModelSchema in lib/api/bounties.ts to apply the change.
- Line 46: There are two incompatible Bounty status unions causing type
mismatches; unify them by defining a single source-of-truth BountyStatus (e.g.,
export type BountyStatus = "open" | "claimed" | "closed" | "in-progress" |
"completed" or pick the canonical set) and update all Bounty/Bounty type
definitions to reference that single type (ensure types/bounty.ts, lib/types.ts
and lib/api/bounties.ts import/use the same BountyStatus) and then adjust any
usage in components like Bounty (and components/cards/bounty-card.tsx) or
functions that expect the previous statuses so they accept the unified
enum/union; update or add a central types module exporting BountyStatus and have
all modules import from it.
---
Outside diff comments:
In `@lib/api/bounties.ts`:
- Around line 63-67: There are three conflicting definitions for Bounty-related
types; pick one authoritative source (prefer the Zod schemas: bountySchema,
bountyTypeSchema, bountyStatusSchema, difficultySchema, claimingModelSchema) and
update all other exports to be derived from that source (replace manual typings
Bounty, BountyType, BountyStatus, DifficultyLevel, ClaimingModel in other
modules with z.infer<typeof ...> references), ensuring the claimingModelSchema
is extended to include "milestone" if needed and reconciling BountyStatus values
to match the chosen canonical set; update exports/imports so all code imports
these unified z.infer types (or export a single re-exporting barrel) to
eliminate the three-way split.
In `@lib/mock-bounty.ts`:
- Around line 186-193: Bounty with id "6" has status "claimed" but lacks
claimInfo, causing SidebarCTA -> ClaimantProfile to render null and hide the
"Claimed By" card; update the mock in lib/mock-bounty.ts for the object with id
"6" to include a realistic claimInfo object (e.g., claimant name/handle/avatar,
claimantAddress, claimedAt timestamp, and claimedAmount) so ClaimantProfile
receives the data it expects, or explicitly mark this entry as an intentional
edge-case in a comment if you want the missing-claimInfo behavior tested;
reference SidebarCTA and ClaimantProfile when adding the fields to ensure shape
matches what ClaimantProfile reads.
---
Nitpick comments:
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 33-44: Duplicate claim/action logic in SidebarCTA and MobileCTA
should be extracted into a shared hook: create useClaimAction(bounty) that
encapsulates the claimMutation setup and computes ctaLabel and isPending, and
exposes handleAction; move the existing claimMutation.mutate logic and the
branch on bounty.claimingModel ("single-claim" vs opening bounty.githubIssueUrl)
into this hook, have SidebarCTA and MobileCTA call useClaimAction(bounty) and
use the returned { handleAction, ctaLabel, isPending } to render the button and
toast behavior so both components stay in sync.
In `@lib/api/bounties.ts`:
- Around line 52-54: The schema currently defines legacy flat fields claimedAt
and claimedBy alongside the new nested claimInfo (claimInfoSchema), which causes
ambiguity since UI code reads exclusively from claimInfo; add a clear in-code
comment/TODO above this schema explaining that claimInfo is the canonical
source, that claimedAt/claimedBy are kept only for backward compatibility, and
include when or under what condition they can be removed (e.g., after migrating
clients or a specific release/milestone); also update any related
function/comment references to claimInfoSchema to state the preferred field so
future maintainers know which to use.
| const handleAction = () => { | ||
| if (!canAct) return; | ||
|
|
||
| if (bounty.claimingModel === "single-claim") { | ||
| claimMutation.mutate(bounty.id, { | ||
| onSuccess: () => toast.success("Bounty claimed successfully!"), | ||
| onError: (err) => toast.error(err.message || "Failed to claim bounty"), | ||
| }); | ||
| } else { | ||
| window.open(bounty.githubIssueUrl, "_blank", "noopener,noreferrer"); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there's an auth context/hook used elsewhere that could be leveraged here
rg -n "useAuth\|useSession\|useUser\|isAuthenticated" --type=ts -g '!node_modules' -C2Repository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
#!/bin/bash
# First, locate and read the component file
find . -name "bounty-detail-sidebar-cta.tsx" -type f 2>/dev/null | head -5Repository: boundlessfi/bounties
Length of output: 120
🏁 Script executed:
#!/bin/bash
# Read the component file to understand the mutation setup
cat -n "./components/bounty-detail/bounty-detail-sidebar-cta.tsx"Repository: boundlessfi/bounties
Length of output: 11817
🏁 Script executed:
#!/bin/bash
# Find and read the bounty mutations hook
find . -name "use-bounty-mutations*" -type f 2>/dev/nullRepository: boundlessfi/bounties
Length of output: 95
🏁 Script executed:
#!/bin/bash
# Read the bounty mutations hook
cat -n "./hooks/use-bounty-mutations.ts"Repository: boundlessfi/bounties
Length of output: 4725
🏁 Script executed:
#!/bin/bash
# Find the API implementation
find . -path "*/lib/api*" -type f 2>/dev/null | head -10Repository: boundlessfi/bounties
Length of output: 315
🏁 Script executed:
#!/bin/bash
# Read the bounties API implementation
cat -n "./lib/api/bounties.ts"Repository: boundlessfi/bounties
Length of output: 4852
🏁 Script executed:
#!/bin/bash
# Read the API client implementation to check for auth handling
cat -n "./lib/api/client.ts"Repository: boundlessfi/bounties
Length of output: 4964
🏁 Script executed:
#!/bin/bash
# Search for auth-related context or hooks
find . -path "*/hooks/*auth*" -o -path "*/context/*auth*" -o -path "*/lib/*auth*" 2>/dev/null | grep -v node_modules | head -20Repository: boundlessfi/bounties
Length of output: 105
🏁 Script executed:
#!/bin/bash
# Read the auth client to see if there's a hook to check auth state
cat -n "./lib/auth-client.ts"Repository: boundlessfi/bounties
Length of output: 714
🏁 Script executed:
#!/bin/bash
# Search for how authClient is used and if there's a useSession hook available
rg "authClient\.\$" --type=ts -A 2 | head -30Repository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Search for uses of better-auth hooks like useSession
rg "useSession\|authClient\.\\\$" --type=ts -C 2 | head -50Repository: boundlessfi/bounties
Length of output: 46
🏁 Script executed:
#!/bin/bash
# Search for better-auth usage patterns in the codebase
rg "better-auth" --type=ts | head -20Repository: boundlessfi/bounties
Length of output: 382
🏁 Script executed:
#!/bin/bash
# Search for useAuth or session-related patterns in the codebase
rg "authClient\.\\\$|use.*[Aa]uth|use.*[Ss]ession" --type=tsx --type=ts | head -30Repository: boundlessfi/bounties
Length of output: 91
🏁 Script executed:
#!/bin/bash
# Search for auth patterns more broadly
rg "authClient\.\\\$|use.*Auth|useSession" --type=ts -C 2 | head -50Repository: boundlessfi/bounties
Length of output: 999
🏁 Script executed:
#!/bin/bash
# Check how useSession is used and if it's available for the component
rg "authClient\.useSession" --type=ts -C 3Repository: boundlessfi/bounties
Length of output: 484
Add authentication check before claiming bounty.
Unauthenticated users can trigger the claim mutation, which will fail at the API with a 401/403 error. Since authClient.useSession() is available (as used in use-socket-sync.ts), check auth state before mutating and either redirect to login or display an appropriate message instead of relying on generic API errors.
The same issue exists in the MobileCTA component.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 33 - 43,
handleAction currently calls claimMutation for "single-claim" bounties without
checking auth; use authClient.useSession() (same hook used in
use-socket-sync.ts) to verify the session at the start of handleAction and in
the MobileCTA equivalent, and if there is no authenticated session either
redirect to the login flow or show a specific toast/notification and return
early; only call claimMutation.mutate(bounty.id, ...) when the session is
present, and ensure the error path still handles API errors as a fallback.
| onSuccess: (data, id) => { | ||
| queryClient.setQueryData<Bounty>(bountyKeys.detail(id), (old) => { | ||
| if (!old) return old; | ||
|
|
||
| // If the API returned a perfectly valid object with a projectName, use it | ||
| if (data && data.projectName) { | ||
| return data; | ||
| } | ||
|
|
||
| // Otherwise, safely patch the existing data with the claimed state | ||
| return { | ||
| ...old, | ||
| status: "claimed", | ||
| claimInfo: { | ||
| claimedBy: { | ||
| userId: "current-user", | ||
| username: "You (Mock User)", | ||
| avatarUrl: "https://github.com/shadcn.png", | ||
| }, | ||
| claimedAt: new Date().toISOString(), | ||
| }, | ||
| }; | ||
| }); |
There was a problem hiding this comment.
Hardcoded mock user data in onSuccess fallback will produce incorrect claim info in production.
When the API response lacks projectName, the fallback path writes a fabricated user ("current-user", "You (Mock User)", shadcn avatar) into the query cache. In production, if the claim endpoint returns a partial response (e.g., missing projectName but valid otherwise), every claim will show this mock identity instead of the actual claimant.
The actual current user's identity should come from auth state (e.g., a session/user context), and the validity check on data.projectName is a fragile proxy for "is this a complete response."
Consider:
- Fetching the current user from an auth context/hook.
- Validating the response with the Zod schema (
parseBounty) instead of checking a single field. - At minimum, marking this as a
// TODO: replace with real user contextso it's not forgotten.
Sketch of a safer approach
- onSuccess: (data, id) => {
- queryClient.setQueryData<Bounty>(bountyKeys.detail(id), (old) => {
- if (!old) return old;
-
- // If the API returned a perfectly valid object with a projectName, use it
- if (data && data.projectName) {
- return data;
- }
-
- // Otherwise, safely patch the existing data with the claimed state
- return {
- ...old,
- status: "claimed",
- claimInfo: {
- claimedBy: {
- userId: "current-user",
- username: "You (Mock User)",
- avatarUrl: "https://github.com/shadcn.png",
- },
- claimedAt: new Date().toISOString(),
- },
- };
- });
+ onSuccess: (data, id) => {
+ // Always use the API response as the source of truth
+ queryClient.setQueryData<Bounty>(bountyKeys.detail(id), (old) => {
+ if (!old) return old;
+ return { ...old, ...data };
+ });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hooks/use-bounty-mutations.ts` around lines 104 - 126, onSuccess's fallback
in queryClient.setQueryData (used in bountyKeys.detail) writes hardcoded mock
claimant data; replace that by obtaining the real current user from the
auth/session context (e.g., useAuth or getCurrentUser) and use that user's
id/username/avatar when patching claimInfo, and validate the API response with
the parseBounty Zod schema (instead of only checking data.projectName) before
deciding to return data vs patching old; if you need a short-term marker, add a
clear TODO in the onSuccess fallback noting "replace with real user context" so
it isn't left as mock data.
| const claimingModelSchema = z.enum([ | ||
| "single-claim", | ||
| "application", | ||
| "competition", | ||
| "multi-winner", | ||
| ]); |
There was a problem hiding this comment.
ClaimingModel mismatch: "milestone" is missing from the Zod schema.
types/bounty.ts (line 13) defines ClaimingModel with a "milestone" variant, but claimingModelSchema here only has four values. If API data ever contains "milestone", Zod validation via parseBounty will reject it at runtime.
Either add "milestone" here or remove it from types/bounty.ts to keep both in sync.
Proposed fix
const claimingModelSchema = z.enum([
"single-claim",
"application",
"competition",
"multi-winner",
+ "milestone",
]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const claimingModelSchema = z.enum([ | |
| "single-claim", | |
| "application", | |
| "competition", | |
| "multi-winner", | |
| ]); | |
| const claimingModelSchema = z.enum([ | |
| "single-claim", | |
| "application", | |
| "competition", | |
| "multi-winner", | |
| "milestone", | |
| ]); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/api/bounties.ts` around lines 15 - 20, The Zod enum claimingModelSchema
in lib/api/bounties.ts is missing the "milestone" variant defined in
types/bounty.ts; update claimingModelSchema (the const named
claimingModelSchema) to include "milestone" so parseBounty and other Zod
validation accepts that variant, or alternatively remove "milestone" from the
ClaimingModel union in types/bounty.ts if that variant is not intended—make the
two definitions consistent.
| export type ClaimingModel = | ||
| | "single-claim" | ||
| | "application" | ||
| | "competition" | ||
| | "multi-winner" | ||
| | "milestone"; |
There was a problem hiding this comment.
"milestone" variant exists here but not in the Zod schema — runtime validation mismatch.
claimingModelSchema in lib/api/bounties.ts only has four values and will reject any bounty with claimingModel: "milestone". Either add it to the Zod schema or remove it here. This is the root cause of the mismatch flagged in the schema file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@types/bounty.ts` around lines 8 - 13, The union type ClaimingModel declares a
"milestone" variant but claimingModelSchema in lib/api/bounties.ts does not
include "milestone", causing runtime validation to reject valid types; update
claimingModelSchema to include "milestone" as an allowed literal (or remove
"milestone" from the ClaimingModel type) so both the TypeScript type
ClaimingModel and the Zod schema claimingModelSchema match exactly; locate
ClaimingModel in types/bounty.ts and claimingModelSchema in lib/api/bounties.ts
to apply the change.
| tags: string[]; | ||
|
|
||
| status: 'open' | 'claimed' | 'closed' | ||
| status: "open" | "claimed" | "closed"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find all BountyStatus definitions and usages
echo "=== BountyStatus type/interface definitions ==="
rg -n "type\s+BountyStatus|interface\s+BountyStatus" --type=ts
echo ""
echo "=== Files containing 'BountyStatus' references ==="
rg -n "BountyStatus" --type=ts -l
echo ""
echo "=== Bounty type/interface definitions ==="
rg -n "type\s+Bounty\b|interface\s+Bounty\b" --type=ts -A5Repository: boundlessfi/bounties
Length of output: 4161
🏁 Script executed:
#!/bin/bash
# Read types/bounty.ts to see the full Bounty interface
echo "=== types/bounty.ts (full file) ==="
cat -n types/bounty.ts
echo ""
echo "=== lib/api/bounties.ts (Zod schemas) ==="
rg -n "bountyStatusSchema|bountySchema" --type=ts -A10 lib/api/bounties.ts
echo ""
echo "=== lib/types.ts (BountyStatus and Bounty definitions) ==="
rg -n "export type BountyStatus|export interface Bounty" --type=ts -A5 lib/types.tsRepository: boundlessfi/bounties
Length of output: 4678
🏁 Script executed:
#!/bin/bash
# Check lib/api/index.ts to see which BountyStatus is re-exported
echo "=== lib/api/index.ts (full file) ==="
cat -n lib/api/index.ts
echo ""
echo "=== Check imports of BountyStatus in consuming files ==="
rg -n "import.*BountyStatus|from.*lib/types|from.*lib/api" --type=ts -B1 -A1 | head -100
echo ""
echo "=== lib/types.ts context - is it part of public API? ==="
head -n 50 lib/types.tsRepository: boundlessfi/bounties
Length of output: 8539
Consolidate conflicting BountyStatus definitions across modules.
Two incompatible BountyStatus definitions exist:
types/bounty.tsandlib/api/bounties.ts:"open" | "claimed" | "closed"lib/types.ts:"open" | "in-progress" | "completed"
At least one component (components/cards/bounty-card.tsx) imports Bounty from lib/types, potentially causing type mismatches when passed to functions expecting the lib/api Bounty type with different status values.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@types/bounty.ts` at line 46, There are two incompatible Bounty status unions
causing type mismatches; unify them by defining a single source-of-truth
BountyStatus (e.g., export type BountyStatus = "open" | "claimed" | "closed" |
"in-progress" | "completed" or pick the canonical set) and update all
Bounty/Bounty type definitions to reference that single type (ensure
types/bounty.ts, lib/types.ts and lib/api/bounties.ts import/use the same
BountyStatus) and then adjust any usage in components like Bounty (and
components/cards/bounty-card.tsx) or functions that expect the previous statuses
so they accept the unified enum/union; update or add a central types module
exporting BountyStatus and have all modules import from it.
6869305 to
8806d35
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
providers/query-provider.tsx (1)
18-21:⚠️ Potential issue | 🟠 MajorRemove global
retryfrom mutations — risky for non-idempotent operations.TanStack Query v5 intentionally defaults
mutations.retryto0. Setting it to3globally means every mutation (claim submissions, deletes, updates) will be automatically retried up to 3 times on failure. For the single-claim model introduced in this PR, a transient network error during the "claim bounty" mutation could trigger two or three additional POST requests, potentially creating duplicate claim records or corrupting the claimed state server-side.Non-idempotent mutations must opt in to retry individually when safe to do so, not inherit a global default.
🔧 Proposed fix
mutations: { - retry: 3, - retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), },If retry is needed for specific mutations (e.g., idempotent updates), configure it per-mutation at the call site:
useMutation({ mutationFn: ..., retry: 3, retryDelay: ... })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@providers/query-provider.tsx` around lines 18 - 21, The global mutations config currently sets mutations.retry and mutations.retryDelay in the query client/provider; remove mutations.retry (and related global retryDelay) so mutations inherit the v5 default of 0 and do not automatically retry non-idempotent operations, then document/encourage using per-mutation retry options at call sites (e.g., useMutation({ mutationFn: ..., retry: 3, retryDelay: ... })) for any idempotent-safe mutations; update the code that defines the mutations config (the mutations object used when creating the QueryClient/QueryProvider) to drop the global retry settings.lib/api/bounties.ts (1)
35-69:⚠️ Potential issue | 🟠 MajorConsolidate competing
Bountytype definitions — API schema is missing required fieldsThe Zod-inferred
Bountytype frombountySchema(line 65) is missing fields that theBountyinterface intypes/bounty.tsdeclares:milestones,applicants,competitors, andmembers. Additionally, the schema'sclaimingModelenum is missing the"milestone"option present in the types.This causes real inconsistency: files like
hooks/use-bounty-mutations.tsandcomponents/bounty-detailcomponents importBountyfrom@/lib/apiand lack these fields, whilelib/store.tsand other components import from@/types/bountyand have them. Evencomponents/bounty/bounty-list.tsximports from both sources (lines 9-10), creating ambiguity.The narrower API schema should either be expanded to include all fields the interface declares, or the interface should be derived from the schema to maintain a single source of truth.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/bounties.ts` around lines 35 - 69, The bountySchema-derived Bounty type is missing fields and an enum option used elsewhere; update bountySchema and claimingModelSchema so the schema includes the missing array/object fields (milestones, applicants, competitors, members) with appropriate types (e.g., z.array(...) or z.record/.../nullable as per types/bounty.ts) and add the "milestone" option to claimingModelSchema, or alternatively remove the separate interface and export Bounty by inferring z.infer<typeof bountySchema> everywhere to enforce a single source of truth; ensure you modify the symbols bountySchema, claimingModelSchema and any exported Bounty type usages so imports across hooks/components use the same schema-derived type.
🧹 Nitpick comments (13)
app/transparency/page.tsx (1)
101-138: DeadstatsErrorbranch instatCardsvalues.The
statsError ? "—" : …ternary in each card'svalue(lines 104, 113, 122, 131) is unreachable because the entire stats grid is conditionally excluded whenstatsErroristrue(line 178). The "—" values are computed but never rendered.This isn't harmful, but it's slightly misleading for future readers. Consider simplifying the
valueexpressions to only handle thestats/ fallback case, since the error state is already gated at the rendering level.♻️ Simplified statCards values (example for one card)
{ title: "Total Funds Distributed", - value: statsError - ? "—" - : stats - ? `$${stats.totalFundsDistributed.toLocaleString()}` - : "$0", + value: stats + ? `$${stats.totalFundsDistributed.toLocaleString()}` + : "$0", icon: DollarSign, },Apply the same pattern to the other three cards.
Also applies to: 177-195
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/transparency/page.tsx` around lines 101 - 138, The statCards array currently uses a dead branch conditional (statsError ? "—" : ...) even though the component already gates rendering when statsError is true; update the statCards entries in statCards so each card's value only checks stats (e.g., value: stats ? `<formatted>` : `<fallback>`) removing the statsError ternary, and apply the same simplification wherever the same pattern appears in this file (the stats grid rendering logic that already hides the grid on statsError). Ensure you update all four cards (titles: "Total Funds Distributed", "Contributors Paid", "Projects Funded", "Avg. Payout Time") to use only stats-based conditionals.providers/query-provider.tsx (1)
50-55: Devtools import is bundled in production despite the render guard.The static
importat line 4 means@tanstack/react-query-devtoolsis included in the production JS bundle even though it is never rendered whenNODE_ENV !== "development". Modern bundlers (including Next.js/Turbopack) cannot tree-shake this because the import is unconditional.For stricter production bundle hygiene, consider a dynamic import:
♻️ Proposed refactor (optional)
-import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import dynamic from "next/dynamic"; +const ReactQueryDevtools = + process.env.NODE_ENV === "development" + ? dynamic(() => + import("@tanstack/react-query-devtools").then( + (mod) => ({ default: mod.ReactQueryDevtools }) + ) + ) + : () => null;Then simplify the JSX:
- {process.env.NODE_ENV === "development" && ( - <ReactQueryDevtools - initialIsOpen={false} - buttonPosition="bottom-right" - /> - )} + <ReactQueryDevtools initialIsOpen={false} buttonPosition="bottom-right" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@providers/query-provider.tsx` around lines 50 - 55, The unconditional static import of ReactQueryDevtools causes it to be bundled in production; change to a dynamic import and lazy render only in development: remove the top-level static import of ReactQueryDevtools and instead dynamically import it inside the providers/query-provider.tsx component (e.g., using React.lazy/Suspense or a framework dynamic import) and render the lazily-loaded ReactQueryDevtools only when process.env.NODE_ENV === "development" (the guard around the JSX should remain); update any references to ReactQueryDevtools in the file to use the dynamically imported component so the devtools module is excluded from production bundles.app/api/reputation/[userId]/completion-history/route.ts (1)
54-57: No authorization check on user-specific endpoint.Any caller can fetch completion history for any
userId. If this route is intended to persist beyond mock/scaffold usage, consider adding authentication/authorization to ensure users can only access their own data (or admins can access others').🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/reputation/`[userId]/completion-history/route.ts around lines 54 - 57, This route reads BountyStore.getBounties() and filters by the path param userId without any auth checks; enforce authorization by retrieving the authenticated caller (session/token) and verifying that its user id matches the requested userId or that the caller has an admin role before returning completed bounties; update the handler in route.ts to reject (401/403) when the caller is unauthenticated or not allowed, and only then call BountyStore.getBounties() and filter by claimedBy === userId.components/bounty/forms/schemas.ts (1)
78-78: Consider stricter wallet address validation.
z.string().min(1)accepts any non-empty string. If this is for Stellar addresses (as suggested by"XLM"in the reward currency), consider adding a regex or length check (e.g., Stellar public keys are 56 characters starting withG). This would prevent form submission with clearly invalid addresses.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty/forms/schemas.ts` at line 78, Replace the permissive walletAddress zod rule (walletAddress: z.string().min(1, ...)) with a stricter validator: require exact Stellar public key shape by enforcing 56 characters and that it starts with 'G' and only contains the Stellar Base32 charset (A–Z and 2–7) — either via z.string().length(56).refine(...) or z.string().regex(...), and provide a clear error message like "Invalid Stellar address" to block clearly invalid inputs; update the walletAddress schema in components/bounty/forms/schemas.ts accordingly.codegen.ts (1)
4-4: Schema path relies on a sibling directory checkout.
"../boundless-nestjs/src/schema.gql"requires the NestJS repo to be co-located. This will break codegen in CI or for contributors who don't have that repo checked out alongside. Consider publishing the schema as an artifact or fetching it via introspection URL to decouple the two repos.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@codegen.ts` at line 4, The codegen config currently points schema: "../boundless-nestjs/src/schema.gql" which requires a sibling repo checkout; change the schema source in codegen.ts to a decoupled option (prefer an introspection URL or a published schema artifact) by replacing the hardcoded relative path in the schema property with either an env-driven introspection endpoint (e.g., process.env.GRAPHQL_SCHEMA_URL) used by graphql-codegen's url loader or a packaged/local schema file shipped via your package/artifact; ensure the new logic in codegen.ts falls back gracefully (env URL -> packaged schema -> error) so CI and external contributors can run codegen without the sibling repo.lib/graphql/client.ts (1)
44-75: Address ESLint-flaggedanytypes and empty object type.Static analysis flags four issues here. The
{}default onTVariablesand theanycasts reduce type safety and will fail CI if ESLint is enforced.Proposed fix
-export const fetcher = <TData, TVariables extends object = {}>( +export const fetcher = <TData, TVariables extends Record<string, unknown> = Record<string, never>>( query: string, variables?: TVariables, ) => { return async (): Promise<TData> => { const token = getAccessToken(); const headers: Record<string, string> = {}; if (token) { headers.authorization = `Bearer ${token}`; } try { - return await (graphQLClient.request as any)(query, variables, headers); + return await graphQLClient.request<TData>({ + document: query, + variables, + requestHeaders: headers, + }); - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { errors?: Array<{ extensions?: { status?: number } }> } }; // Global error handling for auth failures (like Apollo ErrorLink) - if (error?.response?.errors) { - error.response.errors.forEach((err: any) => { - const status = (err?.extensions?.status as number) || 500; + if (err?.response?.errors) { + err.response.errors.forEach((gqlErr) => { + const status = gqlErr?.extensions?.status ?? 500; if (isAuthStatus(status)) { clearAccessToken(); if (typeof window !== "undefined") { window.dispatchEvent( new CustomEvent("auth:unauthorized", { detail: { status } }), ); } } }); } throw error; } }; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/graphql/client.ts` around lines 44 - 75, The fetcher currently uses unsafe any and {} types; update TVariables to extend Record<string, unknown> (e.g., TVariables extends Record<string, unknown> = Record<string, unknown>) instead of {}, change the runtime cast (graphQLClient.request as any) to a properly typed function cast such as (graphQLClient.request as unknown as (query: string, variables?: TVariables, headers?: Record<string,string>) => Promise<TData>)(...), and change the catch parameter to error: unknown and narrow it before use (e.g., const err = error as { response?: { errors?: Array<Record<string, unknown>> } } ) so you can safely access err.response.errors without using any; keep headers typed as Record<string,string> as-is.components/bounty-detail/submission-dialog.tsx (2)
89-99: Staledraftreference due to excluded dependency — acceptable but worth noting.The
draftandbaseDefaultsvalues are intentionally excluded from the dependency array (with the ESLint disable). This means ifdraftchanges while the dialog is already open, the form won't re-sync. This is likely the desired behavior (only restore draft on dialog open), but documenting the intent in the comment would help future maintainers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/submission-dialog.tsx` around lines 89 - 99, Add an explanatory comment above the useEffect that documents the intentional exclusion of draft and baseDefaults from the dependency array: state that the effect only re-syncs the form when the dialog opens (open changes) so updates to draft while the dialog is already open are ignored, and note the ESLint disable is intentional; reference the useEffect that calls form.reset({...draft, walletAddress: baseDefaults.walletAddress}) and the branch that falls back to form.reset(baseDefaults) so future maintainers understand the intended behavior.
84-87:as nevertype cast is a workaround foruseFieldArraywith primitive arrays.This is a known limitation with react-hook-form's
useFieldArraywhen working with arrays of primitives (strings) rather than arrays of objects. Theas nevercast suppresses the type error but reduces type safety.Consider using an array of objects (e.g.,
{ url: string }) for attachments to avoid the cast and get proper typing fromuseFieldArray, or extract a small wrapper that encapsulates the cast.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/submission-dialog.tsx` around lines 84 - 87, The current use of useFieldArray with a cast "as never" for name "attachments" (const { fields, append, remove } = useFieldArray({...})) hides type issues for primitive string arrays; change the attachments state/type to an array of objects (e.g., { url: string }) and update the form schema/type so useFieldArray can be typed properly, then update references to fields/append/remove to use the object shape, or alternatively create a small wrapper function around useFieldArray that performs the cast in one place and documents it so callers (fields, append, remove, form.control, name "attachments") keep type safety without sprinkling "as never" across the codebase.app/profile/[userId]/page.tsx (1)
27-27: Fetching all bounties just to filter for user's claims is inefficient.
useBounties()with no params fetches the entire bounty list. As the dataset grows, this will degrade performance. Consider adding a server-side filter parameter (e.g.,claimedByoruserId) to the bounties API, or using a dedicated endpoint for user claims.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/profile/`[userId]/page.tsx at line 27, Calling useBounties() without parameters fetches the entire bounty list then filters locally (bountyResponse), which will not scale; update the call in app/profile/[userId]/page.tsx to request only that user's claims (e.g., useBounties({ claimedBy: userId }) or useBounties({ userId })) and ensure the backend API supports a claimedBy/userId query parameter or add a dedicated endpoint (or implement a new hook like useUserClaims) so the filtering happens server-side and only the user's bounty data is returned.types/bounty.ts (1)
52-54: Complete the Single Claim Model migration: claimInfo is not being populated while top-level claimedBy/claimedAt remain critical for business logic.The interface coexists
claimedAt?: string+claimedBy?: string(lines 52-53) withclaimInfo?: ClaimInfo(line 54), but the structures don't fully overlap—claimInfo.claimedByis an object containinguserId,username, andavatarUrl, whereas the top-levelclaimedByis a simple string. More critically,claimInfois never populated in the logic layer (only read in UI), while the top-level fields are actively used for filtering (app/profile/[userId]/page.tsx,completion-history/route.ts) and inactivity checks (lib/logic/bounty-logic.ts).To unify around
ClaimInfo: either migrate the logic layer to populate and consumeclaimInfoconsistently, or ifclaimInfoserves a different purpose (e.g., richer metadata for display), clarify the intent and add comments distinguishing which field is authoritative for filtering/logic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@types/bounty.ts` around lines 52 - 54, The types declare both claimedAt/claimedBy and claimInfo (ClaimInfo) but claimInfo is never populated while business logic and filters use the top-level claimedBy/claimedAt; either migrate logic to populate and consume claimInfo or explicitly mark top-level fields as authoritative. Fix by updating the bounty creation/update flows and readers that set/read claim data (places that call/modify claimedAt, claimedBy and any functions in bounty-logic that check inactivity) to populate claimInfo.claimedBy.userId, claimInfo.claimedBy.username, claimInfo.claimedBy.avatarUrl and claimInfo.claimedAt from the same source, and update consumers currently reading claimedBy/claimedAt (e.g., profile filters and completion-history readers) to read from claimInfo where appropriate; alternatively add clear comments in the types/bounty.ts next to claimedAt/claimedBy and claimInfo explaining which is authoritative for filtering/logic and keep population consistent. Ensure the symbol names referenced: claimedAt, claimedBy, claimInfo, and ClaimInfo are updated together across creation, update, and read paths.components/bounty-detail/bounty-detail-sidebar-cta.tsx (1)
205-259:MobileCTAduplicates nearly all action/label logic fromSidebarCTA.
handleAction, the label function, and theuseClaimBounty+dialogOpenstate are copy-pasted between the two components. Extract a shared custom hook (e.g.,useBountyCTA(bounty)) that encapsulates the mutation, dialog state, pending flag, and label derivation — then consume it in both components.This prevents logic drift and ensures both CTAs share the same mutation instance (so pending state is consistent if both ever render simultaneously).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx` around lines 205 - 259, Extract the duplicated CTA logic into a shared hook named useBountyCTA(bounty) that returns { dialogOpen, setDialogOpen, claimMutation, canAct, handleAction, label } and move the label derivation, canAct calculation, useClaimBounty() instantiation and the handleAction branching (single-claim vs dialog) into that hook; then replace the duplicated logic in MobileCTA and SidebarCTA to call useBountyCTA(bounty) and use the returned values (use claimMutation.isPending for UI disabled/loader and label() for button text) so both components share the same mutation instance and dialog state management.types/participation.ts (1)
23-29:contentis required yet deprecated — consider making it optional.Since
contentis deprecated in favor ofexplanation, keeping it as a required (string) field forces every producer of aSubmissionto supply it. Making it optional (content?: string) would allow new code paths to omit it entirely while old consumers degrade gracefully, which better reflects the deprecation intent.♻️ Suggested change
- /** `@deprecated` Use explanation. Kept for backward compatibility with consumers that expect a generic content field. */ - content: string; + /** `@deprecated` Use explanation. Kept for backward compatibility with consumers that expect a generic content field. */ + content?: string;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@types/participation.ts` around lines 23 - 29, The deprecated field content is currently required which forces producers to supply it; change its declaration to optional (content?: string) in the Participation/Submission type so new code can omit it while preserving backward compatibility with explanation, and ensure any code paths constructing or validating Submission (references: content, explanation, attachments, walletAddress) handle undefined content gracefully (e.g., optional checks or fallback to explanation).app/api/bounties/[id]/submit/route.ts (1)
4-4: Server route imports validation schema fromcomponents/— move schema to a shared location.Importing
submissionFormSchemafrom@/components/bounty/forms/schemascouples this API route to the UI layer. If the component directory is reorganised, this route silently breaks. Consider co-locating shared schemas underlib/(e.g.,lib/schemas/submission.ts) and importing from there in both the route and the form component.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/bounties/`[id]/submit/route.ts at line 4, The API route imports submissionFormSchema from the UI components folder; move the schema to a shared location (e.g., create lib/schemas/submission.ts exporting submissionFormSchema) and update imports in both app/api/bounties/[id]/submit/route.ts and the form component to import from the new shared module (ensure the exported symbol name submissionFormSchema is preserved and update any relative import paths 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 `@app/api/bounties/`[id]/submit/route.ts:
- Around line 90-95: The catch block in app/api/bounties/[id]/submit/route.ts
currently swallows errors and returns a 500 without logging; update the catch to
capture the thrown error (e.g., catch (err) or catch (error)), log it
server-side using the existing logger or console.error with context (include
request/bounty id if available), then return the same NextResponse.json({ error:
"Internal Server Error" }, { status: 500 }); ensure you reference the catch
block around NextResponse.json and the handler that performs the submission.
- Around line 46-57: Remove "single-claim" from the allowedModels array in the
submit route so submissions cannot be created for single-claim bounties; locate
the array named allowedModels and the check that uses
allowedModels.includes(bounty.claimingModel) in the submit route handler
(route.ts) and update it to only allow "competition", "multi-winner", and
"application" so single-claim bounties continue to go through the dedicated
claim endpoint which handles status: 'claimed', claimedBy, and claimExpiresAt.
In `@app/profile/`[userId]/page.tsx:
- Around line 20-21: The current unsafe cast of params.userId to string in
page.tsx can panic if useParams() returns a string[]; update the logic around
useParams()/params and the userId variable to defensively handle both string and
string[] values (e.g., check Array.isArray(params.userId) and normalize to a
single string by taking the first element or joining, or handle the
invalid-multiple case explicitly), ensuring downstream code that uses userId
always receives a validated string or returns/redirects on invalid input.
- Around line 33-60: The myClaims computation currently filters bounties using
the legacy bounty.claimedBy field which will miss claims when data is migrated
to the nested claimInfo structure; update the filter inside myClaims to check
claimInfo?.claimedBy?.userId === userId first (with a fallback to the old
bounty.claimedBy === userId) so both shapes are supported, and ensure you
reference bountyResponse?.data and the existing map logic (no other changes
required).
In `@components/bounty-detail/submission-dialog.tsx`:
- Around line 45-51: getBaseDefaults currently seeds the
SubmissionFormValue.walletAddress with a hardcoded mockWalletInfo.address;
replace this before production by wiring the default to the real connected
wallet (e.g., read from your wallet context/provider or currentUser wallet
state) or at minimum add a clear TODO comment. Update the getBaseDefaults
function to pull the wallet address from the authoritative source used elsewhere
in the app (instead of mockWalletInfo.address), and ensure the form
initialization handles the case when no wallet is connected (empty string or
null) to avoid using test data in production.
In `@lib/graphql/client.ts`:
- Around line 39-41: The current graphQLClient uses a possibly relative url
(NEXT_PUBLIC_GRAPHQL_URL || "/api/graphql") which fails in SSR because Node's
fetch requires an absolute URL; update the construction so that if the chosen
url starts with "/" and code is running server-side (typeof window ===
"undefined"), build an absolute URL using a server host env (e.g.,
process.env.NEXT_PUBLIC_VERCEL_URL || process.env.VERCEL_URL ||
process.env.NEXTAUTH_URL) prefixed with "https://" (fall back to
"http://localhost:3000" if none present), then pass that absolute string to new
GraphQLClient; change the symbols url and graphQLClient in lib/graphql/client.ts
accordingly so GraphQLClient always receives an absolute URL on the server.
---
Outside diff comments:
In `@lib/api/bounties.ts`:
- Around line 35-69: The bountySchema-derived Bounty type is missing fields and
an enum option used elsewhere; update bountySchema and claimingModelSchema so
the schema includes the missing array/object fields (milestones, applicants,
competitors, members) with appropriate types (e.g., z.array(...) or
z.record/.../nullable as per types/bounty.ts) and add the "milestone" option to
claimingModelSchema, or alternatively remove the separate interface and export
Bounty by inferring z.infer<typeof bountySchema> everywhere to enforce a single
source of truth; ensure you modify the symbols bountySchema, claimingModelSchema
and any exported Bounty type usages so imports across hooks/components use the
same schema-derived type.
In `@providers/query-provider.tsx`:
- Around line 18-21: The global mutations config currently sets mutations.retry
and mutations.retryDelay in the query client/provider; remove mutations.retry
(and related global retryDelay) so mutations inherit the v5 default of 0 and do
not automatically retry non-idempotent operations, then document/encourage using
per-mutation retry options at call sites (e.g., useMutation({ mutationFn: ...,
retry: 3, retryDelay: ... })) for any idempotent-safe mutations; update the code
that defines the mutations config (the mutations object used when creating the
QueryClient/QueryProvider) to drop the global retry settings.
---
Duplicate comments:
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 35-46: handleAction currently calls claimMutation.mutate without
checking authentication; before calling claimMutation.mutate in the handleAction
function (and before setDialogOpen for multi-claim flow) use
authClient.useSession() to verify a logged-in session (or equivalent auth
helper) and if no session is present, open the login prompt/modal or show a
friendly toast asking the user to sign in instead of calling the API; only call
claimMutation.mutate(bounty.id, ...) or setDialogOpen(true) when a valid session
exists.
In `@hooks/use-bounty-mutations.ts`:
- Around line 104-126: The onSuccess handler for the mutation is writing
hardcoded mock user info into the cache via
queryClient.setQueryData(bountyKeys.detail(id)), which must be removed; instead,
detect whether the API response contains a complete claimInfo (e.g. check
data?.claimInfo?.claimedBy?.userId or data?.status) and if so return data,
otherwise preserve the existing claimInfo from the old object (or set claimInfo
to null) without injecting fabricated values; update the logic inside the
setQueryData callback (referencing onSuccess, queryClient.setQueryData,
bountyKeys.detail) to avoid hardcoded "current-user"/"You (Mock User)" and use
the existing old.claimInfo or authenticated user info from your auth context if
available.
In `@lib/api/bounties.ts`:
- Around line 17-22: The z.enum claimingModelSchema is missing the "milestone"
variant so valid data typed in types/bounty.ts will be rejected; update the
claimingModelSchema declaration (symbol: claimingModelSchema) to include
"milestone" in its enum list so the Zod schema matches the TypeScript type and
validates the same set of claimingModel values.
In `@types/bounty.ts`:
- Around line 8-13: The Zod enum claimingModelSchema in lib/api/bounties.ts is
missing the "milestone" variant present on the ClaimingModel type; update the
claimingModelSchema (the Zod definition referenced where claimingModelSchema is
declared) to include "milestone" alongside "single-claim", "application",
"competition", and "multi-winner" so runtime validation matches the
ClaimingModel type.
---
Nitpick comments:
In `@app/api/bounties/`[id]/submit/route.ts:
- Line 4: The API route imports submissionFormSchema from the UI components
folder; move the schema to a shared location (e.g., create
lib/schemas/submission.ts exporting submissionFormSchema) and update imports in
both app/api/bounties/[id]/submit/route.ts and the form component to import from
the new shared module (ensure the exported symbol name submissionFormSchema is
preserved and update any relative import paths accordingly).
In `@app/api/reputation/`[userId]/completion-history/route.ts:
- Around line 54-57: This route reads BountyStore.getBounties() and filters by
the path param userId without any auth checks; enforce authorization by
retrieving the authenticated caller (session/token) and verifying that its user
id matches the requested userId or that the caller has an admin role before
returning completed bounties; update the handler in route.ts to reject (401/403)
when the caller is unauthenticated or not allowed, and only then call
BountyStore.getBounties() and filter by claimedBy === userId.
In `@app/profile/`[userId]/page.tsx:
- Line 27: Calling useBounties() without parameters fetches the entire bounty
list then filters locally (bountyResponse), which will not scale; update the
call in app/profile/[userId]/page.tsx to request only that user's claims (e.g.,
useBounties({ claimedBy: userId }) or useBounties({ userId })) and ensure the
backend API supports a claimedBy/userId query parameter or add a dedicated
endpoint (or implement a new hook like useUserClaims) so the filtering happens
server-side and only the user's bounty data is returned.
In `@app/transparency/page.tsx`:
- Around line 101-138: The statCards array currently uses a dead branch
conditional (statsError ? "—" : ...) even though the component already gates
rendering when statsError is true; update the statCards entries in statCards so
each card's value only checks stats (e.g., value: stats ? `<formatted>` :
`<fallback>`) removing the statsError ternary, and apply the same simplification
wherever the same pattern appears in this file (the stats grid rendering logic
that already hides the grid on statsError). Ensure you update all four cards
(titles: "Total Funds Distributed", "Contributors Paid", "Projects Funded",
"Avg. Payout Time") to use only stats-based conditionals.
In `@codegen.ts`:
- Line 4: The codegen config currently points schema:
"../boundless-nestjs/src/schema.gql" which requires a sibling repo checkout;
change the schema source in codegen.ts to a decoupled option (prefer an
introspection URL or a published schema artifact) by replacing the hardcoded
relative path in the schema property with either an env-driven introspection
endpoint (e.g., process.env.GRAPHQL_SCHEMA_URL) used by graphql-codegen's url
loader or a packaged/local schema file shipped via your package/artifact; ensure
the new logic in codegen.ts falls back gracefully (env URL -> packaged schema ->
error) so CI and external contributors can run codegen without the sibling repo.
In `@components/bounty-detail/bounty-detail-sidebar-cta.tsx`:
- Around line 205-259: Extract the duplicated CTA logic into a shared hook named
useBountyCTA(bounty) that returns { dialogOpen, setDialogOpen, claimMutation,
canAct, handleAction, label } and move the label derivation, canAct calculation,
useClaimBounty() instantiation and the handleAction branching (single-claim vs
dialog) into that hook; then replace the duplicated logic in MobileCTA and
SidebarCTA to call useBountyCTA(bounty) and use the returned values (use
claimMutation.isPending for UI disabled/loader and label() for button text) so
both components share the same mutation instance and dialog state management.
In `@components/bounty-detail/submission-dialog.tsx`:
- Around line 89-99: Add an explanatory comment above the useEffect that
documents the intentional exclusion of draft and baseDefaults from the
dependency array: state that the effect only re-syncs the form when the dialog
opens (open changes) so updates to draft while the dialog is already open are
ignored, and note the ESLint disable is intentional; reference the useEffect
that calls form.reset({...draft, walletAddress: baseDefaults.walletAddress}) and
the branch that falls back to form.reset(baseDefaults) so future maintainers
understand the intended behavior.
- Around line 84-87: The current use of useFieldArray with a cast "as never" for
name "attachments" (const { fields, append, remove } = useFieldArray({...}))
hides type issues for primitive string arrays; change the attachments state/type
to an array of objects (e.g., { url: string }) and update the form schema/type
so useFieldArray can be typed properly, then update references to
fields/append/remove to use the object shape, or alternatively create a small
wrapper function around useFieldArray that performs the cast in one place and
documents it so callers (fields, append, remove, form.control, name
"attachments") keep type safety without sprinkling "as never" across the
codebase.
In `@components/bounty/forms/schemas.ts`:
- Line 78: Replace the permissive walletAddress zod rule (walletAddress:
z.string().min(1, ...)) with a stricter validator: require exact Stellar public
key shape by enforcing 56 characters and that it starts with 'G' and only
contains the Stellar Base32 charset (A–Z and 2–7) — either via
z.string().length(56).refine(...) or z.string().regex(...), and provide a clear
error message like "Invalid Stellar address" to block clearly invalid inputs;
update the walletAddress schema in components/bounty/forms/schemas.ts
accordingly.
In `@lib/graphql/client.ts`:
- Around line 44-75: The fetcher currently uses unsafe any and {} types; update
TVariables to extend Record<string, unknown> (e.g., TVariables extends
Record<string, unknown> = Record<string, unknown>) instead of {}, change the
runtime cast (graphQLClient.request as any) to a properly typed function cast
such as (graphQLClient.request as unknown as (query: string, variables?:
TVariables, headers?: Record<string,string>) => Promise<TData>)(...), and change
the catch parameter to error: unknown and narrow it before use (e.g., const err
= error as { response?: { errors?: Array<Record<string, unknown>> } } ) so you
can safely access err.response.errors without using any; keep headers typed as
Record<string,string> as-is.
In `@providers/query-provider.tsx`:
- Around line 50-55: The unconditional static import of ReactQueryDevtools
causes it to be bundled in production; change to a dynamic import and lazy
render only in development: remove the top-level static import of
ReactQueryDevtools and instead dynamically import it inside the
providers/query-provider.tsx component (e.g., using React.lazy/Suspense or a
framework dynamic import) and render the lazily-loaded ReactQueryDevtools only
when process.env.NODE_ENV === "development" (the guard around the JSX should
remain); update any references to ReactQueryDevtools in the file to use the
dynamically imported component so the devtools module is excluded from
production bundles.
In `@types/bounty.ts`:
- Around line 52-54: The types declare both claimedAt/claimedBy and claimInfo
(ClaimInfo) but claimInfo is never populated while business logic and filters
use the top-level claimedBy/claimedAt; either migrate logic to populate and
consume claimInfo or explicitly mark top-level fields as authoritative. Fix by
updating the bounty creation/update flows and readers that set/read claim data
(places that call/modify claimedAt, claimedBy and any functions in bounty-logic
that check inactivity) to populate claimInfo.claimedBy.userId,
claimInfo.claimedBy.username, claimInfo.claimedBy.avatarUrl and
claimInfo.claimedAt from the same source, and update consumers currently reading
claimedBy/claimedAt (e.g., profile filters and completion-history readers) to
read from claimInfo where appropriate; alternatively add clear comments in the
types/bounty.ts next to claimedAt/claimedBy and claimInfo explaining which is
authoritative for filtering/logic and keep population consistent. Ensure the
symbol names referenced: claimedAt, claimedBy, claimInfo, and ClaimInfo are
updated together across creation, update, and read paths.
In `@types/participation.ts`:
- Around line 23-29: The deprecated field content is currently required which
forces producers to supply it; change its declaration to optional (content?:
string) in the Participation/Submission type so new code can omit it while
preserving backward compatibility with explanation, and ensure any code paths
constructing or validating Submission (references: content, explanation,
attachments, walletAddress) handle undefined content gracefully (e.g., optional
checks or fallback to explanation).
| const allowedModels = [ | ||
| "single-claim", | ||
| "competition", | ||
| "multi-winner", | ||
| "application", | ||
| ]; | ||
| if (!allowedModels.includes(bounty.claimingModel)) { | ||
| return NextResponse.json( | ||
| { error: "Submission not allowed for this bounty type" }, | ||
| { status: 400 }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find files related to "claim"
echo "=== Files with 'claim' in name ==="
fd "claim" --type f --extension ts --extension tsx -E node_modules | head -20
echo ""
echo "=== useClaimBounty hook references ==="
rg "useClaimBounty|claimBounty|/claim" --type=ts --type=tsx -C 3 -g '!node_modules' | head -100Repository: boundlessfi/bounties
Length of output: 269
🏁 Script executed:
#!/bin/bash
# Search for useClaimBounty without file type issues
echo "=== Search for useClaimBounty ==="
rg "useClaimBounty" -C 5 -g '!node_modules' | head -80
echo ""
echo "=== Search for claim endpoint/route ==="
rg "bounties.*claim|/claim" -C 2 -g '!node_modules' | head -100
echo ""
echo "=== Check the submit route file ==="
cat -n app/api/bounties/[id]/submit/route.ts | head -80
echo ""
echo "=== Look for claim route file ==="
fd "route.ts" -path "*/api/bounties/*" -g '!node_modules'Repository: boundlessfi/bounties
Length of output: 7349
🏁 Script executed:
#!/bin/bash
# Look for claim route file
echo "=== Looking for claim route files ==="
find app/api -name "*.ts" -type f | xargs grep -l "claim" 2>/dev/null
echo ""
echo "=== Check if claim route exists ==="
ls -la app/api/bounties/*/claim 2>/dev/null || echo "No claim directory found"
ls -la app/api/bounties/\[id\]/claim 2>/dev/null || echo "No [id]/claim directory found"
echo ""
echo "=== Look at bounty detail sidebar CTA to see single-claim handling ==="
rg "single-claim" -B 5 -A 5 components/bounty-detail/bounty-detail-sidebar-cta.tsx
echo ""
echo "=== Check if submit endpoint handles single-claim differently ==="
cat -n app/api/bounties/[id]/submit/route.ts | tail -30Repository: boundlessfi/bounties
Length of output: 3249
🏁 Script executed:
#!/bin/bash
echo "=== Claim route implementation ==="
cat -n app/api/bounties/[id]/claim/route.ts
echo ""
echo "=== Check what happens after submission is created ==="
cat -n app/api/bounties/[id]/submit/route.ts | tail -15Repository: boundlessfi/bounties
Length of output: 3163
"single-claim" in allowedModels should be removed—it conflicts with the exclusive claim flow.
The claim endpoint (/api/bounties/[id]/claim) explicitly restricts itself to claimingModel === 'single-claim' (line 31) and manages the bounty state transition (status: 'claimed', claimedBy, claimExpiresAt). The CTA routes single-claim bounties exclusively through useClaimBounty (claim endpoint), never submit.
Including "single-claim" in the submit endpoint's allowedModels creates an unintended alternate path: a POST to /submit for a single-claim bounty would create a submission without transitioning the bounty to claimed status, potentially bypassing the claim expiry and status management logic.
Suggested fix
const allowedModels = [
- "single-claim",
"competition",
"multi-winner",
"application",
];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const allowedModels = [ | |
| "single-claim", | |
| "competition", | |
| "multi-winner", | |
| "application", | |
| ]; | |
| if (!allowedModels.includes(bounty.claimingModel)) { | |
| return NextResponse.json( | |
| { error: "Submission not allowed for this bounty type" }, | |
| { status: 400 }, | |
| ); | |
| } | |
| const allowedModels = [ | |
| "competition", | |
| "multi-winner", | |
| "application", | |
| ]; | |
| if (!allowedModels.includes(bounty.claimingModel)) { | |
| return NextResponse.json( | |
| { error: "Submission not allowed for this bounty type" }, | |
| { status: 400 }, | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/bounties/`[id]/submit/route.ts around lines 46 - 57, Remove
"single-claim" from the allowedModels array in the submit route so submissions
cannot be created for single-claim bounties; locate the array named
allowedModels and the check that uses
allowedModels.includes(bounty.claimingModel) in the submit route handler
(route.ts) and update it to only allow "competition", "multi-winner", and
"application" so single-claim bounties continue to go through the dedicated
claim endpoint which handles status: 'claimed', claimedBy, and claimExpiresAt.
| } catch { | ||
| return NextResponse.json( | ||
| { error: "Internal Server Error" }, | ||
| { status: 500 }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Log the caught error before returning 500.
The catch block swallows the error entirely. In production, unlogged 500s are nearly impossible to diagnose. Capture and log the error server-side.
♻️ Suggested fix
- } catch {
+ } catch (error) {
+ console.error("[POST /api/bounties/[id]/submit]", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/bounties/`[id]/submit/route.ts around lines 90 - 95, The catch block
in app/api/bounties/[id]/submit/route.ts currently swallows errors and returns a
500 without logging; update the catch to capture the thrown error (e.g., catch
(err) or catch (error)), log it server-side using the existing logger or
console.error with context (include request/bounty id if available), then return
the same NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
ensure you reference the catch block around NextResponse.json and the handler
that performs the submission.
| const params = useParams(); | ||
| const userId = params.userId as string; |
There was a problem hiding this comment.
Unsafe cast of params.userId — could be string[] in edge cases.
useParams() returns Record<string, string | string[]>. While [userId] dynamic segments typically yield a string, defensively handling the array case prevents runtime issues.
Proposed fix
const params = useParams();
- const userId = params.userId as string;
+ const rawUserId = params.userId;
+ const userId = Array.isArray(rawUserId) ? rawUserId[0] : rawUserId ?? "";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const params = useParams(); | |
| const userId = params.userId as string; | |
| const params = useParams(); | |
| const rawUserId = params.userId; | |
| const userId = Array.isArray(rawUserId) ? rawUserId[0] : rawUserId ?? ""; |
🤖 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 20 - 21, The current unsafe cast
of params.userId to string in page.tsx can panic if useParams() returns a
string[]; update the logic around useParams()/params and the userId variable to
defensively handle both string and string[] values (e.g., check
Array.isArray(params.userId) and normalize to a single string by taking the
first element or joining, or handle the invalid-multiple case explicitly),
ensuring downstream code that uses userId always receives a validated string or
returns/redirects on invalid input.
| const myClaims = useMemo<MyClaim[]>(() => { | ||
| const bounties = bountyResponse?.data ?? []; | ||
|
|
||
| return bounties | ||
| .filter((bounty) => bounty.claimedBy === userId) | ||
| .map((bounty) => { | ||
| let status = "active"; | ||
|
|
||
| if (bounty.status === "closed") { | ||
| status = "completed"; | ||
| } else if (bounty.status === "claimed" && bounty.claimExpiresAt) { | ||
| const claimExpiry = new Date(bounty.claimExpiresAt); | ||
| if ( | ||
| !Number.isNaN(claimExpiry.getTime()) && | ||
| claimExpiry < new Date() | ||
| ) { | ||
| status = "expired"; | ||
| } | ||
| } | ||
|
|
||
| // Generic Error | ||
| return ( | ||
| <div className="container mx-auto py-16 text-center"> | ||
| <AlertCircle className="w-12 h-12 mx-auto text-destructive mb-4" /> | ||
| <h1 className="text-2xl font-bold mb-2">Something went wrong</h1> | ||
| <p className="text-muted-foreground mb-6"> | ||
| We encountered an error while loading the profile. | ||
| </p> | ||
| <Button variant="outline" onClick={() => window.location.reload()}> | ||
| Try Again | ||
| </Button> | ||
| </div> | ||
| ); | ||
| } | ||
| return { | ||
| bountyId: bounty.id, | ||
| title: bounty.issueTitle, | ||
| status, | ||
| rewardAmount: bounty.rewardAmount ?? undefined, | ||
| }; | ||
| }); | ||
| }, [bountyResponse?.data, userId]); |
There was a problem hiding this comment.
bounty.claimedBy references the legacy top-level field instead of claimInfo.
Line 37 filters bounties using bounty.claimedBy === userId, but the PR is migrating claim data into the nested claimInfo structure. If the API (or mock data) only populates claimInfo.claimedBy.userId and not the top-level claimedBy, this filter will silently produce an empty list.
Proposed fix
return bounties
- .filter((bounty) => bounty.claimedBy === userId)
+ .filter(
+ (bounty) =>
+ bounty.claimInfo?.claimedBy?.userId === userId ||
+ bounty.claimedBy === userId,
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const myClaims = useMemo<MyClaim[]>(() => { | |
| const bounties = bountyResponse?.data ?? []; | |
| return bounties | |
| .filter((bounty) => bounty.claimedBy === userId) | |
| .map((bounty) => { | |
| let status = "active"; | |
| if (bounty.status === "closed") { | |
| status = "completed"; | |
| } else if (bounty.status === "claimed" && bounty.claimExpiresAt) { | |
| const claimExpiry = new Date(bounty.claimExpiresAt); | |
| if ( | |
| !Number.isNaN(claimExpiry.getTime()) && | |
| claimExpiry < new Date() | |
| ) { | |
| status = "expired"; | |
| } | |
| } | |
| // Generic Error | |
| return ( | |
| <div className="container mx-auto py-16 text-center"> | |
| <AlertCircle className="w-12 h-12 mx-auto text-destructive mb-4" /> | |
| <h1 className="text-2xl font-bold mb-2">Something went wrong</h1> | |
| <p className="text-muted-foreground mb-6"> | |
| We encountered an error while loading the profile. | |
| </p> | |
| <Button variant="outline" onClick={() => window.location.reload()}> | |
| Try Again | |
| </Button> | |
| </div> | |
| ); | |
| } | |
| return { | |
| bountyId: bounty.id, | |
| title: bounty.issueTitle, | |
| status, | |
| rewardAmount: bounty.rewardAmount ?? undefined, | |
| }; | |
| }); | |
| }, [bountyResponse?.data, userId]); | |
| const myClaims = useMemo<MyClaim[]>(() => { | |
| const bounties = bountyResponse?.data ?? []; | |
| return bounties | |
| .filter( | |
| (bounty) => | |
| bounty.claimInfo?.claimedBy?.userId === userId || | |
| bounty.claimedBy === userId, | |
| ) | |
| .map((bounty) => { | |
| let status = "active"; | |
| if (bounty.status === "closed") { | |
| status = "completed"; | |
| } else if (bounty.status === "claimed" && bounty.claimExpiresAt) { | |
| const claimExpiry = new Date(bounty.claimExpiresAt); | |
| if ( | |
| !Number.isNaN(claimExpiry.getTime()) && | |
| claimExpiry < new Date() | |
| ) { | |
| status = "expired"; | |
| } | |
| } | |
| return { | |
| bountyId: bounty.id, | |
| title: bounty.issueTitle, | |
| status, | |
| rewardAmount: bounty.rewardAmount ?? undefined, | |
| }; | |
| }); | |
| }, [bountyResponse?.data, userId]); |
🤖 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 33 - 60, The myClaims computation
currently filters bounties using the legacy bounty.claimedBy field which will
miss claims when data is migrated to the nested claimInfo structure; update the
filter inside myClaims to check claimInfo?.claimedBy?.userId === userId first
(with a fallback to the old bounty.claimedBy === userId) so both shapes are
supported, and ensure you reference bountyResponse?.data and the existing map
logic (no other changes required).
| const getBaseDefaults = (): SubmissionFormValue => ({ | ||
| githubUrl: "", | ||
| demoUrl: "", | ||
| explanation: "", | ||
| attachments: [], | ||
| walletAddress: mockWalletInfo.address, | ||
| }); |
There was a problem hiding this comment.
Hardcoded mock wallet address will need replacement before production.
mockWalletInfo.address is used as the default wallet address. This should be sourced from the user's actual connected wallet in production. Consider adding a TODO or wiring this to a real wallet context.
Suggested marker
const getBaseDefaults = (): SubmissionFormValue => ({
githubUrl: "",
demoUrl: "",
explanation: "",
attachments: [],
- walletAddress: mockWalletInfo.address,
+ walletAddress: mockWalletInfo.address, // TODO: Replace with real connected wallet address from wallet context
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const getBaseDefaults = (): SubmissionFormValue => ({ | |
| githubUrl: "", | |
| demoUrl: "", | |
| explanation: "", | |
| attachments: [], | |
| walletAddress: mockWalletInfo.address, | |
| }); | |
| const getBaseDefaults = (): SubmissionFormValue => ({ | |
| githubUrl: "", | |
| demoUrl: "", | |
| explanation: "", | |
| attachments: [], | |
| walletAddress: mockWalletInfo.address, // TODO: Replace with real connected wallet address from wallet context | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/bounty-detail/submission-dialog.tsx` around lines 45 - 51,
getBaseDefaults currently seeds the SubmissionFormValue.walletAddress with a
hardcoded mockWalletInfo.address; replace this before production by wiring the
default to the real connected wallet (e.g., read from your wallet
context/provider or currentUser wallet state) or at minimum add a clear TODO
comment. Update the getBaseDefaults function to pull the wallet address from the
authoritative source used elsewhere in the app (instead of
mockWalletInfo.address), and ensure the form initialization handles the case
when no wallet is connected (empty string or null) to avoid using test data in
production.
| // Create the generic GraphQLClient instance | ||
| const url = process.env.NEXT_PUBLIC_GRAPHQL_URL || "/api/graphql"; | ||
| export const graphQLClient = new GraphQLClient(url); |
There was a problem hiding this comment.
Relative URL fallback may fail during SSR.
When NEXT_PUBLIC_GRAPHQL_URL is unset, the fallback "/api/graphql" is a relative URL. graphql-request's GraphQLClient will pass this to fetch, which requires an absolute URL in Node.js (SSR/server components). This will cause a runtime error like TypeError: Only absolute URLs are supported.
Consider constructing an absolute URL for the server-side case:
Proposed fix
-const url = process.env.NEXT_PUBLIC_GRAPHQL_URL || "/api/graphql";
+const url =
+ process.env.NEXT_PUBLIC_GRAPHQL_URL ||
+ (typeof window === "undefined"
+ ? `${process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"}/api/graphql`
+ : "/api/graphql");graphql-request v7 relative URL server side fetch
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/graphql/client.ts` around lines 39 - 41, The current graphQLClient uses a
possibly relative url (NEXT_PUBLIC_GRAPHQL_URL || "/api/graphql") which fails in
SSR because Node's fetch requires an absolute URL; update the construction so
that if the chosen url starts with "/" and code is running server-side (typeof
window === "undefined"), build an absolute URL using a server host env (e.g.,
process.env.NEXT_PUBLIC_VERCEL_URL || process.env.VERCEL_URL ||
process.env.NEXTAUTH_URL) prefixed with "https://" (fall back to
"http://localhost:3000" if none present), then pass that absolute string to new
GraphQLClient; change the symbols url and graphQLClient in lib/graphql/client.ts
accordingly so GraphQLClient always receives an absolute URL on the server.
Description
This PR implements the Single Claim Model logic for the bounty details page, bridging the gap between an open bounty and a claimed state.
Closes #79
Key Changes
ClaimInfotype to theBountyinterface (types/bounty.ts) and updated the Zod schema (lib/api/bounties.ts) to validate and pass the new data structure.ClaimantProfilecomponent in the sidebar (bounty-detail-sidebar-cta.tsx) that displays the avatar, username, and timestamp of the user who claimed the bounty.mockBountiesto include a fully mockedclaimInfoobject for UI testing.Acceptance Criteria Met
Testing Steps
/bounty/1)./bounty/2). Verify the Claimant Profile renders correctly on load.Summary by CodeRabbit
Release Notes
New Features
Improvements