Production-minded personal task management — persisted, validated, and ready to run end-to-end
Full-stack TypeScript, PostgreSQL, Google sign-in, smart views, tags, subtasks, trash & restore, stats.
Overview · Screenshots · Features · Stack · Getting started · API & server · Data model · Rubric · Deploy
Task-Flow is a browser-based task manager with a real database, shared client/server validation, and HTTP-backed server execution. It was built to satisfy the Acme / intern take-home brief (see Acme.md in this repo): view, create, edit, toggle status, and remove tasks, with filters and clear error handling.
The reference brief assumed a single anonymous user and a small REST surface. This submission extends that baseline with Google OAuth (Better Auth), per-user data isolation, soft delete + trash, and a richer domain (tags, subtasks, priorities, due dates, drag reorder, keyboard shortcuts). Where behavior differs from the literal brief, it is called out under Deviations & design choices.
- Overview
- Screenshots
- Features
- Tech stack
- Architecture
- API & server contract
- Data model
- UI, responsiveness & accessibility
- Prerequisites
- Local setup
- Environment variables
- Scripts
- Testing & quality
- Project structure
- Security & authentication
- Deployment
- Submission checklist
- Evaluation rubric (Acme)
- Bonus scope
- Deviations & design choices
- FAQ
- AI assistance disclosure
- License
Dashboard (/tasks) — filters, productivity trends, insights, quick add, and side panels.
| Brief expectation | Implementation |
|---|---|
| List tasks (title, description, status, created date) | Task list & smart views (/tasks, Today, Upcoming, Completed); cards show metadata, tags, subtask progress |
| Add task (title required, optional description) | Quick-add bar + full modal; persisted via server |
| Toggle complete / incomplete | Checkbox / status control with optimistic updates |
| Delete task | Soft delete by default (recoverable); permanent delete from Trash uses a confirmation dialog |
| Edit task | Edit modal + inline flows where supported |
| Filter All / Pending / Completed | Filters + search + sort; state can sync to URL |
| Validation: reject empty title, show errors | Zod on server functions; React Hook Form + Zod in UI |
| Persist in a real DB | PostgreSQL via Prisma; no localStorage as primary store |
Additional (beyond minimum): tags, subtasks, priorities, due dates (with overdue emphasis), stats, drag-to-reorder, trash restore, bulk actions, dark/light theming.
| Layer | Choice |
|---|---|
| Application | TanStack Start — React 19, Vite 7, SSR-ready |
| Routing | TanStack Router (file-based routes under src/routes/) |
| Server I/O | TanStack createServerFn (type-safe RPC over HTTP), Zod input validation |
| ORM & migrations | Prisma 7 → PostgreSQL |
| Auth | Better Auth + Google OAuth |
| Client data | TanStack Query |
| Forms | React Hook Form + Zod resolver |
| Styling | Tailwind CSS 4 + app-specific tokens / components |
| Tests | Vitest (unit), Playwright (E2E) |
Browser (React)
│ TanStack Query
▼
Server functions (src/lib/*.functions.ts)
│ session (Better Auth), Zod parse
▼
Repositories (src/lib/*.repo.ts)
│ Prisma, scoped by userId
▼
PostgreSQL
- Single deployable app: “frontend” and “backend” share one process in development and one build for production; the client does not talk to the database directly.
- Auth boundary: Better Auth serves
/api/auth/*. Task server functions callauth.api.getSessionand refuse work without a user. - Conventions: Domain validation lives in
src/lib/validations/. Deeper product rules are documented inFEATURES.mdandAGENTS.md.
The Acme brief lists REST endpoints (GET/POST /api/tasks, PUT/PATCH/DELETE /api/tasks/:id, etc.). This project implements the same operations as authenticated server functions (POST-shaped RPC handlers generated by TanStack Start) rather than hand-written Express-style routers.
Why this still satisfies “HTTP API” expectations: each call is an HTTP request from the browser to the app server, returns JSON-shaped data, and uses validation + error semantics appropriate for production (invalid input rejected on the server; unauthenticated calls blocked).
Conceptual mapping:
| Brief endpoint | This codebase |
|---|---|
GET /api/tasks |
listTasksFn — list/filter/paginate tasks for the signed-in user |
POST /api/tasks |
createTaskFn |
PUT /api/tasks/:id |
updateTaskFn |
PATCH /api/tasks/:id/toggle |
toggleTaskFn |
DELETE /api/tasks/:id |
deleteTaskFn — soft delete (see Deviations); trash purge uses permanentDeleteTaskFn |
Auth routes remain standard HTTP: Better Auth under /api/auth/*.
The brief’s minimal tasks table maps to the Prisma Task model (extended for production):
| Brief field | Prisma / notes |
|---|---|
id |
String / cuid |
title |
VarChar(255), required |
description |
Text, optional |
status |
TaskStatus enum (PENDING, IN_PROGRESS, COMPLETED) |
created_at / updated_at |
createdAt, updatedAt |
| — | priority, dueDate, position, isDeleted, deletedAt, userId, relations to Subtask, Tag |
See prisma/schema.prisma and migrations under prisma/migrations/.
- Mobile-first layout: dashboard shell, sidebar, bottom navigation, and task lists adapt down to phone widths (~375px).
- Brief UI checklist: list + create + toggle + delete/edit flows + empty-title errors + pending vs completed visuals (styling, icons, motion where appropriate).
- Components: Radix-based primitives (dialogs, alerts) for confirmations and focus management.
- Node.js 22.x (LTS) or newer — nodejs.org
- PostgreSQL — local or hosted (e.g. Neon)
- Google OAuth app — client ID & secret; authorized redirect:
{BETTER_AUTH_URL}/api/auth/callback/google - Bun (optional) — some scripts invoke
bunx; see Local setup for a purenpxfallback
Target: clone → install → env → migrate → run in under 10 minutes on a machine that already has Node and a blank PostgreSQL database.
-
Clone
git clone <YOUR_REPO_URL> task-flow cd task-flow
-
Install
npm install
-
Environment
cp .env.example .env
Fill in
DATABASE_URL,BETTER_AUTH_URL(dev:http://localhost:5173),BETTER_AUTH_SECRET(≥ 32 random chars),GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET. -
Schema
npm run db:migrate
-
Seed (optional)
npm run db:seed
If
bunxis missing:npx tsx --tsconfig tsconfig.json prisma/seed.ts
Seeding creates
dev@taskflow.localplus sample rows for Prisma Studio; your Google account is a separate user unless you align emails deliberately. -
Develop
npm run dev
Default dev URL:
http://localhost:5173(seeplaywright.config.ts). Sign in with Google → land on/tasks.
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL URL (sslmode=require for Neon) |
BETTER_AUTH_URL |
Yes | Public app origin (https://… in production) |
BETTER_AUTH_SECRET |
Yes | Session signing secret (≥ 32 chars) |
GOOGLE_CLIENT_ID |
Yes* | OAuth client ID |
GOOGLE_CLIENT_SECRET |
Yes* | OAuth client secret |
VITE_BETTER_AUTH_URL |
Optional | When the SPA is served from a different origin than the server |
SKIP_ENV_VALIDATION |
Optional | e.g. 1 in CI helpers |
*Required for Google sign-in. Never commit .env — only .env.example.
| Command | Description |
|---|---|
npm run dev |
Dev server (Vite + TanStack Start) |
npm run build |
Production build (Nitro preset for Vercel-style output when VERCEL=1) |
npm run preview |
Preview production build locally |
npm run lint |
ESLint |
npm run format |
Prettier |
npm run db:generate |
prisma generate |
npm run db:migrate |
prisma migrate dev |
npm run db:migrate:deploy |
prisma migrate deploy (CI / production) |
npm run db:seed |
Seed script |
npm run db:studio |
Prisma Studio |
npm test |
Vitest |
npm run test:e2e |
Playwright |
- Unit / integration-style tests under
tests/unit/(e.g. validation, repository behavior). - E2E under
tests/e2e/via Playwright (PLAYWRIGHT_BASE_URLoverrides default host). - Linting:
npm run lintbefore merge; TypeScript strictness as configured intsconfig.json.
├── docs/
│ └── taskflow-mark.svg
├── public/
│ └── dashboard.jpeg # Screenshots (README)
├── prisma/
│ ├── schema.prisma
│ ├── migrations/
│ └── seed.ts
├── src/
│ ├── routes/ # File routes + layouts
│ ├── components/ # Dashboard UI, primitives
│ ├── hooks/
│ ├── lib/
│ │ ├── auth.ts
│ │ ├── *.functions.ts # Server functions
│ │ ├── *.repo.ts # Prisma access
│ │ └── validations/ # Zod schemas
│ └── generated/prisma/
├── tests/
│ ├── unit/
│ └── e2e/
├── Acme.md # Assignment brief & rubric (evaluation source)
├── .env.example
├── AGENTS.md
├── FEATURES.md
└── README.md
- Secrets only via environment variables (
src/env.ts/ T3-style validation). - Sessions via Better Auth; task queries always scoped by
userId. - No raw Prisma errors leaked to clients on failure paths — errors logged server-side, safe messages to the UI.
- Google OAuth: configure production callback URLs to match
BETTER_AUTH_URL.
- Set production environment variables (same keys as
.env.example;BETTER_AUTH_URLmust equal the public site origin). - Run
npm run db:migrate:deployagainst the production database. - Register
{BETTER_AUTH_URL}/api/auth/callback/googlein Google Cloud Console.
The Vite config disables the default Cloudflare build for production when using Nitro and sets cloudflare: false so npm run build can emit output Vercel expects when VERCEL=1. Do not point Vercel at a Cloudflare-only output directory unless you intentionally target Workers.
wrangler.jsonc points at src/server.ts for an alternative hosting model.
Live demo (optional): add your public URL here after deploy:
https://YOUR_PRODUCTION_DOMAIN