Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/bounty/[bountyId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default async function BountyDetailPage({ params }: Props) {
className="flex items-center gap-1.5 text-xs text-gray-500 mb-8"
>
<Link
href="/bounties"
href="/bounty"
className="hover:text-gray-300 transition-colors flex items-center gap-1"
>
<ArrowLeft className="size-3" /> Bounties
Expand Down
48 changes: 24 additions & 24 deletions app/bounty/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useState, useMemo, useCallback } from "react";
import Link from "next/link";
import { useBounties } from "@/hooks/use-bounties";
import { BountyCard } from "@/components/bounty/bounty-card";
import { BountyListSkeleton } from "@/components/bounty/bounty-card-skeleton";
Expand Down Expand Up @@ -156,14 +157,11 @@ export default function BountiesPage() {
);
}, []);

const toggleTag = useCallback(
(tag: string) => {
setSelectedTags((prev) =>
prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag],
);
},
[],
);
const toggleTag = useCallback((tag: string) => {
setSelectedTags((prev) =>
prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag],
);
}, []);

const clearFilters = () => {
setSearchQuery("");
Expand Down Expand Up @@ -208,23 +206,21 @@ export default function BountiesPage() {
rewardRange[0] !== 0 ||
rewardRange[1] !== 5000 ||
statusFilter !== "open") && (
<Button
variant="ghost"
size="sm"
onClick={clearFilters}
className="h-6 text-[10px] text-primary hover:text-primary/80 p-0 hover:bg-transparent"
>
Reset
</Button>
)}
<Button
variant="ghost"
size="sm"
onClick={clearFilters}
className="h-6 text-[10px] text-primary hover:text-primary/80 p-0 hover:bg-transparent"
>
Reset
</Button>
)}
</div>

