Skip to content

Monorepo Migration Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use compose:subagent (recommended) or compose:execute to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Transform frontend/ into a Bun workspaces monorepo with Turborepo, creating shared packages for reusable code and a new admin app for user management and analytics.

Architecture: Bun workspaces with Turborepo for build orchestration. Four shared packages (ui, api, shared, editor) and two apps (web, admin). Each app deploys independently to Cloudflare Workers.

Tech Stack: Bun, Turborepo, React 19, TanStack Start, TanStack Router, TanStack Query, Vite, Tailwind CSS, shadcn/ui, Orval


File Structure

Packages

frontend/packages/
├── ui/
│   ├── package.json
│   ├── tsconfig.json
│   └── src/
│       ├── index.ts                    # Barrel export
│       ├── styles.css                  # Tailwind CSS + variables
│       ├── components/
│       │   ├── ui/                     # shadcn/ui components
│       │   └── app/                    # App-specific components
│       ├── hooks/
│       │   ├── useTheme.ts
│       │   ├── useDebounce.ts
│       │   └── use-mobile.tsx
│       └── lib/
│           └── utils.ts               # cn(), formatName(), etc.
├── api/
│   ├── package.json
│   ├── tsconfig.json
│   ├── openapi.json                   # Copied from frontend root
│   ├── orval.config.ts                # Moved from frontend root
│   └── src/
│       ├── index.ts                    # Barrel export
│       ├── generated.ts               # Orval output
│       ├── hooks.ts                    # Composed hooks
│       ├── queries.ts                  # Query key factories
│       ├── invalidation.ts            # Mutation invalidation
│       └── mutator.ts                  # Custom fetch wrapper
├── shared/
│   ├── package.json
│   ├── tsconfig.json
│   └── src/
│       ├── index.ts                    # Barrel export
│       ├── server.ts                   # Server-only exports
│       ├── auth.ts
│       ├── session.server.ts
│       ├── config.server.ts
│       ├── error-capture.ts
│       └── utils.ts
└── editor/
    ├── package.json
    ├── tsconfig.json
    └── src/
        ├── index.ts                    # Barrel export
        ├── TipTapEditor.tsx
        ├── TipTapEditorLazy.tsx
        ├── EditorToolbar.tsx
        └── MentionList.tsx

Apps

frontend/apps/
├── web/
│   ├── package.json
│   ├── vite.config.ts
│   ├── tsconfig.json
│   ├── wrangler.jsonc
│   ├── public/
│   └── src/
│       ├── routes/
│       ├── components/
│       ├── api/
│       ├── contexts/
│       ├── hooks/
│       ├── lib/
│       ├── mocks/
│       ├── router.tsx
│       ├── routeTree.gen.ts
│       ├── server.ts
│       ├── start.ts
│       └── styles.css
└── admin/
    ├── package.json
    ├── vite.config.ts
    ├── tsconfig.json
    ├── wrangler.jsonc
    └── src/
        ├── main.tsx
        ├── styles.css
        ├── routes/
        │   ├── __root.tsx
        │   ├── index.tsx
        │   ├── users.tsx
        │   ├── users.$userId.tsx
        │   └── analytics.tsx
        └── components/
            ├── Dashboard/
            ├── Users/
            └── Analytics/

Root Config

frontend/
├── package.json                        # Root workspace config
├── turbo.json                          # Turborepo pipeline
├── bun.lock
└── ...

Task 1: Create Root Workspace Config

Covers: [S2, S6]

Files:

  • Modify: frontend/package.json

  • [ ] Step 1: Update root package.json with workspaces config

json
{
  "name": "frontend",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "scripts": {
    "dev": "turbo dev",
    "dev:web": "turbo dev --filter=web",
    "dev:admin": "turbo dev --filter=admin",
    "build": "turbo build",
    "build:web": "turbo build --filter=web",
    "build:admin": "turbo build --filter=admin",
    "lint": "turbo lint",
    "type-check": "turbo type-check",
    "generate:api": "cd packages/api && bun run generate"
  },
  "devDependencies": {
    "turbo": "^2.5.0"
  }
}
  • [ ] Step 2: Install Turborepo

Run: cd frontend && bun install Expected: Turborepo installed, bun.lock updated

  • [ ] Step 3: Commit
bash
git add frontend/package.json frontend/bun.lock
git commit -m "chore: setup bun workspaces and turbotorepo"

Task 2: Create Turborepo Config

Covers: [S6]

Files:

  • Create: frontend/turbo.json

  • [ ] Step 1: Create turbo.json

json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {},
    "type-check": {
      "dependsOn": ["^build"]
    }
  }
}
  • [ ] Step 2: Commit
bash
git add frontend/turbo.json
git commit -m "chore: add turbotorepo pipeline config"

Task 3: Create packages/shared

Covers: [S3]

