From 7b5685cc25d6c9b847da30da7f02e363d4eb85ce Mon Sep 17 00:00:00 2001 From: Sanju Sivalingam Date: Wed, 18 Feb 2026 22:44:01 +0530 Subject: [PATCH] feat: add Umami analytics tracking across web app Add centralized event tracking with consistent naming convention. Tracks auth flows, license activation, device interactions, API key management, settings, and navigation for conversion analysis. --- web/src/app.html | 4 + web/src/lib/analytics/events.ts | 41 ++++ web/src/lib/analytics/track.ts | 19 ++ web/src/lib/components/DeviceCard.svelte | 48 ++--- web/src/routes/dashboard/+layout.svelte | 4 + web/src/routes/dashboard/+page.svelte | 3 + .../routes/dashboard/activate/+page.svelte | 6 +- .../routes/dashboard/api-keys/+page.svelte | 5 + .../dashboard/devices/[deviceId]/+page.svelte | 199 +++++++++++++----- .../routes/dashboard/settings/+page.svelte | 3 + web/src/routes/login/+page.svelte | 73 +++++-- web/src/routes/signup/+page.svelte | 92 +++++--- 12 files changed, 370 insertions(+), 127 deletions(-) create mode 100644 web/src/lib/analytics/events.ts create mode 100644 web/src/lib/analytics/track.ts diff --git a/web/src/app.html b/web/src/app.html index f273cc5..5053263 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -3,7 +3,11 @@ + + + %sveltekit.head% +
%sveltekit.body%
diff --git a/web/src/lib/analytics/events.ts b/web/src/lib/analytics/events.ts new file mode 100644 index 0000000..3eb83c6 --- /dev/null +++ b/web/src/lib/analytics/events.ts @@ -0,0 +1,41 @@ +/** + * Centralized Umami analytics event names. + * + * Naming convention: {category}-{action} + * Use these constants everywhere to keep tracking consistent. + * Umami auto-tracks page views — these are for custom events only. + */ + +// ─── Auth ──────────────────────────────────────────── +export const AUTH_LOGIN_SUBMIT = 'auth-login-submit'; +export const AUTH_LOGIN_SUCCESS = 'auth-login-success'; +export const AUTH_SIGNUP_SUBMIT = 'auth-signup-submit'; +export const AUTH_SIGNUP_SUCCESS = 'auth-signup-success'; +export const AUTH_SIGNOUT = 'auth-signout'; + +// ─── License / Conversion ──────────────────────────── +export const LICENSE_ACTIVATE_CHECKOUT = 'license-activate-checkout'; +export const LICENSE_ACTIVATE_MANUAL = 'license-activate-manual'; +export const LICENSE_PURCHASE_CLICK = 'license-purchase-click'; + +// ─── Dashboard ─────────────────────────────────────── +export const DASHBOARD_CARD_CLICK = 'dashboard-card-click'; + +// ─── Devices ───────────────────────────────────────── +export const DEVICE_CARD_CLICK = 'device-card-click'; +export const DEVICE_TAB_CHANGE = 'device-tab-change'; +export const DEVICE_GOAL_SUBMIT = 'device-goal-submit'; +export const DEVICE_GOAL_STOP = 'device-goal-stop'; +export const DEVICE_GOAL_COMPLETE = 'device-goal-complete'; +export const DEVICE_SESSION_EXPAND = 'device-session-expand'; + +// ─── API Keys ──────────────────────────────────────── +export const APIKEY_CREATE = 'apikey-create'; +export const APIKEY_COPY = 'apikey-copy'; +export const APIKEY_DELETE = 'apikey-delete'; + +// ─── Settings ──────────────────────────────────────── +export const SETTINGS_SAVE = 'settings-save'; + +// ─── Navigation ────────────────────────────────────── +export const NAV_SIDEBAR_CLICK = 'nav-sidebar-click'; diff --git a/web/src/lib/analytics/track.ts b/web/src/lib/analytics/track.ts new file mode 100644 index 0000000..bbc974c --- /dev/null +++ b/web/src/lib/analytics/track.ts @@ -0,0 +1,19 @@ +/** + * Thin wrapper around Umami's tracking API. + * Use for programmatic event tracking (form success callbacks, etc.). + * For click tracking on buttons/links, prefer data-umami-event attributes. + */ + +declare global { + interface Window { + umami?: { + track: (event: string, data?: Record) => void; + }; + } +} + +export function track(event: string, data?: Record) { + if (typeof window !== 'undefined' && window.umami) { + window.umami.track(event, data); + } +} diff --git a/web/src/lib/components/DeviceCard.svelte b/web/src/lib/components/DeviceCard.svelte index 4b17006..11ac554 100644 --- a/web/src/lib/components/DeviceCard.svelte +++ b/web/src/lib/components/DeviceCard.svelte @@ -1,4 +1,7 @@ - +
- - {batteryIcon(batteryLevel, isCharging)} - + {batteryLevel}% @@ -93,19 +97,7 @@
- - - +
@@ -139,12 +131,20 @@

{lastGoal.goal}

