소스 검색

Add interactive premium checkout flow with plan selection.

Enable selecting yearly or perpetual pricing, then continue to a credit-card form with basic validation and a success state so the paywall flow is fully functional.

Made-with: Cursor
huzaifahayat12 4 달 전
부모
커밋
b437d3566a
1개의 변경된 파일162개의 추가작업 그리고 8개의 파일을 삭제
  1. 162 8
      google_apps/PremiumFeaturesView.swift

+ 162 - 8
google_apps/PremiumFeaturesView.swift

@@ -1,7 +1,15 @@
 import SwiftUI
 
 struct PremiumFeaturesView: View {
+    enum PlanOption: String {
+        case perpetual
+        case yearly
+    }
+
     @Environment(\.dismiss) private var dismiss
+    @State private var selectedPlan: PlanOption = .perpetual
+    @State private var showingCheckout = false
+    @State private var showingSuccessAlert = false
 
     private let featureRows: [(String, String)] = [
         ("Desktop Widgets", "Add frequently used apps as desktop widgets"),
@@ -82,17 +90,25 @@ struct PremiumFeaturesView: View {
                         title: "Perpetual",
                         price: "Rs 9,900.00",
                         subtitle: "Own the software for lifetime",
-                        isPrimary: true
+                        badgeText: "One-time payment",
+                        isSelected: selectedPlan == .perpetual
                     )
+                    .onTapGesture {
+                        selectedPlan = .perpetual
+                    }
 
                     priceCard(
                         title: "Yearly",
                         price: "Rs 7,900.00/year",
                         subtitle: "Rs 658.33/mo",
-                        isPrimary: false
+                        badgeText: nil,
+                        isSelected: selectedPlan == .yearly
                     )
+                    .onTapGesture {
+                        selectedPlan = .yearly
+                    }
 
-                    Button(action: {}) {
+                    Button(action: { showingCheckout = true }) {
                         Text("Continue")
                             .font(.system(size: 16, weight: .bold))
                             .foregroundStyle(.white)
@@ -122,6 +138,22 @@ struct PremiumFeaturesView: View {
                 .padding(16)
             }
         }
+        .sheet(isPresented: $showingCheckout) {
+            PaymentCheckoutView(
+                selectedPlan: selectedPlan,
+                onCancel: { showingCheckout = false },
+                onConfirm: {
+                    showingCheckout = false
+                    showingSuccessAlert = true
+                }
+            )
+            .frame(minWidth: 500, minHeight: 520)
+        }
+        .alert("Payment Successful", isPresented: $showingSuccessAlert) {
+            Button("Done") { dismiss() }
+        } message: {
+            Text("Your \(selectedPlan == .perpetual ? "Perpetual" : "Yearly") plan has been activated.")
+        }
     }
 
     private func featureCard(title: String, subtitle: String, color: Color) -> some View {
@@ -155,7 +187,7 @@ struct PremiumFeaturesView: View {
         )
     }
 
-    private func priceCard(title: String, price: String, subtitle: String, isPrimary: Bool) -> some View {
+    private func priceCard(title: String, price: String, subtitle: String, badgeText: String?, isSelected: Bool) -> some View {
         HStack(alignment: .center) {
             VStack(alignment: .leading, spacing: 5) {
                 Text(title)
@@ -174,8 +206,8 @@ struct PremiumFeaturesView: View {
                     .foregroundStyle(.white)
                     .minimumScaleFactor(0.7)
                     .lineLimit(1)
-                if isPrimary {
-                    Text("One-time payment")
+                if let badgeText {
+                    Text(badgeText)
                         .font(.system(size: 7.5, weight: .bold))
                         .foregroundStyle(.white)
                         .padding(.horizontal, 8)
@@ -190,12 +222,134 @@ struct PremiumFeaturesView: View {
         .padding(10)
         .background(
             RoundedRectangle(cornerRadius: 14, style: .continuous)
-                .stroke(isPrimary ? Color.blue.opacity(0.95) : Color.white.opacity(0.4), lineWidth: 1.5)
+                .stroke(isSelected ? Color.blue.opacity(0.95) : Color.white.opacity(0.4), lineWidth: isSelected ? 2 : 1.5)
                 .background(
                     RoundedRectangle(cornerRadius: 14, style: .continuous)
-                        .fill(Color.white.opacity(0.05))
+                        .fill(isSelected ? Color.blue.opacity(0.12) : Color.white.opacity(0.05))
                 )
         )
+        .contentShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
+    }
+}
+
+private struct PaymentCheckoutView: View {
+    let selectedPlan: PremiumFeaturesView.PlanOption
+    let onCancel: () -> Void
+    let onConfirm: () -> Void
+
+    @State private var cardholderName = ""
+    @State private var cardNumber = ""
+    @State private var expiry = ""
+    @State private var cvv = ""
+    @State private var saveCard = true
+
+    private var amountText: String {
+        switch selectedPlan {
+        case .perpetual:
+            return "Rs 9,900.00"
+        case .yearly:
+            return "Rs 7,900.00/year"
+        }
+    }
+
+    private var canSubmit: Bool {
+        !cardholderName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
+        cardNumber.filter(\.isNumber).count >= 12 &&
+        expiry.count >= 4 &&
+        cvv.filter(\.isNumber).count >= 3
+    }
+
+    var body: some View {
+        ZStack {
+            LinearGradient(
+                colors: [Color.black.opacity(0.95), Color(red: 0.09, green: 0.11, blue: 0.16)],
+                startPoint: .topLeading,
+                endPoint: .bottomTrailing
+            )
+            .ignoresSafeArea()
+
+            VStack(alignment: .leading, spacing: 16) {
+                HStack {
+                    Text("Add Credit Card")
+                        .font(.system(size: 22, weight: .bold))
+                        .foregroundStyle(.white)
+                    Spacer()
+                    Button("Cancel", action: onCancel)
+                        .buttonStyle(.plain)
+                        .foregroundStyle(.white.opacity(0.8))
+                }
+
+                Text("Selected plan: \(selectedPlan == .perpetual ? "Perpetual" : "Yearly")")
+                    .font(.system(size: 13, weight: .medium))
+                    .foregroundStyle(.white.opacity(0.8))
+
+                VStack(spacing: 12) {
+                    checkoutField(title: "Cardholder Name", text: $cardholderName, placeholder: "Name on card")
+                    checkoutField(title: "Card Number", text: $cardNumber, placeholder: "1234 5678 9012 3456")
+
+                    HStack(spacing: 12) {
+                        checkoutField(title: "Expiry (MM/YY)", text: $expiry, placeholder: "08/28")
+                        checkoutField(title: "CVV", text: $cvv, placeholder: "123")
+                    }
+                }
+
+                Toggle("Save card for future purchases", isOn: $saveCard)
+                    .toggleStyle(.checkbox)
+                    .foregroundStyle(.white.opacity(0.88))
+
+                HStack {
+                    Text("Total")
+                        .foregroundStyle(.white.opacity(0.85))
+                    Spacer()
+                    Text(amountText)
+                        .font(.system(size: 15, weight: .bold))
+                        .foregroundStyle(.white)
+                }
+                .padding(12)
+                .background(
+                    RoundedRectangle(cornerRadius: 10, style: .continuous)
+                        .fill(Color.white.opacity(0.08))
+                )
+
+                Button(action: onConfirm) {
+                    Text("Pay \(amountText)")
+                        .font(.system(size: 15, weight: .bold))
+                        .foregroundStyle(.white)
+                        .frame(maxWidth: .infinity)
+                        .padding(.vertical, 12)
+                        .background(
+                            RoundedRectangle(cornerRadius: 12, style: .continuous)
+                                .fill(canSubmit ? Color.blue.opacity(0.95) : Color.gray.opacity(0.6))
+                        )
+                }
+                .buttonStyle(.plain)
+                .disabled(!canSubmit)
+
+                Spacer(minLength: 0)
+            }
+            .padding(20)
+        }
+    }
+
+    private func checkoutField(title: String, text: Binding<String>, placeholder: String) -> some View {
+        VStack(alignment: .leading, spacing: 6) {
+            Text(title)
+                .font(.system(size: 12, weight: .semibold))
+                .foregroundStyle(.white.opacity(0.85))
+            TextField(placeholder, text: text)
+                .textFieldStyle(.plain)
+                .foregroundStyle(.white)
+                .padding(.horizontal, 10)
+                .padding(.vertical, 9)
+                .background(
+                    RoundedRectangle(cornerRadius: 10, style: .continuous)
+                        .fill(Color.white.opacity(0.1))
+                )
+                .overlay(
+                    RoundedRectangle(cornerRadius: 10, style: .continuous)
+                        .stroke(Color.white.opacity(0.13), lineWidth: 1)
+                )
+        }
     }
 }