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:
@@ -477,6 +477,9 @@ struct OnboardingPaywallView: View {
|
||||
.padding(.bottom, Theme.Spacing.xl)
|
||||
}
|
||||
.background(Theme.backgroundGradient(colorScheme))
|
||||
.onAppear {
|
||||
AnalyticsManager.shared.track(.onboardingPaywallViewed)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Feature Page
|
||||
@@ -556,7 +559,7 @@ struct OnboardingPaywallView: View {
|
||||
// MARK: - Pricing Page
|
||||
|
||||
private var pricingPage: some View {
|
||||
PaywallView()
|
||||
PaywallView(source: "onboarding")
|
||||
.storeButton(.hidden, for: .cancellation)
|
||||
}
|
||||
|
||||
@@ -584,6 +587,7 @@ struct OnboardingPaywallView: View {
|
||||
// Continue free (always visible)
|
||||
Button {
|
||||
markOnboardingSeen()
|
||||
AnalyticsManager.shared.track(.onboardingPaywallDismissed)
|
||||
isPresented = false
|
||||
} label: {
|
||||
Text("Continue with Free")
|
||||
|
||||
@@ -13,6 +13,11 @@ struct PaywallView: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
private let storeManager = StoreManager.shared
|
||||
let source: String
|
||||
|
||||
init(source: String = "unknown") {
|
||||
self.source = source
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
SubscriptionStoreView(subscriptions: storeManager.products.filter { $0.subscription != nil }) {
|
||||
@@ -41,14 +46,34 @@ struct PaywallView: View {
|
||||
.storeButton(.visible, for: .restorePurchases)
|
||||
.subscriptionStoreControlStyle(.prominentPicker)
|
||||
.subscriptionStoreButtonLabel(.displayName.multiline)
|
||||
.onInAppPurchaseCompletion { _, result in
|
||||
if case .success(.success) = result {
|
||||
.onInAppPurchaseStart { product in
|
||||
AnalyticsManager.shared.trackPurchaseStarted(productId: product.id, source: source)
|
||||
}
|
||||
.onInAppPurchaseCompletion { product, result in
|
||||
switch result {
|
||||
case .success(.success(_)):
|
||||
AnalyticsManager.shared.trackPurchaseCompleted(productId: product.id, source: source)
|
||||
Task { @MainActor in
|
||||
await storeManager.updateEntitlements()
|
||||
storeManager.trackSubscriptionAnalytics(source: "purchase_success")
|
||||
}
|
||||
dismiss()
|
||||
case .success(.userCancelled):
|
||||
AnalyticsManager.shared.trackPurchaseFailed(productId: product.id, source: source, error: "user_cancelled")
|
||||
case .success(.pending):
|
||||
AnalyticsManager.shared.trackPurchaseFailed(productId: product.id, source: source, error: "pending")
|
||||
case .failure(let error):
|
||||
AnalyticsManager.shared.trackPurchaseFailed(productId: product.id, source: source, error: error.localizedDescription)
|
||||
@unknown default:
|
||||
AnalyticsManager.shared.trackPurchaseFailed(productId: product.id, source: source, error: "unknown_result")
|
||||
}
|
||||
}
|
||||
.task {
|
||||
await storeManager.loadProducts()
|
||||
}
|
||||
.onAppear {
|
||||
AnalyticsManager.shared.trackPaywallViewed(source: source)
|
||||
}
|
||||
}
|
||||
|
||||
private func featurePill(icon: String, text: String) -> some View {
|
||||
|
||||
Reference in New Issue
Block a user