Wire onboarding task suggestions to backend, delete hardcoded catalog
Both "For You" and "Browse All" tabs are now fully server-driven on iOS and Android. No on-device task list, no client-side scoring rules. When the API fails the screen shows error + Retry + Skip so onboarding can still complete on a flaky network. Shared (KMM) - TaskCreateRequest + TaskResponse carry templateId - New BulkCreateTasksRequest/Response, TaskApi.bulkCreateTasks, APILayer.bulkCreateTasks (updates DataManager + TotalSummary) - OnboardingViewModel: templatesGroupedState + loadTemplatesGrouped; createTasks(residenceId, requests) posts once via the bulk path - Deleted regional-template plumbing: APILayer.getRegionalTemplates, OnboardingViewModel.loadRegionalTemplates, TaskTemplateApi. getTemplatesByRegion, TaskTemplate.regionId/regionName - 5 new AnalyticsEvents constants for the onboarding funnel Android (Compose) - OnboardingFirstTaskContent rewritten against the server catalog; ~70 lines of hardcoded taskCategories gone. Loading / Error / Empty panes with Retry + Skip buttons. Category icons derived from name keywords, colours from a 5-value palette keyed by category id - Browse selection carries template.id into the bulk request so task_template_id is populated server-side iOS (SwiftUI) - New OnboardingTasksViewModel (@MainActor ObservableObject) wrapping APILayer.shared for suggestions / grouped / bulk-submit with loading + error state (mirrors the TaskViewModel.swift pattern) - OnboardingFirstTaskView rewritten: buildForYouSuggestions (130 lines) and fallbackCategories (68 lines) deleted; both tabs show the same error+skip UX as Android; ForYouSuggestion/SuggestionRelevance gone - 5 new AnalyticsEvent cases with identical PostHog event names to the Kotlin constants so cross-platform funnels join cleanly - Existing TaskCreateRequest / TaskResponse call sites in TaskCard, TasksSection, TaskFormView updated for the new templateId parameter Docs - CLAUDE.md gains an "Onboarding task suggestions (server-driven)" subsection covering the data flow, key files on both platforms, and the KotlinInt(int: template.id) wrapping requirement Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -638,6 +638,23 @@ object APILayer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically creates 1-50 tasks via POST /api/tasks/bulk/. The whole
|
||||
* batch succeeds or fails together on the server. On success, every
|
||||
* returned task is merged into DataManager.allTasks so observing views
|
||||
* render the new batch immediately.
|
||||
*/
|
||||
suspend fun bulkCreateTasks(request: BulkCreateTasksRequest): ApiResult<BulkCreateTasksResponse> {
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.bulkCreateTasks(token, request)
|
||||
|
||||
if (result is ApiResult.Success) {
|
||||
DataManager.setTotalSummary(result.data.summary)
|
||||
result.data.tasks.forEach { DataManager.updateTask(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun updateTask(id: Int, request: TaskCreateRequest): ApiResult<TaskResponse> {
|
||||
val token = getToken() ?: return ApiResult.Error("Not authenticated", 401)
|
||||
val result = taskApi.updateTask(token, id, request)
|
||||
@@ -1200,15 +1217,6 @@ object APILayer {
|
||||
} ?: ApiResult.Error("Task template not found")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task templates filtered by climate region.
|
||||
* Accepts either a state abbreviation or ZIP code — backend resolves to climate zone.
|
||||
* This calls the API directly since regional templates are not cached in seeded data.
|
||||
*/
|
||||
suspend fun getRegionalTemplates(state: String? = null, zip: String? = null): ApiResult<List<TaskTemplate>> {
|
||||
return taskTemplateApi.getTemplatesByRegion(state = state, zip = zip)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get personalized task suggestions for a residence based on its home profile.
|
||||
*/
|
||||
|
||||
@@ -66,6 +66,31 @@ class TaskApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically creates 1-50 tasks in a single transaction. Used by
|
||||
* onboarding and anywhere else that needs "all or nothing" task
|
||||
* creation. The server overrides every entry's residence_id with the
|
||||
* top-level request.residenceId.
|
||||
*/
|
||||
suspend fun bulkCreateTasks(token: String, request: BulkCreateTasksRequest): ApiResult<BulkCreateTasksResponse> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/tasks/bulk/") {
|
||||
header("Authorization", "Token $token")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
val errorMessage = ErrorParser.parseError(response)
|
||||
ApiResult.Error(errorMessage, response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateTask(token: String, id: Int, request: TaskCreateRequest): ApiResult<WithSummaryResponse<TaskResponse>> {
|
||||
return try {
|
||||
val response = client.put("$baseUrl/tasks/$id/") {
|
||||
|
||||
@@ -85,27 +85,6 @@ class TaskTemplateApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get templates filtered by climate region.
|
||||
* Accepts either a state abbreviation or ZIP code — backend resolves to climate zone.
|
||||
*/
|
||||
suspend fun getTemplatesByRegion(state: String? = null, zip: String? = null): ApiResult<List<TaskTemplate>> {
|
||||
return try {
|
||||
val response = client.get("$baseUrl/tasks/templates/by-region/") {
|
||||
state?.let { parameter("state", it) }
|
||||
zip?.let { parameter("zip", it) }
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
ApiResult.Error("Failed to fetch regional templates", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get personalized task suggestions for a residence based on its home profile.
|
||||
* Requires authentication.
|
||||
|
||||
Reference in New Issue
Block a user