import { useMemo } from "react"; import { useListCompareTools, getListCompareToolsQueryKey, type ToolWithStats, } from "@workspace/api-client-react"; import { Link, useSearch } from "wouter"; import { Layout } from "@/components/layout"; import { useAuth } from "@/hooks/use-auth"; import { RatingStars } from "@/components/rating-stars"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { ShieldAlert, Trophy, Scale } from "lucide-react"; import { cn } from "@/lib/utils"; import { useTranslation } from "react-i18next"; function fmt(v: number | null | undefined): string { return v != null ? v.toFixed(1) : "–"; } export default function Compare() { const { t } = useTranslation(); const { hasFeature, isLoading: authLoading } = useAuth(); const search = useSearch(); const params = useMemo(() => { const ids = new URLSearchParams(search) .get("ids") ?.split(",") .map((s) => Number(s.trim())) .filter((n) => Number.isInteger(n) && n > 0); return ids && ids.length > 0 ? { ids: ids.join(",") } : undefined; }, [search]); const canCompare = hasFeature("compare"); const { data: tools, isLoading: loading } = useListCompareTools( params ?? { ids: "" }, { query: { queryKey: getListCompareToolsQueryKey(params ?? { ids: "" }), enabled: !!params && canCompare, }, }, ); if (!authLoading && !canCompare) { return ( {t("compare.requiresPremium")} {t("compare.requiresPremiumSub")} {t("common.backToTools")} ); } if (loading) { return ( ); } const list = tools ?? []; if (list.length === 0) { return ( {t("compare.nothingToCompare")} {t("compare.nothingToCompareSub")} {t("common.browseTools")} ); } const bestRating = Math.max(...list.map((t) => t.avgCombined ?? 0)); const bestUsefulness = Math.max(...list.map((t) => t.avgUsefulness ?? 0)); const bestUsability = Math.max(...list.map((t) => t.avgUsability ?? 0)); const bestReviews = Math.max(...list.map((t) => t.ratingCount)); const isBest = (value: number | null, best: number) => value != null && value > 0 && value >= best && value === best; return ( {t("compare.title")} {t("compare.subtitle", { count: list.length })} {t("compare.sideBySide")} {t("compare.bestValue")} {t("compare.attribute")} {list.map((t) => ( {t.name} {t.category} ))} {t("compare.rating")} {list.map((t) => ( {fmt(t.avgCombined)}/5 {isBest(t.avgCombined, bestRating) && } ))} {t("detail.usefulness")} {list.map((t) => ( {fmt(t.avgUsefulness)}/5 ))} {t("detail.usability")} {list.map((t) => ( {fmt(t.avgUsability)}/5 ))} {t("compare.reviews")} {list.map((t) => ( {t.ratingCount} ))} {t("compare.description")} {list.map((t) => ( {t.description} ))} {t("filter.features")} {list.map((t) => ( {(t.features ?? []).map((f) => ( {f} ))} {(t.features?.length ?? 0) === 0 && —} ))} {t("filter.tags")} {list.map((t) => ( {(t.tags ?? []).map((tag) => ( {tag} ))} {(t.tags?.length ?? 0) === 0 && —} ))} {t("compare.lastUpdated")} {list.map((t) => ( {new Date(t.updatedAt).toLocaleDateString()} ))} {t("compare.backToBrowse")} ); }
{t("compare.requiresPremiumSub")}
{t("compare.nothingToCompareSub")}
{t("compare.subtitle", { count: list.length })}