fix: feat: basic analytics funnel tracking for launch readiness (#1101)

Add self-hosted Umami analytics to replace the third-party cloud.umami.is
tracker. Creates @harb/analytics package with typed event helpers and
instruments the conversion funnel: CTA clicks (landing), wallet connect,
swap initiated, and stake created (web-app).

- Add Umami Docker service sharing existing postgres (separate DB)
- Add Caddy /analytics route to proxy Umami dashboard
- Configure via VITE_UMAMI_URL and VITE_UMAMI_WEBSITE_ID env vars
- Document setup and funnel events in docs/ENVIRONMENT.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
johba 2026-03-23 13:04:24 +00:00
parent 8d67e61c17
commit ca2bc03567
17 changed files with 195 additions and 6 deletions

View file

@ -0,0 +1,8 @@
{
"name": "@harb/analytics",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts"
}

View file

@ -0,0 +1,69 @@
/**
* Lightweight analytics wrapper for self-hosted Umami.
*
* Call `initAnalytics()` once at app startup to inject the tracker
* script. All other helpers are safe to call at any time if the
* tracker hasn't loaded (ad-blocker, missing config, dev without
* Umami running) every call is a silent no-op.
*/
interface UmamiTracker {
track: (name: string, data?: Record<string, string | number | boolean>) => void;
}
declare global {
interface Window {
umami?: UmamiTracker;
}
}
/**
* Inject the Umami tracker script.
* @param scriptUrl full URL to `script.js`, e.g. `https://analytics.kraiken.org/script.js`
* @param websiteId Umami website ID (UUID)
*/
export function initAnalytics(scriptUrl: string | undefined, websiteId: string | undefined): void {
if (!scriptUrl || !websiteId) return;
if (typeof document === 'undefined') return;
if (document.querySelector(`script[data-website-id="${websiteId}"]`)) return;
const el = document.createElement('script');
el.defer = true;
el.src = scriptUrl;
el.dataset.websiteId = websiteId;
document.head.appendChild(el);
}
function getTracker(): UmamiTracker | undefined {
return typeof window !== 'undefined' ? window.umami : undefined;
}
/** Fire a named event with optional properties. */
export function trackEvent(
name: string,
data?: Record<string, string | number | boolean>,
): void {
getTracker()?.track(name, data);
}
/* ── Funnel events ─────────────────────────────────────────────── */
/** Landing page CTA clicked (e.g. "Get $KRK"). */
export function trackCtaClick(label: string): void {
trackEvent('cta_click', { label });
}
/** Wallet successfully connected. */
export function trackWalletConnect(): void {
trackEvent('wallet_connect');
}
/** Swap (buy/sell) initiated — user submitted the transaction. */
export function trackSwapInitiated(direction: 'buy' | 'sell'): void {
trackEvent('swap_initiated', { direction });
}
/** Stake position created (snatch completed). */
export function trackStakeCreated(): void {
trackEvent('stake_created');
}