api / src /client /pages /Docs.tsx
OhMyDitzzy
Feat: add project
6d9f36a
import { useState, useMemo } from "react";
import { Navbar } from "@/components/Navbar";
import { PluginCard } from "@/components/PluginCard";
import { Footer } from "@/components/Footer";
import { StatsCard } from "@/components/StatsCard";
import { VisitorChart } from "@/components/VisitorChart";
import { usePlugins, useStats } from "@/client/hooks/usePlugin";
import { Activity, CheckCircle2, XCircle, TrendingUp, Loader2, Search, X } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
export default function Docs() {
const { plugins, loading: pluginsLoading } = usePlugins();
const { stats, loading: statsLoading } = useStats();
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const allTags = useMemo(() => {
const tagsSet = new Set<string>();
plugins.forEach(plugin => {
plugin.tags?.forEach(tag => tagsSet.add(tag));
});
return Array.from(tagsSet).sort();
}, [plugins]);
const filteredPlugins = useMemo(() => {
let filtered = plugins;
if (selectedCategory) {
filtered = filtered.filter((p) => p.category.includes(selectedCategory));
}
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
filtered = filtered.filter((p) =>
p.name.toLowerCase().includes(query) ||
p.description.toLowerCase().includes(query) ||
p.endpoint.toLowerCase().includes(query) ||
p.tags?.some(tag => tag.toLowerCase().includes(query))
);
}
if (selectedTags.length > 0) {
filtered = filtered.filter((p) =>
selectedTags.every(tag => p.tags?.includes(tag))
);
}
return filtered;
}, [plugins, selectedCategory, searchQuery, selectedTags]);
const toggleTag = (tag: string) => {
setSelectedTags(prev =>
prev.includes(tag)
? prev.filter(t => t !== tag)
: [...prev, tag]
);
};
const clearAllFilters = () => {
setSearchQuery("");
setSelectedTags([]);
setSelectedCategory(null);
};
return (
<div className="min-h-screen bg-background flex flex-col font-sans selection:bg-primary/30">
{/* Navbar with Categories in Hamburger Menu */}
<Navbar onCategorySelect={setSelectedCategory} selectedCategory={selectedCategory} />
{/* Main Content */}
<main className="flex-grow">
<div className="max-w-7xl mx-auto px-4 py-8">
{/* Statistics Cards */}
<div className="mb-8">
<h2 className="text-2xl font-bold text-white mb-4">API Statistics</h2>
{statsLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="w-8 h-8 text-purple-400 animate-spin" />
</div>
) : stats ? (
<>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<StatsCard
title="Total Requests"
value={stats.totalRequests.toLocaleString()}
icon={Activity}
color="purple"
/>
<StatsCard
title="Successful"
value={stats.totalSuccess.toLocaleString()}
icon={CheckCircle2}
color="green"
/>
<StatsCard
title="Failed"
value={stats.totalFailed.toLocaleString()}
icon={XCircle}
color="red"
/>
<StatsCard
title="Success Rate"
value={`${stats.successRate}%`}
icon={TrendingUp}
color="blue"
/>
</div>
{/* Visitor Chart */}
<VisitorChart />
</>
) : (
<div className="text-sm text-gray-500">Failed to load statistics</div>
)}
</div>
{/* Search and Filter Section */}
<div className="mb-6 space-y-4">
{/* Search Bar */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<Input
type="text"
placeholder="Search endpoints, descriptions, or tags..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 bg-white/[0.02] border-white/10 text-white placeholder:text-gray-500 focus:border-purple-500 h-12"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery("")}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white"
>
<X className="w-4 h-4" />
</button>
)}
</div>
{/* Tags Filter */}
{allTags.length > 0 && (
<div>
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-semibold text-gray-400">Filter by Tags</h3>
{selectedTags.length > 0 && (
<button
onClick={() => setSelectedTags([])}
className="text-xs text-purple-400 hover:text-purple-300"
>
Clear tags
</button>
)}
</div>
<div className="flex flex-wrap gap-2">
{allTags.map((tag) => (
<Badge
key={tag}
onClick={() => toggleTag(tag)}
className={`cursor-pointer transition-colors ${
selectedTags.includes(tag)
? "bg-purple-500/30 text-purple-300 border-purple-500 hover:bg-purple-500/40"
: "bg-white/5 text-gray-400 border-white/10 hover:bg-white/10"
} border`}
>
{tag}
</Badge>
))}
</div>
</div>
)}
{/* Active Filters Summary */}
{(selectedCategory || searchQuery || selectedTags.length > 0) && (
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm text-gray-400">Active filters:</span>
{selectedCategory && (
<Badge className="bg-blue-500/20 text-blue-400 border-blue-500/50">
Category: {selectedCategory}
</Badge>
)}
{searchQuery && (
<Badge className="bg-green-500/20 text-green-400 border-green-500/50">
Search: "{searchQuery}"
</Badge>
)}
{selectedTags.map(tag => (
<Badge key={tag} className="bg-purple-500/20 text-purple-400 border-purple-500/50">
Tag: {tag}
</Badge>
))}
<Button
variant="ghost"
size="sm"
onClick={clearAllFilters}
className="text-xs text-gray-400 hover:text-white"
>
Clear all
</Button>
</div>
)}
</div>
{/* Results Count */}
<div className="mb-6">
<h2 className="text-2xl font-bold text-white capitalize">
{selectedCategory ? `${selectedCategory} Endpoints` : "All Endpoints"}
</h2>
<p className="text-gray-400 text-sm mt-1">
Showing {filteredPlugins.length} of {plugins.length} endpoint{filteredPlugins.length !== 1 ? 's' : ''}
</p>
</div>
{/* Plugins List */}
<div className="space-y-6">
{pluginsLoading ? (
<div className="flex items-center justify-center py-20">
<Loader2 className="w-8 h-8 text-purple-400 animate-spin" />
</div>
) : filteredPlugins.length > 0 ? (
filteredPlugins.map((plugin) => (
<PluginCard key={plugin.endpoint} plugin={plugin} />
))
) : (
<div className="text-center py-20">
<div className="text-gray-400 text-lg mb-2">No endpoints found</div>
<div className="text-gray-600 text-sm mb-4">
{searchQuery || selectedTags.length > 0
? "Try adjusting your search or filters"
: selectedCategory
? "No plugins available in this category"
: "No plugins available"}
</div>
{(searchQuery || selectedTags.length > 0 || selectedCategory) && (
<Button
onClick={clearAllFilters}
variant="outline"
className="border-white/10 text-purple-400 hover:bg-purple-500/10"
>
Clear all filters
</Button>
)}
</div>
)}
</div>
</div>
</main>
<Footer />
</div>
);
}