diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index a854ea7..06052ab 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -51,17 +51,20 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: "npm" + cache: "pnpm" - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Lint - run: npm run lint --workspace=@worksight/${{ matrix.app }} + run: pnpm --filter @worksight/${{ matrix.app }} lint - name: Build Docker image run: | diff --git a/.github/workflows/ci-fallback.yml b/.github/workflows/ci-fallback.yml index 7ccba46..ead4494 100644 --- a/.github/workflows/ci-fallback.yml +++ b/.github/workflows/ci-fallback.yml @@ -6,33 +6,35 @@ on: pull_request: branches: [main, dev] -# This workflow uses npm as a fallback if pnpm issues persist jobs: - quality-npm: - name: Quality Checks (npm) + quality: + name: Quality Checks runs-on: ubuntu-latest - if: false # Disabled by default, enable if pnpm CI fails + if: false # Disabled by default steps: - name: Checkout code uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - cache: 'npm' + cache: 'pnpm' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Type check - run: npm run type-check + run: pnpm type-check - name: Lint check - run: npm run lint + run: pnpm lint - name: Build application - run: npm run build + run: pnpm build env: NEXT_TELEMETRY_DISABLED: 1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index df9029c..e3b29bd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,22 +27,25 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - cache: 'npm' + cache: 'pnpm' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Type check - run: npm run type-check + run: pnpm type-check - name: Lint check - run: npm run lint + run: pnpm lint - name: Build application - run: npm run build + run: pnpm build env: NEXT_TELEMETRY_DISABLED: 1 diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index f734e7d..1ecfa41 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -23,22 +23,25 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'npm' + cache: 'pnpm' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Build web app - run: npm run build --workspace=@worksight/web + run: pnpm --filter @worksight/web build - name: Start application run: | cd apps/web - npm start & + pnpm start & echo $! > server.pid shell: bash @@ -47,7 +50,7 @@ jobs: - name: Run Lighthouse CI run: | - npx lhci autorun --upload.target=temporary-public-storage + pnpm dlx @lhci/cli autorun --upload.target=temporary-public-storage env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -66,17 +69,20 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'npm' + cache: 'pnpm' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Analyze bundle - run: npm run build:analyze --workspace=@worksight/web + run: pnpm --filter @worksight/web build:analyze - name: Upload bundle analysis uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 32f19f2..009c6ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,25 +22,29 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'npm' + cache: 'pnpm' - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Lint ${{ matrix.app }} - run: npm run lint --workspace=${{ matrix.app }} + run: pnpm --filter ${{ matrix.app }} lint - name: Build ${{ matrix.app }} - run: npm run build --workspace=${{ matrix.app }} + run: pnpm --filter ${{ matrix.app }} build - name: Package ${{ matrix.app }} into ZIP run: | mkdir -p release/${{ matrix.app }} - cp -r apps/${{ matrix.app }}/.next release/${{ matrix.app }}/ + cp -r apps/${{ matrix.app }}/.next release/${{ matrix.app }}/ || true + cp -r apps/${{ matrix.app }}/.vitepress/dist release/${{ matrix.app }}/ || true cp -r apps/${{ matrix.app }}/package.json release/${{ matrix.app }}/ cd release zip -r ../${{ matrix.app }}-${GITHUB_REF_NAME}.zip ${{ matrix.app }} diff --git a/.gitignore b/.gitignore index 1ac2009..63c6c84 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,6 @@ node_modules/ !.yarn/plugins !.yarn/releases !.yarn/versions -pnpm-lock.yaml -pnpm-workspace.yaml .pnpm-debug.log* ###################### diff --git a/README.md b/README.md index adb5d9f..2cc5aa6 100644 --- a/README.md +++ b/README.md @@ -3,311 +3,198 @@ [![CI](https://github.com/4sightorg/worksight/workflows/CI/badge.svg)](https://github.com/4sightorg/worksight/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -WorkSight is an employee well-being analytics platform built with Next.js and -Supabase. This monorepo contains the web application, documentation, and shared -packages. +WorkSight is an employee well-being analytics platform. This **pnpm + +Turborepo** monorepo ships a Next.js web app, a NestJS API, VitePress docs, and +shared packages โ€” notably `@worksight/common` (types, fixtures, lookup utils). -## ๐Ÿ—๏ธ Project Structure +**MVP note:** dashboard and API data for the current slice come from +`@worksight/common` fixtures, not live Supabase persistence. Supabase remains +optional for web auth / online mode. + +## Project structure ```text worksight/ โ”œโ”€โ”€ apps/ -โ”‚ โ”œโ”€โ”€ web/ # Next.js web application -โ”‚ โ””โ”€โ”€ docs/ # VitePress documentation -โ”œโ”€โ”€ packages/ # Shared packages (future) -โ””โ”€โ”€ .github/ # CI/CD workflows +โ”‚ โ”œโ”€โ”€ web/ # @worksight/web โ€” Next.js 15 (App Router) +โ”‚ โ”œโ”€โ”€ api/ # @worksight/api โ€” NestJS +โ”‚ โ””โ”€โ”€ docs/ # @worksight/docs โ€” VitePress +โ”œโ”€โ”€ packages/ +โ”‚ โ”œโ”€โ”€ common/ # @worksight/common โ€” types, fixtures, utils +โ”‚ โ””โ”€โ”€ assets/ # @worksight/assets +โ”œโ”€โ”€ doc/ # Repo guides (e.g. DEPLOYMENT.md) +โ”œโ”€โ”€ docs/ # MVP plan + handoffs +โ””โ”€โ”€ .github/ # CI workflows ``` -## ๐Ÿš€ Quick Start - -### Prerequisites +## Prerequisites -- **Node.js**: v18+ -- **pnpm**: v8+ (recommended package manager) -- **Git**: For version control +- **Node.js** `>=18` (repo engines; Node 20+ recommended) +- **pnpm** `>=9` (lockfile uses pnpm 10 โ€” see `packageManager` in root + `package.json`) +- **Git** -### Installation +## Quick start ```bash -# Clone the repository git clone https://github.com/4sightorg/worksight.git cd worksight - -# Install dependencies pnpm install -# Set up environment variables +# Optional web env (offline mode works without Supabase) cp apps/web/env.example apps/web/.env.local -# Edit apps/web/.env.local with your Supabase credentials ``` -### Development +### Run apps ```bash -# Start the web application +# Build shared packages first when developing API/web against common +pnpm --filter @worksight/common build + +# Web (http://localhost:3000) pnpm dev:web +# or: pnpm --filter @worksight/web dev + +# Start the Nest API (fixture-backed common data; default :3001) +pnpm --filter @worksight/common build +pnpm dev:api # Start the documentation site pnpm dev:docs +# or: pnpm --filter @worksight/docs dev -# Start both applications +# Start both web + docs (turbo) pnpm dev + +# MVP E2E demo: API + web with shared common fixtures (see docs/mvp/DEMO.md) +pnpm demo ``` Open: - **Web App**: +- **E2E demo page**: (requires API on :3001) +- **API**: (Swagger at `/api`) - **Documentation**: +> Demo data is **fixture-backed** from `@worksight/common` โ€” not Supabase. Set +> `NEXT_PUBLIC_USE_API=true` and `NEXT_PUBLIC_API_URL=http://localhost:3001` so +> dashboard/admin pages call Nest instead of in-process fixtures. + ## ๐Ÿ“ฆ Available Scripts ### Root Scripts ```bash -# Development -pnpm dev # Start all applications -pnpm dev:web # Start web app only -pnpm dev:docs # Start docs only - -# Building -pnpm build # Build all applications -pnpm build:web # Build web app only -pnpm build:docs # Build docs only - -# Testing -pnpm test # Run all tests -pnpm test:web # Run web app tests -pnpm lint # Lint all code -pnpm type-check # TypeScript type checking - -# Utilities -pnpm clean # Clean all build outputs -pnpm format # Format code with Prettier +pnpm build # turbo build (all packages) +pnpm build:web # @worksight/web +pnpm build:docs # @worksight/docs +pnpm type-check # turbo type-check +pnpm lint # turbo lint +pnpm test # turbo test +pnpm format # Prettier write +pnpm format:check # Prettier check +pnpm quality # turbo quality +pnpm clean # turbo clean ``` -### Application-Specific Scripts +Filter any package directly: ```bash -# Web application (apps/web) -cd apps/web -pnpm dev # Start development server -pnpm build # Build for production -pnpm start # Start production server -pnpm test # Run tests -pnpm test:watch # Run tests in watch mode - -# Documentation (apps/docs) -cd apps/docs -pnpm dev # Start dev server -pnpm build # Build static site -pnpm preview # Preview production build -``` - -## ๐Ÿ› ๏ธ Technology Stack - -### Web Application (`apps/web`) - -- **Framework**: Next.js 14 (App Router) -- **Language**: TypeScript -- **Styling**: Tailwind CSS + shadcn/ui -- **Database**: Supabase (PostgreSQL) -- **Authentication**: Supabase Auth -- **State Management**: React Server Components + Client Components -- **Testing**: Jest + React Testing Library -- **Deployment**: Vercel - -### Documentation (`apps/docs`) - -- **Framework**: VitePress -- **Language**: TypeScript -- **Styling**: Default VitePress theme -- **Deployment**: GitHub Pages - -### Development Tools - -- **Package Manager**: pnpm with workspaces -- **Monorepo**: Turbo for build orchestration -- **Linting**: ESLint + Prettier -- **Type Checking**: TypeScript -- **Git Hooks**: Husky (optional) -- **CI/CD**: GitHub Actions - -## ๐ŸŒ Deployment - -### Production Environments - -- **Web Application**: [worksight.vercel.app](https://worksight.vercel.app) -- **Documentation**: [worksight.github.io](https://worksight.github.io) - -### Deployment Process - -1. **Automatic Deployment**: - - Push to `main` branch triggers production deployment - - Pull requests create preview deployments (web app only) - -2. **Manual Deployment**: - - ```bash - # Trigger GitHub Actions workflow - gh workflow run deploy.yml - ``` - -See [Deployment Guide](./apps/docs/guide/deployment.md) for detailed -instructions. - -## ๐Ÿงช Testing - -### Running Tests - -```bash -# All tests -pnpm test - -# Web application tests -pnpm test:web - -# Watch mode -pnpm test:watch - -# Coverage report -pnpm test:coverage -``` - -### Test Structure - -``` -src/__tests__/ -โ”œโ”€โ”€ components/ # Component tests -โ”œโ”€โ”€ integration/ # Integration tests -โ””โ”€โ”€ schemas/ # Schema validation tests -``` - -## ๐Ÿ“ Project Architecture - -### Web Application - -``` -apps/web/src/ -โ”œโ”€โ”€ app/ # Next.js App Router pages -โ”œโ”€โ”€ components/ # Reusable UI components -โ”œโ”€โ”€ lib/ # Utility functions -โ”œโ”€โ”€ hooks/ # Custom React hooks -โ”œโ”€โ”€ types/ # TypeScript definitions -โ”œโ”€โ”€ styles/ # Global styles -โ””โ”€โ”€ __tests__/ # Test files -``` - -### Key Features - -- **Dashboard**: Employee analytics and insights -- **Survey Builder**: Create and manage employee surveys -- **User Management**: Admin panel for user administration -- **Authentication**: Secure login with multiple providers -- **Reports**: Generate and view analytics reports - -## ๐Ÿ”ง Configuration - -### Environment Variables - -Web application requires these environment variables: - -```env -NEXT_PUBLIC_SUPABASE_URL=your-supabase-url -NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key -NEXT_PUBLIC_APP_NAME=WorkSight -NEXT_PUBLIC_APP_DESCRIPTION=Employee Well-being Analytics Platform -``` - -### Supabase Setup - -1. Create a new Supabase project -2. Set up authentication providers -3. Configure database tables and policies -4. Add environment variables to your deployment - -## ๐Ÿค Contributing - -### Development Workflow - -1. **Fork & Clone**: Fork the repository and clone locally -2. **Branch**: Create a feature branch from `main` -3. **Develop**: Make changes and test locally -4. **Test**: Ensure all tests pass -5. **Commit**: Use conventional commit messages -6. **Pull Request**: Submit PR with clear description - -### Code Standards - -- **TypeScript**: Strict mode enabled -- **ESLint**: Follow configured rules -- **Prettier**: Auto-format on save -- **Testing**: Write tests for new features -- **Documentation**: Update docs for user-facing changes - -### Commit Convention - +pnpm --filter @worksight/common build +pnpm --filter @worksight/api build +pnpm --filter @worksight/api test +pnpm --filter @worksight/docs build ``` -feat: add new survey analytics dashboard -fix: resolve authentication redirect issue -docs: update deployment guide -chore: upgrade dependencies -``` - -## ๐Ÿ“ Documentation - -- **User Guide**: [Apps Documentation](./apps/docs/) -- **API Reference**: [API Documentation](./apps/docs/api/) -- **Deployment**: [Deployment Guide](./apps/docs/guide/deployment.md) -- **Contributing**: [Contributing Guide](./apps/docs/guide/contributing.md) - -## ๐Ÿ› Troubleshooting - -### Common Issues - -1. **Build Failures**: - - ```bash - # Clear cache and reinstall - pnpm clean - rm -rf node_modules - pnpm install - ``` - -2. **Type Errors**: - - ```bash - # Run type checking - pnpm type-check - ``` - -3. **Test Failures**: - - ```bash - # Run tests with verbose output - pnpm test --verbose - ``` - -### Getting Help - -- **Issues**: [GitHub Issues](https://github.com/4sightorg/worksight/issues) -- **Discussions**: - [GitHub Discussions](https://github.com/4sightorg/worksight/discussions) -- **Documentation**: [Project Documentation](./apps/docs/) - -## ๐Ÿ“„ License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file -for details. - -## ๐Ÿ™ Acknowledgments - -- [Next.js](https://nextjs.org/) - React framework -- [Supabase](https://supabase.com/) - Backend as a Service -- [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS -- [shadcn/ui](https://ui.shadcn.com/) - UI component library -- [VitePress](https://vitepress.dev/) - Documentation framework -- [Turbo](https://turbo.build/) - Monorepo build system - ---- -**WorkSight** - Empowering organizations with employee well-being analytics. +## Technology stack + +| Area | Choice | +| ----------- | --------------------------------------------------- | +| Web | Next.js 15, React 19, TypeScript, Tailwind, shadcn | +| API | NestJS (`apps/api`) | +| Shared data | `@worksight/common` types + fixtures + lookup utils | +| Auth (web) | Optional Supabase Auth; offline mode supported | +| Docs | VitePress (`apps/docs`) | +| Monorepo | pnpm workspaces + Turbo | +| CI | GitHub Actions | + +## MVP data layer + +- **Web:** dashboard / admin / tasks views consume `@worksight/common` fixtures + (via a thin bridge such as `apps/web/src/lib/mvp-data.ts` on the wire-web + branch). +- **API:** Nest endpoints return the same fixture shapes (`EmployeeProfile`, + `Team`, `Assignment`, `Activity`). There is **no** DB/Supabase read path for + those endpoints yet. +- Fixture-backed routes (API): `GET /users`, `/users/:id`, `/users/stats`, + `/teams`, `/teams/:id`, `/tasks`, `/tasks/:id`, `/tasks/stats/:employeeId`, + `/activities`, plus `/`, `/ping`, `/health`. + +## Deployment + +Three Vercel projects share the same GitHub repo. Each has its own **Root +Directory** and `vercel.json` (Vercel does **not** merge a root config with +nested ones): + +| App | Package | Vercel project | Root Directory | +| ----------- | ----------------- | ---------------- | -------------- | +| `apps/web` | `@worksight/web` | `worksight` | `apps/web` | +| `apps/api` | `@worksight/api` | `worksight-api` | `apps/api` | +| `apps/docs` | `@worksight/docs` | `worksight-docs` | `apps/docs` | + +- Production branch: **`canary`** +- Install: `pnpm install --frozen-lockfile` +- Build: `pnpm turbo run build --filter=@worksight/` (Turbo builds + workspace dependencies such as `@worksight/common` first) +- Env vars live in the **Vercel dashboard**, not in committed `vercel.json` + +**API on Vercel:** the Nest `main.ts` still calls `app.listen()` โ€” there is no +serverless handler yet, so a Vercel deploy does not expose invocable functions. +Use **Docker** (`docker compose up -d --build`) for a working API today. + +- **Web Application** (`@worksight/web`): Vercel โ€” + [worksight.vercel.app](https://worksight.vercel.app) +- **Documentation** (`@worksight/docs`): GitHub Pages (optionally Vercel) +- **API** (`@worksight/api`): Docker (`docker-compose.yml` + `nginx/`) +Full setup: [doc/DEPLOYMENT.md](./doc/DEPLOYMENT.md). VitePress site: +[apps/docs](./apps/docs/). + +## MVP / handoffs + +This is a Turborepo + pnpm monorepo, so each app deploys as its **own** Vercel +project (or non-Vercel target). Vercel loads a single `vercel.json` per project +based on its dashboard **Root Directory** setting: + +- **Web** (`worksight`): Root Directory `apps/web` โ†’ `apps/web/vercel.json`. +- **API** (`worksight-api`): Root Directory `apps/api` โ†’ `apps/api/vercel.json` + (still needs a serverless handler; Docker is the working path today). +- **Docs** (`worksight-docs`): Root Directory `apps/docs` โ†’ + `apps/docs/vercel.json`. + +All three share `pnpm install --frozen-lockfile`, a +`pnpm turbo run build --filter=@worksight/` command, production branch `canary`, and +skip-unaffected-project deploys. No environment values are committed to +`vercel.json`. + +Dashboard-only steps (creating projects, setting Root Directory, adding env +vars) cannot be performed by repo files. See the +[Deployment Guide](./doc/DEPLOYMENT.md) for the full setup, including the +required Vercel dashboard configuration. + +- Plan: [docs/mvp/README.md](./docs/mvp/README.md) (epic + [#14](https://github.com/4sightorg/worksight/issues/14)) +- Handoffs: [docs/handoffs/](./docs/handoffs/) โ€” issues + [#15](https://github.com/4sightorg/worksight/issues/15)โ€“[#20](https://github.com/4sightorg/worksight/issues/20) + +## Contributing + +1. Branch from the active integration branch (MVP work stacks on + `feat/mvp-stabilize` / `canary` as directed). +2. Use conventional commits (`feat:`, `fix:`, `docs:`, โ€ฆ). +3. Keep type-check / lint green for touched packages. +4. Update docs when behavior or layout changes. + +## License + +MIT โ€” see [LICENSE](LICENSE). diff --git a/apps/api/package.json b/apps/api/package.json index 9428316..6b0b60e 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -25,7 +25,7 @@ "@nestjs/platform-express": "^11.1.6", "@nestjs/swagger": "^11.2.0", "@supabase/supabase-js": "^2.58.0", - "@worksight/common": "*", + "@worksight/common": "workspace:*", "class-transformer": "^0.5.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2" diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 693773b..8dcbeb7 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -1,12 +1,12 @@ import { Module } from '@nestjs/common'; -import { AppController } from './app.controller.js'; -import { AppService } from './app.service.js'; -// import { AttendanceModule } from './attendance/attendance.module'; -import { UsersModule } from './users/users.module.js'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { TasksModule } from './tasks/tasks.module'; +import { UsersModule } from './users/users.module'; @Module({ - imports: [UsersModule], + imports: [UsersModule, TasksModule], controllers: [AppController], providers: [AppService], }) -export class AppModule { } +export class AppModule {} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index f6b776d..a8d9a04 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,12 +1,23 @@ -import { ClassSerializerInterceptor } from '@nestjs/common'; +import { ClassSerializerInterceptor, Logger } from '@nestjs/common'; import { NestFactory, Reflector } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); + const logger = new Logger('Bootstrap'); + const port = Number(process.env.PORT ?? 3001); + const corsOrigins = (process.env.CORS_ORIGINS ?? 'http://localhost:3000') + .split(',') + .map(origin => origin.trim()) + .filter(Boolean); + + app.enableCors({ + origin: corsOrigins, + methods: ['GET', 'HEAD', 'OPTIONS'], + }); app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); - await app.listen(process.env.PORT ?? 3000); + const config = new DocumentBuilder() .setTitle('WorkSight') .setDescription('Check your tasks, manage your well-being') @@ -15,5 +26,9 @@ async function bootstrap() { .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); + + await app.listen(port); + logger.log(`API listening on http://localhost:${port} (CORS: ${corsOrigins.join(', ')})`); + logger.log('Data is fixture-backed from @worksight/common โ€” not Supabase.'); } bootstrap(); diff --git a/apps/api/src/tasks/tasks.controller.ts b/apps/api/src/tasks/tasks.controller.ts new file mode 100644 index 0000000..124314f --- /dev/null +++ b/apps/api/src/tasks/tasks.controller.ts @@ -0,0 +1,37 @@ +import { Controller, Get, NotFoundException, Param, Query } from '@nestjs/common'; +import type { Activity, Assignment } from '@worksight/common'; +import { TasksService } from './tasks.service'; + +@Controller('tasks') +export class TasksController { + constructor(private readonly tasksService: TasksService) {} + + @Get() + getAll(@Query('employee_id') employeeId?: string): Assignment[] { + return this.tasksService.findAll(employeeId); + } + + @Get('stats/:employeeId') + getStats(@Param('employeeId') employeeId: string) { + return this.tasksService.getStatsForEmployee(employeeId); + } + + @Get(':id') + getOne(@Param('id') id: string): Assignment { + const assignment = this.tasksService.findById(id); + if (!assignment) { + throw new NotFoundException(`Task ${id} not found`); + } + return assignment; + } +} + +@Controller('activities') +export class ActivitiesController { + constructor(private readonly tasksService: TasksService) {} + + @Get() + getAll(@Query('employee_id') employeeId?: string): Activity[] { + return this.tasksService.findAllActivities(employeeId); + } +} diff --git a/apps/api/src/tasks/tasks.module.ts b/apps/api/src/tasks/tasks.module.ts new file mode 100644 index 0000000..4a986cc --- /dev/null +++ b/apps/api/src/tasks/tasks.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ActivitiesController, TasksController } from './tasks.controller'; +import { TasksService } from './tasks.service'; + +@Module({ + controllers: [TasksController, ActivitiesController], + providers: [TasksService], +}) +export class TasksModule {} diff --git a/apps/api/src/tasks/tasks.service.spec.ts b/apps/api/src/tasks/tasks.service.spec.ts new file mode 100644 index 0000000..456027a --- /dev/null +++ b/apps/api/src/tasks/tasks.service.spec.ts @@ -0,0 +1,40 @@ +import { Activities, Assignments } from '@worksight/common'; +import { TasksService } from './tasks.service'; + +describe('TasksService', () => { + let service: TasksService; + + beforeEach(() => { + service = new TasksService(); + }); + + it('returns the shared assignment fixtures', () => { + expect(service.findAll()).toEqual(Assignments); + }); + + it('filters assignments by employee', () => { + const employeeId = Assignments[0].employee_id; + const expected = Assignments.filter(a => a.employee_id === employeeId); + expect(service.findAll(employeeId)).toEqual(expected); + }); + + it('finds an assignment by id', () => { + expect(service.findById(Assignments[0].id)).toEqual(Assignments[0]); + expect(service.findById('does-not-exist')).toBeNull(); + }); + + it('computes per-employee stats from the fixtures', () => { + const employeeId = Assignments[0].employee_id; + const expectedTotal = Assignments.filter(a => a.employee_id === employeeId).length; + const stats = service.getStatsForEmployee(employeeId); + expect(stats.totalTasks).toBe(expectedTotal); + expect(stats.completionRate).toBeGreaterThanOrEqual(0); + }); + + it('returns the shared activity fixtures', () => { + expect(service.findAllActivities()).toEqual(Activities); + const employeeId = Activities[0].employee_id; + const expected = Activities.filter(a => a.employee_id === employeeId); + expect(service.findAllActivities(employeeId)).toEqual(expected); + }); +}); diff --git a/apps/api/src/tasks/tasks.service.ts b/apps/api/src/tasks/tasks.service.ts new file mode 100644 index 0000000..79595d8 --- /dev/null +++ b/apps/api/src/tasks/tasks.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { Activity, ActivityLookup, Assignment, AssignmentLookup } from '@worksight/common'; + +@Injectable() +export class TasksService { + private readonly assignments = new AssignmentLookup(); + private readonly activities = new ActivityLookup(); + + findAll(employeeId?: string): Assignment[] { + if (employeeId) { + return this.assignments.getAssignmentsByEmployee(employeeId).all(); + } + return this.assignments.all(); + } + + findById(id: string): Assignment | null { + return this.assignments.filter({ id }).first(); + } + + getStatsForEmployee(employeeId: string): ReturnType { + return this.assignments.getStats(employeeId); + } + + findAllActivities(employeeId?: string): Activity[] { + if (employeeId) { + return this.activities.getActivitiesByEmployee(employeeId).all(); + } + return this.activities.all(); + } +} diff --git a/apps/api/src/users/users.controller.ts b/apps/api/src/users/users.controller.ts index f5d9f4c..9c7f4f7 100644 --- a/apps/api/src/users/users.controller.ts +++ b/apps/api/src/users/users.controller.ts @@ -1,16 +1,46 @@ -import { Controller, Get, Header, Param } from '@nestjs/common'; -import { Roles } from '@worksight/common/types'; +import { Controller, Get, NotFoundException, Param } from '@nestjs/common'; +import type { EmployeeProfile, Team } from '@worksight/common'; +import { UsersService } from './users.service'; + @Controller('users') export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Get() + getAll(): EmployeeProfile[] { + return this.usersService.findAll(); + } + + @Get('stats') + getStats() { + return this.usersService.getStats(); + } + + @Get(':id') + getOne(@Param('id') id: string): EmployeeProfile { + const employee = this.usersService.findById(id); + if (!employee) { + throw new NotFoundException(`User ${id} not found`); + } + return employee; + } +} + +@Controller('teams') +export class TeamsController { + constructor(private readonly usersService: UsersService) {} + @Get() - getAll() { - const role = Roles; - return { message: `Hello, NestJS!`, anotherMessage: `Hi, Karlo!`, test: "balls", role }; + getAll(): Team[] { + return this.usersService.findAllTeams(); } @Get(':id') - @Header('Content-Type', 'text/plain') - getOne(@Param('id') id: string) { - return `stuff ${id}`; + getOne(@Param('id') id: string): Team { + const team = this.usersService.findTeamById(id); + if (!team) { + throw new NotFoundException(`Team ${id} not found`); + } + return team; } } diff --git a/apps/api/src/users/users.module.ts b/apps/api/src/users/users.module.ts index a16f6e5..d8ec8bc 100644 --- a/apps/api/src/users/users.module.ts +++ b/apps/api/src/users/users.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; -import { UsersController } from './users.controller'; +import { TeamsController, UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ - controllers: [UsersController], + controllers: [UsersController, TeamsController], providers: [UsersService], }) -export class UsersModule { } +export class UsersModule {} diff --git a/apps/api/src/users/users.service.spec.ts b/apps/api/src/users/users.service.spec.ts new file mode 100644 index 0000000..ae13fef --- /dev/null +++ b/apps/api/src/users/users.service.spec.ts @@ -0,0 +1,36 @@ +import { Employees, Teams } from '@worksight/common'; +import { UsersService } from './users.service'; + +describe('UsersService', () => { + let service: UsersService; + + beforeEach(() => { + service = new UsersService(); + }); + + it('returns the shared employee fixtures', () => { + expect(service.findAll()).toEqual(Employees); + expect(service.findAll().length).toBeGreaterThan(0); + }); + + it('finds an employee by id', () => { + const employee = Employees[0]; + expect(service.findById(employee.id)).toEqual(employee); + }); + + it('returns null for an unknown employee id', () => { + expect(service.findById('does-not-exist')).toBeNull(); + }); + + it('computes stats over the shared fixtures', () => { + const stats = service.getStats(); + expect(stats.totalEmployees).toBe(Employees.length); + const employeeRole = stats.roles.find(r => r.role === 'employee'); + expect(employeeRole?.count).toBe(Employees.filter(e => e.role === 'employee').length); + }); + + it('returns the shared team fixtures', () => { + expect(service.findAllTeams()).toEqual(Teams); + expect(service.findTeamById(Teams[0].id)).toEqual(Teams[0]); + }); +}); diff --git a/apps/api/src/users/users.service.ts b/apps/api/src/users/users.service.ts index 7e30c6b..d1d7840 100644 --- a/apps/api/src/users/users.service.ts +++ b/apps/api/src/users/users.service.ts @@ -1,4 +1,28 @@ import { Injectable } from '@nestjs/common'; +import { EmployeeLookup, EmployeeProfile, Team, TeamLookup, Teams } from '@worksight/common'; @Injectable() -export class UsersService { } +export class UsersService { + private readonly employees = new EmployeeLookup(); + private readonly teams = new TeamLookup(Teams); + + findAll(): EmployeeProfile[] { + return this.employees.all(); + } + + findById(id: string): EmployeeProfile | null { + return this.employees.getById(id); + } + + getStats(): ReturnType { + return this.employees.getStats(); + } + + findAllTeams(): Team[] { + return this.teams.all(); + } + + findTeamById(id: string): Team | null { + return this.teams.getById(id); + } +} diff --git a/apps/api/tsconfig.build.json b/apps/api/tsconfig.build.json new file mode 100644 index 0000000..cc01185 --- /dev/null +++ b/apps/api/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*.spec.ts"] +} diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index a0bcac0..1b9034c 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -1,8 +1,9 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "module": "es2022", - "moduleResolution": "bundler", + "module": "commonjs", + "moduleResolution": "node", + "noEmit": false, "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, @@ -20,8 +21,10 @@ "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "paths": { - "@worksight/common": ["../../packages/common/src"], - "@worksight/common/*": ["../../packages/common/src/*"] + // Resolve against the built package so type-check matches what Node + // loads at runtime (requires `pnpm --filter @worksight/common build`). + "@worksight/common": ["../../packages/common/dist"], + "@worksight/common/*": ["../../packages/common/dist/*"] } }, "include": ["src/**/*"], diff --git a/apps/api/vercel.json b/apps/api/vercel.json index 7b3f518..0a14dae 100644 --- a/apps/api/vercel.json +++ b/apps/api/vercel.json @@ -1,16 +1,6 @@ { - "version": 2, - "builds": [ - { - "src": "dist/main.js", - "use": "@vercel/node" - } - ], - "routes": [ - { - "src": "/(.*)", - "dest": "dist/main.js", - "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] - } - ] + "$schema": "https://openapi.vercel.sh/vercel.json", + "installCommand": "pnpm install --frozen-lockfile", + "buildCommand": "pnpm turbo run build --filter=@worksight/api", + "outputDirectory": "dist" } diff --git a/apps/docs/.vitepress/content.ts b/apps/docs/.vitepress/content.ts index d5df237..ed1c858 100644 --- a/apps/docs/.vitepress/content.ts +++ b/apps/docs/.vitepress/content.ts @@ -4,10 +4,12 @@ const mainguide = [ { text: 'Getting Started', items: [ + { text: 'Overview', link: '/guide/overview' }, { text: 'Introduction', link: '/guide/introduction' }, { text: 'Quick Start', link: '/guide/getting-started' }, { text: 'Installation', link: '/guide/installation' }, { text: 'Configuration', link: '/guide/configuration' }, + { text: 'Deployment', link: '/guide/deployment' }, ], }, { @@ -17,6 +19,7 @@ const mainguide = [ { text: 'Burnout Assessment', link: '/features/burnout-assessment' }, { text: 'Admin Dashboard', link: '/features/admin-dashboard' }, { text: 'Reporting', link: '/features/reporting' }, + { text: 'Task Management', link: '/features/task-management' }, ], }, ]; @@ -54,7 +57,6 @@ const dev = [ items: [ { text: 'Overview', link: '/dev/api/overview' }, { text: 'Authentication', link: '/dev/api/authentication' }, - { text: 'Survey Endpoints', link: '/dev/api/survey-endpoints' }, { text: 'User Management', link: '/dev/api/user-management' }, ], }, diff --git a/apps/docs/Dockerfile b/apps/docs/Dockerfile index 3ca30f6..4f3ae52 100644 --- a/apps/docs/Dockerfile +++ b/apps/docs/Dockerfile @@ -2,27 +2,23 @@ FROM node:24-alpine AS builder WORKDIR /app -# Copy root manifests (required for monorepo install) -COPY package.json turbo.json ./ +RUN corepack enable && corepack prepare pnpm@10.33.0 --activate + +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json ./ COPY apps/docs/package.json apps/docs/ -# Install dependencies for the docs workspace -RUN npm install --workspace=@worksight/docs --legacy-peer-deps +RUN pnpm install --frozen-lockfile --filter @worksight/docs... -# Copy the rest of the project COPY . . -# Build VitePress site for production -RUN npm --workspace=@worksight/docs run build +RUN pnpm --filter @worksight/docs build # ---- Runtime stage ---- FROM nginx:mainline-alpine WORKDIR /usr/share/nginx/html -# Clean default nginx content RUN rm -rf ./* -# Copy built site COPY --from=builder /app/apps/docs/.vitepress/dist ./ EXPOSE 80 diff --git a/apps/docs/vercel.json b/apps/docs/vercel.json index abd893c..3327b5e 100644 --- a/apps/docs/vercel.json +++ b/apps/docs/vercel.json @@ -1,10 +1,7 @@ { - "version": 2, - "installCommand": "npm install", - "buildCommand": "npm run build --workspace=@worksight/docs", - "outputDirectory": ".vitepress/dist", + "$schema": "https://openapi.vercel.sh/vercel.json", "framework": "vitepress", - "build": { - "env": {} - } + "installCommand": "pnpm install --frozen-lockfile", + "buildCommand": "pnpm turbo run build --filter=@worksight/docs", + "outputDirectory": ".vitepress/dist" } diff --git a/apps/docs/website/dev/api/authentication.md b/apps/docs/website/dev/api/authentication.md index 83bb3d1..37244da 100644 --- a/apps/docs/website/dev/api/authentication.md +++ b/apps/docs/website/dev/api/authentication.md @@ -1,494 +1,35 @@ # Authentication -The WorkSight API uses API key authentication to secure access to your -organization's data and functionality. +## Nest API (`@worksight/api`) -## API Key Management +MVP fixture routes (`/users`, `/teams`, `/tasks`, `/activities`, health) do +**not** enforce API keys or Bearer tokens. There is no Admin โ€œAPI Keysโ€ UI, no +`ws_live_โ€ฆ` key format, and no hosted `api.worksight.com` auth gateway in this +repo. -### Creating API Keys +When you harden the API later, document the real mechanism here โ€” do not assume +the fictional key model from older drafts. -Generate API keys through the Admin Dashboard: +## Web app (`@worksight/web`) -1. Navigate to **Settings > API Keys** -2. Click **Create New API Key** -3. Configure permissions and rate limits -4. Save the generated key securely - -```typescript -interface ApiKey { - id: string; - name: string; - key: string; // Only shown once during creation - keyPrefix: string; // First 8 characters for identification - permissions: Permission[]; - rateLimit: RateLimit; - organizationId: string; - createdBy: string; - createdAt: Date; - lastUsed?: Date; - expiresAt?: Date; - active: boolean; -} -``` - -### API Key Format - -API keys follow a structured format: - -``` -ws_live_1234567890abcdef1234567890abcdef12345678 -โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€ Random key data (40 characters) -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Environment (live/test) -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Prefix (ws = WorkSight) -``` - -## Authentication Methods - -### Bearer Token Authentication - -Include your API key in the `Authorization` header: - -```bash -curl -H "Authorization: Bearer ws_live_1234567890abcdef..." \ - https://api.worksight.com/v1/users -``` - -```javascript -const response = await fetch('https://api.worksight.com/v1/users', { - headers: { - Authorization: 'Bearer ws_live_1234567890abcdef...', - 'Content-Type': 'application/json', - }, -}); -``` - -### Query Parameter (Not Recommended) - -For testing only, you can include the API key as a query parameter: +Optional **Supabase Auth** when offline mode is off: ```bash -curl https://api.worksight.com/v1/users?api_key=ws_live_1234567890abcdef... -``` - -::: warning Never use query parameter authentication in production. API keys in -URLs may be logged by servers, proxies, or browsers. ::: - -## Permission System - -### Permission Levels - -API keys can be configured with granular permissions: - -#### Read Permissions - -- `users:read` - View user information -- `surveys:read` - Access survey data -- `assessments:read` - View assessment results -- `reports:read` - Access reports and analytics -- `organization:read` - View organizational data - -#### Write Permissions - -- `users:write` - Create and update users -- `surveys:write` - Create and manage surveys -- `assessments:write` - Submit assessment responses -- `reports:write` - Generate custom reports -- `organization:write` - Modify organizational settings - -#### Admin Permissions - -- `users:admin` - Full user management including deletion -- `organization:admin` - Complete organizational control -- `api:admin` - Manage API keys and webhooks -- `billing:admin` - Access billing and subscription data - -### Permission Examples - -```json -{ - "name": "HR Dashboard Integration", - "permissions": [ - "users:read", - "assessments:read", - "reports:read", - "reports:write" - ] -} -``` - -```json -{ - "name": "Survey Management Bot", - "permissions": ["surveys:read", "surveys:write", "users:read"] -} -``` - -## Rate Limiting - -### Rate Limit Headers - -Every API response includes rate limit information: - -```http -HTTP/1.1 200 OK -X-RateLimit-Limit: 1000 -X-RateLimit-Remaining: 999 -X-RateLimit-Reset: 1640995200 -X-RateLimit-Window: 3600 -``` - -- `X-RateLimit-Limit`: Maximum requests allowed in the time window -- `X-RateLimit-Remaining`: Requests remaining in current window -- `X-RateLimit-Reset`: Unix timestamp when the rate limit resets -- `X-RateLimit-Window`: Time window in seconds - -### Rate Limit Tiers - -#### Standard Tier - -- 1,000 requests per hour -- 10,000 requests per day -- Suitable for small integrations - -#### Professional Tier - -- 5,000 requests per hour -- 50,000 requests per day -- Ideal for medium-scale applications - -#### Enterprise Tier - -- 10,000 requests per hour -- 100,000 requests per day -- Custom limits available - -### Handling Rate Limits - -When you exceed rate limits, you'll receive a `429 Too Many Requests` response: - -```json -{ - "success": false, - "error": { - "code": "RATE_LIMIT_EXCEEDED", - "message": "Rate limit exceeded. Try again in 3600 seconds.", - "details": { - "limit": 1000, - "window": 3600, - "retryAfter": 3600 - } - } -} -``` - -#### Best Practices for Rate Limiting - -```javascript -async function makeApiRequest(url, options) { - try { - const response = await fetch(url, options); - - if (response.status === 429) { - const retryAfter = response.headers.get('Retry-After'); - console.log(`Rate limited. Retry after ${retryAfter} seconds`); - - // Exponential backoff - await new Promise((resolve) => - setTimeout(resolve, (retryAfter || 60) * 1000) - ); - - return makeApiRequest(url, options); // Retry - } - - return response; - } catch (error) { - console.error('API request failed:', error); - throw error; - } -} +NEXT_PUBLIC_IS_OFFLINE=false +NEXT_PUBLIC_SUPABASE_URL=โ€ฆ +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=โ€ฆ ``` -## Security Best Practices - -### API Key Storage - -#### โœ… Secure Storage +For local / fixture demos: ```bash -# Environment variables -export WORKSIGHT_API_KEY="ws_live_1234567890abcdef..." - -# Configuration files (not in version control) -echo "ws_live_1234567890abcdef..." > /etc/myapp/worksight.key -chmod 600 /etc/myapp/worksight.key +NEXT_PUBLIC_IS_OFFLINE=true +IS_OFFLINE=true ``` -#### โŒ Insecure Storage - -```javascript -// Never hardcode API keys -const API_KEY = 'ws_live_1234567890abcdef...'; // BAD! - -// Never commit keys to version control -const config = { - apiKey: 'ws_live_1234567890abcdef...', // BAD! -}; -``` - -### Key Rotation - -Regularly rotate your API keys: - -1. **Generate a new key** in the Admin Dashboard -2. **Update your applications** with the new key -3. **Test thoroughly** to ensure everything works -4. **Deactivate the old key** after successful migration - -### Monitoring - -Monitor API key usage for security: - -- **Unusual traffic patterns** - Sudden spikes in requests -- **Geographic anomalies** - Requests from unexpected locations -- **Permission escalation** - Attempts to access unauthorized resources -- **Error patterns** - High rates of authentication failures - -## Environment Management - -### Test vs Production Keys - -Use different API keys for different environments: - -```typescript -const API_CONFIG = { - development: { - baseUrl: 'http://localhost:3000/api/v1', - apiKey: process.env.WORKSIGHT_TEST_API_KEY, - }, - staging: { - baseUrl: 'https://staging-api.worksight.com/v1', - apiKey: process.env.WORKSIGHT_STAGING_API_KEY, - }, - production: { - baseUrl: 'https://api.worksight.com/v1', - apiKey: process.env.WORKSIGHT_LIVE_API_KEY, - }, -}; -``` - -### Key Prefixes - -WorkSight uses key prefixes to identify environments: - -- `ws_test_` - Test/development environment -- `ws_live_` - Production environment - -## Authentication Errors - -### Common Error Responses - -#### Missing API Key - -```http -HTTP/1.1 401 Unauthorized -``` - -```json -{ - "success": false, - "error": { - "code": "AUTHENTICATION_REQUIRED", - "message": "API key is required for this endpoint" - } -} -``` - -#### Invalid API Key - -```http -HTTP/1.1 401 Unauthorized -``` - -```json -{ - "success": false, - "error": { - "code": "INVALID_API_KEY", - "message": "The provided API key is invalid or has been revoked" - } -} -``` - -#### Insufficient Permissions - -```http -HTTP/1.1 403 Forbidden -``` - -```json -{ - "success": false, - "error": { - "code": "INSUFFICIENT_PERMISSIONS", - "message": "API key does not have permission to access this resource", - "details": { - "required": "users:write", - "provided": ["users:read", "surveys:read"] - } - } -} -``` - -#### Expired API Key - -```http -HTTP/1.1 401 Unauthorized -``` - -```json -{ - "success": false, - "error": { - "code": "API_KEY_EXPIRED", - "message": "API key has expired", - "details": { - "expiredAt": "2024-01-15T00:00:00Z" - } - } -} -``` - -## Advanced Authentication - -### IP Allowlisting - -Restrict API key usage to specific IP addresses: - -```json -{ - "name": "Production Server", - "permissions": ["users:read", "assessments:write"], - "ipAllowlist": ["203.0.113.1", "203.0.113.0/24"] -} -``` - -### Webhook Authentication - -Secure webhook endpoints with signature verification: - -```javascript -const crypto = require('crypto'); - -function verifyWebhookSignature(payload, signature, secret) { - const expectedSignature = crypto - .createHmac('sha256', secret) - .update(payload) - .digest('hex'); - - return crypto.timingSafeEqual( - Buffer.from(signature, 'hex'), - Buffer.from(expectedSignature, 'hex') - ); -} - -// Express.js middleware -app.use('/webhooks/worksight', (req, res, next) => { - const signature = req.headers['x-worksight-signature']; - const isValid = verifyWebhookSignature( - req.body, - signature, - process.env.WEBHOOK_SECRET - ); - - if (!isValid) { - return res.status(401).json({ error: 'Invalid signature' }); - } - - next(); -}); -``` - -### Session-based Authentication (Web Applications) - -For web applications, use session-based authentication: - -```javascript -// Exchange API key for session token -const session = await fetch('/api/auth/session', { - method: 'POST', - headers: { - Authorization: 'Bearer ws_live_1234567890abcdef...', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - expiresIn: '1h', - }), -}); - -const { token } = await session.json(); - -// Use session token for subsequent requests -const response = await fetch('/api/users', { - headers: { - Authorization: `Bearer ${token}`, - }, -}); -``` - -## Testing Authentication - -### Test API Key - -Use the test environment to validate your authentication setup: - -```bash -curl -H "Authorization: Bearer ws_test_1234567890abcdef..." \ - https://staging-api.worksight.com/v1/auth/test -``` - -Response: - -```json -{ - "success": true, - "data": { - "apiKey": { - "id": "key_123", - "name": "Test Key", - "permissions": ["users:read", "surveys:read"], - "organization": "org_456" - }, - "user": { - "id": "user_789", - "email": "api@company.com", - "role": "api_user" - } - } -} -``` - -### Authentication Health Check - -```bash -# Check if your API key is working -curl -H "Authorization: Bearer YOUR_API_KEY" \ - https://api.worksight.com/v1/health -``` - -## Migration Guide - -### Upgrading from API v1 to v2 - -When migrating to newer API versions: - -1. **Generate new API keys** with v2 permissions -2. **Update base URLs** to include version -3. **Review breaking changes** in the changelog -4. **Test thoroughly** before switching production traffic +See `apps/web/env.example` and [Configuration](/guide/configuration). -### Deprecation Timeline +## Related -- **v1**: Supported until December 2024 -- **v2**: Current stable version -- **v3**: Beta, general availability Q2 2024 +- [API overview](./overview.md) +- [User management](./user-management.md) diff --git a/apps/docs/website/dev/api/overview.md b/apps/docs/website/dev/api/overview.md index f38fdd6..3cb3269 100644 --- a/apps/docs/website/dev/api/overview.md +++ b/apps/docs/website/dev/api/overview.md @@ -1,556 +1,79 @@ # API Overview -The WorkSight API provides comprehensive access to all platform features through -RESTful endpoints, enabling seamless integration with your existing systems and -custom applications. +The NestJS app in `apps/api` (`@worksight/api`) exposes HTTP endpoints for the +MVP. Responses use shapes from `@worksight/common` and are **fixture-backed** โ€” +not loaded from Supabase or another database yet. -## Getting Started +## Base URL -### Base URL +| Environment | URL | +| ----------- | --------------------------------------------------------------------------- | +| Local | `http://localhost:3123` (or your `PORT`) | +| Docker | per `docker-compose.yml` / nginx | +| Vercel | project `worksight-api` builds `dist/` only โ€” **no serverless handler yet** | -``` -Production: https://api.worksight.com/v1 -Staging: https://staging-api.worksight.com/v1 -Development: http://localhost:3000/api/v1 -``` - -### Authentication - -WorkSight API uses API keys for authentication. Include your API key in the -`Authorization` header: - -```bash -curl -H "Authorization: Bearer YOUR_API_KEY" \ - https://api.worksight.com/v1/users -``` - -### Quick Start - -1. **Get your API key** from the Admin Dashboard -2. **Make your first request** to test connectivity -3. **Explore the endpoints** using our interactive documentation -4. **Integrate** with your applications - -```javascript -// Example: Get organization health metrics -const response = await fetch('https://api.worksight.com/v1/metrics/health', { - headers: { - Authorization: 'Bearer YOUR_API_KEY', - 'Content-Type': 'application/json', - }, -}); - -const healthData = await response.json(); -console.log('Organization health score:', healthData.overallScore); -``` - -## API Design Principles - -### RESTful Architecture - -- **Resource-based URLs**: Each endpoint represents a specific resource -- **HTTP Methods**: GET, POST, PUT, PATCH, DELETE for different operations -- **Status Codes**: Standard HTTP status codes for responses -- **JSON Format**: All requests and responses use JSON - -### Consistency - -- **Naming Conventions**: Consistent parameter and field naming -- **Response Structure**: Standardized response format across all endpoints -- **Error Handling**: Uniform error response structure -- **Versioning**: Clear API versioning strategy - -## Authentication & Authorization - -### API Key Management - -```typescript -interface ApiKey { - id: string; - name: string; - key: string; - permissions: Permission[]; - rateLimit: { - requestsPerMinute: number; - requestsPerDay: number; - }; - expiresAt?: Date; - lastUsed?: Date; - active: boolean; -} -``` - -### Permission Levels - -#### Read-Only Access - -- View users and organizational data -- Access reports and analytics -- Retrieve survey responses (with appropriate permissions) - -#### Read-Write Access - -- Create and update users -- Manage surveys and assessments -- Modify organizational settings - -#### Admin Access - -- Full API access -- User management -- System configuration -- Billing and subscription management - -### Rate Limiting - -```http -X-RateLimit-Limit: 1000 -X-RateLimit-Remaining: 999 -X-RateLimit-Reset: 1640995200 -``` - -Default rate limits: - -- **Standard Plan**: 1,000 requests per hour -- **Professional Plan**: 5,000 requests per hour -- **Enterprise Plan**: 10,000 requests per hour - -## Core Resources - -### Users - -Manage user accounts, profiles, and organizational relationships. - -```typescript -interface User { - id: string; - email: string; - firstName: string; - lastName: string; - role: UserRole; - department: string; - manager?: string; - startDate: Date; - status: 'active' | 'inactive' | 'pending'; - lastLogin?: Date; - createdAt: Date; - updatedAt: Date; -} -``` - -**Endpoints:** - -- `GET /users` - List all users -- `GET /users/{id}` - Get specific user -- `POST /users` - Create new user -- `PUT /users/{id}` - Update user -- `DELETE /users/{id}` - Deactivate user - -### Surveys - -Create, manage, and analyze survey data. - -```typescript -interface Survey { - id: string; - title: string; - description: string; - type: 'burnout' | 'engagement' | 'custom'; - questions: Question[]; - schedule: Schedule; - status: 'draft' | 'active' | 'completed' | 'archived'; - participantCount: number; - responseCount: number; - createdAt: Date; - updatedAt: Date; -} -``` - -**Endpoints:** - -- `GET /surveys` - List surveys -- `POST /surveys` - Create survey -- `PUT /surveys/{id}` - Update survey -- `POST /surveys/{id}/send` - Send survey to participants -- `GET /surveys/{id}/responses` - Get survey responses - -### Assessments - -Burnout assessments and well-being evaluations. - -```typescript -interface Assessment { - id: string; - userId: string; - surveyId: string; - responses: Record; - score: number; - riskLevel: 'low' | 'moderate' | 'high' | 'critical'; - completedAt: Date; - analysis: { - emotionalExhaustion: number; - depersonalization: number; - personalAccomplishment: number; - workLifeBalance: number; - }; -} -``` - -**Endpoints:** - -- `GET /assessments` - List assessments -- `POST /assessments` - Submit assessment -- `GET /assessments/{id}` - Get assessment details -- `GET /users/{userId}/assessments` - Get user's assessments - -### Reports - -Access analytics and reporting data. - -```typescript -interface Report { - id: string; - type: 'individual' | 'team' | 'organization'; - title: string; - data: any; - generatedAt: Date; - period: { - startDate: Date; - endDate: Date; - }; - format: 'json' | 'pdf' | 'excel'; -} -``` - -**Endpoints:** - -- `GET /reports` - List available reports -- `POST /reports/generate` - Generate custom report -- `GET /reports/{id}` - Get report data -- `GET /reports/{id}/download` - Download report file - -## Request/Response Format - -### Standard Response Structure - -```typescript -interface ApiResponse { - success: boolean; - data?: T; - error?: { - code: string; - message: string; - details?: any; - }; - pagination?: { - page: number; - limit: number; - total: number; - totalPages: number; - }; - meta?: { - requestId: string; - timestamp: Date; - version: string; - }; -} -``` - -### Success Response Example - -```json -{ - "success": true, - "data": { - "id": "user_123", - "email": "john.doe@company.com", - "firstName": "John", - "lastName": "Doe", - "role": "employee" - }, - "meta": { - "requestId": "req_abc123", - "timestamp": "2024-01-15T10:30:00Z", - "version": "1.0" - } -} -``` - -### Error Response Example - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "Invalid email format", - "details": { - "field": "email", - "value": "invalid-email", - "constraint": "Must be a valid email address" - } - }, - "meta": { - "requestId": "req_xyz789", - "timestamp": "2024-01-15T10:30:00Z", - "version": "1.0" - } -} -``` - -## Pagination - -### Query Parameters - -``` -GET /users?page=1&limit=20&sort=createdAt&order=desc -``` - -- `page`: Page number (default: 1) -- `limit`: Items per page (default: 20, max: 100) -- `sort`: Sort field -- `order`: Sort direction (asc/desc) - -### Response - -```json -{ - "success": true, - "data": [...], - "pagination": { - "page": 1, - "limit": 20, - "total": 150, - "totalPages": 8, - "hasNext": true, - "hasPrev": false - } -} -``` - -## Filtering and Search - -### Query Filters - -``` -GET /users?department=Engineering&role=employee&status=active -``` - -### Search - -``` -GET /users?search=john&fields=firstName,lastName,email -``` - -### Date Ranges - -``` -GET /assessments?startDate=2024-01-01&endDate=2024-01-31 -``` - -## Webhooks +Default Nest listen port is `process.env.PORT ?? 3000`. Prefer a non-3000 port +when `@worksight/web` is already running. -### Event Types +## Authentication -```typescript -type WebhookEvent = - | 'user.created' - | 'user.updated' - | 'user.deactivated' - | 'survey.created' - | 'survey.completed' - | 'assessment.submitted' - | 'assessment.high_risk' - | 'report.generated'; -``` +MVP fixture routes do **not** require API keys. Do not assume production API-key +auth, rate-limit headers, or hosted `api.worksight.com` SDKs โ€” those are not +shipped. -### Webhook Payload - -```json -{ - "event": "assessment.high_risk", - "timestamp": "2024-01-15T10:30:00Z", - "data": { - "assessmentId": "assess_123", - "userId": "user_456", - "riskLevel": "high", - "score": 75, - "previousScore": 45 - }, - "organization": { - "id": "org_789", - "name": "Acme Corp" - } -} -``` - -### Configuration - -```bash -curl -X POST https://api.worksight.com/v1/webhooks \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "url": "https://your-app.com/webhooks/worksight", - "events": ["assessment.high_risk", "survey.completed"], - "secret": "your-webhook-secret" - }' -``` +Web auth (optional Supabase) is separate from this Nest surface. -## SDKs and Libraries +## Endpoints (MVP) -### Official SDKs +### Health -#### JavaScript/TypeScript +| Method | Path | Notes | +| ------ | --------- | ------------ | +| `GET` | `/` | App root | +| `GET` | `/ping` | Liveness | +| `GET` | `/health` | Health check | -```bash -npm install apps/api-client -``` +### Users & teams -```javascript -import { WorkSightAPI } from 'apps/api-client'; +| Method | Path | Returns | +| ------ | -------------- | ------------------------------- | +| `GET` | `/users` | `EmployeeProfile[]` fixtures | +| `GET` | `/users/stats` | Aggregate role/department stats | +| `GET` | `/users/:id` | One profile (404 if missing) | +| `GET` | `/teams` | `Team[]` | +| `GET` | `/teams/:id` | One team | -const api = new WorkSightAPI({ - apiKey: 'your-api-key', - baseUrl: 'https://api.worksight.com/v1', -}); +### Tasks & activities -// Get organization health -const health = await api.metrics.getHealth(); -``` +| Method | Path | Returns | +| ------ | -------------------------- | --------------------------------------- | +| `GET` | `/tasks` | `Assignment[]` (`?employee_id=` filter) | +| `GET` | `/tasks/:id` | One assignment | +| `GET` | `/tasks/stats/:employeeId` | Per-employee task / balance stats | +| `GET` | `/activities` | `Activity[]` (optional `?employee_id=`) | -#### Python +## Quick smoke test ```bash -pip install worksight-api -``` - -```python -from worksight import WorkSightAPI +pnpm --filter @worksight/common build +pnpm --filter @worksight/api build +PORT=3123 node apps/api/dist/main.js -api = WorkSightAPI(api_key='your-api-key') - -# Get all users -users = api.users.list() +curl -s http://localhost:3123/users | head +curl -s http://localhost:3123/tasks +curl -s http://localhost:3123/health ``` -#### PHP - -```bash -composer require worksight/api-client -``` - -```php -use WorkSight\ApiClient; - -$api = new ApiClient('your-api-key'); - -// Create new survey -$survey = $api->surveys->create([ - 'title' => 'Weekly Check-in', - 'type' => 'burnout' -]); -``` - -## Error Codes - -### HTTP Status Codes - -- `200` - OK -- `201` - Created -- `204` - No Content -- `400` - Bad Request -- `401` - Unauthorized -- `403` - Forbidden -- `404` - Not Found -- `409` - Conflict -- `422` - Unprocessable Entity -- `429` - Too Many Requests -- `500` - Internal Server Error - -### Application Error Codes - -```typescript -enum ErrorCode { - VALIDATION_ERROR = 'VALIDATION_ERROR', - AUTHENTICATION_FAILED = 'AUTHENTICATION_FAILED', - INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS', - RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND', - DUPLICATE_RESOURCE = 'DUPLICATE_RESOURCE', - RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED', - EXTERNAL_SERVICE_ERROR = 'EXTERNAL_SERVICE_ERROR', - MAINTENANCE_MODE = 'MAINTENANCE_MODE', -} -``` - -## Best Practices - -### API Usage - -1. **Use appropriate HTTP methods** for different operations -2. **Handle rate limits** gracefully with exponential backoff -3. **Validate input data** before sending requests -4. **Cache responses** when appropriate -5. **Use webhooks** for real-time updates instead of polling - -### Security - -1. **Store API keys securely** - never expose in client-side code -2. **Use HTTPS** for all requests -3. **Validate webhook signatures** to ensure authenticity -4. **Implement proper error handling** without exposing sensitive data -5. **Monitor API usage** for unusual patterns - -### Performance - -1. **Use pagination** for large result sets -2. **Request only needed fields** using field selection -3. **Batch operations** when possible -4. **Implement caching** for frequently accessed data -5. **Use compression** for large payloads - -## Testing - -### Test Environment - -Use the staging environment for testing: - -``` -https://staging-api.worksight.com/v1 -``` - -### Postman Collection - -Download our Postman collection for easy testing: - -``` -https://api.worksight.com/docs/postman.json -``` - -### Sample Data - -The staging environment includes sample data for testing: - -- 50 test users across 5 departments -- Historical survey responses -- Generated reports and analytics - -## Support - -### Documentation - -- **Interactive API Docs**: -- **Changelog**: -- **Status Page**: - -### Contact +## Not available yet -- **Email**: -- **Slack**: #api-support in our community Slack -- **GitHub**: +- POST/PUT/DELETE mutations for users/tasks +- Survey / burnout / attendance HTTP modules (types may exist in common) +- Supabase-backed persistence for these routes +- Serverless Vercel entrypoint +- Official SDKs, webhooks, or Postman collections at `api.worksight.com` -### SLA +## Related -- **Uptime**: 99.9% guaranteed -- **Response Time**: < 200ms average -- **Support Response**: < 24 hours for standard plans, < 4 hours for enterprise +- [User management](./user-management.md) โ€” users/teams detail +- [Authentication](./authentication.md) โ€” web/API auth status +- Repo deployment notes: [Deployment guide](/guide/deployment) diff --git a/apps/docs/website/dev/api/user-management.md b/apps/docs/website/dev/api/user-management.md index 5b61af0..64d3332 100644 --- a/apps/docs/website/dev/api/user-management.md +++ b/apps/docs/website/dev/api/user-management.md @@ -1,838 +1,58 @@ -# User Management +# User Management (API) -The User Management API allows you to programmatically manage users within your -WorkSight organization, including creating accounts, updating profiles, managing -roles, and handling user lifecycle events. +MVP Nest routes for employees and teams. All data comes from `@worksight/common` +fixtures (`EmployeeProfile`, `Team`). -## Core User Object - -```typescript -interface User { - id: string; // Unique user identifier - email: string; // Primary email address - firstName: string; // User's first name - lastName: string; // User's last name - displayName?: string; // Optional display name - avatar?: string; // Profile image URL - role: UserRole; // User's role in the organization - department?: string; // Department or team - jobTitle?: string; // Job title - employeeId?: string; // Employee identifier - startDate?: Date; // Employment start date - status: UserStatus; // Account status - preferences: UserPreferences; // User settings and preferences - metadata: Record; // Custom fields - createdAt: Date; // Account creation timestamp - updatedAt: Date; // Last modification timestamp - lastLoginAt?: Date; // Last successful login - organizationId: string; // Organization identifier -} - -type UserRole = 'admin' | 'manager' | 'employee' | 'contractor' | 'viewer'; - -type UserStatus = 'active' | 'inactive' | 'suspended' | 'pending_invitation'; - -interface UserPreferences { - language: string; // Preferred language (ISO 639-1) - timezone: string; // Timezone (IANA format) - emailNotifications: boolean; // Email notification preference - theme: 'light' | 'dark' | 'auto'; - dashboardLayout: string[]; // Preferred dashboard widgets -} -``` - -## List Users - -Retrieve a paginated list of users in your organization. - -### Request - -```http -GET /api/v1/users -``` - -### Query Parameters - -| Parameter | Type | Description | Default | -| ------------ | ------- | ------------------------------------------ | -------- | -| `page` | integer | Page number (1-based) | `1` | -| `limit` | integer | Items per page (1-100) | `20` | -| `search` | string | Search term (name, email) | - | -| `role` | string | Filter by user role | - | -| `status` | string | Filter by user status | `active` | -| `department` | string | Filter by department | - | -| `sort` | string | Sort field (`name`, `email`, `created_at`) | `name` | -| `order` | string | Sort order (`asc`, `desc`) | `asc` | - -### Example Request - -```bash -curl -H "Authorization: Bearer YOUR_API_KEY" \ - "https://api.worksight.com/v1/users?page=1&limit=10&role=employee&status=active" -``` - -### Response - -```json -{ - "success": true, - "data": { - "users": [ - { - "id": "user_123", - "email": "john.doe@company.com", - "firstName": "John", - "lastName": "Doe", - "displayName": "John D.", - "role": "employee", - "department": "Engineering", - "jobTitle": "Software Engineer", - "status": "active", - "preferences": { - "language": "en", - "timezone": "America/New_York", - "emailNotifications": true, - "theme": "dark" - }, - "createdAt": "2024-01-15T10:30:00Z", - "updatedAt": "2024-01-20T14:22:00Z", - "lastLoginAt": "2024-01-22T09:15:00Z" - } - ], - "pagination": { - "page": 1, - "limit": 10, - "total": 156, - "totalPages": 16, - "hasNext": true, - "hasPrev": false - } - } -} -``` - -## Get User - -Retrieve detailed information about a specific user. - -### Request - -```http -GET /api/v1/users/{userId} -``` - -### Path Parameters - -| Parameter | Type | Description | -| --------- | ------ | ------------------------ | -| `userId` | string | User ID or email address | - -### Example Request - -```bash -curl -H "Authorization: Bearer YOUR_API_KEY" \ - https://api.worksight.com/v1/users/user_123 -``` - -### Response - -```json -{ - "success": true, - "data": { - "user": { - "id": "user_123", - "email": "john.doe@company.com", - "firstName": "John", - "lastName": "Doe", - "displayName": "John D.", - "avatar": "https://cdn.worksight.com/avatars/user_123.jpg", - "role": "employee", - "department": "Engineering", - "jobTitle": "Software Engineer", - "employeeId": "ENG-001", - "startDate": "2023-06-01T00:00:00Z", - "status": "active", - "preferences": { - "language": "en", - "timezone": "America/New_York", - "emailNotifications": true, - "theme": "dark", - "dashboardLayout": ["burnout-score", "recent-surveys", "team-insights"] - }, - "metadata": { - "slackId": "U01234567", - "team": "Platform Team", - "level": "Senior" - }, - "createdAt": "2023-05-15T10:30:00Z", - "updatedAt": "2024-01-20T14:22:00Z", - "lastLoginAt": "2024-01-22T09:15:00Z", - "organizationId": "org_456" - }, - "stats": { - "surveysCompleted": 12, - "assessmentScore": 7.2, - "lastAssessmentAt": "2024-01-18T16:30:00Z", - "streakDays": 5 - } - } -} -``` - -## Create User - -Create a new user account in your organization. - -### Request - -```http -POST /api/v1/users -``` - -### Request Body - -```json -{ - "email": "jane.smith@company.com", - "firstName": "Jane", - "lastName": "Smith", - "role": "employee", - "department": "Marketing", - "jobTitle": "Marketing Manager", - "employeeId": "MKT-003", - "startDate": "2024-02-01", - "sendInvitation": true, - "preferences": { - "language": "en", - "timezone": "America/Los_Angeles" - }, - "metadata": { - "team": "Growth Team", - "level": "Manager" - } -} -``` - -### Required Fields - -- `email` - Must be unique within the organization -- `firstName` - User's first name -- `lastName` - User's last name -- `role` - User role in the organization - -### Example Request - -```bash -curl -X POST \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "email": "jane.smith@company.com", - "firstName": "Jane", - "lastName": "Smith", - "role": "employee", - "department": "Marketing", - "sendInvitation": true - }' \ - https://api.worksight.com/v1/users -``` - -### Response - -```json -{ - "success": true, - "data": { - "user": { - "id": "user_456", - "email": "jane.smith@company.com", - "firstName": "Jane", - "lastName": "Smith", - "role": "employee", - "department": "Marketing", - "status": "pending_invitation", - "createdAt": "2024-01-23T10:30:00Z", - "organizationId": "org_456" - }, - "invitation": { - "id": "inv_789", - "token": "inv_tok_abcdef123456", - "expiresAt": "2024-01-30T10:30:00Z", - "inviteUrl": "https://app.worksight.com/invite/inv_tok_abcdef123456" - } - } -} -``` - -## Update User - -Update an existing user's information. - -### Request - -```http -PUT /api/v1/users/{userId} -``` - -### Request Body - -```json -{ - "firstName": "Jane", - "lastName": "Smith-Johnson", - "department": "Product Marketing", - "jobTitle": "Senior Marketing Manager", - "preferences": { - "theme": "light", - "emailNotifications": false - }, - "metadata": { - "team": "Growth Team", - "level": "Senior Manager" - } -} -``` - -### Example Request - -```bash -curl -X PUT \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "department": "Product Marketing", - "jobTitle": "Senior Marketing Manager" - }' \ - https://api.worksight.com/v1/users/user_456 -``` - -### Response - -```json -{ - "success": true, - "data": { - "user": { - "id": "user_456", - "email": "jane.smith@company.com", - "firstName": "Jane", - "lastName": "Smith-Johnson", - "department": "Product Marketing", - "jobTitle": "Senior Marketing Manager", - "status": "active", - "updatedAt": "2024-01-23T15:45:00Z" - } - } -} -``` - -## Update User Role - -Change a user's role within the organization. - -### Request - -```http -PATCH /api/v1/users/{userId}/role -``` - -### Request Body - -```json -{ - "role": "manager", - "reason": "Promotion to team lead position" -} -``` - -### Available Roles - -- `admin` - Full system access -- `manager` - Team management and reporting -- `employee` - Standard user access -- `contractor` - Limited access for contractors -- `viewer` - Read-only access - -### Example Request - -```bash -curl -X PATCH \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "role": "manager", - "reason": "Promotion to team lead position" - }' \ - https://api.worksight.com/v1/users/user_456/role -``` - -### Response - -```json -{ - "success": true, - "data": { - "user": { - "id": "user_456", - "role": "manager", - "updatedAt": "2024-01-23T16:00:00Z" - }, - "roleChange": { - "previousRole": "employee", - "newRole": "manager", - "changedBy": "user_789", - "reason": "Promotion to team lead position", - "changedAt": "2024-01-23T16:00:00Z" - } - } -} -``` - -## Update User Status - -Activate, deactivate, or suspend a user account. - -### Request - -```http -PATCH /api/v1/users/{userId}/status -``` - -### Request Body - -```json -{ - "status": "suspended", - "reason": "Policy violation", - "notifyUser": false -} -``` - -### Available Statuses - -- `active` - User can access the system -- `inactive` - User account is disabled but can be reactivated -- `suspended` - User account is temporarily suspended -- `pending_invitation` - User has been invited but hasn't activated their - account - -### Example Request - -```bash -curl -X PATCH \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "status": "inactive", - "reason": "Employee left the company", - "notifyUser": false - }' \ - https://api.worksight.com/v1/users/user_456/status -``` - -### Response - -```json -{ - "success": true, - "data": { - "user": { - "id": "user_456", - "status": "inactive", - "updatedAt": "2024-01-23T17:00:00Z" - }, - "statusChange": { - "previousStatus": "active", - "newStatus": "inactive", - "changedBy": "user_789", - "reason": "Employee left the company", - "changedAt": "2024-01-23T17:00:00Z" - } - } -} -``` - -## Delete User - -Permanently delete a user account and all associated data. - -### Request +## List employees ```http -DELETE /api/v1/users/{userId} +GET /users ``` -### Query Parameters - -| Parameter | Type | Description | Default | -| ------------------ | ------- | --------------------------- | ------- | -| `transfer_data_to` | string | User ID to transfer data to | - | -| `confirm` | boolean | Confirmation flag | `false` | - -### Example Request +Returns `EmployeeProfile[]` from the shared fixtures. ```bash -curl -X DELETE \ - -H "Authorization: Bearer YOUR_API_KEY" \ - "https://api.worksight.com/v1/users/user_456?confirm=true" -``` - -### Response - -```json -{ - "success": true, - "data": { - "deleted": true, - "userId": "user_456", - "deletedAt": "2024-01-23T18:00:00Z", - "dataRetention": { - "surveysTransferred": 5, - "reportsArchived": 2, - "transferredTo": null - } - } -} -``` - -::: warning Data Deletion User deletion is permanent and cannot be undone. -Consider deactivating users instead of deleting them to preserve historical data -and analytics. ::: - -## Bulk Operations - -### Bulk Create Users - -Create multiple users in a single request. - -```http -POST /api/v1/users/bulk -``` - -```json -{ - "users": [ - { - "email": "user1@company.com", - "firstName": "User", - "lastName": "One", - "role": "employee", - "department": "Engineering" - }, - { - "email": "user2@company.com", - "firstName": "User", - "lastName": "Two", - "role": "employee", - "department": "Design" - } - ], - "sendInvitations": true -} -``` - -### Bulk Update Users - -Update multiple users at once. - -```http -PATCH /api/v1/users/bulk -``` - -```json -{ - "userIds": ["user_123", "user_456", "user_789"], - "updates": { - "department": "New Department", - "metadata": { - "migrated": true - } - } -} -``` - -## User Invitations - -### Send Invitation - -Send an invitation to an existing user or create a new user with invitation. - -```http -POST /api/v1/users/{userId}/invite -``` - -```json -{ - "message": "Welcome to our WorkSight organization!", - "expiresIn": "7d" -} -``` - -### Resend Invitation - -Resend an invitation to a user with pending status. - -```http -POST /api/v1/users/{userId}/invite/resend -``` - -### Cancel Invitation - -Cancel a pending invitation. - -```http -DELETE /api/v1/users/{userId}/invite +curl http://localhost:3123/users ``` -## User Search - -### Advanced Search - -Search users with complex filters. +## Get one employee ```http -POST /api/v1/users/search -``` - -```json -{ - "query": "engineering", - "filters": { - "roles": ["employee", "manager"], - "departments": ["Engineering", "Product"], - "status": ["active"], - "startDateRange": { - "from": "2023-01-01", - "to": "2023-12-31" - } - }, - "sort": { - "field": "lastLoginAt", - "order": "desc" - }, - "page": 1, - "limit": 20 -} +GET /users/:id ``` -## User Statistics +Returns a single `EmployeeProfile`, or **404** when the id is not in fixtures. -### Get User Statistics - -Retrieve usage and engagement statistics for a user. +## Employee stats ```http -GET /api/v1/users/{userId}/stats -``` - -```json -{ - "success": true, - "data": { - "engagement": { - "surveysCompleted": 15, - "averageCompletionTime": 180, - "lastSurveyAt": "2024-01-20T10:30:00Z", - "streakDays": 7 - }, - "wellbeing": { - "currentScore": 7.2, - "averageScore": 6.8, - "trend": "improving", - "lastAssessmentAt": "2024-01-22T14:00:00Z" - }, - "activity": { - "loginCount": 45, - "lastLoginAt": "2024-01-22T09:15:00Z", - "sessionDuration": 1200, - "pageViews": 156 - } - } -} +GET /users/stats ``` -## User Groups +Aggregate counts (totals, roles, departments) derived from the same fixture set. -### Get User Groups - -Retrieve groups that a user belongs to. +## Teams ```http -GET /api/v1/users/{userId}/groups +GET /teams +GET /teams/:id ``` -### Add User to Group - -Add a user to a specific group. - -```http -POST /api/v1/users/{userId}/groups/{groupId} -``` +Returns `Team[]` / one `Team` from `@worksight/common`. -### Remove User from Group +## Shape source of truth -Remove a user from a group. +Prefer TypeScript types from `@worksight/common/types` over copy-pasted +interfaces in docs. The Nest controllers/services map lookup helpers from +`@worksight/common/utils` onto those types. -```http -DELETE /api/v1/users/{userId}/groups/{groupId} -``` - -## Error Handling - -### Common Errors - -#### User Not Found - -```json -{ - "success": false, - "error": { - "code": "USER_NOT_FOUND", - "message": "User with ID 'user_999' not found" - } -} -``` - -#### Email Already Exists - -```json -{ - "success": false, - "error": { - "code": "EMAIL_ALREADY_EXISTS", - "message": "A user with email 'john@company.com' already exists", - "details": { - "existingUserId": "user_123" - } - } -} -``` - -#### Invalid Role - -```json -{ - "success": false, - "error": { - "code": "INVALID_ROLE", - "message": "Role 'super_admin' is not valid for this organization", - "details": { - "validRoles": ["admin", "manager", "employee", "contractor", "viewer"] - } - } -} -``` +## Not implemented -#### Insufficient Permissions +- Create / update / delete users +- Invitations, suspension, password reset via this API +- Pagination query params (`page`, `limit`) on `/users` +- API-key `Authorization` headers +- Persistence in Supabase -```json -{ - "success": false, - "error": { - "code": "INSUFFICIENT_PERMISSIONS", - "message": "Cannot modify user with higher privileges", - "details": { - "requiredRole": "admin", - "currentRole": "manager" - } - } -} -``` - -## Best Practices - -### User Management - -1. **Email Validation**: Always validate email addresses before creating users -2. **Role Assignment**: Use the principle of least privilege when assigning - roles -3. **Bulk Operations**: Use bulk endpoints for large user imports/updates -4. **Data Transfer**: When deleting users, consider transferring their data to - another user -5. **Audit Trail**: Keep track of user changes for compliance and debugging - -### Performance Optimization - -```javascript -// Batch user operations when possible -const users = await Promise.all([ - worksight.users.get('user_1'), - worksight.users.get('user_2'), - worksight.users.get('user_3'), -]); - -// Better: Use bulk endpoint -const users = await worksight.users.getBulk(['user_1', 'user_2', 'user_3']); -``` - -### Security Considerations - -1. **Sensitive Data**: Never store sensitive information in user metadata -2. **Access Control**: Implement proper role-based access control -3. **Data Retention**: Follow data retention policies when deleting users -4. **Audit Logging**: Log all user management operations - -## SDK Examples - -### JavaScript/TypeScript - -```typescript -import { WorkSight } from 'apps/sdk'; - -const worksight = new WorkSight({ - apiKey: process.env.WORKSIGHT_API_KEY, -}); - -// Create a new user -const newUser = await worksight.users.create({ - email: 'john.doe@company.com', - firstName: 'John', - lastName: 'Doe', - role: 'employee', - department: 'Engineering', - sendInvitation: true, -}); - -// Update user preferences -await worksight.users.update('user_123', { - preferences: { - theme: 'dark', - emailNotifications: false, - }, -}); - -// Search users -const engineers = await worksight.users.search({ - query: 'engineering', - filters: { - departments: ['Engineering'], - roles: ['employee', 'manager'], - }, -}); -``` - -### Python - -```python -from worksight import WorkSight - -worksight = WorkSight(api_key=os.environ['WORKSIGHT_API_KEY']) - -# Create a new user -new_user = worksight.users.create( - email='john.doe@company.com', - first_name='John', - last_name='Doe', - role='employee', - department='Engineering', - send_invitation=True -) - -# Update user status -worksight.users.update_status( - user_id='user_123', - status='inactive', - reason='Employee left the company' -) -``` +For tasks tied to employees, see [API overview](./overview.md) (`/tasks`, +`/activities`). diff --git a/apps/docs/website/dev/nextjs.md b/apps/docs/website/dev/nextjs.md index a5d4e80..7933b85 100644 --- a/apps/docs/website/dev/nextjs.md +++ b/apps/docs/website/dev/nextjs.md @@ -1,39 +1,43 @@ -# Next.js Guide +# Next.js (web app) -## What is Next.js? +The web package is **`@worksight/web`** under `apps/web`, using the **App +Router** on **Next.js 15** (React 19). -Next.js is a React framework for building full-stack web apps with server-side -rendering, API routes, and more. +## Layout -## How to Use +```text +apps/web/src/ +โ”œโ”€โ”€ app/ # App Router routes +โ”œโ”€โ”€ components/ # UI +โ”œโ”€โ”€ lib/ # helpers (incl. MVP common bridges when wired) +โ”œโ”€โ”€ auth/ # auth helpers +โ””โ”€โ”€ __tests__/ # Jest tests (excluded from app type-check) +``` -1. **Pages and Routing** - - Files in `pages/` become routes automatically. - - Example: `pages/about.tsx` โ†’ `/about` +## Data for MVP -2. **API Routes** - - Place serverless functions in `pages/api/`. - - Example: +Dashboard / admin / tasks views should consume `@worksight/common` fixtures via +a thin bridge (e.g. `src/lib/mvp-data.ts` on the wire-web workstream). That is +**not** the same as calling Nest or Supabase for those lists yet. - ```typescript - // pages/api/hello.ts - export default function handler(req, res) { - res.status(200).json({ message: 'Hello World' }); - } - ``` +## Dev / build -3. **Data Fetching** - - Use `getServerSideProps`, `getStaticProps`, or React Server Components for - data fetching. +```bash +pnpm --filter @worksight/common build +pnpm --filter @worksight/web dev +pnpm --filter @worksight/web build +pnpm --filter @worksight/web type-check +``` -4. **Styling** - - Use Tailwind CSS, CSS Modules, or any CSS-in-JS solution. +Or from the repo root: `pnpm dev:web` / `pnpm build:web`. -5. **Deployment** - - Deploy easily to Vercel or any Node.js host. +## Deploy -## Tips +Vercel project **`worksight`**, Root Directory **`apps/web`**, config +`apps/web/vercel.json`. See [Deployment](/guide/deployment). -- Use the App Router (`app/`) for new features (Next.js 13+). -- Leverage API routes for backend logic. -- Use environment variables for secrets. +## Notes + +- Prefer App Router (`app/`), not the legacy `pages/` router. +- Backend HTTP for the monorepo lives in Nest (`apps/api`), not Next `pages/api` + route handlers as the primary API. diff --git a/apps/docs/website/dev/overview.md b/apps/docs/website/dev/overview.md index e69de29..9462241 100644 --- a/apps/docs/website/dev/overview.md +++ b/apps/docs/website/dev/overview.md @@ -0,0 +1,38 @@ +# Developer overview + +Contributor-oriented notes for the WorkSight monorepo. + +## Packages + +| Package | Path | Role | +| ------------------- | ----------------- | ------------------------ | +| `@worksight/web` | `apps/web` | Next.js 15 UI | +| `@worksight/api` | `apps/api` | NestJS API | +| `@worksight/docs` | `apps/docs` | VitePress | +| `@worksight/common` | `packages/common` | Types, fixtures, lookups | +| `@worksight/assets` | `packages/assets` | Shared assets | + +## Commands + +```bash +pnpm install +pnpm --filter @worksight/common build +pnpm type-check +pnpm lint +pnpm --filter @worksight/web dev +PORT=3123 pnpm --filter @worksight/api dev +pnpm --filter @worksight/docs build +``` + +## Docs map + +- Stack notes: [Next.js](./nextjs.md), [VitePress](./vitepress.md), + [Jest](./jest.md), [Supabase](./supabase.md) +- API: [Overview](./api/overview.md), [Users](./api/user-management.md), + [Auth status](./api/authentication.md) +- Product/feature drafts under `/features/*` may still describe aspirational UX + โ€” prefer guide + API pages for MVP truth. + +## Handoffs + +Repo: `docs/mvp/README.md` and `docs/handoffs/` (GitHub issues #14โ€“#20). diff --git a/apps/docs/website/dev/supabase.md b/apps/docs/website/dev/supabase.md index 867b73d..4eabde4 100644 --- a/apps/docs/website/dev/supabase.md +++ b/apps/docs/website/dev/supabase.md @@ -1,154 +1,36 @@ -# Supabase Comprehensive Guide +# Supabase (web, optional) -## What is Supabase? +Supabase is **optional** for `@worksight/web` auth / online mode. MVP dashboard +and Nest list data come from **`@worksight/common` fixtures**, not from Supabase +tables. -Supabase is an open-source Firebase alternative providing a hosted Postgres -database, authentication, real-time APIs, and storage. It integrates easily with -modern web frameworks like Next.js. +## When you need it ---- +- `NEXT_PUBLIC_IS_OFFLINE=false` (and matching `IS_OFFLINE`) +- A Supabase project URL + publishable (anon) key -## 1. Setup +For fixture / offline demos, leave offline mode on and skip Supabase entirely +(`apps/web/env.example`). -### a. Create a Supabase Project +## Env vars (this repo) -1. Go to [supabase.com](https://supabase.com/) and sign up. -2. Create a new project. -3. Note your project URL and anon/public API key (found in Project Settings > - API). - -### b. Install Supabase Client - -```sh -npm install @supabase/supabase-js -``` - -### c. Configure Environment Variables - -Add these to your `.env.local`: - -``` +```bash +# apps/web/.env.local NEXT_PUBLIC_SUPABASE_URL=your-project-url -NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key -``` - ---- - -## 2. Initialize Supabase Client - -Create a utility file (e.g., `lib/supabase.ts`): - -```typescript -import { createClient } from '@supabase/supabase-js'; - -const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; -const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; -export const supabase = createClient(supabaseUrl, supabaseKey); +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-anon-key +NEXT_PUBLIC_IS_OFFLINE=false +IS_OFFLINE=false ``` ---- - -## 3. Usage Patterns - -### a. CRUD Operations - -```typescript -// Fetch all tasks -const { data, error } = await supabase.from('tasks').select('*'); - -// Insert a new task -const { data, error } = await supabase - .from('tasks') - .insert([{ title: 'New Task' }]); - -// Update a task -const { data, error } = await supabase - .from('tasks') - .update({ done: true }) - .eq('id', 1); - -// Delete a task -const { data, error } = await supabase.from('tasks').delete().eq('id', 1); -``` - -### b. Authentication - -```typescript -// Sign up -const { data, error } = await supabase.auth.signUp({ - email: 'user@example.com', - password: 'password', -}); - -// Sign in -const { data, error } = await supabase.auth.signInWithPassword({ - email: 'user@example.com', - password: 'password', -}); - -// Sign out -const { error } = await supabase.auth.signOut(); -``` - -### c. Real-time Subscriptions - -```typescript -supabase - .channel('public:tasks') - .on( - 'postgres_changes', - { event: '*', schema: 'public', table: 'tasks' }, - (payload) => { - console.log('Change received!', payload); - } - ) - .subscribe(); -``` - ---- - -## 4. Security & Best Practices - -- **Never expose your service role key** to the frontend. -- Use **Row Level Security (RLS)** in Supabase for fine-grained access control. -- Store sensitive keys in environment variables, not in code. -- Use Supabase Auth for user authentication and session management. -- Validate all user input before writing to the database. - ---- - -## 5. Advanced Usage - -- **Storage:** Upload and manage files with Supabase Storage. -- **Edge Functions:** Write serverless functions for custom backend logic. -- **Policies:** Write RLS policies in the Supabase dashboard for secure data - access. -- **Server-side Usage:** Use the service key only in API routes or server - functions. - ---- - -## 6. Troubleshooting - -- **Auth errors:** Double-check your API keys and project URL. -- **RLS issues:** Ensure your policies allow the intended operations for - authenticated users. -- **Network issues:** Make sure your environment variables are set and - accessible. - ---- - -## 7. Resources +Client helpers live under `apps/web` (e.g. `src/lib/supabase.ts`, +`src/utils/supabase/*`). Prefer those over inventing a new root client. -- [Supabase Docs](https://supabase.com/docs) -- [Supabase GitHub](https://github.com/supabase/supabase) -- [Next.js Integration Guide](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs) +## Not true for MVP Nest routes ---- +- Nest `/users`, `/teams`, `/tasks`, `/activities` do **not** read Supabase. +- Do not document service-role keys or table schemas as required for the MVP API + slice. -## Tips +## Further reading -- Store keys in environment variables. -- Use Supabase Auth for secure authentication. -- Use TypeScript for type safety with Supabase responses. -- Regularly review your RLS policies for security. +Official docs: diff --git a/apps/docs/website/guide/configuration.md b/apps/docs/website/guide/configuration.md index 6e56080..b29ef67 100644 --- a/apps/docs/website/guide/configuration.md +++ b/apps/docs/website/guide/configuration.md @@ -1,355 +1,70 @@ # Configuration -Learn how to configure WorkSight for your organization's specific needs. +Configure WorkSight with the env vars and packages that actually exist in the +repo. -## Environment Configuration +## Web (`apps/web`) -### Core Settings +Copy the template: -WorkSight uses environment variables for configuration. Here are the essential -settings: - -#### Database Configuration - -```env -# Primary database connection -DATABASE_URL="postgresql://username:password@localhost:5432/worksight" - -# Database connection pool settings -DATABASE_MAX_CONNECTIONS=20 -DATABASE_IDLE_TIMEOUT=30000 -``` - -#### Authentication Settings - -```env -# NextAuth configuration -NEXTAUTH_URL="https://your-domain.com" -NEXTAUTH_SECRET="your-256-bit-secret" - -# Session settings -SESSION_MAX_AGE=2592000 # 30 days -SESSION_UPDATE_AGE=86400 # 24 hours -``` - -#### Supabase Configuration - -```env -# Supabase project settings -NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co" -NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key" -SUPABASE_SERVICE_ROLE_KEY="your-service-role-key" -``` - -### Email Configuration - -```env -# Email provider settings -EMAIL_FROM="noreply@yourcompany.com" -EMAIL_SERVER_HOST="smtp.yourprovider.com" -EMAIL_SERVER_PORT=587 -EMAIL_SERVER_USER="your-smtp-username" -EMAIL_SERVER_PASSWORD="your-smtp-password" -``` - -### Optional Integrations - -```env -# Analytics -NEXT_PUBLIC_GOOGLE_ANALYTICS_ID="GA_MEASUREMENT_ID" - -# Error tracking -SENTRY_DSN="your-sentry-dsn" - -# File storage -AWS_ACCESS_KEY_ID="your-aws-key" -AWS_SECRET_ACCESS_KEY="your-aws-secret" -AWS_REGION="us-east-1" -AWS_BUCKET_NAME="your-bucket" -``` - -## Application Configuration - -### Site Configuration - -Edit `src/config/site.ts` to customize your instance: - -```typescript -export const siteConfig = { - name: 'WorkSight', - description: 'Employee well-being and task management platform', - url: 'https://your-domain.com', - ogImage: 'https://your-domain.com/og.png', - - // Company information - company: { - name: 'Your Company Name', - email: 'contact@yourcompany.com', - phone: '+1 (555) 123-4567', - }, - - // Feature flags - features: { - enableSurveys: true, - enableReporting: true, - enableTeamManagement: true, - enableNotifications: true, - }, -}; -``` - -### Survey Configuration - -Configure survey settings in `src/config/surveys.ts`: - -```typescript -export const surveyConfig = { - // Default survey intervals - burnoutAssessment: { - frequency: 'weekly', // daily, weekly, monthly - reminderTime: '09:00', - timezone: 'America/New_York', - }, - - // Survey customization - questions: { - useCustomQuestions: false, - customQuestionsPath: '/surveys/custom.json', - }, - - // Scoring configuration - scoring: { - burnoutThreshold: 70, - warningThreshold: 50, - scale: '1-5', // 1-5, 1-7, 1-10 - }, -}; -``` - -## User Roles and Permissions - -### Role Configuration - -Define user roles and permissions: - -```typescript -// src/config/roles.ts -export const roles = { - admin: { - permissions: [ - 'user.create', - 'user.update', - 'user.delete', - 'survey.create', - 'survey.update', - 'survey.delete', - 'report.view', - 'report.export', - ], - }, - manager: { - permissions: ['team.view', 'team.manage', 'survey.view', 'report.view'], - }, - employee: { - permissions: [ - 'task.create', - 'task.update', - 'survey.take', - 'profile.update', - ], - }, -}; -``` - -## Database Configuration - -### Connection Settings - -For production deployments, configure database connection pooling: - -```typescript -// src/lib/database.ts -export const dbConfig = { - host: process.env.DB_HOST, - port: parseInt(process.env.DB_PORT || '5432'), - database: process.env.DB_NAME, - username: process.env.DB_USER, - password: process.env.DB_PASSWORD, - - // Connection pool - pool: { - min: 2, - max: 20, - acquire: 30000, - idle: 10000, - }, - - // SSL configuration for production - ssl: - process.env.NODE_ENV === 'production' - ? { - require: true, - rejectUnauthorized: false, - } - : false, -}; -``` - -## Security Configuration - -### Authentication Providers - -Configure supported authentication methods: - -```typescript -// src/config/auth.ts -export const authProviders = { - email: { - enabled: true, - requireVerification: true, - }, - google: { - enabled: true, - clientId: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - }, - microsoft: { - enabled: false, - tenantId: process.env.MICROSOFT_TENANT_ID, - clientId: process.env.MICROSOFT_CLIENT_ID, - clientSecret: process.env.MICROSOFT_CLIENT_SECRET, - }, - saml: { - enabled: false, - entityId: process.env.SAML_ENTITY_ID, - ssoUrl: process.env.SAML_SSO_URL, - certificate: process.env.SAML_CERTIFICATE, - }, -}; -``` - -### Password Policy - -```typescript -export const passwordPolicy = { - minLength: 8, - requireUppercase: true, - requireLowercase: true, - requireNumbers: true, - requireSpecialChars: true, - maxAge: 90, // days - historyCount: 5, -}; +```bash +cp apps/web/env.example apps/web/.env.local ``` -## Deployment Configuration - -### Production Settings +### Supported variables -```env -# Environment -NODE_ENV=production +From `apps/web/env.example`: -# Security -SECURE_COOKIES=true -CSRF_SECRET="your-csrf-secret" - -# Performance -ENABLE_COMPRESSION=true -CACHE_TTL=3600 +```bash +# Force offline mode (disables signup / online-only features) +NEXT_PUBLIC_IS_OFFLINE=false +IS_OFFLINE=false -# Monitoring -LOG_LEVEL=info -ENABLE_METRICS=true +# Optional when offline mode is enabled +# NEXT_PUBLIC_SUPABASE_URL=your_supabase_url_here +# NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your_supabase_anon_key_here ``` -### Health Checks +Additional app branding vars may be set in Vercel for production (see +[Deployment](./deployment.md)): -Configure health check endpoints: - -```typescript -// src/config/health.ts -export const healthConfig = { - endpoints: { - '/health': { - checks: ['database', 'redis', 'external-apis'], - }, - '/health/liveness': { - checks: ['basic'], - }, - '/health/readiness': { - checks: ['database', 'migrations'], - }, - }, -}; +```bash +NEXT_PUBLIC_APP_NAME=WorkSight +NEXT_PUBLIC_APP_DESCRIPTION=Employee Well-being Analytics Platform +NEXT_PUBLIC_APP_URL=https://your-domain.vercel.app ``` -## Advanced Configuration +There is **no** NextAuth config, root `DATABASE_URL`, `pnpm db:migrate`, or +`pnpm config:validate` script in this monorepo. -### Custom Themes +## API (`apps/api`) -Customize the application theme: +Nest boots with `app.listen(process.env.PORT ?? 3000)`. Set `PORT` when web +already occupies 3000: -```typescript -// src/config/theme.ts -export const themeConfig = { - primaryColor: '#3b82f6', - secondaryColor: '#64748b', - accentColor: '#f59e0b', - - // Dark mode - darkMode: { - enabled: true, - default: false, - }, - - // Custom CSS - customStyles: '/styles/custom.css', -}; +```bash +PORT=3123 pnpm --filter @worksight/api dev ``` -### Notification Configuration - -```typescript -// src/config/notifications.ts -export const notificationConfig = { - email: { - enabled: true, - templates: { - welcome: 'welcome-template', - surveyReminder: 'survey-reminder-template', - reportReady: 'report-ready-template', - }, - }, +MVP endpoints read **`@worksight/common` fixtures** in-process. No Supabase +service-role key is required for those routes today. - push: { - enabled: false, - vapidPublicKey: process.env.VAPID_PUBLIC_KEY, - vapidPrivateKey: process.env.VAPID_PRIVATE_KEY, - }, +## Docs (`apps/docs`) - inApp: { - enabled: true, - maxNotifications: 100, - retentionDays: 30, - }, -}; -``` +VitePress uses `VITE_HOSTNAME` / `VITE_BASE` when set (see +`apps/docs/.vitepress/config.mts`). Defaults work for local `pnpm dev:docs`. -## Validation +## Shared package -After configuration, validate your setup: +Consumers resolve `@worksight/common` from the workspace. Build it before +type-checking or running API/web against dist: ```bash -# Check configuration -pnpm config:validate - -# Test database connection -pnpm db:test +pnpm --filter @worksight/common build +``` -# Verify email settings -pnpm email:test +## Vercel -# Run health checks -pnpm health:check -``` +Env vars are **dashboard-managed per project**. Do not commit secrets into +`vercel.json`. Three projects: `worksight`, `worksight-api`, `worksight-docs` +with Root Directories `apps/web`, `apps/api`, `apps/docs`. diff --git a/apps/docs/website/guide/deployment.md b/apps/docs/website/guide/deployment.md new file mode 100644 index 0000000..f335e5a --- /dev/null +++ b/apps/docs/website/guide/deployment.md @@ -0,0 +1,50 @@ +# Deployment + +WorkSight is a pnpm + Turborepo monorepo. Deploy **each app** with its own +target โ€” there is no single root Vercel project that builds everything. + +## Vercel projects + +| App | Package | Vercel project | Root Directory | +| ----------- | ----------------- | ---------------- | -------------- | +| `apps/web` | `@worksight/web` | `worksight` | `apps/web` | +| `apps/api` | `@worksight/api` | `worksight-api` | `apps/api` | +| `apps/docs` | `@worksight/docs` | `worksight-docs` | `apps/docs` | + +Each project loads **only** the `vercel.json` under its Root Directory. Vercel +does not merge a repo-root `vercel.json` with nested ones. + +Shared settings in practice: + +- Production branch: **`canary`** +- Install: `pnpm install --frozen-lockfile` +- Build: `pnpm --filter @worksight/ build` +- Include source files outside Root Directory: **on** +- Skip unaffected projects: **on** + +## API caveat + +`apps/api` still boots with `app.listen()`. A Vercel deployment builds `dist/` +but does **not** expose a serverless function yet. Use Docker for a working API: + +```bash +docker compose up -d --build +``` + +## Local verify before ship + +```bash +pnpm --filter @worksight/common build +pnpm type-check +pnpm --filter @worksight/web build +pnpm --filter @worksight/docs build +pnpm --filter @worksight/api build +``` + +## Canonical guide + +The long-form checklist and dashboard steps live in the repo at +[`doc/DEPLOYMENT.md`](https://github.com/4sightorg/worksight/blob/feat/mvp-stabilize/doc/DEPLOYMENT.md). + +There is **no** root `deploy.yml` / `pnpm deploy` script. Production deploys are +driven by each Vercel project's Git integration. diff --git a/apps/docs/website/guide/getting-started.md b/apps/docs/website/guide/getting-started.md index 63a60b8..04ccbba 100644 --- a/apps/docs/website/guide/getting-started.md +++ b/apps/docs/website/guide/getting-started.md @@ -1,103 +1,99 @@ # Getting Started -Welcome to WorkSight! This guide will help you get up and running quickly. +Welcome to WorkSight. This guide gets the monorepo running locally for the MVP +slice. ## What is WorkSight? -WorkSight is a comprehensive wellness and task management platform designed to -help organizations monitor employee well-being while managing productivity -effectively. +WorkSight is an employee well-being and task analytics platform. The monorepo +includes: -## Quick Start +- **`@worksight/web`** โ€” Next.js 15 app (dashboards, surveys UI, admin) +- **`@worksight/api`** โ€” NestJS API +- **`@worksight/docs`** โ€” this VitePress site +- **`@worksight/common`** โ€” shared types, fixtures, and lookup utilities -### Prerequisites +For the MVP, **populated dashboard / API data comes from `@worksight/common` +fixtures**, not from a live database. Supabase is optional for web auth / online +mode. -- Node.js 18+ and pnpm -- Supabase account (for online features) -- Modern web browser +## Prerequisites -### Installation +- Node.js 18+ (20+ recommended) +- pnpm 9+ (repo pins pnpm 10 via `packageManager`) +- Git +- Optional: Supabase project (only if you leave offline mode off) -1. Clone the repository: +## Install ```bash git clone https://github.com/4sightorg/worksight.git cd worksight +pnpm install ``` -2. Install dependencies: +## Environment (web) ```bash -pnpm install +cp apps/web/env.example apps/web/.env.local ``` -3. Set up environment variables: +Minimal offline-friendly settings: ```bash -cp apps/web/.env.example apps/web/.env.local +NEXT_PUBLIC_IS_OFFLINE=true +IS_OFFLINE=true ``` -4. Configure your Supabase credentials in `.env.local`: +For online Supabase auth, set (names match `env.example`): -```env +```bash NEXT_PUBLIC_SUPABASE_URL=your_supabase_url -NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your_supabase_anon_key +NEXT_PUBLIC_IS_OFFLINE=false ``` -5. Start the development server: +## Run locally + +Build shared packages when you need API/web against compiled common: ```bash -pnpm run dev +pnpm --filter @worksight/common build ``` -### First Login - -WorkSight supports both online and offline modes. For testing, you can use these -credentials: - -**Executives:** - -- Email: `test@worksight.app` -- Password: `testuser` - -**Employees:** - -- Email: `jane.doe@worksight.com` -- Password: `testuser` - -**Admin:** - -- Email: `admin@worksight.com` -- Password: `testuser` - -## Core Features +```bash +# Web โ†’ http://localhost:3000 +pnpm dev:web -### ๐Ÿ  Dashboard +# Docs โ†’ http://localhost:5173 (VitePress default) +pnpm dev:docs -Role-based dashboards showing: +# API (defaults to PORT 3000 โ€” pick another if web is running) +PORT=3123 pnpm --filter @worksight/api dev +``` -- Burnout level tracking -- Task summaries -- Team analytics (for managers/executives) -- Quick actions +Or `pnpm dev` to start all Turbo `dev` tasks. -### ๐Ÿ“‹ Task Management +## MVP data you should see -- Kanban board with drag-and-drop -- Table view with inline editing -- Priority and status management -- Story point estimation +Once web is wired to common fixtures, dashboards/admin/tasks views use employee, +team, assignment, and activity fixtures from `@worksight/common`. The Nest API +exposes the same shapes on: -### ๐Ÿง˜ Wellness Tracking +- `GET /users`, `/users/:id`, `/users/stats` +- `GET /teams`, `/teams/:id` +- `GET /tasks` (`?employee_id=`), `/tasks/:id`, `/tasks/stats/:employeeId` +- `GET /activities` +- `GET /`, `/ping`, `/health` -- Burnout assessment surveys -- Progress tracking over time -- Personalized recommendations -- Risk level monitoring +There is **no** Supabase-backed persistence for those API routes yet, and the +API has **no** Vercel serverless handler (`app.listen` only) โ€” use Docker for a +deployed API. See [Deployment](/guide/deployment). -### โš™๏ธ Settings +## Next steps -- User preferences -- Notification settings -- Theme customization -- Admin controls +- [Installation](./installation.md) โ€” environments and Docker notes +- [Configuration](./configuration.md) โ€” real env vars only +- [API overview](/dev/api/overview) โ€” Nest fixture endpoints +- [MVP plan](https://github.com/4sightorg/worksight/blob/feat/mvp-stabilize/docs/mvp/README.md) + (epic #14) diff --git a/apps/docs/website/guide/installation.md b/apps/docs/website/guide/installation.md index c70a053..7c7494e 100644 --- a/apps/docs/website/guide/installation.md +++ b/apps/docs/website/guide/installation.md @@ -1,198 +1,122 @@ # Installation -This guide provides detailed instructions for installing and setting up -WorkSight in different environments. +Install and run the WorkSight monorepo (pnpm + Turbo). -## Development Environment +## Prerequisites -### Prerequisites +- **Node.js** 18+ (20+ recommended) +- **pnpm** 9+ (lockfile expects pnpm 10 โ€” see root `packageManager`) +- **Git** +- Optional: **Docker** / Docker Compose for the Nest API -Ensure you have the following installed on your system: +## Native (recommended for development) -- **Node.js**: Version 20.0.0 or higher -- **pnpm**: Version 8.0.0 or higher (recommended package manager) -- **Git**: For version control - -### System Requirements - -- **Operating System**: Windows 10+, macOS 10.15+, or Linux -- **Memory**: 502MB RAM minimum (1GB recommended) -- **Storage**: 256MB free disk space - -### Step-by-Step Installation - -#### Docker Installation - -```bash -docker run -rm -it ghcr.io:4sight/worksight/web:latest -``` - -#### Docker Compose Installation - -```bash -docker compose up -d -``` - -#### Native Installation - -1. **Clone the Repository** +1. **Clone** ```bash git clone https://github.com/4sightorg/worksight.git cd worksight ``` -2. **Install Dependencies** +2. **Install** ```bash - # Install pnpm if you haven't already - npm install -g pnpm - - # Install project dependencies + corepack enable pnpm install ``` -3. **Environment Configuration** - - ```bash - # Copy environment template - cp .env.example .env.local - ``` - -4. **Configure Environment Variables** - - Edit `.env.local` with your configuration: - - ```env - # Database - DATABASE_URL="your-database-url" - - # Authentication - NEXTAUTH_URL="http://localhost:3000" - NEXTAUTH_SECRET="your-secret-key" - - # Supabase - NEXT_PUBLIC_SUPABASE_URL="your-supabase-url" - NEXT_PUBLIC_SUPABASE_ANON_KEY="your-supabase-anon-key" - ``` - -5. **Database Setup** +3. **Web env** ```bash - # Run database migrations - pnpm db:migrate - - # Seed initial data (optional) - pnpm db:seed + cp apps/web/env.example apps/web/.env.local ``` -6. **Start Development Server** + Edit `apps/web/.env.local`. For fixture / offline MVP work: ```bash - pnpm dev + NEXT_PUBLIC_IS_OFFLINE=true + IS_OFFLINE=true ``` - The application will be available at `http://localhost:3000` - -## Production Deployment + There is no root `.env.example`, no `pnpm db:migrate` / `pnpm db:seed`, and + no NextAuth-required setup for the MVP slice. -### Using Vercel (Recommended) - -1. **Deploy to Vercel** +4. **Build shared package** ```bash - # Install Vercel CLI - npm install -g vercel - - # Deploy - vercel + pnpm --filter @worksight/common build ``` -2. **Configure Environment Variables** - - Set the following in your Vercel dashboard: - - `DATABASE_URL` - - `NEXTAUTH_URL` - - `NEXTAUTH_SECRET` - - `NEXT_PUBLIC_SUPABASE_URL` - - `NEXT_PUBLIC_SUPABASE_ANON_KEY` - -### Using Docker - -1. **Build Docker Image** +5. **Start apps** ```bash - docker build -t worksight . + pnpm dev:web # :3000 + pnpm dev:docs # VitePress + PORT=3123 pnpm --filter @worksight/api dev # Nest ``` -2. **Run Container** - - ```bash - docker run -p 3000:3000 --env-file .env.local worksight - ``` +## Docker Compose -### Manual Deployment +From the repo root (API-oriented path; see `docker-compose.yml`): -1. **Build for Production** +```bash +docker compose up -d --build +``` - ```bash - pnpm build - ``` +Prefer this for a **running Nest API**. The Vercel API project builds `dist/` +but does not yet expose a serverless handler. -2. **Start Production Server** +## Production builds (local) - ```bash - pnpm start - ``` +```bash +pnpm --filter @worksight/common build +pnpm --filter @worksight/web build +pnpm --filter @worksight/api build +pnpm --filter @worksight/docs build +``` -## Database Setup +Or `pnpm build` via Turbo. -### Supabase (Recommended) +## Vercel (hosted) -1. Create a new project at [supabase.com](https://supabase.com) -2. Copy your project URL and anon key -3. Run the database migrations provided in `/supabase/migrations` +WorkSight uses **three** Vercel projects with per-app Root Directories โ€” not a +single root `vercel.json`: -### Self-hosted PostgreSQL +| Project | Root Directory | +| ---------------- | -------------- | +| `worksight` | `apps/web` | +| `worksight-api` | `apps/api` | +| `worksight-docs` | `apps/docs` | -1. Install PostgreSQL 14+ -2. Create a new database -3. Run the SQL schema from `/database/schema.sql` +Details: [Deployment](./deployment.md) and repo +[`doc/DEPLOYMENT.md`](https://github.com/4sightorg/worksight/blob/feat/mvp-stabilize/doc/DEPLOYMENT.md). ## Troubleshooting -### Common Issues - -**Port 3000 already in use** +**Port 3000 in use** ```bash -# Kill process using port 3000 -lsof -ti:3000 | xargs kill -9 - -# Or use a different port -PORT=3001 pnpm dev +PORT=3001 pnpm --filter @worksight/web dev +# or free the port, then retry ``` -**Database connection errors** +**Empty / stale `@worksight/common`** -- Verify your `DATABASE_URL` is correct -- Check if your database server is running -- Ensure network connectivity to your database +```bash +pnpm --filter @worksight/common build +``` -**Build errors** +**Reinstall** ```bash -# Clear cache and reinstall +pnpm clean rm -rf node_modules -rm pnpm-lock.yaml pnpm install ``` -### Getting Help +Do **not** delete `pnpm-lock.yaml` unless you intend to regenerate the lockfile. -If you encounter issues: +## Getting help -1. Check the [troubleshooting section] -2. Search existing - [GitHub issues](https://github.com/4sightorg/worksight/issues) -3. Create a new issue with detailed information +- [GitHub issues](https://github.com/4sightorg/worksight/issues) +- [MVP plan](https://github.com/4sightorg/worksight/blob/feat/mvp-stabilize/docs/mvp/README.md) diff --git a/apps/docs/website/guide/introduction.md b/apps/docs/website/guide/introduction.md index fddb015..398f36a 100644 --- a/apps/docs/website/guide/introduction.md +++ b/apps/docs/website/guide/introduction.md @@ -1,80 +1,47 @@ # Introduction -WorkSight is a comprehensive employee well-being and task management platform -designed to help organizations monitor and improve workplace productivity while -maintaining employee mental health. - -## What is WorkSight? - -WorkSight combines modern task management with advanced burnout assessment tools -to create a holistic approach to workplace wellness. Our platform helps both -employees and managers: - -- **Track Tasks Efficiently**: Organize and prioritize work with intuitive task - management -- **Monitor Well-being**: Regular burnout assessments and wellness check-ins -- **Generate Insights**: Comprehensive reporting on productivity and wellness - trends -- **Improve Culture**: Data-driven insights to build healthier work environments - -## Key Features - -### ๐ŸŽฏ Task Management - -- Create, assign, and track tasks -- Set priorities and deadlines -- Collaborate with team members -- Track time and progress - -### ๐Ÿ“Š Burnout Assessment - -- Regular wellness surveys -- Science-backed burnout indicators -- Personal wellness dashboards -- Early warning systems - -### ๐Ÿ‘ฅ Team Management - -- Role-based access control -- Team productivity insights -- Manager dashboards -- Employee wellness monitoring - -### ๐Ÿ“ˆ Analytics & Reporting - -- Productivity metrics -- Wellness trends -- Custom reports -- Export capabilities - -## How It Works - -1. **Setup**: Configure your organization and invite team members -2. **Tasks**: Create and manage tasks using our intuitive interface -3. **Assess**: Regular wellness check-ins and burnout assessments -4. **Analyze**: Review insights and reports to improve workplace culture -5. **Improve**: Implement data-driven changes to enhance productivity and - wellness - -## Who Should Use WorkSight? - -- **HR Professionals**: Monitor employee wellness and engagement -- **Team Leaders**: Balance productivity with team well-being -- **Employees**: Track personal productivity and wellness -- **Organizations**: Build healthier, more productive work environments - -## Technology Stack - -WorkSight is built with modern technologies: - -- **Frontend**: Next.js 14, React, TypeScript -- **Backend**: Supabase, PostgreSQL -- **Authentication**: Supabase Auth -- **Deployment**: Vercel, GitHub Actions -- **Monitoring**: Built-in analytics and reporting - -## Getting Started - -Ready to transform your workplace? Check out our -[Getting Started Guide](./getting-started.md) to begin your journey with -WorkSight. +WorkSight helps organizations track employee well-being alongside work activity. +This documentation covers the **monorepo** as it exists today: Next.js web, +NestJS API, VitePress docs, and `@worksight/common`. + +## What ships in the repo + +| Package | Role | +| ------------------- | ---------------------------------------- | +| `@worksight/web` | Next.js 15 App Router UI | +| `@worksight/api` | NestJS HTTP API | +| `@worksight/docs` | VitePress site (`apps/docs`) | +| `@worksight/common` | Shared types, fixtures, lookup utilities | +| `@worksight/assets` | Shared assets | + +## MVP scope (honest) + +- Dashboards and Nest list/detail endpoints are driven by **`@worksight/common` + fixtures** for the MVP slice. +- **No** live Supabase persistence for those new API endpoints yet. +- Supabase Auth remains available on the web app when offline mode is off. +- External connectors (Jira, Trello, GitHub, Odoo, Slack) and production auth + hardening are **non-goals** for the current MVP epic. + +## Product surfaces (UI) + +- Role-aware dashboards and admin views +- Task / assignment views +- Survey and burnout-related UI (backed by common types/fixtures where wired) +- Offline-capable web mode for local demos + +## Technology stack + +- **Web:** Next.js 15, React 19, TypeScript, Tailwind CSS, shadcn/ui +- **API:** NestJS +- **Shared:** `@worksight/common` +- **Auth (web, optional):** Supabase Auth +- **Docs:** VitePress +- **Monorepo:** pnpm workspaces + Turbo +- **Hosting:** Vercel (three projects) + Docker for a working API + +## Getting started + +See [Getting Started](./getting-started.md) and +[Installation](./installation.md). Deployment layout: +[Deployment](./deployment.md). diff --git a/apps/docs/website/guide/overview.md b/apps/docs/website/guide/overview.md index 5b815f2..4c33393 100644 --- a/apps/docs/website/guide/overview.md +++ b/apps/docs/website/guide/overview.md @@ -1,166 +1,21 @@ -# API Overview +# Guide overview -WorkSight provides a comprehensive REST API for integrating with external -systems and building custom applications. +WorkSight documentation for operators and contributors. -## Base URL +## Start here -``` -Production: https://api.worksight.com -Development: http://localhost:3000/api -``` +1. [Introduction](./introduction.md) โ€” what the monorepo is +2. [Getting started](./getting-started.md) โ€” local run +3. [Installation](./installation.md) โ€” native + Docker +4. [Configuration](./configuration.md) โ€” real env vars +5. [Deployment](./deployment.md) โ€” three Vercel projects + Docker API -## Authentication +## MVP reminders -WorkSight supports multiple authentication methods: +- Data for the current slice: **`@worksight/common` fixtures** +- Packages: `@worksight/web`, `@worksight/api`, `@worksight/docs`, + `@worksight/common` +- Stack: Next.js 15 + NestJS + pnpm/Turbo +- API on Vercel is not serverless yet โ€” use Docker for a live API -### Bearer Token - -```bash -curl -H "Authorization: Bearer your_token_here" \ - https://api.worksight.com/users/me -``` - -### API Key - -```bash -curl -H "X-API-Key: your_api_key_here" \ - https://api.worksight.com/tasks -``` - -## Rate Limiting - -API requests are rate-limited based on your role: - -| Role | Requests/Hour | Burst Limit | -| -------- | ------------- | ----------- | -| Admin | 10,000 | 500 | -| Manager | 5,000 | 250 | -| Employee | 1,000 | 100 | -| Guest | 100 | 20 | - -## Response Format - -All API responses follow this structure: - -```json -{ - "success": true, - "data": {...}, - "message": "Operation completed successfully", - "timestamp": "2025-01-27T10:00:00Z" -} -``` - -### Error Format - -```json -{ - "success": false, - "error": { - "code": "VALIDATION_ERROR", - "message": "Invalid input data", - "details": {...} - }, - "timestamp": "2025-01-27T10:00:00Z" -} -``` - -## Core Endpoints - -### Users - -- `GET /api/users` - List users -- `GET /api/users/me` - Current user info -- `PUT /api/users/me` - Update profile - -### Tasks - -- `GET /api/tasks` - List tasks -- `POST /api/tasks` - Create task -- `PUT /api/tasks/:id` - Update task -- `DELETE /api/tasks/:id` - Delete task - -### Surveys - -- `POST /api/surveys/submit` - Submit survey -- `GET /api/surveys/results` - Get results -- `GET /api/surveys/history` - Survey history - -### Analytics - -- `GET /api/analytics/burnout` - Burnout statistics -- `GET /api/analytics/tasks` - Task analytics -- `GET /api/analytics/team` - Team metrics - -## SDKs - -### JavaScript/TypeScript - -```bash -npm install apps/api-client -``` - -```typescript -import { WorkSightAPI } from 'apps/api-client'; - -const api = new WorkSightAPI({ - baseURL: 'https://api.worksight.com', - apiKey: 'your_api_key' -}); - -// Get user tasks -const tasks = await api.tasks.list(); - -// Submit survey -const result = await api.surveys.submit({ - responses: {...}, - userId: 'user123' -}); -``` - -### Python - -```bash -pip install worksight-api -``` - -```python -from worksight import WorkSightAPI - -api = WorkSightAPI( - base_url='https://api.worksight.com', - api_key='your_api_key' -) - -# Get burnout analytics -analytics = api.analytics.burnout() -``` - -## Webhooks - -Subscribe to real-time events: - -### Available Events - -- `survey.completed` - Survey submission -- `task.created` - New task -- `task.updated` - Task status change -- `user.burnout_alert` - High burnout score - -### Configuration - -```json -{ - "url": "https://your-app.com/webhooks/worksight", - "events": ["survey.completed", "task.updated"], - "secret": "your_webhook_secret" -} -``` - -## OpenAPI Specification - -Full API documentation is available in OpenAPI format: - -- [View Interactive Docs](https://api.worksight.com/docs) -- [Download OpenAPI JSON](https://api.worksight.com/openapi.json) +Developer API notes live under [Dev โ†’ API overview](/dev/api/overview). diff --git a/apps/docs/website/index.md b/apps/docs/website/index.md index 93b9c5a..befa8e0 100644 --- a/apps/docs/website/index.md +++ b/apps/docs/website/index.md @@ -47,8 +47,8 @@ features: UI. - icon: โšก - title: Real-time Sync + title: Shared data layer details: - Supabase integration with offline-first architecture for reliable - performance. + MVP dashboards and Nest routes use `@worksight/common` fixtures; optional + Supabase auth when online mode is enabled. --- diff --git a/apps/web/.env.example b/apps/web/.env.example index 9b78e34..c3c82cd 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -29,6 +29,11 @@ NEXT_PUBLIC_IS_OFFLINE=false # Server-side offline flag (for server components) IS_OFFLINE=false +# MVP data source (#18): when true, web fetches Nest API instead of in-process fixtures +# /demo always hits the API regardless of this flag +NEXT_PUBLIC_USE_API=false +NEXT_PUBLIC_API_URL=http://localhost:3001 + # App URL for metadata and OpenGraph NEXT_PUBLIC_APP_URL=http://localhost:3000 @@ -55,7 +60,9 @@ NEXT_PUBLIC_ENABLE_ERROR_REPORTING="false" NEXT_PUBLIC_ENABLE_PERFORMANCE_MONITORING="true" # API Configuration -API_BASE_URL="http://localhost:3000/api" +# Nest API (fixture-backed @worksight/common). Web uses NEXT_PUBLIC_* below. +API_BASE_URL="http://localhost:3001" +NEXT_PUBLIC_API_URL="http://localhost:3001" API_TIMEOUT="10000" # Email Configuration (if needed) diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 9c3721a..5035937 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -2,53 +2,49 @@ FROM node:24-alpine AS base WORKDIR /app -# Enable Corepack (optional, mainly for yarn/pnpm) -RUN corepack enable +RUN corepack enable && corepack prepare pnpm@10.33.0 --activate # ---- Dependencies (dev + prod for build) ---- FROM base AS deps WORKDIR /app -# Copy manifests -COPY package.json turbo.json ./ +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json ./ COPY apps/web/package.json apps/web/ +COPY packages/assets/package.json packages/assets/ +COPY packages/common/package.json packages/common/ -# Install all dependencies (dev + prod) for build -RUN npm install --workspace=@worksight/web --legacy-peer-deps +RUN pnpm install --frozen-lockfile --filter @worksight/web... # ---- Build ---- FROM deps AS build WORKDIR /app -# Copy full source code COPY . . -# Set dummy environment variables for build ENV NEXT_PUBLIC_SUPABASE_URL="http://localhost:54321" ENV NEXT_PUBLIC_SUPABASE_ANON_KEY="dummy_anon_key" ENV NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY="dummy_key" ENV NEXT_PUBLIC_IS_OFFLINE=false -# Build only the web app workspace -RUN npm --workspace=apps/web run build +RUN pnpm --filter @worksight/web build # ---- Runtime (production only) ---- FROM node:24-alpine AS runtime WORKDIR /app ENV NODE_ENV=production -# Copy package manifests -COPY package.json turbo.json ./ +RUN corepack enable && corepack prepare pnpm@10.33.0 --activate + +COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json ./ COPY apps/web/package.json apps/web/ +COPY packages/assets/package.json packages/assets/ +COPY packages/common/package.json packages/common/ -# Install only production dependencies for the web workspace -RUN npm install --workspace=apps/web --omit=dev --legacy-peer-deps +RUN pnpm install --frozen-lockfile --prod --filter @worksight/web... -# Copy build artifacts COPY --from=build /app/apps/web/.next /app/apps/web/.next COPY --from=build /app/apps/web/public /app/apps/web/public EXPOSE 3000 -# Start the app -CMD ["npm", "--workspace=apps/web", "start"] +CMD ["pnpm", "--filter", "@worksight/web", "start"] diff --git a/apps/web/eslint.config.ts b/apps/web/eslint.config.ts index 20f39d3..77b3384 100644 --- a/apps/web/eslint.config.ts +++ b/apps/web/eslint.config.ts @@ -1,23 +1,46 @@ +import { FlatCompat } from '@eslint/eslintrc'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; import baseConfig from '../../eslint.config'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + export default [ ...baseConfig, - // your Next.js specific rules + ...compat.extends('next/core-web-vitals'), { - files: ["**/*.{ts,tsx}"], + files: ['**/*.{ts,tsx}'], languageOptions: { globals: { ...globals.browser, ...globals.node, ...globals.jest, React: 'readonly', - } - } + }, + }, }, { - "extends": [ - "plugin:react-hooks/recommended" - ] - } -]; \ No newline at end of file + files: ['**/*.{ts,tsx,js,jsx}'], + plugins: { + 'react-hooks': reactHooks, + }, + rules: { + ...reactHooks.configs.recommended.rules, + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@next/next/no-html-link-for-pages': 'off', + 'react/react-in-jsx-scope': 'off', + 'react/no-unescaped-entities': 'warn', + 'no-console': ['warn', { allow: ['warn', 'error'] }], + 'no-empty': ['error', { allowEmptyCatch: true }], + 'prefer-const': 'error', + }, + }, +]; diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 435c5c1..71ab30e 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -59,12 +59,16 @@ const nextConfig: NextConfig = { }, }; } + const commonDist = path.resolve(__dirname, '../../packages/common/dist'); config.resolve.alias = { ...config.resolve.alias, '@': path.resolve(__dirname, 'src'), '@worksight/assets': path.resolve(__dirname, '../../packages/assets/dist'), - '@worksight/common': path.resolve(__dirname, '../../packages/common/dist') - } + '@worksight/common/data': path.resolve(commonDist, 'data'), + '@worksight/common/types': path.resolve(commonDist, 'types'), + '@worksight/common/utils': path.resolve(commonDist, 'utils'), + '@worksight/common': commonDist, + }; return config; }, diff --git a/apps/web/package.json b/apps/web/package.json index d815a2a..85c4057 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -43,8 +43,10 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@supabase/ssr": "^0.7.0", + "@supabase/supabase-js": "^2.57.4", "@tailwindcss/postcss": "^4.1.13", - "@worksight/common": "*", + "@worksight/assets": "workspace:*", + "@worksight/common": "workspace:*", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.544.0", @@ -60,20 +62,24 @@ "tw-animate-css": "^1.4.0", "vaul": "^1.1.2", "web-vitals": "^5.1.0", + "zod": "^4.1.11", "zustand": "^5.0.8" }, "devDependencies": { + "@eslint/eslintrc": "^3.3.1", "@types/js-cookie": "^3.0.6", + "@types/node": "^20.17.6", "@types/react": "^19.1.15", "@types/react-dom": "^19.1.9", "eslint-config-next": "^15.5.4", "eslint-plugin-react-hooks": "^5.2.0", + "npm-run-all": "^4.1.5", "prettier-plugin-tailwindcss": "^0.6.14", "rimraf": "^6.0.1", - "run-s": "^0.0.0", "stylelint": "^16.24.0", "stylelint-config-standard": "^39.0.0", "stylelint-config-tailwindcss": "^1.0.0", - "tailwindcss": "^4.1.13" + "tailwindcss": "^4.1.13", + "typescript": "^5.9.2" } } diff --git a/apps/web/src/app/admin/surveys/page.tsx b/apps/web/src/app/admin/surveys/page.tsx index 7575478..9a2a728 100644 --- a/apps/web/src/app/admin/surveys/page.tsx +++ b/apps/web/src/app/admin/surveys/page.tsx @@ -41,103 +41,23 @@ import { } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useMemo, useState } from 'react'; - -interface Survey { - id: string; - title: string; - description: string; - status: 'draft' | 'active' | 'paused' | 'completed'; - questionCount: number; - responseCount: number; - createdAt: string; - lastModified: string; - createdBy: string; - category: 'burnout' | 'satisfaction' | 'wellness' | 'feedback'; - targetAudience: 'all' | 'managers' | 'employees' | 'specific'; -} - -const mockSurveys: Survey[] = [ - { - id: '1', - title: 'Burnout Assessment 2025', - description: 'Comprehensive burnout evaluation for all employees', - status: 'active', - questionCount: 15, - responseCount: 247, - createdAt: '2025-01-15', - lastModified: '2025-01-20', - createdBy: 'Admin User', - category: 'burnout', - targetAudience: 'all', - }, - { - id: '2', - title: 'Job Satisfaction Survey', - description: 'Quarterly job satisfaction and engagement survey', - status: 'active', - questionCount: 12, - responseCount: 156, - createdAt: '2025-01-10', - lastModified: '2025-01-18', - createdBy: 'HR Manager', - category: 'satisfaction', - targetAudience: 'employees', - }, - { - id: '3', - title: 'Manager Feedback Survey', - description: 'Leadership effectiveness and team dynamics assessment', - status: 'draft', - questionCount: 8, - responseCount: 0, - createdAt: '2025-01-22', - lastModified: '2025-01-22', - createdBy: 'Admin User', - category: 'feedback', - targetAudience: 'managers', - }, - { - id: '4', - title: 'Wellness Check Q4 2024', - description: 'Mental health and wellness assessment', - status: 'completed', - questionCount: 10, - responseCount: 312, - createdAt: '2024-10-01', - lastModified: '2024-12-31', - createdBy: 'Wellness Team', - category: 'wellness', - targetAudience: 'all', - }, - { - id: '5', - title: 'Remote Work Experience', - description: 'Evaluation of remote work setup and productivity', - status: 'paused', - questionCount: 14, - responseCount: 89, - createdAt: '2025-01-05', - lastModified: '2025-01-19', - createdBy: 'Operations Lead', - category: 'feedback', - targetAudience: 'all', - }, -]; +import { getMvpSurveys, type MvpSurvey } from '@/lib/mvp-data'; function SurveyManagementContent() { const { logout } = useAuth(); - const [surveys, setSurveys] = useState([]); + const [surveys, setSurveys] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [categoryFilter, setCategoryFilter] = useState('all'); const [isLoading, setIsLoading] = useState(true); useEffect(() => { - // Simulate API call - setTimeout(() => { - setSurveys(mockSurveys); - setIsLoading(false); - }, 1000); + const fixtureSurveys = getMvpSurveys(); + if (fixtureSurveys.length === 0) { + throw new Error('Common survey fixtures empty; refusing silent empty fallback'); + } + setSurveys(fixtureSurveys); + setIsLoading(false); }, []); const handleLogout = async () => { diff --git a/apps/web/src/app/admin/users/page.tsx b/apps/web/src/app/admin/users/page.tsx index 453602b..1ceaba4 100644 --- a/apps/web/src/app/admin/users/page.tsx +++ b/apps/web/src/app/admin/users/page.tsx @@ -2,121 +2,55 @@ import { useAuth } from '@/auth'; import { getRoleColor, getUserRoleDisplay } from '@/auth/admin'; -import { UserRole } from '@/auth/types'; import { AdminRoute } from '@/components/admin'; import { SessionTimer } from '@/components/features'; import { AppSidebar } from '@/components/main/sidebar'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { Badge } from '@/components/ui/badge'; import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, } from '@/components/ui/select'; import { Separator } from '@/components/ui/separator'; import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'; import { sections } from '@/data/sections'; import { - UserFilters, - UserWithMetrics, - validateUserArray, - validateUserFilters, + UserFilters, + UserWithMetrics, + validateUserArray, + validateUserFilters, } from '@/schemas/user'; +import { fetchUsersWithMetricsFromApi } from '@/lib/mvp-api-bridge'; +import { getUsersWithMetrics } from '@/lib/mvp-data'; +import { isApiDataMode } from '@/lib/worksight-api'; import { - AlertTriangle, - CheckCircle, - Clock, - Edit3, - LogOut, - MoreHorizontal, - Plus, - Search, - Shield, - Users, + AlertTriangle, + CheckCircle, + Clock, + Edit3, + LogOut, + MoreHorizontal, + Plus, + Search, + Shield, + Users, } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; -const mockUsers: UserWithMetrics[] = [ - { - id: '1', - name: 'John Doe', - email: 'john.doe@company.com', - role: UserRole.EMPLOYEE, - department: 'Engineering', - team: 'Frontend', - burnoutScore: 3.2, - lastActive: '2 hours ago', - surveyCompleted: true, - riskLevel: 'low', - tasksCompleted: 24, - }, - { - id: '2', - name: 'Jane Smith', - email: 'jane.smith@company.com', - role: UserRole.TEAM_LEAD, - department: 'Engineering', - team: 'Backend', - burnoutScore: 7.8, - lastActive: '30 minutes ago', - surveyCompleted: true, - riskLevel: 'high', - tasksCompleted: 31, - }, - { - id: '3', - name: 'Bob Wilson', - email: 'bob.wilson@company.com', - role: UserRole.MANAGER, - department: 'Product', - team: 'Design', - burnoutScore: 5.4, - lastActive: '1 day ago', - surveyCompleted: false, - riskLevel: 'medium', - tasksCompleted: 18, - }, - { - id: '4', - name: 'Alice Johnson', - email: 'alice.johnson@company.com', - role: UserRole.EMPLOYEE, - department: 'Marketing', - team: 'Content', - burnoutScore: 2.1, - lastActive: '5 minutes ago', - surveyCompleted: true, - riskLevel: 'low', - tasksCompleted: 42, - }, - { - id: '5', - name: 'Charlie Brown', - email: 'charlie.brown@company.com', - role: UserRole.ADMIN, - department: 'IT', - team: 'DevOps', - burnoutScore: 6.7, - lastActive: '1 hour ago', - surveyCompleted: true, - riskLevel: 'medium', - tasksCompleted: 15, - }, -]; - function UserManagementContent() { const { logout } = useAuth(); const [users, setUsers] = useState([]); @@ -125,25 +59,49 @@ function UserManagementContent() { const [riskFilter, setRiskFilter] = useState('all'); const [isLoading, setIsLoading] = useState(true); const [validationErrors, setValidationErrors] = useState([]); + const [dataSource, setDataSource] = useState<'api' | 'fixtures'>('fixtures'); useEffect(() => { - // Simulate API call with validation - setTimeout(() => { - // Validate mock data using Zod - const validationResult = validateUserArray(mockUsers); + let cancelled = false; + + const applyUsers = (rawUsers: UserWithMetrics[], source: 'api' | 'fixtures') => { + const validationResult = validateUserArray(rawUsers); if (!validationResult.allValid) { const errors = validationResult.invalid.map( - (item) => + item => `User at index ${item.index}: ${item.errors?.map((e: { message: string }) => e.message).join(', ')}` ); setValidationErrors(errors); } - // Use only valid users - setUsers(validationResult.valid.map((item) => item.data!)); - setIsLoading(false); - }, 1000); + if (validationResult.valid.length === 0 && rawUsers.length > 0) { + throw new Error('Employee payloads failed validation; refusing empty fallback'); + } + + if (!cancelled) { + setDataSource(source); + setUsers(validationResult.valid.map(item => item.data!)); + setIsLoading(false); + } + }; + + (async () => { + if (isApiDataMode()) { + try { + const apiUsers = await fetchUsersWithMetricsFromApi(); + applyUsers(apiUsers, 'api'); + return; + } catch (err) { + console.warn('API user load failed; falling back to fixtures', err); + } + } + applyUsers(getUsersWithMetrics(), 'fixtures'); + })(); + + return () => { + cancelled = true; + }; }, []); // Validate filters when they change @@ -179,7 +137,7 @@ function UserManagementContent() { }; const filteredUsers = useMemo(() => { - return users.filter((user) => { + return users.filter(user => { const matchesSearch = user.name?.toLowerCase().includes(searchTerm.toLowerCase()) || user.email?.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -193,7 +151,7 @@ function UserManagementContent() { }, [users, searchTerm, departmentFilter, riskFilter]); const departments = useMemo(() => { - const depts = Array.from(new Set(users.map((u) => u.department))); + const depts = Array.from(new Set(users.map(u => u.department))); return depts.sort(); }, [users]); @@ -244,7 +202,12 @@ function UserManagementContent() {
-

