dear 'X" :
the Partial, this time's, is back in-game
} "use client"; import { useState, useEffect } from "react"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { Search, Star, Download, Users } from "lucide-react"; // Define types interface MarketplaceItem { id: string; name: string; description: string; category: string; rating: number; downloads: number; users: number; tags: string[]; author: string; version: string; lastUpdated: string; size: string; license: string; } // Mock data const mockItems: MarketplaceItem[] = [ { id: "1", name: "TaskFlow AI", description: "AI-powered task automation for personal productivity", category: "AI Models", rating: 4.8, downloads: 12500, users: 8900, tags: ["productivity", "ai", "automation"], author: "AI Labs", version: "2.3.1", lastUpdated: "2023-10-15", size: "45MB", license: "MIT" }, { id: "2", name: "WeatherPWA", description: "Beautiful weather app with location-based forecasts", category: "PWAs", rating: 4.5, downloads: 8700, users: 5400, tags: ["weather", "pwa", "forecast"], author: "Climate Devs", version: "1.8.2", lastUpdated: "2023-09-22", size: "12MB", license: "Apache 2.0" }, { id: "3", name: "DataSync Pro", description: "Cross-platform data synchronization tool", category: "Automations", rating: 4.9, downloads: 21000, users: 15600, tags: ["sync", "data", "automation"], author: "SyncMaster Inc", version: "3.1.0", lastUpdated: "2023-11-05", size: "68MB", license: "GPL v3" }, { id: "4", name: "CodeAssistant", description: "AI pair programmer for multiple languages", category: "AI Models", rating: 4.7, downloads: 34200, users: 28900, tags: ["coding", "ai", "developer"], author: "DevAI Solutions", version: "1.5.4", lastUpdated: "2023-10-30", size: "120MB", license: "MIT" }, { id: "5", name: "NoteVault", description: "Secure note-taking PWA with encryption", category: "PWAs", rating: 4.6, downloads: 15600, users: 9800, tags: ["notes", "pwa", "security"], author: "SecureApps", version: "2.0.1", lastUpdated: "2023-08-17", size: "22MB", license: "BSD" }, { id: "6", name: "SocialBot", description: "Automated social media scheduler and analyzer", category: "Automations", rating: 4.4, downloads: 9800, users: 6700, tags: ["social", "automation", "analytics"], author: "SocialTech", version: "1.9.3", lastUpdated: "2023-09-10", size: "35MB", license: "MIT" } ]; const categories = ["All", "PWAs", "AI Models", "Automations"]; export default function BazaarMarketplace() { const [items, setItems] = useState<MarketplaceItem[]>(mockItems); const [filteredItems, setFilteredItems] = useState<MarketplaceItem[]>(mockItems); const [searchTerm, setSearchTerm] = useState(""); const [selectedCategory, setSelectedCategory] = useState("All"); const [selectedItem, setSelectedItem] = useState<MarketplaceItem | null>(null); // Filter items based on search and category useEffect(() => { let result = items; if (selectedCategory !== "All") { result = result.filter(item => item.category === selectedCategory); } if (searchTerm) { const term = searchTerm.toLowerCase(); result = result.filter(item => item.name.toLowerCase().includes(term) || item.description.toLowerCase().includes(term) || item.tags.some(tag => tag.toLowerCase().includes(term)) ); } setFilteredItems(result); }, [searchTerm, selectedCategory, items]); const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { setSearchTerm(e.target.value); }; const handleCategoryChange = (value: string) => { setSelectedCategory(value); }; const openItemDetails = (item: MarketplaceItem) => { setSelectedItem(item); }; const closeItemDetails = () => { setSelectedItem(null); }; return ( <div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 p-4 md:p-8"> <div className="max-w-7xl mx-auto"> {/* Header */} <header className="mb-8 text-center"> <h1 className="text-4xl font-bold text-gray-900 mb-2">Bazaar Marketplace</h1> <p className="text-gray-600 max-w-2xl mx-auto"> Discover open-source PWAs, AI models, automations, and cross-platform tools </p> </header> {/* Search and Filter Section */} <div className="bg-white rounded-xl shadow-md p-6 mb-8"> <div className="flex flex-col md:flex-row gap-4"> <div className="relative flex-1"> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" /> <Input placeholder="Search tools, apps, models..." className="pl-10 py-6" value={searchTerm} onChange={handleSearch} /> </div> <div className="w-full md:w-64"> <Select value={selectedCategory} onValueChange={handleCategoryChange}> <SelectTrigger className="py-6"> <SelectValue placeholder="Category" /> </SelectTrigger> <SelectContent> {categories.map(category => ( <SelectItem key={category} value={category}> {category} </SelectItem> ))} </SelectContent> </Select> </div> </div> </div> {/* Results Info */} <div className="mb-6 flex justify-between items-center"> <p className="text-gray-600"> Showing {filteredItems.length} of {items.length} items </p> </div> {/* Items Grid */} {filteredItems.length === 0 ? ( <div className="text-center py-12"> <h3 className="text-xl font-semibold text-gray-900 mb-2">No items found</h3> <p className="text-gray-600">Try adjusting your search or filter criteria</p> </div> ) : ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {filteredItems.map(item => ( <Card key={item.id} className="overflow-hidden hover:shadow-lg transition-shadow cursor-pointer" onClick={() => openItemDetails(item)} > <CardHeader className="pb-3"> <div className="flex justify-between items-start"> <CardTitle className="text-xl">{item.name}</CardTitle> <span className="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded"> {item.category} </span> </div> <CardDescription className="line-clamp-2">{item.description}</CardDescription> </CardHeader> <CardContent> <div className="flex items-center mb-3"> <div className="flex items-center mr-4"> <Star className="h-4 w-4 text-yellow-400 fill-current" /> <span className="ml-1 text-sm font-medium">{item.rating}</span> </div> <div className="flex items-center"> <Download className="h-4 w-4 text-gray-500" /> <span className="ml-1 text-sm text-gray-600">{item.downloads.toLocaleString()}</span> </div> </div> <div className="flex flex-wrap gap-2"> {item.tags.slice(0, 3).map(tag => ( <span key={tag} className="bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded" > {tag} </span> ))} </div> </CardContent> <CardFooter className="bg-gray-50 py-3"> <div className="flex justify-between items-center w-full"> <span className="text-sm text-gray-600">by {item.author}</span> <Button variant="outline" size="sm">Install</Button> </div> </CardFooter> </Card> ))} </div> )} {/* Item Detail Modal */} {selectedItem && ( <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"> <div className="bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto" onClick={(e) => e.stopPropagation()} > <div className="p-6"> <div className="flex justify-between items-start mb-4"> <div> <h2 className="text-2xl font-bold text-gray-900">{selectedItem.name}</h2> <p className="text-gray-600">{selectedItem.category}</p> </div> <Button variant="ghost" onClick={closeItemDetails}>✕</Button> </div> <div className="mb-6"> <p className="text-gray-700 mb-4">{selectedItem.description}</p> <div className="grid grid-cols-2 gap-4 mb-6"> <div className="bg-gray-50 p-4 rounded-lg"> <h3 className="font-semibold text-gray-900 mb-2">Details</h3> <ul className="space-y-2 text-sm text-gray-600"> <li className="flex justify-between"> <span>Version:</span> <span className="font-medium">{selectedItem.version}</span> </li> <li className="flex justify-between"> <span>Last Updated:</span> <span className="font-medium">{selectedItem.lastUpdated}</span> </li> <li className="flex justify-between"> <span>Size:</span> <span className="font-medium">{selectedItem.size}</span> </li> <li className="flex justify-between"> <span>License:</span> <span className="font-medium">{selectedItem.license}</span> </li> </ul> </div> <div className="bg-gray-50 p-4 rounded-lg"> <h3 className="font-semibold text-gray-900 mb-2">Statistics</h3> <ul className="space-y-2 text-sm text-gray-600"> <li className="flex justify-between"> <span>Rating:</span> <span className="font-medium flex items-center"> <Star className="h-4 w-4 text-yellow-400 fill-current mr-1" /> {selectedItem.rating} </span> </li> <li className="flex justify-between"> <span>Downloads:</span> <span className="font-medium">{selectedItem.downloads.toLocaleString()}</span> </li> <li className="flex justify-between"> <span>Active Users:</span> <span className="font-medium flex items-center"> <Users className="h-4 w-4 text-gray-500 mr-1" /> {selectedItem.users.toLocaleString()} </span> </li> </ul> </div> </div> <div className="mb-6"> <h3 className="font-semibold text-gray-900 mb-2">Tags</h3> <div className="flex flex-wrap gap-2"> {selectedItem.tags.map(tag => ( <span key={tag} className="bg-blue-100 text-blue-800 text-sm px-3 py-1 rounded-full" > {tag} </span> ))} </div> </div> </div> <div className="flex flex-col sm:flex-row gap-3"> <Button className="flex-1">Install</Button> <Button variant="outline" className="flex-1">View Source</Button> </div> </div> </div> </div> )} </div> </div> ); } this one from the Bigg Hand