Skip to content

Latest commit

 

History

History

TaskFlow.Web

README.md

TaskFlow.Web

The React TypeScript frontend for TaskFlow — a full-stack task management application. Built with Vite 8, React 19, Tailwind CSS v4, and TanStack Query v5.

Overview

TaskFlow.Web is a single-page application (SPA) that provides a UI for managing tasks, per-task notes, and a daily journal. It communicates with the TaskFlow.Api backend via a fully typed API client that is auto-generated from the OpenAPI specification.

Tech Stack

Concern Technology
Framework React 19 + TypeScript
Build tool Vite 8
Styling Tailwind CSS v4
Routing React Router v7
Server state TanStack Query v5
API client hey-api/openapi-ts (generated from OpenAPI spec)
Testing Vitest + React Testing Library
Production serve vite preview (with /api proxy)

Getting Started

Prerequisites

  • Node.js 26+
  • The TaskFlow.Api running at http://localhost:8080 (or via Docker Compose)

Install and run

cd TaskFlow.Web
npm install
npm run dev

The app is available at http://localhost:5173.

The Vite dev server proxies /api and /openapi requests to http://localhost:8080 by default, so no CORS configuration is needed during development. To target a different API host:

API_TARGET=http://myhost:8080 npm run dev

Run via Docker Compose (full stack)

From the repo root:

docker compose up
Service URL
Frontend http://localhost:3000
API http://localhost:8080
Seq (logs) http://localhost:5380

Available Scripts

Script Description
npm run dev Vite dev server with HMR at http://localhost:5173
npm run build Type-check + production bundle → dist/
npm run preview Serve the production build locally
npm run test Run tests with Vitest (watch mode)
npm run test -- --run Run tests once and exit
npm run type-check TypeScript check without emitting files
npm run lint ESLint
npm run gen:api Regenerate typed client from live OpenAPI spec

Project Structure

TaskFlow.Web/
├── src/
│   ├── api/
│   │   ├── client/             # Auto-generated typed API client (do not edit manually)
│   │   │   ├── client.gen.ts   # Client instance (relative paths by default)
│   │   │   ├── sdk.gen.ts      # All generated API functions
│   │   │   └── types.gen.ts    # All generated TypeScript types
│   │   └── journal.ts          # Hand-written journal API functions + DTO types
│   ├── hooks/
│   │   ├── useTasks.ts         # TanStack Query hooks for task CRUD
│   │   ├── useNotes.ts         # TanStack Query hooks for note CRUD
│   │   └── useJournal.ts       # TanStack Query hooks for journal (entries, todos, logs)
│   ├── pages/
│   │   ├── TasksPage.tsx       # Task list — filter (Status multi-select, default Draft + To-do), sort, create, edit, delete
│   │   ├── TaskDetailPage.tsx  # Task detail + inline note management
│   │   └── JournalPage.tsx     # Daily journal page + PrefsBar settings panel
│   ├── components/
│   │   ├── TaskCard.tsx        # Task summary card (used in list)
│   │   ├── TaskForm.tsx        # Create/edit task form (shared)
│   │   ├── NoteCard.tsx        # Note display with edit/delete actions
│   │   ├── NoteForm.tsx        # Create/edit note form
│   │   └── journal/
│   │       ├── DateNav.tsx         # Prev/next date navigation bar
│   │       ├── JournalHeader.tsx   # Day/week stat header (stat or minimal style)
│   │       ├── TodosSection.tsx    # Linked tasks with toggle, edit
│   │       ├── DailyLogSection.tsx # Timestamped activity log
│   │       └── NotesSection.tsx    # Freeform notes (debounced auto-save)
│   ├── lib/
│   │   ├── utils.ts            # cn() class helper, formatDate()
│   │   └── journal-utils.ts    # Date helpers (ISO↔URL, day/week counters, formatting)
│   ├── journal.css             # Journal page styles (scoped under .journal-page)
│   ├── App.tsx                 # Router setup (/ → today's journal, /journal/:date, /tasks)
│   └── main.tsx                # React root, QueryClient provider
├── .env.development            # (empty — Vite proxy handles /api → localhost:8080)
├── .env.production             # (empty — reverse proxy handles /api routing)
├── vite.config.ts              # Vite config — @ alias, dev proxy, test config
├── Dockerfile                  # Multi-stage: build → vite preview
└── package.json

API Client Generation

The typed API client is generated by @hey-api/openapi-ts directly from the running backend's OpenAPI spec. This ensures TypeScript types always match the actual API contract.

