Remove ZIP code step from onboarding, use home profile instead
ZIP code was US-only and redundant now that the suggestion engine uses home profile features (heating, pool, etc.) for personalization. Onboarding flow: Welcome → Value Props → Name → Account → Verify → Home Profile → Task Selection (was: ...Verify → ZIP → Home Profile...) Removed regionalTemplates references from task selection view. Both iOS and Compose flows updated.
This commit is contained in:
@@ -64,8 +64,8 @@ struct OnboardingCoordinator: View {
|
||||
return
|
||||
}
|
||||
|
||||
let postalCode = onboardingState.pendingPostalCode.isEmpty ? nil : onboardingState.pendingPostalCode
|
||||
print("🏠 ONBOARDING: Creating residence with name: \(onboardingState.pendingResidenceName), zip: \(postalCode ?? "none")")
|
||||
let postalCode: String? = nil
|
||||
print("🏠 ONBOARDING: Creating residence with name: \(onboardingState.pendingResidenceName)")
|
||||
|
||||
isCreatingResidence = true
|
||||
|
||||
@@ -126,7 +126,7 @@ struct OnboardingCoordinator: View {
|
||||
}
|
||||
|
||||
/// Current step index for progress indicator (0-based)
|
||||
/// Flow: Welcome → Features → Name → Account → Verify → Location → Home Profile → Tasks → Upsell
|
||||
/// Flow: Welcome → Features → Name → Account → Verify → Home Profile → Tasks → Upsell
|
||||
private var currentProgressStep: Int {
|
||||
switch onboardingState.currentStep {
|
||||
case .welcome: return 0
|
||||
@@ -155,7 +155,7 @@ struct OnboardingCoordinator: View {
|
||||
/// Whether to show the skip button
|
||||
private var showSkipButton: Bool {
|
||||
switch onboardingState.currentStep {
|
||||
case .valueProps, .joinResidence, .residenceLocation, .homeProfile, .firstTask, .subscriptionUpsell:
|
||||
case .valueProps, .joinResidence, .homeProfile, .firstTask, .subscriptionUpsell:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -197,9 +197,6 @@ struct OnboardingCoordinator: View {
|
||||
switch onboardingState.currentStep {
|
||||
case .valueProps:
|
||||
goForward()
|
||||
case .residenceLocation:
|
||||
// Skipping location — go to home profile
|
||||
goForward(to: .homeProfile)
|
||||
case .homeProfile:
|
||||
// Skipping home profile — create residence without profile data, go to tasks
|
||||
createResidenceIfNeeded(thenNavigateTo: .firstTask)
|
||||
@@ -304,7 +301,7 @@ struct OnboardingCoordinator: View {
|
||||
if onboardingState.userIntent == .joinExisting {
|
||||
goForward(to: .joinResidence)
|
||||
} else {
|
||||
goForward(to: .residenceLocation)
|
||||
goForward(to: .homeProfile)
|
||||
}
|
||||
} else {
|
||||
goForward()
|
||||
@@ -320,7 +317,7 @@ struct OnboardingCoordinator: View {
|
||||
if onboardingState.userIntent == .joinExisting {
|
||||
goForward(to: .joinResidence)
|
||||
} else {
|
||||
goForward(to: .residenceLocation)
|
||||
goForward(to: .homeProfile)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -336,18 +333,10 @@ struct OnboardingCoordinator: View {
|
||||
.transition(navigationTransition)
|
||||
|
||||
case .residenceLocation:
|
||||
OnboardingLocationContent(
|
||||
onLocationDetected: { zip in
|
||||
// Load regional templates in background
|
||||
onboardingState.loadRegionalTemplates(zip: zip)
|
||||
// Go to home profile step (residence created after profile)
|
||||
goForward()
|
||||
},
|
||||
onSkip: {
|
||||
// Handled by handleSkip() above
|
||||
}
|
||||
)
|
||||
.transition(navigationTransition)
|
||||
// Location step removed — skip to home profile if we land here
|
||||
EmptyView()
|
||||
.onAppear { goForward(to: .homeProfile) }
|
||||
.transition(navigationTransition)
|
||||
|
||||
case .homeProfile:
|
||||
OnboardingHomeProfileContent(
|
||||
|
||||
@@ -53,12 +53,9 @@ struct OnboardingFirstTaskContent: View {
|
||||
/// Cached categories — computed once and stored to preserve stable UUIDs
|
||||
@State private var taskCategoriesCache: [OnboardingTaskCategory]? = nil
|
||||
|
||||
/// Uses API-driven regional templates when available, falls back to hardcoded defaults
|
||||
/// Task categories for the Browse tab
|
||||
private var taskCategories: [OnboardingTaskCategory] {
|
||||
if let cached = taskCategoriesCache { return cached }
|
||||
if !onboardingState.regionalTemplates.isEmpty {
|
||||
return categoriesFromAPI(onboardingState.regionalTemplates)
|
||||
}
|
||||
return fallbackCategories
|
||||
}
|
||||
|
||||
@@ -297,9 +294,7 @@ struct OnboardingFirstTaskContent: View {
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
.a11yHeader()
|
||||
|
||||
Text(onboardingState.regionalTemplates.isEmpty
|
||||
? "Let's get you started with some tasks.\nThe more you pick, the more we'll help you remember!"
|
||||
: "Here are tasks recommended for your area.\nPick the ones you'd like to track!")
|
||||
Text("Let's get you started with some tasks.\nThe more you pick, the more we'll help you remember!")
|
||||
.font(.system(size: 15, weight: .medium))
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -465,11 +460,7 @@ struct OnboardingFirstTaskContent: View {
|
||||
isAnimating = true
|
||||
// Build and cache categories once to preserve stable UUIDs
|
||||
if taskCategoriesCache == nil {
|
||||
if !onboardingState.regionalTemplates.isEmpty {
|
||||
taskCategoriesCache = categoriesFromAPI(onboardingState.regionalTemplates)
|
||||
} else {
|
||||
taskCategoriesCache = fallbackCategories
|
||||
}
|
||||
taskCategoriesCache = fallbackCategories
|
||||
}
|
||||
// Expand first category by default
|
||||
if let first = taskCategories.first?.name {
|
||||
@@ -485,23 +476,15 @@ struct OnboardingFirstTaskContent: View {
|
||||
|
||||
private func selectPopularTasks() {
|
||||
withAnimation(.spring(response: 0.3)) {
|
||||
if !onboardingState.regionalTemplates.isEmpty {
|
||||
// API templates: select the first tasks (they're ordered by display_order)
|
||||
for task in allTasks {
|
||||
selectedTasks.insert(task.id)
|
||||
}
|
||||
} else {
|
||||
// Fallback: select hardcoded popular tasks
|
||||
let popularTaskTitles = [
|
||||
"Change HVAC Filter",
|
||||
"Test Smoke Detectors",
|
||||
"Check for Leaks",
|
||||
"Clean Gutters",
|
||||
"Clean Refrigerator Coils"
|
||||
]
|
||||
for task in allTasks where popularTaskTitles.contains(task.title) {
|
||||
selectedTasks.insert(task.id)
|
||||
}
|
||||
let popularTaskTitles = [
|
||||
"Change HVAC Filter",
|
||||
"Test Smoke Detectors",
|
||||
"Check for Leaks",
|
||||
"Clean Gutters",
|
||||
"Clean Refrigerator Coils"
|
||||
]
|
||||
for task in allTasks where popularTaskTitles.contains(task.title) {
|
||||
selectedTasks.insert(task.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,15 +36,6 @@ class OnboardingState: ObservableObject {
|
||||
/// The ID of the residence created during onboarding (used for task creation)
|
||||
@Published var createdResidenceId: Int32? = nil
|
||||
|
||||
/// ZIP code entered during the location step (used for residence creation and regional templates)
|
||||
@Published var pendingPostalCode: String = ""
|
||||
|
||||
/// Regional task templates loaded from API based on ZIP code
|
||||
@Published var regionalTemplates: [TaskTemplate] = []
|
||||
|
||||
/// Whether regional templates are currently loading
|
||||
@Published var isLoadingTemplates: Bool = false
|
||||
|
||||
// MARK: - Home Profile State (collected during onboarding)
|
||||
|
||||
@Published var pendingHeatingType: String? = nil
|
||||
@@ -80,20 +71,6 @@ class OnboardingState: ObservableObject {
|
||||
|
||||
private init() {}
|
||||
|
||||
/// Load regional task templates from the backend for the given ZIP code
|
||||
func loadRegionalTemplates(zip: String) {
|
||||
pendingPostalCode = zip
|
||||
isLoadingTemplates = true
|
||||
Task {
|
||||
defer { self.isLoadingTemplates = false }
|
||||
let result = try await APILayer.shared.getRegionalTemplates(state: nil, zip: zip)
|
||||
if let success = result as? ApiResultSuccess<NSArray>,
|
||||
let templates = success.data as? [TaskTemplate] {
|
||||
self.regionalTemplates = templates
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the onboarding flow
|
||||
func startOnboarding() {
|
||||
isOnboardingActive = true
|
||||
@@ -102,7 +79,7 @@ class OnboardingState: ObservableObject {
|
||||
}
|
||||
|
||||
/// Move to the next step in the flow
|
||||
/// Order: Welcome → Features → Name → Account → Verify → Location → Tasks → Upsell
|
||||
/// Order: Welcome → Features → Name → Account → Verify → Home Profile → Tasks → Upsell
|
||||
func nextStep() {
|
||||
switch currentStep {
|
||||
case .welcome:
|
||||
@@ -121,11 +98,12 @@ class OnboardingState: ObservableObject {
|
||||
if userIntent == .joinExisting {
|
||||
currentStep = .joinResidence
|
||||
} else {
|
||||
currentStep = .residenceLocation
|
||||
currentStep = .homeProfile
|
||||
}
|
||||
case .joinResidence:
|
||||
completeOnboarding()
|
||||
case .residenceLocation:
|
||||
// Skip past this step if we somehow land here
|
||||
currentStep = .homeProfile
|
||||
case .homeProfile:
|
||||
currentStep = .firstTask
|
||||
@@ -152,8 +130,6 @@ class OnboardingState: ObservableObject {
|
||||
hasCompletedOnboarding = true
|
||||
isOnboardingActive = false
|
||||
pendingResidenceName = ""
|
||||
pendingPostalCode = ""
|
||||
regionalTemplates = []
|
||||
createdResidenceId = nil
|
||||
userIntent = .unknown
|
||||
resetHomeProfile()
|
||||
@@ -165,8 +141,6 @@ class OnboardingState: ObservableObject {
|
||||
hasCompletedOnboarding = false
|
||||
isOnboardingActive = false
|
||||
pendingResidenceName = ""
|
||||
pendingPostalCode = ""
|
||||
regionalTemplates = []
|
||||
createdResidenceId = nil
|
||||
userIntent = .unknown
|
||||
currentStep = .welcome
|
||||
|
||||
Reference in New Issue
Block a user