Files:

  • Create: frontend/packages/shared/package.json

  • Create: frontend/packages/shared/tsconfig.json

  • Create: frontend/packages/shared/src/index.ts

  • Create: frontend/packages/shared/src/server.ts

  • Move: frontend/src/lib/auth.tsfrontend/packages/shared/src/auth.ts

  • Move: frontend/src/lib/session.server.tsfrontend/packages/shared/src/session.server.ts

  • Move: frontend/src/lib/config.server.tsfrontend/packages/shared/src/config.server.ts

  • Move: frontend/src/lib/error-capture.tsfrontend/packages/shared/src/error-capture.ts

  • Move: frontend/src/lib/utils.tsfrontend/packages/shared/src/utils.ts

  • [ ] Step 1: Create package.json

json
{
  "name": "@repo/shared",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "exports": {
    ".": "./src/index.ts",
    "./server": "./src/server.ts"
  },
  "dependencies": {
    "@upstash/redis": "^1.38.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "typescript": "^5.8.3"
  }
}
  • [ ] Step 2: Create tsconfig.json
json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "paths": {}
  },
  "include": ["src"]
}
  • [ ] Step 3: Create src/index.ts barrel export
typescript
export { cn, formatName, formatDate } from './utils';
  • [ ] Step 4: Create src/server.ts barrel export
typescript
export { getSession, destroySession } from './session.server';
export { config } from './config.server';
export { captureError } from './error-capture';
  • [ ] Step 5: Move files from frontend/src/lib/
bash
cd frontend
mv src/lib/auth.ts packages/shared/src/auth.ts
mv src/lib/session.server.ts packages/shared/src/session.server.ts
mv src/lib/config.server.ts packages/shared/src/config.server.ts
mv src/lib/error-capture.ts packages/shared/src/error-capture.ts
mv src/lib/utils.ts packages/shared/src/utils.ts
  • [ ] Step 6: Update imports in moved files

Check each moved file for imports from @/ and update to relative imports.

  • [ ] Step 7: Commit
bash
git add frontend/packages/shared/
git commit -m "feat: create @repo/shared package with auth and utils"

Task 4: Create packages/ui

Covers: [S3]

Files:

  • Create: frontend/packages/ui/package.json

  • Create: frontend/packages/ui/tsconfig.json

  • Create: frontend/packages/ui/src/index.ts

  • Create: frontend/packages/ui/src/styles.css

  • Move: frontend/src/components/ui/frontend/packages/ui/src/components/ui/

  • Move: frontend/src/components/ui/app/frontend/packages/ui/src/components/app/

  • Move: frontend/src/hooks/useTheme.tsfrontend/packages/ui/src/hooks/useTheme.ts

  • Move: frontend/src/hooks/useDebounce.tsfrontend/packages/ui/src/hooks/useDebounce.ts

  • Move: frontend/src/hooks/use-mobile.tsxfrontend/packages/ui/src/hooks/use-mobile.tsx

  • [ ] Step 1: Create package.json

json
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "exports": {
    ".": "./src/index.ts",
    "./styles": "./src/styles.css"
  },
  "dependencies": {
    "@radix-ui/react-accordion": "^1.2.12",
    "@radix-ui/react-alert-dialog": "^1.1.15",
    "@radix-ui/react-aspect-ratio": "^1.1.8",
    "@radix-ui/react-avatar": "^1.1.11",
    "@radix-ui/react-checkbox": "^1.3.3",
    "@radix-ui/react-collapsible": "^1.1.12",
    "@radix-ui/react-context-menu": "^2.2.16",
    "@radix-ui/react-dialog": "^1.1.15",
    "@radix-ui/react-dropdown-menu": "^2.1.16",
    "@radix-ui/react-hover-card": "^1.1.15",
    "@radix-ui/react-label": "^2.1.8",
    "@radix-ui/react-menubar": "^1.1.16",
    "@radix-ui/react-navigation-menu": "^1.2.14",
    "@radix-ui/react-popover": "^1.1.15",
    "@radix-ui/react-progress": "^1.1.8",
    "@radix-ui/react-radio-group": "^1.3.8",
    "@radix-ui/react-scroll-area": "^1.2.10",
    "@radix-ui/react-select": "^2.2.6",
    "@radix-ui/react-separator": "^1.1.8",
    "@radix-ui/react-slider": "^1.3.6",
    "@radix-ui/react-slot": "^1.2.4",
    "@radix-ui/react-switch": "^1.2.6",
    "@radix-ui/react-tabs": "^1.1.13",
    "@radix-ui/react-toggle": "^1.1.10",
    "@radix-ui/react-toggle-group": "^1.1.11",
    "@radix-ui/react-tooltip": "^1.2.8",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "date-fns": "^4.1.0",
    "embla-carousel-react": "^8.6.0",
    "input-otp": "^1.4.2",
    "lucide-react": "^0.575.0",
    "react": "^19.2.0",
    "react-day-picker": "^9.14.0",
    "react-dom": "^19.2.0",
    "react-resizable-panels": "^4.6.5",
    "sonner": "^2.0.7",
    "tailwind-merge": "^3.5.0",
    "tw-animate-css": "^1.3.4",
    "vaul": "^1.1.2"
  },
  "devDependencies": {
    "@types/react": "^19.2.0",
    "@types/react-dom": "^19.2.0",
    "tailwindcss": "^4.2.1",
    "typescript": "^5.8.3"
  }
}
  • [ ] Step 2: Create tsconfig.json