+ {lastGoal.status === 'completed' ? 'Success' : lastGoal.status === 'running' diff --git a/web/src/routes/dashboard/+layout.svelte b/web/src/routes/dashboard/+layout.svelte index a15d8f0..535e669 100644 --- a/web/src/routes/dashboard/+layout.svelte +++ b/web/src/routes/dashboard/+layout.svelte @@ -5,6 +5,7 @@ import { page } from '$app/state'; import Icon from '@iconify/svelte'; import { Toaster } from 'svelte-sonner'; + import { AUTH_SIGNOUT, NAV_SIDEBAR_CLICK } from '$lib/analytics/events'; let { children, data } = $props(); @@ -37,6 +38,8 @@ {#each navItems as item} diff --git a/web/src/routes/dashboard/+page.svelte b/web/src/routes/dashboard/+page.svelte index d175bcb..887bf16 100644 --- a/web/src/routes/dashboard/+page.svelte +++ b/web/src/routes/dashboard/+page.svelte @@ -1,5 +1,6 @@ @@ -23,6 +24,7 @@ {/each} @@ -234,11 +264,14 @@ {#if activeTab === 'overview'}
-
-

- Device Info -

-
+
+
+ +

+ Device Info +

+
+
{#if deviceData?.model}
Model
@@ -266,35 +299,51 @@ {#if battery !== null && battery >= 0}
Battery
-
- {battery}%{charging ? ' (Charging)' : ''} +
+ 50 ? 'ph:battery-high-duotone' : 'ph:battery-low-duotone'} + class="h-4 w-4" + /> + {battery}%{charging ? ' Charging' : ''}
{/if}
Last seen
- {deviceData ? relativeTime(deviceData.lastSeen) : '—'} + {deviceData ? relativeTime(deviceData.lastSeen) : '\u2014'}
-
-

- Stats -

+
+
+ +

+ Stats +

+
-
+
+
+ +

{stats?.totalSessions ?? 0}

Sessions

-
+
+
+ +

{stats?.successRate ?? 0}%

Success

-
+
+
+ +

{stats?.avgSteps ?? 0}

Avg Steps

@@ -304,22 +353,35 @@ {#if deviceData && deviceData.installedApps.length > 0} -
+
-

- Installed Apps - ({deviceData.installedApps.length}) -

- +
+ +

+ Installed Apps + ({deviceData.installedApps.length}) +

+
+
+ + +
{#each filteredApps as app (app.packageName)} -
+
{app.label} {app.packageName}
@@ -333,9 +395,12 @@ {:else if activeTab === 'sessions'} {#if sessions.length === 0} -

No sessions yet. Go to the Run tab to send a goal.

+
+ +

No sessions yet. Go to the Run tab to send a goal.

+
{:else} -
+
{#each sessions as sess (sess.id)}
{:else} {/if} @@ -430,28 +510,36 @@ {#if steps.length > 0 || runStatus !== 'idle'} -
+
-

+

+ {currentGoal ? `Goal: ${currentGoal}` : 'Current Run'}

{#if runStatus === 'running'} - + Running {:else if runStatus === 'completed'} - Completed + + + Completed + {:else if runStatus === 'failed'} - Failed + + + Failed + {/if}
{#if runError} -
+
+ {runError}
{/if} @@ -474,7 +562,10 @@ {/each}
{:else} -
Waiting for first step...
+
+ + Waiting for first step... +
{/if}
{/if} diff --git a/web/src/routes/dashboard/settings/+page.svelte b/web/src/routes/dashboard/settings/+page.svelte index d002f95..75a66f3 100644 --- a/web/src/routes/dashboard/settings/+page.svelte +++ b/web/src/routes/dashboard/settings/+page.svelte @@ -3,6 +3,8 @@ import { page } from '$app/state'; import Icon from '@iconify/svelte'; import { toast } from '$lib/toast'; + import { track } from '$lib/analytics/track'; + import { SETTINGS_SAVE } from '$lib/analytics/events'; const config = await getConfig(); const layoutData = page.data; @@ -10,6 +12,7 @@ $effect(() => { if (updateConfig.result?.saved) { toast.success('Settings saved'); + track(SETTINGS_SAVE); } }); diff --git a/web/src/routes/login/+page.svelte b/web/src/routes/login/+page.svelte index 250a1bf..1ab14e0 100644 --- a/web/src/routes/login/+page.svelte +++ b/web/src/routes/login/+page.svelte @@ -1,32 +1,61 @@ -
-

Log in

+
+
+
+
+ +
+

Log in to DroidClaw

+

Welcome back

+
-
- + + - + -

+ +

+ +

Don't have an account? - Sign up + Sign up

- - - +
diff --git a/web/src/routes/signup/+page.svelte b/web/src/routes/signup/+page.svelte index dde2edd..28bb306 100644 --- a/web/src/routes/signup/+page.svelte +++ b/web/src/routes/signup/+page.svelte @@ -1,35 +1,75 @@ -
-

Sign up

+
+
+
+
+ +
+

Create your account

+

Get started with DroidClaw

+
-
- + + - + - + - -
+ + + +

+ Already have an account? + Log in +

+