<div className="space-y-6">
{/* Search */}
<div className="space-y-2">
<Label className="text-xs font-medium">
Search
</Label>
<Label className="text-xs font-medium">Search</Label>
<div className="relative group">
<Search className="absolute left-3 top-2.5 size-4 group-focus-within:text-primary transition-colors" />
<Input
Expand Down Expand Up @@ -473,9 +469,13 @@ export default function BountiesPage() {
) : filteredBounties.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 auto-rows-fr">
{filteredBounties.map((bounty) => (
<div key={bounty.id} className="h-full">
<Link
key={bounty.id}
href={`/bounty/${bounty.id}`}
className="h-full block"
>
<BountyCard bounty={bounty} />
</div>
</Link>
Comment on lines +472 to +478
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine BountyCard component
echo "=== Finding BountyCard component ==="
fd "bounty-card" --type f | head -10

echo ""
echo "=== Examining BountyCard implementation ==="
fd "bounty-card" --type f | xargs cat 2>/dev/null

Repository: boundlessfi/bounties

Length of output: 20043


🏁 Script executed:

#!/bin/bash
# Check imports in app/bounty/page.tsx
echo "=== Import statements in app/bounty/page.tsx ==="
head -50 app/bounty/page.tsx | grep -E "import|from"

Repository: boundlessfi/bounties

Length of output: 1117


Refactor BountyCard or remove the <Link> wrapper to avoid conflicting interactive semantics.

The BountyCard component from /components/bounty/bounty-card.tsx is designed as an interactive element with role="button", onClick, and keyboard handlers (onKeyDown). Wrapping it in a <Link> creates a <a> tag around a <div role="button">, which is semantically confusing. Additionally, since no onClick prop is passed to BountyCard, the component's click and keyboard handlers become dead code while the Link handles navigation independently.

Choose one approach:

  1. Remove the <Link> wrapper and pass an onClick handler to BountyCard that navigates programmatically.
  2. Remove the interactive role and handlers from BountyCard and use only the <Link> for navigation (recommended, as seen in components/bounty/github-bounty-card.tsx).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/bounty/page.tsx` around lines 472 - 478, The BountyCard component is
currently interactive (role="button", onClick, onKeyDown) but is wrapped by a
Link in page.tsx, causing conflicting semantics and dead handlers; refactor
BountyCard (components/bounty/bounty-card.tsx) to be non-interactive by removing
role="button" and any onClick/onKeyDown keyboard handlers and ensure it renders
as a plain semantic container (e.g., div/section) so the outer Link handles
navigation, and mirror the approach used in
components/bounty/github-bounty-card.tsx (update any props/tests that expect
interactive behavior accordingly).

))}
</div>
) : (
Expand All @@ -501,7 +501,7 @@ export default function BountiesPage() {
)}
</main>
</div>
</div >
</div >
</div>
</div>
);
}
71 changes: 71 additions & 0 deletions app/bounty/review/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client";

import { SponsorReviewDashboard } from "@/components/bounty/sponsor-review-dashboard";
import { ReviewSubmission } from "@/types/participation";
import { Button } from "@/components/ui/button";
import { ChevronLeft } from "lucide-react";
import Link from "next/link";

// Mock data for the dashboard
const mockSubmissions: ReviewSubmission[] = [
{
submissionId: "sub-1",
contributor: {
username: "alex_dev",
avatarUrl: "https://github.com/shadcn.png",
},
milestoneId: "Milestone 1",
submittedAt: new Date().toISOString(),
status: "pending",
},
{
submissionId: "sub-2",
contributor: {
username: "sarah_smith",
},
milestoneId: "Milestone 2",
submittedAt: new Date(Date.now() - 86400000).toISOString(),
status: "pending",
},
];

export default function ReviewDashboardPage() {
const handleAction = async (id: string, action: string) => {
console.log(`Action ${action} on submission ${id}`);
// In a real app, this would be an API call
await new Promise((resolve) => setTimeout(resolve, 500));
};

return (
<div className="container mx-auto max-w-5xl px-4 py-8 space-y-8">
<div className="flex items-center justify-between">
<div className="space-y-1">
<Button
asChild
variant="ghost"
size="sm"
className="-ml-2 text-muted-foreground"
>
<Link href="/bounty">
<ChevronLeft className="mr-2 h-4 w-4" />
Back to Bounties
</Link>
</Button>
<h1 className="text-3xl font-bold tracking-tight">
Review Submissions
</h1>
<p className="text-muted-foreground">
Manage and review contributions submitted for your bounties.
</p>
</div>
</div>

<div className="rounded-xl border bg-card p-6 shadow-sm">
<SponsorReviewDashboard
submissions={mockSubmissions}
onAction={handleAction}
/>
</div>
</div>
);
Comment on lines +32 to +70
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's any auth/middleware protecting the /bounty/review route
echo "=== Checking for middleware files ==="
fd middleware --type f 2>/dev/null | head -20 || echo "No middleware files found"

echo ""
echo "=== Checking for Next.js middleware file ==="
fd -t f 'middleware\.ts|middleware\.js' --max-depth 2 2>/dev/null || echo "No root middleware found"

echo ""
echo "=== Checking for auth patterns in the bounty directory ==="
rg -n "auth|session|protect|guard|middleware|useAuth|getServerSession|protected" --type=ts --type=tsx app/bounty/ 2>/dev/null || echo "No auth patterns found"

echo ""
echo "=== Checking the review page file directly ==="
if [ -f "app/bounty/review/page.tsx" ]; then
  echo "=== File exists, checking content ==="
  head -35 app/bounty/review/page.tsx
fi

echo ""
echo "=== Checking for layout files with potential auth in bounty routes ==="
find app/bounty -name "layout.tsx" -o -name "layout.ts" | xargs ls -la 2>/dev/null || echo "No layout files found"

echo ""
echo "=== Searching for auth configuration or providers ==="
rg -n "getServerSession|useSession|auth|middleware" --type=ts --type=tsx app/ | grep -E "(auth|session|middleware)" | head -20 || echo "No auth config found"

Repository: boundlessfi/bounties

Length of output: 3467


🏁 Script executed:

#!/bin/bash
echo "=== Full bounty directory structure ==="
find app/bounty -type f | head -30

echo ""
echo "=== Search for auth-related files anywhere in app ==="
find app -name '*auth*' -o -name '*session*' | head -20

echo ""
echo "=== Check providers directory ==="
ls -la providers/ && cat providers/* 2>/dev/null | head -50

echo ""
echo "=== Check root layout file ==="
if [ -f "app/layout.tsx" ]; then
  head -80 app/layout.tsx
fi

echo ""
echo "=== Search for useAuth, useSession, or similar hooks ==="
rg "useAuth|useSession|getServerSession" --type ts --type tsx app/ 2>/dev/null | head -20 || echo "No session hooks found"

echo ""
echo "=== Check if there's any import of auth-related code in the review page ==="
cat app/bounty/review/page.tsx | head -20

Repository: boundlessfi/bounties

Length of output: 4416


🏁 Script executed:

#!/bin/bash
echo "=== Contents of app/(auth) directory ==="
find "app/(auth)" -type f 2>/dev/null || echo "Directory not found or check differently"

echo ""
echo "=== Checking auth folder ==="
find app -name "auth" -type d 2>/dev/null

echo ""
echo "=== Check bounty layout or any route protection ==="
if [ -f "app/bounty/layout.tsx" ]; then
  echo "=== app/bounty/layout.tsx exists ==="
  cat app/bounty/layout.tsx
fi

echo ""
echo "=== Check app/(auth) directory listing ==="
ls -la app/ | grep auth

Repository: boundlessfi/bounties

Length of output: 329


🏁 Script executed:

#!/bin/bash
echo "=== Check auth page implementation ==="
cat app/'(auth)'/auth/page.tsx

echo ""
echo "=== Check if review page is in a route segment with protections ==="
ls -la app/bounty/review/

echo ""
echo "=== List all layout files in app ==="
find app -name "layout.tsx" -o -name "layout.ts"

Repository: boundlessfi/bounties

Length of output: 614


Add access control to the review dashboard.

This page is publicly accessible and currently displays with mock data. Review/approval dashboards require sponsor or admin authentication. Add a route guard or middleware protection, or add a TODO comment documenting when auth will be wired.

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

In `@app/bounty/review/page.tsx` around lines 32 - 70, The ReviewDashboardPage is
currently public and uses mockSubmissions; add access control by protecting the
page before rendering SponsorReviewDashboard: check the current user’s role
(sponsor or admin) and only render the dashboard when authorized, otherwise
redirect or render an unauthorized message; implement this either by calling
your auth/session helper at the top of ReviewDashboardPage (before returning
JSX) or wiring middleware to guard the route, and ensure handleAction and
SponsorReviewDashboard are only invoked for authorized users; if you cannot
implement auth now, add a clear TODO comment at the top of ReviewDashboardPage
noting that sponsor/admin authentication must be enforced before this page is
published and remove mockSubmissions when real data is available.

}
190 changes: 95 additions & 95 deletions components/bounty-detail/bounty-detail-client.tsx
Original file line number Diff line number Diff line change
@@ -1,95 +1,95 @@
"use client";
import { useRouter } from "next/navigation";
import { AlertCircle, ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
ClaimModelInfo,
MobileCTA,
SidebarCTA,
} from "./bounty-detail-sidebar-cta";
import { RequirementsCard, ScopeCard } from "./bounty-detail-requirements-card";
import { HeaderCard } from "./bounty-detail-header-card";
import { DescriptionCard } from "./bounty-detail-description-card";
import { BountyDetailSkeleton } from "./bounty-detail-bounty-detail-skeleton";
import { useBountyDetail } from "@/hooks/Use-bounty-detail";
export function BountyDetailClient({ bountyId }: { bountyId: string }) {
const router = useRouter();
const { data: bounty, isPending, isError, error } = useBountyDetail(bountyId);
if (isPending) return <BountyDetailSkeleton />;
if (isError) {
return (
<div className="min-h-screen flex flex-col items-center justify-center gap-4 text-center px-4">
<div className="size-16 rounded-full bg-gray-800/50 flex items-center justify-center">
<AlertCircle className="size-8 text-gray-600" />
</div>
<h2 className="text-xl font-bold text-gray-200">
Failed to load bounty
</h2>
<p className="text-gray-400 max-w-sm text-sm">
{error instanceof Error
? error.message
: "Something went wrong. Please try again."}
</p>
<Button
variant="outline"
className="border-gray-700 hover:bg-gray-800 mt-2"
onClick={() => router.push("/bounties")}
>
<ArrowLeft className="size-4 mr-2" />
Back to bounties
</Button>
</div>
);
}
if (!bounty) {
return (
<div className="min-h-screen flex flex-col items-center justify-center gap-4 text-center px-4">
<div className="size-16 rounded-full bg-gray-800/50 flex items-center justify-center">
<AlertCircle className="size-8 text-gray-600" />
</div>
<h2 className="text-xl font-bold text-gray-200">Bounty not found</h2>
<p className="text-gray-400 max-w-sm text-sm">
This bounty may have been removed or doesn&apos;t exist.
</p>
<Button
variant="outline"
className="border-gray-700 hover:bg-gray-800 mt-2"
onClick={() => router.push("/bounties")}
>
<ArrowLeft className="size-4 mr-2" />
Back to bounties
</Button>
</div>
);
}
return (
<div className="flex flex-col lg:flex-row gap-10">
{/* Main content */}
<div className="flex-1 min-w-0 space-y-6">
<HeaderCard bounty={bounty} />
<DescriptionCard description={bounty.description} />
{bounty.requirements && bounty.requirements.length > 0 && (
<RequirementsCard requirements={bounty.requirements} />
)}
{bounty.scope && <ScopeCard scope={bounty.scope} />}
</div>
{/* Sidebar */}
<aside className="w-full lg:w-72 shrink-0">
<div className="lg:sticky lg:top-24 space-y-4">
<SidebarCTA bounty={bounty} />
<ClaimModelInfo claimingModel={bounty.claimingModel} />
</div>
</aside>
{/* Mobile sticky CTA */}
<MobileCTA bounty={bounty} />
</div>
);
}
"use client";

import { useRouter } from "next/navigation";
import { AlertCircle, ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
ClaimModelInfo,
MobileCTA,
SidebarCTA,
} from "./bounty-detail-sidebar-cta";
import { RequirementsCard, ScopeCard } from "./bounty-detail-requirements-card";
import { HeaderCard } from "./bounty-detail-header-card";
import { DescriptionCard } from "./bounty-detail-description-card";
import { BountyDetailSkeleton } from "./bounty-detail-bounty-detail-skeleton";
import { useBountyDetail } from "@/hooks/Use-bounty-detail";

export function BountyDetailClient({ bountyId }: { bountyId: string }) {
const router = useRouter();
const { data: bounty, isPending, isError, error } = useBountyDetail(bountyId);

if (isPending) return <BountyDetailSkeleton />;

if (isError) {
return (
<div className="min-h-screen flex flex-col items-center justify-center gap-4 text-center px-4">
<div className="size-16 rounded-full bg-gray-800/50 flex items-center justify-center">
<AlertCircle className="size-8 text-gray-600" />
</div>
<h2 className="text-xl font-bold text-gray-200">
Failed to load bounty
</h2>
<p className="text-gray-400 max-w-sm text-sm">
{error instanceof Error
? error.message
: "Something went wrong. Please try again."}
</p>
<Button
variant="outline"
className="border-gray-700 hover:bg-gray-800 mt-2"
onClick={() => router.push("/bounty")}
>
<ArrowLeft className="size-4 mr-2" />
Back to bounties
</Button>
</div>
);
}

if (!bounty) {
return (
<div className="min-h-screen flex flex-col items-center justify-center gap-4 text-center px-4">
<div className="size-16 rounded-full bg-gray-800/50 flex items-center justify-center">
<AlertCircle className="size-8 text-gray-600" />
</div>
<h2 className="text-xl font-bold text-gray-200">Bounty not found</h2>
<p className="text-gray-400 max-w-sm text-sm">
This bounty may have been removed or doesn&apos;t exist.
</p>
<Button
variant="outline"
className="border-gray-700 hover:bg-gray-800 mt-2"
onClick={() => router.push("/bounty")}
>
<ArrowLeft className="size-4 mr-2" />
Back to bounties
</Button>
</div>
);
}

return (
<div className="flex flex-col lg:flex-row gap-10">
{/* Main content */}
<div className="flex-1 min-w-0 space-y-6">
<HeaderCard bounty={bounty} />
<DescriptionCard description={bounty.description} />
{bounty.requirements && bounty.requirements.length > 0 && (
<RequirementsCard requirements={bounty.requirements} />
)}
{bounty.scope && <ScopeCard scope={bounty.scope} />}
</div>

{/* Sidebar */}
<aside className="w-full lg:w-72 shrink-0">
<div className="lg:sticky lg:top-24 space-y-4">
<SidebarCTA bounty={bounty} />
<ClaimModelInfo claimingModel={bounty.claimingModel} />
</div>
</aside>

{/* Mobile sticky CTA */}
<MobileCTA bounty={bounty} />
</div>
);
}
Loading
Loading