json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "paths": {}
  },
  "include": ["src"]
}
  • [ ] Step 3: Create src/index.ts barrel export
typescript
// Components
export * from './components/ui/button';
export * from './components/ui/input';
export * from './components/ui/dialog';
// ... add all component exports

// App components
export * from './components/app/member-avatar';

// Hooks
export { useTheme } from './hooks/useTheme';
export { useDebounce } from './hooks/useDebounce';
export { useMobile } from './hooks/use-mobile';

// Utils
export { cn } from './lib/utils';
  • [ ] Step 4: Create src/styles.css

Copy Tailwind CSS variables and base styles from frontend/src/styles.css.

  • [ ] Step 5: Move component files
bash
cd frontend
mv src/components/ui packages/ui/src/components/ui
mkdir -p packages/ui/src/components/app
mv src/components/ui/app/* packages/ui/src/components/app/
mv src/hooks/useTheme.ts packages/ui/src/hooks/
mv src/hooks/useDebounce.ts packages/ui/src/hooks/
mv src/hooks/use-mobile.tsx packages/ui/src/hooks/
  • [ ] Step 6: Update imports in moved files

Check each moved file for imports from @/ and update to relative imports.

  • [ ] Step 7: Commit
bash
git add frontend/packages/ui/
git commit -m "feat: create @repo/ui package with shadcn components"

Task 5: Create packages/api

Covers: [S3]

Files:

  • Create: frontend/packages/api/package.json

  • Create: frontend/packages/api/tsconfig.json

  • Create: frontend/packages/api/src/index.ts

  • Move: frontend/src/api/generated.tsfrontend/packages/api/src/generated.ts

  • Move: frontend/src/api/hooks.tsfrontend/packages/api/src/hooks.ts

  • Move: frontend/src/api/queries.tsfrontend/packages/api/src/queries.ts

  • Move: frontend/src/api/invalidation.tsfrontend/packages/api/src/invalidation.ts

  • Move: frontend/src/api/mutator.tsfrontend/packages/api/src/mutator.ts

  • Move: frontend/src/api/paywall.tsfrontend/packages/api/src/paywall.ts

  • Move: frontend/openapi.jsonfrontend/packages/api/openapi.json

  • Move: frontend/orval.config.tsfrontend/packages/api/orval.config.ts

  • [ ] Step 1: Create package.json

json
{
  "name": "@repo/api",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "exports": {
    ".": "./src/index.ts"
  },
  "scripts": {
    "generate": "curl -s http://localhost:8000/api/schema/ > openapi.json && bunx orval"
  },
  "dependencies": {
    "@tanstack/react-query": "^5.83.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "orval": "^8.22.0",
    "typescript": "^5.8.3"
  }
}
  • [ ] Step 2: Create tsconfig.json
json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "paths": {}
  },
  "include": ["src"]
}
  • [ ] Step 3: Create src/index.ts barrel export
typescript
export * from './generated';
export * from './hooks';
export * from './queries';
export * from './invalidation';
export * from './paywall';
  • [ ] Step 4: Move API files
bash
cd frontend
mv src/api/generated.ts packages/api/src/
mv src/api/hooks.ts packages/api/src/
mv src/api/queries.ts packages/api/src/
mv src/api/invalidation.ts packages/api/src/
mv src/api/mutator.ts packages/api/src/
mv src/api/paywall.ts packages/api/src/
mv openapi.json packages/api/
mv orval.config.ts packages/api/
  • [ ] Step 5: Update imports in moved files

Check each moved file for imports from @/ and update to relative imports or @repo/shared.

  • [ ] Step 6: Update orval.config.ts output path

Update orval.config.ts to output to ./src/generated.ts instead of ../src/api/generated.ts.

  • [ ] Step 7: Commit
bash
git add frontend/packages/api/
git commit -m "feat: create @repo/api package with Orval hooks"

Task 6: Create packages/editor

Covers: [S3]

Files:

  • Create: frontend/packages/editor/package.json

  • Create: frontend/packages/editor/tsconfig.json

  • Create: frontend/packages/editor/src/index.ts

  • Move: frontend/src/components/editor/frontend/packages/editor/src/

  • [ ] Step 1: Create package.json

json
{
  "name": "@repo/editor",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "exports": {
    ".": "./src/index.ts"
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@tiptap/extension-highlight": "^3.28.0",
    "@tiptap/extension-image": "^3.28.0",
    "@tiptap/extension-link": "^3.28.0",
    "@tiptap/extension-mention": "^3.28.0",
    "@tiptap/extension-placeholder": "^3.28.0",
    "@tiptap/extension-task-item": "^3.28.0",
    "@tiptap/extension-task-list": "^3.28.0",
    "@tiptap/extension-text-align": "^3.28.0",
    "@tiptap/extension-underline": "^3.28.0",
    "@tiptap/pm": "^3.28.0",
    "@tiptap/react": "^3.28.0",
    "@tiptap/starter-kit": "^3.28.0",
    "react": "^19.2.0",
    "tippy.js": "^6.3.7"
  },
  "devDependencies": {
    "@types/react": "^19.2.0",
    "typescript": "^5.8.3"
  }
}
  • [ ] Step 2: Create tsconfig.json
json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "paths": {
      "@repo/ui": ["../ui/src"]
    }
  },
  "include": ["src"]
}
  • [ ] Step 3: Create src/index.ts barrel export
typescript
export { TipTapEditor } from './TipTapEditor';
export { TipTapEditorLazy } from './TipTapEditorLazy';
export { EditorToolbar } from './EditorToolbar';
export { MentionList } from './MentionList';
  • [ ] Step 4: Move editor files
bash
cd frontend
mv src/components/editor/* packages/editor/src/
  • [ ] Step 5: Update imports in moved files

Check each moved file for imports from @/ and update to @repo/ui or relative imports.

  • [ ] Step 6: Commit
bash
git add frontend/packages/editor/
git commit -m "feat: create @repo/editor package with TipTap components"

Task 7: Create apps/web Structure

Covers: [S4]

Files:

  • Create: frontend/apps/web/package.json

  • Create: frontend/apps/web/vite.config.ts

  • Create: frontend/apps/web/tsconfig.json

  • Move: frontend/src/routes/frontend/apps/web/src/routes/

  • Move: frontend/src/components/circle/frontend/apps/web/src/components/circle/

  • Move: frontend/src/components/chat/frontend/apps/web/src/components/chat/

  • Move: frontend/src/components/courses/frontend/apps/web/src/components/courses/

  • Move: frontend/src/components/events/frontend/apps/web/src/components/events/

  • Move: frontend/src/components/gamification/frontend/apps/web/src/components/gamification/

  • Move: frontend/src/components/moderation/frontend/apps/web/src/components/moderation/

  • Move: frontend/src/components/notifications/frontend/apps/web/src/components/notifications/

  • Move: frontend/src/components/onboarding/frontend/apps/web/src/components/onboarding/

  • Move: frontend/src/components/settings/frontend/apps/web/src/components/settings/

  • Move: frontend/src/components/account/frontend/apps/web/src/components/account/

  • Move: frontend/src/components/auth/frontend/apps/web/src/components/auth/

  • Move: frontend/src/components/debug-bar/frontend/apps/web/src/components/debug-bar/

  • Move: frontend/src/contexts/frontend/apps/web/src/contexts/

  • Move: frontend/src/hooks/useNotificationSocket.tsxfrontend/apps/web/src/hooks/

  • Move: frontend/src/hooks/useSpacesList.tsfrontend/apps/web/src/hooks/

  • Move: frontend/src/hooks/useWebSocket.tsfrontend/apps/web/src/hooks/

  • Move: frontend/src/lib/api/frontend/apps/web/src/lib/api/

  • Move: frontend/src/lib/cropImage.tsfrontend/apps/web/src/lib/

  • Move: frontend/src/lib/editor.tsfrontend/apps/web/src/lib/

  • Move: frontend/src/lib/posts.tsxfrontend/apps/web/src/lib/

  • Move: frontend/src/lib/reactions.tsfrontend/apps/web/src/lib/

  • Move: frontend/src/lib/upstash.server.tsfrontend/apps/web/src/lib/

  • Move: frontend/src/lib/video-thumbnails.tsfrontend/apps/web/src/lib/

  • Move: frontend/src/lib/video-types.tsfrontend/apps/web/src/lib/

  • Move: frontend/src/mocks/frontend/apps/web/src/mocks/

  • Move: frontend/src/router.tsxfrontend/apps/web/src/

  • Move: frontend/src/routeTree.gen.tsfrontend/apps/web/src/

  • Move: frontend/src/server.tsfrontend/apps/web/src/

  • Move: frontend/src/start.tsfrontend/apps/web/src/

  • Move: frontend/src/styles.cssfrontend/apps/web/src/

  • Move: frontend/public/frontend/apps/web/public/

  • [ ] Step 1: Create package.json

json
{
  "name": "web",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "sideEffects": false,
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/api": "workspace:*",
    "@repo/shared": "workspace:*",
    "@repo/editor": "workspace:*",
    "@tanstack/react-query": "^5.83.0",
    "@tanstack/react-router": "^1.168.25",
    "@tanstack/react-start": "^1.167.50",
    "react": "^19.2.0",
    "react-dom": "^19.2.0"
  },
  "devDependencies": {
    "@tanstack/router-plugin": "^1.167.28",
    "nitro": "3.0.260429-beta",
    "typescript": "^5.8.3",
    "vite": "^7.3.1",
    "vite-tsconfig-paths": "^6.0.2"
  }
}
  • [ ] Step 2: Create vite.config.ts
typescript
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
import tsconfigPaths from "vite-tsconfig-paths";
import react from "@vitejs/plugin-react";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";

export default defineConfig({
  plugins: [
    tailwindcss(),
    tsconfigPaths({ projects: ["./tsconfig.json"] }),
    tanstackStart({
      server: { entry: "server" },
    }),
    react(),
  ],
  resolve: {
    alias: {
      "@": `${process.cwd()}/src`,
    },
    dedupe: [
      "react",
      "react-dom",
      "react/jsx-runtime",
      "react/jsx-dev-runtime",
      "@tanstack/react-query",
      "@tanstack/query-core",
    ],
  },
  server: {
    host: "::",
    port: 3000,
    proxy: {
      "/ws": {
        target: process.env.DJANGO_API_BASE_URL ?? "http://localhost:8000",
        ws: true,
        changeOrigin: false,
      },
      "/api/headless/v1": {
        target: process.env.DJANGO_API_BASE_URL ?? "http://localhost:8000",
        changeOrigin: false,
        headers: {
          "X-Community-Slug": "tenant",
        },
      },
      "/api": {
        target: process.env.DJANGO_API_BASE_URL ?? "http://localhost:8000",
        changeOrigin: false,
      },
      "/accounts": {
        target: process.env.DJANGO_API_BASE_URL ?? "http://localhost:8000",
        changeOrigin: false,
      },
    },
    watch: {
      awaitWriteFinish: {
        stabilityThreshold: 1000,
        pollInterval: 100,
      },
    },
  },
});
  • [ ] Step 3: Create tsconfig.json
json
{
  "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"],
  "compilerOptions": {
    "target": "ES2022",
    "jsx": "react-jsx",
    "module": "ESNext",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "types": ["vite/client"],
    "moduleResolution": "Bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": false,
    "noEmit": true,
    "skipLibCheck": true,
    "strict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,
    "paths": {
      "@/*": ["./src/*"],
      "@repo/ui": ["../packages/ui/src"],
      "@repo/api": ["../packages/api/src"],
      "@repo/shared": ["../packages/shared/src"],
      "@repo/editor": ["../packages/editor/src"]
    }
  }
}
  • [ ] Step 4: Move web app files
bash
cd frontend
mkdir -p apps/web/src
mkdir -p apps/web/public

# Move source files
mv src/routes apps/web/src/
mv src/components/circle apps/web/src/components/
mv src/components/chat apps/web/src/components/
mv src/components/courses apps/web/src/components/
mv src/components/events apps/web/src/components/
mv src/components/gamification apps/web/src/components/
mv src/components/moderation apps/web/src/components/
mv src/components/notifications apps/web/src/components/
mv src/components/onboarding apps/web/src/components/
mv src/components/settings apps/web/src/components/
mv src/components/account apps/web/src/components/
mv src/components/auth apps/web/src/components/
mv src/components/debug-bar apps/web/src/components/
mv src/contexts apps/web/src/
mv src/hooks/useNotificationSocket.tsx apps/web/src/hooks/
mv src/hooks/useSpacesList.ts apps/web/src/hooks/
mv src/hooks/useWebSocket.ts apps/web/src/hooks/
mv src/lib/api apps/web/src/lib/
mv src/lib/cropImage.ts apps/web/src/lib/
mv src/lib/editor.ts apps/web/src/lib/
mv src/lib/posts.tsx apps/web/src/lib/
mv src/lib/reactions.ts apps/web/src/lib/
mv src/lib/upstash.server.ts apps/web/src/lib/
mv src/lib/video-thumbnails.ts apps/web/src/lib/
mv src/lib/video-types.ts apps/web/src/lib/
mv src/mocks apps/web/src/
mv src/router.tsx apps/web/src/
mv src/routeTree.gen.ts apps/web/src/
mv src/server.ts apps/web/src/
mv src/start.ts apps/web/src/
mv src/styles.css apps/web/src/
mv public/* apps/web/public/
  • [ ] Step 5: Update imports in moved files

Update all imports from @/components/ui/ to @repo/ui/components/ui/ Update all imports from @/hooks/ to @repo/ui/hooks/ or @repo/api/ Update all imports from @/api/ to @repo/api/ Update all imports from @/lib/ to @repo/shared/ or relative imports

  • [ ] Step 6: Commit
bash
git add frontend/apps/web/
git commit -m "feat: create apps/web with moved source files"

Task 8: Clean Up frontend/src

Covers: [S4]

Files:

  • Delete: frontend/src/ (after moving all files)

  • [ ] Step 1: Remove empty directories

bash
cd frontend
rm -rf src/components
rm -rf src/hooks
rm -rf src/lib
rm -rf src/contexts
rm -rf src/mocks
rm -rf src/api
rm -rf src/routes
  • [ ] Step 2: Verify no files remain

Run: find frontend/src -type f Expected: No files found

  • [ ] Step 3: Commit
bash
git add -A frontend/src/
git commit -m "chore: remove empty frontend/src directory"

Task 9: Verify Web App Works

Covers: [S4]

Files:

  • None (verification only)

  • [ ] Step 1: Install dependencies

Run: cd frontend && bun install Expected: All workspace dependencies installed

  • [ ] Step 2: Run type check

Run: cd frontend && bun run type-check Expected: No TypeScript errors

  • [ ] Step 3: Start web dev server

Run: cd frontend && bun run dev:web Expected: Dev server starts on port 3000

  • [ ] Step 4: Verify app loads

Open browser to http://localhost:3000 Expected: App loads without errors

  • [ ] Step 5: Commit any fixes
bash
git add -A
git commit -m "fix: update imports for monorepo structure"

Task 10: Create apps/admin Scaffold

Covers: [S4]

Files:

  • Create: frontend/apps/admin/package.json

  • Create: frontend/apps/admin/vite.config.ts

  • Create: frontend/apps/admin/tsconfig.json

  • Create: frontend/apps/admin/index.html

  • Create: frontend/apps/admin/src/main.tsx

  • Create: frontend/apps/admin/src/styles.css

  • Create: frontend/apps/admin/src/routes/__root.tsx

  • Create: frontend/apps/admin/src/routes/index.tsx

  • [ ] Step 1: Create package.json

json
{
  "name": "admin",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite --port 3001",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/api": "workspace:*",
    "@repo/shared": "workspace:*",
    "@tanstack/react-query": "^5.83.0",
    "@tanstack/react-router": "^1.168.25",
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "recharts": "^2.15.4"
  },
  "devDependencies": {
    "@tanstack/router-plugin": "^1.167.28",
    "@types/react": "^19.2.0",
    "@types/react-dom": "^19.2.0",
    "@vitejs/plugin-react": "^5.0.4",
    "typescript": "^5.8.3",
    "vite": "^7.3.1",
    "vite-tsconfig-paths": "^6.0.2"
  }
}
  • [ ] Step 2: Create vite.config.ts
typescript
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
import tsconfigPaths from "vite-tsconfig-paths";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [
    tailwindcss(),
    tsconfigPaths({ projects: ["./tsconfig.json"] }),
    react(),
  ],
  resolve: {
    alias: {
      "@": `${process.cwd()}/src`,
    },
    dedupe: [
      "react",
      "react-dom",
      "react/jsx-runtime",
      "react/jsx-dev-runtime",
      "@tanstack/react-query",
      "@tanstack/query-core",
    ],
  },
  server: {
    host: "::",
    port: 3001,
    proxy: {
      "/api": {
        target: process.env.DJANGO_API_BASE_URL ?? "http://localhost:8000",
        changeOrigin: false,
      },
      "/accounts": {
        target: process.env.DJANGO_API_BASE_URL ?? "http://localhost:8000",
        changeOrigin: false,
      },
    },
  },
});
  • [ ] Step 3: Create tsconfig.json
json
{
  "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"],
  "compilerOptions": {
    "target": "ES2022",
    "jsx": "react-jsx",
    "module": "ESNext",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "types": ["vite/client"],
    "moduleResolution": "Bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": false,
    "noEmit": true,
    "skipLibCheck": true,
    "strict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,
    "paths": {
      "@/*": ["./src/*"],
      "@repo/ui": ["../packages/ui/src"],
      "@repo/api": ["../packages/api/src"],
      "@repo/shared": ["../packages/shared/src"]
    }
  }
}
  • [ ] Step 4: Create index.html
html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Admin Panel</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
  • [ ] Step 5: Create src/main.tsx
tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider, createRouter } from '@tanstack/react-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { routeTree } from './routeTree.gen';
import '@repo/ui/styles';
import './styles.css';

const router = createRouter({ routeTree });

declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router;
  }
}

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  </React.StrictMode>
);
  • [ ] Step 6: Create src/styles.css
css
@import "tailwindcss";
  • [ ] Step 7: Create src/routes/__root.tsx
tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router';

function RootComponent() {
  return (
    <div className="min-h-screen bg-background">
      <nav className="border-b">
        <div className="container mx-auto px-4 py-4">
          <div className="flex items-center gap-6">
            <Link to="/" className="text-xl font-bold">
              Admin Panel
            </Link>
            <Link to="/users" className="text-sm text-muted-foreground hover:text-foreground">
              Users
            </Link>
            <Link to="/analytics" className="text-sm text-muted-foreground hover:text-foreground">
              Analytics
            </Link>
          </div>
        </div>
      </nav>
      <main className="container mx-auto px-4 py-8">
        <Outlet />
      </main>
    </div>
  );
}

export const Route = createRootRoute({
  component: RootComponent,
});
  • [ ] Step 8: Create src/routes/index.tsx
tsx
import { createFileRoute } from '@tanstack/react-router';

function Dashboard() {
  return (
    <div>
      <h1 className="text-2xl font-bold mb-4">Dashboard</h1>
      <p className="text-muted-foreground">Welcome to the admin panel.</p>
    </div>
  );
}

export const Route = createFileRoute('/')({
  component: Dashboard,
});
  • [ ] Step 9: Commit
bash
git add frontend/apps/admin/
git commit -m "feat: scaffold admin app with basic layout"

Task 11: Create Admin User Management Pages

Covers: [S4]

Files:

  • Create: frontend/apps/admin/src/routes/users.tsx

  • Create: frontend/apps/admin/src/routes/users.$userId.tsx

  • Create: frontend/apps/admin/src/components/Users/UserList.tsx

  • Create: frontend/apps/admin/src/components/Users/UserDetail.tsx

  • [ ] Step 1: Create users route

tsx
import { createFileRoute } from '@tanstack/react-router';
import { UserList } from '@/components/Users/UserList';

function Users() {
  return (
    <div>
      <h1 className="text-2xl font-bold mb-4">User Management</h1>
      <UserList />
    </div>
  );
}

export const Route = createFileRoute('/users')({
  component: Users,
});
  • [ ] Step 2: Create user detail route
tsx
import { createFileRoute } from '@tanstack/react-router';
import { UserDetail } from '@/components/Users/UserDetail';

function UserDetailPage() {
  const { userId } = Route.useParams();
  return (
    <div>
      <h1 className="text-2xl font-bold mb-4">User Detail</h1>
      <UserDetail userId={userId} />
    </div>
  );
}

export const Route = createFileRoute('/users/$userId')({
  component: UserDetailPage,
});
  • [ ] Step 3: Create UserList component
tsx
import { useQuery } from '@tanstack/react-query';
import { Link } from '@tanstack/react-router';
import { useUsersControllerList } from '@repo/api';
import { Button } from '@repo/ui/components/ui/button';
import { Input } from '@repo/ui/components/ui/input';
import { useState } from 'react';

export function UserList() {
  const [search, setSearch] = useState('');
  const { data, isLoading } = useUsersControllerList({});

  if (isLoading) {
    return <div className="text-muted-foreground">Loading...</div>;
  }

  return (
    <div className="space-y-4">
      <Input
        placeholder="Search users..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        className="max-w-sm"
      />
      <div className="border rounded-lg">
        <table className="w-full">
          <thead>
            <tr className="border-b">
              <th className="text-left p-3">Email</th>
              <th className="text-left p-3">Name</th>
              <th className="text-left p-3">Joined</th>
              <th className="text-left p-3">Actions</th>
            </tr>
          </thead>
          <tbody>
            {data?.results?.map((user) => (
              <tr key={user.id} className="border-b">
                <td className="p-3">{user.email}</td>
                <td className="p-3">{user.name || '-'}</td>
                <td className="p-3">{new Date(user.dateJoined).toLocaleDateString()}</td>
                <td className="p-3">
                  <Link to="/users/$userId" params={{ userId: user.id }}>
                    <Button variant="ghost" size="sm">View</Button>
                  </Link>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}
  • [ ] Step 4: Create UserDetail component
tsx
import { useQuery } from '@tanstack/react-query';
import { useUsersControllerRetrieve } from '@repo/api';
import { Button } from '@repo/ui/components/ui/button';

interface UserDetailProps {
  userId: string;
}

export function UserDetail({ userId }: UserDetailProps) {
  const { data, isLoading } = useUsersControllerRetrieve(userId);

  if (isLoading) {
    return <div className="text-muted-foreground">Loading...</div>;
  }

  if (!data) {
    return <div className="text-muted-foreground">User not found</div>;
  }

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <div>
          <h2 className="text-xl font-semibold">{data.name || data.email}</h2>
          <p className="text-muted-foreground">{data.email}</p>
        </div>
        <div className="flex gap-2">
          <Button variant="outline">Ban User</Button>
          <Button variant="destructive">Delete User</Button>
        </div>
      </div>
      
      <div className="grid grid-cols-2 gap-4">
        <div className="border rounded-lg p-4">
          <h3 className="font-medium mb-2">Account Info</h3>
          <dl className="space-y-1 text-sm">
            <dt className="text-muted-foreground">Joined</dt>
            <dd>{new Date(data.dateJoined).toLocaleDateString()}</dd>
            <dt className="text-muted-foreground">Last Login</dt>
            <dd>{data.lastLogin ? new Date(data.lastLogin).toLocaleDateString() : 'Never'}</dd>
          </dl>
        </div>
        <div className="border rounded-lg p-4">
          <h3 className="font-medium mb-2">Stats</h3>
          <dl className="space-y-1 text-sm">
            <dt className="text-muted-foreground">Posts</dt>
            <dd>{data.postCount || 0}</dd>
            <dt className="text-muted-foreground">Comments</dt>
            <dd>{data.commentCount || 0}</dd>
          </dl>
        </div>
      </div>
    </div>
  );
}
  • [ ] Step 5: Commit
bash
git add frontend/apps/admin/src/routes/users*.tsx frontend/apps/admin/src/components/Users/
git commit -m "feat: add user management pages to admin"

Task 12: Create Admin Analytics Pages

Covers: [S4]

Files:

  • Create: frontend/apps/admin/src/routes/analytics.tsx

  • Create: frontend/apps/admin/src/components/Analytics/UsageChart.tsx

  • Create: frontend/apps/admin/src/components/Analytics/EngagementMetrics.tsx

  • [ ] Step 1: Create analytics route

tsx
import { createFileRoute } from '@tanstack/react-router';
import { UsageChart } from '@/components/Analytics/UsageChart';
import { EngagementMetrics } from '@/components/Analytics/EngagementMetrics';

function Analytics() {
  return (
    <div className="space-y-6">
      <h1 className="text-2xl font-bold">Analytics</h1>
      <EngagementMetrics />
      <UsageChart />
    </div>
  );
}

export const Route = createFileRoute('/analytics')({
  component: Analytics,
});
  • [ ] Step 2: Create EngagementMetrics component
tsx
import { useQuery } from '@tanstack/react-query';
import { useAnalyticsControllerGetEngagement } from '@repo/api';

export function EngagementMetrics() {
  const { data, isLoading } = useAnalyticsControllerGetEngagement({});

  if (isLoading) {
    return <div className="text-muted-foreground">Loading metrics...</div>;
  }

  return (
    <div className="grid grid-cols-4 gap-4">
      <div className="border rounded-lg p-4">
        <h3 className="text-sm font-medium text-muted-foreground">Total Users</h3>
        <p className="text-2xl font-bold">{data?.totalUsers || 0}</p>
      </div>
      <div className="border rounded-lg p-4">
        <h3 className="text-sm font-medium text-muted-foreground">Active Today</h3>
        <p className="text-2xl font-bold">{data?.activeToday || 0}</p>
      </div>
      <div className="border rounded-lg p-4">
        <h3 className="text-sm font-medium text-muted-foreground">Total Posts</h3>
        <p className="text-2xl font-bold">{data?.totalPosts || 0}</p>
      </div>
      <div className="border rounded-lg p-4">
        <h3 className="text-sm font-medium text-muted-foreground">Total Comments</h3>
        <p className="text-2xl font-bold">{data?.totalComments || 0}</p>
      </div>
    </div>
  );
}
  • [ ] Step 3: Create UsageChart component
tsx
import { useQuery } from '@tanstack/react-query';
import { useAnalyticsControllerGetUsageOverTime } from '@repo/api';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';

export function UsageChart() {
  const { data, isLoading } = useAnalyticsControllerGetUsageOverTime({
    period: '30d',
  });

  if (isLoading) {
    return <div className="text-muted-foreground">Loading chart...</div>;
  }

  return (
    <div className="border rounded-lg p-4">
      <h3 className="font-medium mb-4">Usage Over Time (30 days)</h3>
      <ResponsiveContainer width="100%" height={300}>
        <LineChart data={data || []}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="date" />
          <YAxis />
          <Tooltip />
          <Line type="monotone" dataKey="activeUsers" stroke="#8884d8" name="Active Users" />
          <Line type="monotone" dataKey="newUsers" stroke="#82ca9d" name="New Users" />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
}
  • [ ] Step 4: Commit
bash
git add frontend/apps/admin/src/routes/analytics.tsx frontend/apps/admin/src/components/Analytics/
git commit -m "feat: add analytics pages to admin"

Task 13: Create wrangler.jsonc for Admin

Covers: [S7]

Files:

  • Create: frontend/apps/admin/wrangler.jsonc

  • [ ] Step 1: Create wrangler.jsonc

jsonc
{
  // https://developers.cloudflare.com/workers/wrangler/configuration/
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "admin",
  "main": "dist/_worker.js",
  "compatibility_date": "2024-01-01",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "dist",
    "binding": "ASSETS",
  },
}
  • [ ] Step 2: Add deploy script to package.json

Update frontend/apps/admin/package.json to add:

json
"scripts": {
  "deploy": "wrangler deploy"
}
  • [ ] Step 3: Commit
bash
git add frontend/apps/admin/wrangler.jsonc frontend/apps/admin/package.json
git commit -m "chore: add wrangler config for admin deployment"

Task 14: Update Makefile

Covers: [S7]

Files:

  • Modify: Makefile

  • [ ] Step 1: Add new make targets

makefile
# TanStack SPA (updated for monorepo)
f-install:
	cd frontend && bun install

f-dev:
	cd frontend && bun run dev:web

f-build:
	cd frontend && bun run build:web

f-admin-dev:
	cd frontend && bun run dev:admin

f-admin-build:
	cd frontend && bun run build:admin

f-lint:
	cd frontend && bun run lint

f-type-check:
	cd frontend && bun run type-check

f-generate-api:
	cd frontend && bun run generate:api
  • [ ] Step 2: Commit
bash
git add Makefile
git commit -m "chore: update Makefile with monorepo commands"

Task 15: Final Verification

Covers: [S10]

Files:

  • None (verification only)

  • [ ] Step 1: Install all dependencies

Run: cd frontend && bun install Expected: All workspace dependencies installed

  • [ ] Step 2: Run type check for all packages

Run: cd frontend && bun run type-check Expected: No TypeScript errors

  • [ ] Step 3: Build all packages

Run: cd frontend && bun run build Expected: All packages and apps build successfully

  • [ ] Step 4: Start web app

Run: cd frontend && bun run dev:web Expected: Web app starts on port 3000

  • [ ] Step 5: Start admin app

Run: cd frontend && bun run dev:admin Expected: Admin app starts on port 3001

  • [ ] Step 6: Verify both apps load

Open browser to http://localhost:3000 and http://localhost:3001 Expected: Both apps load without errors

  • [ ] Step 7: Final commit
bash
git add -A
git commit -m "chore: complete monorepo migration"

Strum — Documentação.