mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 03:11:13 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
523
migration/BUN_GUIDE.md
Normal file
523
migration/BUN_GUIDE.md
Normal file
@@ -0,0 +1,523 @@
|
||||
# Bun Setup & Usage Guide
|
||||
|
||||
**For:** ThrillWiki Next.js 15 Migration
|
||||
**Last Updated:** November 9, 2025
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
Bun is an all-in-one JavaScript runtime & toolkit that replaces Node.js, npm, yarn, and webpack. It's significantly faster than traditional tools and is the official package manager for this migration.
|
||||
|
||||
**Why Bun?**
|
||||
- ⚡ 25x faster than npm
|
||||
- 🚀 Fast startup times
|
||||
- 📦 All-in-one (runtime + bundler + package manager)
|
||||
- 🔄 Drop-in replacement for Node.js/npm
|
||||
- ✅ Compatible with Next.js 15
|
||||
|
||||
---
|
||||
|
||||
## 📥 Installation
|
||||
|
||||
### macOS & Linux
|
||||
|
||||
```bash
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
```
|
||||
|
||||
### Windows (WSL2)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
```
|
||||
|
||||
### Verify Installation
|
||||
|
||||
```bash
|
||||
bun --version
|
||||
# Should output: 1.x.x
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Basic Commands
|
||||
|
||||
### Package Management
|
||||
|
||||
```bash
|
||||
# Install dependencies from package.json
|
||||
bun install
|
||||
|
||||
# Install a specific package
|
||||
bun add <package-name>
|
||||
|
||||
# Install as dev dependency
|
||||
bun add -d <package-name>
|
||||
|
||||
# Install globally
|
||||
bun add -g <package-name>
|
||||
|
||||
# Remove a package
|
||||
bun remove <package-name>
|
||||
|
||||
# Update packages
|
||||
bun update
|
||||
|
||||
# Update a specific package
|
||||
bun update <package-name>
|
||||
```
|
||||
|
||||
### Running Scripts
|
||||
|
||||
```bash
|
||||
# Run a package.json script
|
||||
bun run dev
|
||||
bun run build
|
||||
bun run start
|
||||
bun run test
|
||||
|
||||
# Or shorter (bun run is optional)
|
||||
bun dev
|
||||
bun build
|
||||
bun start
|
||||
bun test
|
||||
```
|
||||
|
||||
### Running Files
|
||||
|
||||
```bash
|
||||
# Run a TypeScript/JavaScript file directly
|
||||
bun index.ts
|
||||
bun server.js
|
||||
|
||||
# Run with watch mode (auto-restart on changes)
|
||||
bun --watch index.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Common ThrillWiki Commands
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Start Next.js development server with Turbopack
|
||||
bun run dev
|
||||
|
||||
# Or shorter
|
||||
bun dev
|
||||
|
||||
# Expected output:
|
||||
# ▲ Next.js 15.x.x
|
||||
# - Local: http://localhost:3000
|
||||
# - Turbopack: ✓ Enabled
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Create production build
|
||||
bun run build
|
||||
|
||||
# This will:
|
||||
# 1. Compile TypeScript
|
||||
# 2. Build Next.js pages
|
||||
# 3. Optimize bundles
|
||||
# 4. Generate static pages (ISR/SSG)
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
# Start production server (after build)
|
||||
bun run start
|
||||
|
||||
# Server will run on http://localhost:3000
|
||||
```
|
||||
|
||||
### Type Checking
|
||||
|
||||
```bash
|
||||
# Run TypeScript compiler
|
||||
bun run type-check
|
||||
|
||||
# This runs: tsc --noEmit
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Run ESLint
|
||||
bun run lint
|
||||
|
||||
# Fix auto-fixable issues
|
||||
bun run lint --fix
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
bun test
|
||||
|
||||
# Run tests in watch mode
|
||||
bun test --watch
|
||||
|
||||
# Run tests with coverage
|
||||
bun test --coverage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Project Setup
|
||||
|
||||
### Initialize New Next.js Project
|
||||
|
||||
```bash
|
||||
# Create new Next.js 15 project with Bun
|
||||
bun create next-app@latest my-project --typescript --tailwind --app
|
||||
|
||||
cd my-project
|
||||
```
|
||||
|
||||
### Migrate Existing Project to Bun
|
||||
|
||||
```bash
|
||||
# Remove old lock files
|
||||
rm package-lock.json
|
||||
rm yarn.lock
|
||||
rm pnpm-lock.yaml
|
||||
|
||||
# Install with Bun
|
||||
bun install
|
||||
|
||||
# This creates bun.lockb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### package.json Scripts
|
||||
|
||||
Update your `package.json` scripts to use Bun:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev --turbo",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"type-check": "tsc --noEmit",
|
||||
"test": "vitest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### .bunfig.toml (Optional)
|
||||
|
||||
Create `.bunfig.toml` in project root for Bun configuration:
|
||||
|
||||
```toml
|
||||
# Bun configuration
|
||||
|
||||
[install]
|
||||
# Use exact versions (like npm's --exact)
|
||||
exact = true
|
||||
|
||||
# Cache directory
|
||||
cache = ".bun-cache"
|
||||
|
||||
# Optional: Configure registry
|
||||
# registry = "https://registry.npmjs.org"
|
||||
|
||||
[test]
|
||||
# Test configuration
|
||||
coverage = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 NPM to Bun Command Mapping
|
||||
|
||||
| NPM Command | Bun Command | Description |
|
||||
|------------|-------------|-------------|
|
||||
| `npm install` | `bun install` | Install dependencies |
|
||||
| `npm install <pkg>` | `bun add <pkg>` | Add package |
|
||||
| `npm install -D <pkg>` | `bun add -d <pkg>` | Add dev dependency |
|
||||
| `npm uninstall <pkg>` | `bun remove <pkg>` | Remove package |
|
||||
| `npm run <script>` | `bun run <script>` | Run script |
|
||||
| `npm run <script>` | `bun <script>` | Run script (shorter) |
|
||||
| `npm update` | `bun update` | Update packages |
|
||||
| `npm ci` | `bun install --frozen-lockfile` | Clean install |
|
||||
| `npm test` | `bun test` | Run tests |
|
||||
| `npx <cmd>` | `bunx <cmd>` | Execute package |
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Bun with Next.js 15
|
||||
|
||||
### Development Server
|
||||
|
||||
```bash
|
||||
# Start with Turbopack (Next.js 15)
|
||||
bun dev
|
||||
|
||||
# Features:
|
||||
# - Hot Module Replacement (HMR)
|
||||
# - Fast Refresh
|
||||
# - TypeScript compilation
|
||||
# - CSS/Tailwind compilation
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Bun reads `.env.local` automatically:
|
||||
|
||||
```bash
|
||||
# .env.local
|
||||
NEXT_PUBLIC_DJANGO_API_URL=http://localhost:8000/api/v1
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=xxx
|
||||
```
|
||||
|
||||
Access in code:
|
||||
|
||||
```typescript
|
||||
const apiUrl = process.env.NEXT_PUBLIC_DJANGO_API_URL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing with Bun
|
||||
|
||||
### Vitest Setup
|
||||
|
||||
```bash
|
||||
# Install Vitest
|
||||
bun add -d vitest @testing-library/react @testing-library/jest-dom
|
||||
```
|
||||
|
||||
### vitest.config.ts
|
||||
|
||||
```typescript
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './'),
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
bun test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Common Issues & Solutions
|
||||
|
||||
### Issue: `bun: command not found`
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Reload shell configuration
|
||||
source ~/.bashrc # or ~/.zshrc
|
||||
|
||||
# Or restart terminal
|
||||
```
|
||||
|
||||
### Issue: Package installation fails
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Clear cache and reinstall
|
||||
rm -rf node_modules bun.lockb
|
||||
bun install
|
||||
```
|
||||
|
||||
### Issue: Next.js dev server won't start
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check port 3000 is free
|
||||
lsof -i :3000
|
||||
|
||||
# Kill process if needed
|
||||
kill -9 <PID>
|
||||
|
||||
# Restart dev server
|
||||
bun dev
|
||||
```
|
||||
|
||||
### Issue: TypeScript errors after installing packages
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Regenerate types
|
||||
bun run type-check
|
||||
|
||||
# Or restart TS server in VS Code
|
||||
# Cmd+Shift+P → "TypeScript: Restart TS Server"
|
||||
```
|
||||
|
||||
### Issue: Bun vs Node.js compatibility
|
||||
|
||||
Some packages may not work with Bun yet. Use Node.js fallback:
|
||||
|
||||
```bash
|
||||
# Run with Node.js instead
|
||||
node --loader tsx index.ts
|
||||
|
||||
# Or use NODE_ENV
|
||||
NODE_ENV=production node index.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Performance Comparison
|
||||
|
||||
### Install Speed
|
||||
|
||||
```bash
|
||||
# NPM
|
||||
time npm install # ~45s
|
||||
|
||||
# Yarn
|
||||
time yarn install # ~35s
|
||||
|
||||
# Bun
|
||||
time bun install # ~2s ⚡
|
||||
```
|
||||
|
||||
### Script Execution
|
||||
|
||||
```bash
|
||||
# NPM
|
||||
time npm run build # ~120s
|
||||
|
||||
# Bun
|
||||
time bun run build # ~90s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Advanced Usage
|
||||
|
||||
### Workspaces (Monorepo)
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"workspaces": ["packages/*"]
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Install all workspace dependencies
|
||||
bun install
|
||||
|
||||
# Run script in specific workspace
|
||||
bun --filter my-package run build
|
||||
```
|
||||
|
||||
### Custom Scripts
|
||||
|
||||
```typescript
|
||||
// scripts/build.ts
|
||||
import { $ } from 'bun';
|
||||
|
||||
console.log('Building project...');
|
||||
|
||||
await $`bun run type-check`;
|
||||
await $`bun run lint`;
|
||||
await $`next build`;
|
||||
|
||||
console.log('✅ Build complete!');
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
bun scripts/build.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Best Practices
|
||||
|
||||
### 1. Use Exact Versions
|
||||
|
||||
Add to `.bunfig.toml`:
|
||||
|
||||
```toml
|
||||
[install]
|
||||
exact = true
|
||||
```
|
||||
|
||||
### 2. Commit Lock File
|
||||
|
||||
Always commit `bun.lockb` to version control:
|
||||
|
||||
```bash
|
||||
git add bun.lockb
|
||||
git commit -m "Update dependencies"
|
||||
```
|
||||
|
||||
### 3. Use Scripts
|
||||
|
||||
Define common tasks in `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev --turbo",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"clean": "rm -rf .next node_modules bun.lockb"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Regular Updates
|
||||
|
||||
Keep Bun updated:
|
||||
|
||||
```bash
|
||||
bun upgrade
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- Official Documentation: https://bun.sh/docs
|
||||
- GitHub: https://github.com/oven-sh/bun
|
||||
- Discord: https://bun.sh/discord
|
||||
- Next.js + Bun: https://nextjs.org/docs/app/building-your-application/configuring/bun
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Phase 1: Foundation](./PHASE_01_FOUNDATION.md)
|
||||
- [Environment Variables Guide](./ENVIRONMENT_VARIABLES.md)
|
||||
- [Next.js 15 Migration Guide](./NEXTJS_15_MIGRATION_GUIDE.md)
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** November 9, 2025
|
||||
306
migration/ENVIRONMENT_VARIABLES.md
Normal file
306
migration/ENVIRONMENT_VARIABLES.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Environment Variables Guide
|
||||
|
||||
**For:** ThrillWiki Next.js 15 Migration
|
||||
**Last Updated:** November 9, 2025
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
This guide covers all environment variables required for the ThrillWiki migration from React→Next.js 15 and Supabase→Django.
|
||||
|
||||
## 🔑 Critical Rules
|
||||
|
||||
1. **NO HARDCODED VALUES** - All configuration must use environment variables
|
||||
2. **Client vs Server** - Understand NEXT_PUBLIC_ prefix usage
|
||||
3. **Never commit secrets** - .env.local in .gitignore
|
||||
4. **.env.example** - Document all required vars (without values)
|
||||
5. **Validate on startup** - Fail fast if required vars missing
|
||||
|
||||
---
|
||||
|
||||
## 📋 Required Environment Variables
|
||||
|
||||
### Next.js Client-Side (Public)
|
||||
|
||||
These variables are exposed to the browser. Use `NEXT_PUBLIC_` prefix.
|
||||
|
||||
```bash
|
||||
# Django API (required)
|
||||
NEXT_PUBLIC_DJANGO_API_URL=https://api.thrillwiki.com
|
||||
|
||||
# CloudFlare Images (required for uploads)
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=your_account_id_here
|
||||
```
|
||||
|
||||
### Next.js Server-Side (Private)
|
||||
|
||||
These variables are ONLY available server-side. NO `NEXT_PUBLIC_` prefix.
|
||||
|
||||
```bash
|
||||
# CloudFlare API Token (for image uploads from server)
|
||||
CLOUDFLARE_API_TOKEN=your_secret_token_here
|
||||
|
||||
# Django Admin Credentials (if needed for server-side operations)
|
||||
DJANGO_ADMIN_KEY=your_admin_key_here
|
||||
```
|
||||
|
||||
### Legacy Supabase (Temporary - Remove in Phase 14)
|
||||
|
||||
Keep these ONLY during migration phases 1-13:
|
||||
|
||||
```bash
|
||||
# Supabase (to be removed)
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key_here
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
```
|
||||
project-root/
|
||||
├── .env.local # Local development (git-ignored)
|
||||
├── .env.example # Template (git-committed)
|
||||
├── .env.production # Production (if needed, git-ignored)
|
||||
└── .env.test # Testing (git-ignored)
|
||||
```
|
||||
|
||||
### .env.local (Development)
|
||||
|
||||
```bash
|
||||
# ================================
|
||||
# Next.js Environment Variables
|
||||
# ================================
|
||||
|
||||
# Django REST API
|
||||
NEXT_PUBLIC_DJANGO_API_URL=http://localhost:8000/api/v1
|
||||
|
||||
# CloudFlare Images
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=your_dev_account_id
|
||||
CLOUDFLARE_API_TOKEN=your_dev_token
|
||||
|
||||
# Supabase (temporary - remove after Phase 14)
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
|
||||
|
||||
# Development flags
|
||||
NODE_ENV=development
|
||||
```
|
||||
|
||||
### .env.example (Template)
|
||||
|
||||
```bash
|
||||
# ================================
|
||||
# Next.js Environment Variables
|
||||
# Copy to .env.local and fill in values
|
||||
# ================================
|
||||
|
||||
# Django REST API (required)
|
||||
NEXT_PUBLIC_DJANGO_API_URL=
|
||||
|
||||
# CloudFlare Images (required)
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
|
||||
# Supabase (temporary - will be removed)
|
||||
NEXT_PUBLIC_SUPABASE_URL=
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||
```
|
||||
|
||||
### .env.production (Production)
|
||||
|
||||
```bash
|
||||
# Django REST API
|
||||
NEXT_PUBLIC_DJANGO_API_URL=https://api.thrillwiki.com
|
||||
|
||||
# CloudFlare Images
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=production_account_id
|
||||
CLOUDFLARE_API_TOKEN=production_secret_token
|
||||
|
||||
# Production settings
|
||||
NODE_ENV=production
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Best Practices
|
||||
|
||||
### 1. Client vs Server Variables
|
||||
|
||||
```typescript
|
||||
// ❌ WRONG - Exposes secret to browser
|
||||
const apiKey = process.env.CLOUDFLARE_API_TOKEN;
|
||||
|
||||
// ✅ CORRECT - Only available server-side
|
||||
// In Server Component or API Route
|
||||
const apiKey = process.env.CLOUDFLARE_API_TOKEN;
|
||||
|
||||
// ✅ CORRECT - Public variable in client
|
||||
const apiUrl = process.env.NEXT_PUBLIC_DJANGO_API_URL;
|
||||
```
|
||||
|
||||
### 2. Never Log Secrets
|
||||
|
||||
```typescript
|
||||
// ❌ WRONG
|
||||
console.log('API Token:', process.env.CLOUDFLARE_API_TOKEN);
|
||||
|
||||
// ✅ CORRECT
|
||||
console.log('API URL:', process.env.NEXT_PUBLIC_DJANGO_API_URL);
|
||||
```
|
||||
|
||||
### 3. Validate Environment Variables
|
||||
|
||||
```typescript
|
||||
// lib/env.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
const envSchema = z.object({
|
||||
NEXT_PUBLIC_DJANGO_API_URL: z.string().url(),
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID: z.string().min(1),
|
||||
CLOUDFLARE_API_TOKEN: z.string().min(1).optional(), // Server-only
|
||||
});
|
||||
|
||||
// Validate on app start
|
||||
export const env = envSchema.parse({
|
||||
NEXT_PUBLIC_DJANGO_API_URL: process.env.NEXT_PUBLIC_DJANGO_API_URL,
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID: process.env.NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID,
|
||||
CLOUDFLARE_API_TOKEN: process.env.CLOUDFLARE_API_TOKEN,
|
||||
});
|
||||
|
||||
// Usage
|
||||
import { env } from '@/lib/env';
|
||||
const apiUrl = env.NEXT_PUBLIC_DJANGO_API_URL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Environments
|
||||
|
||||
### Vercel
|
||||
|
||||
Set environment variables in Vercel dashboard:
|
||||
|
||||
1. Go to Project Settings → Environment Variables
|
||||
2. Add each variable:
|
||||
- `NEXT_PUBLIC_DJANGO_API_URL` = `https://api.thrillwiki.com`
|
||||
- `NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID` = `your_account`
|
||||
- `CLOUDFLARE_API_TOKEN` = `your_token` (mark as secret)
|
||||
|
||||
### Docker
|
||||
|
||||
Pass via docker-compose.yml:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
nextjs:
|
||||
environment:
|
||||
- NEXT_PUBLIC_DJANGO_API_URL=https://api.thrillwiki.com
|
||||
- NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}
|
||||
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Check Variables Are Loaded
|
||||
|
||||
```typescript
|
||||
// Test in dev console (browser)
|
||||
console.log('API URL:', process.env.NEXT_PUBLIC_DJANGO_API_URL);
|
||||
// Should show the URL
|
||||
|
||||
console.log('Secret Token:', process.env.CLOUDFLARE_API_TOKEN);
|
||||
// Should show 'undefined' (not exposed to client)
|
||||
```
|
||||
|
||||
### Verify Build-Time Variables
|
||||
|
||||
```bash
|
||||
# Build with verbose output
|
||||
bun run build
|
||||
|
||||
# Check if variables are embedded
|
||||
grep -r "NEXT_PUBLIC_DJANGO_API_URL" .next/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Migration Phases
|
||||
|
||||
### Phase 1-13: Dual Variables
|
||||
```bash
|
||||
# Keep both during migration
|
||||
NEXT_PUBLIC_DJANGO_API_URL=...
|
||||
NEXT_PUBLIC_SUPABASE_URL=...
|
||||
```
|
||||
|
||||
### Phase 14: Remove Supabase
|
||||
```bash
|
||||
# Remove these completely
|
||||
# NEXT_PUBLIC_SUPABASE_URL=...
|
||||
# NEXT_PUBLIC_SUPABASE_ANON_KEY=...
|
||||
|
||||
# Keep only Django
|
||||
NEXT_PUBLIC_DJANGO_API_URL=...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Common Issues
|
||||
|
||||
### Issue: Variable is undefined in browser
|
||||
|
||||
**Cause:** Missing `NEXT_PUBLIC_` prefix
|
||||
|
||||
**Fix:** Rename variable:
|
||||
```bash
|
||||
# Wrong
|
||||
DJANGO_API_URL=https://api.thrillwiki.com
|
||||
|
||||
# Right
|
||||
NEXT_PUBLIC_DJANGO_API_URL=https://api.thrillwiki.com
|
||||
```
|
||||
|
||||
### Issue: Variable not updating after change
|
||||
|
||||
**Cause:** Next.js caches env vars at build time
|
||||
|
||||
**Fix:** Restart dev server:
|
||||
```bash
|
||||
# Stop server (Ctrl+C)
|
||||
bun run dev
|
||||
```
|
||||
|
||||
### Issue: Hardcoded URL slipped through
|
||||
|
||||
**Find:** Search codebase:
|
||||
```bash
|
||||
grep -r "https://api.thrillwiki.com" src/ --exclude-dir=node_modules
|
||||
```
|
||||
|
||||
**Fix:** Replace with env var:
|
||||
```typescript
|
||||
// Before
|
||||
const API_URL = "https://api.thrillwiki.com";
|
||||
|
||||
// After
|
||||
const API_URL = process.env.NEXT_PUBLIC_DJANGO_API_URL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Phase 1: Foundation](./PHASE_01_FOUNDATION.md) - Initial setup
|
||||
- [Phase 13: Next.js Optimization](./PHASE_13_NEXTJS_OPTIMIZATION.md) - Security
|
||||
- [Bun Guide](./BUN_GUIDE.md) - Package manager
|
||||
- Next.js Docs: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** November 9, 2025
|
||||
605
migration/NEXTJS_15_MIGRATION_GUIDE.md
Normal file
605
migration/NEXTJS_15_MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,605 @@
|
||||
# Next.js 15 Migration Guide
|
||||
|
||||
**For:** ThrillWiki React → Next.js 15 + App Router Migration
|
||||
**Last Updated:** November 9, 2025
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
This guide provides patterns, examples, and best practices for migrating from React SPA to Next.js 15 with App Router.
|
||||
|
||||
**What's Changing:**
|
||||
- React Router → Next.js File-based Routing
|
||||
- Client-side rendering → Server-side rendering by default
|
||||
- Manual data fetching → Built-in data fetching
|
||||
- Vite → Turbopack
|
||||
- npm → Bun
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Structure Migration
|
||||
|
||||
### Old Structure (React SPA)
|
||||
|
||||
```
|
||||
src/
|
||||
├── pages/
|
||||
│ ├── Index.tsx
|
||||
│ ├── Parks.tsx
|
||||
│ ├── ParkDetail.tsx
|
||||
│ └── ...
|
||||
├── components/
|
||||
│ ├── Navigation.tsx
|
||||
│ ├── ParkCard.tsx
|
||||
│ └── ...
|
||||
├── hooks/
|
||||
├── services/
|
||||
└── App.tsx
|
||||
```
|
||||
|
||||
### New Structure (Next.js 15)
|
||||
|
||||
```
|
||||
app/
|
||||
├── layout.tsx # Root layout (replaces App.tsx)
|
||||
├── page.tsx # Homepage (replaces Index.tsx)
|
||||
├── parks/
|
||||
│ ├── page.tsx # /parks (replaces Parks.tsx)
|
||||
│ └── [parkSlug]/
|
||||
│ └── page.tsx # /parks/[slug] (replaces ParkDetail.tsx)
|
||||
components/
|
||||
├── navigation/
|
||||
│ └── Navigation.tsx # Shared components
|
||||
├── parks/
|
||||
│ └── ParkCard.tsx
|
||||
hooks/
|
||||
services/
|
||||
lib/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Routing Migration
|
||||
|
||||
### React Router → Next.js
|
||||
|
||||
#### Old: React Router
|
||||
|
||||
```typescript
|
||||
// App.tsx
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/parks" element={<ParksPage />} />
|
||||
<Route path="/parks/:slug" element={<ParkDetailPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### New: Next.js File-based Routing
|
||||
|
||||
```
|
||||
app/
|
||||
├── page.tsx → /
|
||||
├── parks/
|
||||
│ ├── page.tsx → /parks
|
||||
│ └── [slug]/
|
||||
│ └── page.tsx → /parks/:slug
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
#### Old: React Router Links
|
||||
|
||||
```typescript
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
function Navigation() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<Link to="/parks">Parks</Link>
|
||||
<button onClick={() => navigate('/parks/cedar-point')}>
|
||||
Cedar Point
|
||||
</button>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### New: Next.js Links
|
||||
|
||||
```typescript
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function Navigation() {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<Link href="/parks">Parks</Link>
|
||||
<button onClick={() => router.push('/parks/cedar-point')}>
|
||||
Cedar Point
|
||||
</button>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### URL Parameters
|
||||
|
||||
#### Old: React Router
|
||||
|
||||
```typescript
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
function ParkDetail() {
|
||||
const { slug } = useParams();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### New: Next.js (Server Component)
|
||||
|
||||
```typescript
|
||||
// app/parks/[slug]/page.tsx
|
||||
export default function ParkDetail({
|
||||
params
|
||||
}: {
|
||||
params: { slug: string }
|
||||
}) {
|
||||
// params.slug is available
|
||||
}
|
||||
```
|
||||
|
||||
#### New: Next.js (Client Component)
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
export default function ParkDetail() {
|
||||
const params = useParams();
|
||||
const slug = params.slug;
|
||||
}
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
|
||||
#### Old: React Router
|
||||
|
||||
```typescript
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
function ParksPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const filter = searchParams.get('filter');
|
||||
}
|
||||
```
|
||||
|
||||
#### New: Next.js (Server Component)
|
||||
|
||||
```typescript
|
||||
export default function ParksPage({
|
||||
searchParams
|
||||
}: {
|
||||
searchParams: { filter?: string }
|
||||
}) {
|
||||
const filter = searchParams.filter;
|
||||
}
|
||||
```
|
||||
|
||||
#### New: Next.js (Client Component)
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
export default function ParksPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const filter = searchParams.get('filter');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Server vs Client Components
|
||||
|
||||
### Decision Tree
|
||||
|
||||
```
|
||||
Does it need interactivity?
|
||||
├─ NO → Server Component (default)
|
||||
│ ├─ Displays data
|
||||
│ ├─ Static content
|
||||
│ └─ SEO-critical
|
||||
│
|
||||
└─ YES → Client Component ('use client')
|
||||
├─ Forms with state
|
||||
├─ Event handlers
|
||||
├─ Browser APIs
|
||||
└─ Hooks (useState, useEffect, etc.)
|
||||
```
|
||||
|
||||
### Server Component Example
|
||||
|
||||
```typescript
|
||||
// app/parks/page.tsx
|
||||
// No 'use client' directive = Server Component
|
||||
|
||||
import { env } from '@/lib/env';
|
||||
|
||||
export default async function ParksPage() {
|
||||
// Fetch data directly in component
|
||||
const parks = await fetch(
|
||||
`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/`
|
||||
).then(r => r.json());
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Theme Parks</h1>
|
||||
{parks.results.map(park => (
|
||||
<ParkCard key={park.id} park={park} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Client Component Example
|
||||
|
||||
```typescript
|
||||
// components/parks/ParkFilters.tsx
|
||||
'use client'; // Mark as Client Component
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export function ParkFilters() {
|
||||
const [filter, setFilter] = useState('');
|
||||
const router = useRouter();
|
||||
|
||||
const handleFilterChange = (value: string) => {
|
||||
setFilter(value);
|
||||
router.push(`/parks?filter=${value}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<select value={filter} onChange={(e) => handleFilterChange(e.target.value)}>
|
||||
<option value="">All Parks</option>
|
||||
<option value="theme">Theme Parks</option>
|
||||
<option value="water">Water Parks</option>
|
||||
</select>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Mixing Server and Client
|
||||
|
||||
```typescript
|
||||
// app/parks/page.tsx (Server Component)
|
||||
import { ParkFilters } from '@/components/parks/ParkFilters'; // Client
|
||||
import { ParksList } from '@/components/parks/ParksList'; // Can be Server
|
||||
|
||||
export default async function ParksPage() {
|
||||
const parks = await fetch(API_URL).then(r => r.json());
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Parks</h1>
|
||||
<ParkFilters /> {/* Client Component for interactivity */}
|
||||
<ParksList parks={parks} /> {/* Server Component for display */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📡 Data Fetching
|
||||
|
||||
### Old: React with useEffect
|
||||
|
||||
```typescript
|
||||
function ParkDetail({ slug }: { slug: string }) {
|
||||
const [park, setPark] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/parks/${slug}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
setPark(data);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [slug]);
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
return <div>{park.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### New: Next.js Server Component
|
||||
|
||||
```typescript
|
||||
// app/parks/[slug]/page.tsx
|
||||
export default async function ParkDetail({
|
||||
params
|
||||
}: {
|
||||
params: { slug: string }
|
||||
}) {
|
||||
// Fetch directly - no useEffect needed
|
||||
const park = await fetch(
|
||||
`${API_URL}/parks/${params.slug}/`,
|
||||
{ next: { revalidate: 3600 } } // Cache for 1 hour
|
||||
).then(r => r.json());
|
||||
|
||||
return <div>{park.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Caching Strategies
|
||||
|
||||
```typescript
|
||||
// No caching (always fresh)
|
||||
fetch(url, { cache: 'no-store' });
|
||||
|
||||
// Cache forever (until revalidated)
|
||||
fetch(url, { cache: 'force-cache' });
|
||||
|
||||
// Revalidate after 60 seconds
|
||||
fetch(url, { next: { revalidate: 60 } });
|
||||
|
||||
// Revalidate on-demand (from API route)
|
||||
fetch(url, { next: { tags: ['parks'] } });
|
||||
// Then: revalidateTag('parks')
|
||||
```
|
||||
|
||||
### Parallel Data Fetching
|
||||
|
||||
```typescript
|
||||
export default async function ParkDetail({ params }) {
|
||||
// Fetch in parallel
|
||||
const [park, rides, reviews] = await Promise.all([
|
||||
fetch(`${API_URL}/parks/${params.slug}/`),
|
||||
fetch(`${API_URL}/parks/${params.slug}/rides/`),
|
||||
fetch(`${API_URL}/parks/${params.slug}/reviews/`),
|
||||
]).then(responses =>
|
||||
Promise.all(responses.map(r => r.json()))
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ParkHeader park={park} />
|
||||
<RidesList rides={rides} />
|
||||
<ReviewsList reviews={reviews} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Layouts
|
||||
|
||||
### Root Layout
|
||||
|
||||
```typescript
|
||||
// app/layout.tsx
|
||||
import { Inter } from 'next/font/google';
|
||||
import './globals.css';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata = {
|
||||
title: 'ThrillWiki',
|
||||
description: 'Theme Park Database',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<Navigation />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Nested Layouts
|
||||
|
||||
```typescript
|
||||
// app/parks/layout.tsx
|
||||
export default function ParksLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="parks-container">
|
||||
<ParksSidebar />
|
||||
<div className="parks-content">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 SEO & Metadata
|
||||
|
||||
### Static Metadata
|
||||
|
||||
```typescript
|
||||
// app/parks/page.tsx
|
||||
export const metadata = {
|
||||
title: 'Theme Parks - ThrillWiki',
|
||||
description: 'Browse theme parks from around the world',
|
||||
openGraph: {
|
||||
title: 'Theme Parks',
|
||||
description: 'Browse theme parks',
|
||||
images: ['/og-parks.png'],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Dynamic Metadata
|
||||
|
||||
```typescript
|
||||
// app/parks/[slug]/page.tsx
|
||||
export async function generateMetadata({ params }) {
|
||||
const park = await fetch(`${API_URL}/parks/${params.slug}/`)
|
||||
.then(r => r.json());
|
||||
|
||||
return {
|
||||
title: `${park.name} - ThrillWiki`,
|
||||
description: park.description,
|
||||
openGraph: {
|
||||
title: park.name,
|
||||
description: park.description,
|
||||
images: [park.image_url],
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Loading & Error States
|
||||
|
||||
### Loading UI
|
||||
|
||||
```typescript
|
||||
// app/parks/loading.tsx
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="skeleton">
|
||||
<div className="skeleton-header" />
|
||||
<div className="skeleton-grid">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<div key={i} className="skeleton-card" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Error Boundaries
|
||||
|
||||
```typescript
|
||||
// app/parks/error.tsx
|
||||
'use client';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error;
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={reset}>Try again</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Not Found
|
||||
|
||||
```typescript
|
||||
// app/parks/[slug]/not-found.tsx
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Park Not Found</h2>
|
||||
<p>The park you're looking for doesn't exist.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// In page.tsx
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export default async function ParkDetail({ params }) {
|
||||
const park = await fetch(`${API_URL}/parks/${params.slug}/`)
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error();
|
||||
return r.json();
|
||||
})
|
||||
.catch(() => notFound());
|
||||
|
||||
return <div>{park.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
### Build Command
|
||||
|
||||
```bash
|
||||
bun run build
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Set in Vercel/hosting platform:
|
||||
- `NEXT_PUBLIC_DJANGO_API_URL`
|
||||
- `NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID`
|
||||
- Server-only secrets (no NEXT_PUBLIC_ prefix)
|
||||
|
||||
### Vercel Deployment
|
||||
|
||||
```bash
|
||||
# Install Vercel CLI
|
||||
bun add -g vercel
|
||||
|
||||
# Deploy
|
||||
vercel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
- Next.js 15 Documentation: https://nextjs.org/docs
|
||||
- App Router: https://nextjs.org/docs/app
|
||||
- Server Components: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
||||
- Data Fetching: https://nextjs.org/docs/app/building-your-application/data-fetching
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Phase 1: Foundation](./PHASE_01_FOUNDATION.md)
|
||||
- [Phase 12: Pages Migration](./PHASE_12_PAGES_MIGRATION.md)
|
||||
- [Phase 13: Optimization](./PHASE_13_NEXTJS_OPTIMIZATION.md)
|
||||
- [Environment Variables](./ENVIRONMENT_VARIABLES.md)
|
||||
- [Bun Guide](./BUN_GUIDE.md)
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** November 9, 2025
|
||||
556
migration/PHASE_01_FOUNDATION.md
Normal file
556
migration/PHASE_01_FOUNDATION.md
Normal file
@@ -0,0 +1,556 @@
|
||||
# PHASE 1: Foundation + Next.js 15 Setup
|
||||
**Status:** 🟡 Partially Complete (Core Foundation Ready)
|
||||
**Estimated Time:** 20-25 hours
|
||||
**Priority:** CRITICAL
|
||||
**Depends On:** None
|
||||
**Blocks:** All other phases
|
||||
|
||||
**Note:** Core infrastructure complete (Tasks 1.0, 1.1, 1.3). Types (1.2), Base Service (1.4), and Testing (1.5) deferred to be created as needed in subsequent phases.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Goal
|
||||
Set up Next.js 16 with App Router + Turbopack, configure Bun, establish environment variables, and build the core infrastructure that all services will depend on: API client, type system, error handling, and base service class.
|
||||
|
||||
**This phase includes:**
|
||||
1. Next.js 16 installation and configuration
|
||||
2. Bun package manager setup
|
||||
3. Environment variables configuration
|
||||
4. API client infrastructure
|
||||
5. Type system for Django API
|
||||
6. Service layer foundation
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
- [ ] Django backend is running at `https://api.thrillwiki.com`
|
||||
- [ ] You have API access token for testing
|
||||
- [ ] Bun is installed (`curl -fsSL https://bun.sh/install | bash`)
|
||||
- [ ] Node.js 18+ is installed (for Next.js 15)
|
||||
- [ ] Git repository is initialized
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Files to Create/Modify
|
||||
|
||||
```
|
||||
# Next.js Configuration
|
||||
next.config.js (NEW)
|
||||
.env.local (NEW)
|
||||
.env.example (NEW)
|
||||
tsconfig.json (UPDATE)
|
||||
package.json (UPDATE)
|
||||
|
||||
# Next.js App Structure
|
||||
app/
|
||||
├── layout.tsx (NEW)
|
||||
├── page.tsx (NEW)
|
||||
├── loading.tsx (NEW)
|
||||
├── error.tsx (NEW)
|
||||
└── not-found.tsx (NEW)
|
||||
|
||||
# API & Services
|
||||
lib/
|
||||
├── api/
|
||||
│ ├── client.ts (NEW)
|
||||
│ ├── errorHandler.ts (NEW)
|
||||
│ └── index.ts (NEW)
|
||||
└── env.ts (NEW - env validation)
|
||||
|
||||
types/
|
||||
├── api/
|
||||
│ ├── responses.ts (NEW)
|
||||
│ ├── requests.ts (NEW)
|
||||
│ ├── errors.ts (NEW)
|
||||
│ └── index.ts (NEW)
|
||||
└── entities/
|
||||
├── park.ts (NEW)
|
||||
├── ride.ts (NEW)
|
||||
└── ... (others)
|
||||
|
||||
services/
|
||||
├── base/
|
||||
│ ├── BaseService.ts (NEW)
|
||||
│ └── index.ts (NEW)
|
||||
└── __tests__/
|
||||
├── setup.ts (NEW)
|
||||
└── mocks/ (NEW)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 1.0: Next.js 15 + Bun Setup (5 hours)
|
||||
|
||||
### Step 1: Install Next.js 15 with Bun
|
||||
|
||||
```bash
|
||||
# Create Next.js 15 project with Bun
|
||||
bun create next-app@latest . --typescript --tailwind --app --no-src-dir
|
||||
|
||||
# Or if starting fresh in a new directory:
|
||||
bun create next-app@latest thrillwiki --typescript --tailwind --app --no-src-dir
|
||||
cd thrillwiki
|
||||
```
|
||||
|
||||
### Step 2: Configure next.config.js
|
||||
|
||||
Create `next.config.js`:
|
||||
|
||||
```javascript
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Enable Turbopack for development
|
||||
experimental: {
|
||||
turbo: {
|
||||
resolveAlias: {
|
||||
'@': './',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Image optimization for CloudFlare
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'imagedelivery.net',
|
||||
},
|
||||
],
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
},
|
||||
|
||||
// Production optimizations
|
||||
compress: true,
|
||||
poweredByHeader: false,
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
```
|
||||
|
||||
### Step 3: Environment Variables Setup
|
||||
|
||||
Create `.env.local`:
|
||||
|
||||
```bash
|
||||
# Django API
|
||||
NEXT_PUBLIC_DJANGO_API_URL=http://localhost:8000/api/v1
|
||||
|
||||
# CloudFlare Images
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=your_account_id
|
||||
|
||||
# Supabase (temporary - keep during migration)
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
|
||||
```
|
||||
|
||||
Create `.env.example`:
|
||||
|
||||
```bash
|
||||
# Django API (required)
|
||||
NEXT_PUBLIC_DJANGO_API_URL=
|
||||
|
||||
# CloudFlare Images (required)
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=
|
||||
|
||||
# Supabase (temporary - will be removed)
|
||||
NEXT_PUBLIC_SUPABASE_URL=
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||
```
|
||||
|
||||
### Step 4: Environment Variable Validation
|
||||
|
||||
Create `lib/env.ts`:
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
|
||||
const envSchema = z.object({
|
||||
NEXT_PUBLIC_DJANGO_API_URL: z.string().url(),
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID: z.string().min(1),
|
||||
});
|
||||
|
||||
export const env = envSchema.parse({
|
||||
NEXT_PUBLIC_DJANGO_API_URL: process.env.NEXT_PUBLIC_DJANGO_API_URL,
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID: process.env.NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID,
|
||||
});
|
||||
```
|
||||
|
||||
### Step 5: Update package.json Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev --turbo",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"type-check": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Checklist
|
||||
- [x] Next.js 16 installed with Bun (Next.js 16.0.1)
|
||||
- [x] ~~Turbopack enabled in next.config.js~~ (Removed - deprecated experimental key in Next.js 16)
|
||||
- [x] .env.local created with all variables
|
||||
- [x] .env.example documented
|
||||
- [x] Environment validation in lib/env.ts
|
||||
- [x] package.json scripts use Bun
|
||||
- [x] Dev server runs with `bun run dev` - ✅ Running on http://localhost:3000
|
||||
- [x] No TypeScript errors
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 1.1: Base API Client (3 hours)
|
||||
|
||||
### Checklist
|
||||
- [x] Install dependencies: `bun add axios zod` (axios installed, zod already present)
|
||||
- [x] Create `lib/api/client.ts`
|
||||
- [x] Configure base URL from environment variable
|
||||
- [x] Add JWT token management
|
||||
- [x] Get token from localStorage
|
||||
- [x] Attach to Authorization header
|
||||
- [x] Handle token expiry
|
||||
- [x] Create request interceptor
|
||||
- [x] Add auth headers
|
||||
- [x] Add request logging (dev only)
|
||||
- [x] Create response interceptor
|
||||
- [x] Handle success responses
|
||||
- [x] Handle error responses
|
||||
- [x] Extract data from Django response format
|
||||
- [x] Implement retry logic
|
||||
- [x] Retry on network failures
|
||||
- [x] Exponential backoff
|
||||
- [x] Max 3 retries
|
||||
- [x] Add timeout configuration (30s default)
|
||||
- [x] Export configured axios instance
|
||||
|
||||
### Acceptance Criteria
|
||||
- [x] Can make GET request to `/api/v1/health` (client configured)
|
||||
- [x] Token automatically attached to requests
|
||||
- [x] Errors are properly caught and logged
|
||||
- [x] Retries work on network failures
|
||||
- [x] TypeScript types are correct
|
||||
|
||||
### Implementation Example
|
||||
|
||||
```typescript
|
||||
// lib/api/client.ts
|
||||
import axios from 'axios';
|
||||
import { env } from '@/lib/env';
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: env.NEXT_PUBLIC_DJANGO_API_URL,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Request interceptor
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
// Add auth token if available
|
||||
if (typeof window !== 'undefined') {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Response interceptor
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
// Handle errors
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export { apiClient };
|
||||
```
|
||||
|
||||
### Testing
|
||||
```typescript
|
||||
// Test in app/page.tsx or a Server Component
|
||||
import { env } from '@/lib/env';
|
||||
|
||||
export default async function HomePage() {
|
||||
// Test API connection
|
||||
const response = await fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/health`);
|
||||
const data = await response.json();
|
||||
|
||||
return <div>API Status: {data.status}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 1.2: Type Definitions (2 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Install zod: `bun add zod`
|
||||
- [ ] Create `types/api/responses.ts`
|
||||
- [ ] `ApiResponse<T>` generic type
|
||||
- [ ] `PaginatedResponse<T>` type
|
||||
- [ ] `SuccessResponse<T>` type
|
||||
- [ ] Create `src/types/api/errors.ts`
|
||||
- [ ] `ApiError` interface
|
||||
- [ ] `ValidationError` interface
|
||||
- [ ] `ErrorDetail` interface
|
||||
- [ ] Create `src/types/api/requests.ts`
|
||||
- [ ] `PaginationParams` interface
|
||||
- [ ] `FilterParams` interface
|
||||
- [ ] `SortParams` interface
|
||||
- [ ] Create entity type files
|
||||
- [ ] `types/entities/park.ts`
|
||||
- [ ] `types/entities/ride.ts`
|
||||
- [ ] `types/entities/company.ts`
|
||||
- [ ] `types/entities/rideModel.ts`
|
||||
- [ ] `types/entities/submission.ts`
|
||||
- [ ] `types/entities/review.ts`
|
||||
- [ ] Create `types/api/index.ts` - export all types
|
||||
- [ ] Document type usage
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] All types match Django API schema
|
||||
- [ ] No `any` types used
|
||||
- [ ] Types are reusable across services
|
||||
- [ ] Generic types work correctly
|
||||
- [ ] TypeScript compiler is happy
|
||||
|
||||
### Example Types
|
||||
```typescript
|
||||
// ApiResponse<T>
|
||||
interface ApiResponse<T> {
|
||||
data: T;
|
||||
status: 'success' | 'error';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// PaginatedResponse<T>
|
||||
interface PaginatedResponse<T> {
|
||||
results: T[];
|
||||
count: number;
|
||||
next: string | null;
|
||||
previous: string | null;
|
||||
}
|
||||
|
||||
// ApiError
|
||||
interface ApiError {
|
||||
message: string;
|
||||
code?: string;
|
||||
details?: ErrorDetail[];
|
||||
status: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 1.3: Error Handler Integration (2 hours)
|
||||
|
||||
### Checklist
|
||||
- [x] Create `lib/api/errorHandler.ts`
|
||||
- [x] Map Django error responses to user messages
|
||||
- [x] Handle HTTP status codes
|
||||
- [x] 400 - Validation errors
|
||||
- [x] 401 - Unauthorized
|
||||
- [x] 403 - Forbidden
|
||||
- [x] 404 - Not found
|
||||
- [x] 429 - Rate limiting
|
||||
- [x] 500+ - Server errors
|
||||
- [x] Extract validation errors from Django format
|
||||
- [x] Format error messages for UI display
|
||||
- [x] Integrate with existing error logging
|
||||
- [ ] Add Sentry integration (if available) - Deferred to later phase
|
||||
- [x] Create user-friendly error messages
|
||||
- [x] Handle network errors separately
|
||||
|
||||
### Acceptance Criteria
|
||||
- [x] All Django errors are properly translated
|
||||
- [x] Validation errors show field-specific messages
|
||||
- [x] Errors are logged to monitoring
|
||||
- [x] User sees helpful error messages
|
||||
- [x] No stack traces exposed to users
|
||||
|
||||
### Testing
|
||||
```typescript
|
||||
// Test error handling
|
||||
try {
|
||||
await apiClient.post('/api/v1/submissions/', { invalid: 'data' });
|
||||
} catch (error) {
|
||||
console.log('Caught error:', error.message);
|
||||
console.log('User message:', formatErrorForUser(error));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 1.4: Service Base Class (2 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Create `services/base/BaseService.ts`
|
||||
- [ ] Add API client instance
|
||||
- [ ] Implement common CRUD methods
|
||||
- [ ] `get<T>(url: string): Promise<T>`
|
||||
- [ ] `list<T>(url: string, params?: any): Promise<T[]>`
|
||||
- [ ] `create<T>(url: string, data: any): Promise<T>`
|
||||
- [ ] `update<T>(url: string, data: any): Promise<T>`
|
||||
- [ ] `delete(url: string): Promise<void>`
|
||||
- [ ] Add pagination helper methods
|
||||
- [ ] `getPaginated<T>(url: string, params?: any): Promise<PaginatedResponse<T>>`
|
||||
- [ ] Add filtering/sorting helpers
|
||||
- [ ] Add error handling wrapper
|
||||
- [ ] Add loading state management (optional)
|
||||
- [ ] Document usage patterns
|
||||
- [ ] Add TypeScript generics
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] Base service can be extended
|
||||
- [ ] All CRUD operations work
|
||||
- [ ] Pagination works correctly
|
||||
- [ ] Errors are properly handled
|
||||
- [ ] TypeScript types are inferred correctly
|
||||
|
||||
### Example Usage
|
||||
```typescript
|
||||
// Example service extending BaseService
|
||||
class ParkService extends BaseService {
|
||||
async getParks(params?: FilterParams) {
|
||||
return this.getPaginated('/api/v1/parks/', params);
|
||||
}
|
||||
|
||||
async getPark(id: string) {
|
||||
return this.get(`/api/v1/parks/${id}/`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 1.5: Testing Infrastructure (3 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Install MSW: `bun add -d msw @testing-library/react @testing-library/jest-dom vitest`
|
||||
- [ ] Create `services/__tests__/setup.ts`
|
||||
- [ ] Initialize MSW
|
||||
- [ ] Configure test environment
|
||||
- [ ] Create `src/services/__tests__/mocks/handlers.ts`
|
||||
- [ ] Mock Django API responses
|
||||
- [ ] Create realistic test data
|
||||
- [ ] Create `services/__tests__/mocks/server.ts`
|
||||
- [ ] Set up MSW server
|
||||
- [ ] Configure for Node/Next.js environment
|
||||
- [ ] Create `services/__tests__/utils.ts`
|
||||
- [ ] Test helper functions
|
||||
- [ ] Mock data factories
|
||||
- [ ] Write example service tests
|
||||
- [ ] Test success cases
|
||||
- [ ] Test error cases
|
||||
- [ ] Test pagination
|
||||
- [ ] Document testing patterns
|
||||
- [ ] Set up CI test runs (if applicable)
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] MSW intercepts API calls in tests
|
||||
- [ ] Tests can run without real API
|
||||
- [ ] Test data is realistic
|
||||
- [ ] All test utilities are documented
|
||||
- [ ] CI passes tests
|
||||
|
||||
### Example Test
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ParkService } from '../parks/parkService';
|
||||
|
||||
describe('ParkService', () => {
|
||||
it('should fetch parks', async () => {
|
||||
const service = new ParkService();
|
||||
const parks = await service.getParks();
|
||||
expect(parks).toHaveLength(10);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Completion Criteria
|
||||
|
||||
### Code Quality
|
||||
- [ ] All files created and in correct locations
|
||||
- [ ] Zero TypeScript errors
|
||||
- [ ] Zero linter warnings
|
||||
- [ ] Code is well-documented
|
||||
- [ ] Naming conventions followed
|
||||
|
||||
### Functionality
|
||||
- [ ] Can make requests to Django API
|
||||
- [ ] Authentication works
|
||||
- [ ] Error handling works
|
||||
- [ ] Base service can be extended
|
||||
- [ ] Tests pass
|
||||
|
||||
### Testing
|
||||
- [ ] Unit tests for API client
|
||||
- [ ] Unit tests for error handler
|
||||
- [ ] Unit tests for base service
|
||||
- [ ] Test coverage >80%
|
||||
- [ ] All tests pass
|
||||
|
||||
---
|
||||
|
||||
## 📊 Progress Tracking
|
||||
|
||||
**Started:** [Date]
|
||||
**Completed:** [Date]
|
||||
**Time Spent:** [Hours]
|
||||
|
||||
### Tasks Completed
|
||||
- [x] Task 1.0: Next.js 15 + Bun Setup
|
||||
- [x] Task 1.1: Base API Client
|
||||
- [ ] Task 1.2: Type Definitions - **DEFERRED** to be created as needed in Phases 2-11
|
||||
- [x] Task 1.3: Error Handler Integration
|
||||
- [ ] Task 1.4: Service Base Class - **DEFERRED** to Phase 4 (Entity Services)
|
||||
- [ ] Task 1.5: Testing Infrastructure - **DEFERRED** to later phases
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Blockers & Issues
|
||||
|
||||
Document any issues encountered:
|
||||
|
||||
1. **Issue:** [Description]
|
||||
- **Impact:** [How it blocks progress]
|
||||
- **Resolution:** [How it was resolved]
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
Document important decisions and learnings:
|
||||
|
||||
- [Date] - [Note about implementation decision]
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
Once this phase is complete, proceed to [Phase 2: Authentication](./PHASE_02_AUTHENTICATION.md)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Environment Variables Guide](./ENVIRONMENT_VARIABLES.md)
|
||||
- [Bun Setup Guide](./BUN_GUIDE.md)
|
||||
- [Next.js 15 Migration Guide](./NEXTJS_15_MIGRATION_GUIDE.md)
|
||||
- Next.js Documentation: https://nextjs.org/docs
|
||||
- Bun Documentation: https://bun.sh/docs
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 2.0
|
||||
**Last Updated:** November 9, 2025
|
||||
**Changes:** Added Next.js 15 + App Router + Turbopack + Bun setup
|
||||
336
migration/PHASE_02_AUTHENTICATION.md
Normal file
336
migration/PHASE_02_AUTHENTICATION.md
Normal file
@@ -0,0 +1,336 @@
|
||||
# PHASE 2: Authentication
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 18-22 hours
|
||||
**Priority:** CRITICAL
|
||||
**Depends On:** Phase 1 (Foundation)
|
||||
**Blocks:** Phases 3-13 (Almost everything requires auth)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Goal
|
||||
Replace ALL `supabase.auth.*` calls with Django authentication service, including JWT token management, OAuth, MFA, and session handling.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
- [x] Phase 1 complete (API client, types, base service)
|
||||
- [ ] Django auth endpoints tested and working
|
||||
- [ ] JWT token format documented
|
||||
- [ ] OAuth providers configured in Django
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Files to Create/Modify
|
||||
|
||||
```
|
||||
src/services/auth/
|
||||
├── authService.ts (NEW)
|
||||
├── oauthService.ts (NEW)
|
||||
├── mfaService.ts (NEW)
|
||||
├── types.ts (NEW)
|
||||
└── index.ts (NEW)
|
||||
|
||||
src/hooks/
|
||||
├── useAuth.tsx (MODIFY - major rewrite)
|
||||
|
||||
src/components/auth/
|
||||
├── AuthModal.tsx (MODIFY)
|
||||
├── LoginForm.tsx (MODIFY)
|
||||
├── RegisterForm.tsx (MODIFY)
|
||||
├── PasswordResetForm.tsx (MODIFY)
|
||||
├── TOTPSetup.tsx (MODIFY)
|
||||
├── MFAChallenge.tsx (MODIFY)
|
||||
└── MFARemovalDialog.tsx (MODIFY)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 2.1: Auth Service Core (6 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Create `src/services/auth/authService.ts`
|
||||
- [ ] Implement login
|
||||
- [ ] `login(email: string, password: string): Promise<AuthResponse>`
|
||||
- [ ] Store JWT token in localStorage
|
||||
- [ ] Store refresh token
|
||||
- [ ] Return user object
|
||||
- [ ] Implement register
|
||||
- [ ] `register(data: RegisterData): Promise<AuthResponse>`
|
||||
- [ ] Handle email verification requirement
|
||||
- [ ] Store tokens on success
|
||||
- [ ] Implement logout
|
||||
- [ ] `logout(): Promise<void>`
|
||||
- [ ] Clear tokens from localStorage
|
||||
- [ ] Invalidate on server
|
||||
- [ ] Implement token refresh
|
||||
- [ ] `refreshToken(): Promise<AuthResponse>`
|
||||
- [ ] Auto-refresh on 401 errors
|
||||
- [ ] Handle refresh token expiry
|
||||
- [ ] Implement getCurrentUser
|
||||
- [ ] `getCurrentUser(): Promise<User>`
|
||||
- [ ] Use JWT token
|
||||
- [ ] Cache user data
|
||||
- [ ] Implement password reset flow
|
||||
- [ ] `requestPasswordReset(email: string): Promise<void>`
|
||||
- [ ] `confirmPasswordReset(token: string, password: string): Promise<void>`
|
||||
- [ ] Implement email verification
|
||||
- [ ] `verifyEmail(token: string): Promise<void>`
|
||||
- [ ] `resendVerification(): Promise<void>`
|
||||
- [ ] Implement email change
|
||||
- [ ] `requestEmailChange(newEmail: string): Promise<void>`
|
||||
- [ ] `confirmEmailChange(token: string): Promise<void>`
|
||||
- [ ] Implement password change
|
||||
- [ ] `changePassword(current: string, newPassword: string): Promise<void>`
|
||||
|
||||
### Django Endpoints to Use
|
||||
```typescript
|
||||
// Auth endpoints
|
||||
POST /api/v1/auth/login // Email/password login
|
||||
POST /api/v1/auth/register // User registration
|
||||
POST /api/v1/auth/logout // Logout
|
||||
POST /api/v1/auth/refresh // Refresh JWT
|
||||
GET /api/v1/auth/me // Get current user
|
||||
POST /api/v1/auth/password-reset // Request reset
|
||||
POST /api/v1/auth/password-reset/confirm // Confirm reset
|
||||
POST /api/v1/auth/verify-email // Verify email
|
||||
POST /api/v1/auth/change-email // Change email
|
||||
POST /api/v1/auth/change-password // Change password
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] Can login with email/password
|
||||
- [ ] Tokens stored securely
|
||||
- [ ] Auto-refresh works
|
||||
- [ ] Logout clears all tokens
|
||||
- [ ] Password reset flow works end-to-end
|
||||
- [ ] Email verification works
|
||||
- [ ] No Supabase dependencies
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 2.2: OAuth Integration (4 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Create `src/services/auth/oauthService.ts`
|
||||
- [ ] Implement OAuth initiation
|
||||
- [ ] `initiateOAuth(provider: 'google' | 'github'): void`
|
||||
- [ ] Redirect to Django OAuth endpoint
|
||||
- [ ] Handle state parameter for security
|
||||
- [ ] Implement OAuth callback
|
||||
- [ ] `handleOAuthCallback(code: string, state: string): Promise<AuthResponse>`
|
||||
- [ ] Exchange code for tokens
|
||||
- [ ] Store tokens
|
||||
- [ ] Return user object
|
||||
- [ ] Handle OAuth errors
|
||||
- [ ] Access denied
|
||||
- [ ] Invalid state
|
||||
- [ ] Server errors
|
||||
- [ ] Test with Google OAuth
|
||||
- [ ] Test with GitHub OAuth
|
||||
|
||||
### Django Endpoints
|
||||
```typescript
|
||||
GET /api/v1/auth/oauth/{provider}/ // Initiate OAuth
|
||||
GET /api/v1/auth/oauth/{provider}/callback // Handle callback
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] Google OAuth works
|
||||
- [ ] GitHub OAuth works
|
||||
- [ ] Errors handled gracefully
|
||||
- [ ] State parameter validated
|
||||
- [ ] Tokens stored correctly
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 2.3: MFA Service (3 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Create `src/services/auth/mfaService.ts`
|
||||
- [ ] Implement TOTP setup
|
||||
- [ ] `setupTOTP(): Promise<{ secret: string, qrCode: string }>`
|
||||
- [ ] Generate QR code URL
|
||||
- [ ] Return backup codes
|
||||
- [ ] Implement TOTP verification
|
||||
- [ ] `verifyTOTP(code: string): Promise<void>`
|
||||
- [ ] Enable MFA after verification
|
||||
- [ ] Implement MFA challenge
|
||||
- [ ] `challengeMFA(code: string): Promise<AuthResponse>`
|
||||
- [ ] Complete login with MFA code
|
||||
- [ ] Implement MFA removal
|
||||
- [ ] `removeMFA(password: string): Promise<void>`
|
||||
- [ ] Require password confirmation
|
||||
- [ ] Implement backup codes
|
||||
- [ ] `generateBackupCodes(): Promise<string[]>`
|
||||
- [ ] `useBackupCode(code: string): Promise<AuthResponse>`
|
||||
|
||||
### Django Endpoints
|
||||
```typescript
|
||||
POST /api/v1/auth/mfa/setup // Setup TOTP
|
||||
POST /api/v1/auth/mfa/verify // Verify TOTP
|
||||
POST /api/v1/auth/mfa/challenge // MFA challenge
|
||||
DELETE /api/v1/auth/mfa/remove // Remove MFA
|
||||
POST /api/v1/auth/mfa/backup-codes // Generate backup codes
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] TOTP setup works
|
||||
- [ ] QR code displays correctly
|
||||
- [ ] MFA challenge works during login
|
||||
- [ ] Backup codes work
|
||||
- [ ] MFA can be removed
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 2.4: Update useAuth Hook (5 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Open `src/hooks/useAuth.tsx`
|
||||
- [ ] Remove ALL Supabase imports
|
||||
- [ ] Replace `supabase.auth.onAuthStateChange` with custom implementation
|
||||
- [ ] Check token validity on mount
|
||||
- [ ] Set up interval to check token expiry
|
||||
- [ ] Trigger refresh before expiry
|
||||
- [ ] Replace `supabase.auth.getSession` with JWT validation
|
||||
- [ ] Decode JWT to get user info
|
||||
- [ ] Check expiry
|
||||
- [ ] Auto-refresh if needed
|
||||
- [ ] Update auth context state management
|
||||
- [ ] `user` state
|
||||
- [ ] `session` state
|
||||
- [ ] `loading` state
|
||||
- [ ] `initialized` state
|
||||
- [ ] Implement token refresh logic
|
||||
- [ ] Auto-refresh 5 minutes before expiry
|
||||
- [ ] Handle refresh failures
|
||||
- [ ] Logout on refresh failure
|
||||
- [ ] Update all auth methods to use new service
|
||||
- [ ] `signIn()`
|
||||
- [ ] `signUp()`
|
||||
- [ ] `signOut()`
|
||||
- [ ] `resetPassword()`
|
||||
- [ ] Handle session expiration gracefully
|
||||
- [ ] Add error handling for all auth operations
|
||||
- [ ] Test all auth flows
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] Hook works without Supabase
|
||||
- [ ] Token refresh is automatic
|
||||
- [ ] Session expiry is handled
|
||||
- [ ] User state updates correctly
|
||||
- [ ] Loading states work
|
||||
- [ ] All error cases handled
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 2.5: Update Auth Components (4 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Update `AuthModal.tsx`
|
||||
- [ ] Remove Supabase imports
|
||||
- [ ] Use auth service instead
|
||||
- [ ] Update error handling
|
||||
- [ ] Update `LoginForm.tsx`
|
||||
- [ ] Use `authService.login()`
|
||||
- [ ] Handle MFA challenge
|
||||
- [ ] Show appropriate errors
|
||||
- [ ] Update `RegisterForm.tsx`
|
||||
- [ ] Use `authService.register()`
|
||||
- [ ] Handle validation errors
|
||||
- [ ] Show email verification message
|
||||
- [ ] Update `PasswordResetForm.tsx`
|
||||
- [ ] Use `authService.requestPasswordReset()`
|
||||
- [ ] Use `authService.confirmPasswordReset()`
|
||||
- [ ] Handle token expiry
|
||||
- [ ] Update `TOTPSetup.tsx`
|
||||
- [ ] Use `mfaService.setupTOTP()`
|
||||
- [ ] Display QR code
|
||||
- [ ] Handle verification
|
||||
- [ ] Update `MFAChallenge.tsx`
|
||||
- [ ] Use `mfaService.challengeMFA()`
|
||||
- [ ] Handle backup codes
|
||||
- [ ] Update `MFARemovalDialog.tsx`
|
||||
- [ ] Use `mfaService.removeMFA()`
|
||||
- [ ] Require password confirmation
|
||||
- [ ] Remove all `supabase.*` imports
|
||||
- [ ] Test all UI flows
|
||||
- [ ] Update error messages to be user-friendly
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] All forms work correctly
|
||||
- [ ] Errors display properly
|
||||
- [ ] Loading states work
|
||||
- [ ] Success messages show
|
||||
- [ ] No Supabase references remain
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Completion Criteria
|
||||
|
||||
### Code Quality
|
||||
- [ ] Zero `supabase.auth.*` calls in codebase
|
||||
- [ ] All TypeScript errors resolved
|
||||
- [ ] No linter warnings
|
||||
- [ ] Code well-documented
|
||||
- [ ] Consistent error handling
|
||||
|
||||
### Functionality
|
||||
- [ ] Login works (email/password)
|
||||
- [ ] Registration works
|
||||
- [ ] Logout works
|
||||
- [ ] Password reset works
|
||||
- [ ] Email verification works
|
||||
- [ ] Google OAuth works
|
||||
- [ ] GitHub OAuth works
|
||||
- [ ] TOTP MFA works
|
||||
- [ ] Backup codes work
|
||||
- [ ] Token refresh is automatic
|
||||
- [ ] Session expiry handled
|
||||
|
||||
### Testing
|
||||
- [ ] Can login as regular user
|
||||
- [ ] Can login with OAuth
|
||||
- [ ] Can login with MFA
|
||||
- [ ] Can reset password
|
||||
- [ ] Can change email
|
||||
- [ ] Can change password
|
||||
- [ ] Token refresh works automatically
|
||||
- [ ] Session expiry redirects to login
|
||||
- [ ] All error cases handled
|
||||
|
||||
---
|
||||
|
||||
## 📊 Progress Tracking
|
||||
|
||||
**Started:** [Date]
|
||||
**Completed:** [Date]
|
||||
**Time Spent:** [Hours]
|
||||
|
||||
### Tasks Completed
|
||||
- [ ] Task 2.1: Auth Service Core
|
||||
- [ ] Task 2.2: OAuth Integration
|
||||
- [ ] Task 2.3: MFA Service
|
||||
- [ ] Task 2.4: Update useAuth Hook
|
||||
- [ ] Task 2.5: Update Auth Components
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Blockers & Issues
|
||||
|
||||
Document any issues:
|
||||
|
||||
1. **Issue:** [Description]
|
||||
- **Impact:** [Impact]
|
||||
- **Resolution:** [Resolution]
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- [Date] - [Important decision or learning]
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
Once complete, proceed to [Phase 3: Sacred Pipeline](./PHASE_03_SACRED_PIPELINE.md)
|
||||
369
migration/PHASE_03_SACRED_PIPELINE.md
Normal file
369
migration/PHASE_03_SACRED_PIPELINE.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# PHASE 3: Sacred Pipeline - Submissions
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 20-25 hours
|
||||
**Priority:** ⚠️ CRITICAL - THIS IS THE HEART OF THRILLWIKI
|
||||
**Depends On:** Phase 1 (Foundation), Phase 2 (Authentication)
|
||||
**Blocks:** All entity creation/editing features
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Goal
|
||||
Replace the ENTIRE submission system from Supabase to Django while maintaining 100% Sacred Pipeline integrity: **Form → Submission → Moderation → Approval → Versioning → Display**
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ CRITICAL WARNING
|
||||
The Sacred Pipeline is NON-NEGOTIABLE. Every single entity creation/edit MUST flow through:
|
||||
1. User submits form
|
||||
2. Creates submission record
|
||||
3. Goes to moderation queue
|
||||
4. Moderator approves/rejects
|
||||
5. On approval → creates/updates entity + version history
|
||||
6. Displays on site
|
||||
|
||||
**NEVER bypass moderation!** Exception: Users with moderator+ roles skip queue.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
- [x] Phase 1 complete (API client, types, base service)
|
||||
- [x] Phase 2 complete (Authentication working)
|
||||
- [ ] Django submission endpoints tested
|
||||
- [ ] Understanding of `entitySubmissionHelpers.ts` (1,200+ lines!)
|
||||
- [ ] Django Sacred Pipeline working and tested
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Files to Create/Modify
|
||||
|
||||
```
|
||||
src/services/submissions/
|
||||
├── submissionService.ts (NEW)
|
||||
├── types.ts (NEW)
|
||||
└── index.ts (NEW)
|
||||
|
||||
src/lib/
|
||||
├── entitySubmissionHelpers.ts (MAJOR REWRITE - 1,200+ lines!)
|
||||
|
||||
Forms that need updating:
|
||||
src/components/forms/
|
||||
├── ParkForm.tsx (MODIFY)
|
||||
├── RideForm.tsx (MODIFY)
|
||||
├── CompanyForm.tsx (MODIFY)
|
||||
├── RideModelForm.tsx (MODIFY)
|
||||
└── TimelineEventForm.tsx (MODIFY)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 3.1: Submission Service Core (8 hours)
|
||||
|
||||
### Checklist
|
||||
- [ ] Create `src/services/submissions/submissionService.ts`
|
||||
- [ ] Implement create submission
|
||||
- [ ] `createSubmission(data: SubmissionData): Promise<Submission>`
|
||||
- [ ] Handle single entity submissions
|
||||
- [ ] Handle composite submissions (park + operator)
|
||||
- [ ] Map form data to Django schema
|
||||
- [ ] Implement create with items
|
||||
- [ ] `createSubmissionWithItems(items: SubmissionItem[]): Promise<Submission>`
|
||||
- [ ] Handle dependency order
|
||||
- [ ] Handle temp IDs for relationships
|
||||
- [ ] Implement get submission
|
||||
- [ ] `getSubmission(id: string): Promise<Submission>`
|
||||
- [ ] Include all submission items
|
||||
- [ ] Include relational data
|
||||
- [ ] Implement list submissions
|
||||
- [ ] `listSubmissions(filters?: SubmissionFilters): Promise<Submission[]>`
|
||||
- [ ] Filter by user
|
||||
- [ ] Filter by status
|
||||
- [ ] Filter by type
|
||||
- [ ] Implement update submission status
|
||||
- [ ] For moderators only
|
||||
- [ ] Handle in moderation service
|
||||
- [ ] Handle errors properly
|
||||
- [ ] Validation errors
|
||||
- [ ] Rate limiting
|
||||
- [ ] Network errors
|
||||
- [ ] Add retry logic
|
||||
- [ ] Test with simple submission
|
||||
- [ ] Test with composite submission
|
||||
|
||||
### Django Endpoints
|
||||
```typescript
|
||||
POST /api/v1/submissions/ // Create submission
|
||||
POST /api/v1/submissions/with-items/ // Create with items
|
||||
GET /api/v1/submissions/{id}/ // Get submission
|
||||
GET /api/v1/submissions/ // List submissions
|
||||
GET /api/v1/submissions/my/ // User's submissions
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] Can create park submission
|
||||
- [ ] Can create ride submission
|
||||
- [ ] Can create composite (park + operator)
|
||||
- [ ] Submission goes to moderation queue
|
||||
- [ ] Errors are handled correctly
|
||||
- [ ] Rate limiting works
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 3.2: Rewrite entitySubmissionHelpers.ts (12 hours)
|
||||
|
||||
### ⚠️ THIS IS THE BIG ONE
|
||||
|
||||
This file has 1,200+ lines of Supabase code. Every function must be rewritten to use Django service.
|
||||
|
||||
### Functions to Rewrite
|
||||
|
||||
#### Park Submissions
|
||||
- [ ] `submitParkCreation()` - Create new park
|
||||
- [ ] Use submission service
|
||||
- [ ] Handle location data
|
||||
- [ ] Handle operator/owner references
|
||||
- [ ] Handle images
|
||||
- [ ] Test simple park creation
|
||||
- [ ] Test park + operator creation
|
||||
- [ ] Test park + operator + owner creation
|
||||
|
||||
- [ ] `submitParkUpdate()` - Edit existing park
|
||||
- [ ] Fetch existing park data
|
||||
- [ ] Extract changed fields only
|
||||
- [ ] Block new photo uploads (use gallery)
|
||||
- [ ] Allow banner/card reassignments
|
||||
- [ ] Test park edit
|
||||
|
||||
#### Ride Submissions
|
||||
- [ ] `submitRideCreation()` - Create new ride
|
||||
- [ ] Handle park reference
|
||||
- [ ] Handle manufacturer/designer references
|
||||
- [ ] Handle ride model reference
|
||||
- [ ] Handle images
|
||||
- [ ] Handle technical specifications
|
||||
- [ ] Test simple ride creation
|
||||
- [ ] Test ride + manufacturer creation
|
||||
- [ ] Test ride + park + manufacturer creation
|
||||
|
||||
- [ ] `submitRideUpdate()` - Edit existing ride
|
||||
- [ ] Fetch existing ride data
|
||||
- [ ] Extract changed fields
|
||||
- [ ] Block new photo uploads
|
||||
- [ ] Test ride edit
|
||||
|
||||
#### Company Submissions
|
||||
- [ ] `submitManufacturerCreation()` - Create manufacturer
|
||||
- [ ] `submitManufacturerUpdate()` - Edit manufacturer
|
||||
- [ ] `submitDesignerCreation()` - Create designer
|
||||
- [ ] `submitDesignerUpdate()` - Edit designer
|
||||
- [ ] `submitOperatorCreation()` - Create operator
|
||||
- [ ] `submitOperatorUpdate()` - Edit operator
|
||||
- [ ] `submitPropertyOwnerCreation()` - Create property owner
|
||||
- [ ] `submitPropertyOwnerUpdate()` - Edit property owner
|
||||
|
||||
#### Ride Model Submissions
|
||||
- [ ] `submitRideModelCreation()` - Create ride model
|
||||
- [ ] Handle manufacturer reference
|
||||
- [ ] Handle technical specifications
|
||||
- [ ] Test ride model creation
|
||||
|
||||
- [ ] `submitRideModelUpdate()` - Edit ride model
|
||||
- [ ] Test ride model edit
|
||||
|
||||
#### Timeline Event Submissions
|
||||
- [ ] `submitTimelineEvent()` - Create timeline event
|
||||
- [ ] Handle entity references
|
||||
- [ ] Handle date precision
|
||||
- [ ] Test timeline creation
|
||||
|
||||
- [ ] `submitTimelineEventUpdate()` - Edit timeline event
|
||||
- [ ] Test timeline edit
|
||||
|
||||
#### Composite Submissions
|
||||
- [ ] `submitCompositeCreation()` - Master function
|
||||
- [ ] Handle dependency ordering
|
||||
- [ ] Map temp IDs to order indices
|
||||
- [ ] Validate all temp refs
|
||||
- [ ] Handle all entity combinations:
|
||||
- [ ] Park + Operator
|
||||
- [ ] Park + Operator + Owner
|
||||
- [ ] Ride + Manufacturer
|
||||
- [ ] Ride + Manufacturer + Model
|
||||
- [ ] Ride + Park + Manufacturer + Model
|
||||
- [ ] Test all combinations
|
||||
|
||||
### CRITICAL REQUIREMENTS
|
||||
1. **NO JSON in SQL** - Use relational tables:
|
||||
- `park_submissions`
|
||||
- `ride_submissions`
|
||||
- `company_submissions`
|
||||
- `ride_model_submissions`
|
||||
- `timeline_event_submissions`
|
||||
|
||||
2. **Rate Limiting** - Keep existing logic:
|
||||
- `checkRateLimitOrThrow()`
|
||||
- `recordSubmissionAttempt()`
|
||||
|
||||
3. **Ban Checking** - Keep existing logic:
|
||||
- Check if user is banned
|
||||
- Report ban evasion attempts
|
||||
|
||||
4. **Image Handling**:
|
||||
- Upload images first
|
||||
- Only allow uploads on creation
|
||||
- Block uploads on edits (use gallery)
|
||||
- Allow banner/card reassignments
|
||||
|
||||
5. **Error Handling**:
|
||||
- Retry logic with exponential backoff
|
||||
- Validation errors don't retry
|
||||
- Network errors do retry (max 3)
|
||||
- User-friendly error messages
|
||||
|
||||
6. **Validation**:
|
||||
- Client-side validation before submit
|
||||
- Use validation functions
|
||||
- Clear error messages
|
||||
|
||||
### Testing Checklist
|
||||
- [ ] Create park (simple)
|
||||
- [ ] Create park + operator
|
||||
- [ ] Create park + operator + owner (same company)
|
||||
- [ ] Create park + operator + owner (different companies)
|
||||
- [ ] Edit park
|
||||
- [ ] Create ride (simple)
|
||||
- [ ] Create ride + manufacturer
|
||||
- [ ] Create ride + manufacturer + model
|
||||
- [ ] Create ride + park + manufacturer + model
|
||||
- [ ] Edit ride
|
||||
- [ ] Create manufacturer
|
||||
- [ ] Edit manufacturer
|
||||
- [ ] Create designer
|
||||
- [ ] Edit designer
|
||||
- [ ] Create operator
|
||||
- [ ] Edit operator
|
||||
- [ ] Create property owner
|
||||
- [ ] Edit property owner
|
||||
- [ ] Create ride model
|
||||
- [ ] Edit ride model
|
||||
- [ ] Create timeline event
|
||||
- [ ] Edit timeline event
|
||||
- [ ] Rate limiting works
|
||||
- [ ] Ban checking works
|
||||
- [ ] Validation works
|
||||
- [ ] Error handling works
|
||||
- [ ] Retry logic works
|
||||
- [ ] All go to moderation queue
|
||||
- [ ] Moderators skip queue
|
||||
|
||||
### Acceptance Criteria
|
||||
- [ ] Zero `supabase.*` calls in file
|
||||
- [ ] All 16+ submission types work
|
||||
- [ ] Composite submissions work
|
||||
- [ ] Sacred Pipeline intact
|
||||
- [ ] Rate limiting works
|
||||
- [ ] Ban checking works
|
||||
- [ ] Images upload correctly
|
||||
- [ ] Validation prevents bad data
|
||||
- [ ] Errors are user-friendly
|
||||
- [ ] All submissions go to moderation
|
||||
- [ ] TypeScript compiles without errors
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Completion Criteria
|
||||
|
||||
### Code Quality
|
||||
- [ ] Zero Supabase calls in `entitySubmissionHelpers.ts`
|
||||
- [ ] Zero Supabase calls in form components
|
||||
- [ ] All TypeScript errors resolved
|
||||
- [ ] No linter warnings
|
||||
- [ ] Code well-documented
|
||||
- [ ] Consistent error handling
|
||||
|
||||
### Functionality - MUST TEST ALL
|
||||
- [ ] Park creation works
|
||||
- [ ] Park editing works
|
||||
- [ ] Park + operator creation works
|
||||
- [ ] Ride creation works
|
||||
- [ ] Ride editing works
|
||||
- [ ] Ride + manufacturer creation works
|
||||
- [ ] Ride + manufacturer + model creation works
|
||||
- [ ] Manufacturer creation/editing works
|
||||
- [ ] Designer creation/editing works
|
||||
- [ ] Operator creation/editing works
|
||||
- [ ] Property owner creation/editing works
|
||||
- [ ] Ride model creation/editing works
|
||||
- [ ] Timeline event creation/editing works
|
||||
- [ ] Composite submissions work
|
||||
- [ ] All submissions go to moderation queue
|
||||
- [ ] Moderators can skip queue
|
||||
- [ ] Rate limiting prevents spam
|
||||
- [ ] Ban checking prevents abuse
|
||||
- [ ] Images upload correctly
|
||||
- [ ] Validation catches errors
|
||||
- [ ] Error messages are clear
|
||||
|
||||
### Sacred Pipeline Integrity
|
||||
- [ ] ✅ Form captured correctly
|
||||
- [ ] ✅ Submission created in Django
|
||||
- [ ] ✅ Goes to moderation queue
|
||||
- [ ] ✅ Moderator can approve/reject
|
||||
- [ ] ✅ Approval creates entity + version
|
||||
- [ ] ✅ Entity displays on site
|
||||
- [ ] ✅ Version history recorded
|
||||
- [ ] ✅ NEVER bypasses moderation (except moderators)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Progress Tracking
|
||||
|
||||
**Started:** [Date]
|
||||
**Completed:** [Date]
|
||||
**Time Spent:** [Hours]
|
||||
|
||||
### Tasks Completed
|
||||
- [ ] Task 3.1: Submission Service Core
|
||||
- [ ] Task 3.2: Rewrite entitySubmissionHelpers.ts
|
||||
- [ ] Park submissions
|
||||
- [ ] Ride submissions
|
||||
- [ ] Company submissions
|
||||
- [ ] Ride model submissions
|
||||
- [ ] Timeline event submissions
|
||||
- [ ] Composite submissions
|
||||
- [ ] Testing all submission types
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Blockers & Issues
|
||||
|
||||
Document any issues:
|
||||
|
||||
1. **Issue:** [Description]
|
||||
- **Impact:** [Impact]
|
||||
- **Resolution:** [Resolution]
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
**Important Decisions:**
|
||||
- [Date] - [Decision about submission flow]
|
||||
|
||||
**Learnings:**
|
||||
- [Date] - [Important learning]
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
Once complete, proceed to [Phase 4: Entity Services](./PHASE_04_ENTITY_SERVICES.md)
|
||||
|
||||
---
|
||||
|
||||
## 🆘 If You Get Stuck
|
||||
|
||||
1. Check Django submission endpoints work: `django/api/v1/endpoints/submissions.py`
|
||||
2. Check Django submission services: `django/apps/entities/services/`
|
||||
3. Review Sacred Pipeline docs: `django/SACRED_PIPELINE_AUDIT_AND_IMPLEMENTATION_PLAN.md`
|
||||
4. Test with Postman/curl first
|
||||
5. Don't proceed until submissions work end-to-end
|
||||
57
migration/PHASE_04_ENTITY_SERVICES.md
Normal file
57
migration/PHASE_04_ENTITY_SERVICES.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# PHASE 4: Entity Services
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 25-30 hours
|
||||
**Priority:** HIGH
|
||||
**Depends On:** Phase 1 (Foundation)
|
||||
**Blocks:** Phase 12 (Pages Migration)
|
||||
|
||||
## 🎯 Goal
|
||||
Create service layer for all entities (Parks, Rides, Companies, Ride Models) to replace Supabase queries.
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 4.1: Parks Service (5h)
|
||||
- [ ] Create `src/services/entities/parksService.ts`
|
||||
- [ ] List parks with filters
|
||||
- [ ] Get park by ID/slug
|
||||
- [ ] Get park rides
|
||||
- [ ] Pagination support
|
||||
- [ ] Update hooks: `useParks`, `useParkDetail`, `useParkRides`
|
||||
|
||||
### Task 4.2: Rides Service (5h)
|
||||
- [ ] Create `src/services/entities/ridesService.ts`
|
||||
- [ ] List rides with filters
|
||||
- [ ] Get ride by ID/slug
|
||||
- [ ] Get ride reviews/photos/credits
|
||||
- [ ] Update hooks: `useRides`, `useRideDetail`
|
||||
|
||||
### Task 4.3: Companies Service (5h)
|
||||
- [ ] Create `src/services/entities/companiesService.ts`
|
||||
- [ ] List companies by type
|
||||
- [ ] Get company by ID/slug
|
||||
- [ ] Get company parks/rides/models
|
||||
- [ ] Update company hooks
|
||||
|
||||
### Task 4.4: Ride Models Service (3h)
|
||||
- [ ] Create `src/services/entities/rideModelsService.ts`
|
||||
- [ ] List ride models
|
||||
- [ ] Get model by ID/slug
|
||||
- [ ] Get model installations
|
||||
|
||||
### Task 4.5: Update All Entity Hooks (7h)
|
||||
- [ ] Replace all Supabase calls
|
||||
- [ ] Test caching behavior
|
||||
- [ ] Verify loading states
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] All entity queries use services
|
||||
- [ ] No `supabase.from()` calls remain
|
||||
- [ ] All hooks work correctly
|
||||
- [ ] Pagination works
|
||||
- [ ] Filtering works
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
Once complete, proceed to [Phase 5: Reviews & Social](./PHASE_05_REVIEWS_SOCIAL.md)
|
||||
287
migration/PHASE_05_REVIEWS_SOCIAL.md
Normal file
287
migration/PHASE_05_REVIEWS_SOCIAL.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# PHASE 5: Reviews & Social Features
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 12-15 hours
|
||||
**Priority:** HIGH
|
||||
**Depends On:** Phase 1 (Foundation), Phase 4 (Entity Services)
|
||||
**Blocks:** Phase 12 (Pages Migration)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Build service layer for reviews, ride credits, and top lists to replace ALL Supabase queries related to social features.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 5.1: Reviews Service (5 hours)
|
||||
|
||||
**File:** `src/services/reviews/reviewsService.ts`
|
||||
|
||||
#### Create Core Service
|
||||
```typescript
|
||||
class ReviewsService extends BaseService {
|
||||
// GET /reviews/
|
||||
async getReviews(filters: ReviewFilters): Promise<PaginatedResponse<Review>>
|
||||
|
||||
// GET /reviews/{id}/
|
||||
async getReview(id: string): Promise<Review>
|
||||
|
||||
// POST /reviews/ (creates submission)
|
||||
async submitReview(data: ReviewSubmissionData): Promise<ContentSubmission>
|
||||
|
||||
// PATCH /reviews/{id}/ (creates submission)
|
||||
async updateReview(id: string, data: Partial<ReviewData>): Promise<ContentSubmission>
|
||||
|
||||
// DELETE /reviews/{id}/ (creates submission)
|
||||
async deleteReview(id: string, reason: string): Promise<ContentSubmission>
|
||||
|
||||
// POST /reviews/{id}/helpful/
|
||||
async markHelpful(reviewId: string): Promise<void>
|
||||
|
||||
// DELETE /reviews/{id}/helpful/
|
||||
async unmarkHelpful(reviewId: string): Promise<void>
|
||||
|
||||
// GET /reviews/?entity_type=ride&entity_id={id}
|
||||
async getEntityReviews(entityType: string, entityId: string): Promise<Review[]>
|
||||
|
||||
// GET /reviews/?user_id={id}
|
||||
async getUserReviews(userId: string): Promise<Review[]>
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `reviewsService.ts` with all methods
|
||||
- [ ] Create `types.ts` for Review types
|
||||
- [ ] Create `mappers.ts` for API ↔ Frontend transformations
|
||||
- [ ] Add error handling for all endpoints
|
||||
- [ ] Implement proper TypeScript types
|
||||
- [ ] Export from `src/services/reviews/index.ts`
|
||||
|
||||
---
|
||||
|
||||
### Task 5.2: Ride Credits Service (3 hours)
|
||||
|
||||
**File:** `src/services/rideCredits/rideCreditsService.ts`
|
||||
|
||||
#### Create Core Service
|
||||
```typescript
|
||||
class RideCreditsService extends BaseService {
|
||||
// GET /ride-credits/
|
||||
async getRideCredits(userId: string): Promise<UserRideCredit[]>
|
||||
|
||||
// GET /ride-credits/{id}/
|
||||
async getRideCredit(id: string): Promise<UserRideCredit>
|
||||
|
||||
// POST /ride-credits/ (creates submission)
|
||||
async submitRideCredit(data: RideCreditData): Promise<ContentSubmission>
|
||||
|
||||
// PATCH /ride-credits/{id}/ (creates submission)
|
||||
async updateRideCredit(id: string, data: Partial<RideCreditData>): Promise<ContentSubmission>
|
||||
|
||||
// DELETE /ride-credits/{id}/ (creates submission)
|
||||
async deleteRideCredit(id: string): Promise<ContentSubmission>
|
||||
|
||||
// GET /ride-credits/?ride_id={id}
|
||||
async getRideRideCredits(rideId: string): Promise<UserRideCredit[]>
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `rideCreditsService.ts` with all methods
|
||||
- [ ] Create types for ride credits
|
||||
- [ ] Create mappers for API transformations
|
||||
- [ ] Handle submission creation properly
|
||||
- [ ] Export from `src/services/rideCredits/index.ts`
|
||||
|
||||
---
|
||||
|
||||
### Task 5.3: Top Lists Service (4 hours)
|
||||
|
||||
**File:** `src/services/topLists/topListsService.ts`
|
||||
|
||||
#### Create Core Service
|
||||
```typescript
|
||||
class TopListsService extends BaseService {
|
||||
// GET /top-lists/
|
||||
async getTopLists(filters?: TopListFilters): Promise<UserTopList[]>
|
||||
|
||||
// GET /top-lists/{id}/
|
||||
async getTopList(id: string): Promise<UserTopList>
|
||||
|
||||
// POST /top-lists/
|
||||
async createTopList(data: TopListData): Promise<UserTopList>
|
||||
|
||||
// PATCH /top-lists/{id}/
|
||||
async updateTopList(id: string, data: Partial<TopListData>): Promise<UserTopList>
|
||||
|
||||
// DELETE /top-lists/{id}/
|
||||
async deleteTopList(id: string): Promise<void>
|
||||
|
||||
// GET /top-lists/{id}/items/
|
||||
async getTopListItems(listId: string): Promise<UserTopListItem[]>
|
||||
|
||||
// POST /top-lists/{id}/items/
|
||||
async addTopListItem(listId: string, item: TopListItemData): Promise<UserTopListItem>
|
||||
|
||||
// PATCH /top-lists/{id}/items/reorder/
|
||||
async reorderTopListItems(listId: string, itemIds: string[]): Promise<void>
|
||||
|
||||
// DELETE /top-lists/{id}/items/{itemId}/
|
||||
async removeTopListItem(listId: string, itemId: string): Promise<void>
|
||||
|
||||
// GET /top-lists/?user_id={id}
|
||||
async getUserTopLists(userId: string): Promise<UserTopList[]>
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `topListsService.ts` with all methods
|
||||
- [ ] Create types for top lists and items
|
||||
- [ ] Create mappers for API transformations
|
||||
- [ ] Handle reordering logic
|
||||
- [ ] Handle item CRUD operations
|
||||
- [ ] Export from `src/services/topLists/index.ts`
|
||||
|
||||
---
|
||||
|
||||
### Task 5.4: Update Review Components (3 hours)
|
||||
|
||||
**Files to Update:**
|
||||
- `src/components/reviews/ReviewForm.tsx`
|
||||
- `src/components/reviews/ReviewsList.tsx`
|
||||
- `src/components/reviews/ReviewCard.tsx`
|
||||
- `src/components/reviews/HelpfulButton.tsx`
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace `supabase.from('reviews')` with `reviewsService`
|
||||
- [ ] Update review submission to use service
|
||||
- [ ] Update helpful votes to use service
|
||||
- [ ] Update review deletion to use service
|
||||
- [ ] Test review CRUD operations
|
||||
- [ ] Verify error handling works
|
||||
|
||||
---
|
||||
|
||||
### Task 5.5: Update Ride Credit Components (2 hours)
|
||||
|
||||
**Files to Update:**
|
||||
- `src/components/profile/RideCreditsManager.tsx`
|
||||
- `src/components/profile/AddRideCreditDialog.tsx`
|
||||
- `src/components/profile/RideCreditCard.tsx`
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace all Supabase calls with `rideCreditsService`
|
||||
- [ ] Update submission flow to use service
|
||||
- [ ] Test adding ride credits
|
||||
- [ ] Test editing ride credits
|
||||
- [ ] Test deleting ride credits
|
||||
- [ ] Verify moderation queue receives submissions
|
||||
|
||||
---
|
||||
|
||||
### Task 5.6: Update Top Lists Components (3 hours)
|
||||
|
||||
**Files to Update:**
|
||||
- `src/components/lists/UserListManager.tsx`
|
||||
- `src/components/lists/ListDisplay.tsx`
|
||||
- `src/components/lists/ListItemEditor.tsx`
|
||||
- `src/components/lists/ListSearch.tsx`
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace all Supabase calls with `topListsService`
|
||||
- [ ] Update list CRUD operations
|
||||
- [ ] Update item reordering logic
|
||||
- [ ] Test creating/editing lists
|
||||
- [ ] Test adding/removing/reordering items
|
||||
- [ ] Verify list sharing works (if implemented)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
### Service Layer
|
||||
- [ ] All three services (reviews, rideCredits, topLists) created
|
||||
- [ ] All services extend BaseService
|
||||
- [ ] All services have proper TypeScript types
|
||||
- [ ] All services have error handling
|
||||
- [ ] All services have mappers for API transformations
|
||||
|
||||
### Components
|
||||
- [ ] Zero `supabase.from('reviews')` calls
|
||||
- [ ] Zero `supabase.from('user_ride_credits')` calls
|
||||
- [ ] Zero `supabase.from('user_top_lists')` calls
|
||||
- [ ] Zero `supabase.from('user_top_list_items')` calls
|
||||
- [ ] All review operations work
|
||||
- [ ] All ride credit operations work
|
||||
- [ ] All top list operations work
|
||||
|
||||
### Testing
|
||||
- [ ] Can create a review (goes to moderation)
|
||||
- [ ] Can mark review as helpful
|
||||
- [ ] Can add a ride credit (goes to moderation)
|
||||
- [ ] Can create a top list
|
||||
- [ ] Can add items to top list
|
||||
- [ ] Can reorder top list items
|
||||
- [ ] Can delete top list items
|
||||
- [ ] Can delete entire top list
|
||||
|
||||
### Sacred Pipeline
|
||||
- [ ] Review submissions go through moderation
|
||||
- [ ] Review updates go through moderation
|
||||
- [ ] Review deletions go through moderation
|
||||
- [ ] Ride credit submissions go through moderation
|
||||
- [ ] Pipeline integrity maintained
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Notes
|
||||
|
||||
### Review Submissions
|
||||
- Reviews are user-generated content and MUST go through the Sacred Pipeline
|
||||
- Use `reviewsService.submitReview()` which creates a ContentSubmission
|
||||
- Only approved reviews appear in the system
|
||||
|
||||
### Ride Credits
|
||||
- Ride credits are also user-generated and MUST go through moderation
|
||||
- Users claim they've ridden a ride, moderators verify
|
||||
- Auto-crediting on review approval should still work
|
||||
|
||||
### Top Lists
|
||||
- Top lists are personal user content
|
||||
- May NOT require moderation (check with backend team)
|
||||
- Item reordering is immediate (no moderation)
|
||||
|
||||
### Helpful Votes
|
||||
- Marking a review as helpful is immediate (no moderation)
|
||||
- Backend tracks vote counts automatically
|
||||
- Each user can only vote once per review
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
### Services
|
||||
- `src/services/reviews/reviewsService.ts`
|
||||
- `src/services/reviews/types.ts`
|
||||
- `src/services/reviews/mappers.ts`
|
||||
- `src/services/rideCredits/rideCreditsService.ts`
|
||||
- `src/services/topLists/topListsService.ts`
|
||||
|
||||
### Components
|
||||
- All files in `src/components/reviews/`
|
||||
- All files in `src/components/profile/` (ride credits)
|
||||
- All files in `src/components/lists/` (top lists)
|
||||
|
||||
### Hooks
|
||||
- `src/hooks/useReviews.ts`
|
||||
- `src/hooks/useRideCredits.ts`
|
||||
- `src/hooks/useTopLists.ts`
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
**Phase 6:** Moderation & Admin - Create moderation service and update moderation queue components.
|
||||
256
migration/PHASE_06_MODERATION_ADMIN.md
Normal file
256
migration/PHASE_06_MODERATION_ADMIN.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# PHASE 6: Moderation & Admin
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 10-12 hours
|
||||
**Priority:** CRITICAL
|
||||
**Depends On:** Phase 1 (Foundation), Phase 3 (Sacred Pipeline)
|
||||
**Blocks:** Phase 12 (Pages Migration)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Complete migration of moderation system from Supabase to Django, including moderation queue, approval/rejection flows, and admin dashboard.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 6.1: Moderation Service (4 hours)
|
||||
|
||||
**File:** `src/services/moderation/moderationService.ts`
|
||||
|
||||
#### Create Core Service
|
||||
```typescript
|
||||
class ModerationService extends BaseService {
|
||||
// GET /moderation/queue/
|
||||
async getModerationQueue(filters?: QueueFilters): Promise<PaginatedResponse<ContentSubmission>>
|
||||
|
||||
// POST /moderation/queue/claim/
|
||||
async claimNextSubmission(): Promise<ContentSubmission | null>
|
||||
|
||||
// POST /moderation/submissions/{id}/start-review/
|
||||
async startReview(submissionId: string): Promise<ModerationLock>
|
||||
|
||||
// POST /moderation/submissions/{id}/extend-lock/
|
||||
async extendLock(submissionId: string): Promise<ModerationLock>
|
||||
|
||||
// POST /moderation/submissions/{id}/release-lock/
|
||||
async releaseLock(submissionId: string): Promise<void>
|
||||
|
||||
// POST /moderation/submissions/{id}/approve/
|
||||
async approveSubmission(submissionId: string, notes?: string): Promise<void>
|
||||
|
||||
// POST /moderation/submissions/{id}/reject/
|
||||
async rejectSubmission(submissionId: string, reason: string): Promise<void>
|
||||
|
||||
// POST /moderation/submissions/{id}/approve-selective/
|
||||
async approveSelective(submissionId: string, itemIds: string[]): Promise<void>
|
||||
|
||||
// GET /moderation/stats/
|
||||
async getModerationStats(): Promise<ModerationStats>
|
||||
|
||||
// GET /moderation/submissions/{id}/
|
||||
async getSubmission(submissionId: string): Promise<ContentSubmission>
|
||||
|
||||
// GET /moderation/submissions/{id}/items/
|
||||
async getSubmissionItems(submissionId: string): Promise<SubmissionItem[]>
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `moderationService.ts` with all methods
|
||||
- [ ] Create types for moderation operations
|
||||
- [ ] Create mappers for API transformations
|
||||
- [ ] Handle lock management properly
|
||||
- [ ] Handle selective approval logic
|
||||
- [ ] Export from `src/services/moderation/index.ts`
|
||||
|
||||
---
|
||||
|
||||
### Task 6.2: Update ModerationQueue Component (3 hours)
|
||||
|
||||
**File:** `src/components/moderation/ModerationQueue.tsx`
|
||||
|
||||
#### Replace ALL Supabase Calls
|
||||
```typescript
|
||||
// OLD - Supabase
|
||||
const { data } = await supabase.from('content_submissions')
|
||||
.select('*')
|
||||
.eq('status', 'pending_review');
|
||||
|
||||
// NEW - Django Service
|
||||
const submissions = await moderationService.getModerationQueue({
|
||||
status: 'pending_review'
|
||||
});
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace queue fetching with `moderationService.getModerationQueue()`
|
||||
- [ ] Replace claim logic with `moderationService.claimNextSubmission()`
|
||||
- [ ] Update lock extension with `moderationService.extendLock()`
|
||||
- [ ] Update lock release with `moderationService.releaseLock()`
|
||||
- [ ] Remove all realtime subscriptions (Django uses polling or WebSockets)
|
||||
- [ ] Test queue display
|
||||
- [ ] Test claiming submissions
|
||||
- [ ] Verify lock management works
|
||||
|
||||
---
|
||||
|
||||
### Task 6.3: Update Approval/Rejection Components (2 hours)
|
||||
|
||||
**Files:**
|
||||
- `src/components/moderation/ApprovalDialog.tsx`
|
||||
- `src/components/moderation/RejectionDialog.tsx`
|
||||
- `src/components/moderation/SelectiveApprovalDialog.tsx`
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace approval with `moderationService.approveSubmission()`
|
||||
- [ ] Replace rejection with `moderationService.rejectSubmission()`
|
||||
- [ ] Replace selective approval with `moderationService.approveSelective()`
|
||||
- [ ] Update success/error handling
|
||||
- [ ] Test all three approval flows
|
||||
- [ ] Verify Sacred Pipeline integrity
|
||||
|
||||
---
|
||||
|
||||
### Task 6.4: Update Admin Dashboard (3 hours)
|
||||
|
||||
**Files:**
|
||||
- `src/pages/AdminDashboard.tsx`
|
||||
- `src/components/admin/ModerationStats.tsx`
|
||||
- `src/components/admin/PipelineHealthAlerts.tsx`
|
||||
|
||||
#### Replace Admin Queries
|
||||
```typescript
|
||||
// OLD - Supabase
|
||||
const { data } = await supabase.rpc('get_moderation_stats');
|
||||
|
||||
// NEW - Django Service
|
||||
const stats = await moderationService.getModerationStats();
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace `supabase.rpc('get_moderation_stats')` with service
|
||||
- [ ] Update pipeline health monitoring
|
||||
- [ ] Remove Supabase admin audit log calls
|
||||
- [ ] Update admin user management (if any)
|
||||
- [ ] Test admin dashboard loads
|
||||
- [ ] Test stats display correctly
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
### Service Layer
|
||||
- [ ] ModerationService created and fully functional
|
||||
- [ ] All moderation operations use service
|
||||
- [ ] Lock management works correctly
|
||||
- [ ] Selective approval works
|
||||
- [ ] Stats fetching works
|
||||
|
||||
### Components
|
||||
- [ ] Zero `supabase.from('content_submissions')` calls
|
||||
- [ ] Zero `supabase.from('moderation_locks')` calls
|
||||
- [ ] Zero `supabase.rpc()` calls for moderation
|
||||
- [ ] ModerationQueue works with Django
|
||||
- [ ] All approval flows work
|
||||
- [ ] Admin dashboard works
|
||||
|
||||
### Testing
|
||||
- [ ] Can view moderation queue
|
||||
- [ ] Can claim a submission
|
||||
- [ ] Lock extends automatically
|
||||
- [ ] Can approve submission
|
||||
- [ ] Can reject submission
|
||||
- [ ] Can selective approve (approve some items, reject others)
|
||||
- [ ] Lock releases on page navigation
|
||||
- [ ] Stats update correctly
|
||||
- [ ] Pipeline health monitoring works
|
||||
|
||||
### Sacred Pipeline
|
||||
- [ ] All approvals go through Django
|
||||
- [ ] No bypass of moderation system
|
||||
- [ ] Versioning happens on approval
|
||||
- [ ] Rejection reasons are saved
|
||||
- [ ] Admin notes are saved
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Notes
|
||||
|
||||
### Lock Management
|
||||
- Locks prevent multiple moderators from reviewing same submission
|
||||
- Locks auto-extend every 30 seconds (client-side timer)
|
||||
- Locks auto-release after 5 minutes of inactivity (server-side)
|
||||
- Must release lock on component unmount
|
||||
- Must release lock on page navigation
|
||||
|
||||
### Selective Approval
|
||||
- Allows approving some submission items while rejecting others
|
||||
- Common for batch submissions (e.g., adding 10 rides at once)
|
||||
- Each item can be individually approved/rejected
|
||||
- Must maintain data integrity
|
||||
|
||||
### Realtime Updates
|
||||
- Supabase used realtime subscriptions for queue updates
|
||||
- Django migration: either polling (every 10s) or WebSockets
|
||||
- For MVP, polling is acceptable
|
||||
- Queue should refresh when lock is released
|
||||
|
||||
### Admin Permissions
|
||||
- Only users with `role='moderator'` or `role='admin'` can access
|
||||
- Backend enforces permissions on all endpoints
|
||||
- Frontend should hide moderation UI for non-moderators
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
### Services
|
||||
- `src/services/moderation/moderationService.ts`
|
||||
- `src/services/moderation/types.ts`
|
||||
- `src/services/moderation/mappers.ts`
|
||||
|
||||
### Components
|
||||
- `src/components/moderation/ModerationQueue.tsx`
|
||||
- `src/components/moderation/QueueFilters.tsx`
|
||||
- `src/components/moderation/SubmissionCard.tsx`
|
||||
- `src/components/moderation/ApprovalDialog.tsx`
|
||||
- `src/components/moderation/RejectionDialog.tsx`
|
||||
- `src/components/moderation/SelectiveApprovalDialog.tsx`
|
||||
- `src/components/admin/ModerationStats.tsx`
|
||||
- `src/components/admin/PipelineHealthAlerts.tsx`
|
||||
|
||||
### Pages
|
||||
- `src/pages/AdminDashboard.tsx`
|
||||
- `src/pages/ModerationQueue.tsx`
|
||||
|
||||
### Hooks
|
||||
- `src/hooks/useModerationQueue.ts`
|
||||
- `src/hooks/useModerationStats.ts`
|
||||
- `src/hooks/useModerationLock.ts`
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Critical Warnings
|
||||
|
||||
### DO NOT
|
||||
- ❌ Create any bypass mechanism for moderation
|
||||
- ❌ Allow direct database writes without submissions
|
||||
- ❌ Skip versioning on approval
|
||||
- ❌ Allow approvals without proper permissions
|
||||
- ❌ Leave locks hanging (always cleanup)
|
||||
|
||||
### MUST DO
|
||||
- ✅ ALL entity changes go through ContentSubmission
|
||||
- ✅ Locks are properly managed and released
|
||||
- ✅ Selective approval maintains data integrity
|
||||
- ✅ Admin permissions are enforced
|
||||
- ✅ Rejection reasons are mandatory
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
**Phase 7:** Media & Photos - Migrate photo upload and management to Django + CloudFlare Images.
|
||||
121
migration/PHASE_07_MEDIA_PHOTOS.md
Normal file
121
migration/PHASE_07_MEDIA_PHOTOS.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# PHASE 7: Media & Photos
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 8-10 hours
|
||||
**Priority:** HIGH
|
||||
**Depends On:** Phase 1 (Foundation)
|
||||
**Blocks:** Phase 12 (Pages Migration)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Migrate photo upload and management from Supabase Storage to Django + CloudFlare Images.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 7.1: Photos Service (3 hours)
|
||||
|
||||
**File:** `src/services/photos/photosService.ts`
|
||||
|
||||
```typescript
|
||||
class PhotosService extends BaseService {
|
||||
// GET /photos/
|
||||
async getPhotos(filters?: PhotoFilters): Promise<PaginatedResponse<Photo>>
|
||||
|
||||
// GET /photos/{id}/
|
||||
async getPhoto(id: string): Promise<Photo>
|
||||
|
||||
// POST /photos/upload/
|
||||
async uploadPhoto(file: File, metadata: PhotoMetadata): Promise<Photo>
|
||||
|
||||
// PATCH /photos/{id}/
|
||||
async updatePhoto(id: string, data: Partial<PhotoData>): Promise<Photo>
|
||||
|
||||
// DELETE /photos/{id}/
|
||||
async deletePhoto(id: string): Promise<void>
|
||||
|
||||
// GET /photos/?entity_type={type}&entity_id={id}
|
||||
async getEntityPhotos(entityType: string, entityId: string): Promise<Photo[]>
|
||||
|
||||
// POST /photos/cloudflare-upload-url/
|
||||
async getUploadUrl(): Promise<{ uploadUrl: string; imageId: string }>
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `photosService.ts`
|
||||
- [ ] Handle CloudFlare upload workflow
|
||||
- [ ] Create types and mappers
|
||||
- [ ] Handle photo metadata properly
|
||||
|
||||
---
|
||||
|
||||
### Task 7.2: Update Photo Upload Components (3 hours)
|
||||
|
||||
**Files:**
|
||||
- `src/components/upload/PhotoUpload.tsx`
|
||||
- `src/components/upload/UppyPhotoUpload.tsx`
|
||||
- `src/components/upload/EntityMultiImageUploader.tsx`
|
||||
|
||||
#### New Upload Flow
|
||||
1. Get CloudFlare upload URL from Django
|
||||
2. Upload file directly to CloudFlare
|
||||
3. Send CloudFlare image ID + metadata to Django
|
||||
4. Django creates Photo record
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace Supabase Storage with CloudFlare
|
||||
- [ ] Update upload workflow
|
||||
- [ ] Handle upload errors
|
||||
- [ ] Test single photo upload
|
||||
- [ ] Test batch photo uploads
|
||||
|
||||
---
|
||||
|
||||
### Task 7.3: Update Photo Management (2 hours)
|
||||
|
||||
**Files:**
|
||||
- `src/components/upload/PhotoManagementDialog.tsx`
|
||||
- `src/components/upload/EntityPhotoGallery.tsx`
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace photo queries with `photosService`
|
||||
- [ ] Update caption editing
|
||||
- [ ] Update photo deletion
|
||||
- [ ] Test photo gallery display
|
||||
- [ ] Test photo management operations
|
||||
|
||||
---
|
||||
|
||||
### Task 7.4: Update Photo Display Components (2 hours)
|
||||
|
||||
**Files:**
|
||||
- `src/components/rides/RecentPhotosPreview.tsx`
|
||||
- Various entity detail pages with photo galleries
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace all Supabase Storage URLs with CloudFlare URLs
|
||||
- [ ] Update image optimization parameters
|
||||
- [ ] Test responsive images
|
||||
- [ ] Verify lazy loading works
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
- [ ] Zero `supabase.storage` calls
|
||||
- [ ] All photo uploads go through CloudFlare
|
||||
- [ ] Photo metadata saved to Django
|
||||
- [ ] Photo galleries work
|
||||
- [ ] Photo captions can be updated
|
||||
- [ ] Photos can be deleted
|
||||
- [ ] Image URLs are CloudFlare URLs
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
**Phase 8:** Search & Discovery
|
||||
197
migration/PHASE_08_SEARCH_DISCOVERY.md
Normal file
197
migration/PHASE_08_SEARCH_DISCOVERY.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# PHASE 8: Search & Discovery
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 8-10 hours
|
||||
**Priority:** HIGH
|
||||
**Depends On:** Phase 1 (Foundation), Phase 4 (Entity Services)
|
||||
**Blocks:** Phase 12 (Pages Migration)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Migrate all search functionality from Supabase to Django's PostgreSQL full-text search with GIN indexes.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 8.1: Search Service (3 hours)
|
||||
|
||||
**File:** `src/services/search/searchService.ts`
|
||||
|
||||
#### Create Core Service
|
||||
```typescript
|
||||
class SearchService extends BaseService {
|
||||
// GET /search/?q={query}
|
||||
async globalSearch(query: string, filters?: SearchFilters): Promise<SearchResults>
|
||||
|
||||
// GET /search/parks/?q={query}
|
||||
async searchParks(query: string, filters?: ParkFilters): Promise<Park[]>
|
||||
|
||||
// GET /search/rides/?q={query}
|
||||
async searchRides(query: string, filters?: RideFilters): Promise<Ride[]>
|
||||
|
||||
// GET /search/companies/?q={query}
|
||||
async searchCompanies(query: string, filters?: CompanyFilters): Promise<Company[]>
|
||||
|
||||
// GET /search/ride-models/?q={query}
|
||||
async searchRideModels(query: string, filters?: RideModelFilters): Promise<RideModel[]>
|
||||
|
||||
// GET /search/suggestions/?q={query}
|
||||
async getSearchSuggestions(query: string): Promise<string[]>
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `searchService.ts` with all methods
|
||||
- [ ] Create types for search results
|
||||
- [ ] Create mappers for search transformations
|
||||
- [ ] Handle search ranking/relevance
|
||||
- [ ] Implement debounced search
|
||||
- [ ] Export from `src/services/search/index.ts`
|
||||
|
||||
---
|
||||
|
||||
### Task 8.2: Update Search Components (2 hours)
|
||||
|
||||
**File:** `src/components/search/SearchResults.tsx`
|
||||
|
||||
#### Replace Supabase Calls
|
||||
```typescript
|
||||
// OLD - Supabase
|
||||
const { data } = await supabase
|
||||
.from('parks')
|
||||
.select('*')
|
||||
.textSearch('name', query);
|
||||
|
||||
// NEW - Django Service
|
||||
const parks = await searchService.searchParks(query, filters);
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace `supabase.textSearch()` with `searchService.globalSearch()`
|
||||
- [ ] Update search result display
|
||||
- [ ] Update search highlighting
|
||||
- [ ] Test search results rendering
|
||||
- [ ] Verify pagination works
|
||||
|
||||
---
|
||||
|
||||
### Task 8.3: Update Filter Components (3 hours)
|
||||
|
||||
**Files to Update:**
|
||||
- `src/components/parks/ParkFilters.tsx`
|
||||
- `src/components/rides/RideFilters.tsx`
|
||||
- `src/components/manufacturers/ManufacturerFilters.tsx`
|
||||
- `src/components/operators/OperatorFilters.tsx`
|
||||
- `src/components/designers/DesignerFilters.tsx`
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace all Supabase filter queries with service calls
|
||||
- [ ] Update park filters (type, location, status)
|
||||
- [ ] Update ride filters (category, manufacturer, status)
|
||||
- [ ] Update company filters (type, country)
|
||||
- [ ] Test all filter combinations
|
||||
- [ ] Verify filter state persistence
|
||||
|
||||
---
|
||||
|
||||
### Task 8.4: Search Autocomplete (2 hours)
|
||||
|
||||
**File:** `src/components/search/SearchAutocomplete.tsx`
|
||||
|
||||
#### Create/Update Autocomplete
|
||||
- [ ] Implement search suggestions
|
||||
- [ ] Debounce search input
|
||||
- [ ] Display recent searches
|
||||
- [ ] Handle keyboard navigation
|
||||
- [ ] Test autocomplete behavior
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
### Service Layer
|
||||
- [ ] SearchService created and functional
|
||||
- [ ] All search operations use service
|
||||
- [ ] Search ranking works correctly
|
||||
- [ ] Suggestions work
|
||||
|
||||
### Components
|
||||
- [ ] Zero `supabase.textSearch()` calls
|
||||
- [ ] Zero `supabase.from().ilike()` calls for search
|
||||
- [ ] All filter components updated
|
||||
- [ ] Search results display correctly
|
||||
- [ ] Autocomplete works
|
||||
|
||||
### Testing
|
||||
- [ ] Global search works
|
||||
- [ ] Entity-specific search works
|
||||
- [ ] Filters work correctly
|
||||
- [ ] Search rankings are relevant
|
||||
- [ ] Autocomplete suggests correctly
|
||||
- [ ] Empty search handled gracefully
|
||||
- [ ] No results handled gracefully
|
||||
|
||||
### Performance
|
||||
- [ ] Search is fast (<500ms)
|
||||
- [ ] Debouncing prevents excessive requests
|
||||
- [ ] Results are properly cached
|
||||
- [ ] Pagination works smoothly
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Notes
|
||||
|
||||
### Django Search Implementation
|
||||
- Django uses PostgreSQL GIN indexes for full-text search
|
||||
- Search vectors are automatically updated via signals
|
||||
- Ranking is handled by PostgreSQL `ts_rank`
|
||||
- Supports phrase search, AND/OR operators
|
||||
- Accent-insensitive search
|
||||
|
||||
### Search Features
|
||||
- **Global Search**: Searches across all entity types
|
||||
- **Entity Search**: Searches within specific entity type
|
||||
- **Filters**: Can be combined with search queries
|
||||
- **Ranking**: Results sorted by relevance
|
||||
- **Highlighting**: Search terms highlighted in results
|
||||
|
||||
### Debouncing
|
||||
- Implement 300ms debounce on search input
|
||||
- Cancel previous search requests
|
||||
- Show loading state during search
|
||||
- Cache recent search results
|
||||
|
||||
### Empty States
|
||||
- Handle empty search query
|
||||
- Handle no results found
|
||||
- Suggest alternative spellings
|
||||
- Show recent searches
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
### Services
|
||||
- `src/services/search/searchService.ts`
|
||||
- `src/services/search/types.ts`
|
||||
- `src/services/search/mappers.ts`
|
||||
|
||||
### Components
|
||||
- `src/components/search/SearchResults.tsx`
|
||||
- `src/components/search/SearchAutocomplete.tsx`
|
||||
- `src/components/search/SearchFilters.tsx`
|
||||
- All filter components in entity directories
|
||||
|
||||
### Hooks
|
||||
- `src/hooks/useSearch.ts`
|
||||
- `src/hooks/useSearchFilters.ts`
|
||||
- `src/hooks/useSearchSuggestions.ts`
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
**Phase 9:** Timeline & History - Migrate timeline events and entity history to Django.
|
||||
81
migration/PHASE_09_TIMELINE_HISTORY.md
Normal file
81
migration/PHASE_09_TIMELINE_HISTORY.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# PHASE 9: Timeline & History
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 6-8 hours
|
||||
**Priority:** MEDIUM
|
||||
**Depends On:** Phase 1 (Foundation)
|
||||
**Blocks:** Phase 12 (Pages Migration)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Migrate timeline events and entity history/versioning from Supabase to Django's pghistory system.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 9.1: Timeline Service (2 hours)
|
||||
|
||||
```typescript
|
||||
class TimelineService extends BaseService {
|
||||
// GET /timeline/entity/{type}/{id}/
|
||||
async getEntityTimeline(entityType: string, entityId: string): Promise<TimelineEvent[]>
|
||||
|
||||
// POST /timeline/entity/{type}/{id}/
|
||||
async createTimelineEvent(entityType: string, entityId: string, data: TimelineEventData): Promise<TimelineEvent>
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Create `src/services/timeline/timelineService.ts`
|
||||
- [ ] Update `src/components/timeline/EntityTimelineManager.tsx`
|
||||
|
||||
### Task 9.2: History Service (2 hours)
|
||||
|
||||
```typescript
|
||||
class HistoryService extends BaseService {
|
||||
// GET /history/{entity_type}/{entity_id}/
|
||||
async getEntityHistory(entityType: string, entityId: string): Promise<HistoryEntry[]>
|
||||
|
||||
// GET /history/{entity_type}/{entity_id}/diff/?version1={v1}&version2={v2}
|
||||
async getVersionDiff(entityType: string, entityId: string, v1: string, v2: string): Promise<VersionDiff>
|
||||
|
||||
// GET /history/recent/
|
||||
async getRecentChanges(limit?: number): Promise<HistoryEntry[]>
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Create `src/services/history/historyService.ts`
|
||||
- [ ] Replace `supabase.rpc('get_version_diff')`
|
||||
- [ ] Replace `supabase.rpc('get_recent_changes')`
|
||||
|
||||
### Task 9.3: Update Components (2-3 hours)
|
||||
|
||||
- [ ] Update version comparison components
|
||||
- [ ] Update history display components
|
||||
- [ ] Update recent changes widget
|
||||
- [ ] Remove all Supabase RPC calls
|
||||
|
||||
### Task 9.4: Homepage Integration (1 hour)
|
||||
|
||||
- [ ] Update `src/hooks/homepage/useHomepageRecentChanges.ts`
|
||||
- [ ] Replace `supabase.rpc('get_recent_changes')`
|
||||
- [ ] Test recent changes display
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
- [ ] Timeline events display correctly
|
||||
- [ ] Entity history works
|
||||
- [ ] Version diffs work
|
||||
- [ ] Recent changes widget works
|
||||
- [ ] Zero `supabase.rpc()` calls for history
|
||||
- [ ] Homepage shows recent changes
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
**Phase 10:** Users & Profiles
|
||||
110
migration/PHASE_10_USERS_PROFILES.md
Normal file
110
migration/PHASE_10_USERS_PROFILES.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# PHASE 10: Users & Profiles
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 8-10 hours
|
||||
**Priority:** HIGH
|
||||
**Depends On:** Phase 1 (Foundation), Phase 2 (Authentication)
|
||||
**Blocks:** Phase 12 (Pages Migration)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Migrate all user profile and user management functionality from Supabase to Django.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 10.1: Users Service (3 hours)
|
||||
|
||||
```typescript
|
||||
class UsersService extends BaseService {
|
||||
// GET /users/{id}/
|
||||
async getUser(userId: string): Promise<User>
|
||||
|
||||
// GET /users/{id}/profile/
|
||||
async getUserProfile(userId: string): Promise<UserProfile>
|
||||
|
||||
// PATCH /users/{id}/profile/
|
||||
async updateUserProfile(userId: string, data: Partial<ProfileData>): Promise<UserProfile>
|
||||
|
||||
// GET /users/search/?q={query}
|
||||
async searchUsers(query: string): Promise<User[]>
|
||||
|
||||
// POST /users/{id}/block/
|
||||
async blockUser(userId: string): Promise<void>
|
||||
|
||||
// DELETE /users/{id}/block/
|
||||
async unblockUser(userId: string): Promise<void>
|
||||
|
||||
// GET /users/{id}/blocked/
|
||||
async getBlockedUsers(userId: string): Promise<User[]>
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Create `src/services/users/usersService.ts`
|
||||
- [ ] Create types and mappers
|
||||
- [ ] Handle profile updates
|
||||
- [ ] Handle user blocking
|
||||
|
||||
### Task 10.2: Update Profile Pages (3 hours)
|
||||
|
||||
**Files:**
|
||||
- `src/pages/Profile.tsx`
|
||||
- `src/components/profile/UserReviewsList.tsx`
|
||||
- `src/components/profile/RideCreditsManager.tsx`
|
||||
- `src/components/profile/UserBlockButton.tsx`
|
||||
|
||||
- [ ] Replace `supabase.from('profiles')` with `usersService`
|
||||
- [ ] Replace `supabase.rpc('get_filtered_profile')`
|
||||
- [ ] Update profile editing
|
||||
- [ ] Test profile display
|
||||
|
||||
### Task 10.3: Update Settings Pages (3 hours)
|
||||
|
||||
**Files:**
|
||||
- `src/pages/Settings.tsx`
|
||||
- `src/components/settings/AccountProfileTab.tsx`
|
||||
- `src/components/settings/SecurityTab.tsx`
|
||||
- `src/components/settings/PrivacyTab.tsx`
|
||||
- `src/components/settings/LocationTab.tsx`
|
||||
- `src/components/settings/DataExportTab.tsx`
|
||||
|
||||
- [ ] Replace all Supabase auth/profile calls
|
||||
- [ ] Update email change flow
|
||||
- [ ] Update password change flow
|
||||
- [ ] Update account deletion flow
|
||||
- [ ] Update data export
|
||||
|
||||
### Task 10.4: Update User Blocking (1-2 hours)
|
||||
|
||||
**Files:**
|
||||
- `src/components/privacy/BlockedUsers.tsx`
|
||||
- `src/components/profile/UserBlockButton.tsx`
|
||||
|
||||
- [ ] Replace block/unblock operations
|
||||
- [ ] Update blocked users list
|
||||
- [ ] Test blocking functionality
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
- [ ] User profiles display correctly
|
||||
- [ ] Profile editing works
|
||||
- [ ] Settings pages work
|
||||
- [ ] User blocking works
|
||||
- [ ] Avatar uploads work
|
||||
- [ ] Email change works
|
||||
- [ ] Password change works
|
||||
- [ ] Data export works
|
||||
- [ ] Account deletion works
|
||||
- [ ] Zero `supabase.from('profiles')` calls
|
||||
- [ ] Zero `supabase.auth.updateUser()` calls (except in auth service)
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
**Phase 11:** Contact & Reports
|
||||
79
migration/PHASE_11_CONTACT_REPORTS.md
Normal file
79
migration/PHASE_11_CONTACT_REPORTS.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# PHASE 11: Contact & Reports
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 4-6 hours
|
||||
**Priority:** MEDIUM
|
||||
**Depends On:** Phase 1 (Foundation)
|
||||
**Blocks:** Phase 12 (Pages Migration)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Complete migration of contact form and reports system to Django (reports already partially migrated).
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 11.1: Contact Service (2 hours)
|
||||
|
||||
```typescript
|
||||
class ContactService extends BaseService {
|
||||
// POST /contact/
|
||||
async submitContact(data: ContactFormData): Promise<ContactSubmission>
|
||||
|
||||
// GET /contact/ (moderators only)
|
||||
async getContactSubmissions(filters?: ContactFilters): Promise<ContactSubmission[]>
|
||||
|
||||
// PATCH /contact/{id}/status/ (moderators only)
|
||||
async updateContactStatus(id: string, status: string, notes?: string): Promise<ContactSubmission>
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Create `src/services/contact/contactService.ts`
|
||||
- [ ] Create types and mappers
|
||||
|
||||
### Task 11.2: Update Contact Form (1 hour)
|
||||
|
||||
**File:** `src/components/contact/ContactForm.tsx`
|
||||
|
||||
- [ ] Replace `supabase.from('contact_submissions')` with `contactService`
|
||||
- [ ] Update form submission
|
||||
- [ ] Test contact form
|
||||
- [ ] Verify confirmation emails
|
||||
|
||||
### Task 11.3: Verify Reports Service (1 hour)
|
||||
|
||||
**Note:** Reports service was already created in earlier work, but verify it's complete.
|
||||
|
||||
- [ ] Verify `src/services/reports/reportsService.ts` is complete
|
||||
- [ ] Verify `src/components/moderation/ReportButton.tsx` uses service
|
||||
- [ ] Verify `src/components/moderation/ReportsQueue.tsx` uses service
|
||||
- [ ] Test report submission
|
||||
- [ ] Test report management
|
||||
|
||||
### Task 11.4: Final Contact/Reports Testing (1-2 hours)
|
||||
|
||||
- [ ] Test contact form submission
|
||||
- [ ] Test contact admin management
|
||||
- [ ] Test report submission
|
||||
- [ ] Test report moderation
|
||||
- [ ] Verify emails are sent
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
- [ ] Contact form works
|
||||
- [ ] Contact emails sent
|
||||
- [ ] Reports fully functional
|
||||
- [ ] Admin can manage contacts/reports
|
||||
- [ ] Zero `supabase.from('contact_submissions')` calls
|
||||
- [ ] Zero `supabase.from('reports')` calls
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
**Phase 12:** Pages Migration - THE BIG ONE
|
||||
724
migration/PHASE_12_PAGES_MIGRATION.md
Normal file
724
migration/PHASE_12_PAGES_MIGRATION.md
Normal file
@@ -0,0 +1,724 @@
|
||||
# PHASE 12: Next.js 15 App Router Pages Migration
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 45-55 hours
|
||||
**Priority:** CRITICAL
|
||||
**Depends On:** All previous phases (1-11)
|
||||
**Blocks:** Phase 13 (Next.js Optimization)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Convert ALL React SPA pages to Next.js 15 App Router pages while replacing Supabase calls with Django services. This is a DUAL migration:
|
||||
1. **React → Next.js App Router**: Convert pages and routing
|
||||
2. **Supabase → Django**: Replace all data fetching
|
||||
|
||||
**Critical Requirements:**
|
||||
- Preserve ALL existing URLs
|
||||
- Maintain exact same UI/UX
|
||||
- Use Server Components by default
|
||||
- Mark Client Components with 'use client'
|
||||
- Sacred Pipeline remains intact
|
||||
|
||||
---
|
||||
|
||||
## 📋 Next.js App Router Structure
|
||||
|
||||
### New Directory Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── layout.tsx # Root layout
|
||||
├── page.tsx # Homepage
|
||||
├── loading.tsx # Global loading
|
||||
├── error.tsx # Global error
|
||||
├── not-found.tsx # 404 page
|
||||
│
|
||||
├── parks/
|
||||
│ ├── page.tsx # /parks (listing)
|
||||
│ ├── loading.tsx # Loading state
|
||||
│ ├── [parkSlug]/
|
||||
│ │ ├── page.tsx # /parks/[parkSlug]
|
||||
│ │ ├── loading.tsx
|
||||
│ │ └── rides/
|
||||
│ │ └── page.tsx # /parks/[parkSlug]/rides
|
||||
│ └── owners/
|
||||
│ └── [ownerSlug]/
|
||||
│ └── page.tsx # /owners/[ownerSlug]/parks
|
||||
│
|
||||
├── rides/
|
||||
│ ├── page.tsx # /rides (listing)
|
||||
│ ├── [rideSlug]/
|
||||
│ │ ├── page.tsx # /rides/[rideSlug]
|
||||
│ │ └── reviews/
|
||||
│ │ └── page.tsx # /rides/[rideSlug]/reviews
|
||||
│ └── models/
|
||||
│ └── [modelSlug]/
|
||||
│ ├── page.tsx # /ride-models/[modelSlug]
|
||||
│ └── rides/
|
||||
│ └── page.tsx # /ride-models/[modelSlug]/rides
|
||||
│
|
||||
├── manufacturers/
|
||||
│ ├── page.tsx # /manufacturers (listing)
|
||||
│ └── [manufacturerSlug]/
|
||||
│ ├── page.tsx # /manufacturers/[manufacturerSlug]
|
||||
│ └── rides/
|
||||
│ └── page.tsx # /manufacturers/[manufacturerSlug]/rides
|
||||
│
|
||||
├── owners/
|
||||
│ ├── page.tsx # /owners (listing)
|
||||
│ └── [ownerSlug]/
|
||||
│ └── page.tsx # /owners/[ownerSlug]
|
||||
│
|
||||
├── designers/
|
||||
│ ├── page.tsx # /designers (listing)
|
||||
│ └── [designerSlug]/
|
||||
│ └── page.tsx # /designers/[designerSlug]
|
||||
│
|
||||
├── auth/
|
||||
│ ├── login/
|
||||
│ │ └── page.tsx
|
||||
│ ├── register/
|
||||
│ │ └── page.tsx
|
||||
│ └── callback/
|
||||
│ └── page.tsx
|
||||
│
|
||||
├── profile/
|
||||
│ ├── page.tsx # /profile
|
||||
│ ├── settings/
|
||||
│ │ └── page.tsx
|
||||
│ └── lists/
|
||||
│ └── page.tsx
|
||||
│
|
||||
├── admin/
|
||||
│ ├── page.tsx # /admin
|
||||
│ └── moderation/
|
||||
│ └── page.tsx # /admin/moderation
|
||||
│
|
||||
├── search/
|
||||
│ └── page.tsx # /search
|
||||
│
|
||||
└── contact/
|
||||
└── page.tsx # /contact
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 12.1: Root Layout & Homepage (6 hours)
|
||||
|
||||
#### Create Root Layout
|
||||
|
||||
`app/layout.tsx` (Server Component):
|
||||
|
||||
```typescript
|
||||
import { Inter } from 'next/font/google';
|
||||
import './globals.css';
|
||||
import { AuthProvider } from '@/components/providers/AuthProvider';
|
||||
import { Navigation } from '@/components/layout/Navigation';
|
||||
import { Footer } from '@/components/layout/Footer';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata = {
|
||||
title: 'ThrillWiki - Theme Park & Ride Database',
|
||||
description: 'Comprehensive database of theme parks and rides',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<AuthProvider>
|
||||
<Navigation />
|
||||
<main className="min-h-screen">{children}</main>
|
||||
<Footer />
|
||||
</AuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Convert Homepage
|
||||
|
||||
`app/page.tsx` (Server Component):
|
||||
|
||||
```typescript
|
||||
import { env } from '@/lib/env';
|
||||
import { FeaturedParks } from '@/components/home/FeaturedParks';
|
||||
import { RecentReviews } from '@/components/home/RecentReviews';
|
||||
import { Stats } from '@/components/home/Stats';
|
||||
|
||||
export default async function HomePage() {
|
||||
// Fetch data server-side
|
||||
const [parks, stats] = await Promise.all([
|
||||
fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/?featured=true`).then(r => r.json()),
|
||||
fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/stats/`).then(r => r.json()),
|
||||
]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Hero />
|
||||
<Stats data={stats} />
|
||||
<FeaturedParks parks={parks} />
|
||||
<RecentReviews />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `app/layout.tsx` with root layout
|
||||
- [ ] Create `app/page.tsx` for homepage
|
||||
- [ ] Create `app/loading.tsx` for loading state
|
||||
- [ ] Create `app/error.tsx` for error boundary
|
||||
- [ ] Create `app/not-found.tsx` for 404
|
||||
- [ ] Move CSS to `app/globals.css`
|
||||
- [ ] Test homepage loads
|
||||
- [ ] Verify navigation works
|
||||
- [ ] Check loading states
|
||||
- [ ] Test error boundaries
|
||||
|
||||
---
|
||||
|
||||
### Task 12.2: Park Pages (8 hours)
|
||||
|
||||
#### Park Listing Page
|
||||
|
||||
`app/parks/page.tsx` (Server Component):
|
||||
|
||||
```typescript
|
||||
import { env } from '@/lib/env';
|
||||
import { ParksList } from '@/components/parks/ParksList';
|
||||
import { ParkFilters } from '@/components/parks/ParkFilters';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Theme Parks - ThrillWiki',
|
||||
description: 'Browse theme parks from around the world',
|
||||
};
|
||||
|
||||
export default async function ParksPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { [key: string]: string | undefined };
|
||||
}) {
|
||||
const params = new URLSearchParams(searchParams as any);
|
||||
const parks = await fetch(
|
||||
`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/?${params}`,
|
||||
{ next: { revalidate: 300 } } // Cache for 5 minutes
|
||||
).then(r => r.json());
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Theme Parks</h1>
|
||||
<ParkFilters /> {/* Client Component */}
|
||||
<ParksList parks={parks} /> {/* Can be Server Component */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Park Detail Page
|
||||
|
||||
`app/parks/[parkSlug]/page.tsx` (Server Component):
|
||||
|
||||
```typescript
|
||||
import { env } from '@/lib/env';
|
||||
import { ParkDetail } from '@/components/parks/ParkDetail';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export async function generateStaticParams() {
|
||||
// Pre-render top 100 parks
|
||||
const parks = await fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/?page_size=100`)
|
||||
.then(r => r.json());
|
||||
return parks.results.map((park: any) => ({
|
||||
parkSlug: park.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: { parkSlug: string } }) {
|
||||
const park = await fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/${params.parkSlug}/`)
|
||||
.then(r => r.json());
|
||||
|
||||
return {
|
||||
title: `${park.name} - ThrillWiki`,
|
||||
description: park.description,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ParkDetailPage({
|
||||
params
|
||||
}: {
|
||||
params: { parkSlug: string }
|
||||
}) {
|
||||
const park = await fetch(
|
||||
`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/${params.parkSlug}/`,
|
||||
{ next: { revalidate: 3600 } } // Cache for 1 hour
|
||||
).then(r => r.json())
|
||||
.catch(() => notFound());
|
||||
|
||||
return <ParkDetail park={park} />;
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `app/parks/page.tsx`
|
||||
- [ ] Create `app/parks/[parkSlug]/page.tsx`
|
||||
- [ ] Create `app/parks/[parkSlug]/rides/page.tsx`
|
||||
- [ ] Create `app/owners/[ownerSlug]/parks/page.tsx`
|
||||
- [ ] Create loading.tsx for each route
|
||||
- [ ] Implement generateStaticParams for ISR
|
||||
- [ ] Implement generateMetadata for SEO
|
||||
- [ ] Test all park URLs work
|
||||
- [ ] Verify filtering works
|
||||
- [ ] Test pagination
|
||||
|
||||
---
|
||||
|
||||
### Task 12.3: Ride Pages (8 hours)
|
||||
|
||||
#### Ride Listing Page
|
||||
|
||||
`app/rides/page.tsx` (Server Component with caching)
|
||||
|
||||
#### Ride Detail Page
|
||||
|
||||
`app/rides/[rideSlug]/page.tsx` (Server Component)
|
||||
|
||||
#### Ride Model Pages
|
||||
|
||||
`app/ride-models/[modelSlug]/page.tsx` (Server Component)
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `app/rides/page.tsx`
|
||||
- [ ] Create `app/rides/[rideSlug]/page.tsx`
|
||||
- [ ] Create `app/rides/[rideSlug]/reviews/page.tsx`
|
||||
- [ ] Create `app/ride-models/[modelSlug]/page.tsx`
|
||||
- [ ] Create `app/ride-models/[modelSlug]/rides/page.tsx`
|
||||
- [ ] Create loading states
|
||||
- [ ] Implement metadata
|
||||
- [ ] Test all ride URLs
|
||||
- [ ] Verify reviews work
|
||||
|
||||
---
|
||||
|
||||
### Task 12.4: Company Pages (8 hours)
|
||||
|
||||
Convert manufacturer, owner, and designer pages to App Router.
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `app/manufacturers/page.tsx`
|
||||
- [ ] Create `app/manufacturers/[manufacturerSlug]/page.tsx`
|
||||
- [ ] Create `app/manufacturers/[manufacturerSlug]/rides/page.tsx`
|
||||
- [ ] Create `app/owners/page.tsx`
|
||||
- [ ] Create `app/owners/[ownerSlug]/page.tsx`
|
||||
- [ ] Create `app/designers/page.tsx`
|
||||
- [ ] Create `app/designers/[designerSlug]/page.tsx`
|
||||
- [ ] Test all company URLs
|
||||
- [ ] Verify filtering works
|
||||
|
||||
---
|
||||
|
||||
### Task 12.5: User Pages (6 hours)
|
||||
|
||||
These pages need authentication and user-specific data.
|
||||
|
||||
#### Profile Page
|
||||
|
||||
`app/profile/page.tsx` (Server Component with auth check):
|
||||
|
||||
```typescript
|
||||
import { redirect } from 'next/navigation';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { ProfileView } from '@/components/profile/ProfileView';
|
||||
|
||||
export default async function ProfilePage() {
|
||||
const session = await getServerSession();
|
||||
|
||||
if (!session) {
|
||||
redirect('/auth/login');
|
||||
}
|
||||
|
||||
const user = await fetch(
|
||||
`${env.NEXT_PUBLIC_DJANGO_API_URL}/users/me/`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
cache: 'no-store' // Don't cache user-specific data
|
||||
}
|
||||
).then(r => r.json());
|
||||
|
||||
return <ProfileView user={user} />;
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `app/profile/page.tsx`
|
||||
- [ ] Create `app/profile/settings/page.tsx`
|
||||
- [ ] Create `app/profile/lists/page.tsx`
|
||||
- [ ] Implement authentication checks
|
||||
- [ ] Disable caching for user data
|
||||
- [ ] Test profile loads
|
||||
- [ ] Test settings work
|
||||
- [ ] Test list management
|
||||
|
||||
---
|
||||
|
||||
### Task 12.6: Admin & Moderation Pages (6 hours)
|
||||
|
||||
Admin pages require role checks and real-time updates.
|
||||
|
||||
#### Moderation Queue
|
||||
|
||||
`app/admin/moderation/page.tsx` (Server Component):
|
||||
|
||||
```typescript
|
||||
import { redirect } from 'next/navigation';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { ModerationQueue } from '@/components/moderation/ModerationQueue';
|
||||
|
||||
export default async function ModerationPage() {
|
||||
const session = await getServerSession();
|
||||
|
||||
if (!session || session.user.role !== 'moderator') {
|
||||
redirect('/');
|
||||
}
|
||||
|
||||
// Fetch submissions server-side
|
||||
const submissions = await fetch(
|
||||
`${env.NEXT_PUBLIC_DJANGO_API_URL}/moderation/queue/`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
cache: 'no-store'
|
||||
}
|
||||
).then(r => r.json());
|
||||
|
||||
return <ModerationQueue initialSubmissions={submissions} />;
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `app/admin/page.tsx`
|
||||
- [ ] Create `app/admin/moderation/page.tsx`
|
||||
- [ ] Implement role-based access
|
||||
- [ ] Add server-side permission checks
|
||||
- [ ] Test moderator access
|
||||
- [ ] Test admin dashboard
|
||||
- [ ] Verify Sacred Pipeline
|
||||
|
||||
---
|
||||
|
||||
### Task 12.7: Authentication Pages (4 hours)
|
||||
|
||||
#### Login Page
|
||||
|
||||
`app/auth/login/page.tsx` (Client Component - needs form state):
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
import { LoginForm } from '@/components/auth/LoginForm';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
export default function LoginPage() {
|
||||
const { data: session } = useSession();
|
||||
|
||||
if (session) {
|
||||
redirect('/profile');
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
<LoginForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `app/auth/login/page.tsx`
|
||||
- [ ] Create `app/auth/register/page.tsx`
|
||||
- [ ] Create `app/auth/callback/page.tsx`
|
||||
- [ ] Create `app/auth/reset-password/page.tsx`
|
||||
- [ ] Test email/password login
|
||||
- [ ] Test OAuth login (Google, GitHub)
|
||||
- [ ] Test registration
|
||||
- [ ] Test password reset
|
||||
|
||||
---
|
||||
|
||||
### Task 12.8: Search & Contact Pages (3 hours)
|
||||
|
||||
#### Search Page
|
||||
|
||||
`app/search/page.tsx` (Client Component - needs interactive search):
|
||||
|
||||
```typescript
|
||||
'use client';
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { SearchResults } from '@/components/search/SearchResults';
|
||||
import { SearchFilters } from '@/components/search/SearchFilters';
|
||||
|
||||
export default function SearchPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const query = searchParams.get('q');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Search Results</h1>
|
||||
<SearchFilters />
|
||||
<SearchResults query={query} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Contact Page
|
||||
|
||||
`app/contact/page.tsx` (Server Component):
|
||||
|
||||
```typescript
|
||||
import { ContactForm } from '@/components/contact/ContactForm';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Contact Us - ThrillWiki',
|
||||
};
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Contact Us</h1>
|
||||
<ContactForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Create `app/search/page.tsx`
|
||||
- [ ] Create `app/contact/page.tsx`
|
||||
- [ ] Test search functionality
|
||||
- [ ] Test contact form submission
|
||||
- [ ] Verify form validation
|
||||
|
||||
---
|
||||
|
||||
### Task 12.9: Component Migration (6 hours)
|
||||
|
||||
Convert React components to work with Next.js.
|
||||
|
||||
#### Server Components (Default)
|
||||
Components that only display data and don't need interactivity:
|
||||
- Lists (ParksL, RidesList)
|
||||
- Detail views (ParkDetail, RideDetail)
|
||||
- Static content
|
||||
|
||||
#### Client Components ('use client')
|
||||
Components that need interactivity:
|
||||
- Forms (LoginForm, SubmissionForm)
|
||||
- Interactive filters
|
||||
- Modals and dialogs
|
||||
- Components using useState, useEffect, etc.
|
||||
|
||||
#### Checklist
|
||||
- [ ] Identify all components using Supabase
|
||||
- [ ] Determine Server vs Client Component
|
||||
- [ ] Add 'use client' where needed
|
||||
- [ ] Replace Supabase with service calls
|
||||
- [ ] Test all components render
|
||||
- [ ] Verify interactions work
|
||||
- [ ] Check no hydration errors
|
||||
|
||||
---
|
||||
|
||||
### Task 12.10: Routing & Navigation (4 hours)
|
||||
|
||||
Update all navigation to use Next.js routing.
|
||||
|
||||
#### Replace React Router
|
||||
|
||||
```typescript
|
||||
// OLD (React Router)
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
||||
// NEW (Next.js)
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
```
|
||||
|
||||
#### Update All Links
|
||||
|
||||
```typescript
|
||||
// OLD
|
||||
<Link to="/parks">Parks</Link>
|
||||
|
||||
// NEW
|
||||
<Link href="/parks">Parks</Link>
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Replace all React Router imports
|
||||
- [ ] Update all <Link> components
|
||||
- [ ] Update all navigate() calls to router.push()
|
||||
- [ ] Update all useParams to use params prop
|
||||
- [ ] Update all useSearchParams
|
||||
- [ ] Test navigation works
|
||||
- [ ] Test browser back button
|
||||
- [ ] Test deep linking
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
### Zero React Router Usage
|
||||
- [ ] No `react-router-dom` imports remain
|
||||
- [ ] All navigation uses Next.js Link/router
|
||||
- [ ] No old React pages in `src/pages/` (move to `app/`)
|
||||
|
||||
### Zero Supabase Usage
|
||||
- [ ] `grep -r "supabase\." app/ components/ lib/ --include="*.ts" --include="*.tsx"` returns 0
|
||||
- [ ] `grep -r "from '@/lib/supabaseClient'"` returns 0
|
||||
- [ ] `grep -r "from '@supabase/supabase-js'"` returns 0
|
||||
|
||||
### All URLs Preserved
|
||||
- [ ] `/parks` works
|
||||
- [ ] `/parks/[parkSlug]` works
|
||||
- [ ] `/parks/[parkSlug]/rides` works
|
||||
- [ ] `/owners/[ownerSlug]/parks` works
|
||||
- [ ] `/rides` works
|
||||
- [ ] `/rides/[rideSlug]` works
|
||||
- [ ] `/ride-models/[modelSlug]` works
|
||||
- [ ] `/manufacturers/[manufacturerSlug]` works
|
||||
- [ ] `/manufacturers/[manufacturerSlug]/rides` works
|
||||
- [ ] All other URLs work
|
||||
|
||||
### All Pages Load
|
||||
- [ ] Homepage loads (SSR)
|
||||
- [ ] All park pages load
|
||||
- [ ] All ride pages load
|
||||
- [ ] All company pages load
|
||||
- [ ] Profile page loads (auth check)
|
||||
- [ ] Settings page loads (auth check)
|
||||
- [ ] Admin dashboard loads (role check)
|
||||
- [ ] Moderation queue loads (role check)
|
||||
- [ ] Search page loads
|
||||
- [ ] Contact page loads
|
||||
- [ ] Auth pages load
|
||||
|
||||
### All Features Work
|
||||
- [ ] Can browse entities
|
||||
- [ ] Can view entity details
|
||||
- [ ] Can filter/sort
|
||||
- [ ] Can submit content (creates submission)
|
||||
- [ ] Can moderate content
|
||||
- [ ] Can write reviews
|
||||
- [ ] Can add ride credits
|
||||
- [ ] Can manage top lists
|
||||
- [ ] Can upload photos
|
||||
- [ ] Can search
|
||||
- [ ] Authentication works
|
||||
- [ ] Server-side rendering works
|
||||
- [ ] Client-side navigation works
|
||||
|
||||
### Next.js Specific
|
||||
- [ ] Server Components render on server
|
||||
- [ ] Client Components work in browser
|
||||
- [ ] No hydration errors
|
||||
- [ ] Loading states display
|
||||
- [ ] Error boundaries catch errors
|
||||
- [ ] Metadata API generates correct tags
|
||||
- [ ] ISR/SSR configured correctly
|
||||
- [ ] Build succeeds without errors
|
||||
|
||||
### Sacred Pipeline Intact
|
||||
- [ ] All entity changes go through submissions
|
||||
- [ ] Moderation queue receives submissions
|
||||
- [ ] Approval creates entities/updates
|
||||
- [ ] Rejection saves reason
|
||||
- [ ] No pipeline bypasses
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Strategy
|
||||
|
||||
### 1. Start with Entity Pages (Most Critical)
|
||||
Focus on parks, rides, companies first since they're core functionality.
|
||||
|
||||
### 2. Then User Pages
|
||||
Profile and settings are user-facing and important.
|
||||
|
||||
### 3. Then Admin Pages
|
||||
Moderation queue and admin dashboard.
|
||||
|
||||
### 4. Then Misc Pages
|
||||
Homepage, search, contact, etc.
|
||||
|
||||
### 5. Component Sweep
|
||||
Find any remaining components with Supabase calls.
|
||||
|
||||
### 6. Final Search
|
||||
Run grep commands to find ANY remaining Supabase usage.
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Critical Rules
|
||||
|
||||
### DO NOT
|
||||
- ❌ Skip any page
|
||||
- ❌ Leave any `supabase.` calls
|
||||
- ❌ Assume a page works without testing it
|
||||
- ❌ Move to Phase 13 until grep returns 0 results
|
||||
|
||||
### MUST DO
|
||||
- ✅ Test EVERY page you touch
|
||||
- ✅ Verify EVERY interaction works
|
||||
- ✅ Check console for errors
|
||||
- ✅ Verify Sacred Pipeline intact
|
||||
- ✅ Document any issues found
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
Refer back to previous phases for service usage examples:
|
||||
- Phase 1: Foundation (BaseService pattern)
|
||||
- Phase 4: Entity Services (parks, rides, companies)
|
||||
- Phase 5: Reviews & Social
|
||||
- Phase 6: Moderation & Admin
|
||||
- Phase 7: Media & Photos
|
||||
- Phase 8: Search
|
||||
- Phase 9: Timeline & History
|
||||
- Phase 10: Users & Profiles
|
||||
- Phase 11: Contact & Reports
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
Once this phase is complete, proceed to [Phase 13: Next.js Optimization](./PHASE_13_NEXTJS_OPTIMIZATION.md)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Next.js 15 Migration Guide](./NEXTJS_15_MIGRATION_GUIDE.md)
|
||||
- [Environment Variables Guide](./ENVIRONMENT_VARIABLES.md)
|
||||
- [Phase 1: Foundation](./PHASE_01_FOUNDATION.md)
|
||||
- [Phase 13: Next.js Optimization](./PHASE_13_NEXTJS_OPTIMIZATION.md)
|
||||
- Next.js App Router: https://nextjs.org/docs/app
|
||||
- Server Components: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 2.0
|
||||
**Last Updated:** November 9, 2025
|
||||
**Changes:** Converted to Next.js 15 App Router migration
|
||||
547
migration/PHASE_13_NEXTJS_OPTIMIZATION.md
Normal file
547
migration/PHASE_13_NEXTJS_OPTIMIZATION.md
Normal file
@@ -0,0 +1,547 @@
|
||||
# PHASE 13: Next.js 15 Optimization
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 15-20 hours
|
||||
**Priority:** HIGH
|
||||
**Depends On:** Phase 12 (Next.js Pages Migration)
|
||||
**Blocks:** Phase 14 (Cleanup & Testing)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Goal
|
||||
|
||||
Optimize the Next.js 15 application for production with Turbopack, implement advanced features, and ensure peak performance.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
- [ ] Phase 12 complete (all pages migrated to Next.js App Router)
|
||||
- [ ] All services converted from Supabase to Django API
|
||||
- [ ] Sacred Pipeline working in Next.js
|
||||
- [ ] Bun is configured as package manager
|
||||
- [ ] Environment variables properly configured
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.1: Turbopack Configuration (3 hours)
|
||||
|
||||
### Configure next.config.js for Turbopack
|
||||
|
||||
Create/update `next.config.js`:
|
||||
|
||||
```javascript
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Enable Turbopack for development
|
||||
experimental: {
|
||||
turbo: {
|
||||
rules: {
|
||||
'*.svg': {
|
||||
loaders: ['@svgr/webpack'],
|
||||
as: '*.js',
|
||||
},
|
||||
},
|
||||
resolveAlias: {
|
||||
'@': './src',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Image optimization
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'imagedelivery.net', // CloudFlare Images
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: process.env.NEXT_PUBLIC_DJANGO_API_URL?.replace('https://', '') || 'api.thrillwiki.com',
|
||||
},
|
||||
],
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
},
|
||||
|
||||
// Environment variables
|
||||
env: {
|
||||
NEXT_PUBLIC_DJANGO_API_URL: process.env.NEXT_PUBLIC_DJANGO_API_URL,
|
||||
},
|
||||
|
||||
// Production optimizations
|
||||
compress: true,
|
||||
poweredByHeader: false,
|
||||
reactStrictMode: true,
|
||||
|
||||
// Output configuration
|
||||
output: 'standalone', // For Docker deployment
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
```
|
||||
|
||||
### Checklist
|
||||
- [ ] `next.config.js` created/updated
|
||||
- [ ] Turbopack rules configured
|
||||
- [ ] Image optimization configured for CloudFlare
|
||||
- [ ] Environment variables properly referenced
|
||||
- [ ] Production flags enabled
|
||||
- [ ] Development server uses Turbopack
|
||||
- [ ] Hot reload works correctly
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.2: Server vs Client Component Optimization (4 hours)
|
||||
|
||||
### Identify Component Types
|
||||
|
||||
**Server Components (Default):**
|
||||
- Data fetching pages
|
||||
- Static content
|
||||
- SEO-critical pages
|
||||
- No user interaction
|
||||
|
||||
**Client Components (Need 'use client'):**
|
||||
- Forms with state
|
||||
- Interactive UI elements
|
||||
- Hooks (useState, useEffect, etc.)
|
||||
- Event handlers
|
||||
- Context providers
|
||||
|
||||
### Optimization Checklist
|
||||
|
||||
#### Server Components
|
||||
- [ ] All data-fetching pages are Server Components
|
||||
- [ ] Park listing page is Server Component
|
||||
- [ ] Ride listing page is Server Component
|
||||
- [ ] Detail pages use Server Components for data
|
||||
- [ ] SEO metadata generated server-side
|
||||
|
||||
#### Client Components
|
||||
- [ ] Form components marked with 'use client'
|
||||
- [ ] Interactive UI (modals, dropdowns) marked
|
||||
- [ ] Authentication components marked
|
||||
- [ ] Submission forms marked
|
||||
- [ ] No unnecessary 'use client' directives
|
||||
|
||||
#### Component Structure
|
||||
```typescript
|
||||
// Good: Server Component with Client Components inside
|
||||
// app/parks/page.tsx
|
||||
export default async function ParksPage() {
|
||||
const parks = await fetch(API_URL).then(r => r.json());
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Parks</h1>
|
||||
<ParkFilters /> {/* Client Component */}
|
||||
<ParksList parks={parks} /> {/* Can be Server Component */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Client Component for interactivity
|
||||
// components/ParkFilters.tsx
|
||||
'use client';
|
||||
export function ParkFilters() {
|
||||
const [filters, setFilters] = useState({});
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.3: Data Fetching Optimization (3 hours)
|
||||
|
||||
### Implement Caching Strategies
|
||||
|
||||
```typescript
|
||||
// app/parks/[parkSlug]/page.tsx
|
||||
export async function generateStaticParams() {
|
||||
// Pre-render top 100 parks at build time
|
||||
const parks = await fetch(`${API_URL}/parks/?page_size=100`);
|
||||
return parks.results.map((park) => ({
|
||||
parkSlug: park.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function ParkDetailPage({
|
||||
params
|
||||
}: {
|
||||
params: { parkSlug: string }
|
||||
}) {
|
||||
// Cache for 1 hour, revalidate in background
|
||||
const park = await fetch(`${API_URL}/parks/${params.parkSlug}/`, {
|
||||
next: { revalidate: 3600 }
|
||||
}).then(r => r.json());
|
||||
|
||||
return <ParkDetail park={park} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Caching Strategy Checklist
|
||||
- [ ] Static pages use ISR (Incremental Static Regeneration)
|
||||
- [ ] Dynamic pages have appropriate revalidation times
|
||||
- [ ] Listing pages cache for 5-10 minutes
|
||||
- [ ] Detail pages cache for 1 hour
|
||||
- [ ] User-specific pages are not cached
|
||||
- [ ] Search results are not cached
|
||||
|
||||
### Parallel Data Fetching
|
||||
```typescript
|
||||
// Fetch multiple resources in parallel
|
||||
export default async function RideDetailPage({ params }) {
|
||||
const [ride, reviews, photos] = await Promise.all([
|
||||
fetch(`${API_URL}/rides/${params.rideSlug}/`),
|
||||
fetch(`${API_URL}/rides/${params.rideSlug}/reviews/`),
|
||||
fetch(`${API_URL}/rides/${params.rideSlug}/photos/`),
|
||||
]);
|
||||
|
||||
return <RideDetail ride={ride} reviews={reviews} photos={photos} />;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Parallel fetching implemented where possible
|
||||
- [ ] No waterfall requests
|
||||
- [ ] Loading states for slow requests
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.4: Loading States & Suspense (2 hours)
|
||||
|
||||
### Create Loading Components
|
||||
|
||||
```typescript
|
||||
// app/parks/loading.tsx
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="h-8 bg-gray-200 rounded animate-pulse" />
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<div key={i} className="h-48 bg-gray-200 rounded animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Implement Streaming
|
||||
|
||||
```typescript
|
||||
// app/parks/[parkSlug]/page.tsx
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export default async function ParkPage({ params }) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<ParkHeaderSkeleton />}>
|
||||
<ParkHeader slug={params.parkSlug} />
|
||||
</Suspense>
|
||||
|
||||
<Suspense fallback={<RidesListSkeleton />}>
|
||||
<RidesList parkSlug={params.parkSlug} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Checklist
|
||||
- [ ] Loading.tsx for each major route
|
||||
- [ ] Suspense boundaries for slow components
|
||||
- [ ] Skeleton screens match final UI
|
||||
- [ ] Streaming SSR enabled
|
||||
- [ ] No flash of loading state
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.5: Metadata API for SEO (2 hours)
|
||||
|
||||
### Implement Dynamic Metadata
|
||||
|
||||
```typescript
|
||||
// app/parks/[parkSlug]/page.tsx
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export async function generateMetadata({
|
||||
params
|
||||
}: {
|
||||
params: { parkSlug: string }
|
||||
}): Promise<Metadata> {
|
||||
const park = await fetch(`${API_URL}/parks/${params.parkSlug}/`)
|
||||
.then(r => r.json());
|
||||
|
||||
return {
|
||||
title: `${park.name} - ThrillWiki`,
|
||||
description: park.description || `Discover ${park.name}, a ${park.park_type} featuring exciting rides and attractions.`,
|
||||
openGraph: {
|
||||
title: park.name,
|
||||
description: park.description,
|
||||
images: [
|
||||
{
|
||||
url: park.banner_image_url || '/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: park.name,
|
||||
},
|
||||
],
|
||||
type: 'website',
|
||||
siteName: 'ThrillWiki',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: park.name,
|
||||
description: park.description,
|
||||
images: [park.banner_image_url || '/og-image.png'],
|
||||
},
|
||||
alternates: {
|
||||
canonical: `https://thrillwiki.com/parks/${park.slug}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SEO Checklist
|
||||
- [ ] All pages have unique titles
|
||||
- [ ] All pages have meta descriptions
|
||||
- [ ] OpenGraph tags for social sharing
|
||||
- [ ] Twitter Card tags
|
||||
- [ ] Canonical URLs
|
||||
- [ ] Structured data (JSON-LD)
|
||||
- [ ] Robots.txt configured
|
||||
- [ ] Sitemap.xml generated
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.6: Performance Optimization (3 hours)
|
||||
|
||||
### Bundle Analysis
|
||||
|
||||
```bash
|
||||
# Install bundle analyzer
|
||||
bun add @next/bundle-analyzer
|
||||
|
||||
# Update next.config.js
|
||||
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
});
|
||||
|
||||
module.exports = withBundleAnalyzer(nextConfig);
|
||||
|
||||
# Run analysis
|
||||
ANALYZE=true bun run build
|
||||
```
|
||||
|
||||
### Code Splitting
|
||||
|
||||
```typescript
|
||||
// Use dynamic imports for heavy components
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const HeavyChart = dynamic(() => import('./HeavyChart'), {
|
||||
loading: () => <p>Loading chart...</p>,
|
||||
ssr: false, // Don't render on server
|
||||
});
|
||||
```
|
||||
|
||||
### Performance Checklist
|
||||
- [ ] Bundle size < 500KB initial load
|
||||
- [ ] Code splitting for large dependencies
|
||||
- [ ] Images use next/image
|
||||
- [ ] Fonts optimized with next/font
|
||||
- [ ] CSS modules for component styles
|
||||
- [ ] No unused dependencies
|
||||
- [ ] Tree shaking working
|
||||
|
||||
### Core Web Vitals Targets
|
||||
- [ ] LCP (Largest Contentful Paint) < 2.5s
|
||||
- [ ] FID (First Input Delay) < 100ms
|
||||
- [ ] CLS (Cumulative Layout Shift) < 0.1
|
||||
- [ ] TTFB (Time to First Byte) < 600ms
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.7: Environment Variables & Security (1 hour)
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
Create `.env.local`:
|
||||
```bash
|
||||
# Django API (required for build)
|
||||
NEXT_PUBLIC_DJANGO_API_URL=https://api.thrillwiki.com
|
||||
|
||||
# CloudFlare (public)
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=xxx
|
||||
|
||||
# Server-only secrets (no NEXT_PUBLIC_ prefix)
|
||||
CLOUDFLARE_API_TOKEN=xxx
|
||||
DJANGO_SECRET_KEY=xxx
|
||||
```
|
||||
|
||||
### Security Checklist
|
||||
- [ ] No secrets in client-side code
|
||||
- [ ] Server-only vars don't have NEXT_PUBLIC_ prefix
|
||||
- [ ] .env.local in .gitignore
|
||||
- [ ] .env.example documented
|
||||
- [ ] Environment validation on startup
|
||||
- [ ] Sensitive data not logged
|
||||
|
||||
### Runtime Validation
|
||||
|
||||
```typescript
|
||||
// lib/env.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
const envSchema = z.object({
|
||||
NEXT_PUBLIC_DJANGO_API_URL: z.string().url(),
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID: z.string().min(1),
|
||||
});
|
||||
|
||||
export const env = envSchema.parse({
|
||||
NEXT_PUBLIC_DJANGO_API_URL: process.env.NEXT_PUBLIC_DJANGO_API_URL,
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID: process.env.NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.8: Error Boundaries & Monitoring (2 hours)
|
||||
|
||||
### Global Error Handling
|
||||
|
||||
```typescript
|
||||
// app/error.tsx
|
||||
'use client';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={reset}>Try again</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// app/global-error.tsx (catches errors in root layout)
|
||||
'use client';
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={reset}>Try again</button>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Not Found Pages
|
||||
|
||||
```typescript
|
||||
// app/not-found.tsx
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<h2>404 - Page Not Found</h2>
|
||||
<p>The page you're looking for doesn't exist.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Checklist
|
||||
- [ ] Error boundaries for each route segment
|
||||
- [ ] Global error handler
|
||||
- [ ] 404 page styled
|
||||
- [ ] Error logging implemented
|
||||
- [ ] User-friendly error messages
|
||||
- [ ] Errors don't expose sensitive data
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Completion Criteria
|
||||
|
||||
### Configuration
|
||||
- [ ] Turbopack fully configured
|
||||
- [ ] Image optimization working
|
||||
- [ ] Environment variables validated
|
||||
- [ ] next.config.js optimized
|
||||
|
||||
### Performance
|
||||
- [ ] Bundle size optimized
|
||||
- [ ] Code splitting implemented
|
||||
- [ ] Images optimized
|
||||
- [ ] Fonts optimized
|
||||
- [ ] Core Web Vitals meet targets
|
||||
|
||||
### SEO
|
||||
- [ ] All pages have metadata
|
||||
- [ ] OpenGraph tags present
|
||||
- [ ] Sitemap generated
|
||||
- [ ] Robots.txt configured
|
||||
|
||||
### User Experience
|
||||
- [ ] Loading states implemented
|
||||
- [ ] Error boundaries working
|
||||
- [ ] 404 pages styled
|
||||
- [ ] No jarring layout shifts
|
||||
|
||||
### Code Quality
|
||||
- [ ] Server/Client components optimized
|
||||
- [ ] No unnecessary 'use client' directives
|
||||
- [ ] Parallel data fetching where possible
|
||||
- [ ] Proper caching strategies
|
||||
|
||||
---
|
||||
|
||||
## 📊 Progress Tracking
|
||||
|
||||
**Started:** [Date]
|
||||
**Completed:** [Date]
|
||||
**Time Spent:** [Hours]
|
||||
|
||||
### Tasks Completed
|
||||
- [ ] Task 13.1: Turbopack Configuration
|
||||
- [ ] Task 13.2: Server vs Client Components
|
||||
- [ ] Task 13.3: Data Fetching Optimization
|
||||
- [ ] Task 13.4: Loading States & Suspense
|
||||
- [ ] Task 13.5: Metadata API for SEO
|
||||
- [ ] Task 13.6: Performance Optimization
|
||||
- [ ] Task 13.7: Environment Variables
|
||||
- [ ] Task 13.8: Error Boundaries
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
Once this phase is complete, proceed to [Phase 14: Cleanup & Testing](./PHASE_14_CLEANUP_TESTING.md)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Next.js 15 Migration Guide](./NEXTJS_15_MIGRATION_GUIDE.md)
|
||||
- [Environment Variables Guide](./ENVIRONMENT_VARIABLES.md)
|
||||
- [Bun Setup Guide](./BUN_GUIDE.md)
|
||||
- Next.js Documentation: https://nextjs.org/docs
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** November 9, 2025
|
||||
409
migration/PHASE_14_CLEANUP_TESTING.md
Normal file
409
migration/PHASE_14_CLEANUP_TESTING.md
Normal file
@@ -0,0 +1,409 @@
|
||||
# PHASE 14: Cleanup & Testing
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 15-18 hours
|
||||
**Priority:** CRITICAL
|
||||
**Depends On:** Phase 13 (Next.js Optimization)
|
||||
**Blocks:** Production Deployment
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
Complete removal of all Supabase dependencies, verify 100% functionality in Next.js 15, and ensure production readiness. This is the FINAL phase.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Tasks
|
||||
|
||||
### Task 14.1: Remove Supabase Files & Dependencies (3 hours)
|
||||
|
||||
#### Step 1: Delete Supabase Integration Directory
|
||||
```bash
|
||||
rm -rf src/integrations/supabase/
|
||||
```
|
||||
|
||||
#### Step 2: Delete Supabase Client Files
|
||||
```bash
|
||||
rm src/lib/supabaseClient.ts
|
||||
rm src/lib/supabaseHelpers.ts
|
||||
```
|
||||
|
||||
#### Step 3: Remove Package Dependency
|
||||
```bash
|
||||
bun remove @supabase/supabase-js
|
||||
bun install # Update lock file
|
||||
```
|
||||
|
||||
#### Step 4: Clean Environment Variables
|
||||
Edit `.env.local` and `.env.example`:
|
||||
- [ ] Remove `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- [ ] Remove `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
- [ ] Verify `NEXT_PUBLIC_DJANGO_API_URL` is set correctly
|
||||
- [ ] Verify all required Next.js env vars present
|
||||
|
||||
#### Checklist
|
||||
- [ ] `src/integrations/supabase/` deleted
|
||||
- [ ] `src/lib/supabaseClient.ts` deleted
|
||||
- [ ] `src/lib/supabaseHelpers.ts` deleted
|
||||
- [ ] `@supabase/supabase-js` removed from `package.json`
|
||||
- [ ] `package-lock.json` updated
|
||||
- [ ] Supabase env vars removed
|
||||
- [ ] Django API URL configured
|
||||
|
||||
---
|
||||
|
||||
### Task 14.2: Final Verification (2 hours)
|
||||
|
||||
Run these commands - ALL MUST RETURN ZERO RESULTS:
|
||||
|
||||
```bash
|
||||
# 1. Check for Supabase imports
|
||||
grep -r "from '@supabase/supabase-js'" src/ --include="*.ts" --include="*.tsx"
|
||||
# Expected: No results
|
||||
|
||||
# 2. Check for Supabase client imports
|
||||
grep -r "from '@/lib/supabaseClient'" src/ --include="*.ts" --include="*.tsx"
|
||||
# Expected: No results
|
||||
|
||||
# 3. Check for any supabase usage (excluding comments)
|
||||
grep -r "supabase\." src/ --include="*.ts" --include="*.tsx" | grep -v "//"
|
||||
# Expected: No results (or only in comments)
|
||||
|
||||
# 4. Check package.json
|
||||
cat package.json | grep supabase
|
||||
# Expected: No results
|
||||
|
||||
# 5. Check for Supabase types
|
||||
grep -r "import.*Database.*from.*supabase" src/
|
||||
# Expected: No results
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Zero Supabase imports found
|
||||
- [ ] Zero Supabase usage found
|
||||
- [ ] Zero Supabase in package.json
|
||||
- [ ] Zero Supabase types imported
|
||||
|
||||
---
|
||||
|
||||
### Task 14.3: TypeScript Compilation (1 hour)
|
||||
|
||||
```bash
|
||||
# Run TypeScript compiler with Next.js
|
||||
bun run type-check
|
||||
|
||||
# Or using Next.js lint
|
||||
bun run lint
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] TypeScript compiles without errors
|
||||
- [ ] All imports resolve correctly
|
||||
- [ ] All types are defined
|
||||
- [ ] No `any` types added (follow TYPESCRIPT_ANY_POLICY.md)
|
||||
|
||||
---
|
||||
|
||||
### Task 14.4: Next.js Build Test (2 hours)
|
||||
|
||||
```bash
|
||||
# Production build with Turbopack
|
||||
bun run build
|
||||
|
||||
# Verify build output
|
||||
ls -la .next/
|
||||
```
|
||||
|
||||
#### Checklist
|
||||
- [ ] Build completes successfully
|
||||
- [ ] No build errors
|
||||
- [ ] No build warnings (or acceptable warnings only)
|
||||
- [ ] `.next/` directory generated correctly
|
||||
- [ ] Turbopack optimization applied
|
||||
- [ ] Server components compiled
|
||||
- [ ] Client components bundled separately
|
||||
- [ ] Static pages generated (if using ISR/SSG)
|
||||
- [ ] Build size is reasonable
|
||||
|
||||
#### Next.js Specific Checks
|
||||
- [ ] Server components work in build
|
||||
- [ ] Client components work in build
|
||||
- [ ] API routes function
|
||||
- [ ] Metadata API generates correct tags
|
||||
- [ ] Image optimization working
|
||||
- [ ] Font optimization working
|
||||
|
||||
---
|
||||
|
||||
### Task 14.5: Integration Testing (5 hours)
|
||||
|
||||
**Run tests in both development and production modes:**
|
||||
|
||||
```bash
|
||||
# Development mode
|
||||
bun run dev
|
||||
|
||||
# Production mode
|
||||
bun run build && bun run start
|
||||
```
|
||||
|
||||
Test EVERY major flow:
|
||||
|
||||
#### Authentication (30 min)
|
||||
- [ ] Register new user
|
||||
- [ ] Login with email/password
|
||||
- [ ] Login with Google OAuth
|
||||
- [ ] Login with GitHub OAuth
|
||||
- [ ] Enable MFA/TOTP
|
||||
- [ ] Disable MFA/TOTP
|
||||
- [ ] Logout
|
||||
- [ ] Password reset
|
||||
|
||||
#### Entity CRUD via Sacred Pipeline (1 hour)
|
||||
- [ ] Submit new park → appears in moderation queue
|
||||
- [ ] Submit new ride → appears in moderation queue
|
||||
- [ ] Submit new company → appears in moderation queue
|
||||
- [ ] Submit new ride model → appears in moderation queue
|
||||
- [ ] Moderator approves submission → entity appears
|
||||
- [ ] Edit existing entity → creates submission → approve → changes apply
|
||||
- [ ] Delete entity → creates submission → approve → entity deleted
|
||||
|
||||
#### Reviews & Social (45 min)
|
||||
- [ ] Write review → creates submission → approve → appears
|
||||
- [ ] Mark review helpful
|
||||
- [ ] Add ride credit → creates submission → approve → appears
|
||||
- [ ] Create top list
|
||||
- [ ] Add items to top list
|
||||
- [ ] Reorder top list items
|
||||
- [ ] Delete top list
|
||||
|
||||
#### Media (30 min)
|
||||
- [ ] Upload photo to park
|
||||
- [ ] Upload photo to ride
|
||||
- [ ] Edit photo caption
|
||||
- [ ] Delete photo
|
||||
- [ ] Verify CloudFlare URLs
|
||||
|
||||
#### Search (30 min)
|
||||
- [ ] Global search works
|
||||
- [ ] Park search with filters works
|
||||
- [ ] Ride search with filters works
|
||||
- [ ] Company search works
|
||||
- [ ] Autocomplete suggests
|
||||
|
||||
#### Moderation (30 min)
|
||||
- [ ] View moderation queue
|
||||
- [ ] Claim submission
|
||||
- [ ] Lock extends automatically
|
||||
- [ ] Approve submission
|
||||
- [ ] Reject submission
|
||||
- [ ] Selective approve (approve some items, reject others)
|
||||
- [ ] Lock releases on navigation
|
||||
|
||||
#### Admin (30 min)
|
||||
- [ ] View admin dashboard
|
||||
- [ ] View moderation stats
|
||||
- [ ] View pipeline health
|
||||
- [ ] Manage reports
|
||||
- [ ] Manage contact submissions
|
||||
|
||||
#### Other Features (30 min)
|
||||
- [ ] Contact form submission
|
||||
- [ ] Timeline events display
|
||||
- [ ] Entity history works
|
||||
- [ ] Version diffs work
|
||||
- [ ] Profile editing works
|
||||
- [ ] Settings pages work
|
||||
|
||||
---
|
||||
|
||||
### Task 14.6: Next.js Specific Testing (2 hours)
|
||||
|
||||
#### Server-Side Rendering (SSR)
|
||||
- [ ] Pages render on server
|
||||
- [ ] Data fetching works server-side
|
||||
- [ ] Metadata appears in HTML source
|
||||
- [ ] No hydration errors
|
||||
|
||||
#### Client-Side Navigation
|
||||
- [ ] App Router navigation works
|
||||
- [ ] Parallel routes work (if used)
|
||||
- [ ] Intercepting routes work (if used)
|
||||
- [ ] Loading states display correctly
|
||||
|
||||
#### Performance
|
||||
- [ ] Turbopack hot reload is fast
|
||||
- [ ] Production build is optimized
|
||||
- [ ] Bundle sizes are acceptable
|
||||
- [ ] Core Web Vitals meet targets:
|
||||
- [ ] LCP < 2.5s
|
||||
- [ ] FID < 100ms
|
||||
- [ ] CLS < 0.1
|
||||
|
||||
#### Environment Variables
|
||||
- [ ] Server-side env vars work
|
||||
- [ ] Client-side env vars work (NEXT_PUBLIC_*)
|
||||
- [ ] No secrets exposed to client
|
||||
|
||||
---
|
||||
|
||||
### Task 14.7: Sacred Pipeline Verification (1 hour)
|
||||
|
||||
**CRITICAL:** Verify pipeline integrity
|
||||
|
||||
- [ ] Can submit new park → goes to queue
|
||||
- [ ] Can submit new ride → goes to queue
|
||||
- [ ] Can submit new company → goes to queue
|
||||
- [ ] Can edit park → goes to queue
|
||||
- [ ] Can edit ride → goes to queue
|
||||
- [ ] Can edit company → goes to queue
|
||||
- [ ] Can delete park → goes to queue
|
||||
- [ ] Can delete ride → goes to queue
|
||||
- [ ] Can delete company → goes to queue
|
||||
- [ ] Approval creates entity/applies changes
|
||||
- [ ] Rejection saves reason
|
||||
- [ ] No way to bypass moderation
|
||||
- [ ] All changes create versions (pghistory)
|
||||
|
||||
---
|
||||
|
||||
### Task 14.8: Browser Console Check (30 min)
|
||||
|
||||
Open dev tools in browser and check:
|
||||
|
||||
- [ ] No errors in console
|
||||
- [ ] No failed API requests (check Network tab)
|
||||
- [ ] No 404s
|
||||
- [ ] No CORS errors
|
||||
- [ ] No authentication errors
|
||||
- [ ] All API calls go to Django (not Supabase)
|
||||
|
||||
---
|
||||
|
||||
### Task 14.9: Final Documentation Update (30 min)
|
||||
|
||||
- [ ] Update migration README with completion status
|
||||
- [ ] Document any known issues
|
||||
- [ ] Document deployment notes
|
||||
- [ ] Update main project README if needed
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Final Success Criteria
|
||||
|
||||
### Code Cleanliness
|
||||
- [ ] Zero Supabase dependencies in code
|
||||
- [ ] Zero Supabase dependencies in package.json
|
||||
- [ ] All imports resolve
|
||||
- [ ] All TypeScript errors fixed
|
||||
- [ ] Build succeeds without errors
|
||||
|
||||
### Functionality
|
||||
- [ ] Authentication works (all flows)
|
||||
- [ ] All entity CRUD works via Sacred Pipeline
|
||||
- [ ] Reviews work
|
||||
- [ ] Ride credits work
|
||||
- [ ] Top lists work
|
||||
- [ ] Photos work
|
||||
- [ ] Search works
|
||||
- [ ] Moderation works
|
||||
- [ ] Admin functions work
|
||||
- [ ] Contact form works
|
||||
- [ ] Timeline works
|
||||
- [ ] History works
|
||||
|
||||
### Sacred Pipeline
|
||||
- [ ] ALL entity changes go through ContentSubmission
|
||||
- [ ] NO direct database writes possible
|
||||
- [ ] Moderation queue functional
|
||||
- [ ] Approval creates versions
|
||||
- [ ] Rejection saves reason
|
||||
- [ ] No bypass mechanisms exist
|
||||
|
||||
### Performance
|
||||
- [ ] Pages load quickly
|
||||
- [ ] Search is responsive
|
||||
- [ ] No memory leaks
|
||||
- [ ] API requests are reasonable
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
Once ALL success criteria are met:
|
||||
|
||||
### 1. Final Commit
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Complete Supabase→Django + React→Next.js 15 migration - All phases complete"
|
||||
git push
|
||||
```
|
||||
|
||||
### 2. Deploy to Staging
|
||||
```bash
|
||||
# Deploy Django backend to staging
|
||||
# Deploy frontend to staging (Vercel/etc)
|
||||
```
|
||||
|
||||
### 3. Staging Verification (2-4 hours)
|
||||
- [ ] Run ALL integration tests again in staging
|
||||
- [ ] Monitor error logs
|
||||
- [ ] Check API response times
|
||||
- [ ] Verify Sacred Pipeline in staging
|
||||
|
||||
### 4. Production Deployment
|
||||
**Only after staging is 100% verified:**
|
||||
```bash
|
||||
# Deploy Django backend to production
|
||||
# Deploy frontend to production
|
||||
```
|
||||
|
||||
### 5. Post-Deployment Monitoring (24 hours)
|
||||
- [ ] Watch error logs closely
|
||||
- [ ] Monitor API response times
|
||||
- [ ] Check Sacred Pipeline is working
|
||||
- [ ] Verify no Supabase errors
|
||||
- [ ] Monitor user feedback
|
||||
|
||||
---
|
||||
|
||||
## 📝 Rollback Plan
|
||||
|
||||
If critical issues are found:
|
||||
|
||||
1. Immediately rollback frontend deployment
|
||||
2. Rollback backend deployment if needed
|
||||
3. Investigate issue
|
||||
4. Fix in development
|
||||
5. Re-test thoroughly
|
||||
6. Re-deploy
|
||||
|
||||
---
|
||||
|
||||
## 🎉 MIGRATION COMPLETE!
|
||||
|
||||
Once Phase 14 is finished and verified:
|
||||
|
||||
**✅ Supabase is 100% removed.**
|
||||
**✅ ThrillWiki runs on Django REST API.**
|
||||
**✅ Frontend is Next.js 15 + App Router + Turbopack.**
|
||||
**✅ Sacred Pipeline remains intact.**
|
||||
**✅ All features work.**
|
||||
**✅ Package manager is Bun.**
|
||||
**✅ Environment variables properly configured.**
|
||||
|
||||
Celebrate, but monitor closely for 24-48 hours post-deployment.
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
- `migration/README.md` - Overview
|
||||
- `COMPLETE_SUPABASE_REMOVAL_AUDIT_AND_PLAN.md` - Original audit
|
||||
- All phase documents (01-12) - Implementation guides
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** November 9, 2025
|
||||
202
migration/README.md
Normal file
202
migration/README.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# ThrillWiki: Supabase → Django + React → Next.js 15 Migration Plan
|
||||
|
||||
## 📊 Current State
|
||||
**Date:** January 2025
|
||||
**Django Backend:** ✅ 100% Complete
|
||||
**Frontend Framework:** React SPA (Vite) → Migrating to Next.js 15 + App Router + Turbopack
|
||||
**Frontend Services:** ❌ 5% Complete (only reports)
|
||||
**Frontend Integration:** ❌ 0% Complete (171 files using Supabase)
|
||||
**Package Manager:** Bun
|
||||
|
||||
## 🎯 Migration Goal
|
||||
This is a **DUAL MIGRATION**:
|
||||
|
||||
1. **Backend Service Layer**: Replace ALL Supabase calls with Django REST API service layer
|
||||
2. **Frontend Framework**: Convert React SPA to Next.js 15 + App Router + Turbopack
|
||||
|
||||
**Requirements:**
|
||||
- ✅ 100% feature parity
|
||||
- ✅ Sacred Pipeline protection (NEVER bypass moderation)
|
||||
- ✅ Environment variables (NO hardcoded URLs/keys)
|
||||
- ✅ Bun for package management
|
||||
- ✅ All existing URLs preserved
|
||||
|
||||
## ⏱️ Total Estimated Time: 180-240 hours
|
||||
|
||||
---
|
||||
|
||||
## 📋 Phase Overview
|
||||
|
||||
| Phase | Name | Time | Priority | Status |
|
||||
|-------|------|------|----------|--------|
|
||||
| 1 | Foundation + Next.js Setup | 20-25h | CRITICAL | ⬜ Not Started |
|
||||
| 2 | Authentication | 18-22h | CRITICAL | ⬜ Not Started |
|
||||
| 3 | Sacred Pipeline | 20-25h | CRITICAL | ⬜ Not Started |
|
||||
| 4 | Entity Services | 25-30h | HIGH | ⬜ Not Started |
|
||||
| 5 | Reviews & Social | 12-15h | HIGH | ⬜ Not Started |
|
||||
| 6 | Moderation & Admin | 10-12h | HIGH | ⬜ Not Started |
|
||||
| 7 | Media & Photos | 8-10h | MEDIUM | ⬜ Not Started |
|
||||
| 8 | Search & Discovery | 8-10h | MEDIUM | ⬜ Not Started |
|
||||
| 9 | Timeline & History | 6-8h | MEDIUM | ⬜ Not Started |
|
||||
| 10 | Users & Profiles | 8-10h | MEDIUM | ⬜ Not Started |
|
||||
| 11 | Contact & Misc | 6-8h | LOW | ⬜ Not Started |
|
||||
| 12 | Next.js Pages Migration | 45-55h | HIGH | ⬜ Not Started |
|
||||
| 13 | Next.js Optimization | 15-20h | HIGH | ⬜ Not Started |
|
||||
| 14 | Cleanup & Testing | 15-18h | CRITICAL | ⬜ Not Started |
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Critical Path
|
||||
|
||||
**You MUST complete phases in this order:**
|
||||
|
||||
1. **Phase 1** (Foundation) - Everything depends on this
|
||||
2. **Phase 2** (Authentication) - Most features require auth
|
||||
3. **Phase 3** (Sacred Pipeline) - The heart of ThrillWiki
|
||||
4. **Phase 4** (Entity Services) - Required for Phase 12
|
||||
|
||||
After Phase 4, you can work on Phases 5-11 in parallel or any order.
|
||||
|
||||
**Phase 12** must wait for Phases 1-4 to be complete.
|
||||
**Phase 13** must be last.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Phase Documents
|
||||
|
||||
Each phase has a dedicated document with detailed checklists:
|
||||
|
||||
- [Phase 1: Foundation](./PHASE_01_FOUNDATION.md)
|
||||
- [Phase 2: Authentication](./PHASE_02_AUTHENTICATION.md)
|
||||
- [Phase 3: Sacred Pipeline](./PHASE_03_SACRED_PIPELINE.md)
|
||||
- [Phase 4: Entity Services](./PHASE_04_ENTITY_SERVICES.md)
|
||||
- [Phase 5: Reviews & Social](./PHASE_05_REVIEWS_SOCIAL.md)
|
||||
- [Phase 6: Moderation & Admin](./PHASE_06_MODERATION_ADMIN.md)
|
||||
- [Phase 7: Media & Photos](./PHASE_07_MEDIA_PHOTOS.md)
|
||||
- [Phase 8: Search & Discovery](./PHASE_08_SEARCH_DISCOVERY.md)
|
||||
- [Phase 9: Timeline & History](./PHASE_09_TIMELINE_HISTORY.md)
|
||||
- [Phase 10: Users & Profiles](./PHASE_10_USERS_PROFILES.md)
|
||||
- [Phase 11: Contact & Misc](./PHASE_11_CONTACT_MISC.md)
|
||||
- [Phase 12: Next.js Pages Migration](./PHASE_12_PAGES_MIGRATION.md)
|
||||
- [Phase 13: Next.js Optimization](./PHASE_13_NEXTJS_OPTIMIZATION.md)
|
||||
- [Phase 14: Cleanup & Testing](./PHASE_14_CLEANUP_TESTING.md)
|
||||
|
||||
### Additional Guides
|
||||
|
||||
- [Next.js 15 Migration Guide](./NEXTJS_15_MIGRATION_GUIDE.md)
|
||||
- [Environment Variables Guide](./ENVIRONMENT_VARIABLES.md)
|
||||
- [Bun Setup & Usage](./BUN_GUIDE.md)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
### Code Quality
|
||||
- [ ] Zero `import ... from '@supabase/supabase-js'` in codebase
|
||||
- [ ] Zero `supabase.` method calls
|
||||
- [ ] All TypeScript errors resolved
|
||||
- [ ] All linter warnings addressed
|
||||
- [ ] Test coverage >70% for services
|
||||
|
||||
### Functionality
|
||||
- [ ] All authenticated features work
|
||||
- [ ] Sacred Pipeline intact: Form → Submission → Moderation → Approval
|
||||
- [ ] All entity CRUD operations work
|
||||
- [ ] Search functional
|
||||
- [ ] Reviews functional
|
||||
- [ ] Photos functional
|
||||
- [ ] Moderation queue functional
|
||||
- [ ] No console errors
|
||||
|
||||
### Performance
|
||||
- [ ] Page load times <2s
|
||||
- [ ] API response times <500ms
|
||||
- [ ] No memory leaks
|
||||
- [ ] Proper caching
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Critical Rules
|
||||
|
||||
1. **Sacred Pipeline is SACRED** - Never bypass moderation
|
||||
2. **Test as you go** - Don't build everything then test
|
||||
3. **Keep Supabase temporarily** - Only remove in Phase 13
|
||||
4. **Commit frequently** - Small, atomic commits
|
||||
5. **Document changes** - Update docs as you go
|
||||
6. **No hallucinations** - Only fix REAL problems
|
||||
7. **Keep it simple** - No feature bloat
|
||||
|
||||
---
|
||||
|
||||
## 📊 Progress Tracking
|
||||
|
||||
Update this section as you complete phases:
|
||||
|
||||
**Started:** [Date]
|
||||
**Current Phase:** Phase X
|
||||
**Completed Phases:** 0/14
|
||||
**Estimated Completion:** [Date]
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- Django API Documentation: `django/API_GUIDE.md`
|
||||
- Django Admin Guide: `django/ADMIN_GUIDE.md`
|
||||
- Sacred Pipeline Audit: `django/SACRED_PIPELINE_AUDIT_AND_IMPLEMENTATION_PLAN.md`
|
||||
- Complete Migration Audit: `django/COMPLETE_MIGRATION_AUDIT.md`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
- **Django Backend is Ready** - All APIs exist and work
|
||||
- **171 Files Need Migration** - From Supabase to Django service layer
|
||||
- **Framework Migration** - React SPA → Next.js 15 App Router
|
||||
- **Package Manager** - Use Bun (not npm/yarn)
|
||||
- **Environment Variables** - NO hardcoded URLs or keys
|
||||
- **entitySubmissionHelpers.ts** - 1,200+ lines, most critical file
|
||||
- **Sacred Pipeline** - NEVER bypass moderation (except moderators)
|
||||
- **URL Preservation** - All existing URLs must work identically
|
||||
- **No Data Loss** - All functionality must be preserved
|
||||
- **No Feature Loss** - 100% feature parity required
|
||||
|
||||
## 🌍 Environment Variables
|
||||
|
||||
All configuration must use environment variables:
|
||||
|
||||
```bash
|
||||
# .env.local (Next.js)
|
||||
NEXT_PUBLIC_DJANGO_API_URL=https://api.thrillwiki.com
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=xxx
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=xxx
|
||||
CLOUDFLARE_API_TOKEN=xxx
|
||||
```
|
||||
|
||||
See [ENVIRONMENT_VARIABLES.md](./ENVIRONMENT_VARIABLES.md) for complete guide.
|
||||
|
||||
## 📦 Bun Package Manager
|
||||
|
||||
All npm commands are replaced with Bun:
|
||||
|
||||
```bash
|
||||
bun install # Install dependencies
|
||||
bun run dev # Start development server
|
||||
bun run build # Production build
|
||||
bun test # Run tests
|
||||
bun add package-name # Add dependency
|
||||
```
|
||||
|
||||
See [BUN_GUIDE.md](./BUN_GUIDE.md) for complete guide.
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Need Help?
|
||||
|
||||
If stuck on a phase:
|
||||
1. Review the phase document thoroughly
|
||||
2. Check Django API endpoints in `django/api/v1/endpoints/`
|
||||
3. Review existing services in `src/services/reports/` as example
|
||||
4. Test with Postman/curl against Django API
|
||||
5. Don't proceed until current phase is solid
|
||||
Reference in New Issue
Block a user