import React, { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Progress } from '@/components/ui/progress'; import { Separator } from '@/components/ui/separator'; import { Clock, Users, Trophy, Filter, Search, TrendingUp, AlertCircle } from 'lucide-react'; import { useWallet } from '@/hooks/use-wallet'; import { useToast } from '@/hooks/use-toast'; import { formatDistanceToNow } from 'date-fns'; interface Bounty { bounty_id: string; title: string; description: string; reward_amount: number; creator_id: string; tier: 'bronze' | 'silver' | 'gold' | 'platinum'; status: 'created' | 'active' | 'submitted' | 'verified' | 'completed' | 'expired' | 'disputed'; performance_criteria: Record; min_accuracy: number; max_response_time?: number; deadline: string; creation_time: string; max_submissions: number; submission_count: number; requires_zk_proof: boolean; tags: string[]; category?: string; difficulty?: string; winning_submission_id?: string; winner_address?: string; } interface BountyFilters { status?: string; tier?: string; category?: string; min_reward?: number; max_reward?: number; tags?: string[]; requires_zk_proof?: boolean; } const BountyBoard: React.FC = () => { const { address, isConnected } = useWallet(); const { toast } = useToast(); const [bounties, setBounties] = useState([]); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); const [filters, setFilters] = useState({}); const [showCreateModal, setShowCreateModal] = useState(false); const [selectedBounty, setSelectedBounty] = useState(null); const [mySubmissions, setMySubmissions] = useState([]); // Load bounties on component mount useEffect(() => { loadBounties(); if (isConnected) { loadMySubmissions(); } }, [isConnected]); const loadBounties = async () => { try { setLoading(true); const response = await fetch('/api/v1/bounties', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...filters, page: 1, limit: 50 }) }); if (response.ok) { const data = await response.json(); setBounties(data); } else { throw new Error('Failed to load bounties'); } } catch (error) { console.error('Error loading bounties:', error); toast({ title: 'Error', description: 'Failed to load bounties', variant: 'destructive' }); } finally { setLoading(false); } }; const loadMySubmissions = async () => { try { const response = await fetch('/api/v1/bounties/my/submissions', { headers: { 'Authorization': `Bearer ${address}` } }); if (response.ok) { const submissions = await response.json(); setMySubmissions(submissions.map((s: any) => s.bounty_id)); } } catch (error) { console.error('Error loading submissions:', error); } }; const handleBountySubmit = async (bountyId: string) => { if (!isConnected) { toast({ title: 'Wallet Required', description: 'Please connect your wallet to submit to bounties', variant: 'destructive' }); return; } // Navigate to submission page or open modal setSelectedBounty(bounties.find(b => b.bounty_id === bountyId) || null); }; const getTierColor = (tier: string) => { const colors = { bronze: 'bg-orange-100 text-orange-800 border-orange-200', silver: 'bg-gray-100 text-gray-800 border-gray-200', gold: 'bg-yellow-100 text-yellow-800 border-yellow-200', platinum: 'bg-purple-100 text-purple-800 border-purple-200' }; return colors[tier as keyof typeof colors] || colors.bronze; }; const getStatusColor = (status: string) => { const colors = { created: 'bg-gray-100 text-gray-800', active: 'bg-green-100 text-green-800', submitted: 'bg-blue-100 text-blue-800', verified: 'bg-purple-100 text-purple-800', completed: 'bg-emerald-100 text-emerald-800', expired: 'bg-red-100 text-red-800', disputed: 'bg-orange-100 text-orange-800' }; return colors[status as keyof typeof colors] || colors.created; }; const getTimeRemaining = (deadline: string) => { const deadlineDate = new Date(deadline); const now = new Date(); const timeRemaining = deadlineDate.getTime() - now.getTime(); if (timeRemaining <= 0) return 'Expired'; return formatDistanceToNow(deadlineDate, { addSuffix: true }); }; const filteredBounties = bounties.filter(bounty => { const matchesSearch = bounty.title.toLowerCase().includes(searchQuery.toLowerCase()) || bounty.description.toLowerCase().includes(searchQuery.toLowerCase()); const matchesTab = activeTab === 'all' || (activeTab === 'my-submissions' && mySubmissions.includes(bounty.bounty_id)) || (activeTab === 'active' && bounty.status === 'active') || (activeTab === 'completed' && bounty.status === 'completed'); return matchesSearch && matchesTab; }); if (loading) { return (
); } return (
{/* Header */}

Bounty Board

Discover and participate in AI agent development challenges

{isConnected && ( )}
{/* Stats Cards */}

Active Bounties

{bounties.filter(b => b.status === 'active').length}

Total Rewards

{bounties.reduce((sum, b) => sum + b.reward_amount, 0).toLocaleString()} AITBC

Completion Rate

{bounties.length > 0 ? Math.round((bounties.filter(b => b.status === 'completed').length / bounties.length) * 100) : 0}%

My Submissions

{mySubmissions.length}

{/* Search and Filters */}
setSearchQuery(e.target.value)} className="pl-10" />
{/* Tabs */} All Bounties Active Completed {isConnected && My Submissions} {/* Bounty Grid */}
{filteredBounties.map((bounty) => (
{bounty.title}
{bounty.tier.charAt(0).toUpperCase() + bounty.tier.slice(1)} {bounty.status.charAt(0).toUpperCase() + bounty.status.slice(1)}

{bounty.reward_amount.toLocaleString()}

AITBC

{bounty.description}
Min Accuracy {bounty.min_accuracy}%
{bounty.max_response_time && (
Max Response Time {bounty.max_response_time}ms
)}
Submissions {bounty.submission_count}/{bounty.max_submissions}
Time Remaining {getTimeRemaining(bounty.deadline)}
{/* Progress bar for submissions */} {/* Tags */} {bounty.tags.length > 0 && (
{bounty.tags.slice(0, 3).map((tag) => ( {tag} ))} {bounty.tags.length > 3 && ( +{bounty.tags.length - 3} )}
)} {/* ZK Proof indicator */} {bounty.requires_zk_proof && (
ZK-Proof Required
)}
{bounty.status === 'active' && ( )} {bounty.status === 'completed' && bounty.winner_address && (

Won by

{bounty.winner_address.slice(0, 8)}...{bounty.winner_address.slice(-6)}

)}
))}
{filteredBounties.length === 0 && (

No bounties found

{searchQuery ? 'Try adjusting your search terms' : 'Check back later for new opportunities'}

)}
{/* Bounty Detail Modal */} {selectedBounty && (
{selectedBounty.title}
{selectedBounty.tier.charAt(0).toUpperCase() + selectedBounty.tier.slice(1)} {selectedBounty.status.charAt(0).toUpperCase() + selectedBounty.status.slice(1)}

{selectedBounty.reward_amount.toLocaleString()}

AITBC

Description

{selectedBounty.description}

Requirements

Minimum Accuracy

{selectedBounty.min_accuracy}%

{selectedBounty.max_response_time && (

Max Response Time

{selectedBounty.max_response_time}ms

)}

Submissions

{selectedBounty.submission_count}/{selectedBounty.max_submissions}

Deadline

{new Date(selectedBounty.deadline).toLocaleDateString()}

{selectedBounty.performance_criteria && (

Performance Criteria

                    {JSON.stringify(selectedBounty.performance_criteria, null, 2)}
                  
)} {selectedBounty.tags.length > 0 && (

Tags

{selectedBounty.tags.map((tag) => ( {tag} ))}
)}
{selectedBounty.status === 'active' && ( )}
)}
); }; export default BountyBoard;