- Add complete onboarding flow with 7 screens: Welcome, Name Residence, Value Props, Create Account, Verify Email, First Task, Subscription - Auto-create residence after email verification for "Start Fresh" users - Add predefined task templates (HVAC, Smoke Detectors, Lawn Care, Leaks) that create real tasks with today as due date - Add returning user login button on welcome screen - Update RootView to prioritize onboarding flow for first-time users - Use app icon asset instead of house.fill SF Symbol - Smooth slide transitions with fade-out for back navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
237 lines
8.2 KiB
Swift
237 lines
8.2 KiB
Swift
import SwiftUI
|
|
import StoreKit
|
|
|
|
/// Screen 7: Subscription upsell - Content only (no navigation bar)
|
|
struct OnboardingSubscriptionContent: View {
|
|
var onSubscribe: () -> Void
|
|
|
|
@State private var isLoading = false
|
|
|
|
private let benefits: [SubscriptionBenefit] = [
|
|
SubscriptionBenefit(
|
|
icon: "building.2.fill",
|
|
title: "Unlimited Properties",
|
|
description: "Manage multiple homes, rentals, or vacation properties"
|
|
),
|
|
SubscriptionBenefit(
|
|
icon: "checklist",
|
|
title: "Unlimited Tasks",
|
|
description: "Track as many maintenance items as you need"
|
|
),
|
|
SubscriptionBenefit(
|
|
icon: "doc.fill",
|
|
title: "Warranty Storage",
|
|
description: "Keep all your warranty documents in one place"
|
|
),
|
|
SubscriptionBenefit(
|
|
icon: "person.2.fill",
|
|
title: "Household Sharing",
|
|
description: "Invite family members to collaborate on tasks"
|
|
)
|
|
]
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: AppSpacing.xl) {
|
|
// Header
|
|
VStack(spacing: AppSpacing.md) {
|
|
// Pro badge
|
|
HStack(spacing: AppSpacing.xs) {
|
|
Image(systemName: "star.fill")
|
|
.foregroundColor(Color.appAccent)
|
|
Text("PRO")
|
|
.font(.headline)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(Color.appAccent)
|
|
}
|
|
.padding(.horizontal, AppSpacing.md)
|
|
.padding(.vertical, AppSpacing.xs)
|
|
.background(Color.appAccent.opacity(0.15))
|
|
.clipShape(Capsule())
|
|
|
|
Text("Unlock the full power of Casera")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(Color.appTextPrimary)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Text("Get more done with Pro features")
|
|
.font(.subheadline)
|
|
.foregroundColor(Color.appTextSecondary)
|
|
}
|
|
.padding(.top, AppSpacing.xxl)
|
|
|
|
// Benefits list
|
|
VStack(spacing: AppSpacing.md) {
|
|
ForEach(benefits) { benefit in
|
|
SubscriptionBenefitRow(benefit: benefit)
|
|
}
|
|
}
|
|
.padding(.horizontal, AppSpacing.lg)
|
|
|
|
// Pricing card
|
|
VStack(spacing: AppSpacing.md) {
|
|
HStack {
|
|
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
|
|
Text("Monthly")
|
|
.font(.headline)
|
|
.foregroundColor(Color.appTextPrimary)
|
|
Text("Cancel anytime")
|
|
.font(.caption)
|
|
.foregroundColor(Color.appTextSecondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
VStack(alignment: .trailing, spacing: AppSpacing.xxs) {
|
|
Text("$4.99")
|
|
.font(.title2)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(Color.appPrimary)
|
|
Text("/month")
|
|
.font(.caption)
|
|
.foregroundColor(Color.appTextSecondary)
|
|
}
|
|
}
|
|
.padding(AppSpacing.lg)
|
|
.background(Color.appBackgroundSecondary)
|
|
.cornerRadius(AppRadius.lg)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: AppRadius.lg)
|
|
.stroke(Color.appPrimary, lineWidth: 2)
|
|
)
|
|
}
|
|
.padding(.horizontal, AppSpacing.lg)
|
|
|
|
// CTA buttons
|
|
VStack(spacing: AppSpacing.md) {
|
|
Button(action: startFreeTrial) {
|
|
HStack {
|
|
if isLoading {
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
|
}
|
|
Text("Start Free Trial")
|
|
.font(.headline)
|
|
.fontWeight(.semibold)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 56)
|
|
.foregroundColor(Color.appTextOnPrimary)
|
|
.background(
|
|
LinearGradient(
|
|
colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
)
|
|
.cornerRadius(AppRadius.md)
|
|
.shadow(color: Color.appPrimary.opacity(0.3), radius: 10, y: 5)
|
|
}
|
|
.disabled(isLoading)
|
|
|
|
// Legal text
|
|
Text("7-day free trial, then $4.99/month. Cancel anytime in Settings.")
|
|
.font(.caption)
|
|
.foregroundColor(Color.appTextSecondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.top, AppSpacing.xs)
|
|
}
|
|
.padding(.horizontal, AppSpacing.xl)
|
|
.padding(.bottom, AppSpacing.xxxl)
|
|
}
|
|
}
|
|
.background(Color.appBackgroundPrimary)
|
|
}
|
|
|
|
private func startFreeTrial() {
|
|
isLoading = true
|
|
|
|
// Initiate StoreKit purchase flow
|
|
Task {
|
|
do {
|
|
// This would integrate with your StoreKitManager
|
|
// For now, we'll simulate the flow
|
|
try await Task.sleep(nanoseconds: 1_500_000_000)
|
|
|
|
await MainActor.run {
|
|
isLoading = false
|
|
onSubscribe()
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
isLoading = false
|
|
// Handle error - still proceed (they can subscribe later)
|
|
onSubscribe()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Legacy wrapper with navigation bar (for backwards compatibility)
|
|
|
|
struct OnboardingSubscriptionView: View {
|
|
var onSubscribe: () -> Void
|
|
var onSkip: () -> Void
|
|
|
|
var body: some View {
|
|
OnboardingSubscriptionContent(onSubscribe: onSubscribe)
|
|
}
|
|
}
|
|
|
|
// MARK: - Subscription Benefit Model
|
|
|
|
struct SubscriptionBenefit: Identifiable {
|
|
let id = UUID()
|
|
let icon: String
|
|
let title: String
|
|
let description: String
|
|
}
|
|
|
|
// MARK: - Subscription Benefit Row
|
|
|
|
struct SubscriptionBenefitRow: View {
|
|
let benefit: SubscriptionBenefit
|
|
|
|
var body: some View {
|
|
HStack(spacing: AppSpacing.md) {
|
|
ZStack {
|
|
Circle()
|
|
.fill(Color.appPrimary.opacity(0.1))
|
|
.frame(width: 44, height: 44)
|
|
|
|
Image(systemName: benefit.icon)
|
|
.font(.title3)
|
|
.foregroundColor(Color.appPrimary)
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: AppSpacing.xxs) {
|
|
Text(benefit.title)
|
|
.font(.headline)
|
|
.foregroundColor(Color.appTextPrimary)
|
|
|
|
Text(benefit.description)
|
|
.font(.subheadline)
|
|
.foregroundColor(Color.appTextSecondary)
|
|
.lineLimit(2)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.font(.title2)
|
|
.foregroundColor(Color.appPrimary)
|
|
}
|
|
.padding(AppSpacing.md)
|
|
.background(Color.appBackgroundSecondary)
|
|
.cornerRadius(AppRadius.lg)
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
OnboardingSubscriptionContent(onSubscribe: {})
|
|
}
|