Skip to content
Closed
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
155 changes: 155 additions & 0 deletions components/bounty/BountyManageNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import {
Settings,
Users,
FileText,
Trophy,
Wallet,
AlertTriangle,
CheckCircle,
BarChart3,
} from "lucide-react";

interface BountyManageNavProps {
bountyId: string;
bountyStatus: "draft" | "active" | "submissions" | "judging" | "payout" | "completed" | "cancelled";
hasDisputes?: boolean;
pendingPayouts?: number;
}
Comment on lines +17 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only check for a shared bounty status enum/type.
rg -n --glob '*.{ts,tsx,js,jsx}' '\b(BountyStatus|bountyStatus)\b|draft|active|submissions|judging|payout|completed|cancelled' .

Repository: boundlessfi/boundless

Length of output: 50378


Verify this status list matches the shared bounty lifecycle.

The BountyManageNav component hardcodes the bountyStatus type with a specific set of literals. While the codebase contains a types/hackathon/core.ts file with a status type that is similar, it currently appears scoped to Hackathons and includes 'published' while the nav component uses 'draft' in a slightly different structure.

To prevent subtle mismatches where a Bounty type is introduced or modified independently later, please import the canonical Bounty status type directly from the backend-shared type definitions (e.g., pages/api/... or types/bounty.ts if it exists) rather than maintaining a parallel list. If a unified canonical type does not exist yet, it would be safer to centralize these statuses to avoid drift as the dashboard grows.

Currently, the mapping appears to match common expectations, but hardcoding in the component creates a maintenance risk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/bounty/BountyManageNav.tsx` around lines 17 - 22, The
BountyManageNavProps.bountyStatus field is duplicating the bounty lifecycle
literals instead of using the shared canonical Bounty status type. Update
BountyManageNav to import and use the backend-shared bounty status type from the
existing shared definitions (for example, a types/bounty.ts export or the
relevant API-shared type) so the nav stays aligned with the source of truth. If
no shared Bounty type exists yet, extract the status union into a central shared
type and reference that from BountyManageNavProps.

Source: Coding guidelines


export function BountyManageNav({
bountyId,
bountyStatus,
hasDisputes = false,
pendingPayouts = 0,
}: BountyManageNavProps) {
const pathname = usePathname();
const baseUrl = `/me/bounties/${bountyId}/manage`;

const navItems = [
{
label: "Overview",
href: baseUrl,
icon: BarChart3,
enabled: true,
},
{
label: "Configure",
href: `${baseUrl}/configure`,
icon: Settings,
enabled: ["draft", "active"].includes(bountyStatus),
},
{
label: "Applications",
href: `${baseUrl}/applications`,
icon: Users,
enabled: true,
badge: bountyStatus === "active" ? "Review" : undefined,
},
{
label: "Submissions",
href: `${baseUrl}/submissions`,
icon: FileText,
enabled: ["submissions", "judging", "payout", "completed"].includes(bountyStatus),
},
{
label: "Select Winners",
href: `${baseUrl}/winners`,
icon: Trophy,
enabled: ["judging", "payout"].includes(bountyStatus),
highlight: bountyStatus === "judging",
},
{
label: "Payout",
href: `${baseUrl}/payout`,
icon: Wallet,
enabled: ["payout", "completed"].includes(bountyStatus),
badge: pendingPayouts > 0 ? `${pendingPayouts} pending` : undefined,
},
{
label: "Disputes",
href: `${baseUrl}/disputes`,
icon: AlertTriangle,
enabled: true,
badge: hasDisputes ? "Action needed" : undefined,
badgeVariant: hasDisputes ? "destructive" : "default",
},
{
label: "Results",
href: `${baseUrl}/results`,
icon: CheckCircle,
enabled: bountyStatus === "completed",
},
];

return (
<nav className="flex flex-col space-y-1">
{navItems.map((item) => {
const isActive = pathname === item.href;
const Icon = item.icon;

if (!item.enabled) {
return (
<div
key={item.href}
className="flex items-center gap-3 px-3 py-2 text-sm text-muted-foreground/50 cursor-not-allowed"
>
<Icon className="h-4 w-4" />
<span>{item.label}</span>
</div>
);
}

return (
<Link
key={item.href}
href={item.href}
className={cn(
"flex items-center justify-between gap-3 px-3 py-2 text-sm rounded-md transition-colors",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground",
item.highlight && !isActive && "border-l-2 border-yellow-500"
)}
>
<div className="flex items-center gap-3">
<Icon className="h-4 w-4" />
<span>{item.label}</span>
</div>
{item.badge && (
<span
className={cn(
"text-xs px-2 py-0.5 rounded-full",
item.badgeVariant === "destructive"
? "bg-destructive text-destructive-foreground"
: "bg-muted-foreground/20"
)}
>
{item.badge}
</span>
)}
</Link>
);
})}
</nav>
);
}

// Management CTA button for the bounty list
export function ManageBountyCTA({ bountyId }: { bountyId: string }) {
return (
<Link
href={`/me/bounties/${bountyId}/manage`}
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
>
<Settings className="h-4 w-4" />
Manage
</Link>
);
}

export default BountyManageNav;