feat: auth foundation, similar tools, costs, redundancy, anonymous voting
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Layout } from "@/components/layout";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Star, AlertTriangle } from "lucide-react";
|
||||
import { Link } from "wouter";
|
||||
import { customFetch } from "@workspace/api-client-react";
|
||||
|
||||
export default function RedundancyPage() {
|
||||
const [data, setData] = useState<any[] | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
customFetch<any[]>("/api/admin/redundancy")
|
||||
.then(setData)
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertTriangle className="w-6 h-6 text-amber-500" />
|
||||
<h1 className="text-3xl font-bold">Redundancy Dashboard</h1>
|
||||
</div>
|
||||
<p className="text-muted-foreground">
|
||||
Tools grouped by category with feature overlap analysis.
|
||||
</p>
|
||||
|
||||
{loading ? (
|
||||
<div className="space-y-6">
|
||||
{Array.from({ length: 3 }).map((_, i) => <Skeleton key={i} className="h-40 rounded-xl" />)}
|
||||
</div>
|
||||
) : data && data.length > 0 ? (
|
||||
<div className="space-y-8">
|
||||
{data.map((group) => (
|
||||
<div key={group.category}>
|
||||
<h2 className="text-xl font-semibold mb-4 capitalize">{group.category}</h2>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
|
||||
{group.tools.map((tool: any) => (
|
||||
<Link key={tool.id} href={`/tools/${tool.id}`}>
|
||||
<Card className="hover-elevate transition-all cursor-pointer hover:border-primary/50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<span className="font-medium">{tool.name}</span>
|
||||
<div className="flex items-center gap-2 mt-1 text-xs text-muted-foreground">
|
||||
<span>{tool.ratingCount} reviews</span>
|
||||
{tool.avgCombined != null && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span className="flex items-center gap-0.5">
|
||||
<Star className="w-3 h-3 fill-primary text-primary" />
|
||||
{tool.avgCombined.toFixed(1)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline">{tool.features.length} features</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{group.pairs.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">Overlap Analysis</h3>
|
||||
{group.pairs.map((pair: any, i: number) => (
|
||||
<Card key={i} className="border-dashed">
|
||||
<CardContent className="p-3 flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
<span className="font-medium text-sm truncate">{pair.a.name}</span>
|
||||
<span className="text-muted-foreground text-xs shrink-0">vs</span>
|
||||
<span className="font-medium text-sm truncate">{pair.b.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Progress value={pair.overlap} className="w-16 h-2" />
|
||||
<span className="text-xs text-muted-foreground w-8">{pair.overlap}%</span>
|
||||
</div>
|
||||
{pair.scoreDiff !== 0 && (
|
||||
<Badge variant={pair.scoreDiff > 0 ? "default" : "secondary"} className="text-[10px]">
|
||||
{pair.scoreDiff > 0 ? `${pair.b.name} +${pair.scoreDiff.toFixed(1)}` : `${pair.a.name} +${Math.abs(pair.scoreDiff).toFixed(1)}`}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground py-8 text-center">No tools found.</p>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user