Regenerate the client:

# 1. Ensure the API is running at http://localhost:8080
dotnet run --project ../TaskFlow.Api

# 2. Regenerate
npm run gen:api

The generated files land in src/api/client/. Do not edit them manually — they will be overwritten on the next gen:api run. Commit the generated files to source control so the build is reproducible without a live API server.

How it works:

  • client.gen.ts exports the configured client singleton (relative paths by default, routed via Vite proxy in dev or the reverse proxy in production)
  • sdk.gen.ts exports one typed function per API endpoint (e.g. getApiV1TaskItems, postApiV1TaskItems)
  • types.gen.ts exports all request/response types derived from the OpenAPI schema

TanStack Query Hooks

All server state (fetching, caching, mutation, invalidation) is encapsulated in hooks in src/hooks/. Pages and components call hooks and don't touch the API client directly.

useTasks.ts exports:

  • useTasksQuery() — fetch all tasks
  • useTaskQuery(id) — fetch a single task (disabled when id is not finite)
  • useCreateTaskMutation() — create task, invalidates task list
  • useUpdateTaskMutation() — update task, invalidates list and detail
  • useDeleteTaskMutation() — delete task, invalidates list, removes detail from cache

useNotes.ts exports:

  • useNotesQuery(taskId) — fetch notes for a task (disabled when taskId is not finite)
  • useCreateNoteMutation(taskId) — add note, invalidates note list
  • useUpdateNoteMutation(taskId) — update note, invalidates note list
  • useDeleteNoteMutation(taskId) — delete note, invalidates note list

useJournal.ts exports:

  • useJournalEntries() — fetch all journal entries (includes embedded log entries)
  • useJournalTodos(entryId) — fetch tasks linked to an entry
  • useEnsureJournalEntry(isoDate) — finds or auto-creates the entry for a given date
  • useUpdateNotesMutation(entryId, entryTitle) — debounced summary save
  • useCreateTodoMutation(entryId) — creates a TaskItem then links it to the entry
  • useToggleTodoMutation(entryId) — toggles task status (todo ↔ completed)
  • useEditTodoMutation(entryId) — renames a linked task
  • useRemoveTodoMutation(entryId) — unlinks a task from the entry
  • useAddLogEntryMutation(entryId) — adds a log entry, invalidates journal cache
  • useDeleteLogEntryMutation(entryId) — removes a log entry

Configuration

No frontend environment variables are required. Both .env.development and .env.production are empty files.

The API client uses relative paths (/api/v1/...) by default. Routing is handled entirely by the proxy layer — no CORS configuration is needed:

  • Dev: Vite dev server proxies /api and /openapi to http://localhost:8080
  • Docker Compose / production: vite preview proxies the same paths to $API_TARGET

The Vite dev server proxy target can be overridden via environment variable:

API_TARGET=http://taskflow-api:8080 npm run dev

The vite preview server also supports an ALLOWED_HOSTS environment variable (comma-separated hostnames). When unset, all hostnames are accepted. Set this in your deployment environment to restrict which hostnames the preview server responds to:

ALLOWED_HOSTS=taskflow.skalaforge.com API_TARGET=http://taskflow-api:8080 vite preview

Testing

# Run all tests once
npm run test -- --run

# Watch mode
npm run test

Tests are in *.test.ts(x) files co-located alongside the source files they cover. There are 24 tests across components and hooks.

Coverage areas:

  • TaskCard, TaskForm, NoteCard, NoteForm — rendering, interaction, form submission
  • useTasks, useNotes — all query and mutation hooks, verifying correct SDK calls and arguments

Docker

The production Dockerfile is a two-stage build:

# Stage 1: build
FROM node:26-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: serve
FROM node:26-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/package.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY vite.preview.config.js ./
EXPOSE 3000
CMD ["node_modules/.bin/vite", "preview", "--config", "vite.preview.config.js", "--host", "0.0.0.0", "--port", "3000"]

The vite preview server handles SPA fallback routing and proxies /api and /openapi requests to $API_TARGET (set to http://taskflow-api:8080 in Docker Compose). This removes the need for CORS since all requests appear same-origin to the browser.

Runtime environment variables (read at container startup, not baked into the image):

Variable Default Purpose
API_TARGET http://localhost:8080 API URL the preview server proxies to
ALLOWED_HOSTS (unset — allows all) Comma-separated allowed hostnames (e.g. taskflow.skalaforge.com)

Related Documentation