Files
website/components/publication-card.tsx
Steffen Illium 4255c61fab
All checks were successful
Next.js App CI / docker (push) Successful in 7m21s
Better Tracking
2025-09-16 11:45:04 +02:00

209 lines
5.5 KiB
TypeScript

"use client";
import { Button } from "@/components/ui/button";
import { CardTitle } from "@/components/ui/card";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import {
BookOpen,
Check,
Copy,
FileText,
Paperclip,
X,
} from "lucide-react";
import Image from "next/image";
import { useEffect, useState } from "react";
import { TrackedButton } from "./util-tracked-button";
import { TrackedLink } from "./util-tracked-link";
interface Props {
bibtexKey: string;
title: string;
authors: string[];
journal: string;
year: string;
url?: string;
pdfUrl?: string;
bibtex: string;
className?: string;
pdfAvailable?: boolean;
}
export function PublicationCard({
bibtexKey,
title,
authors,
journal,
year,
url,
pdfUrl,
bibtex,
className,
pdfAvailable = false,
}: Props) {
const [copyStatus, setCopyStatus] = useState<"idle" | "success" | "error">(
"idle"
);
const [imageError, setImageError] = useState(false);
const handleCopy = () => {
if (navigator.clipboard?.writeText) {
navigator.clipboard
.writeText(bibtex)
.then(() => {
setCopyStatus("success");
setTimeout(() => setCopyStatus("idle"), 2000);
})
.catch((err) => {
console.error("Failed to copy BibTeX:", err);
setCopyStatus("error");
setTimeout(() => setCopyStatus("idle"), 2000);
});
} else {
setCopyStatus("error");
setTimeout(() => setCopyStatus("idle"), 2000);
}
};
const handleDownload = () => {
const blob = new Blob([bibtex], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${bibtexKey}.bib`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url); // Clean up immediately
};
const handleCardClick = () => {
if (url) {
if (window.umami) {
window.umami.track(`${bibtexKey}-card-click`);
}
window.open(url, "_blank", "noopener,noreferrer");
}
};
const imageUrl = `/publications/${bibtexKey}.png`;
const formattedAuthors =
authors.length > 3
? `${authors.slice(0, 3).join(", ")}, et al.`
: authors.join(", ");
const ImageContent = (
<>
{!imageError ? (
<Image
src={imageUrl}
alt={`Preview for ${title}`}
fill
className="rounded-md object-cover object-top"
onError={() => setImageError(true)}
sizes="96px"
loading="lazy"
objectFit="cover"
/>
) : (
<div className="flex h-full w-full items-center justify-center rounded-md bg-muted/20">
<Paperclip className="h-6 w-6 text-muted-foreground" />
</div>
)}
</>
);
return (
<div
className={cn(
"cards flex items-start space-x-4 rounded-lg border p-4",
url,
className
)}
id={bibtexKey}
onClick={handleCardClick}
>
<div className="pointer-events-none relative h-24 w-24 flex-shrink-0">
{ImageContent}
</div>
<div className="pointer-events-none flex-grow space-y-1">
<CardTitle className="text-base font-medium leading-snug">
<span className={cn(url && "hover:underline")}>{title}</span>
</CardTitle>
<p className="text-sm text-muted-foreground">
{formattedAuthors}. <em>{journal}</em>. {year}
</p>
</div>
<div className="z-10 flex flex-shrink-0 flex-col space-y-1">
<Tooltip>
<TooltipTrigger asChild>
<TrackedButton
variant="ghost"
size="sm"
className="h-7 w-7 cursor-pointer px-2"
onClick={handleDownload}
eventName={`${bibtexKey}-bibfile`}
>
<FileText className="h-4 w-4" />
</TrackedButton>
</TooltipTrigger>
<TooltipContent>
<p>Download BibTeX</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<TrackedButton
variant="ghost"
size="sm"
className="relative h-7 w-7 cursor-pointer px-2"
onClick={handleCopy}
eventName={`${bibtexKey}-copycite`}
>
{copyStatus === "idle" && <Copy className="h-4 w-4" />}
{copyStatus === "success" && (
<Check className="h-4 w-4 text-green-500" />
)}
{copyStatus === "error" && <X className="h-4 w-4 text-red-500" />}
</TrackedButton>
</TooltipTrigger>
<TooltipContent>
<p>Copy Citation</p>
</TooltipContent>
</Tooltip>
{pdfUrl && pdfAvailable && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-7 w-7 px-2"
asChild
>
<TrackedLink
href={pdfUrl}
target="_blank"
eventName={`${bibtexKey}-pdf`}
rel="noopener noreferrer"
>
<BookOpen className="h-4 w-4" />
</TrackedLink>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Open PDF</p>
</TooltipContent>
</Tooltip>
)}
</div>
</div>
);
};