Skip to content

Commit 70dde51

Browse files
actions-userclaude
andcommitted
Add Supabase analytics tracking
- page_view on every page load - project_click when a project is opened (homepage + projects page) - post_read when a post is opened (homepage + writing listing + post view) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 268f512 commit 70dde51

5 files changed

Lines changed: 70 additions & 4 deletions

File tree

‎js/analytics.js‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* ============================================
2+
Analytics — Supabase event tracking
3+
Anon key is safe to expose (insert-only RLS)
4+
============================================ */
5+
6+
const URL = 'https://bfqcfhvpauvvakgmeuhr.supabase.co/rest/v1/events';
7+
const KEY = 'sb_publishable_yH1CSzF3YQ8FpU-hF-QEtg_ZWaKDLsR';
8+
9+
const HEADERS = {
10+
'Content-Type': 'application/json',
11+
'apikey': KEY,
12+
'Authorization': `Bearer ${KEY}`,
13+
'Prefer': 'return=minimal'
14+
};
15+
16+
export async function trackEvent(event_type, label = null) {
17+
try {
18+
await fetch(URL, {
19+
method: 'POST',
20+
headers: HEADERS,
21+
body: JSON.stringify({
22+
event_type,
23+
label,
24+
page: window.location.pathname
25+
})
26+
});
27+
} catch {
28+
// Silent fail — never break UX for analytics
29+
}
30+
}
31+
32+
export function trackPageView() {
33+
trackEvent('page_view', document.title.replace('jebin2 — ', '') || 'home');
34+
}

‎js/blog.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { initPage, renderSkeletonRows } from './shared.js';
77
import { fetchCached, fetchTextCached } from './cache.js';
88
import { formatDate, formatDateShort } from './utils.js';
9+
import { trackEvent } from './analytics.js';
910

1011
async function init() {
1112
const params = new URLSearchParams(window.location.search);
@@ -35,6 +36,11 @@ async function initListingView() {
3536
try {
3637
const manifest = await fetchCached(config.blog_manifest);
3738
renderListing(manifest.posts || [], document.getElementById('posts-list'));
39+
// Track post clicks from listing
40+
document.getElementById('posts-list')?.addEventListener('click', e => {
41+
const row = e.target.closest('[data-track]');
42+
if (row) trackEvent('post_read', row.dataset.track);
43+
});
3844
} catch (err) {
3945
console.error(err);
4046
document.getElementById('posts-list').innerHTML =
@@ -66,7 +72,8 @@ function renderListing(posts, container) {
6672
<div class="group-label">— ${cat} —</div>
6773
<div class="project-group-items">
6874
${items.map(p => `
69-
<a class="blog-row" href="/writing?post=${encodeURIComponent(p.path)}">
75+
<a class="blog-row" href="/writing?post=${encodeURIComponent(p.path)}"
76+
data-track="${p.title}">
7077
<span class="blog-title">${p.title}</span>
7178
<span class="blog-meta">
7279
<span class="blog-cat">[ ${cat} ]</span>
@@ -101,6 +108,7 @@ async function initPostView(postPath) {
101108
const nextPost = idx > 0 ? posts[idx - 1] : null;
102109

103110
document.title = meta ? `jebin2 — ${meta.title}` : 'jebin2 — writing';
111+
if (meta?.title) trackEvent('post_read', meta.title);
104112

105113
renderPost(mdText, meta, prevPost, nextPost, main);
106114
} catch (err) {

‎js/home.js‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import { initPage, renderSkeletonRows } from './shared.js';
66
import { fetchCached } from './cache.js';
77
import { formatDateShort } from './utils.js';
8+
import { trackEvent } from './analytics.js';
89

910
async function init() {
1011
const config = await initPage('');
@@ -43,13 +44,18 @@ function renderFeatured(projects, container) {
4344

4445
container.innerHTML = featured.map((p, i) => `
4546
<a class="project-row" href="${p.url}" target="_blank" rel="noopener noreferrer"
46-
title="${p.description}">
47+
title="${p.description}" data-track="${p.title}">
4748
<span class="project-num">${String(i + 1).padStart(2, '0')}.</span>
4849
<span class="project-name">${p.title}</span>
4950
<span class="project-desc">${p.description}</span>
5051
<span class="project-tag">[ ${p.category} ]</span>
5152
</a>
5253
`).join('');
54+
55+
container.addEventListener('click', e => {
56+
const row = e.target.closest('[data-track]');
57+
if (row) trackEvent('project_click', row.dataset.track);
58+
});
5359
}
5460

5561
function renderLatestPosts(manifest, container) {
@@ -68,13 +74,18 @@ function renderLatestPosts(manifest, container) {
6874
container.innerHTML = posts.map((p, i) => {
6975
const slug = encodeURIComponent(p.path);
7076
return `
71-
<a class="project-row" href="/writing?post=${slug}">
77+
<a class="project-row" href="/writing?post=${slug}" data-track="${p.title}">
7278
<span class="project-num">${String(i + 1).padStart(2, '0')}.</span>
7379
<span class="project-name">${p.title}</span>
7480
<span class="blog-date">${formatDateShort(p.date)}</span>
7581
</a>
7682
`;
7783
}).join('');
84+
85+
container.addEventListener('click', e => {
86+
const row = e.target.closest('[data-track]');
87+
if (row) trackEvent('post_read', row.dataset.track);
88+
});
7889
}
7990

8091
init();

‎js/projects.js‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import { initPage, renderSkeletonRows } from './shared.js';
66
import { fetchCached } from './cache.js';
7+
import { trackEvent } from './analytics.js';
78

89
const GROUPS = [
910
{ key: 'ai', label: '— ai —' },
@@ -24,6 +25,7 @@ async function init() {
2425
allProjects = await fetchCached(config.projects);
2526
render('all');
2627
initFilter();
28+
initTracking();
2729
} catch (err) {
2830
console.error(err);
2931
if (listEl) listEl.innerHTML = '<p class="state-msg">failed to load projects.</p>';
@@ -75,14 +77,23 @@ function projectRow(p) {
7577
href="${p.url}"
7678
target="_blank"
7779
rel="noopener noreferrer"
78-
title="${p.description}">
80+
title="${p.description}"
81+
data-track="${p.title}">
7982
<span class="project-name">${p.title}</span>
8083
<span class="project-desc">${p.description}</span>
8184
<span class="project-tag">[ ${p.category} ]</span>
8285
</a>
8386
`;
8487
}
8588

89+
/* ---- Track project clicks ---- */
90+
function initTracking() {
91+
document.getElementById('project-list')?.addEventListener('click', e => {
92+
const row = e.target.closest('[data-track]');
93+
if (row) trackEvent('project_click', row.dataset.track);
94+
});
95+
}
96+
8697
/* ---- Filter buttons ---- */
8798
function initFilter() {
8899
const buttons = document.querySelectorAll('.filter-btn');

‎js/shared.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
============================================ */
44

55
import { fetchCached } from './cache.js';
6+
import { trackPageView } from './analytics.js';
67

78
let _config = null;
89

@@ -95,5 +96,6 @@ export async function initPage(activePage = '') {
9596
if (footerEl) footerEl.innerHTML = renderFooter(config);
9697

9798
initNav();
99+
trackPageView();
98100
return config;
99101
}

0 commit comments

Comments
 (0)