feat(analytics): Plausible-Events für Profilseite

Trackt Profilaufruf, Passkey-/PIN-Aktionen und Gerät vergessen;
Dokumentation in docs/plausible-events.md ergänzt.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-31 09:47:12 +02:00
parent d4538ec06e
commit 3698c6fbca
3 changed files with 35 additions and 1 deletions
+15
View File
@@ -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 (
+9 -1
View File
@@ -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]
+11
View File
@@ -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