import SwiftUI import ComposeApp import StoreKit struct FeatureComparisonView: View { @Binding var isPresented: Bool @StateObject private var subscriptionCache = SubscriptionCacheWrapper.shared @StateObject private var storeKit = StoreKitManager.shared @State private var showUpgradePrompt = false @State private var selectedProduct: Product? @State private var isProcessing = false @State private var errorMessage: String? @State private var showSuccessAlert = false var body: some View { NavigationStack { ScrollView { VStack(spacing: AppSpacing.xl) { // Header VStack(spacing: AppSpacing.sm) { Text("Choose Your Plan") .font(.title.weight(.bold)) .foregroundColor(Color.appTextPrimary) Text("Upgrade to Pro for unlimited access") .font(.subheadline) .foregroundColor(Color.appTextSecondary) } .padding(.top, AppSpacing.lg) // Feature Comparison Table VStack(spacing: 0) { // Header Row HStack { Text("Feature") .font(.headline) .foregroundColor(Color.appTextPrimary) .frame(maxWidth: .infinity, alignment: .leading) Text("Free") .font(.headline) .foregroundColor(Color.appTextSecondary) .frame(width: 80) Text("Pro") .font(.headline) .foregroundColor(Color.appPrimary) .frame(width: 80) } .padding() .background(Color.appBackgroundSecondary) Divider() // Feature Rows ForEach(subscriptionCache.featureBenefits, id: \.featureName) { benefit in ComparisonRow( featureName: benefit.featureName, freeText: benefit.freeTierText, proText: benefit.proTierText ) Divider() } // Default features if no data loaded if subscriptionCache.featureBenefits.isEmpty { ComparisonRow(featureName: "Properties", freeText: "1 property", proText: "Unlimited") Divider() ComparisonRow(featureName: "Tasks", freeText: "10 tasks", proText: "Unlimited") Divider() ComparisonRow(featureName: "Contractors", freeText: "Not available", proText: "Unlimited") Divider() ComparisonRow(featureName: "Documents", freeText: "Not available", proText: "Unlimited") } } .background(Color.appBackgroundSecondary) .cornerRadius(AppRadius.lg) .padding(.horizontal) // Subscription Products if storeKit.isLoading { ProgressView() .tint(Color.appPrimary) .padding() } else if !storeKit.products.isEmpty { VStack(spacing: AppSpacing.md) { ForEach(storeKit.products, id: \.id) { product in SubscriptionButton( product: product, isSelected: selectedProduct?.id == product.id, isProcessing: isProcessing, onSelect: { selectedProduct = product handlePurchase(product) } ) } } .padding(.horizontal) } else { // Fallback if products fail to load Button(action: { Task { await storeKit.loadProducts() } }) { Text("Retry Loading Products") .fontWeight(.semibold) .frame(maxWidth: .infinity) .foregroundColor(Color.appTextOnPrimary) .padding() .background(Color.appPrimary) .cornerRadius(AppRadius.md) } .padding(.horizontal) } // Error Message if let error = errorMessage { HStack { Image(systemName: "exclamationmark.triangle.fill") .foregroundColor(Color.appError) Text(error) .font(.subheadline) .foregroundColor(Color.appError) } .padding() .background(Color.appError.opacity(0.1)) .cornerRadius(AppRadius.md) .padding(.horizontal) } // Restore Purchases Button(action: { handleRestore() }) { Text("Restore Purchases") .font(.caption) .foregroundColor(Color.appTextSecondary) } .padding(.bottom, AppSpacing.xl) } } .background(WarmGradientBackground()) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Close") { isPresented = false } } } .alert("Subscription Active", isPresented: $showSuccessAlert) { Button("Done") { isPresented = false } } message: { Text("You now have full access to all Pro features!") } .task { await storeKit.loadProducts() } } } // MARK: - Purchase Handling private func handlePurchase(_ product: Product) { isProcessing = true errorMessage = nil Task { do { let transaction = try await storeKit.purchase(product) await MainActor.run { isProcessing = false if transaction != nil { showSuccessAlert = true } } } catch { await MainActor.run { isProcessing = false errorMessage = "Purchase failed: \(error.localizedDescription)" } } } } private func handleRestore() { isProcessing = true errorMessage = nil Task { await storeKit.restorePurchases() await MainActor.run { isProcessing = false if !storeKit.purchasedProductIDs.isEmpty { showSuccessAlert = true } else { errorMessage = "No purchases found to restore" } } } } } // MARK: - Subscription Button struct SubscriptionButton: View { let product: Product let isSelected: Bool let isProcessing: Bool let onSelect: () -> Void var isAnnual: Bool { product.id.contains("annual") } var savingsText: String? { if isAnnual { return "Save 17%" } return nil } var body: some View { Button(action: onSelect) { HStack { VStack(alignment: .leading, spacing: 4) { Text(product.displayName) .font(.headline) .foregroundColor(Color.appTextPrimary) if let savings = savingsText { Text(savings) .font(.caption) .foregroundColor(Color.appPrimary) } } Spacer() if isProcessing && isSelected { ProgressView() .tint(Color.appTextOnPrimary) } else { Text(product.displayPrice) .font(.title3.weight(.bold)) .foregroundColor(Color.appTextOnPrimary) } } .padding() .frame(maxWidth: .infinity) .background(isAnnual ? Color.appPrimary : Color.appSecondary) .cornerRadius(AppRadius.md) .overlay( RoundedRectangle(cornerRadius: AppRadius.md) .stroke(isAnnual ? Color.appAccent : Color.clear, lineWidth: 2) ) } .disabled(isProcessing) } } struct ComparisonRow: View { let featureName: String let freeText: String let proText: String var body: some View { HStack { Text(featureName) .font(.body) .foregroundColor(Color.appTextPrimary) .frame(maxWidth: .infinity, alignment: .leading) Text(freeText) .font(.subheadline) .foregroundColor(Color.appTextSecondary) .frame(width: 80) .multilineTextAlignment(.center) Text(proText) .font(.subheadline.weight(.medium)) .foregroundColor(Color.appPrimary) .frame(width: 80) .multilineTextAlignment(.center) } .padding() } } #Preview { FeatureComparisonView(isPresented: .constant(true)) }