feat(analytics): add external link click tracking in footer and header components

This commit is contained in:
Fatih Kadir Akın
2026-01-05 22:10:15 +03:00
parent 859d6fc59a
commit 119564e3ba
8 changed files with 90 additions and 4 deletions

View File

@@ -15,6 +15,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { toast } from "sonner";
import { analyticsComment } from "@/lib/analytics";
interface CommentFormProps {
promptId: string;
@@ -90,6 +91,7 @@ export function CommentForm({
const data = await response.json();
onCommentAdded(data.comment);
setContent("");
analyticsComment.post(promptId, !!parentId);
toast.success(t("commentPosted"));
} catch (error) {
toast.error(error instanceof Error ? error.message : tCommon("error"));

View File

@@ -5,6 +5,7 @@ import Link from "next/link";
import { useTranslations } from "next-intl";
import DeepWikiIcon from "@/../public/deepwiki.svg";
import { useBranding } from "@/components/providers/branding-provider";
import { analyticsExternal } from "@/lib/analytics";
export function Footer() {
const branding = useBranding();
@@ -21,7 +22,7 @@ export function Footer() {
<nav className="flex flex-wrap items-center justify-center gap-x-4 gap-y-2">
{!branding.useCloneBranding && (
<>
<Link href="https://deepwiki.com/f/awesome-chatgpt-prompts" target="_blank" rel="noopener noreferrer" className="hover:text-foreground flex items-center gap-1">
<Link href="https://deepwiki.com/f/awesome-chatgpt-prompts" target="_blank" rel="noopener noreferrer" className="hover:text-foreground flex items-center gap-1" onClick={() => analyticsExternal.clickFooterLink("deepwiki")}>
<Image src={DeepWikiIcon} alt="" width={14} height={14} />
DeepWiki
</Link>
@@ -34,7 +35,7 @@ export function Footer() {
<Link href="/about" className="hover:text-foreground">{t("about")}</Link>
</>
)}
<Link href="https://github.com/f/awesome-chatgpt-prompts" target="_blank" rel="noopener noreferrer" className="hover:text-foreground flex items-center gap-1">
<Link href="https://github.com/f/awesome-chatgpt-prompts" target="_blank" rel="noopener noreferrer" className="hover:text-foreground flex items-center gap-1" onClick={() => analyticsExternal.clickFooterLink("github")}>
<svg className="h-3.5 w-3.5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
</svg>

View File

@@ -45,7 +45,7 @@ import {
import { NotificationBell } from "@/components/layout/notification-bell";
import { setLocale } from "@/lib/i18n/client";
import { useBranding } from "@/components/providers/branding-provider";
import { analyticsAuth, analyticsSettings } from "@/lib/analytics";
import { analyticsAuth, analyticsSettings, analyticsExternal } from "@/lib/analytics";
import { isChromeBrowser } from "@/lib/utils";
const languages = [
@@ -349,6 +349,7 @@ export function Header({ authProvider = "credentials", allowRegistration = true
href={branding.chromeExtensionUrl}
target="_blank"
rel="noopener noreferrer"
onClick={() => analyticsExternal.clickChromeExtension()}
>
<Chromium className="h-4 w-4" />
<span className="sr-only">Get Chrome Extension</span>

View File

@@ -4,6 +4,7 @@ import { useState } from "react";
import { Check, Copy } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { analyticsMcp } from "@/lib/analytics";
type Client = "cursor" | "claude-code" | "vscode" | "codex" | "windsurf" | "gemini";
type Mode = "remote" | "local";
@@ -209,6 +210,7 @@ export function McpConfigTabs({ baseUrl, queryParams, className, mode, onModeCha
const handleCopy = async () => {
await navigator.clipboard.writeText(config);
analyticsMcp.copyCommand(`${selectedClient}-${selectedMode}`);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

View File

@@ -15,6 +15,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { McpConfigTabs } from "./mcp-config-tabs";
import { analyticsMcp } from "@/lib/analytics";
// MCP Logo component - shows dark version in dark mode
export function McpIcon({ className }: { className?: string }) {
@@ -129,7 +130,10 @@ export function McpServerPopup({
const removeTag = (tag: string) => setTags(tags.filter((t) => t !== tag));
return (
<Popover modal open={isOpen} onOpenChange={setIsOpen}>
<Popover modal open={isOpen} onOpenChange={(open) => {
if (open) analyticsMcp.openPopup();
setIsOpen(open);
}}>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 gap-1.5">
<McpIcon className="h-4 w-4" />

View File

@@ -5,6 +5,7 @@ import { useTranslations } from "next-intl";
import { Bookmark, Check, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { analyticsCollection } from "@/lib/analytics";
interface AddToCollectionButtonProps {
promptId: string;
@@ -38,6 +39,7 @@ export function AddToCollectionButton({
if (res.ok) {
setInCollection(false);
analyticsCollection.remove(promptId);
setShowTooltip(true);
setTimeout(() => setShowTooltip(false), 2000);
}
@@ -50,6 +52,7 @@ export function AddToCollectionButton({
if (res.ok) {
setInCollection(true);
analyticsCollection.add(promptId);
setShowTooltip(true);
setTimeout(() => setShowTooltip(false), 2000);
}

View File

@@ -5,6 +5,7 @@ import { useLocale, useTranslations } from "next-intl";
import { Languages, Loader2 } from "lucide-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { toast } from "sonner";
import { analyticsTranslate } from "@/lib/analytics";
// Map locale codes to full language names for OpenAI
const localeToLanguage: Record<string, string> = {
@@ -70,6 +71,7 @@ export function TranslateButton({ content, onTranslate, isLoggedIn }: TranslateB
const data = await response.json();
onTranslate(data.translatedContent);
setIsTranslated(true);
analyticsTranslate.translate(targetLanguage);
toast.success(t("translated"));
} catch (error) {
console.error("Translation error:", error);

View File

@@ -546,6 +546,77 @@ export const analyticsEngagement = {
},
};
// ============================================================================
// Comment Events
// ============================================================================
export const analyticsComment = {
post: (promptId: string, isReply: boolean) => {
trackEvent({
action: isReply ? "post_reply" : "post_comment",
category: "comment",
prompt_id: promptId,
});
},
};
// ============================================================================
// Collection Events
// ============================================================================
export const analyticsCollection = {
add: (promptId: string) => {
trackEvent({
action: "add_to_collection",
category: "collection",
prompt_id: promptId,
});
},
remove: (promptId: string) => {
trackEvent({
action: "remove_from_collection",
category: "collection",
prompt_id: promptId,
});
},
};
// ============================================================================
// Translation Events
// ============================================================================
export const analyticsTranslate = {
translate: (targetLanguage: string) => {
trackEvent({
action: "translate_prompt",
category: "translate",
label: targetLanguage,
});
},
};
// ============================================================================
// External Link Events
// ============================================================================
export const analyticsExternal = {
clickChromeExtension: () => {
trackEvent({
action: "chrome_extension_click",
category: "external",
});
},
clickFooterLink: (linkName: string) => {
trackEvent({
action: "footer_link_click",
category: "external",
label: linkName,
});
},
};
// ============================================================================
// Admin Events
// ============================================================================