spicebook/frontend/src/components/NotebookCard.tsx
Ryan Malloy 72cfd8191a Fix React hydration mismatch and clean up stale service workers
- Pin formatDate to timeZone: 'UTC' so server and client produce
  identical date strings (fixes React error #418)
- Add one-shot service worker unregistration since SpiceBook doesn't
  use one — clears phantom registrations from browser cache
2026-02-20 16:07:42 -07:00

70 lines
2.0 KiB
TypeScript

import { LayoutGrid } from 'lucide-react';
import { Badge } from './ui/Badge';
import type { NotebookSummary } from '../lib/types';
const engineVariant: Record<string, 'blue' | 'amber'> = {
ngspice: 'blue',
ltspice: 'amber',
};
function formatDate(iso: string): string {
try {
return new Date(iso).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
timeZone: 'UTC',
});
} catch {
return iso;
}
}
interface NotebookCardProps {
notebook: NotebookSummary;
}
export function NotebookCard({ notebook }: NotebookCardProps) {
const { id, title, engine, tags, cell_count, modified } = notebook;
return (
<a
href={`/notebook/${encodeURIComponent(id)}`}
className="group block rounded-lg border border-slate-800 bg-slate-900/50 overflow-hidden hover:border-blue-500/40 hover:bg-slate-800/70 transition-all"
>
<div className="notebook-card-strip" data-engine={engine} />
<div className="p-5">
<div className="flex items-start justify-between mb-2">
<h3 className="text-base font-semibold text-slate-100 group-hover:text-blue-400 transition-colors line-clamp-1">
{title}
</h3>
<Badge variant={engineVariant[engine] ?? 'default'} className="shrink-0 ml-2">
{engine}
</Badge>
</div>
<div className="flex items-center gap-3 text-xs text-slate-500 mb-3">
<span className="inline-flex items-center gap-1">
<LayoutGrid className="w-3 h-3" />
{cell_count} cell{cell_count !== 1 ? 's' : ''}
</span>
<span>{formatDate(modified)}</span>
</div>
{tags.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{tags.map((tag) => (
<span
key={tag}
className="text-xs bg-slate-700/50 text-slate-400 px-2 py-0.5 rounded"
>
{tag}
</span>
))}
</div>
)}
</div>
</a>
);
}