import SwiftUI struct PaywallView: View { @Environment(\.dismiss) private var dismiss @EnvironmentObject var iapService: IAPService @State private var isPurchasing = false @State private var errorMessage: String? @State private var showingError = false var body: some View { NavigationStack { ScrollView { VStack(spacing: 24) { // Header headerSection // Features List featuresSection // Price Card priceCard // Purchase Button purchaseButton // Restore Button restoreButton // Legal legalSection } .padding() } .navigationTitle("Upgrade to Premium") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button { dismiss() } label: { Image(systemName: "xmark") .foregroundColor(.secondary) } } } .alert("Error", isPresented: $showingError) { Button("OK", role: .cancel) {} } message: { Text(errorMessage ?? "An error occurred") } .onChange(of: iapService.isPremium) { _, isPremium in if isPremium { dismiss() } } } } // MARK: - Header Section private var headerSection: some View { VStack(spacing: 16) { // Crown icon ZStack { Circle() .fill( LinearGradient( colors: [Color.yellow.opacity(0.3), Color.orange.opacity(0.3)], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(width: 80, height: 80) Image(systemName: "crown.fill") .font(.system(size: 36)) .foregroundStyle( LinearGradient( colors: [.yellow, .orange], startPoint: .topLeading, endPoint: .bottomTrailing ) ) } Text("Unlock Full Potential") .font(.title.weight(.bold)) Text("Get unlimited access to all features with a one-time purchase") .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) } } // MARK: - Features Section private var featuresSection: some View { VStack(spacing: 12) { ForEach(IAPService.premiumFeatures, id: \.title) { feature in FeatureRow( icon: feature.icon, title: feature.title, description: feature.description ) } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } // MARK: - Price Card private var priceCard: some View { VStack(spacing: 8) { HStack(alignment: .top, spacing: 4) { Text("€") .font(.title2.weight(.semibold)) .foregroundColor(.appPrimary) Text("4.69") .font(.system(size: 48, weight: .bold, design: .rounded)) .foregroundColor(.appPrimary) } Text("One-time purchase") .font(.subheadline) .foregroundColor(.secondary) HStack(spacing: 4) { Image(systemName: "person.2.fill") .font(.caption) Text("Includes Family Sharing") .font(.caption) } .foregroundColor(.appSecondary) .padding(.top, 4) } .frame(maxWidth: .infinity) .padding(.vertical, 24) .background( RoundedRectangle(cornerRadius: AppConstants.UI.cornerRadius) .fill(Color.appPrimary.opacity(0.1)) .overlay( RoundedRectangle(cornerRadius: AppConstants.UI.cornerRadius) .stroke(Color.appPrimary, lineWidth: 2) ) ) } // MARK: - Purchase Button private var purchaseButton: some View { Button { purchase() } label: { HStack { if isPurchasing { ProgressView() .tint(.white) } else { Text("Upgrade Now") .font(.headline) } } .frame(maxWidth: .infinity) .padding() .background(Color.appPrimary) .foregroundColor(.white) .cornerRadius(AppConstants.UI.cornerRadius) } .disabled(isPurchasing) } // MARK: - Restore Button private var restoreButton: some View { Button { restore() } label: { Text("Restore Purchases") .font(.subheadline) .foregroundColor(.appPrimary) } .disabled(isPurchasing) } // MARK: - Legal Section private var legalSection: some View { VStack(spacing: 8) { Text("Payment will be charged to your Apple ID account. By purchasing, you agree to our Terms of Service and Privacy Policy.") .font(.caption2) .foregroundColor(.secondary) .multilineTextAlignment(.center) HStack(spacing: 16) { Link("Terms", destination: URL(string: AppConstants.URLs.termsOfService)!) .font(.caption) Link("Privacy", destination: URL(string: AppConstants.URLs.privacyPolicy)!) .font(.caption) } } .padding(.top, 8) } // MARK: - Actions private func purchase() { isPurchasing = true FirebaseService.shared.logPurchaseAttempt(productId: IAPService.premiumProductID) Task { do { try await iapService.purchase() } catch { errorMessage = error.localizedDescription showingError = true } isPurchasing = false } } private func restore() { isPurchasing = true Task { await iapService.restorePurchases() if !iapService.isPremium { errorMessage = "No purchases found to restore" showingError = true } isPurchasing = false } } } // MARK: - Feature Row struct FeatureRow: View { let icon: String let title: String let description: String var body: some View { HStack(spacing: 16) { ZStack { Circle() .fill(Color.appPrimary.opacity(0.1)) .frame(width: 44, height: 44) Image(systemName: icon) .font(.system(size: 18)) .foregroundColor(.appPrimary) } VStack(alignment: .leading, spacing: 2) { Text(title) .font(.subheadline.weight(.semibold)) Text(description) .font(.caption) .foregroundColor(.secondary) } Spacer() Image(systemName: "checkmark.circle.fill") .foregroundColor(.positiveGreen) } } } // MARK: - Compact Paywall (for inline use) struct CompactPaywallBanner: View { @Binding var showingPaywall: Bool var body: some View { HStack(spacing: 12) { Image(systemName: "crown.fill") .font(.title2) .foregroundStyle( LinearGradient( colors: [.yellow, .orange], startPoint: .topLeading, endPoint: .bottomTrailing ) ) VStack(alignment: .leading, spacing: 2) { Text("Unlock Premium") .font(.subheadline.weight(.semibold)) Text("Get unlimited access to all features") .font(.caption) .foregroundColor(.secondary) } Spacer() Button { showingPaywall = true } label: { Text("€4.69") .font(.caption.weight(.semibold)) .padding(.horizontal, 12) .padding(.vertical, 6) .background(Color.appPrimary) .foregroundColor(.white) .cornerRadius(16) } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } } // MARK: - Premium Lock Overlay struct PremiumLockOverlay: View { let feature: String @Binding var showingPaywall: Bool var body: some View { VStack(spacing: 16) { Image(systemName: "lock.fill") .font(.system(size: 32)) .foregroundColor(.appWarning) Text("Premium Feature") .font(.headline) Text(feature) .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) Button { showingPaywall = true } label: { Label("Unlock", systemImage: "crown.fill") .font(.subheadline.weight(.semibold)) .padding(.horizontal, 20) .padding(.vertical, 10) .background(Color.appPrimary) .foregroundColor(.white) .cornerRadius(20) } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.systemBackground).opacity(0.95)) } } #Preview { PaywallView() .environmentObject(IAPService()) }