feat: add PostHog analytics with full event tracking across app
Integrate self-hosted PostHog (SPM) with AnalyticsManager singleton wrapping all SDK calls. Adds ~40 type-safe events covering trip planning, schedule, progress, IAP, settings, polls, export, and share flows. Includes session replay, autocapture, network telemetry, privacy opt-out toggle in Settings, and super properties (app version, device, pro status, selected sports). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -116,6 +116,7 @@ struct TripDetailView: View {
|
||||
Text("This trip has \(multiRouteChunks.flatMap { $0 }.count) stops, which exceeds Apple Maps' limit of 16. Open the route in parts?")
|
||||
}
|
||||
.onAppear {
|
||||
AnalyticsManager.shared.track(.tripViewed(tripId: trip.id.uuidString, source: allowCustomItems ? "saved" : "new"))
|
||||
checkIfSaved()
|
||||
// Demo mode: auto-favorite the trip
|
||||
if isDemoMode && !hasAppliedDemoSelection && !isSaved {
|
||||
@@ -1203,6 +1204,7 @@ struct TripDetailView: View {
|
||||
private func exportPDF() async {
|
||||
isExporting = true
|
||||
exportProgress = nil
|
||||
AnalyticsManager.shared.track(.pdfExportStarted(tripId: trip.id.uuidString, stopCount: trip.stops.count))
|
||||
|
||||
do {
|
||||
// Build complete itinerary items (games + travel + custom)
|
||||
@@ -1219,8 +1221,9 @@ struct TripDetailView: View {
|
||||
}
|
||||
exportURL = url
|
||||
showExportSheet = true
|
||||
AnalyticsManager.shared.track(.pdfExportCompleted(tripId: trip.id.uuidString))
|
||||
} catch {
|
||||
// PDF export failed silently
|
||||
AnalyticsManager.shared.track(.pdfExportFailed(tripId: trip.id.uuidString, error: error.localizedDescription))
|
||||
}
|
||||
|
||||
isExporting = false
|
||||
@@ -1323,6 +1326,11 @@ struct TripDetailView: View {
|
||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
|
||||
isSaved = true
|
||||
}
|
||||
AnalyticsManager.shared.track(.tripSaved(
|
||||
tripId: trip.id.uuidString,
|
||||
stopCount: trip.stops.count,
|
||||
gameCount: trip.totalGames
|
||||
))
|
||||
} catch {
|
||||
// Save failed silently
|
||||
}
|
||||
@@ -1343,6 +1351,7 @@ struct TripDetailView: View {
|
||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
|
||||
isSaved = false
|
||||
}
|
||||
AnalyticsManager.shared.track(.tripDeleted(tripId: tripId.uuidString))
|
||||
} catch {
|
||||
// Unsave failed silently
|
||||
}
|
||||
@@ -2047,7 +2056,7 @@ private struct SheetModifiers: ViewModifier {
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showProPaywall) {
|
||||
PaywallView()
|
||||
PaywallView(source: "trip_detail")
|
||||
}
|
||||
.sheet(item: $addItemAnchor) { anchor in
|
||||
QuickAddItemSheet(
|
||||
|
||||
@@ -165,6 +165,8 @@ struct TripWizardView: View {
|
||||
// MARK: - Planning
|
||||
|
||||
private func planTrip() async {
|
||||
let mode = viewModel.planningMode?.rawValue ?? "unknown"
|
||||
AnalyticsManager.shared.track(.tripWizardStarted(mode: mode))
|
||||
viewModel.isPlanning = true
|
||||
defer { viewModel.isPlanning = false }
|
||||
|
||||
@@ -242,18 +244,29 @@ struct TripWizardView: View {
|
||||
if options.isEmpty {
|
||||
planningError = "No valid trip options found for your criteria. Try expanding your date range or regions."
|
||||
showError = true
|
||||
AnalyticsManager.shared.track(.tripPlanFailed(mode: mode, error: "no_options_found"))
|
||||
} else {
|
||||
tripOptions = options
|
||||
gamesForDisplay = richGamesDict
|
||||
showTripOptions = true
|
||||
if let first = options.first {
|
||||
AnalyticsManager.shared.track(.tripPlanned(
|
||||
sportCount: viewModel.selectedSports.count,
|
||||
stopCount: first.stops.count,
|
||||
dayCount: first.stops.count,
|
||||
mode: mode
|
||||
))
|
||||
}
|
||||
}
|
||||
case .failure(let failure):
|
||||
planningError = failure.message
|
||||
showError = true
|
||||
AnalyticsManager.shared.track(.tripPlanFailed(mode: mode, error: failure.message))
|
||||
}
|
||||
} catch {
|
||||
planningError = error.localizedDescription
|
||||
showError = true
|
||||
AnalyticsManager.shared.track(.tripPlanFailed(mode: mode, error: error.localizedDescription))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user