Files
honeyDueKMP/iosApp/iosApp/Onboarding/OnboardingSubscriptionView.swift
Trey t 0652908c20 Add iOS onboarding flow with residence creation and task templates
- 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>
2025-12-02 11:00:51 -06:00

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: {})
}