3de59ccaaf
Build & Push Docker Image / build (push) Successful in 2m33s
- translate remaining pages/components (admin, analytics, redundancy, trash, compare, browse, watchlist, admin tools tab, category combobox, theme toggle, tool preview card, tool-new/edit) to t() calls - locales: 453 keys each, parity verified - docs: bilingual handbook + release notes via *.en.md variants, language-aware docs.tsx (markdown paths, nav titles, search index), LanguageSwitcher in docs header - generate-docs: emit per-locale handbook/release/search output, localized index.json fields (fileEn/titleEn), en-aware snapshots
56 lines
2.1 KiB
TypeScript
56 lines
2.1 KiB
TypeScript
import { Link } from "wouter";
|
||
import { useTranslation } from "react-i18next";
|
||
import type { ToolWithStats } from "@workspace/api-client-react";
|
||
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { RatingStars } from "@/components/rating-stars";
|
||
import { MiniBars } from "@/components/mini-bars";
|
||
|
||
export function ToolPreviewCard({ tool }: { tool: ToolWithStats }) {
|
||
const { t } = useTranslation();
|
||
return (
|
||
<div className="space-y-3">
|
||
<div>
|
||
<div className="flex items-start justify-between gap-2">
|
||
<div className="min-w-0">
|
||
<div className="font-semibold leading-tight truncate">{tool.name}</div>
|
||
<div className="text-xs text-muted-foreground">{tool.category}</div>
|
||
</div>
|
||
<RatingStars value={tool.avgCombined ?? 0} size="sm" />
|
||
</div>
|
||
</div>
|
||
|
||
<p className="text-xs text-muted-foreground line-clamp-3">{tool.description}</p>
|
||
|
||
<div className="space-y-1.5">
|
||
<div className="flex items-center justify-between text-xs">
|
||
<span className="text-muted-foreground">{t("browse.reviewCount", { count: tool.ratingCount })}</span>
|
||
<span className="font-semibold tabular-nums">
|
||
{tool.avgCombined != null ? tool.avgCombined.toFixed(1) : "–"}/5
|
||
</span>
|
||
</div>
|
||
<MiniBars usefulness={tool.avgUsefulness} usability={tool.avgUsability} />
|
||
</div>
|
||
|
||
{tool.tags && tool.tags.length > 0 && (
|
||
<div className="flex flex-wrap gap-1">
|
||
{tool.tags.slice(0, 4).map((tag) => (
|
||
<Badge key={tag} variant="secondary" className="text-[10px]">
|
||
{tag}
|
||
</Badge>
|
||
))}
|
||
{tool.tags.length > 4 && (
|
||
<span className="text-[10px] text-muted-foreground">+{tool.tags.length - 4}</span>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
<Link
|
||
href={`/tools/${tool.id}`}
|
||
className="block w-full rounded-md border border-primary/40 bg-primary/5 px-3 py-1.5 text-center text-xs font-medium text-primary hover:bg-primary/10"
|
||
>
|
||
{t("common.viewDetails")}
|
||
</Link> </div>
|
||
);
|
||
}
|