Better Tracking
All checks were successful
Next.js App CI / docker (push) Successful in 7m21s

This commit is contained in:
2025-09-16 11:45:04 +02:00
parent 669e915e1d
commit 4255c61fab
3 changed files with 96 additions and 99 deletions

View File

@@ -17,8 +17,8 @@ import {
} from "lucide-react"; } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { TrackedLink } from "./util-tracked-link";
import { TrackedButton } from "./util-tracked-button"; import { TrackedButton } from "./util-tracked-button";
import { TrackedLink } from "./util-tracked-link";
interface Props { interface Props {
bibtexKey: string; bibtexKey: string;
@@ -49,24 +49,9 @@ export function PublicationCard({
"idle" "idle"
); );
const [imageError, setImageError] = useState(false); const [imageError, setImageError] = useState(false);
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
const [isClient, setIsClient] = useState(false);
useEffect(() => { const handleCopy = () => {
setIsClient(true); if (navigator.clipboard?.writeText) {
}, []);
useEffect(() => {
return () => {
if (downloadUrl) {
URL.revokeObjectURL(downloadUrl);
}
};
}, [downloadUrl]);
const handleCopy = (e: React.MouseEvent) => {
e.stopPropagation();
if (isClient && navigator.clipboard?.writeText) {
navigator.clipboard navigator.clipboard
.writeText(bibtex) .writeText(bibtex)
.then(() => { .then(() => {
@@ -84,17 +69,16 @@ export function PublicationCard({
} }
}; };
const handleDownload = (e: React.MouseEvent) => { const handleDownload = () => {
e.stopPropagation();
const blob = new Blob([bibtex], { type: "text/plain" }); const blob = new Blob([bibtex], { type: "text/plain" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
setDownloadUrl(url);
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = url;
a.download = `${bibtexKey}.bib`; a.download = `${bibtexKey}.bib`;
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); // Clean up immediately
}; };
const handleCardClick = () => { const handleCardClick = () => {
@@ -133,94 +117,93 @@ export function PublicationCard({
</> </>
); );
return ( return (
<div <div
className={cn( className={cn(
"cards flex items-start space-x-4 rounded-lg border p-4", "cards flex items-start space-x-4 rounded-lg border p-4",
url, url,
className className
)} )}
id={bibtexKey} id={bibtexKey}
onClick={handleCardClick} onClick={handleCardClick}
> >
<div className="pointer-events-none relative h-24 w-24 flex-shrink-0"> <div className="pointer-events-none relative h-24 w-24 flex-shrink-0">
{ImageContent} {ImageContent}
</div> </div>
<div className="pointer-events-none flex-grow space-y-1"> <div className="pointer-events-none flex-grow space-y-1">
<CardTitle className="text-base font-medium leading-snug"> <CardTitle className="text-base font-medium leading-snug">
<span className={cn(url && "hover:underline")}>{title}</span> <span className={cn(url && "hover:underline")}>{title}</span>
</CardTitle> </CardTitle>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{formattedAuthors}. <em>{journal}</em>. {year} {formattedAuthors}. <em>{journal}</em>. {year}
</p> </p>
</div> </div>
<div className="z-10 flex flex-shrink-0 flex-col space-y-1"> <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> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <TrackedButton
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-7 w-7 px-2" className="h-7 w-7 cursor-pointer px-2"
asChild onClick={handleDownload}
onClick={(e) => e.stopPropagation()} eventName={`${bibtexKey}-bibfile`}
> >
<TrackedLink <FileText className="h-4 w-4" />
href={pdfUrl} </TrackedButton>
target="_blank"
eventName={`${bibtexKey}-pdf`}
rel="noopener noreferrer"
>
<BookOpen className="h-4 w-4" />
</TrackedLink>
</Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Open PDF</p> <p>Download BibTeX</p>
</TooltipContent> </TooltipContent>
</Tooltip> </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> </div>
</div> );
);
}; };

View File

@@ -18,6 +18,9 @@ export const TrackedButton = ({
const handleTrackedClick = ( const handleTrackedClick = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent> e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => { ) => {
e.stopPropagation();
if (window.umami) { if (window.umami) {
window.umami.track(eventName); window.umami.track(eventName);
} }

11
types/umami.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
// types/umami.d.ts
declare global {
interface Window {
umami?: {
track: (event_name: string, data?: object) => void;
};
}
}
export {};