User Management

+
+

User Management

+ + data: {dataSource === 'api' ? 'Nest API' : 'common fixtures'} + +

Manage user accounts, roles, and monitor burnout metrics @@ -296,7 +259,7 @@ function UserManagementContent() {

- {users.filter((u) => u.riskLevel === 'high').length} + {users.filter(u => u.riskLevel === 'high').length}
@@ -308,7 +271,7 @@ function UserManagementContent() {
- {users.filter((u) => u.surveyCompleted).length} + {users.filter(u => u.surveyCompleted).length}
@@ -339,7 +302,7 @@ function UserManagementContent() { setSearchTerm(e.target.value)} + onChange={e => setSearchTerm(e.target.value)} className="pl-10" />
@@ -349,7 +312,7 @@ function UserManagementContent() { All Departments - {departments.map((dept) => ( + {departments.map(dept => ( {dept} @@ -395,7 +358,7 @@ function UserManagementContent() { {user.name ?.split(' ') - .map((n) => n[0]) + .map(n => n[0]) .join('') || 'U'} diff --git a/apps/web/src/app/dashboard/page.tsx b/apps/web/src/app/dashboard/page.tsx index 5afc928..209da78 100644 --- a/apps/web/src/app/dashboard/page.tsx +++ b/apps/web/src/app/dashboard/page.tsx @@ -45,7 +45,7 @@ export default function DashboardPage() { setTasksStarted(tasksCreated > 0); const allDone = profileComplete && !!survey && tasksCreated > 0; setShowGettingStarted(!dismissed && !allDone); - } catch (e) { + } catch { // Fail open (do not block UI) setShowGettingStarted(false); } diff --git a/apps/web/src/app/dashboard/tasks/page.tsx b/apps/web/src/app/dashboard/tasks/page.tsx index e4197a8..6fdd80b 100644 --- a/apps/web/src/app/dashboard/tasks/page.tsx +++ b/apps/web/src/app/dashboard/tasks/page.tsx @@ -10,51 +10,55 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, } from '@/components/ui/select'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, } from '@/components/ui/table'; import { Textarea } from '@/components/ui/textarea'; import { - DndContext, - DragEndEvent, - DragOverlay, - DragStartEvent, - PointerSensor, - closestCenter, - useSensor, - useSensors, + DndContext, + DragEndEvent, + DragOverlay, + DragStartEvent, + PointerSensor, + closestCenter, + useSensor, + useSensors, } from '@dnd-kit/core'; import { - SortableContext, - arrayMove, - useSortable, - verticalListSortingStrategy, + SortableContext, + arrayMove, + useSortable, + verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { - AlertCircle, - CheckCircle, - Clock, - Flag, - GripVertical, - LayoutGrid, - List, - Plus, + AlertCircle, + CheckCircle, + Clock, + Flag, + GripVertical, + LayoutGrid, + List, + Plus, } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; +import { fetchDashboardTasksFromApi } from '@/lib/mvp-api-bridge'; +import { assignmentLookup } from '@/lib/mvp-data'; +import { isApiDataMode } from '@/lib/worksight-api'; +import type { Assignment } from '@worksight/common/types'; interface Task { id: string; @@ -68,63 +72,33 @@ interface Task { type ViewMode = 'kanban' | 'table'; -// Mock tasks data - in real app this would come from your backend -const initialTasks: Task[] = [ - { - id: '1', - title: 'Implement survey results storage', - description: 'Create Zustand store for survey results with persistence', - status: 'completed', - priority: 'high', - dueDate: '2025-08-24', - storyPoints: 8, - }, - { - id: '2', - title: 'Fix ESLint warnings', - description: 'Resolve remaining TypeScript and linting issues', - status: 'in-progress', - priority: 'medium', - dueDate: '2025-08-25', - storyPoints: 3, - }, - { - id: '3', - title: 'Dashboard navigation improvements', - description: 'Implement client-side routing for dashboard sections', - status: 'in-progress', - priority: 'high', - dueDate: '2025-08-26', - storyPoints: 5, - }, - { - id: '4', - title: 'Mobile responsive design', - description: 'Ensure dashboard works well on mobile devices', - status: 'pending', - priority: 'medium', - dueDate: '2025-08-28', - storyPoints: 13, - }, - { - id: '5', - title: 'User authentication improvements', - description: 'Add password reset and email verification', - status: 'pending', - priority: 'low', - dueDate: '2025-08-30', - storyPoints: 8, - }, - { - id: '6', - title: 'Add task drag and drop', - description: 'Implement kanban board with draggable tasks', - status: 'in-progress', - priority: 'high', - dueDate: '2025-08-25', - storyPoints: 8, - }, -]; +function mapDashboardStatus(status: Assignment['status']): Task['status'] { + if (status === 'completed') return 'completed'; + if (status === 'in_progress') return 'in-progress'; + return 'pending'; +} + +function mapDashboardPriority(priority: Assignment['priority']): Task['priority'] { + if (priority === 'critical' || priority === 'high') return 'high'; + if (priority === 'medium') return 'medium'; + return 'low'; +} + +function getInitialTasksFromCommon(): Task[] { + const assignments = assignmentLookup.all(); + if (assignments.length === 0) { + throw new Error('Common assignment fixtures empty; refusing silent empty fallback'); + } + return assignments.map(assignment => ({ + id: assignment.id, + title: assignment.title ?? assignment.external_id ?? 'Untitled task', + description: [assignment.epic, assignment.sprint, assignment.type].filter(Boolean).join(' ยท '), + status: mapDashboardStatus(assignment.status), + priority: mapDashboardPriority(assignment.priority), + dueDate: assignment.updated_at.toISOString().slice(0, 10), + storyPoints: assignment.points ?? 1, + })); +} const statusConfig = { pending: { icon: Clock, color: 'text-orange-500', bg: 'bg-orange-50', label: 'Pending' }, @@ -147,11 +121,30 @@ const statusOrder: Task['status'][] = ['pending', 'in-progress', 'completed']; export default function TasksPage() { const [viewMode, setViewMode] = useState('kanban'); - const [tasks, setTasks] = useState(initialTasks); + const [tasks, setTasks] = useState(() => + isApiDataMode() ? [] : getInitialTasksFromCommon() + ); const [activeTask, setActiveTask] = useState(null); const [showNewTaskDialog, setShowNewTaskDialog] = useState(false); const [editingTaskId, setEditingTaskId] = useState(null); + useEffect(() => { + if (!isApiDataMode()) return; + let cancelled = false; + (async () => { + try { + const apiTasks = await fetchDashboardTasksFromApi(); + if (!cancelled) setTasks(apiTasks); + } catch (err) { + console.warn('API dashboard tasks failed; falling back to fixtures', err); + if (!cancelled) setTasks(getInitialTasksFromCommon()); + } + })(); + return () => { + cancelled = true; + }; + }, []); + // Get current user's stats if they're an employee const sensors = useSensors( @@ -162,14 +155,14 @@ export default function TasksPage() { }) ); - const completedTasks = tasks.filter((task) => task.status === 'completed'); - const inProgressTasks = tasks.filter((task) => task.status === 'in-progress'); + const completedTasks = tasks.filter(task => task.status === 'completed'); + const inProgressTasks = tasks.filter(task => task.status === 'in-progress'); const totalStoryPoints = tasks.reduce((sum, task) => sum + task.storyPoints, 0); const completedStoryPoints = completedTasks.reduce((sum, task) => sum + task.storyPoints, 0); const handleDragStart = (event: DragStartEvent) => { - const task = tasks.find((t) => t.id === event.active.id); + const task = tasks.find(t => t.id === event.active.id); setActiveTask(task || null); }; @@ -181,7 +174,7 @@ export default function TasksPage() { return; } - const activeTask = tasks.find((t) => t.id === active.id); + const activeTask = tasks.find(t => t.id === active.id); if (!activeTask) { setActiveTask(null); return; @@ -191,25 +184,25 @@ export default function TasksPage() { if (over.id === 'pending' || over.id === 'in-progress' || over.id === 'completed') { const newStatus = over.id as Task['status']; if (activeTask.status !== newStatus) { - setTasks((prev) => - prev.map((task) => (task.id === activeTask.id ? { ...task, status: newStatus } : task)) + setTasks(prev => + prev.map(task => (task.id === activeTask.id ? { ...task, status: newStatus } : task)) ); } } else { // Reordering within same status or between tasks const overId = over.id as string; - const overTask = tasks.find((t) => t.id === overId); + const overTask = tasks.find(t => t.id === overId); if (overTask && activeTask.id !== overTask.id) { - setTasks((prev) => { - const oldIndex = prev.findIndex((t) => t.id === activeTask.id); - const newIndex = prev.findIndex((t) => t.id === overTask.id); + setTasks(prev => { + const oldIndex = prev.findIndex(t => t.id === activeTask.id); + const newIndex = prev.findIndex(t => t.id === overTask.id); const updatedTasks = arrayMove(prev, oldIndex, newIndex); // If moving to a different status group, update the status if (activeTask.status !== overTask.status) { - return updatedTasks.map((task) => + return updatedTasks.map(task => task.id === activeTask.id ? { ...task, status: overTask.status } : task ); } @@ -223,8 +216,8 @@ export default function TasksPage() { }; const toggleTaskStatus = (taskId: string) => { - setTasks((prev) => - prev.map((task) => { + setTasks(prev => + prev.map(task => { if (task.id === taskId) { const currentIndex = statusOrder.indexOf(task.status); const nextIndex = (currentIndex + 1) % statusOrder.length; @@ -240,12 +233,12 @@ export default function TasksPage() { ...newTask, id: Date.now().toString(), // Simple ID generation }; - setTasks((prev) => [...prev, task]); + setTasks(prev => [...prev, task]); setShowNewTaskDialog(false); }; const updateTask = (taskId: string, updates: Partial) => { - setTasks((prev) => prev.map((task) => (task.id === taskId ? { ...task, ...updates } : task))); + setTasks(prev => prev.map(task => (task.id === taskId ? { ...task, ...updates } : task))); }; return ( @@ -314,94 +307,128 @@ export default function TasksPage() { label: 'Learn about tasks', href: '/help', }} - illustration={} + illustration={ + + } /> ) : ( -
- - - Total Tasks - - -
{tasks.length}
-

- {totalStoryPoints} story points total -

-
-
- - - - Completed - - - -
{completedTasks.length}
-

- {completedStoryPoints} story points -

-
-
- - - - In Progress - - - -
{inProgressTasks.length}
-

Active work items

-
-
- - - - Completion Rate - - -
- {Math.round((completedTasks.length / tasks.length) * 100)}% -
-

Task completion rate

-
-
-
+
+ + + Total Tasks + + +
{tasks.length}
+

+ {totalStoryPoints} story points total +

+
+
+ + + + Completed + + + +
{completedTasks.length}
+

+ {completedStoryPoints} story points +

+
+
+ + + + In Progress + + + +
{inProgressTasks.length}
+

Active work items

+
+
+ + + + Completion Rate + + +
+ {Math.round((completedTasks.length / tasks.length) * 100)}% +
+

Task completion rate

+
+
+
)} {/* Task Views */} {tasks.length > 0 && ( - - {viewMode === 'kanban' ? ( - - ) : ( - - )} - - - {activeTask ? : null} - - + + {viewMode === 'kanban' ? ( + + ) : ( + + )} + + + {activeTask ? : null} + + )} {/* New Task Dialog */} @@ -427,15 +454,15 @@ function KanbanView({ onToggleStatus: (taskId: string) => void; }) { const tasksByStatus = { - pending: tasks.filter((task) => task.status === 'pending'), - 'in-progress': tasks.filter((task) => task.status === 'in-progress'), - completed: tasks.filter((task) => task.status === 'completed'), + pending: tasks.filter(task => task.status === 'pending'), + 'in-progress': tasks.filter(task => task.status === 'in-progress'), + completed: tasks.filter(task => task.status === 'completed'), }; return (
- {statusOrder.map((status) => { + {statusOrder.map(status => { const statusInfo = statusConfig[status]; const StatusIcon = statusInfo.icon; const statusTasks = tasksByStatus[status]; @@ -486,11 +513,8 @@ function KanbanColumn({ ref={setNodeRef} className="border-muted-foreground/25 max-h-[600px] min-h-[200px] space-y-3 overflow-y-auto rounded-lg border-2 border-dashed p-4" > - task.id)} - strategy={verticalListSortingStrategy} - > - {tasks.map((task) => ( + task.id)} strategy={verticalListSortingStrategy}> + {tasks.map(task => ( ))} @@ -521,10 +545,7 @@ function TableView({ Tasks Table - task.id)} - strategy={verticalListSortingStrategy} - > + task.id)} strategy={verticalListSortingStrategy}> @@ -538,14 +559,14 @@ function TableView({ - {tasks.map((task) => ( + {tasks.map(task => ( setEditingTaskId(editing ? task.id : null)} + setEditing={editing => setEditingTaskId(editing ? task.id : null)} disabled={isDragging} /> ))} @@ -658,13 +679,13 @@ function SortableTableRow({
setEditValues({ ...editValues, title: e.target.value })} + onChange={e => setEditValues({ ...editValues, title: e.target.value })} placeholder="Task title" className="font-medium" />