From c510d6fbc66b952e9e98e3a48c392775a7ff0f7a Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 22 Feb 2026 21:49:38 -0600 Subject: [PATCH] feat(web): wire tasks page to real API data Replace mock data with real fetchTasks() API call. Add workspace-aware data fetching, MosaicSpinner loading state, empty state card, and error state with retry using design tokens throughout. Refs #467 --- .../src/app/(authenticated)/tasks/page.tsx | 122 ++++++++++++++---- apps/web/src/lib/api/tasks.ts | 80 +----------- 2 files changed, 95 insertions(+), 107 deletions(-) diff --git a/apps/web/src/app/(authenticated)/tasks/page.tsx b/apps/web/src/app/(authenticated)/tasks/page.tsx index 6873ce1..a22a2c6 100644 --- a/apps/web/src/app/(authenticated)/tasks/page.tsx +++ b/apps/web/src/app/(authenticated)/tasks/page.tsx @@ -4,57 +4,123 @@ import { useState, useEffect } from "react"; import type { ReactElement } from "react"; import { TaskList } from "@/components/tasks/TaskList"; -import { mockTasks } from "@/lib/api/tasks"; +import { MosaicSpinner } from "@/components/ui/MosaicSpinner"; +import { fetchTasks } from "@/lib/api/tasks"; +import { useWorkspaceId } from "@/lib/hooks"; import type { Task } from "@mosaic/shared"; export default function TasksPage(): ReactElement { + const workspaceId = useWorkspaceId(); const [tasks, setTasks] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { - void loadTasks(); - }, []); - - async function loadTasks(): Promise { - setIsLoading(true); - setError(null); - - try { - // TODO: Replace with real API call when backend is ready - // const data = await fetchTasks(); - await new Promise((resolve) => setTimeout(resolve, 300)); - setTasks(mockTasks); - } catch (err) { - setError( - err instanceof Error - ? err.message - : "We had trouble loading your tasks. Please try again when you're ready." - ); - } finally { + if (!workspaceId) { setIsLoading(false); + return; } + + let cancelled = false; + setError(null); + setIsLoading(true); + + async function loadTasks(): Promise { + try { + const filters = workspaceId !== null ? { workspaceId } : {}; + const data = await fetchTasks(filters); + if (!cancelled) { + setTasks(data); + } + } catch (err: unknown) { + console.error("[Tasks] Failed to fetch tasks:", err); + if (!cancelled) { + setError( + err instanceof Error + ? err.message + : "We had trouble loading your tasks. Please try again when you're ready." + ); + } + } finally { + if (!cancelled) { + setIsLoading(false); + } + } + } + + void loadTasks(); + + return (): void => { + cancelled = true; + }; + }, [workspaceId]); + + function handleRetry(): void { + if (!workspaceId) return; + setError(null); + setIsLoading(true); + + fetchTasks({ workspaceId }) + .then((data) => { + setTasks(data); + }) + .catch((err: unknown) => { + console.error("[Tasks] Retry failed:", err); + setError( + err instanceof Error + ? err.message + : "We had trouble loading your tasks. Please try again when you're ready." + ); + }) + .finally(() => { + setIsLoading(false); + }); } return (
-

Tasks

-

Organize your work at your own pace

+

+ Tasks +

+

+ Organize your work at your own pace +

- {error !== null ? ( -
-

{error}

+ {isLoading ? ( +
+ +
+ ) : error !== null ? ( +
+

{error}

+ ) : tasks.length === 0 ? ( +
+

No tasks found

+
) : ( - + )}
); diff --git a/apps/web/src/lib/api/tasks.ts b/apps/web/src/lib/api/tasks.ts index db01403..9074b59 100644 --- a/apps/web/src/lib/api/tasks.ts +++ b/apps/web/src/lib/api/tasks.ts @@ -4,7 +4,7 @@ */ import type { Task } from "@mosaic/shared"; -import { TaskStatus, TaskPriority } from "@mosaic/shared"; +import type { TaskStatus, TaskPriority } from "@mosaic/shared"; import { apiGet, type ApiResponse } from "./client"; export interface TaskFilters { @@ -34,81 +34,3 @@ export async function fetchTasks(filters?: TaskFilters): Promise { const response = await apiGet>(endpoint, filters?.workspaceId); return response.data; } - -/** - * Mock tasks for development (until backend endpoints are ready) - */ -export const mockTasks: Task[] = [ - { - id: "task-1", - title: "Review pull request", - description: "Review and provide feedback on frontend PR", - status: TaskStatus.IN_PROGRESS, - priority: TaskPriority.HIGH, - dueDate: new Date("2026-01-29"), - creatorId: "user-1", - assigneeId: "user-1", - workspaceId: "workspace-1", - projectId: null, - parentId: null, - sortOrder: 0, - metadata: {}, - completedAt: null, - createdAt: new Date("2026-01-28"), - updatedAt: new Date("2026-01-28"), - }, - { - id: "task-2", - title: "Update documentation", - description: "Add setup instructions for new developers", - status: TaskStatus.IN_PROGRESS, - priority: TaskPriority.MEDIUM, - dueDate: new Date("2026-01-30"), - creatorId: "user-1", - assigneeId: "user-1", - workspaceId: "workspace-1", - projectId: null, - parentId: null, - sortOrder: 1, - metadata: {}, - completedAt: null, - createdAt: new Date("2026-01-28"), - updatedAt: new Date("2026-01-28"), - }, - { - id: "task-3", - title: "Plan Q1 roadmap", - description: "Define priorities for Q1 2026", - status: TaskStatus.NOT_STARTED, - priority: TaskPriority.HIGH, - dueDate: new Date("2026-02-03"), - creatorId: "user-1", - assigneeId: "user-1", - workspaceId: "workspace-1", - projectId: null, - parentId: null, - sortOrder: 2, - metadata: {}, - completedAt: null, - createdAt: new Date("2026-01-28"), - updatedAt: new Date("2026-01-28"), - }, - { - id: "task-4", - title: "Research new libraries", - description: "Evaluate options for state management", - status: TaskStatus.PAUSED, - priority: TaskPriority.LOW, - dueDate: new Date("2026-02-10"), - creatorId: "user-1", - assigneeId: "user-1", - workspaceId: "workspace-1", - projectId: null, - parentId: null, - sortOrder: 3, - metadata: {}, - completedAt: null, - createdAt: new Date("2026-01-28"), - updatedAt: new Date("2026-01-28"), - }, -]; -- 2.49.1