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 +

+