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.tsxApps
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
{
"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
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
{
"$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
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.jsonCreate:
frontend/packages/shared/tsconfig.jsonCreate:
frontend/packages/shared/src/index.tsCreate:
frontend/packages/shared/src/server.tsMove:
frontend/src/lib/auth.ts→frontend/packages/shared/src/auth.tsMove:
frontend/src/lib/session.server.ts→frontend/packages/shared/src/session.server.tsMove:
frontend/src/lib/config.server.ts→frontend/packages/shared/src/config.server.tsMove:
frontend/src/lib/error-capture.ts→frontend/packages/shared/src/error-capture.tsMove:
frontend/src/lib/utils.ts→frontend/packages/shared/src/utils.ts[ ] Step 1: Create package.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
{
"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
export { cn, formatName, formatDate } from './utils';- [ ] Step 4: Create src/server.ts barrel export
export { getSession, destroySession } from './session.server';
export { config } from './config.server';
export { captureError } from './error-capture';- [ ] Step 5: Move files from frontend/src/lib/
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
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.jsonCreate:
frontend/packages/ui/tsconfig.jsonCreate:
frontend/packages/ui/src/index.tsCreate:
frontend/packages/ui/src/styles.cssMove:
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.ts→frontend/packages/ui/src/hooks/useTheme.tsMove:
frontend/src/hooks/useDebounce.ts→frontend/packages/ui/src/hooks/useDebounce.tsMove:
frontend/src/hooks/use-mobile.tsx→frontend/packages/ui/src/hooks/use-mobile.tsx[ ] Step 1: Create package.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
{
"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
// 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
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
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.jsonCreate:
frontend/packages/api/tsconfig.jsonCreate:
frontend/packages/api/src/index.tsMove:
frontend/src/api/generated.ts→frontend/packages/api/src/generated.tsMove:
frontend/src/api/hooks.ts→frontend/packages/api/src/hooks.tsMove:
frontend/src/api/queries.ts→frontend/packages/api/src/queries.tsMove:
frontend/src/api/invalidation.ts→frontend/packages/api/src/invalidation.tsMove:
frontend/src/api/mutator.ts→frontend/packages/api/src/mutator.tsMove:
frontend/src/api/paywall.ts→frontend/packages/api/src/paywall.tsMove:
frontend/openapi.json→frontend/packages/api/openapi.jsonMove:
frontend/orval.config.ts→frontend/packages/api/orval.config.ts[ ] Step 1: Create package.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
{
"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
export * from './generated';
export * from './hooks';
export * from './queries';
export * from './invalidation';
export * from './paywall';- [ ] Step 4: Move API files
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
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.jsonCreate:
frontend/packages/editor/tsconfig.jsonCreate:
frontend/packages/editor/src/index.tsMove:
frontend/src/components/editor/→frontend/packages/editor/src/[ ] Step 1: Create package.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
{
"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
export { TipTapEditor } from './TipTapEditor';
export { TipTapEditorLazy } from './TipTapEditorLazy';
export { EditorToolbar } from './EditorToolbar';
export { MentionList } from './MentionList';- [ ] Step 4: Move editor files
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
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.jsonCreate:
frontend/apps/web/vite.config.tsCreate:
frontend/apps/web/tsconfig.jsonMove:
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.tsx→frontend/apps/web/src/hooks/Move:
frontend/src/hooks/useSpacesList.ts→frontend/apps/web/src/hooks/Move:
frontend/src/hooks/useWebSocket.ts→frontend/apps/web/src/hooks/Move:
frontend/src/lib/api/→frontend/apps/web/src/lib/api/Move:
frontend/src/lib/cropImage.ts→frontend/apps/web/src/lib/Move:
frontend/src/lib/editor.ts→frontend/apps/web/src/lib/Move:
frontend/src/lib/posts.tsx→frontend/apps/web/src/lib/Move:
frontend/src/lib/reactions.ts→frontend/apps/web/src/lib/Move:
frontend/src/lib/upstash.server.ts→frontend/apps/web/src/lib/Move:
frontend/src/lib/video-thumbnails.ts→frontend/apps/web/src/lib/Move:
frontend/src/lib/video-types.ts→frontend/apps/web/src/lib/Move:
frontend/src/mocks/→frontend/apps/web/src/mocks/Move:
frontend/src/router.tsx→frontend/apps/web/src/Move:
frontend/src/routeTree.gen.ts→frontend/apps/web/src/Move:
frontend/src/server.ts→frontend/apps/web/src/Move:
frontend/src/start.ts→frontend/apps/web/src/Move:
frontend/src/styles.css→frontend/apps/web/src/Move:
frontend/public/→frontend/apps/web/public/[ ] Step 1: Create package.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
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
{
"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
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
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
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
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
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.jsonCreate:
frontend/apps/admin/vite.config.tsCreate:
frontend/apps/admin/tsconfig.jsonCreate:
frontend/apps/admin/index.htmlCreate:
frontend/apps/admin/src/main.tsxCreate:
frontend/apps/admin/src/styles.cssCreate:
frontend/apps/admin/src/routes/__root.tsxCreate:
frontend/apps/admin/src/routes/index.tsx[ ] Step 1: Create package.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
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
{
"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
<!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
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
@import "tailwindcss";- [ ] Step 7: Create src/routes/__root.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
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
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.tsxCreate:
frontend/apps/admin/src/routes/users.$userId.tsxCreate:
frontend/apps/admin/src/components/Users/UserList.tsxCreate:
frontend/apps/admin/src/components/Users/UserDetail.tsx[ ] Step 1: Create users route
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
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
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
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
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.tsxCreate:
frontend/apps/admin/src/components/Analytics/UsageChart.tsxCreate:
frontend/apps/admin/src/components/Analytics/EngagementMetrics.tsx[ ] Step 1: Create analytics route
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
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
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
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
{
// 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:
"scripts": {
"deploy": "wrangler deploy"
}- [ ] Step 3: Commit
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
# 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
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
git add -A
git commit -m "chore: complete monorepo migration"