Files
mcpedia/apps/web/app/components/DocumentCard.tsx
T
mytheclipsebotreviewandinternal-model 4330f47c65
CI / typecheck + tests (push) Canceled after 0s
CI / build + deploy (Nix) — api (push) Canceled after 0s
CI / build + deploy (Nix) — mcp (push) Canceled after 0s
CI / build + deploy (Nix) — worker (push) Canceled after 0s
CI / build + deploy (web) (push) Canceled after 0s
feat(web): FE overhaul — SWR centralized data layer + dashboard + islands
- Add SWR foundation (lib/fetcher, lib/swr, lib/api) with typed
  hooks: useSections/useDocuments/useSearch/useRelated/useRevisions/useQueueStatus
- Replace ad-hoc useEffect+fetch in Header/Sidebar/CommandMenu/search
  with SWR (dedup 4s, focus throttle 10s, debounced search 180ms)
- Doc page: RelatedGrid + RevisionList as SWR client islands (RSC hero stays)
- DocForm: SWR mutate on create/update for live sidebar invalidation
- New APIs: /api/related, /api/revisions, /api/tags, /api/queue/status,
  /api/docs filtering (?section=&status=)
- New UI: DocumentCard, SectionCard, SearchBar, TagCloud, QueueBadge,
  EmptyState, dashboard (/dashboard with QueueStrip live + section stats)
- Layout wraps SWRProvider; no TanStack code (SWR-only per spec)
- Verified: next build passes (16.3.4 Turbopack), no manual fetch sprawl remains

Co-Authored-By: internal-model
2026-09-08 19:37:27 +07:00

89 lines
3.7 KiB
TypeScript

import Link from "next/link";
import { Clock, FileText } from "lucide-react";
import type { DocumentMeta } from "@mcpedia/types";
import { getSectionMeta } from "@mcpedia/config/sections";
export function DocumentCard({
doc,
showSection = false,
score,
}: {
doc: DocumentMeta;
showSection?: boolean;
score?: number;
}) {
const sInfo = getSectionMeta(doc.section);
return (
<Link
href={`/${doc.slug}`}
className="group flex items-center justify-between gap-4 p-4 bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-xl transition-all shadow-xs"
>
<div className="flex items-center gap-3 min-w-0">
<FileText className="w-4 h-4 text-[var(--text-muted)] shrink-0" />
<div className="min-w-0">
<h3 className="text-xs sm:text-sm font-semibold text-[var(--text-primary)] group-hover:underline truncate">
{doc.title}
</h3>
<div className="flex flex-wrap items-center gap-1.5 mt-1.5">
{showSection && (
<span className="text-[10px] font-mono px-1.5 py-0.2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)] uppercase font-semibold">
{sInfo.icon} {doc.section}
</span>
)}
{doc.tags.slice(0, 4).map((t: string) => (
<span
key={t}
className="text-[10px] px-1.5 py-0.2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)] font-mono"
>
#{t}
</span>
))}
{typeof score === "number" && (
<span className="text-[10px] font-mono text-[var(--text-dim)] bg-[var(--bg-elevated)] px-1.5 py-0.2 rounded">
{score.toFixed(3)}
</span>
)}
</div>
</div>
</div>
<time className="text-xs text-[var(--text-dim)] font-mono shrink-0 flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>
{new Date(doc.updatedAt).toLocaleDateString(undefined, { month: "short", day: "numeric" })}
</span>
</time>
</Link>
);
}
export function CompactDocCard({ doc, snippet, score }: { doc: DocumentMeta; snippet?: string; score?: number }) {
const sInfo = getSectionMeta(doc.section);
return (
<Link
href={`/${doc.slug}`}
className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-xl p-4.5 transition-all shadow-xs"
>
<div className="flex items-center justify-between gap-3 mb-2">
<div className="flex items-center gap-2">
<span className="text-[10px] font-mono px-1.5 py-0.2 rounded bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] uppercase font-semibold">
{sInfo.icon} {doc.section}
</span>
<span className="text-xs text-[var(--text-dim)] font-mono truncate">/{doc.slug}</span>
</div>
{typeof score === "number" && (
<span className="text-xs font-mono text-[var(--text-dim)] bg-[var(--bg-elevated)] px-2 py-0.5 rounded">
{score.toFixed(3)}
</span>
)}
</div>
<h2 className="text-sm font-semibold text-[var(--text-primary)] group-hover:underline mb-1.5">{doc.title}</h2>
{snippet && (
<p
className="text-xs text-[var(--text-muted)] leading-relaxed line-clamp-2 [&>mark]:bg-[var(--bg-elevated-hover)] [&>mark]:text-[var(--text-primary)] [&>mark]:font-semibold"
dangerouslySetInnerHTML={{ __html: snippet }}
/>
)}
</Link>
);
}