diff --git a/client/src/components/UserProfilePage.tsx b/client/src/components/UserProfilePage.tsx index c84f748..c894a82 100644 --- a/client/src/components/UserProfilePage.tsx +++ b/client/src/components/UserProfilePage.tsx @@ -50,6 +50,7 @@ import { type AccountStatsSummary } from '../services/statsAggregation.js' import { db } from '../services/db.js' +import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' interface UserProfilePageProps { onBack: () => void @@ -154,6 +155,10 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro void loadData() }, [loadData]) + useEffect(() => { + trackPlausibleEvent(PlausibleEvents.PROFILE_OPENED) + }, []) + useEffect(() => { const handleOnline = () => setOnline(true) const handleOffline = () => setOnline(false) @@ -198,9 +203,11 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro setPasskeyBusy(true) setError(null) try { + const hadLabel = Boolean(newPasskeyLabel.trim()) await addPasskey(newPasskeyLabel) setNewPasskeyLabel('') await loadData() + trackPlausibleEvent(PlausibleEvents.PASSKEY_ADDED, { labeled: hadLabel }) showAlert(t('profile.add_passkey_success')) } catch (err: unknown) { setError(err instanceof Error ? err.message : t('profile.add_passkey_failed')) @@ -215,6 +222,7 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro try { await renamePasskey(credentialId, passkeyLabels[credentialId] ?? '') await loadData() + trackPlausibleEvent(PlausibleEvents.PASSKEY_RENAMED) showAlert(t('profile.passkey_rename_success')) } catch (err: unknown) { setError(err instanceof Error ? err.message : t('profile.add_passkey_failed')) @@ -234,10 +242,12 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro forgetUsername(username) setIsKnownDevice(false) + trackPlausibleEvent(PlausibleEvents.DEVICE_FORGOTTEN) } const handleRemovePasskey = async (credentialId: string) => { if (profile && profile.credentials.length <= 1) { + trackPlausibleEvent(PlausibleEvents.LAST_PASSKEY_REMOVE_HINTED) await showAlert( t('profile.remove_passkey_last_desc'), t('profile.remove_passkey_last_title') @@ -258,6 +268,7 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro try { await removePasskey(credentialId) await loadData() + trackPlausibleEvent(PlausibleEvents.PASSKEY_REMOVED) } catch (err: unknown) { setError(err instanceof Error ? err.message : t('profile.remove_passkey_failed')) } finally { @@ -282,6 +293,8 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro return } + const pinAction = pinActive ? 'change' : 'set' + setPinBusy(true) setError(null) try { @@ -289,6 +302,7 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro setPinActive(true) setPinInput('') setPinConfirm('') + trackPlausibleEvent(PlausibleEvents.LOCAL_PIN_SET, { action: pinAction }) showAlert(t('profile.pin_saved')) } catch (err: unknown) { setError(err instanceof Error ? err.message : t('profile.pin_save_failed')) @@ -310,6 +324,7 @@ export default function UserProfilePage({ onBack, onLogout }: UserProfilePagePro setPinActive(false) setPinInput('') setPinConfirm('') + trackPlausibleEvent(PlausibleEvents.LOCAL_PIN_REMOVED) } return ( diff --git a/client/src/services/analytics.ts b/client/src/services/analytics.ts index 105e9ee..f384d0a 100644 --- a/client/src/services/analytics.ts +++ b/client/src/services/analytics.ts @@ -25,7 +25,15 @@ export const PlausibleEvents = { DEMO_OPENED: 'Demo Opened', PUSH_ENABLED: 'Push Enabled', PUSH_DISABLED: 'Push Disabled', - FOOTER_LINK_CLICKED: 'Footer Link Clicked' + FOOTER_LINK_CLICKED: 'Footer Link Clicked', + PROFILE_OPENED: 'Profile Opened', + PASSKEY_ADDED: 'Passkey Added', + PASSKEY_REMOVED: 'Passkey Removed', + PASSKEY_RENAMED: 'Passkey Renamed', + LAST_PASSKEY_REMOVE_HINTED: 'Last Passkey Remove Hinted', + LOCAL_PIN_SET: 'Local PIN Set', + LOCAL_PIN_REMOVED: 'Local PIN Removed', + DEVICE_FORGOTTEN: 'Device Forgotten' } as const export type PlausibleEventName = (typeof PlausibleEvents)[keyof typeof PlausibleEvents] diff --git a/docs/plausible-events.md b/docs/plausible-events.md index 9322e90..edb5a57 100644 --- a/docs/plausible-events.md +++ b/docs/plausible-events.md @@ -40,12 +40,22 @@ Kapteins Daagbok nutzt [Plausible Analytics](https://plausible.io/) mit dem Scri | Push Enabled | Crew-Änderungs-Push aktiviert (`PushNotificationSettings.tsx`) | — | | Push Disabled | Crew-Änderungs-Push deaktiviert (`PushNotificationSettings.tsx`) | — | | Footer Link Clicked | Klick auf Autoren-Link im App-Footer (`AppFooter.tsx`) | — | +| Profile Opened | Profilseite geöffnet (`UserProfilePage.tsx`, einmal pro Mount) | — | +| Passkey Added | Passkey erfolgreich registriert (`UserProfilePage.tsx`) | `labeled`: `true` \| `false` (optionaler Name gesetzt) | +| Passkey Removed | Passkey entfernt, mindestens ein Key verbleibt (`UserProfilePage.tsx`) | — | +| Passkey Renamed | Passkey-Name gespeichert (`UserProfilePage.tsx`) | — | +| Last Passkey Remove Hinted | Löschen des einzigen Passkeys abgebrochen — Hinweisdialog zur Kontolöschung (`UserProfilePage.tsx`) | — | +| Local PIN Set | Lokaler PIN gesetzt oder geändert (`UserProfilePage.tsx`) | `action`: `set` \| `change` | +| Local PIN Removed | Lokaler PIN entfernt (`UserProfilePage.tsx`) | — | +| Device Forgotten | Account aus Schnell-Login-Liste dieses Geräts entfernt (`UserProfilePage.tsx`) | — | ## Bewusst nicht getrackt - **Demo-Logbuch:** Beim automatischen Seed (`demoLogbook.ts`) werden keine Events ausgelöst — nur echte Nutzeraktionen zählen. - **Manuelle Signaturen:** Nur Passkey-Signaturen lösen `Entry Signed` aus. - **PII:** Keine Inhalte aus verschlüsselten Logbüchern in Properties. +- **Profil-KPIs:** Statistik-Karten und User-ID-Kopieren werden nicht getrackt (reine Anzeige bzw. zu granular). +- **Kontolöschung:** `Account Deleted` bleibt in `auth.ts` — unabhängig davon, ob die Gefahrenzone auf der Profilseite oder früher in den Einstellungen genutzt wurde. ## Typische Funnels (Plausible Goals) @@ -57,6 +67,7 @@ Empfohlene Goal-Ketten für Auswertung: 4. **Öffentliche Freigabe:** Logbook Shared → Public Link Opened 5. **Export:** Travel Day Saved → PDF Exported / CSV Exported 6. **Datensicherung:** Backup Exported → Backup Restored +7. **Kontosicherheit:** Profile Opened → Passkey Added / Local PIN Set; Last Passkey Remove Hinted → Account Deleted (selten, aber aussagekräftig) ## Entwicklung