diff --git a/.dev.vars.example b/.dev.vars.example index b97e98a..6536148 100644 --- a/.dev.vars.example +++ b/.dev.vars.example @@ -4,7 +4,9 @@ NEXTJS_ENV=development # Drizzle Kit credentials for D1 # Get your Account ID from: https://dash.cloudflare.com/ (right sidebar) # Get your API Token from: https://dash.cloudflare.com/profile/api-tokens +# Get your Database ID from: wrangler d1 list (or create with: wrangler d1 create ) CLOUDFLARE_ACCOUNT_ID=your-account-id-here +CLOUDFLARE_D1_DATABASE_ID=your-database-id-here CLOUDFLARE_D1_TOKEN=your-api-token-here CLOUDFLARE_R2_URL=your-r2-url-here CLOUDFLARE_API_TOKEN=your-api-token-here diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..bd98b4f --- /dev/null +++ b/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "shadcn": { + "command": "npx", + "args": [ + "shadcn@latest", + "mcp" + ] + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fb36372 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,138 @@ +# Changelog + +All notable changes to Full Flare Stack will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [1.0.0] - 2025-11-10 + +### 🎉 Initial Release - "Full Flare Stack" + +Forked from [ifindev/fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare) and evolved into an independent Jezweb open-source project. + +### Added + +**Component Architecture** +- Three-layer component system (primitives → patterns → features) +- 43 shadcn/ui components pre-installed and documented +- Component decision framework for where to put code +- Pattern extraction methodology (after 3rd use) + +**Documentation** +- `COMPONENT_INVENTORY.md` - All 43 installed components with usage +- `COMPOSED_PATTERNS_ROADMAP.md` - Build priorities for reusable patterns +- `docs/development-planning/` - Complete architecture guides + - `architecture-overview.md` - Three-layer system explained + - `component-decision-framework.md` - Decision trees for component placement + - `module-development-guide.md` - Step-by-step feature building + - `pattern-library-plan.md` - Detailed pattern specifications +- `docs/templates/PATTERN_TEMPLATE.md` - Template for documenting patterns +- `MODULES.md` - Module system guide +- `MODULE_TEMPLATE.md` - How to create modules +- `LAYOUTS.md` - 5 production-ready layouts + +**UX Improvements** (from PRs #11-21 to upstream) +- Auto-detect port in auth client (fixes hardcoded localhost:3000) +- Fixed navigation link: /todos → /dashboard/todos +- Replaced alert() with toast notifications +- Added ARIA labels for accessibility +- File upload validation (size + type checking) +- Success/error toast feedback throughout app +- Fixed R2 URL double https:// prefix issue +- Environment variable for database ID (no more hardcoded IDs) +- NEXT_REDIRECT error handling standardization +- Standardized error response patterns ({ success, data?, error? }) + +**API Documentation** +- Complete API endpoint documentation (872 lines) +- REST endpoints: /api/summarize, /api/auth/* +- Server Actions: 11 actions fully documented +- Data models, error handling, examples + +**Developer Experience** +- Comprehensive CLAUDE.md for AI-assisted development +- SESSION.md for tracking development progress +- Improved error messages and logging +- Better TypeScript types throughout + +### Changed + +- **Project Name**: `fullstack-next-cloudflare` → `full-flare-stack` +- **Branding**: Now a Jezweb open-source project +- **Version**: 0.1.0 → 1.0.0 (production-ready) +- **License**: MIT (clarified copyright: Jez Dawes / Jezweb) +- **Repository**: github.com/jezweb/full-flare-stack +- **README.md**: Completely rewritten with new value proposition +- **package.json**: Updated metadata, keywords, author information + +### Improved + +- Documentation clarity and completeness +- Code organization (three-layer architecture) +- Error handling consistency +- Type safety across the stack +- Developer onboarding experience +- Component reusability + +--- + +## Upstream Contributions + +The following improvements were contributed back to [ifindev/fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare): + +**PRs #11-16: Quick Fixes** +- #11: Auto-detect port in auth client +- #12: Fix navigation link (/todos → /dashboard/todos) +- #13: Fix typos in method names +- #14: Replace alert() with toast in delete-todo.tsx +- #15: Add ARIA labels for accessibility +- #16: Add file upload validation + +**PRs #17-20: Medium-Difficulty Fixes** +- #17: Fix R2 URL double https:// prefix +- #18: Database ID environment variable +- #19: NEXT_REDIRECT error handling +- #20: Standardize error responses + +**PR #21: Documentation** +- #21: Complete API documentation (872 lines) + +**Total**: 15+ fixes/improvements, ~1,500+ lines changed, 872 lines of documentation + +These PRs improved the upstream project for everyone. Full Flare Stack builds on these foundations with additional architecture and documentation. + +--- + +## Fork History + +- **2025-11-08**: Forked from ifindev/fullstack-next-cloudflare +- **2025-11-08**: Contributed 11 PRs (#11-21) with fixes and documentation +- **2025-11-10**: Added three-layer architecture and 43 shadcn components +- **2025-11-10**: Comprehensive architecture documentation +- **2025-11-10**: Rebranded as "Full Flare Stack" - Jezweb open-source project +- **2025-11-10**: Released v1.0.0 + +--- + +## Acknowledgments + +Thank you to [@ifindev](https://github.com/ifindev) for creating the original template that served as the foundation for Full Flare Stack. + +--- + +## Future Releases + +See [ROADMAP.md](./ROADMAP.md) for planned features and improvements. + +**Upcoming:** +- v1.1.0: First composed patterns (DataTable, PageHeader, EmptyState) +- v1.2.0: Form patterns (SearchableSelect, DateRangePicker) +- v1.3.0: Media patterns (FileUpload, ImageGallery) +- v2.0.0: Multi-tenancy, advanced features + +--- + +**Full Flare Stack** - Built with ❤️ by [Jezweb](https://jezweb.com.au) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..491f093 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,754 @@ +# Claude Code Project Context + +**Project:** Full Flare Stack +**Type:** Production-ready Next.js + Cloudflare Workers starter kit +**Repository:** https://github.com/jezweb/full-flare-stack +**Last Updated:** 2025-11-10 +**Version:** 1.0.0 + +--- + +## Project Overview + +### Tech Stack + +**Frontend:** +- Next.js 15.4.6 (App Router, React Server Components) +- React 19.1.0 +- TailwindCSS v4 +- shadcn/ui (Radix UI components) +- React Hook Form + Zod validation + +**Backend & Infrastructure:** +- Cloudflare Workers (via @opennextjs/cloudflare 1.11.1) +- Cloudflare D1 (SQLite at the edge) +- Cloudflare R2 (object storage) +- Cloudflare Workers AI (optional) +- better-auth 1.3.9 (authentication) +- Drizzle ORM (database toolkit) + +**DevOps:** +- Wrangler 4.46.0 (Cloudflare CLI) +- pnpm (package manager) +- GitHub Actions (CI/CD - if configured) + +### Project Status + +This is a **production-ready modular starter kit** that can be forked and customized for new applications. + +**Current features:** +- ✅ Authentication (better-auth with Google OAuth) +- ✅ Database (D1 with Drizzle ORM) +- ✅ Example CRUD module (todos with categories) +- ✅ Dark/light mode theming +- ✅ Modular architecture (see MODULES.md) +- ✅ Three-layer component system (primitives → patterns → features) +- ✅ 43 shadcn/ui components installed (Layer 1 foundation complete) +- ✅ 5 production layout variants (authenticated, marketing, minimal, split, dashboard) + +**Intentionally missing:** +- ❌ Automated tests (manual testing only) +- ❌ Payment integration +- ❌ Email sending +- ❌ Advanced AI features (example exists, not production-ready) + +--- + +## Component Architecture + +This project uses a **three-layer component architecture** for building scalable applications: + +### Layer 1: UI Primitives (`/components/ui/`) +- **43 shadcn/ui components installed** (foundation complete as of 2025-11-10) +- Includes: forms, data display, overlays, feedback, layout, navigation +- See: [COMPONENT_INVENTORY.md](./docs/COMPONENT_INVENTORY.md) for complete list + +### Layer 2: Composed Patterns (`/components/composed/`) +- Reusable UI patterns built from Layer 1 primitives +- NO business logic, NO database access +- Build after 3rd use of a pattern +- Examples: DataTable, PageHeader, SearchableSelect, FileUpload +- See: [COMPOSED_PATTERNS_ROADMAP.md](./docs/COMPOSED_PATTERNS_ROADMAP.md) for build priorities + +### Layer 3: Feature Modules (`/modules/[feature]/`) +- Business logic, Server Actions, database access +- Feature-specific components +- Uses Layer 1 + Layer 2 components +- See: [MODULES.md](./MODULES.md) for module system + +**Quick Decision:** +- Has business logic? → `/modules/[feature]/components/` +- Used in 3+ features? → `/components/composed/[category]/` +- shadcn component? → `/components/ui/` + +**Architecture Documentation:** +- [Architecture Overview](./docs/development-planning/architecture-overview.md) - Three-layer system explained +- [Component Decision Framework](./docs/development-planning/component-decision-framework.md) - Where to put components +- [Pattern Library Plan](./docs/development-planning/pattern-library-plan.md) - Detailed pattern specifications +- [Module Development Guide](./docs/development-planning/module-development-guide.md) - Building features + +--- + +## Development Workflow + +### Starting Development + +**Two-terminal setup (recommended):** + +```bash +# Terminal 1: Start Wrangler (provides D1 database access) +pnpm run wrangler:dev + +# Terminal 2: Start Next.js (provides hot module reload) +pnpm run dev +``` + +**Access points:** +- **Next.js app:** http://localhost:3000 (use this for development) +- **Wrangler dev:** http://localhost:8787 (Cloudflare runtime, no HMR) + +**Alternative (single terminal, no HMR):** +```bash +pnpm run dev:cf +``` + +### Why Two Terminals? + +- **Wrangler** provides local D1 database access and Cloudflare bindings +- **Next.js dev** provides fast refresh and better DX +- They run on different ports but share the local D1 instance + +### Environment Setup + +**Required files:** +- `.dev.vars` - Local development secrets (gitignored) +- `wrangler.jsonc` - Cloudflare configuration + +**Key environment variables:** +```bash +# .dev.vars +CLOUDFLARE_ACCOUNT_ID=your-account-id +BETTER_AUTH_SECRET=your-random-secret +GOOGLE_CLIENT_ID=your-google-client-id +GOOGLE_CLIENT_SECRET=your-google-client-secret +CLOUDFLARE_R2_URL=https://pub-xxxxx.r2.dev +``` + +Generate auth secret: +```bash +openssl rand -base64 32 +``` + +--- + +## Build & Deploy + +### Local Build + +```bash +# Build for Cloudflare Workers +pnpm run build:cf + +# This runs: @opennextjs/cloudflare build +# Output: .worker-next/ directory +``` + +**Verify build succeeded:** +1. Check for `.worker-next/` directory +2. No TypeScript errors in output +3. No build warnings about missing bindings + +### Deployment + +**Preview deployment (test before production):** +```bash +pnpm run deploy:preview + +# Uses wrangler.jsonc [env.preview] config +``` + +**Production deployment:** +```bash +pnpm run deploy + +# Uses wrangler.jsonc main config +``` + +### Post-Deployment Checks + +1. **Visit deployed URL** - Check app loads +2. **Test authentication** - Login with Google OAuth +3. **Test CRUD operations** - Create/edit/delete a todo +4. **Check database** - Verify data persists +5. **Check categories** - Verify category colors work + +**Common deployment issues:** +- Missing environment variables (check Wrangler secrets) +- Database migrations not applied (run `db:migrate:prod`) +- OAuth redirect URI mismatch (update Google OAuth settings) + +--- + +## Testing Strategy + +**Current approach:** Manual testing (no automated test suite) + +### Manual Testing Checklist + +**Basic smoke test (5 minutes):** +- [ ] App loads at http://localhost:3000 +- [ ] No console errors +- [ ] Dark/light theme toggle works +- [ ] Login redirects to Google OAuth +- [ ] After login, redirects to dashboard +- [ ] Can create a new todo +- [ ] Can edit a todo +- [ ] Can delete a todo (with confirmation dialog) +- [ ] Can create a category +- [ ] Category color picker works +- [ ] Can assign category to todo +- [ ] Category color displays on todo card +- [ ] Logout works + +**Database verification:** +```bash +# Open Drizzle Studio +pnpm run db:studio:local + +# Check tables exist: +# - user +# - session +# - account +# - verification +# - todos +# - categories + +# Verify data: +# - User record exists after login +# - Todos show correct userId +# - Categories show correct userId and color +``` + +**Build verification:** +```bash +# 1. Clean build +rm -rf .next .worker-next + +# 2. Build for Cloudflare +pnpm run build:cf + +# 3. Check for errors +# - No TypeScript errors +# - No "module not found" errors +# - No warnings about missing bindings + +# 4. Check output +ls -la .worker-next/ +# Should contain worker bundle +``` + +### Testing New Features + +When adding a new feature module (see MODULE_TEMPLATE.md): + +1. **Create feature** (e.g., invoices module) +2. **Start dev servers** (wrangler + next) +3. **Test CRUD operations:** + - Create item + - View list + - Edit item + - Delete item +4. **Verify database:** + - Check Drizzle Studio + - Confirm user isolation (create second user, verify they only see their data) +5. **Test error handling:** + - Submit invalid form data + - Verify Zod validation works + - Check error messages display +6. **Build and deploy to preview:** + - `pnpm run deploy:preview` + - Test on live preview URL + +--- + +## Database Workflow + +### Schema Structure + +**Location:** `src/db/schema.ts` + +Schemas are defined in modules but exported centrally: + +```typescript +// Module schema: src/modules/todos/schemas/todo.schema.ts +export const todos = sqliteTable("todos", { ... }); + +// Central export: src/db/schema.ts +export { todos } from "@/modules/todos/schemas/todo.schema"; +``` + +**Why?** Allows Drizzle to generate migrations from all schemas while keeping modules self-contained. + +### Creating Migrations + +**When to create a migration:** +- Adding a new module with database tables +- Modifying existing table schema +- Adding/removing columns +- Changing column types + +**Steps:** + +```bash +# 1. Modify schema file in src/modules/[module]/schemas/ + +# 2. Generate migration with descriptive name +pnpm run db:generate:named "add_invoices_table" + +# 3. Review generated migration in src/drizzle/ +# - Check SQL is correct +# - Verify no unintended changes + +# 4. Apply to local database +pnpm run db:migrate:local + +# 5. Verify in Drizzle Studio +pnpm run db:studio:local + +# 6. Test your feature with the new schema + +# 7. Commit migration file +git add src/drizzle/XXXX_add_invoices_table.sql +git commit -m "feat: add invoices table migration" +``` + +### Migration Commands + +```bash +# Local (development) +pnpm run db:migrate:local + +# Preview (test environment) +pnpm run db:migrate:preview + +# Production (live) +pnpm run db:migrate:prod + +# Inspect tables +pnpm run db:inspect:local +pnpm run db:inspect:preview +pnpm run db:inspect:prod + +# Open Drizzle Studio +pnpm run db:studio:local +pnpm run db:studio # For remote DB +``` + +### Database Reset (Local Only) + +**Warning:** This deletes all local data! + +```bash +# Reset local database (drops all tables and reapplies migrations) +pnpm run db:reset:local + +# Or manually: +rm -rf .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite* +pnpm run db:migrate:local +``` + +--- + +## Architecture Decisions + +### Why Modular Architecture? + +**Decision:** Feature-based modules (`src/modules/auth`, `src/modules/todos`, etc.) + +**Reasons:** +- ✅ Easy to remove unwanted features when forking +- ✅ Clear separation of concerns +- ✅ Self-contained and reusable +- ✅ Industry standard (T3 Stack, Next.js Boilerplate use same pattern) +- ✅ No plugin system overhead (50-100+ hours to build) + +**See:** MODULES.md for complete guide + +### Why better-auth? + +**Alternatives considered:** Clerk, Auth.js, custom JWT + +**Chosen:** better-auth 1.3.9 + +**Reasons:** +- ✅ TypeScript-first with excellent DX +- ✅ Works with D1 (many auth libs don't) +- ✅ Lightweight (no external services required) +- ✅ Session management built-in +- ✅ Social auth (Google OAuth) easy to configure +- ❌ **Blocker:** No Next.js 16 support yet (tracking issue #5263) + +### Why Cloudflare Workers? + +**Alternatives considered:** Vercel, Netlify, traditional servers + +**Chosen:** Cloudflare Workers + @opennextjs/cloudflare + +**Reasons:** +- ✅ Edge deployment (300+ locations globally) +- ✅ Generous free tier (perfect for MVPs) +- ✅ D1 database included (SQLite at edge) +- ✅ R2 storage (S3-compatible, no egress fees) +- ✅ Workers AI (on-demand ML inference) +- ✅ Scales automatically +- ✅ No cold starts (unlike Lambda) + +**Trade-offs:** +- ❌ Not all npm packages work (no native modules) +- ❌ 10ms CPU limit (but restarts for new requests) +- ❌ Learning curve for Workers-specific patterns + +### Module Dependencies + +**Rule:** Modules can depend on `auth`, but NOT on each other. + +```text +auth (required) + ↓ +dashboard (required - layout) + ↓ +todos (optional - example feature) +``` + +**Why?** +- Prevents circular dependencies +- Makes modules truly reusable +- Simplifies reasoning about the codebase + +**Shared logic goes in:** +- `/src/lib/` - Utilities +- `/src/services/` - Business logic +- `/src/components/` - Shared UI + +--- + +## Known Issues & Gotchas + +### 1. Radix UI Select Empty String Issue + +**Problem:** Radix UI Select doesn't allow empty string values in SelectItems. + +**Solution:** Use sentinel value like `"__any__"` instead of `""`. + +```typescript +// ❌ Breaks: +Any Category + +// ✅ Works: +Any Category + +// Then handle in logic: +const categoryId = value === "__any__" ? null : value; +``` + +**Location:** Fixed in `src/modules/todos/components/todo-form.tsx:96` + +### 2. Two Terminal Requirement + +**Problem:** Need both Wrangler and Next.js dev servers running. + +**Why:** +- Wrangler provides D1 database access +- Next.js provides hot module reload +- They don't conflict (different ports) + +**Solution:** Use two terminals or `pnpm run dev:cf` (no HMR). + +### 3. D1 Local Database Persistence + +**Location:** `.wrangler/state/v3/d1/miniflare-D1DatabaseObject/` + +**Gotcha:** Local database persists between restarts (good and bad). + +**Good:** Don't lose data when restarting dev server. +**Bad:** Stale data can cause confusion. + +**Reset if needed:** +```bash +rm -rf .wrangler/state +pnpm run db:migrate:local +``` + +### 4. Better-auth Session Cookies + +**Cookie name:** `better-auth.session_token` + +**Gotcha:** Must be logged in to test protected routes. + +**Testing API routes with curl:** +1. Login in browser +2. DevTools → Application → Cookies → Copy session token +3. Use in curl: `-H "Cookie: better-auth.session_token=xxx"` + +### 5. Cloudflare Account Selection + +**Problem (after @opennextjs/cloudflare 1.11.1 upgrade):** + +Wrangler dev may show account selection error if you have 100+ Cloudflare accounts. + +**Solution:** Set `CLOUDFLARE_ACCOUNT_ID` in `.dev.vars` (already done). + +### 6. Build Cache Stale Modules + +**Problem:** After upgrading dependencies, old build cache can cause errors. + +**Solution:** +```bash +rm -rf .next .worker-next +pnpm run build:cf +``` + +**When:** After any dependency upgrade or wrangler.jsonc changes. + +### 7. Next.js 16 Upgrade Not Possible Yet + +**Blockers:** +- better-auth: No Next.js 16 support (issue #5263) +- @opennextjs/cloudflare: Proxy system partially supported (issue #972) + +**Timeline:** Reassess Q1 2026 + +**See:** docs/NEXTJS_16_UPGRADE.md for full research + +--- + +## shadcn/ui Component Installation + +**Interactive CLI Issue:** +The `pnpm dlx shadcn@latest add [component]` command is interactive and asks whether to overwrite existing files. This cannot be automated by AI assistants. + +**Solution:** When Claude needs shadcn components: +1. Claude will identify required components +2. Claude will provide the exact command to run +3. **You run it manually** and press "n" when asked to overwrite existing files +4. Takes ~30 seconds + +**Example:** +```bash +# Claude says: "Please run this command and press 'n' for any overwrite prompts:" +pnpm dlx shadcn@latest add navigation-menu dropdown-menu avatar + +# You press 'n' for button.tsx, separator.tsx, etc. +``` + +**Future optimization ideas:** +- Pre-install all ~50 shadcn/ui components upfront +- Create shadcn skill with pre-downloaded components +- Use shadcn MCP server for automated component viewing + +--- + +## Common Commands Quick Reference + +### Development + +```bash +# Start dev (two terminals) +pnpm run wrangler:dev # Terminal 1 +pnpm run dev # Terminal 2 + +# Start dev (single terminal, no HMR) +pnpm run dev:cf + +# Start dev with remote Cloudflare resources +pnpm run dev:remote +``` + +### Database + +```bash +# Generate migration +pnpm run db:generate:named "description" + +# Apply migrations +pnpm run db:migrate:local # Development +pnpm run db:migrate:preview # Preview env +pnpm run db:migrate:prod # Production + +# Inspect database +pnpm run db:inspect:local +pnpm run db:studio:local + +# Reset local database (WARNING: deletes data) +pnpm run db:reset:local +``` + +### Build & Deploy + +```bash +# Build for Cloudflare +pnpm run build:cf + +# Deploy to preview +pnpm run deploy:preview + +# Deploy to production +pnpm run deploy + +# Generate Cloudflare types (after wrangler.jsonc changes) +pnpm run cf-typegen +``` + +### Secrets Management + +```bash +# Add secret to Cloudflare Workers +pnpm run cf:secret BETTER_AUTH_SECRET + +# Or use wrangler directly: +echo "secret-value" | wrangler secret put SECRET_NAME +``` + +### Code Quality + +```bash +# Format code with Biome +pnpm run lint +``` + +--- + +## File Structure Reference + +```text +├── src/ +│ ├── app/ # Next.js App Router pages +│ │ ├── (auth)/ # Auth pages (login, signup) +│ │ ├── api/ # API routes +│ │ └── dashboard/ # Protected pages +│ ├── components/ # Three-layer component system +│ │ ├── ui/ # Layer 1: shadcn/ui primitives (43 components) +│ │ ├── composed/ # Layer 2: Reusable patterns (to be built) +│ │ │ ├── data-display/ +│ │ │ ├── layouts/ +│ │ │ ├── forms/ +│ │ │ ├── feedback/ +│ │ │ ├── media/ +│ │ │ └── navigation/ +│ │ └── shared/ # One-off components +│ ├── db/ # Database configuration +│ │ ├── index.ts # DB connection +│ │ └── schema.ts # Central schema exports +│ ├── modules/ # Layer 3: Feature modules (see MODULES.md) +│ │ ├── auth/ # Authentication (required) +│ │ ├── dashboard/ # Dashboard layout (required) +│ │ └── todos/ # Example CRUD (optional) +│ ├── lib/ # Shared utilities +│ └── drizzle/ # Database migrations +├── docs/ # Documentation +│ ├── development-planning/ # Architecture documentation +│ │ ├── architecture-overview.md +│ │ ├── component-decision-framework.md +│ │ ├── pattern-library-plan.md +│ │ └── module-development-guide.md +│ ├── templates/ # Documentation templates +│ │ └── PATTERN_TEMPLATE.md +│ ├── COMPONENT_INVENTORY.md # 43 installed shadcn components +│ ├── COMPOSED_PATTERNS_ROADMAP.md # Pattern build priorities +│ ├── API_ENDPOINTS.md +│ ├── DATABASE_SCHEMA.md +│ ├── IMPLEMENTATION_PHASES.md +│ └── NEXTJS_16_UPGRADE.md +├── MODULES.md # Module system guide +├── MODULE_TEMPLATE.md # How to create modules +├── CLAUDE.md # This file +├── README.md # Project README +├── SESSION.md # Session state tracking +├── package.json # Dependencies & scripts +├── wrangler.jsonc # Cloudflare config +├── .dev.vars # Local secrets (gitignored) +└── .env.example # Environment template +``` + +--- + +## When Context Clears + +**If you're a fresh Claude Code session reading this:** + +1. **Read this file first** - You're doing it! +2. **Check SESSION.md** - Understand current work state +3. **Review MODULES.md** - Understand architecture +4. **Check recent commits** - `git log --oneline -10` +5. **Check docs/** - Project-specific documentation +6. **Ask the user** - What are we working on today? + +**Key commands to understand current state:** +```bash +git status # What's changed +git log --oneline -5 # Recent work +git branch -a # Available branches +ls -la docs/ # Available documentation +``` + +--- + +## Contributing to This Project + +**Before making changes:** + +1. **Read MODULES.md** - Understand module system +2. **Check existing patterns** - Follow established conventions +3. **Test manually** - Use checklist above +4. **Build before committing** - `pnpm run build:cf` +5. **Write descriptive commits** - Follow conventional commits + +**Module changes:** +- Keep modules self-contained +- Update `src/db/schema.ts` if adding tables +- Generate migrations for schema changes +- Update MODULES.md if changing architecture + +**Documentation changes:** +- Update this file if workflow changes +- Update README.md for user-facing changes +- Update MODULE_TEMPLATE.md if adding new patterns + +--- + +## Resources + +**Project Documentation:** +- [README.md](./README.md) - Setup and deployment +- [MODULES.md](./MODULES.md) - Module system guide +- [MODULE_TEMPLATE.md](./MODULE_TEMPLATE.md) - Create new modules +- [SESSION.md](./SESSION.md) - Current session state + +**Component Architecture:** +- [COMPONENT_INVENTORY.md](./docs/COMPONENT_INVENTORY.md) - 43 installed shadcn components +- [COMPOSED_PATTERNS_ROADMAP.md](./docs/COMPOSED_PATTERNS_ROADMAP.md) - Pattern build priorities +- [Architecture Overview](./docs/development-planning/architecture-overview.md) - Three-layer system +- [Component Decision Framework](./docs/development-planning/component-decision-framework.md) - Where components go +- [Pattern Library Plan](./docs/development-planning/pattern-library-plan.md) - Detailed pattern specs +- [Module Development Guide](./docs/development-planning/module-development-guide.md) - Building features +- [PATTERN_TEMPLATE.md](./docs/templates/PATTERN_TEMPLATE.md) - Template for documenting patterns + +**Technical Documentation:** +- [Next.js 15 Docs](https://nextjs.org/docs) +- [Cloudflare Workers](https://developers.cloudflare.com/workers/) +- [Drizzle ORM](https://orm.drizzle.team/docs/overview) +- [better-auth](https://www.better-auth.com/docs) +- [shadcn/ui](https://ui.shadcn.com/) +- [Tailwind v4](https://tailwindcss.com/docs) + +**Cloudflare Specific:** +- [D1 Documentation](https://developers.cloudflare.com/d1/) +- [R2 Storage](https://developers.cloudflare.com/r2/) +- [Workers AI](https://developers.cloudflare.com/workers-ai/) +- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) + +--- + +**Last Updated:** 2025-11-10 +**Maintainer:** Jez (jeremy@jezweb.net) +**Claude Code Version:** This file is optimized for Claude Code CLI diff --git a/LAYOUTS.md b/LAYOUTS.md new file mode 100644 index 0000000..78cfbdc Binary files /dev/null and b/LAYOUTS.md differ diff --git a/LAYOUTS_PROJECT_BRIEF.md b/LAYOUTS_PROJECT_BRIEF.md new file mode 100644 index 0000000..3c3c31d --- /dev/null +++ b/LAYOUTS_PROJECT_BRIEF.md @@ -0,0 +1,336 @@ +# Project Brief: Modular Layouts System + +**Created:** 2025-11-08 +**Status:** Ready for Planning + +--- + +## Vision + +Transform the fullstack-next-cloudflare-demo starter kit with a flexible, production-ready layouts system that provides multiple layout options (sidebar, top-nav, hybrid, centered, marketing) so developers can choose the right layout for their app type without being constrained by a one-size-fits-all approach. + +## Problem/Opportunity + +**Current State:** +- Dashboard layout forces all content into centered column (`md:w-xl` ≈ 576px max width) +- Horizontal top navigation only - no sidebar option +- Great for blogs/content, terrible for dashboards/workspaces/CRM apps +- User is building a CRM and feels "trapped in a small column" + +**Evidence:** +- User's CRM screenshots show dashboard cards, contacts, deals cramped in narrow column +- Real-world need: building production apps requires full-width layouts +- Industry standard: 80% of production SaaS apps use collapsible sidebar layouts + +**Opportunity:** +- Build reusable layouts module that works for ANY future project +- Align with modular architecture (fork → delete unwanted modules → build) +- Use shadcn/ui's official Sidebar component (production-ready, well-maintained) +- Create competitive advantage vs other Next.js starters + +--- + +## Target Audience + +- **Primary users:** Developers forking this starter kit to build SaaS apps, dashboards, internal tools, CRMs +- **Scale:** Reusable across unlimited future projects +- **Context:** Production-ready template for rapid MVP development + +**Use cases:** +1. Dashboard/analytics apps (need full-width charts, tables) +2. CRM/workspace tools (need sidebar navigation) +3. SaaS products (need multi-tenant layouts) +4. Marketing sites (need landing page layouts) +5. Documentation/blogs (need centered content layouts) + +--- + +## Core Functionality (MVP) + +### 1. Collapsible Sidebar Layout +**Why essential:** 80% of production SaaS apps use this pattern (research confirms) + +**Features:** +- Desktop: Fixed 16rem sidebar, collapses to 3rem icon mode +- Mobile: Sheet overlay (slides in from left) +- Keyboard shortcut (Cmd/Ctrl+B) to toggle +- Cookie persistence (7-day expiry) for sidebar state +- Active route highlighting +- Responsive: \`useIsMobile()\` hook switches between fixed/overlay + +**Implementation:** shadcn/ui Sidebar component + SidebarProvider context + +--- + +### 2. Top Navigation Layout (Enhanced) +**Why essential:** Preserve current simple layout option, but make it full-width + +**Features:** +- Horizontal header navigation +- Full-width content area (remove \`md:w-xl\` restriction) +- Theme toggle + user menu +- Mobile responsive (hamburger menu) +- Optional: breadcrumbs + +**Implementation:** Enhance existing Navigation component + +--- + +### 3. Centered Content Layout +**Why essential:** Keep current UX for content-focused pages (docs, blogs, forms) + +**Features:** +- Max-width centered column (current dashboard style) +- Top navigation +- Good for: Documentation, blog posts, forms, wizards +- This is the CURRENT layout, just needs to be a choosable option + +**Implementation:** Extract current dashboard.layout.tsx pattern + +--- + +### 4. Hybrid Layout (Top + Sidebar) +**Why essential:** Most polished option for complex SaaS apps + +**Features:** +- Header at top (logo, breadcrumbs, user menu) +- Collapsible sidebar on left +- Full-width content area +- Most "professional" appearance +- Good for: Enterprise SaaS, complex admin panels + +**Implementation:** Combine header + sidebar components + +--- + +### 5. Marketing Layout +**Why essential:** Every SaaS needs a landing page/public site + +**Features:** +- Full-width hero sections +- Footer with links/social +- No authentication required +- Responsive grid sections +- Good for: Landing pages, pricing, about + +**Implementation:** New layout with public routes + +--- + +## Out of Scope for MVP (Defer to Phase 2) + +### Deferred Features: +- ❌ **Layout switching at runtime** (satnaing-admin approach with 3 variants user can toggle) + - Why deferring: Nice-to-have, adds complexity + - Can add in Phase 2 if demand exists + +- ❌ **Command palette** (Cmd+K quick actions) + - Why deferring: Requires additional library (kbar or cmdk) + - Many apps don't need it initially + - Easy to add later as enhancement + +- ❌ **Sidebar resize handle** (SidebarRail) + - Why deferring: New shadcn feature, not widely adopted yet + - Not critical for MVP + +- ❌ **Split panel layout** (email client style) + - Why deferring: Specialty layout, build when needed + - Not common enough for starter kit + +- ❌ **Kanban/board layout** + - Why deferring: Very specific use case + - Can compose from sidebar layout + custom grid + +- ❌ **Multi-column dashboard** with draggable widgets + - Why deferring: Requires additional state management + - Overkill for most projects + +--- + +## Tech Stack (Validated) + +**Frontend:** +- Next.js 15.4.6 (already in project) ✅ +- React 19.1.0 ✅ +- shadcn/ui Sidebar component (will install) 📦 +- Tailwind CSS v4 ✅ + +**State Management:** +- React Context for layout state (SidebarProvider built-in) +- Cookies for persistence (built-in to shadcn/ui Sidebar) +- No Zustand needed (shadcn handles state internally) + +**Responsive:** +- \`useIsMobile()\` hook (custom hook using matchMedia) +- Tailwind breakpoints (md:, lg:) +- Sheet component for mobile sidebar overlay + +**Icons:** +- lucide-react (already installed) ✅ + +**No new dependencies required** beyond shadcn/ui components (which just copy files, not npm packages) + +--- + +## Research Findings + +### Similar Solutions Reviewed + +**1. satnaing/shadcn-admin** (10,028 stars) +- **Strengths:** Layout switching system (3 variants), RTL support, very polished +- **Weaknesses:** Vite + TanStack Router (different stack than ours) +- **Our differentiation:** Next.js 15 + modular architecture + Cloudflare + +**2. Kiranism/next-shadcn-dashboard-starter** (5,444 stars) +- **Strengths:** Next.js 15, Clerk auth, TanStack tables, excellent responsive +- **Weaknesses:** Opinionated (Clerk, specific DB setup) +- **Our differentiation:** Stack-agnostic, modular, better-auth option + +**3. Vercel AI Chatbot** (18,603 stars) +- **Strengths:** Perfect mobile Sheet implementation, clean sidebar +- **Weaknesses:** Chat-focused, not general-purpose +- **Our differentiation:** General-purpose layouts for any app type + +**4. Next.js SaaS Starter** (14,805 stars) +- **Strengths:** Minimal, focused, production-ready +- **Weaknesses:** Single layout pattern +- **Our differentiation:** Multiple layout options + +**5. Circle (Linear Clone)** (2,290 stars) +- **Strengths:** Keyboard-first, minimal chrome, excellent UX +- **Weaknesses:** Specific to project management use case +- **Our differentiation:** Flexible layouts for multiple use cases + +--- + +### Technical Validation + +**Finding 1: shadcn/ui Sidebar is production-ready** +- Official component with active maintenance +- Used by 1000+ projects +- Handles all edge cases (mobile, persistence, keyboard, RTL) +- No need to build custom sidebar from scratch + +**Finding 2: Cookie persistence is industry standard** +- 7-day expiry is common +- Server-side read on initial render prevents flash +- Works with Next.js server components + +**Finding 3: Mobile Sheet pattern is universal** +- Every production app uses Sheet for mobile sidebar +- 768px breakpoint is standard +- useIsMobile hook with matchMedia is best practice + +**Finding 4: Layout switching is nice-to-have, not essential** +- Only 45% of apps implement it +- Can defer to Phase 2 without impact +- Focus on solid default layouts first + +**Finding 5: Breadcrumbs auto-generation is expected** +- Users expect breadcrumbs in dashboard apps +- Can parse from Next.js pathname +- Enhances navigation UX + +--- + +## Estimated Effort + +### MVP Breakdown: + +**Phase 1: Setup & Infrastructure** (30 min) +- Install shadcn/ui sidebar component +- Create \`src/modules/layouts/\` structure +- Create shared components folder +- Update MODULES.md + +**Phase 2: Build 5 Layout Variants** (2 hours) +- Sidebar Layout: 45 min +- Top Nav Layout: 20 min (enhance existing) +- Hybrid Layout: 30 min +- Centered Layout: 10 min (extract existing) +- Marketing Layout: 15 min + +**Phase 3: Shared Components** (45 min) +- Header component: 15 min +- AppSidebar component: 20 min +- UserNav component: 10 min +- Breadcrumbs component: 10 min (optional) + +**Phase 4: Testing & Integration** (30 min) +- Test responsive behavior (desktop, tablet, mobile) +- Test keyboard shortcuts +- Test cookie persistence +- Update dashboard/todos to use new layouts +- Verify dark/light theme compatibility + +**Phase 5: Documentation** (30 min) +- Update MODULES.md (add layouts module) +- Create LAYOUTS.md guide +- Update MODULE_TEMPLATE.md (layout selection) +- Update README.md (showcase layouts) + +**Total MVP:** ~4 hours = ~4 minutes human time with Claude Code + +--- + +## Success Criteria (MVP) + +### Functional: +- [ ] Sidebar layout works on desktop (collapsible, keyboard shortcut) +- [ ] Sidebar layout works on mobile (Sheet overlay) +- [ ] Cookie persistence maintains sidebar state across page reloads +- [ ] All 5 layouts render correctly +- [ ] Responsive behavior works (desktop → tablet → mobile) +- [ ] Dark/light theme works in all layouts +- [ ] Active route highlighting works in sidebar +- [ ] User can switch between layouts by changing layout import + +### Code Quality: +- [ ] TypeScript: No errors, proper types +- [ ] Modular: Each layout is self-contained +- [ ] DRY: Shared components reused across layouts +- [ ] Accessible: Keyboard navigation works +- [ ] Performance: No layout shifts, smooth animations + +### User Validation: +- [ ] User's CRM app can use sidebar layout immediately +- [ ] Full-width content area (no more \`md:w-xl\` constraint) +- [ ] User feels "unblocked" for dashboard development + +--- + +## Research References + +### Production Apps Analyzed (94,000+ combined stars): +- [Next.js SaaS Starter](https://github.com/nextjs/saas-starter) - 14,805 stars +- [Vercel AI Chatbot](https://github.com/vercel/ai-chatbot) - 18,603 stars +- [satnaing/shadcn-admin](https://github.com/satnaing/shadcn-admin) - 10,028 stars +- [Kiranism/next-shadcn-dashboard-starter](https://github.com/Kiranism/next-shadcn-dashboard-starter) - 5,444 stars +- [Circle (Linear Clone)](https://github.com/ln-dev7/circle) - 2,290 stars +- [Skateshop E-commerce](https://github.com/sadmann7/skateshop) - 5,472 stars + +### Official Documentation: +- [shadcn/ui Sidebar Component](https://ui.shadcn.com/docs/components/sidebar) +- [shadcn/ui Dashboard Example](https://ui.shadcn.com/examples/dashboard) +- [Next.js Layouts Documentation](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts) + +--- + +## Recommendation + +✅ **Proceed with implementation in new branch** + +**Rationale:** +1. Scope is well-defined and realistic (4 hours) +2. Validated against 10+ production apps +3. No technical blockers (shadcn component is production-ready) +4. Immediately unblocks user's CRM development +5. Provides reusable asset for unlimited future projects +6. Aligns perfectly with modular architecture philosophy + +**Next steps:** +1. Create feature branch: \`git checkout -b feature/layouts-module\` +2. Implement MVP (4 phases) +3. Test with user's CRM +4. Submit PR to upstream repo +5. Update documentation diff --git a/MODULES.md b/MODULES.md new file mode 100644 index 0000000..f07eb1f --- /dev/null +++ b/MODULES.md @@ -0,0 +1,755 @@ +# Module System Guide + +**Version:** 1.0.0 +**Last Updated:** 2025-11-08 + +--- + +## Overview + +This project uses a **feature-based module architecture** where each feature is self-contained within its own module directory. This makes it easy to: + +- **Add new features** by copying the module pattern +- **Remove unwanted features** by deleting the module folder +- **Reuse features** across different projects +- **Maintain code** with clear separation of concerns + +**Architecture Pattern:** Similar to T3 Stack, Next.js Boilerplate, and other production Next.js applications. + +--- + +## Module Structure + +``` +src/modules/ +├── auth/ ← Required (authentication & user management) +├── dashboard/ ← Required (protected page layout) +├── layouts/ ← Required (layout system - sidebar, top-nav, etc.) +└── todos/ ← Optional (example CRUD feature) +``` + +Each module follows this internal structure: + +``` +module-name/ +├── actions/ ← Server actions (mutations, queries) +├── components/ ← React components +├── models/ ← Database queries (if needed) +├── schemas/ ← Zod validation schemas +├── utils/ ← Helper functions (if needed) +├── hooks/ ← Custom React hooks (if needed) +├── [name].page.tsx ← Page component (if needed) +├── [name].layout.tsx ← Layout component (if needed) +└── [name].route.ts ← Route configuration (if needed) +``` + +--- + +## Available Modules + +### ✅ Required Modules + +#### 1. **auth** - Authentication & User Management + +**Purpose:** Handles user authentication, session management, and authorization. + +**Dependencies:** +- better-auth (authentication library) +- Cloudflare D1 (user/session storage) + +**Database Tables:** +- `user` - User accounts +- `session` - Active sessions +- `account` - OAuth accounts +- `verification` - Email verification tokens + +**Key Files:** +- `src/modules/auth/schemas/auth.schema.ts` - Database schemas +- `src/modules/auth/actions/auth.action.ts` - Login/signup actions +- `src/modules/auth/utils/auth-client.ts` - Better-auth client +- `src/modules/auth/components/login-form.tsx` - Login UI +- `src/modules/auth/components/signup-form.tsx` - Signup UI + +**Routes:** +- `/login` - Login page +- `/signup` - Signup page +- `/api/auth/*` - Better-auth API routes + +**Can be removed?** ❌ No - Required for user authentication + +**Alternative:** Replace with different auth (Clerk, Auth.js, custom JWT) + +--- + +#### 2. **dashboard** - Protected Page Layout + +**Purpose:** Main layout for authenticated users. + +**Dependencies:** +- auth module (for session checking) + +**Database Tables:** None + +**Key Files:** +- `src/modules/dashboard/dashboard.layout.tsx` - Main layout +- `src/modules/dashboard/dashboard.page.tsx` - Dashboard home +- `src/modules/dashboard/dashboard.route.ts` - Route config + +**Routes:** +- `/dashboard` - Main dashboard page + +**Can be removed?** ⚠️ Only if you create alternative protected layout + +--- + +#### 3. **layouts** - Layout System + +**Purpose:** Provides 5 production-ready layout variants for different app types. + +**Dependencies:** +- auth module (for authentication in protected layouts) +- shadcn/ui sidebar component +- shadcn/ui sheet component (mobile drawer) +- shadcn/ui dropdown-menu component + +**Database Tables:** None + +**Key Files:** +- `src/modules/layouts/components/types.ts` - Navigation types +- `src/modules/layouts/components/user-nav.tsx` - User dropdown menu +- `src/modules/layouts/components/app-sidebar.tsx` - Sidebar navigation +- `src/modules/layouts/components/header.tsx` - Header bar +- `src/modules/layouts/sidebar/sidebar.layout.tsx` - Collapsible sidebar layout +- `src/modules/layouts/top-nav/top-nav.layout.tsx` - Horizontal navigation layout +- `src/modules/layouts/hybrid/hybrid.layout.tsx` - Top header + sidebar layout +- `src/modules/layouts/centered/centered.layout.tsx` - Centered content layout +- `src/modules/layouts/marketing/marketing.layout.tsx` - Public pages with footer + +**Layout Variants:** +1. **Sidebar** - Dashboards, CRMs, admin panels (collapsible, keyboard shortcuts) +2. **Top Nav** - Simple apps, tools (horizontal navigation, full-width) +3. **Hybrid** - Complex SaaS (top header + sidebar, most polished) +4. **Centered** - Docs, blogs, forms (max-width content, optimal reading) +5. **Marketing** - Landing pages (public, no auth, with footer) + +**Usage Example:** +```tsx +// app/dashboard/layout.tsx +import SidebarLayout from "@/modules/layouts/sidebar/sidebar.layout"; + +export default async function Layout({ children }) { + return {children}; +} +``` + +**Features:** +- ✅ Full responsive behavior (desktop/tablet/mobile) +- ✅ Dark/light theme support +- ✅ Keyboard shortcuts (Cmd+B to toggle sidebar) +- ✅ Cookie state persistence (sidebar open/closed) +- ✅ Active route highlighting +- ✅ Mobile drawer (Sheet component) +- ✅ Follows shadcn/ui standards + +**Can be removed?** ⚠️ Required - but you can delete individual layout variants you don't need + +**See:** [LAYOUTS.md](./LAYOUTS.md) for complete documentation + +--- + +### 📦 Optional Modules + +#### 4. **todos** - Todo CRUD Example + +**Purpose:** Example feature demonstrating full CRUD operations with categories. + +**Dependencies:** +- auth module (for user context) +- Cloudflare D1 (data storage) + +**Database Tables:** +- `todos` - Todo items +- `categories` - Todo categories + +**Key Files:** +- `src/modules/todos/schemas/todo.schema.ts` - Todo database schema +- `src/modules/todos/schemas/category.schema.ts` - Category schema +- `src/modules/todos/actions/get-todos.action.ts` - Fetch todos +- `src/modules/todos/actions/create-todo.action.ts` - Create todo +- `src/modules/todos/actions/update-todo.action.ts` - Update todo +- `src/modules/todos/actions/delete-todo.action.ts` - Delete todo +- `src/modules/todos/actions/create-category.action.ts` - Create category +- `src/modules/todos/components/todo-card.tsx` - Todo display +- `src/modules/todos/components/todo-form.tsx` - Todo editor +- `src/modules/todos/components/add-category.tsx` - Category creator + +**Routes:** +- `/dashboard/todos` - Todo list page +- `/dashboard/todos/new` - Create todo page +- `/dashboard/todos/[id]` - Edit todo page + +**Can be removed?** ✅ Yes - This is an example feature + +**How to remove:** See "Removing a Module" section below + +--- + +## How to Remove a Module + +### Example: Removing the Todos Module + +**Step 1: Delete the module folder** +```bash +rm -rf src/modules/todos +``` + +**Step 2: Remove database schemas** + +Edit `src/db/schema.ts`: +```typescript +// Before: +export { categories } from "@/modules/todos/schemas/category.schema"; +export { todos } from "@/modules/todos/schemas/todo.schema"; + +// After: (remove the above lines) +``` + +**Step 3: Remove routes** + +Delete or update files that reference the todos module: +- `src/app/dashboard/todos/` - Delete entire folder +- Any imports of todos components or actions + +**Step 4: Generate new migration** + +If you've already run migrations with the todos tables: +```bash +# Option A: Generate migration to drop tables +pnpm run db:generate:named "remove_todos" + +# Then manually edit the migration to drop tables: +# DROP TABLE IF EXISTS todos; +# DROP TABLE IF EXISTS categories; + +# Option B: Reset local database entirely +pnpm run db:reset:local +``` + +**Step 5: Test** +```bash +pnpm run dev +# Verify app works without the module +``` + +--- + +## How to Add a New Module + +### Example: Adding an "Invoices" Module + +**Step 1: Create module folder** +```bash +mkdir -p src/modules/invoices/{actions,components,models,schemas} +``` + +**Step 2: Create database schema** + +Create `src/modules/invoices/schemas/invoice.schema.ts`: +```typescript +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import { z } from "zod"; + +export const invoices = sqliteTable("invoices", { + id: text("id").primaryKey(), + userId: text("user_id").notNull(), + amount: integer("amount").notNull(), + status: text("status").notNull(), // 'draft', 'sent', 'paid' + createdAt: text("created_at").notNull(), + updatedAt: text("updated_at").notNull(), +}); + +export const insertInvoiceSchema = createInsertSchema(invoices); +export const selectInvoiceSchema = createSelectSchema(invoices); +``` + +**Step 3: Export schema** + +Add to `src/db/schema.ts`: +```typescript +export { invoices } from "@/modules/invoices/schemas/invoice.schema"; +``` + +**Step 4: Generate migration** +```bash +pnpm run db:generate:named "add_invoices" +pnpm run db:migrate:local +``` + +**Step 5: Create server actions** + +Create `src/modules/invoices/actions/get-invoices.action.ts`: +```typescript +"use server"; + +import { db } from "@/db"; +import { invoices } from "@/db/schema"; +import { auth } from "@/modules/auth/utils/auth-utils"; +import { eq } from "drizzle-orm"; + +export async function getInvoices() { + const session = await auth(); + + if (!session?.user) { + return { success: false, error: "Unauthorized", data: null }; + } + + try { + const userInvoices = await db + .select() + .from(invoices) + .where(eq(invoices.userId, session.user.id)); + + return { success: true, data: userInvoices, error: null }; + } catch (error) { + console.error("Error fetching invoices:", error); + return { success: false, error: "Failed to fetch invoices", data: null }; + } +} +``` + +**Step 6: Create components** + +Create `src/modules/invoices/components/invoice-list.tsx`: +```typescript +import { getInvoices } from "../actions/get-invoices.action"; + +export async function InvoiceList() { + const { data: invoices, error } = await getInvoices(); + + if (error) { + return
Error: {error}
; + } + + return ( +
+ {invoices?.map((invoice) => ( +
+ Invoice #{invoice.id} - ${invoice.amount / 100} +
+ ))} +
+ ); +} +``` + +**Step 7: Create routes** + +Create `src/app/dashboard/invoices/page.tsx`: +```typescript +import { InvoiceList } from "@/modules/invoices/components/invoice-list"; + +export default function InvoicesPage() { + return ( +
+

Invoices

+ +
+ ); +} +``` + +**Step 8: Test** +```bash +pnpm run dev +# Visit http://localhost:3000/dashboard/invoices +``` + +--- + +## Module Best Practices + +### ✅ DO: + +1. **Keep modules self-contained** + - All module code stays in `/src/modules/[name]/` + - Minimize dependencies on other modules (except auth) + +2. **Follow consistent structure** + - Use the same folder pattern: actions/, components/, schemas/, models/ + - Name files descriptively: `get-todos.action.ts`, `todo-card.tsx` + +3. **Export schemas properly** + - Add to `src/db/schema.ts` for Drizzle to detect + - Use `createInsertSchema` and `createSelectSchema` for type safety + +4. **Use Server Actions** + - All mutations through Server Actions (not API routes) + - Add `"use server"` directive at top of action files + +5. **Validate with Zod** + - Create schemas for all input data + - Use `zodResolver` in forms + +6. **Handle authentication** + - Check `auth()` in all server actions + - Return proper error responses for unauthorized access + +### ❌ DON'T: + +1. **Don't create circular dependencies** + - Modules shouldn't depend on each other (except auth) + - Extract shared logic to `/src/lib/` or `/src/services/` + +2. **Don't bypass the module structure** + - Keep all feature code in the module folder + - Don't scatter components across multiple directories + +3. **Don't skip database migrations** + - Always generate migrations for schema changes + - Test migrations locally before production + +4. **Don't hardcode user IDs** + - Always use `session.user.id` from `auth()` + - Filter database queries by user ID + +5. **Don't expose internal module details** + - Export only what's needed + - Keep implementation details private + +--- + +## Module Dependencies + +``` +┌─────────────┐ +│ auth │ ← Required by all modules +└─────────────┘ + ↑ + │ +┌──────┴──────┐ +│ dashboard │ ← Layout for protected pages +└─────────────┘ + ↑ + │ +┌──────┴──────┐ +│ todos │ ← Example feature module +└─────────────┘ +``` + +**Rule:** Modules can depend on `auth`, but not on each other. + +If two modules need shared logic, extract it to: +- `/src/lib/` - Shared utilities +- `/src/services/` - Business logic services +- `/src/components/` - Shared UI components + +--- + +## Database Integration + +### Schema Location + +Database schemas live in modules but are exported centrally: + +```typescript +// src/modules/todos/schemas/todo.schema.ts +export const todos = sqliteTable("todos", { ... }); + +// src/db/schema.ts (central export) +export { todos } from "@/modules/todos/schemas/todo.schema"; +``` + +This allows: +- ✅ Drizzle to generate migrations from all schemas +- ✅ Modules to import only what they need +- ✅ Type safety across the entire app + +### Migration Workflow + +**When adding a module with database tables:** +```bash +# 1. Create schema in module +# 2. Export from src/db/schema.ts +# 3. Generate migration +pnpm run db:generate:named "add_invoices_table" + +# 4. Apply locally +pnpm run db:migrate:local + +# 5. Verify +pnpm run db:inspect:local + +# 6. Commit migration file +git add src/drizzle/*.sql +git commit -m "feat: add invoices table" +``` + +**When removing a module with database tables:** +```bash +# 1. Generate drop table migration +pnpm run db:generate:named "remove_invoices_table" + +# 2. Manually edit migration to drop tables +# In src/drizzle/XXXX_remove_invoices_table.sql: +# DROP TABLE IF EXISTS invoices; + +# 3. Apply locally +pnpm run db:migrate:local + +# Or reset entirely: +pnpm run db:reset:local +``` + +--- + +## Type Safety + +### Automatic Type Generation + +Database types are automatically generated from schemas: + +```typescript +// src/modules/todos/schemas/todo.schema.ts +export const insertTodoSchema = createInsertSchema(todos); +export const selectTodoSchema = createSelectSchema(todos); + +// Usage in actions: +import { insertTodoSchema } from "../schemas/todo.schema"; + +export async function createTodo(data: z.infer) { + // Type-safe input +} +``` + +### Cloudflare Bindings + +After any `wrangler.jsonc` changes: +```bash +pnpm run cf-typegen +``` + +This generates types for: +- D1 database bindings +- R2 bucket bindings +- KV namespace bindings +- Workers AI bindings + +--- + +## Testing a Module + +### Manual Testing Checklist + +When creating a new module, test: + +- [ ] **Server Actions work** + - Can create/read/update/delete items + - Authentication is enforced + - Error handling works + +- [ ] **UI renders correctly** + - Components display data + - Forms submit properly + - Loading states work + +- [ ] **Database integration** + - Migrations applied successfully + - Queries return expected data + - User isolation works (users see only their data) + +- [ ] **Type safety** + - No TypeScript errors + - Form validation works + - Zod schemas validate input + +### Example Test Flow (Invoices Module) + +```bash +# 1. Start dev servers +pnpm run wrangler:dev # Terminal 1 +pnpm run dev # Terminal 2 + +# 2. Login at http://localhost:3000 +# 3. Navigate to /dashboard/invoices +# 4. Create an invoice +# 5. Verify it appears in the list +# 6. Edit the invoice +# 7. Delete the invoice + +# 8. Check database +pnpm run db:studio:local +# Verify invoice was created/updated/deleted +``` + +--- + +## Reusing Modules Across Projects + +### Option 1: Fork This Repository + +**Best for:** Starting new projects from scratch + +```bash +# 1. Fork this repo +git clone https://github.com/your-username/fullstack-next-cloudflare.git my-new-app +cd my-new-app + +# 2. Remove unwanted modules (e.g., todos) +rm -rf src/modules/todos +# Edit src/db/schema.ts to remove todo exports +# Delete src/app/dashboard/todos/ + +# 3. Add your own modules +mkdir -p src/modules/invoices +# Follow "How to Add a New Module" guide + +# 4. Build your app! +``` + +--- + +### Option 2: Copy Module to Existing Project + +**Best for:** Adding a feature to an existing Next.js + Cloudflare app + +```bash +# 1. Copy module folder +cp -r /path/to/this-repo/src/modules/todos /path/to/your-project/src/modules/ + +# 2. Copy schema +# From: src/modules/todos/schemas/*.schema.ts +# To: your-project/src/modules/todos/schemas/ + +# 3. Export schema in your src/db/schema.ts +export { todos } from "@/modules/todos/schemas/todo.schema"; +export { categories } from "@/modules/todos/schemas/category.schema"; + +# 4. Generate migration +cd /path/to/your-project +pnpm run db:generate:named "add_todos" +pnpm run db:migrate:local + +# 5. Copy routes +cp -r /path/to/this-repo/src/app/dashboard/todos /path/to/your-project/src/app/dashboard/ + +# 6. Test +pnpm run dev +``` + +--- + +## Common Issues & Solutions + +### Issue: "Module not found" errors + +**Cause:** TypeScript can't resolve module imports + +**Solution:** +```bash +# Rebuild the app +rm -rf .next +pnpm run build:cf +``` + +--- + +### Issue: Database table doesn't exist + +**Cause:** Migrations weren't applied + +**Solution:** +```bash +# Check migration status +pnpm run db:inspect:local + +# Apply migrations +pnpm run db:migrate:local + +# If stuck, reset: +pnpm run db:reset:local +``` + +--- + +### Issue: "User is not authenticated" errors + +**Cause:** Session not passed to server action + +**Solution:** +- Ensure you're calling `auth()` in server actions +- Check that Better Auth is configured correctly +- Verify session cookies in DevTools + +--- + +### Issue: Type errors after removing a module + +**Cause:** Stale imports referencing deleted module + +**Solution:** +```bash +# Search for remaining imports +grep -r "from.*todos" src/ + +# Remove all references to the deleted module +# Rebuild +pnpm run build:cf +``` + +--- + +## Architecture Comparison + +### This Approach (Feature Modules) + +``` +✅ Simple to understand +✅ Easy to copy/paste modules +✅ Low maintenance overhead +✅ Works for small-medium apps +✅ No special tooling required +``` + +### Plugin System Approach + +``` +❌ 50-100+ hours to build +❌ High maintenance burden +❌ Overkill for most projects +✅ Good for CMSes or marketplaces +✅ Dynamic loading at runtime +``` + +**Recommendation:** Stick with feature modules unless you're building a CMS or marketplace platform. + +--- + +## Examples from Other Projects + +**Similar architecture in production:** + +- **T3 Stack** - Feature-based modules, fork-and-customize +- **Next.js Boilerplate** - Module organization, delete what you don't need +- **Supermemory.ai** - This template's inspiration, modular Cloudflare stack + +--- + +## Module Template + +See `MODULE_TEMPLATE.md` for a complete step-by-step guide to creating a new module from scratch. + +--- + +## Questions or Issues? + +If you have questions about the module system or run into issues: + +1. Check this guide for common solutions +2. Review the `todos` module as a reference implementation +3. See `MODULE_TEMPLATE.md` for detailed examples +4. Open an issue in the GitHub repository + +--- + +**Happy building!** 🚀 diff --git a/MODULE_TEMPLATE.md b/MODULE_TEMPLATE.md new file mode 100644 index 0000000..52e80cb --- /dev/null +++ b/MODULE_TEMPLATE.md @@ -0,0 +1,1058 @@ +# Module Template Guide + +**Version:** 1.0.0 +**Purpose:** Step-by-step guide for creating a new feature module + +--- + +## Overview + +This guide walks you through creating a complete feature module from scratch using the established patterns in this project. + +**Example:** We'll build an "Invoices" module to demonstrate all the concepts. + +**Time estimate:** 30-60 minutes for your first module, 15-30 minutes once familiar + +--- + +## Before You Start + +**Prerequisites:** +- ✅ Project is set up and running locally +- ✅ You understand the basics of Next.js App Router +- ✅ You've reviewed the `todos` module as a reference +- ✅ Database migrations are working + +**Recommended:** Review [MODULES.md](./MODULES.md) first for architecture overview. + +--- + +## Step 1: Plan Your Module + +### Define the Module + +Before writing code, answer these questions: + +**What is the feature?** +- Example: "Invoice management system" + +**What data does it store?** +- Example: Invoice with amount, status, due date, customer info + +**What actions can users take?** +- Example: Create invoice, view invoices, update invoice, delete invoice, send invoice + +**Does it depend on other modules?** +- Almost always depends on: `auth` (for user context) +- Avoid depending on: other feature modules + +**What routes does it need?** +- Example: + - `/dashboard/invoices` - List all invoices + - `/dashboard/invoices/new` - Create new invoice + - `/dashboard/invoices/[id]` - View/edit invoice + +--- + +## Step 2: Create Module Structure + +### Create Folders + +```bash +# Create the module directory structure +mkdir -p src/modules/invoices/{actions,components,models,schemas,utils} +``` + +**Result:** +```text +src/modules/invoices/ +├── actions/ ← Server actions (create, read, update, delete) +├── components/ ← React components +├── models/ ← Database query helpers (optional) +├── schemas/ ← Zod schemas and database tables +└── utils/ ← Helper functions (optional) +``` + +--- + +## Step 3: Define Database Schema + +### Create Schema File + +Create `src/modules/invoices/schemas/invoice.schema.ts`: + +```typescript +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import { z } from "zod"; + +/** + * Invoice table schema + */ +export const invoices = sqliteTable("invoices", { + // Primary key + id: text("id").primaryKey(), + + // Foreign key to user + userId: text("user_id").notNull(), + + // Invoice data + invoiceNumber: text("invoice_number").notNull(), + customerName: text("customer_name").notNull(), + customerEmail: text("customer_email"), + amount: integer("amount").notNull(), // Amount in cents + status: text("status").notNull(), // 'draft', 'sent', 'paid', 'overdue' + dueDate: text("due_date").notNull(), + + // Metadata + createdAt: text("created_at").notNull(), + updatedAt: text("updated_at").notNull(), +}); + +/** + * Zod schemas for validation + */ +export const insertInvoiceSchema = createInsertSchema(invoices, { + invoiceNumber: z.string().min(1, "Invoice number is required"), + customerName: z.string().min(1, "Customer name is required"), + customerEmail: z.string().email("Invalid email").optional(), + amount: z.number().positive("Amount must be positive"), + status: z.enum(["draft", "sent", "paid", "overdue"]), + dueDate: z.string().min(1, "Due date is required"), +}); + +export const selectInvoiceSchema = createSelectSchema(invoices); + +/** + * TypeScript types + */ +export type Invoice = z.infer; +export type InsertInvoice = z.infer; +``` + +**Key points:** +- Use `text()` for strings and IDs (D1 is SQLite) +- Use `integer()` for numbers (store money in cents to avoid floating point issues) +- Add `.notNull()` for required fields +- Create Zod schemas with `createInsertSchema` and `createSelectSchema` +- Add custom validation in Zod schema if needed + +--- + +### Export Schema Centrally + +Edit `src/db/schema.ts` and add: + +```typescript +export { invoices } from "@/modules/invoices/schemas/invoice.schema"; +``` + +**Full file should look like:** +```typescript +export { + account, + session, + user, + verification, +} from "@/modules/auth/schemas/auth.schema"; +export { categories } from "@/modules/todos/schemas/category.schema"; +export { todos } from "@/modules/todos/schemas/todo.schema"; +export { invoices } from "@/modules/invoices/schemas/invoice.schema"; // ← Add this +``` + +--- + +### Generate and Apply Migration + +```bash +# Generate migration from schema changes +pnpm run db:generate:named "add_invoices_table" + +# Apply migration to local database +pnpm run db:migrate:local + +# Verify table was created +pnpm run db:inspect:local +# Should show "invoices" in the table list +``` + +--- + +## Step 4: Create Server Actions + +### Action 1: Get All Invoices + +Create `src/modules/invoices/actions/get-invoices.action.ts`: + +```typescript +"use server"; + +import { db } from "@/db"; +import { invoices } from "@/db/schema"; +import { auth } from "@/modules/auth/utils/auth-utils"; +import { desc, eq } from "drizzle-orm"; + +export async function getInvoices() { + // 1. Check authentication + const session = await auth(); + + if (!session?.user) { + return { success: false, error: "Unauthorized", data: null }; + } + + try { + // 2. Query database (filtered by user ID) + const userInvoices = await db + .select() + .from(invoices) + .where(eq(invoices.userId, session.user.id)) + .orderBy(desc(invoices.createdAt)); + + // 3. Return success response + return { success: true, data: userInvoices, error: null }; + } catch (error) { + console.error("Error fetching invoices:", error); + return { success: false, error: "Failed to fetch invoices", data: null }; + } +} +``` + +--- + +### Action 2: Create Invoice + +Create `src/modules/invoices/actions/create-invoice.action.ts`: + +```typescript +"use server"; + +import { db } from "@/db"; +import { invoices } from "@/db/schema"; +import { auth } from "@/modules/auth/utils/auth-utils"; +import { insertInvoiceSchema } from "../schemas/invoice.schema"; +import { revalidatePath } from "next/cache"; + +export async function createInvoice(data: unknown) { + // 1. Check authentication + const session = await auth(); + + if (!session?.user) { + return { success: false, error: "Unauthorized", data: null }; + } + + try { + // 2. Validate input + const validatedData = insertInvoiceSchema.parse(data); + + // 3. Generate ID and timestamps + const now = new Date().toISOString(); + const invoice = { + ...validatedData, + id: crypto.randomUUID(), + userId: session.user.id, + createdAt: now, + updatedAt: now, + }; + + // 4. Insert into database + await db.insert(invoices).values(invoice); + + // 5. Revalidate the invoices page + revalidatePath("/dashboard/invoices"); + + // 6. Return success + return { success: true, data: invoice, error: null }; + } catch (error) { + console.error("Error creating invoice:", error); + + // Handle Zod validation errors + if (error instanceof Error && error.name === "ZodError") { + return { success: false, error: "Invalid invoice data", data: null }; + } + + return { success: false, error: "Failed to create invoice", data: null }; + } +} +``` + +--- + +### Action 3: Update Invoice + +Create `src/modules/invoices/actions/update-invoice.action.ts`: + +```typescript +"use server"; + +import { db } from "@/db"; +import { invoices } from "@/db/schema"; +import { auth } from "@/modules/auth/utils/auth-utils"; +import { and, eq } from "drizzle-orm"; +import { insertInvoiceSchema } from "../schemas/invoice.schema"; +import { revalidatePath } from "next/cache"; + +export async function updateInvoice(id: string, data: unknown) { + const session = await auth(); + + if (!session?.user) { + return { success: false, error: "Unauthorized", data: null }; + } + + try { + // Validate input + const validatedData = insertInvoiceSchema.partial().parse(data); + + // Update only if invoice belongs to user + await db + .update(invoices) + .set({ + ...validatedData, + updatedAt: new Date().toISOString(), + }) + .where( + and( + eq(invoices.id, id), + eq(invoices.userId, session.user.id) + ) + ); + + revalidatePath("/dashboard/invoices"); + revalidatePath(`/dashboard/invoices/${id}`); + + return { success: true, data: { id }, error: null }; + } catch (error) { + console.error("Error updating invoice:", error); + return { success: false, error: "Failed to update invoice", data: null }; + } +} +``` + +--- + +### Action 4: Delete Invoice + +Create `src/modules/invoices/actions/delete-invoice.action.ts`: + +```typescript +"use server"; + +import { db } from "@/db"; +import { invoices } from "@/db/schema"; +import { auth } from "@/modules/auth/utils/auth-utils"; +import { and, eq } from "drizzle-orm"; +import { revalidatePath } from "next/cache"; + +export async function deleteInvoice(id: string) { + const session = await auth(); + + if (!session?.user) { + return { success: false, error: "Unauthorized" }; + } + + try { + // Delete only if invoice belongs to user + await db + .delete(invoices) + .where( + and( + eq(invoices.id, id), + eq(invoices.userId, session.user.id) + ) + ); + + revalidatePath("/dashboard/invoices"); + + return { success: true, error: null }; + } catch (error) { + console.error("Error deleting invoice:", error); + return { success: false, error: "Failed to delete invoice" }; + } +} +``` + +--- + +### Action 5: Get Single Invoice + +Create `src/modules/invoices/actions/get-invoice-by-id.action.ts`: + +```typescript +"use server"; + +import { db } from "@/db"; +import { invoices } from "@/db/schema"; +import { auth } from "@/modules/auth/utils/auth-utils"; +import { and, eq } from "drizzle-orm"; + +export async function getInvoiceById(id: string) { + const session = await auth(); + + if (!session?.user) { + return { success: false, error: "Unauthorized", data: null }; + } + + try { + const invoice = await db + .select() + .from(invoices) + .where( + and( + eq(invoices.id, id), + eq(invoices.userId, session.user.id) + ) + ) + .limit(1); + + if (!invoice || invoice.length === 0) { + return { success: false, error: "Invoice not found", data: null }; + } + + return { success: true, data: invoice[0], error: null }; + } catch (error) { + console.error("Error fetching invoice:", error); + return { success: false, error: "Failed to fetch invoice", data: null }; + } +} +``` + +--- + +## Step 5: Create Components + +### Component 1: Invoice List + +Create `src/modules/invoices/components/invoice-list.tsx`: + +```typescript +import { getInvoices } from "../actions/get-invoices.action"; +import { InvoiceCard } from "./invoice-card"; + +export async function InvoiceList() { + const { data: invoices, error } = await getInvoices(); + + if (error) { + return ( +
+ Error loading invoices: {error} +
+ ); + } + + if (!invoices || invoices.length === 0) { + return ( +
+ No invoices yet. Create your first invoice! +
+ ); + } + + return ( +
+ {invoices.map((invoice) => ( + + ))} +
+ ); +} +``` + +--- + +### Component 2: Invoice Card + +Create `src/modules/invoices/components/invoice-card.tsx`: + +```typescript +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; +import type { Invoice } from "../schemas/invoice.schema"; +import { DeleteInvoice } from "./delete-invoice"; + +interface InvoiceCardProps { + invoice: Invoice; +} + +export function InvoiceCard({ invoice }: InvoiceCardProps) { + const statusColors = { + draft: "bg-secondary", + sent: "bg-blue-500", + paid: "bg-green-500", + overdue: "bg-destructive", + }; + + return ( + +
+
+
+

+ Invoice #{invoice.invoiceNumber} +

+ + {invoice.status} + +
+

+ {invoice.customerName} +

+

+ ${(invoice.amount / 100).toFixed(2)} +

+

+ Due: {new Date(invoice.dueDate).toLocaleDateString()} +

+
+ +
+ + + + +
+
+
+ ); +} +``` + +--- + +### Component 3: Invoice Form + +Create `src/modules/invoices/components/invoice-form.tsx`: + +```typescript +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { useRouter } from "next/navigation"; +import { useState, useTransition } from "react"; +import toast from "react-hot-toast"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { createInvoice } from "../actions/create-invoice.action"; +import { updateInvoice } from "../actions/update-invoice.action"; +import { insertInvoiceSchema, type Invoice } from "../schemas/invoice.schema"; + +interface InvoiceFormProps { + invoice?: Invoice; + mode: "create" | "edit"; +} + +export function InvoiceForm({ invoice, mode }: InvoiceFormProps) { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + + const form = useForm({ + resolver: zodResolver(insertInvoiceSchema), + defaultValues: { + invoiceNumber: invoice?.invoiceNumber || "", + customerName: invoice?.customerName || "", + customerEmail: invoice?.customerEmail || "", + amount: invoice?.amount || 0, + status: invoice?.status || "draft", + dueDate: invoice?.dueDate || "", + }, + }); + + const onSubmit = (data: any) => { + startTransition(async () => { + const result = + mode === "create" + ? await createInvoice(data) + : await updateInvoice(invoice!.id, data); + + if (!result.success) { + toast.error(result.error || "Operation failed"); + return; + } + + toast.success( + mode === "create" + ? "Invoice created!" + : "Invoice updated!" + ); + router.push("/dashboard/invoices"); + }); + }; + + return ( +
+ + ( + + Invoice Number + + + + + + )} + /> + + ( + + Customer Name + + + + + + )} + /> + + ( + + Customer Email (Optional) + + + + + + )} + /> + + ( + + Amount (cents) + + + field.onChange(Number(e.target.value)) + } + /> + + + + )} + /> + + ( + + Status + + + + )} + /> + + ( + + Due Date + + + + + + )} + /> + +
+ + +
+ + + ); +} +``` + +--- + +### Component 4: Delete Invoice Button + +Create `src/modules/invoices/components/delete-invoice.tsx`: + +```typescript +"use client"; + +import { useState, useTransition } from "react"; +import { useRouter } from "next/navigation"; +import toast from "react-hot-toast"; +import { Trash2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { deleteInvoice } from "../actions/delete-invoice.action"; + +interface DeleteInvoiceProps { + invoiceId: string; +} + +export function DeleteInvoice({ invoiceId }: DeleteInvoiceProps) { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [isPending, startTransition] = useTransition(); + + const handleDelete = () => { + startTransition(async () => { + const result = await deleteInvoice(invoiceId); + + if (!result.success) { + toast.error(result.error || "Failed to delete invoice"); + return; + } + + toast.success("Invoice deleted"); + setOpen(false); + router.refresh(); + }); + }; + + return ( + + + + + + + Delete Invoice + + Are you sure? This action cannot be undone. + + + + + Cancel + + + {isPending ? "Deleting..." : "Delete"} + + + + + ); +} +``` + +--- + +## Step 6: Create Routes + +### Route 1: Invoice List Page + +Create `src/app/dashboard/invoices/page.tsx`: + +```typescript +import { Button } from "@/components/ui/button"; +import { InvoiceList } from "@/modules/invoices/components/invoice-list"; +import { Plus } from "lucide-react"; +import Link from "next/link"; + +export default function InvoicesPage() { + return ( +
+
+

Invoices

+ + + +
+ + +
+ ); +} +``` + +--- + +### Route 2: Create Invoice Page + +Create `src/app/dashboard/invoices/new/page.tsx`: + +```typescript +import { InvoiceForm } from "@/modules/invoices/components/invoice-form"; + +export default function NewInvoicePage() { + return ( +
+

Create Invoice

+ +
+ ); +} +``` + +--- + +### Route 3: Edit Invoice Page + +Create `src/app/dashboard/invoices/[id]/page.tsx`: + +```typescript +import { notFound } from "next/navigation"; +import { getInvoiceById } from "@/modules/invoices/actions/get-invoice-by-id.action"; +import { InvoiceForm } from "@/modules/invoices/components/invoice-form"; + +interface EditInvoicePageProps { + params: Promise<{ id: string }>; +} + +export default async function EditInvoicePage({ params }: EditInvoicePageProps) { + const { id } = await params; + const { data: invoice, error } = await getInvoiceById(id); + + if (error || !invoice) { + notFound(); + } + + return ( +
+

Edit Invoice

+ +
+ ); +} +``` + +--- + +## Step 7: Test Your Module + +### Start Dev Servers + +```bash +# Terminal 1 +pnpm run wrangler:dev + +# Terminal 2 +pnpm run dev +``` + +### Manual Testing Checklist + +- [ ] Visit `http://localhost:3000/dashboard/invoices` +- [ ] Click "New Invoice" button +- [ ] Fill out the form and create an invoice +- [ ] Verify invoice appears in the list +- [ ] Click "Edit" on an invoice +- [ ] Update the invoice details +- [ ] Verify changes are saved +- [ ] Click delete button +- [ ] Confirm deletion works +- [ ] Check database: `pnpm run db:studio:local` +- [ ] Verify data isolation (create second user, ensure they see only their invoices) + +--- + +## Common Patterns + +### Pattern: Optimistic UI Updates + +For better UX, you can update the UI before the server responds: + +```typescript +"use client"; + +import { useOptimistic } from "react"; + +export function InvoiceList({ initialInvoices }) { + const [optimisticInvoices, addOptimisticInvoice] = useOptimistic( + initialInvoices, + (state, newInvoice) => [...state, newInvoice] + ); + + // Use optimisticInvoices in your JSX +} +``` + +--- + +### Pattern: Loading States + +Show skeletons while data loads: + +```typescript +import { Suspense } from "react"; +import { InvoiceListSkeleton } from "./invoice-list-skeleton"; + +export default function InvoicesPage() { + return ( + }> + + + ); +} +``` + +--- + +### Pattern: Error Boundaries + +Catch errors gracefully: + +Create `src/app/dashboard/invoices/error.tsx`: + +```typescript +"use client"; + +import { Button } from "@/components/ui/button"; + +export default function InvoicesError({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + return ( +
+

Something went wrong!

+

{error.message}

+ +
+ ); +} +``` + +--- + +## Deployment Checklist + +Before deploying your new module: + +- [ ] All server actions have authentication checks +- [ ] Database queries filter by `userId` +- [ ] Migrations have been tested locally +- [ ] Forms have proper validation +- [ ] Error handling is in place +- [ ] Loading states exist +- [ ] Delete actions have confirmation dialogs +- [ ] No sensitive data exposed in client components +- [ ] TypeScript has no errors (`pnpm run build`) + +--- + +## Next Steps + +**You now have a complete CRUD module!** + +**Optional enhancements:** +- Add search/filter functionality +- Implement pagination +- Add sorting options +- Create export-to-PDF feature +- Add email notifications +- Implement recurring invoices + +**Copy this pattern for other modules:** +- Projects +- Clients +- Products +- Orders +- etc. + +--- + +## Reference + +**See existing modules for examples:** +- `src/modules/todos` - Full CRUD example +- `src/modules/auth` - Authentication patterns +- `src/modules/dashboard` - Layout patterns + +**Documentation:** +- [MODULES.md](./MODULES.md) - Module system overview +- [README.md](./README.md) - Project setup +- [Next.js Docs](https://nextjs.org/docs) - Framework reference +- [Drizzle ORM](https://orm.drizzle.team/docs/overview) - Database patterns + +--- + +**You're all set!** Follow this template for any new feature module you want to add. 🚀 diff --git a/README.md b/README.md index 333f7ab..c9481de 100644 --- a/README.md +++ b/README.md @@ -1,729 +1,357 @@ -![Banner](banner.svg) +# 🔥 Full Flare Stack -# ⚡ Full-Stack Next.js + Cloudflare Template +**Production-ready Next.js + Cloudflare Workers starter with 43 shadcn components, three-layer architecture, and D1/R2/AI integration.** -A production-ready template for building full-stack applications with Next.js 15 and Cloudflare's powerful edge infrastructure. Perfect for MVPs with generous free tiers and seamless scaling to enterprise-level applications. +[![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) +[![Next.js 15](https://img.shields.io/badge/Next.js-15-black)](https://nextjs.org/) +[![Cloudflare Workers](https://img.shields.io/badge/Cloudflare-Workers-orange)](https://workers.cloudflare.com/) +[![shadcn/ui](https://img.shields.io/badge/shadcn%2Fui-43_components-blue)](https://ui.shadcn.com/) -**Inspired by the [Cloudflare SaaS Stack](https://github.com/supermemoryai/cloudflare-saas-stack)** - the same stack powering [Supermemory.ai](https://git.new/memory), which serves 20k+ users on just $5/month. This template modernizes that approach with Cloudflare Workers (vs Pages), includes comprehensive D1 and R2 examples, and provides a complete development workflow. +> A [Jezweb](https://jezweb.com.au) Open Source Project +> Maintained by [Jez Dawes](https://github.com/jezweb) • jeremy@jezweb.net -You can read detail explanations and code architecture of this template from Devin AI on [Deepwiki](https://deepwiki.com/ifindev/fullstack-next-cloudflare). - -Don't forget to leave a star if you find this helpful ⭐️ - - -## 🌟 Why Cloudflare + Next.js? - -**Cloudflare's Edge Network** provides unparalleled performance and reliability: -- ⚡ **Ultra-low latency** - Deploy to 300+ locations worldwide -- 💰 **Generous free tiers** - Perfect for MVPs and side projects -- 📈 **Effortless scaling** - From zero to millions of users automatically -- 🔒 **Built-in security** - DDoS protection, WAF, and more -- 🌍 **Global by default** - Your app runs close to every user - -Combined with **Next.js 15**, you get modern React features, Server Components, and Server Actions for optimal performance and developer experience. - -## 🛠️ Tech Stack - -### 🎯 **Frontend** -- ⚛️ **Next.js 15** - App Router with React Server Components (RSC) -- 🎨 **TailwindCSS 4** - Utility-first CSS framework -- 📘 **TypeScript** - Full type safety throughout -- 🧩 **Shadcn UI** - Unstyled, accessible components -- 📋 **React Hook Form + Zod** - Type-safe form handling - -### ☁️ **Backend & Infrastructure** -- 🌐 **Cloudflare Workers** - Serverless edge compute platform -- 🗃️ **Cloudflare D1** - Distributed SQLite database at the edge -- 📦 **Cloudflare R2** - S3-compatible object storage -- 🤖 **Cloudflare Workers AI** - Edge AI inference with OpenSource models -- 🔑 **Better Auth** - Modern authentication with Google OAuth -- 🛠️ **Drizzle ORM** - TypeScript-first database toolkit - -### 🚀 **DevOps & Deployment** -- ⚙️ **GitHub Actions** - Automated CI/CD pipeline -- 🔧 **Wrangler** - Cloudflare's CLI tool -- 👁️ **Preview Deployments** - Test changes before production -- 🔄 **Database Migrations** - Version-controlled schema changes -- 💾 **Automated Backups** - Production database safety - -### 📊 **Data Flow Architecture** -- **Fetching**: Server Actions + React Server Components for optimal performance -- **Mutations**: Server Actions with automatic revalidation -- **AI Processing**: Edge AI inference with Cloudflare Workers AI -- **Type Safety**: End-to-end TypeScript from database to UI -- **Caching**: Built-in Next.js caching with Cloudflare edge caching - -## 🏗️ Project Structure - -This template uses a **feature-based/module-sliced architecture** for better maintainability and scalability: - -``` -src/ -├── app/ # Next.js App Router -│ ├── (auth)/ # Auth-related pages -│ ├── api/ # API routes (for external access) -│ │ └── summarize/ # AI summarization endpoint -│ ├── dashboard/ # Dashboard pages -│ └── globals.css # Global styles -├── components/ # Shared UI components -├── constants/ # App constants -├── db/ # Database configuration -│ ├── index.ts # DB connection -│ └── schema.ts # Database schemas -├── lib/ # Shared utilities -├── modules/ # Feature modules -│ ├── auth/ # Authentication module -│ │ ├── actions/ # Auth server actions -│ │ ├── components/ # Auth components -│ │ ├── hooks/ # Auth hooks -│ │ ├── models/ # Auth models -│ │ ├── schemas/ # Auth schemas -│ │ └── utils/ # Auth utilities -│ ├── dashboard/ # Dashboard module -│ └── todos/ # Todo module -│ ├── actions/ # Todo server actions -│ ├── components/ # Todo components -│ ├── models/ # Todo models -│ └── schemas/ # Todo schemas -├── services/ # Business logic services -│ └── summarizer.service.ts # AI summarization service -└── drizzle/ # Database migrations -``` - -**Key Architecture Benefits:** -- **Feature Isolation** - Each module contains its own actions, components, and logic -- **Server Actions** - Modern data mutations with automatic revalidation -- **React Server Components** - Optimal performance with server-side rendering -- **Type Safety** - End-to-end TypeScript from database to UI -- **Testable** - Clear separation of concerns makes testing easier - -## 🚀 Getting Started - -### 1. Prerequisites +--- -- **Cloudflare Account** - [Sign up for free](https://dash.cloudflare.com/sign-up) -- **Node.js 20+** and **pnpm** installed -- **Google OAuth App** - For authentication setup +## 🚀 What is Full Flare Stack? -### 2. Create Cloudflare API Token +Full Flare Stack is a **modular, production-ready starter kit** for building full-stack applications on Cloudflare's edge infrastructure. It combines Next.js 15 with Cloudflare Workers, D1, R2, and Workers AI to deliver ultra-fast, globally distributed applications that scale effortlessly. -Create an API token for Wrangler authentication: +**Built for:** +- 🎯 **MVPs & Side Projects** - Generous free tiers, deploy in minutes +- 💼 **SaaS Applications** - Multi-tenancy ready, enterprise-scale performance +- 🏢 **Business Tools** - CRMs, dashboards, admin panels with 5 production layouts +- 🤖 **AI-Powered Apps** - Edge AI inference with Workers AI -1. In the Cloudflare dashboard, go to the **Account API tokens** page -2. Select **Create Token** > find **Edit Cloudflare Workers** > select **Use Template** -3. Customize your token name (e.g., "Next.js Cloudflare Template") -4. Scope your token to your account and zones (if using custom domains) -5. **Add additional permissions** for D1 database and AI access: - - Account - D1:Edit - - Account - D1:Read - - Account - Cloudflare Workers AI:Read +--- -**Final Token Permissions:** -- All permissions from "Edit Cloudflare Workers" template -- Account - D1:Edit (for database operations) -- Account - D1:Read (for database queries) -- Account - Cloudflare Workers AI:Read (for AI inference) +## ✨ What Makes It Different? -### 3. Clone and Setup +### **1. Three-Layer Component Architecture** -```bash -# Clone the repository -git clone https://github.com/ifindev/fullstack-next-cloudflare.git -cd fullstack-next-cloudflare +Full Flare Stack pioneered a clean separation of concerns: -# Install dependencies -pnpm install ``` +Layer 1: UI Primitives (/components/ui/) +↓ 43 shadcn/ui components (foundation complete) -### 4. Environment Configuration +Layer 2: Composed Patterns (/components/composed/) +↓ Reusable patterns (DataTable, forms, etc.) - build as you need -Create your environment file: - -```bash -# Copy example environment file -cp .dev.vars.example .dev.vars +Layer 3: Feature Modules (/modules/[feature]/) +↓ Business logic, Server Actions, database access ``` -Edit `.dev.vars` with your credentials: - -```bash -# Cloudflare Configuration -CLOUDFLARE_ACCOUNT_ID=your-account-id -CLOUDFLARE_D1_TOKEN=your-api-token - -# Authentication Secrets -BETTER_AUTH_SECRET=your-random-secret-here -GOOGLE_CLIENT_ID=your-google-client-id -GOOGLE_CLIENT_SECRET=your-google-client-secret +**Why it matters:** +- ✅ Know exactly where every component belongs +- ✅ Extract patterns after 3rd use (not speculatively) +- ✅ Reusable across projects +- ✅ Clear boundaries prevent technical debt -# Storage -CLOUDFLARE_R2_URL=your-r2-bucket-url -``` +**See:** [Component Architecture Docs](./docs/development-planning/) -### 5. Authentication Setup +--- -**Better Auth Secret:** -```bash -# Generate a random secret -openssl rand -base64 32 -# Add to BETTER_AUTH_SECRET in .dev.vars -``` +### **2. 43 shadcn/ui Components Pre-Installed** -**Google OAuth Setup:** -Follow the [Better Auth Google documentation](https://www.better-auth.com/docs/authentication/google) to: -1. Create a Google OAuth 2.0 application -2. Get your Client ID and Client Secret -3. Add authorized redirect URIs +Every primitive you need, ready to use: -### 6. Storage Setup & R2 URL Configuration +| Category | Components | Count | +|----------|------------|-------| +| **Forms** | button, input, select, checkbox, radio-group, slider, switch, calendar, etc. | 13 | +| **Data Display** | table, card, badge, avatar, pagination, separator | 6 | +| **Overlays** | dialog, sheet, popover, tooltip, dropdown-menu, etc. | 8 | +| **Feedback** | alert, toast (sonner), progress, skeleton, scroll-area | 5 | +| **Layout & Nav** | tabs, accordion, breadcrumb, sidebar, command (⌘K) | 7 | -**Understanding R2 URLs:** -Cloudflare R2 has different URL behaviors for development vs production: +**See:** [Component Inventory](./docs/COMPONENT_INVENTORY.md) • [Build Roadmap](./docs/COMPOSED_PATTERNS_ROADMAP.md) -- **Development**: R2 provides a public URL that works immediately -- **Production**: R2 public URLs are not meant for production use - you should set up a custom domain +--- -**Step 1: Create R2 Bucket** -```bash -# Create R2 bucket for development -wrangler r2 bucket create your-app-bucket-dev +### **3. 5 Production-Ready Layouts** -# For production, create a separate bucket -wrangler r2 bucket create your-app-bucket-prod -``` +Choose the layout that matches your app: -**Step 2: Get R2 URL for Development** +| Layout | Best For | Features | +|--------|----------|----------| +| **Sidebar** | Dashboards, Admin Panels | Collapsible sidebar, keyboard shortcuts | +| **Top Nav** | Simple Tools | Horizontal nav, full-width content | +| **Hybrid** | Complex SaaS | Top header + sidebar, most polished | +| **Centered** | Docs, Blogs | Max-width content, optimal reading | +| **Marketing** | Landing Pages | Public pages with footer | -**Enable Public Development URL:** -1. Go to Cloudflare Dashboard -2. Click "R2 Object Storage" in the sidebar -3. Select your bucket from the list -4. Go to the "Settings" tab -5. Find "Public Development URL" section -6. Click "Enable Public URL" -7. Copy the displayed URL (it will be in format: `https://pub-xxxxx.r2.dev`) +**See:** [Layouts Documentation](./LAYOUTS.md) -**Example URL Format:** -``` -https://pub-a1b2c3d4e5f6g7h8i9j0.r2.dev -``` +--- -**Important Notes:** -- The URL is automatically generated by Cloudflare -- No account ID needed - it's all in the provided URL -- This URL is for development only (not production) +### **4. Modular Feature System** -**Step 3: Add R2 URL to Environment Variables** ```bash -# Add to your .dev.vars file (use the URL you copied from dashboard) -CLOUDFLARE_R2_URL=https://pub-a1b2c3d4e5f6g7h8i9j0.r2.dev -``` - -**Note:** Replace the example URL with the actual URL you copied from your R2 bucket settings. +src/modules/ +├── auth/ # Required - Google OAuth, session management +├── dashboard/ # Required - Layout wrapper +└── todos/ # Optional - Example CRUD module -**For Production - Custom Domain Setup (Required):** +# Remove unwanted modules: +rm -rf src/modules/todos -⚠️ **Important**: The default R2 public URL should NOT be used in production as it's not optimized for performance and may have limitations. +# Add new module: +mkdir -p src/modules/products/{actions,components,schemas} +``` -**Setup Custom Domain for R2:** -```bash -# 1. Go to Cloudflare Dashboard → R2 Storage → Your Bucket → Custom Domains -# 2. Click "Connect Domain" and enter your desired domain (e.g., files.yourdomain.com) -# 3. Update your DNS records as instructed by Cloudflare -# 4. Wait for SSL certificate to be issued (usually a few minutes) +**See:** [Module System Guide](./MODULES.md) • [Module Template](./MODULE_TEMPLATE.md) -# Your production R2 URL will be: -# https://files.yourdomain.com +--- -# Add this to your production secrets: -echo "https://files.yourdomain.com" | wrangler secret put CLOUDFLARE_R2_URL -``` +### **5. Comprehensive Documentation** -**R2 URL Summary:** -- **Development**: Use URL from R2 bucket "Public Development URL" setting -- **Production**: Must use custom domain for better performance and reliability +Full Flare Stack includes **production-tested documentation** that saves hours: -**How R2 URLs Work:** -- **Base URL**: Get from R2 bucket settings (format: `https://pub-xxxxx.r2.dev`) -- **File URLs**: `https://pub-xxxxx.r2.dev/{folder}/{file-name}.{extension}` -- **Environment Variable**: Only the base URL goes into `CLOUDFLARE_R2_URL` -- **Code**: The full file paths are constructed programmatically using the base URL +**Architecture Guides:** +- [Architecture Overview](./docs/development-planning/architecture-overview.md) - Three-layer system +- [Component Decision Framework](./docs/development-planning/component-decision-framework.md) - Where components go +- [Module Development Guide](./docs/development-planning/module-development-guide.md) - Building features +- [Pattern Library Plan](./docs/development-planning/pattern-library-plan.md) - Reusable patterns -## 🛠️ Manual Setup (Detailed) +**Reference Docs:** +- [Component Inventory](./docs/COMPONENT_INVENTORY.md) - All 43 installed components +- [Composed Patterns Roadmap](./docs/COMPOSED_PATTERNS_ROADMAP.md) - Build order & priorities +- [API Endpoints](./docs/API_ENDPOINTS.md) - Complete API documentation +- [Database Schema](./docs/DATABASE_SCHEMA.md) - D1 schema reference -If you prefer to set everything up manually or want to understand each step in detail, follow this comprehensive guide. +**Templates:** +- [Pattern Template](./docs/templates/PATTERN_TEMPLATE.md) - Document new patterns -### Step 1: Create Cloudflare Resources +--- -**Create D1 Database:** -```bash -# Create a new SQLite database at the edge -wrangler d1 create your-app-name +## 🛠️ Tech Stack -# Output will show: -# database_name = "your-app-name" -# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -``` +### Frontend +- **Next.js 15.4.6** - App Router, React Server Components, Server Actions +- **React 19.1.0** - Latest React with concurrent features +- **TailwindCSS v4** - Utility-first CSS with native CSS variables +- **shadcn/ui** - 43 pre-installed Radix UI components +- **React Hook Form + Zod** - Type-safe form validation + +### Backend & Infrastructure +- **Cloudflare Workers** - Serverless edge compute (via @opennextjs/cloudflare) +- **Cloudflare D1** - SQLite database at the edge +- **Cloudflare R2** - S3-compatible object storage (no egress fees) +- **Cloudflare Workers AI** - Edge AI inference with open-source models +- **better-auth 1.3.9** - Modern authentication with Google OAuth +- **Drizzle ORM 0.44.5** - TypeScript-first database toolkit + +### DevOps +- **Wrangler 4.46.0** - Cloudflare CLI +- **pnpm** - Fast, efficient package manager +- **Biome** - Fast code formatter +- **GitHub Actions** - CI/CD (ready to configure) -**Create R2 Bucket:** -```bash -# Create object storage bucket -wrangler r2 bucket create your-app-bucket +--- -# List buckets to confirm -wrangler r2 bucket list -``` +## 🚀 Quick Start -### Step 2: Configure Wrangler - -Update `wrangler.jsonc` with your resource IDs: - -```jsonc -{ - "name": "your-app-name", - "d1_databases": [ - { - "binding": "DB", - "database_name": "your-app-name", - "database_id": "your-database-id-from-step-1", - "migrations_dir": "./src/drizzle" - } - ], - "r2_buckets": [ - { - "bucket_name": "your-app-bucket", - "binding": "FILES" - } - ], - "ai": { - "binding": "AI" - } -} -``` +### Prerequisites +- Node.js 18+ (20+ recommended) +- pnpm (`npm install -g pnpm`) +- Cloudflare account (free tier works) +- Google OAuth credentials (for auth) -### Step 3: Set Up Authentication +### 1. Clone & Install -**Generate Better Auth Secret:** ```bash -# On macOS/Linux -openssl rand -base64 32 - -# On Windows (PowerShell) -[System.Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)) - -# Or use online generator: https://generate-secret.vercel.app/32 +git clone https://github.com/jezweb/full-flare-stack.git my-app +cd my-app +pnpm install ``` -**Configure Google OAuth:** -1. Go to [Google Cloud Console](https://console.cloud.google.com/) -2. Create a new project or select existing one -3. Enable Google+ API -4. Create OAuth 2.0 credentials -5. Add authorized redirect URIs: - - `http://localhost:3000/api/auth/callback/google` (development) - - `https://your-app.your-subdomain.workers.dev/api/auth/callback/google` (production) - -### Step 4: Environment Configuration +### 2. Environment Setup -**Create Local Environment File:** ```bash -# .dev.vars for local development -CLOUDFLARE_ACCOUNT_ID=your-account-id -CLOUDFLARE_D1_TOKEN=your-api-token -BETTER_AUTH_SECRET=your-generated-secret -GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com -GOOGLE_CLIENT_SECRET=your-google-client-secret -# Get this from R2 bucket settings: R2 Object Storage → Your Bucket → Settings → Public Development URL -CLOUDFLARE_R2_URL=https://pub-a1b2c3d4e5f6g7h8i9j0.r2.dev -``` +# Copy environment template +cp .dev.vars.example .dev.vars -**Set Production Secrets:** -```bash -# Add each secret to Cloudflare Workers -echo "your-secret-here" | wrangler secret put BETTER_AUTH_SECRET -echo "your-client-id" | wrangler secret put GOOGLE_CLIENT_ID -echo "your-client-secret" | wrangler secret put GOOGLE_CLIENT_SECRET -echo "your-r2-url" | wrangler secret put CLOUDFLARE_R2_URL +# Edit .dev.vars with your credentials: +# - CLOUDFLARE_ACCOUNT_ID (from Cloudflare dashboard) +# - BETTER_AUTH_SECRET (generate with: openssl rand -base64 32) +# - GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET (from Google Cloud Console) +# - CLOUDFLARE_R2_URL (your R2 bucket public URL) ``` -### Step 5: Database Setup +**See:** [Environment Setup Guide](./docs/ENVIRONMENT_SETUP.md) (if available) -**Generate TypeScript Types:** -```bash -# Generate Cloudflare bindings for TypeScript -pnpm run cf-typegen -``` +### 3. Database Setup -**Initialize Database:** ```bash -# Generate initial migration from schema -pnpm run db:generate - -# Apply migrations to local database +# Create D1 database (local development) pnpm run db:migrate:local -# Verify database structure -pnpm run db:inspect:local +# Or use Wrangler to create remote database +pnpm wrangler d1 create full-flare-stack +# Update wrangler.jsonc with database ID ``` -**Optional: Seed Sample Data** -```bash -# Create and run a seed script -wrangler d1 execute your-app-name --local --command=" -INSERT INTO todos (id, title, description, completed, created_at, updated_at) VALUES -('1', 'Welcome to your app', 'This is a sample todo item', false, datetime('now'), datetime('now')), -('2', 'Set up authentication', 'Configure Google OAuth', true, datetime('now'), datetime('now')); -" -``` +### 4. Start Development -### Step 6: Test Your Setup +**Two-terminal setup (recommended):** -**Start Development Servers:** ```bash -# Terminal 1: Start Wrangler (provides D1 access) +# Terminal 1: Wrangler (provides D1 database access) pnpm run wrangler:dev -# Terminal 2: Start Next.js (provides HMR) +# Terminal 2: Next.js (provides hot module reload) pnpm run dev - -# Alternative: Single command (no HMR) -pnpm run dev:cf ``` -**Verify Everything Works:** -1. Open `http://localhost:3000` -2. Test authentication flow -3. Create a todo item -4. Check database: `pnpm run db:studio:local` - -### Step 7: Set Up GitHub Actions (Optional) +**Access:** http://localhost:3000 -**Add Repository Secrets:** -Go to your GitHub repository → Settings → Secrets and add: - -- `CLOUDFLARE_API_TOKEN` - Your API token from Step 2 -- `CLOUDFLARE_ACCOUNT_ID` - Your account ID -- `BETTER_AUTH_SECRET` - Your auth secret -- `GOOGLE_CLIENT_ID` - Your Google client ID -- `GOOGLE_CLIENT_SECRET` - Your Google client secret -- `CLOUDFLARE_R2_URL` - Your R2 bucket URL - -**Deploy Production Database:** +**Alternative (single terminal, no HMR):** ```bash -# Apply migrations to production -pnpm run db:migrate:prod - -# Verify production database -pnpm run db:inspect:prod +pnpm run dev:cf ``` -## 🔧 Advanced Manual Configuration - -### Custom Domain Setup - -**Add Custom Domain:** -1. Go to Cloudflare dashboard → Workers & Pages -2. Select your worker → Settings → Triggers -3. Click "Add Custom Domain" -4. Enter your domain (must be in your Cloudflare account) - -**Update OAuth Redirect URLs:** -Add your custom domain to Google OAuth settings: -- `https://yourdomain.com/api/auth/callback/google` - -### Database Optimization +### 5. Build & Deploy -**Add Indexes for Performance:** -```sql --- Create indexes for better query performance -CREATE INDEX IF NOT EXISTS idx_todos_user_id ON todos(user_id); -CREATE INDEX IF NOT EXISTS idx_todos_created_at ON todos(created_at); -CREATE INDEX IF NOT EXISTS idx_todos_completed ON todos(completed); -``` - -**Monitor Database Performance:** ```bash -# View database insights -wrangler d1 insights your-app-name --since 1h - -# Export data for analysis -wrangler d1 export your-app-name --output backup.sql -``` +# Build for Cloudflare Workers +pnpm run build:cf -### R2 Storage Configuration +# Deploy to preview environment +pnpm run deploy:preview -**Configure CORS for Direct Uploads:** -```bash -# Create CORS policy file -echo '[ - { - "AllowedOrigins": ["https://yourdomain.com", "http://localhost:3000"], - "AllowedMethods": ["GET", "PUT", "POST", "DELETE"], - "AllowedHeaders": ["*"], - "ExposeHeaders": [], - "MaxAgeSeconds": 3000 - } -]' > cors.json - -# Apply CORS policy -wrangler r2 bucket cors put your-app-bucket --file cors.json +# Deploy to production +pnpm run deploy ``` -## 🏃‍♂️ Development Workflow +--- -### Initial Setup -```bash -# 1. Generate Cloudflare types (run after any wrangler.jsonc changes) -pnpm run cf-typegen +## 📖 Documentation Quick Links -# 2. Apply database migrations -pnpm run db:migrate:local +**Getting Started:** +- [Quick Start](#-quick-start) (this file) +- [Environment Setup](./docs/ENVIRONMENT_SETUP.md) (create if needed) +- [Deployment Guide](./docs/DEPLOYMENT.md) (create if needed) -# 3. Build the application for Cloudflare -pnpm run build:cf -``` +**Architecture:** +- [Three-Layer Component System](./docs/development-planning/architecture-overview.md) +- [Module System Guide](./MODULES.md) +- [Component Decision Framework](./docs/development-planning/component-decision-framework.md) -### Daily Development -```bash -# Terminal 1: Start Wrangler for D1 database access -pnpm run wrangler:dev +**Development:** +- [Building Features](./docs/development-planning/module-development-guide.md) +- [Extracting Patterns](./docs/COMPOSED_PATTERNS_ROADMAP.md) +- [Database Migrations](./docs/DATABASE_SCHEMA.md) +- [API Reference](./docs/API_ENDPOINTS.md) -# Terminal 2: Start Next.js development server with HMR -pnpm run dev -``` +**Reference:** +- [Component Inventory](./docs/COMPONENT_INVENTORY.md) +- [Pattern Library Plan](./docs/development-planning/pattern-library-plan.md) +- [Changelog](./CHANGELOG.md) +- [Roadmap](./ROADMAP.md) -**Development URLs:** -- 🌐 **Next.js with HMR**: `http://localhost:3000` (recommended) -- ⚙️ **Wrangler Dev Server**: `http://localhost:8787` +--- -### Alternative Development Options +## 🎯 Example Use Cases + +### SaaS Dashboard ```bash -# Single command - Cloudflare runtime (no HMR) -pnpm run dev:cf +# 1. Keep auth + dashboard modules +# 2. Remove todos module +rm -rf src/modules/todos -# Test with remote Cloudflare resources -pnpm run dev:remote -``` +# 3. Add your feature modules +mkdir -p src/modules/{customers,subscriptions,billing} -## 📜 Available Scripts - -### **Core Development** -| Script | Description | -|--------|-------------| -| `pnpm dev` | Start Next.js with HMR | -| `pnpm run build:cf` | Build for Cloudflare Workers | -| `pnpm run wrangler:dev` | Start Wrangler for local D1 access | -| `pnpm run dev:cf` | Combined build + Cloudflare dev server | - -### **Database Operations** -| Script | Description | -|--------|-------------| -| `pnpm run db:generate` | Generate new migration | -| `pnpm run db:generate:named "migration_name"` | Generate named migration | -| `pnpm run db:migrate:local` | Apply migrations to local D1 | -| `pnpm run db:migrate:preview` | Apply migrations to preview | -| `pnpm run db:migrate:prod` | Apply migrations to production | -| `pnpm run db:studio:local` | Open Drizzle Studio for local DB | -| `pnpm run db:inspect:local` | List local database tables | -| `pnpm run db:reset:local` | Reset local database | - -### **Deployment & Production** -| Script | Description | -|--------|-------------| -| `pnpm run deploy` | Deploy to production | -| `pnpm run deploy:preview` | Deploy to preview environment | -| `pnpm run cf-typegen` | Generate Cloudflare TypeScript types | -| `pnpm run cf:secret` | Add secrets to Cloudflare Workers | - -### **Development Order** - -**First-time setup:** -1. `pnpm run cf-typegen` - Generate types -2. `pnpm run db:migrate:local` - Setup database -3. `pnpm run build:cf` - Build application - -**Daily development:** -1. `pnpm run wrangler:dev` - Start D1 access (Terminal 1) -2. `pnpm run dev` - Start Next.js with HMR (Terminal 2) - -**After schema changes:** -1. `pnpm run db:generate` - Generate migration -2. `pnpm run db:migrate:local` - Apply to local DB - -**After wrangler.jsonc changes:** -1. `pnpm run cf-typegen` - Regenerate types - -## 🤖 AI Development & Testing - -### Testing the AI API - -**⚠️ Authentication Required**: Login to your app first, then test the API. - -**Browser Console (Easiest):** -1. Login at `http://localhost:3000` -2. Open DevTools Console (F12) -3. Run: -```javascript -fetch('/api/summarize', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ - text: "Your text to summarize here...", - config: { maxLength: 100, style: "concise" } - }) -}).then(r => r.json()).then(console.log); +# 4. Use Sidebar or Hybrid layout +# 5. Build with DataTable, forms, and charts patterns ``` -**cURL (with session cookies):** -1. Login in browser first -2. DevTools → Application → Cookies → Copy `better-auth.session_token` -3. Use cookie in cURL: +### Marketing Site + App ```bash -curl -X POST http://localhost:3000/api/summarize \ - -H "Content-Type: application/json" \ - -H "Cookie: better-auth.session_token=your-token-here" \ - -d '{"text": "Your text here...", "config": {"maxLength": 100}}' +# 1. Use Marketing layout for landing pages +# 2. Use Sidebar layout for dashboard +# 3. Add CMS module for content management +# 4. Deploy to Cloudflare Workers (global CDN) ``` -**Postman:** -1. Login in browser, copy session cookie from DevTools -2. Add header: `Cookie: better-auth.session_token=your-token-here` - -**Unauthenticated Request Response:** -```json -{ - "success": false, - "error": "Authentication required", - "data": null -} -``` - - -### AI Service Architecture - -The AI integration follows a clean service-based architecture: - -1. **API Route** (`/api/summarize`) - Handles HTTP requests, authentication, and validation -2. **Authentication Layer** - Validates user session before processing requests -3. **SummarizerService** - Encapsulates AI business logic -4. **Error Handling** - Comprehensive error responses with proper status codes -5. **Type Safety** - Full TypeScript support with Zod validation - -### AI Model Options - -Cloudflare Workers AI supports various models: -- **@cf/meta/llama-3.2-1b-instruct** - Text generation (current) -- **@cf/meta/llama-3.2-3b-instruct** - More capable text generation -- **@cf/meta/m2m100-1.2b** - Translation -- **@cf/baai/bge-base-en-v1.5** - Text embeddings -- **@cf/microsoft/resnet-50** - Image classification - -## 🔧 Advanced Configuration - -### Database Schema Changes +### AI-Powered Tool ```bash -# 1. Modify schema files in src/db/schemas/ -# 2. Generate migration -pnpm run db:generate:named "add_user_table" -# 3. Apply to local database -pnpm run db:migrate:local -# 4. Test your changes -# 5. Commit and deploy (migrations run automatically) +# 1. Use Centered layout for focused UX +# 2. Add AI module with Workers AI +# 3. Use Cloudflare R2 for file uploads +# 4. Stream responses with Server Actions ``` -### Adding New Cloudflare Resources -```bash -# 1. Update wrangler.jsonc with new resources -# 2. Regenerate types -pnpm run cf-typegen -# 3. Update your code to use new bindings -``` +--- -### Production Secrets Management -```bash -# Add secrets to production environment -pnpm run cf:secret BETTER_AUTH_SECRET -pnpm run cf:secret GOOGLE_CLIENT_ID -pnpm run cf:secret GOOGLE_CLIENT_SECRET -``` +## 🤝 Contributing -## 📊 Performance & Monitoring +Full Flare Stack is open source and welcomes contributions! -**Built-in Observability:** -- ✅ Cloudflare Analytics (enabled by default) -- ✅ Real User Monitoring (RUM) -- ✅ Error tracking and logging -- ✅ Performance metrics +**Ways to contribute:** +- 🐛 Report bugs via [GitHub Issues](https://github.com/jezweb/full-flare-stack/issues) +- 💡 Suggest features via [GitHub Discussions](https://github.com/jezweb/full-flare-stack/discussions) +- 📝 Improve documentation +- 🎨 Submit composed patterns +- 🧩 Share example modules -**Database Monitoring:** -```bash -# Monitor database performance -wrangler d1 insights next-cf-app +**See:** [Contributing Guide](./CONTRIBUTING.md) for detailed instructions. -# View database metrics in Cloudflare Dashboard -# Navigate to Workers & Pages → D1 → next-cf-app → Metrics -``` +--- -## 🚀 Deployment +## 🙏 Acknowledgments -### Automatic Deployment (Recommended) +Full Flare Stack is a fork of [fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare) by [@ifindev](https://github.com/ifindev). -Push to `main` branch triggers automatic deployment via GitHub Actions: +**What we added:** +- ✅ Three-layer component architecture +- ✅ 43 shadcn/ui components (complete foundation) +- ✅ 5 production-ready layout variants +- ✅ Comprehensive architecture documentation +- ✅ Component decision framework +- ✅ Pattern build roadmap +- ✅ 15+ UX/DX improvements -```bash -git add . -git commit -m "feat: add new feature" -git push origin main -``` +Thank you to **@ifindev** for the excellent starting point! -**Deployment Pipeline:** -1. ✅ Install dependencies -2. ✅ Build application -3. ✅ Run database migrations -4. ✅ Deploy to Cloudflare Workers +**Also inspired by:** +- [Cloudflare SaaS Stack](https://github.com/supermemoryai/cloudflare-saas-stack) - Proven stack powering 20k+ users on $5/month +- [Supermemory.ai](https://git.new/memory) - Real-world Cloudflare Workers production app -### Manual Deployment +--- -```bash -# Deploy to production -pnpm run deploy +## 📄 License -# Deploy to preview environment -pnpm run deploy:preview -``` +MIT License - see [LICENSE](./LICENSE) for details. -## ✍️ Todos +Copyright (c) 2025 Jez Dawes / Jezweb -### 🤖 AI Features -- [ ] Add text translation service with `@cf/meta/m2m100-1.2b` -- [ ] Implement text embeddings for semantic search with `@cf/baai/bge-base-en-v1.5` -- [ ] Add image classification API with `@cf/microsoft/resnet-50` -- [ ] Create chat/conversation API with conversation memory -- [ ] Add content moderation with AI classification -- [ ] Implement sentiment analysis for user feedback +--- -### 💳 Payments & Communication -- [ ] Implement email sending with [Resend](https://resend.com/) & [Cloudflare Email Routing](https://www.cloudflare.com/developer-platform/products/email-routing/) -- [ ] Implement international payment gateway with [Polar.sh](https://polar.sh/) -- [ ] Implement Indonesian payment gateway either with [Xendit](https://www.xendit.co/en-id/), [Midtrans](https://midtrans.com/en), or [Duitku](https://www.duitku.com/) +## 💬 Community & Support -### 📊 Analytics & Performance -- [ ] Add Cloudflare Analytics integration -- [ ] Implement custom metrics tracking -- [ ] Add performance monitoring dashboard -- [ ] Create AI usage analytics and cost tracking +**GitHub:** +- [Issues](https://github.com/jezweb/full-flare-stack/issues) - Bug reports & feature requests +- [Discussions](https://github.com/jezweb/full-flare-stack/discussions) - Community chat +**Jezweb:** +- Website: [jezweb.com.au](https://jezweb.com.au) +- Email: jeremy@jezweb.net +- Phone: +61 411 056 876 +**Official Cloudflare Docs:** +- [Workers](https://developers.cloudflare.com/workers/) +- [D1 Database](https://developers.cloudflare.com/d1/) +- [R2 Storage](https://developers.cloudflare.com/r2/) +- [Workers AI](https://developers.cloudflare.com/workers-ai/) -## 🤝 Contributing +--- -Contributions are welcome! Please feel free to submit issues and pull requests. +## ⭐ Star History -## 📝 License +If you find Full Flare Stack useful, please consider giving it a star on GitHub! -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +[![Star History Chart](https://api.star-history.com/svg?repos=jezweb/full-flare-stack&type=Date)](https://star-history.com/#jezweb/full-flare-stack&Date) --- -© 2025 Muhammad Arifin. All rights reserved. +**Built with ❤️ by [Jezweb](https://jezweb.com.au) • Powered by ⚡ Cloudflare** diff --git a/SESSION.md b/SESSION.md new file mode 100644 index 0000000..433ee88 --- /dev/null +++ b/SESSION.md @@ -0,0 +1,163 @@ +# Session State + +**Project**: fullstack-next-cloudflare (Open Source Contributions) +**Repository**: https://github.com/ifindev/fullstack-next-cloudflare +**Fork**: https://github.com/jezweb/fullstack-next-cloudflare +**Current Phase**: UX Improvements +**Last Checkpoint**: fa233f3 (2025-11-08) + +--- + +## Completed PRs ✅ + +### Phase 1: Quick Fixes (PRs #11-16) +**Status**: Complete | **Date**: 2025-11-08 + +1. **PR #11** - Auto-detect port in auth client + - Fixed hardcoded localhost:3000 to use window.location.origin + - Prevents port conflicts in development + +2. **PR #12** - Fix navigation link + - Changed /todos → /dashboard/todos (404 fix) + +3. **PR #13** - Fix typos in method names + - buildSystenPrompt → buildSystemPrompt + - styleInstructructions → styleInstructions + +4. **PR #14** - Replace alert() with toast + - delete-todo.tsx: alert() → toast.error() + +5. **PR #15** - Add ARIA labels + - Added aria-label to delete button for accessibility + +6. **PR #16** - Add file validation + - File size limit: 5MB + - File types: PNG, JPG only + - Toast error messages + +--- + +### Phase 2: Medium-Difficulty Fixes (PRs #17-20) +**Status**: Complete | **Date**: 2025-11-08 + +7. **PR #17** - Fix R2 URL double https:// + - File: src/lib/r2.ts + - Removed hardcoded https:// prefix (env var already includes it) + +8. **PR #18** - Database ID environment variable + - Files: drizzle.config.ts, .dev.vars.example, README.md + - Added CLOUDFLARE_D1_DATABASE_ID env var + - Replaced hardcoded database ID + +9. **PR #19** - NEXT_REDIRECT error handling + - File: src/modules/todos/actions/update-todo.action.ts + - Added NEXT_REDIRECT handling to match createTodoAction + +10. **PR #20** - Standardize error responses + - Files: create-category.action.ts, add-category.tsx + - Changed from throw pattern to { success, data?, error? } pattern + - Consistent with other mutations + +--- + +### Phase 3: Documentation (PR #21) +**Status**: Complete | **Date**: 2025-11-08 + +11. **PR #21** - Complete API documentation + - File: docs/API_ENDPOINTS.md (872 lines) + - REST endpoints: /api/summarize, /api/auth/* + - Server actions: 11 actions documented + - Data models, error handling, examples + +--- + +## Current Phase: UX Improvements 🔄 + +### Planned PRs (Next 5) + +**PR #22** - Replace alert() with toast (remaining instances) +- Files: toggle-complete.tsx +- Estimated: 15 min + +**PR #23** - Add success feedback for todo create/edit +- Files: todo-form.tsx, create-todo.action.ts, update-todo.action.ts +- Add toast.success() before redirect +- Estimated: 30 min + +**PR #24** - Image upload failure warnings +- Files: create-todo.action.ts, update-todo.action.ts +- Show toast when R2 upload fails +- Estimated: 20 min + +**PR #25** - Loading state for image uploads +- File: todo-form.tsx +- Add loading indicator/progress +- Estimated: 45 min + +**PR #26** - Theme-aware colors (optional) +- Files: todo-card.tsx, dashboard.page.tsx +- Replace hard-coded colors with semantic theme colors +- Estimated: 45 min + +--- + +## Key Files Reference + +**Actions**: +- `src/modules/todos/actions/create-todo.action.ts` +- `src/modules/todos/actions/update-todo.action.ts` +- `src/modules/todos/actions/delete-todo.action.ts` +- `src/modules/todos/actions/create-category.action.ts` + +**Components**: +- `src/modules/todos/components/todo-form.tsx` +- `src/modules/todos/components/todo-card.tsx` +- `src/modules/todos/components/delete-todo.tsx` +- `src/modules/todos/components/toggle-complete.tsx` +- `src/modules/todos/components/add-category.tsx` + +**API Routes**: +- `src/app/api/summarize/route.ts` +- `src/app/api/auth/[...all]/route.ts` + +**Config**: +- `drizzle.config.ts` +- `wrangler.jsonc` +- `.dev.vars.example` + +--- + +## Development Setup + +**Dev Servers Running**: +- Wrangler: Port 8787 (Bash 23e213) +- Next.js: Port 3001 (Bash d83043) +- Additional: Bash bc259d + +**Environment**: +- Account ID: 0460574641fdbb98159c98ebf593e2bd +- Database ID: 757a32d1-5779-4f09-bcf3-b268013395d4 +- Auth: Google OAuth configured + +--- + +## Contribution Stats + +**Total PRs**: 11 submitted +**Lines Changed**: ~1,500+ lines +**Documentation Added**: 872 lines +**Issues Fixed**: 15+ + +**Focus Areas**: +- Error handling consistency +- Environment configuration +- API documentation +- User experience improvements + +--- + +## Next Action + +**After context compact**: Continue with UX improvement PRs (#22-26) + +Start with PR #22: Replace remaining alert() calls with toast notifications in toggle-complete.tsx diff --git a/docs/API_ENDPOINTS.md b/docs/API_ENDPOINTS.md new file mode 100644 index 0000000..ccbf78e --- /dev/null +++ b/docs/API_ENDPOINTS.md @@ -0,0 +1,872 @@ +# API Documentation + +Complete API surface documentation for the Full-Stack Next.js Cloudflare Demo application. + +## Table of Contents + +1. [REST API Endpoints](#rest-api-endpoints) +2. [Server Actions](#server-actions) +3. [Authentication](#authentication) +4. [Data Models](#data-models) +5. [Error Handling](#error-handling) + +--- + +## REST API Endpoints + +### POST /api/summarize + +Summarize text using Cloudflare Workers AI. + +**Purpose**: Generate summaries of text content with configurable length, style, and language. + +**Authentication**: Required (session-based) + +**Request Body**: +```typescript +{ + text: string; // 50-50,000 characters + config?: { + maxLength?: number; // 50-1000, default: 200 + style?: "concise" | "detailed" | "bullet-points"; // default: "concise" + language?: string; // default: "English" + } +} +``` + +**Response** (200 OK): +```typescript +{ + success: true; + data: { + summary: string; + originalLength: number; + summaryLength: number; + tokensUsed: { + input: number; + output: number; + } + }; + error: null; +} +``` + +**Error Responses**: +- `401 Unauthorized`: User not authenticated + ```json + { "success": false, "error": "Authentication required", "data": null } + ``` +- `400 Bad Request`: Invalid input (via zod validation) +- `500 Internal Server Error`: AI service unavailable + ```json + { "success": false, "error": "AI service is not available", "data": null } + ``` + +**Example Usage**: +```typescript +const response = await fetch('/api/summarize', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + text: 'Long article text here...', + config: { maxLength: 150, style: 'bullet-points' } + }) +}); +const result = await response.json(); +``` + +--- + +### Better Auth Endpoints + +All Better Auth endpoints are handled via `/api/auth/[...all]`. + +**Base Path**: `/api/auth` + +**Supported Methods**: GET, POST + +**Available Routes** (handled by Better Auth): +- `POST /api/auth/sign-up/email` - Email/password sign up +- `POST /api/auth/sign-in/email` - Email/password sign in +- `POST /api/auth/sign-out` - Sign out current session +- `GET /api/auth/session` - Get current session +- `GET /api/auth/get-session` - Alternative session endpoint +- OAuth routes for Google sign-in (configured) + +**Configuration**: +- Email/Password authentication: Enabled +- Google OAuth: Enabled (requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET) +- Session storage: D1 database via Drizzle adapter +- Cookie handling: Next.js cookies plugin + +**Example - Get Session**: +```typescript +const response = await fetch('/api/auth/session'); +const session = await response.json(); +// Returns: { user: { id, name, email, ... }, session: { ... } } +``` + +--- + +## Server Actions + +All server actions use Next.js Server Actions (`"use server"`) and return structured responses. + +### Authentication Actions + +#### signIn() + +**Path**: `src/modules/auth/actions/auth.action.ts` + +**Purpose**: Authenticate user with email and password. + +**Parameters**: +```typescript +{ + email: string; // Valid email format + password: string; // Minimum 8 characters +} +``` + +**Returns**: +```typescript +{ + success: boolean; + message: string; // "Signed in successfully" or error message +} +``` + +**Authentication**: No (this creates the session) + +**Usage**: +```typescript +import { signIn } from '@/modules/auth/actions/auth.action'; + +const result = await signIn({ email: 'user@example.com', password: 'password123' }); +if (result.success) { + // Redirect to dashboard +} +``` + +--- + +#### signUp() + +**Path**: `src/modules/auth/actions/auth.action.ts` + +**Purpose**: Create new user account with email and password. + +**Parameters**: +```typescript +{ + email: string; // Valid email format + password: string; // Minimum 8 characters + username: string; // Minimum 3 characters +} +``` + +**Returns**: +```typescript +{ + success: boolean; + message: string; // "Signed up successfully" or error message +} +``` + +**Authentication**: No (creates new user) + +**Usage**: +```typescript +import { signUp } from '@/modules/auth/actions/auth.action'; + +const result = await signUp({ + email: 'user@example.com', + password: 'password123', + username: 'johndoe' +}); +``` + +--- + +#### signOut() + +**Path**: `src/modules/auth/actions/auth.action.ts` + +**Purpose**: End current user session. + +**Parameters**: None + +**Returns**: +```typescript +{ + success: boolean; + message: string; // "Signed out successfully" or error message +} +``` + +**Authentication**: Required (implicitly via session headers) + +**Usage**: +```typescript +import { signOut } from '@/modules/auth/actions/auth.action'; + +const result = await signOut(); +if (result.success) { + // Redirect to login +} +``` + +--- + +### Todo Actions + +#### getAllTodos() + +**Path**: `src/modules/todos/actions/get-todos.action.ts` + +**Purpose**: Fetch all todos for the authenticated user with category information. + +**Parameters**: None + +**Returns**: +```typescript +Todo[] // Array of todos (see Data Models section) +``` + +**Authentication**: Yes (via requireAuth()) + +**Database Query**: +- Joins todos with categories +- Filters by authenticated user ID +- Orders by creation date + +**Error Handling**: Returns empty array on error + +**Usage**: +```typescript +import getAllTodos from '@/modules/todos/actions/get-todos.action'; + +const todos = await getAllTodos(); +// Returns: [{ id, title, description, categoryName, ... }, ...] +``` + +--- + +#### getTodoById() + +**Path**: `src/modules/todos/actions/get-todo-by-id.action.ts` + +**Purpose**: Fetch a single todo by ID for the authenticated user. + +**Parameters**: +```typescript +id: number // Todo ID +``` + +**Returns**: +```typescript +Todo | null // Todo object or null if not found +``` + +**Authentication**: Yes (via requireAuth()) + +**Database Query**: +- Joins with categories +- Verifies todo belongs to authenticated user +- Returns null if not found or unauthorized + +**Usage**: +```typescript +import { getTodoById } from '@/modules/todos/actions/get-todo-by-id.action'; + +const todo = await getTodoById(123); +if (todo) { + // Display todo details +} +``` + +--- + +#### createTodoAction() + +**Path**: `src/modules/todos/actions/create-todo.action.ts` + +**Purpose**: Create a new todo with optional image upload to R2. + +**Parameters**: `FormData` object with the following fields: +```typescript +{ + title: string; // Required, 3-255 characters + description?: string; // Optional, max 1000 characters + categoryId?: number; // Optional category ID + status?: "pending" | "in_progress" | "completed" | "archived"; + priority?: "low" | "medium" | "high" | "urgent"; + completed?: boolean; // Default: false + dueDate?: string; // ISO date string + imageUrl?: string; // Optional image URL + imageAlt?: string; // Optional alt text + image?: File; // Optional image file for upload +} +``` + +**Returns**: Redirects to todo list on success + +**Authentication**: Yes (via requireAuth()) + +**Side Effects**: +- Uploads image to R2 if provided (bucket: "todo-images") +- Revalidates `/dashboard/todos` path +- Redirects to todo list after creation + +**Throws**: +- Zod validation errors for invalid data +- "Authentication required" error if not authenticated +- Generic error message for other failures + +**Usage**: +```typescript +import { createTodoAction } from '@/modules/todos/actions/create-todo.action'; + +const formData = new FormData(); +formData.append('title', 'New Task'); +formData.append('description', 'Task description'); +formData.append('priority', 'high'); +formData.append('image', fileInput.files[0]); + +await createTodoAction(formData); +// Automatically redirects on success +``` + +--- + +#### updateTodoAction() + +**Path**: `src/modules/todos/actions/update-todo.action.ts` + +**Purpose**: Update an existing todo with optional new image. + +**Parameters**: +```typescript +todoId: number // Todo ID to update +formData: FormData // Form fields (all optional, partial update) +``` + +**FormData Fields** (all optional): +```typescript +{ + title?: string; + description?: string; + categoryId?: number; + status?: "pending" | "in_progress" | "completed" | "archived"; + priority?: "low" | "medium" | "high" | "urgent"; + completed?: boolean; + dueDate?: string; + imageUrl?: string; + imageAlt?: string; + image?: File; // New image file +} +``` + +**Returns**: Redirects to todo list on success + +**Authentication**: Yes (via requireAuth()) + +**Database Query**: +- Verifies todo belongs to authenticated user +- Only updates provided fields +- Sets updatedAt timestamp + +**Side Effects**: +- Uploads new image to R2 if provided +- Revalidates `/dashboard/todos` path +- Redirects to todo list + +**Throws**: +- "Todo not found or unauthorized" if todo doesn't exist or belongs to another user +- Validation errors for invalid data + +**Usage**: +```typescript +import { updateTodoAction } from '@/modules/todos/actions/update-todo.action'; + +const formData = new FormData(); +formData.append('title', 'Updated Title'); +formData.append('status', 'completed'); + +await updateTodoAction(123, formData); +``` + +--- + +#### updateTodoFieldAction() + +**Path**: `src/modules/todos/actions/update-todo.action.ts` + +**Purpose**: Update specific fields of a todo (optimized for checkbox toggles). + +**Parameters**: +```typescript +{ + todoId: number; + data: { + completed?: boolean; // Currently only supports completed field + } +} +``` + +**Returns**: +```typescript +{ + success: boolean; + data?: Todo; // Updated todo object if successful + error?: string; // Error message if failed +} +``` + +**Authentication**: Yes (via requireAuth()) + +**Side Effects**: +- Auto-updates status to "completed" or "pending" based on completed field +- Revalidates `/dashboard/todos` path +- Does NOT redirect (returns data for optimistic UI updates) + +**Usage**: +```typescript +import { updateTodoFieldAction } from '@/modules/todos/actions/update-todo.action'; + +const result = await updateTodoFieldAction(123, { completed: true }); +if (result.success) { + // Update UI optimistically +} +``` + +--- + +#### deleteTodoAction() + +**Path**: `src/modules/todos/actions/delete-todo.action.ts` + +**Purpose**: Delete a todo by ID. + +**Parameters**: +```typescript +todoId: number // Todo ID to delete +``` + +**Returns**: +```typescript +{ + success: boolean; + message?: string; // Success message + error?: string; // Error message if failed +} +``` + +**Authentication**: Yes (via requireAuth()) + +**Database Query**: +- Verifies todo exists and belongs to authenticated user +- Deletes todo record + +**Side Effects**: +- Revalidates `/dashboard/todos` path + +**Usage**: +```typescript +import { deleteTodoAction } from '@/modules/todos/actions/delete-todo.action'; + +const result = await deleteTodoAction(123); +if (result.success) { + // Show success message +} +``` + +--- + +### Category Actions + +#### getAllCategories() + +**Path**: `src/modules/todos/actions/get-categories.action.ts` + +**Purpose**: Fetch all categories for a specific user. + +**Parameters**: +```typescript +userId: string // User ID +``` + +**Returns**: +```typescript +Category[] // Array of categories (see Data Models section) +``` + +**Authentication**: No (but requires userId parameter) + +**Database Query**: +- Filters categories by userId +- Orders by creation date + +**Error Handling**: Returns empty array on error + +**Usage**: +```typescript +import { getAllCategories } from '@/modules/todos/actions/get-categories.action'; + +const categories = await getAllCategories(user.id); +``` + +--- + +#### createCategory() + +**Path**: `src/modules/todos/actions/create-category.action.ts` + +**Purpose**: Create a new category for the authenticated user. + +**Parameters**: +```typescript +{ + name: string; // Required + color?: string; // Optional, default: "#6366f1" + description?: string; // Optional +} +``` + +**Returns**: +```typescript +Category // Created category object +``` + +**Authentication**: Yes (via requireAuth()) + +**Side Effects**: +- Revalidates `/dashboard/todos` and `/dashboard/todos/new` paths + +**Throws**: +- Zod validation errors for invalid data +- "Failed to create category" if database operation fails + +**Usage**: +```typescript +import { createCategory } from '@/modules/todos/actions/create-category.action'; + +const category = await createCategory({ + name: 'Work', + color: '#ff6347', + description: 'Work-related tasks' +}); +``` + +--- + +## Authentication + +### Better Auth Configuration + +**Provider**: Better Auth library with Drizzle adapter +**Database**: Cloudflare D1 (SQLite) +**Session Storage**: Database-backed sessions + +**Authentication Methods**: +1. Email/Password (enabled) +2. Google OAuth (enabled) + +**Environment Variables Required**: +- `BETTER_AUTH_SECRET` - Secret key for signing tokens +- `GOOGLE_CLIENT_ID` - Google OAuth client ID +- `GOOGLE_CLIENT_SECRET` - Google OAuth client secret + +**Session Management**: +- Sessions stored in database with expiration +- Cookies managed by Better Auth Next.js plugin +- Automatic session refresh + +### Auth Utility Functions + +All utilities in `src/modules/auth/utils/auth-utils.ts`: + +#### getCurrentUser() + +Returns the current authenticated user or null. + +```typescript +const user = await getCurrentUser(); +// Returns: { id: string, name: string, email: string } | null +``` + +#### requireAuth() + +Returns the current user or throws "Authentication required" error. + +```typescript +const user = await requireAuth(); +// Throws if not authenticated +``` + +#### isAuthenticated() + +Check if user has valid session. + +```typescript +const authenticated = await isAuthenticated(); +// Returns: boolean +``` + +#### getSession() + +Get full session object including user and session metadata. + +```typescript +const session = await getSession(); +// Returns session object or null +``` + +--- + +## Data Models + +### Todo + +```typescript +{ + id: number; + title: string; + description: string | null; + categoryId: number | null; + categoryName: string | null; // Joined from categories table + userId: string; + status: "pending" | "in_progress" | "completed" | "archived"; + priority: "low" | "medium" | "high" | "urgent"; + imageUrl: string | null; + imageAlt: string | null; + completed: boolean; + dueDate: string | null; // ISO date string + createdAt: string; // ISO date string + updatedAt: string; // ISO date string +} +``` + +**Validation Rules**: +- `title`: 3-255 characters +- `description`: max 1000 characters +- `imageUrl`: valid URL or empty string +- `status`: defaults to "pending" +- `priority`: defaults to "medium" +- `completed`: defaults to false + +--- + +### Category + +```typescript +{ + id: number; + name: string; + color: string; // Hex color, default: "#6366f1" + description: string | null; + userId: string; + createdAt: string; // ISO date string + updatedAt: string; // ISO date string +} +``` + +**Validation Rules**: +- `name`: required, minimum 1 character +- `color`: optional, defaults to indigo +- `userId`: automatically set from authenticated user + +--- + +### User (Auth) + +```typescript +{ + id: string; + name: string; + email: string; + emailVerified: boolean; + image: string | null; + createdAt: Date; + updatedAt: Date; +} +``` + +**Public Interface** (AuthUser): +```typescript +{ + id: string; + name: string; + email: string; +} +``` + +--- + +### Session + +```typescript +{ + id: string; + expiresAt: Date; + token: string; + createdAt: Date; + updatedAt: Date; + ipAddress: string | null; + userAgent: string | null; + userId: string; +} +``` + +--- + +## Error Handling + +### API Endpoint Errors + +REST endpoints return standardized error responses: + +```typescript +{ + success: false; + error: string; // Human-readable error message + data: null; +} +``` + +**Common HTTP Status Codes**: +- `400` - Bad Request (validation errors) +- `401` - Unauthorized (authentication required) +- `404` - Not Found +- `500` - Internal Server Error + +### Server Action Errors + +**Pattern 1: Return Object** (for UI handling) +```typescript +{ + success: false; + error: string; +} +``` + +**Pattern 2: Throw Error** (for form actions with redirects) +```typescript +throw new Error("Specific error message"); +``` + +**Pattern 3: Redirect on Success** (form actions) +- Uses Next.js `redirect()` after successful mutation +- Throws `NEXT_REDIRECT` error (normal behavior) +- Revalidates paths before redirect + +### Authentication Errors + +All protected actions check authentication: + +```typescript +const user = await requireAuth(); +// Throws "Authentication required" if not authenticated +``` + +Caller should handle: +```typescript +try { + await protectedAction(); +} catch (error) { + if (error.message === "Authentication required") { + // Redirect to login + } +} +``` + +--- + +## Security Considerations + +1. **Authorization**: All todo/category operations verify userId matches authenticated user +2. **File Uploads**: Images validated and uploaded to R2 with scoped paths +3. **SQL Injection**: Prevented by Drizzle ORM parameterized queries +4. **XSS**: React escapes output by default +5. **CSRF**: Better Auth handles CSRF tokens automatically +6. **Rate Limiting**: Not implemented (consider adding for production) + +--- + +## Development Notes + +### Revalidation Strategy + +Actions that modify data revalidate affected paths: + +```typescript +revalidatePath('/dashboard/todos'); // List page +revalidatePath('/dashboard/todos/new'); // New todo page +``` + +### Redirect Pattern + +Form actions redirect after success: + +```typescript +revalidatePath(todosRoutes.list); +redirect(todosRoutes.list); // Throws NEXT_REDIRECT +``` + +### Image Upload Flow + +1. Check if FormData contains image file +2. Upload to R2 bucket ("todo-images") +3. Store returned URL in database +4. Generate alt text from filename if not provided +5. Log errors but don't fail todo creation + +### Database Adapter + +- Uses Drizzle ORM with D1 adapter +- Better Auth uses Drizzle adapter for session storage +- All queries are type-safe with TypeScript + +--- + +## Quick Reference + +### Most Common Operations + +**Create Todo**: +```typescript +const formData = new FormData(); +formData.append('title', 'Task'); +await createTodoAction(formData); +``` + +**Update Todo Checkbox**: +```typescript +await updateTodoFieldAction(todoId, { completed: true }); +``` + +**Delete Todo**: +```typescript +await deleteTodoAction(todoId); +``` + +**Get All Todos**: +```typescript +const todos = await getAllTodos(); +``` + +**Sign In**: +```typescript +const result = await signIn({ email, password }); +``` + +**Check Authentication**: +```typescript +const user = await getCurrentUser(); +if (!user) redirect('/login'); +``` + +--- + +**Last Updated**: 2025-11-08 +**API Version**: 1.0.0 diff --git a/docs/COMPONENT_INVENTORY.md b/docs/COMPONENT_INVENTORY.md new file mode 100644 index 0000000..91325fc --- /dev/null +++ b/docs/COMPONENT_INVENTORY.md @@ -0,0 +1,274 @@ +# shadcn/ui Component Inventory + +Complete inventory of installed shadcn/ui components (Layer 1: UI Primitives) and their role in the three-layer architecture. + +**Last Updated:** 2025-11-10 +**Total Components:** 43 + +--- + +## 📊 Component Categories + +### Forms & Inputs (13 components) + +| Component | File | Purpose | Used In Patterns | +|-----------|------|---------|------------------| +| **button** | `button.tsx` | Interactive buttons | All patterns, FormActions, PageHeader | +| **input** | `input.tsx` | Text input | FormField, SearchableSelect, DataTable filters | +| **textarea** | `textarea.tsx` | Multi-line text | FormField, rich text patterns | +| **select** | `select.tsx` | Dropdown selection | FormField, filters, status selectors | +| **checkbox** | `checkbox.tsx` | Boolean selection | DataTable row selection, FormField | +| **label** | `label.tsx` | Form labels | FormField, all forms | +| **form** | `form.tsx` | Form context (React Hook Form) | All form patterns | +| **radio-group** | `radio-group.tsx` | Single selection from visible options | FormField, filter controls, plan selectors | +| **slider** | `slider.tsx` | Range input | Price filters, settings, volume controls | +| **switch** | `switch.tsx` | Toggle on/off | Settings, feature toggles, dark mode | +| **calendar** | `calendar.tsx` | Date selection | DateRangePicker, DatePicker patterns | +| **toggle** | `toggle.tsx` | Toggle button state | Text formatting, active filters | +| **toggle-group** | `toggle-group.tsx` | Exclusive/multiple toggle selection | ViewSwitcher, toolbar controls | + +--- + +### Data Display (6 components) + +| Component | File | Purpose | Used In Patterns | +|-----------|------|---------|------------------| +| **card** | `card.tsx` | Container with sections | CardView, dashboard widgets, product cards | +| **table** | `table.tsx` | Tabular data display | DataTable, CollectionContainer table view | +| **pagination** | `pagination.tsx` | Page navigation | DataTable, ListView, any paginated content | +| **badge** | `badge.tsx` | Status indicators | DataTable columns, status displays, counts | +| **avatar** | `avatar.tsx` | User images | User menus, lists, comment threads | +| **separator** | `separator.tsx` | Visual dividers | Layouts, menus, sections | + +--- + +### Overlays & Popups (8 components) + +| Component | File | Purpose | Used In Patterns | +|-----------|------|---------|------------------| +| **dialog** | `dialog.tsx` | Modal dialogs | Forms, detail views, confirmations | +| **alert-dialog** | `alert-dialog.tsx` | Confirmation dialogs | ConfirmDialog pattern, delete confirmations | +| **sheet** | `sheet.tsx` | Side panels | Mobile navigation, filters, detail views | +| **popover** | `popover.tsx` | Floating content | Date pickers, SearchableSelect (combobox), menus | +| **tooltip** | `tooltip.tsx` | Help text on hover | Icon buttons, abbreviations, help hints | +| **hover-card** | `hover-card.tsx` | Rich preview on hover | User cards, link previews, data previews | +| **dropdown-menu** | `dropdown-menu.tsx` | Action menus | Table row actions, user menus, filters | +| **navigation-menu** | `navigation-menu.tsx` | Complex navigation | Marketing site nav, mega menus | + +--- + +### Feedback & Status (5 components) + +| Component | File | Purpose | Used In Patterns | +|-----------|------|---------|------------------| +| **alert** | `alert.tsx` | Static notifications | EmptyState variants, error states, warnings | +| **sonner** | `sonner.tsx` | Toast notifications | Success/error feedback after actions | +| **progress** | `progress.tsx` | Loading progress | File uploads, multi-step forms, async operations | +| **skeleton** | `skeleton.tsx` | Loading placeholders | LoadingState pattern, suspense fallbacks | +| **scroll-area** | `scroll-area.tsx` | Custom scrollbars | Sidebar navigation, modals with long content | + +--- + +### Layout & Navigation (7 components) + +| Component | File | Purpose | Used In Patterns | +|-----------|------|---------|------------------| +| **tabs** | `tabs.tsx` | Tab navigation | PageHeader tabs, settings sections, view organization | +| **accordion** | `accordion.tsx` | Collapsible sections | Settings, FAQs, mobile nav, MultiStepForm sections | +| **breadcrumb** | `breadcrumb.tsx` | Hierarchical navigation | PageHeader, deep navigation paths | +| **sidebar** | `sidebar.tsx` | Sidebar layouts | DashboardLayout, app navigation | +| **command** | `command.tsx` | Command palette (⌘K) | Global search, quick actions, SearchableSelect | +| **color-picker** | `color-picker.tsx` | Color selection | Theme customization, category colors | +| ~~**combobox**~~ | *N/A* | **Not a component** - Pattern built from `command` + `popover` | SearchableSelect pattern | + +--- + +## 🎯 Installation History + +### November 10, 2025 - Foundation Installation + +**Session 1: Core components** (via layouts module) +- button, card, form, input, label, select, separator, sheet +- dialog, badge, dropdown-menu, navigation-menu, avatar +- checkbox, textarea, tooltip, popover, skeleton +- sidebar, color-picker + +**Session 2: Data display & feedback** (manual installation) +- table, pagination, calendar, sonner, alert, progress +- breadcrumb, tabs, scroll-area, switch + +**Session 3: Advanced forms & UI** (manual installation) +- accordion, radio-group, slider, hover-card +- command, toggle, toggle-group + +--- + +## 📦 Components by Composed Pattern + +Shows which Layer 2 (composed patterns) each Layer 1 (primitive) enables. + +### Priority 1: Data Display Patterns + +**DataTable** +- Uses: `table`, `pagination`, `checkbox`, `badge`, `dropdown-menu`, `button`, `input` +- Status: Ready to build + +**CardView** +- Uses: `card`, `badge`, `button`, `dropdown-menu`, `avatar` +- Status: Ready to build + +**ListView** +- Uses: `separator`, `avatar`, `badge`, `button` +- Status: Ready to build + +**ViewSwitcher** +- Uses: `toggle-group` +- Status: Ready to build + +**CollectionContainer** +- Uses: All data display patterns + `input`, `select`, `button` +- Status: Ready to build + +--- + +### Priority 2: Layout Patterns + +**PageHeader** +- Uses: `breadcrumb`, `tabs`, `button`, `separator` +- Status: Ready to build + +**DashboardLayout** +- Uses: `sidebar`, `navigation-menu`, `dropdown-menu`, `avatar`, `scroll-area` +- Status: Ready to build + +**SidebarNav** +- Uses: `sidebar`, `scroll-area`, `badge`, `separator` +- Status: Ready to build + +--- + +### Priority 3: Form Patterns + +**FormField** +- Uses: `form`, `label`, `input`, `textarea`, `select`, all form primitives +- Status: Ready to build + +**SearchableSelect** (Combobox) +- Uses: `command`, `popover`, `button` +- Status: Ready to build (requires custom composition - see shadcn docs) +- Reference: https://ui.shadcn.com/docs/components/combobox + +**DateRangePicker** +- Uses: `calendar`, `popover`, `button` +- Status: Ready to build + +**MultiStepForm** +- Uses: `tabs`, `accordion`, `progress`, `button`, `form` +- Status: Ready to build + +**FormActions** +- Uses: `button`, `separator` +- Status: Ready to build + +--- + +### Priority 4: Feedback Patterns + +**EmptyState** +- Uses: `alert`, `button` +- Status: Ready to build + +**LoadingState** +- Uses: `skeleton`, `progress` +- Status: Ready to build + +**ConfirmDialog** +- Uses: `alert-dialog`, `button` +- Status: Ready to build + +**Toast System** +- Uses: `sonner` +- Status: Ready to build (just wrap sonner with consistent API) + +--- + +### Priority 5: Media/Upload Patterns + +**FileUpload** +- Uses: `button`, `progress`, `badge`, `card` +- Status: Ready to build (needs R2 integration) + +**ImageGallery** +- Uses: `dialog`, `button`, `card` +- Status: Ready to build + +**AvatarUpload** +- Uses: `avatar`, `button`, `dialog` +- Status: Ready to build + +--- + +## 🚫 Components NOT Installed (Intentionally Skipped) + +| Component | Reason for Skipping | +|-----------|---------------------| +| **drawer** | Already have `sheet` which serves the same purpose | +| **context-menu** | Niche use case (right-click menus), have `dropdown-menu` | +| **menubar** | Desktop app pattern, rare in web apps | +| **carousel** | Marketing site component, not core to business apps | +| **resizable** | Code editor pattern, very niche | +| **input-otp** | Only needed for 2FA, add when implementing auth features | +| **collapsible** | Use `accordion` instead for consistent patterns | +| **combobox** | Not a registry component - build from `command` + `popover` | + +--- + +## 🔄 Upgrade Strategy + +When shadcn/ui releases new components or updates: + +1. **Check changelog**: https://ui.shadcn.com/docs/changelog +2. **Review breaking changes** in Tailwind v4 compatibility +3. **Update components**: `pnpm dlx shadcn@latest update` +4. **Test composed patterns** that depend on updated primitives +5. **Update this inventory** with new components + +--- + +## 📚 Resources + +**Official Documentation:** +- Component docs: https://ui.shadcn.com/docs/components +- Installation guide: https://ui.shadcn.com/docs/installation/vite +- Tailwind v4 setup: https://ui.shadcn.com/docs/tailwind-v4 + +**Related Project Docs:** +- [Architecture Overview](./development-planning/architecture-overview.md) - Three-layer system +- [Component Decision Framework](./development-planning/component-decision-framework.md) - Where to put components +- [Pattern Library Plan](./development-planning/pattern-library-plan.md) - Detailed pattern specifications +- [Composed Patterns Roadmap](./COMPOSED_PATTERNS_ROADMAP.md) - Build order and dependencies + +--- + +## ✅ Component Health Check + +Run this checklist after major updates: + +- [ ] All components render without errors +- [ ] Dark/light mode works for all components +- [ ] TypeScript types are up to date +- [ ] Accessibility features intact (keyboard nav, ARIA labels) +- [ ] Mobile responsive behavior maintained +- [ ] No breaking changes in composed patterns + +--- + +**Next Steps:** +1. Start building Priority 1 composed patterns (DataTable, ViewSwitcher) +2. Document patterns as you build them +3. Extract patterns after 3rd use in features +4. Keep this inventory updated with new components + +--- + +*This inventory represents the foundation (Layer 1) for the three-layer architecture. All business applications will use these primitives to build composed patterns (Layer 2) and feature modules (Layer 3).* diff --git a/docs/COMPOSED_PATTERNS_ROADMAP.md b/docs/COMPOSED_PATTERNS_ROADMAP.md new file mode 100644 index 0000000..55bd6b4 --- /dev/null +++ b/docs/COMPOSED_PATTERNS_ROADMAP.md @@ -0,0 +1,895 @@ +# Composed Patterns Development Roadmap + +Build order and dependencies for Layer 2 (composed patterns) based on the three-layer architecture. + +**Last Updated:** 2025-11-10 +**Status:** Foundation complete, ready to build patterns + +--- + +## 🎯 Build Strategy + +**Golden Rule:** Build patterns as you need them, not speculatively. + +### When to Build a Pattern + +1. **After 3rd use** - You've used similar code in 3 different features +2. **Clear reuse case** - Other features will obviously need this +3. **No business logic** - Pattern can be made generic with props +4. **Dependencies ready** - All required shadcn primitives are installed + +### How to Build + +1. Create pattern in `/components/composed/[category]/[Pattern].tsx` +2. Use only Layer 1 primitives (shadcn/ui components) +3. Accept data via props, emit events via callbacks +4. NO Server Actions, NO database access +5. Document with PATTERN_TEMPLATE.md +6. Use in at least 3 features before considering it "proven" + +--- + +## 📋 Priority 1: Data Display Patterns + +**Why First:** Every feature displays data. These provide maximum ROI. + +**Build Order:** Build when you create your first feature that needs data display (products, users, orders, etc.) + +--- + +### 1.1 DataTable ⚡ HIGHEST PRIORITY + +**Location:** `/components/composed/data-display/DataTable.tsx` + +**Dependencies (shadcn):** +- ✅ table +- ✅ pagination +- ✅ checkbox (for row selection) +- ✅ badge (for status columns) +- ✅ dropdown-menu (for row actions) +- ✅ button +- ✅ input (for search/filters) + +**Features to Implement:** +- [ ] Basic table with columns +- [ ] Sortable columns (client-side) +- [ ] Pagination controls +- [ ] Row selection (single/multiple) +- [ ] Column visibility toggle +- [ ] Search/filter input +- [ ] Row actions dropdown +- [ ] Empty state +- [ ] Loading state +- [ ] Mobile responsive (card view on mobile) +- [ ] Export to CSV (optional) + +**Tech Stack:** +- TanStack Table v8 +- Zod for column definitions (optional) +- Server Actions for data fetching (passed as props) + +**Reference:** +- shadcn Data Table: https://ui.shadcn.com/docs/components/data-table +- TanStack Table: https://tanstack.com/table/latest + +**Usage Example:** +```typescript +// Feature component (Layer 3) +export async function ProductList() { + const products = await getProducts(); + + return ( + router.push(`/products/${product.id}`)} + /> + ); +} +``` + +**Build Trigger:** Create this when building your first CRUD feature module. + +--- + +### 1.2 ViewSwitcher ⚡ HIGH PRIORITY + +**Location:** `/components/composed/data-display/ViewSwitcher.tsx` + +**Dependencies (shadcn):** +- ✅ toggle-group + +**Features to Implement:** +- [ ] Toggle between views (table/card/list/kanban/calendar) +- [ ] Persist preference to localStorage +- [ ] Icon indicators for each view +- [ ] Active state styling +- [ ] Responsive (hide labels on mobile) + +**Usage Example:** +```typescript + +``` + +**Build Trigger:** Build immediately after DataTable (you'll want alternative views). + +--- + +### 1.3 CardView + +**Location:** `/components/composed/data-display/CardView.tsx` + +**Dependencies (shadcn):** +- ✅ card +- ✅ badge +- ✅ button +- ✅ dropdown-menu +- ✅ avatar + +**Features to Implement:** +- [ ] Responsive grid layout +- [ ] Card actions menu +- [ ] Image/icon support +- [ ] Status badges +- [ ] Hover effects +- [ ] Empty state +- [ ] Loading skeleton + +**Usage Example:** +```typescript + ( + + )} + actions={[ + { label: 'Edit', onClick: handleEdit }, + { label: 'Delete', onClick: handleDelete } + ]} +/> +``` + +**Build Trigger:** After ViewSwitcher, when you want grid view option. + +--- + +### 1.4 ListView + +**Location:** `/components/composed/data-display/ListView.tsx` + +**Dependencies (shadcn):** +- ✅ separator +- ✅ avatar +- ✅ badge +- ✅ button + +**Features to Implement:** +- [ ] Compact list items +- [ ] Avatar/icon support +- [ ] Secondary text +- [ ] Right-side actions +- [ ] Grouped lists with headers +- [ ] Sticky section headers +- [ ] Empty state + +**Usage Example:** +```typescript + user.role} + renderItem={(user) => ( + + )} +/> +``` + +**Build Trigger:** After CardView, for mobile-friendly alternative. + +--- + +### 1.5 CollectionContainer + +**Location:** `/components/composed/data-display/CollectionContainer.tsx` + +**Dependencies (shadcn):** +- ✅ All above patterns +- ✅ input (search) +- ✅ select (filters) +- ✅ button (actions) + +**Features to Implement:** +- [ ] Page header with title/actions +- [ ] Search bar +- [ ] Filter controls +- [ ] Sort dropdown +- [ ] View switcher integration +- [ ] Content area (renders active view) +- [ ] Loading/error/empty states + +**Usage Example:** +```typescript + }, + { type: 'card', render: (data) => } + ]} + filters={[ + { field: 'category', label: 'Category', options: categories }, + { field: 'status', label: 'Status', options: ['active', 'draft'] } + ]} + actions={[{ label: 'Add Product', onClick: openDialog, icon: Plus }]} + onSearch={handleSearch} +/> +``` + +**Build Trigger:** After all data display patterns, when you want turnkey collection pages. + +--- + +## 📋 Priority 2: Layout Patterns + +**Why Second:** Consistent layouts speed up every feature. + +**Build Order:** Build after first 2-3 features reveal common layout needs. + +--- + +### 2.1 PageHeader ⚡ HIGH PRIORITY + +**Location:** `/components/composed/layouts/PageHeader.tsx` + +**Dependencies (shadcn):** +- ✅ breadcrumb +- ✅ tabs +- ✅ button +- ✅ separator + +**Features to Implement:** +- [ ] Page title + description +- [ ] Breadcrumbs +- [ ] Action buttons (right-aligned) +- [ ] Back button (optional) +- [ ] Tab navigation (optional) +- [ ] Responsive (stack on mobile) + +**Usage Example:** +```typescript +Export, + + ]} + tabs={[ + { label: 'All', value: 'all', href: '/products' }, + { label: 'Active', value: 'active', href: '/products?status=active' } + ]} +/> +``` + +**Build Trigger:** Immediately, you'll use this on every page. + +--- + +### 2.2 DashboardLayout + +**Location:** `/components/composed/layouts/DashboardLayout.tsx` + +**Dependencies (shadcn):** +- ✅ sidebar +- ✅ navigation-menu +- ✅ dropdown-menu +- ✅ avatar +- ✅ scroll-area + +**Features to Implement:** +- [ ] Collapsible sidebar +- [ ] Top navigation bar +- [ ] Mobile drawer +- [ ] User menu +- [ ] Breadcrumbs integration +- [ ] Content area +- [ ] Footer (optional) + +**Build Trigger:** Already exists (via layouts module). May need refinement. + +--- + +### 2.3 SidebarNav + +**Location:** `/components/composed/layouts/SidebarNav.tsx` + +**Dependencies (shadcn):** +- ✅ sidebar +- ✅ scroll-area +- ✅ badge +- ✅ separator + +**Features to Implement:** +- [ ] Nested navigation items +- [ ] Active state highlighting +- [ ] Icon support +- [ ] Badge/notification counts +- [ ] Collapsible groups +- [ ] Mobile-friendly + +**Build Trigger:** Already exists (via layouts module). May need refinement. + +--- + +## 📋 Priority 3: Form Patterns + +**Why Third:** Forms are everywhere, good patterns save hours per feature. + +**Build Order:** Build as you encounter complex form needs. + +--- + +### 3.1 SearchableSelect (Combobox) ⚡ HIGH PRIORITY + +**Location:** `/components/composed/forms/SearchableSelect.tsx` + +**Dependencies (shadcn):** +- ✅ command +- ✅ popover +- ✅ button + +**Features to Implement:** +- [ ] Search/filter options +- [ ] Keyboard navigation +- [ ] Multi-select mode (optional) +- [ ] Create new option (optional) +- [ ] Async loading support +- [ ] Custom option rendering +- [ ] Clear selection + +**Reference:** +- shadcn Combobox: https://ui.shadcn.com/docs/components/combobox + +**Usage Example:** +```typescript + ( +
+ + {user.name} +
+ )} +/> +``` + +**Build Trigger:** When you need to select from 10+ options (categories, users, tags). + +--- + +### 3.2 DateRangePicker + +**Location:** `/components/composed/forms/DateRangePicker.tsx` + +**Dependencies (shadcn):** +- ✅ calendar +- ✅ popover +- ✅ button + +**Features to Implement:** +- [ ] Date range selection +- [ ] Preset ranges (Today, Last 7 days, Last 30 days, etc.) +- [ ] Clear selection +- [ ] Timezone aware +- [ ] Min/max date constraints +- [ ] Format customization + +**Usage Example:** +```typescript + +``` + +**Build Trigger:** When building reports/analytics features. + +--- + +### 3.3 FormField Enhancement + +**Location:** `/components/composed/forms/FormField.tsx` + +**Dependencies (shadcn):** +- ✅ form +- ✅ label +- ✅ All input primitives + +**Features to Implement:** +- [ ] Unified field wrapper +- [ ] Required indicator +- [ ] Character counter +- [ ] Help text +- [ ] Error message display +- [ ] Field-level loading state + +**Usage Example:** +```typescript + + + +``` + +**Build Trigger:** After building 2-3 forms with repetitive field markup. + +--- + +### 3.4 MultiStepForm + +**Location:** `/components/composed/forms/MultiStepForm.tsx` + +**Dependencies (shadcn):** +- ✅ tabs +- ✅ accordion +- ✅ progress +- ✅ button +- ✅ form + +**Features to Implement:** +- [ ] Step indicator with progress +- [ ] Next/Previous navigation +- [ ] Per-step validation +- [ ] Data persistence between steps +- [ ] Review step +- [ ] Save draft functionality + +**Usage Example:** +```typescript + +``` + +**Build Trigger:** When building onboarding or complex creation flows. + +--- + +### 3.5 FormActions + +**Location:** `/components/composed/forms/FormActions.tsx` + +**Dependencies (shadcn):** +- ✅ button +- ✅ separator + +**Features to Implement:** +- [ ] Save/Cancel buttons +- [ ] Loading states +- [ ] Disabled states +- [ ] Custom actions +- [ ] Sticky footer (optional) +- [ ] Responsive layout + +**Usage Example:** +```typescript + +``` + +**Build Trigger:** After building 2-3 forms with similar button layouts. + +--- + +## 📋 Priority 4: Feedback Patterns + +**Why Fourth:** Good feedback improves UX and perceived performance. + +**Build Order:** Build as you encounter the need for consistent feedback. + +--- + +### 4.1 EmptyState ⚡ HIGH PRIORITY + +**Location:** `/components/composed/feedback/EmptyState.tsx` + +**Dependencies (shadcn):** +- ✅ alert +- ✅ button + +**Features to Implement:** +- [ ] Icon/illustration +- [ ] Title and description +- [ ] Call-to-action button +- [ ] Variants (no-data, no-results, error, no-permission) +- [ ] Custom rendering + +**Usage Example:** +```typescript + +``` + +**Build Trigger:** Immediately, you'll use this everywhere. + +--- + +### 4.2 LoadingState + +**Location:** `/components/composed/feedback/LoadingState.tsx` + +**Dependencies (shadcn):** +- ✅ skeleton +- ✅ progress + +**Features to Implement:** +- [ ] Skeleton loaders (table, card, list) +- [ ] Spinner variants +- [ ] Progress bar +- [ ] Loading messages +- [ ] Full-page overlay option + +**Usage Example:** +```typescript + +``` + +**Build Trigger:** Immediately, for Suspense fallbacks. + +--- + +### 4.3 ConfirmDialog + +**Location:** `/components/composed/feedback/ConfirmDialog.tsx` + +**Dependencies (shadcn):** +- ✅ alert-dialog +- ✅ button + +**Features to Implement:** +- [ ] Variants (danger, warning, info) +- [ ] Custom title/description +- [ ] Async action support +- [ ] Loading state during action +- [ ] Keyboard shortcuts (Enter/Esc) + +**Usage Example:** +```typescript + { + await deleteProduct(id); + toast.success('Product deleted'); + }} +/> +``` + +**Build Trigger:** When implementing delete actions. + +--- + +### 4.4 Toast System + +**Location:** Already provided by `sonner` + +**Usage:** +```typescript +import { toast } from 'sonner'; + +toast.success('Product created successfully'); +toast.error('Failed to create product'); +toast.info('Feature coming soon'); +toast.warning('Unsaved changes'); +``` + +**Build Trigger:** Already available, just document usage patterns. + +--- + +## 📋 Priority 5: Media/Upload Patterns + +**Why Fifth:** File handling is common but requires careful implementation. + +**Build Order:** Build when implementing file upload features. + +--- + +### 5.1 FileUpload + +**Location:** `/components/composed/media/FileUpload.tsx` + +**Dependencies (shadcn):** +- ✅ button +- ✅ progress +- ✅ badge +- ✅ card + +**External Dependencies:** +- react-dropzone (optional, for better DX) +- R2 integration (via Server Action) + +**Features to Implement:** +- [ ] Drag and drop zone +- [ ] Click to upload +- [ ] Multiple file support +- [ ] File type validation +- [ ] Size validation +- [ ] Upload progress +- [ ] Preview thumbnails +- [ ] Remove files +- [ ] Error states + +**Usage Example:** +```typescript + { + const result = await uploadToR2(files); + return result; + }} + existingFiles={product.images} + onRemove={handleRemoveImage} +/> +``` + +**Build Trigger:** When building features with image/file uploads. + +--- + +### 5.2 ImageGallery + +**Location:** `/components/composed/media/ImageGallery.tsx` + +**Dependencies (shadcn):** +- ✅ dialog +- ✅ button +- ✅ card + +**Features to Implement:** +- [ ] Grid layout +- [ ] Lightbox/modal view +- [ ] Image zoom +- [ ] Navigation (prev/next) +- [ ] Thumbnails +- [ ] Delete action +- [ ] Download action +- [ ] Responsive grid + +**Build Trigger:** When displaying multiple images per item. + +--- + +### 5.3 AvatarUpload + +**Location:** `/components/composed/media/AvatarUpload.tsx` + +**Dependencies (shadcn):** +- ✅ avatar +- ✅ button +- ✅ dialog + +**External Dependencies:** +- react-image-crop (for cropping) + +**Features to Implement:** +- [ ] Circle preview +- [ ] Crop/resize UI +- [ ] Replace/remove +- [ ] Fallback initials +- [ ] Loading state +- [ ] Upload to R2 + +**Build Trigger:** When implementing user profiles or team members. + +--- + +## 📋 Priority 6: Navigation Patterns + +**Build Order:** As needed for complex navigation. + +--- + +### 6.1 Breadcrumbs (Wrapper) + +**Location:** `/components/composed/navigation/Breadcrumbs.tsx` + +**Dependencies (shadcn):** +- ✅ breadcrumb + +**Features to Implement:** +- [ ] Auto-generate from route +- [ ] Custom labels +- [ ] Overflow handling +- [ ] Mobile responsive + +**Build Trigger:** Low priority (shadcn breadcrumb is already good). + +--- + +### 6.2 Pagination (Enhanced) + +**Location:** `/components/composed/navigation/Pagination.tsx` + +**Dependencies (shadcn):** +- ✅ pagination +- ✅ select + +**Features to Implement:** +- [ ] Items per page selector +- [ ] Jump to page input +- [ ] Total count display +- [ ] First/last buttons +- [ ] Responsive (compact on mobile) + +**Build Trigger:** When DataTable pagination needs enhancement. + +--- + +## 🎯 Build Phases + +### Phase 1: Foundation (Week 1-2) + +**Build when creating first CRUD feature:** +1. ✅ PageHeader +2. ✅ EmptyState +3. ✅ LoadingState +4. ✅ DataTable +5. ✅ ViewSwitcher + +**Outcome:** Can build complete feature pages with data display. + +--- + +### Phase 2: Forms & Actions (Week 3-4) + +**Build when creating second feature with forms:** +1. ✅ FormField (enhanced) +2. ✅ FormActions +3. ✅ ConfirmDialog +4. ✅ SearchableSelect (Combobox) + +**Outcome:** Can build complex forms with rich interactions. + +--- + +### Phase 3: Alternative Views (Week 5-6) + +**Build when users request different data views:** +1. ✅ CardView +2. ✅ ListView +3. ✅ CollectionContainer +4. ✅ DateRangePicker (if needed) + +**Outcome:** Flexible data display options. + +--- + +### Phase 4: Advanced Features (Week 7+) + +**Build as specific needs arise:** +1. ✅ MultiStepForm (if building onboarding) +2. ✅ FileUpload (if uploading files) +3. ✅ ImageGallery (if displaying images) +4. ✅ Command Palette (for power users) + +**Outcome:** Polish and advanced UX features. + +--- + +## 📊 Progress Tracking + +| Pattern | Priority | Dependencies Ready | Status | Built Date | +|---------|----------|-------------------|--------|------------| +| DataTable | P1 | ✅ | ⏸️ Not started | - | +| ViewSwitcher | P1 | ✅ | ⏸️ Not started | - | +| CardView | P1 | ✅ | ⏸️ Not started | - | +| ListView | P1 | ✅ | ⏸️ Not started | - | +| CollectionContainer | P1 | ✅ | ⏸️ Not started | - | +| PageHeader | P2 | ✅ | ⏸️ Not started | - | +| DashboardLayout | P2 | ✅ | ✅ Exists | 2025-11-08 | +| SidebarNav | P2 | ✅ | ✅ Exists | 2025-11-08 | +| SearchableSelect | P3 | ✅ | ⏸️ Not started | - | +| DateRangePicker | P3 | ✅ | ⏸️ Not started | - | +| FormField | P3 | ✅ | ⏸️ Not started | - | +| MultiStepForm | P3 | ✅ | ⏸️ Not started | - | +| FormActions | P3 | ✅ | ⏸️ Not started | - | +| EmptyState | P4 | ✅ | ⏸️ Not started | - | +| LoadingState | P4 | ✅ | ⏸️ Not started | - | +| ConfirmDialog | P4 | ✅ | ⏸️ Not started | - | +| FileUpload | P5 | ✅ | ⏸️ Not started | - | +| ImageGallery | P5 | ✅ | ⏸️ Not started | - | +| AvatarUpload | P5 | ✅ | ⏸️ Not started | - | + +--- + +## 🔄 Pattern Review Schedule + +After building each pattern: + +1. **Document it** - Use PATTERN_TEMPLATE.md +2. **Test in 3 features** - Ensure it's truly reusable +3. **Refine API** - Improve props based on usage +4. **Add to examples** - Create usage examples +5. **Mark complete** - Update progress table + +--- + +## 📚 Resources + +**Official References:** +- shadcn/ui Components: https://ui.shadcn.com/docs/components +- shadcn/ui Pro Blocks: https://ui.shadcn.com/blocks +- TanStack Table: https://tanstack.com/table/latest +- React Hook Form: https://react-hook-form.com/ + +**Inspiration:** +- Tremor: https://tremor.so/ (Data viz) +- Magic UI: https://magicui.design/ (Animated components) +- Aceternity UI: https://ui.aceternity.com/ (Creative components) + +**Related Docs:** +- [Component Inventory](./COMPONENT_INVENTORY.md) - All installed primitives +- [Pattern Library Plan](./development-planning/pattern-library-plan.md) - Detailed specs +- [Component Decision Framework](./development-planning/component-decision-framework.md) - Where to put components +- [Module Development Guide](./development-planning/module-development-guide.md) - Building features + +--- + +**Next Action:** Start building your first feature module (e.g., products, invoices, tasks). Extract DataTable pattern when you need it in the 3rd feature. diff --git a/docs/NEXTJS_16_UPGRADE.md b/docs/NEXTJS_16_UPGRADE.md new file mode 100644 index 0000000..516e981 --- /dev/null +++ b/docs/NEXTJS_16_UPGRADE.md @@ -0,0 +1,205 @@ +# Next.js 16 Upgrade Research + +**Date:** 2025-11-08 +**Current Version:** Next.js 15.4.6 +**Target Version:** Next.js 16.x + +--- + +## Executive Summary + +**Recommendation: WAIT for dependency support** + +Next.js 16 was released October 21, 2025, but critical dependencies are not yet compatible: +- **better-auth**: No Next.js 16 support (Issue #5263) +- **@opennextjs/cloudflare**: Proxy system partially supported (Issue #972) + +**Timeline:** Reassess in Q1 2026 when ecosystem matures. + +--- + +## Current Stack Status + +### Installed Versions +- Next.js: 15.4.6 ✅ +- React: 19.1.0 ✅ +- @opennextjs/cloudflare: 1.11.1 ✅ (upgraded 2025-11-08) +- wrangler: 4.46.0 ✅ (upgraded 2025-11-08) +- better-auth: 1.3.9 ⚠️ (blocks Next.js 16) + +### Architecture +- Platform: Cloudflare Workers via @opennextjs/cloudflare +- Auth: better-auth with session management +- Database: Drizzle ORM + D1 +- Framework: App Router, Server Components, Server Actions + +--- + +## Next.js 16 Key Changes + +### Major Features +1. **Turbopack Stable** - Now default bundler (webpack deprecated) +2. **Proxy System** - Replaces `middleware.ts` with `proxy.ts` +3. **Cache Components** - PPR with `use cache` directive +4. **React 19.2** - View Transitions, useEffectEvent + +### Breaking Changes + +#### 1. Async Request APIs ✅ COMPATIBLE +Project already uses async params pattern: +```typescript +params: Promise<{ id: string }> +const { id } = await params; +``` + +#### 2. Middleware → Proxy ❌ BLOCKER +Must rename `middleware.ts` → `proxy.ts` and update export: +```typescript +// proxy.ts +export async function proxy(request: NextRequest) { + // Same logic as middleware +} +``` + +**Issue:** @opennextjs/cloudflare doesn't fully support proxy yet (tracking in #972) + +#### 3. Minimum Requirements ✅ MET +- Node.js 20.9+ ✅ +- TypeScript 5.1+ ✅ +- React 19+ ✅ + +--- + +## Dependency Compatibility + +### Critical Blockers + +#### better-auth ❌ INCOMPATIBLE +- **Version:** 1.3.9 +- **Status:** No Next.js 16 support +- **Issue:** https://github.com/better-auth/better-auth/issues/5263 +- **Impact:** Middleware → proxy changes break session handling +- **Timeline:** Unknown - no committed release date + +#### @opennextjs/cloudflare ⚠️ PARTIAL +- **Version:** 1.11.1 +- **Status:** Proxy not fully supported +- **Issue:** https://github.com/opennextjs/opennextjs-cloudflare/issues/972 +- **Workaround:** Use deprecated middleware.ts naming (generates warnings) +- **Timeline:** Active development - likely 2-4 weeks + +### Compatible Dependencies ✅ + +- **drizzle-orm** - Framework-agnostic ORM +- **Radix UI** - React 19 compatible +- **react-hook-form, zod, tailwind** - Framework-agnostic +- **Cloudflare bindings** - Worker-level, independent of Next.js + +--- + +## Upgrade Path (When Ready) + +### Pre-Upgrade Checklist +- [ ] better-auth releases Next.js 16 compatible version +- [ ] @opennextjs/cloudflare completes Issue #972 +- [ ] Review community success stories +- [ ] Backup D1 database +- [ ] Create git checkpoint + +### Upgrade Steps + +```bash +# 1. Update dependencies +pnpm install next@16 react@latest react-dom@latest +pnpm install @opennextjs/cloudflare@latest better-auth@latest + +# 2. Run automated codemod +npx @next/codemod@canary upgrade latest + +# 3. Manual changes +# - Rename middleware.ts → proxy.ts +# - Update export: middleware → proxy +# - Update better-auth integration (follow their migration guide) + +# 4. Update wrangler.jsonc if needed +# "compatibility_date": "2025-11-08" or later + +# 5. Clear cache and test +rm -rf .next +pnpm run dev + +# 6. Deploy to preview and validate +pnpm run deploy:preview +``` + +**Estimated effort when ready:** 2-4 hours + +--- + +## Monitoring Strategy + +### Weekly Checks (Until Dependencies Ready) + +**better-auth:** +- GitHub: https://github.com/better-auth/better-auth/issues/5263 +- NPM: https://www.npmjs.com/package/better-auth +- Look for: "Next.js 16" in changelog + +**@opennextjs/cloudflare:** +- GitHub: https://github.com/opennextjs/opennextjs-cloudflare/issues/972 +- NPM: https://www.npmjs.com/package/@opennextjs/cloudflare +- Releases: https://github.com/opennextjs/opennextjs-cloudflare/releases + +**Community:** +- Reddit: r/nextjs +- X/Twitter: #nextjs hashtag +- Discord: Next.js Discord server + +### Reassessment Date +**December 2025** - Check dependency status and reassess upgrade viability. + +--- + +## Alternative Auth Options (If better-auth Never Updates) + +### Option A: NextAuth.js v5 +- Has Next.js 16 support roadmap +- Migration effort: 4-8 hours +- Loss of better-auth features + +### Option B: Custom Auth +- Use jose (JWT library) + session cookies +- Migration effort: 8-16 hours +- Full control, more maintenance + +### Option C: Cloudflare Access +- Zero Trust authentication +- No code changes +- Limits to Cloudflare ecosystem + +--- + +## Risk Assessment + +| Risk | Likelihood | Impact | Mitigation | +|------|------------|--------|------------| +| Auth breaks in production | HIGH | CRITICAL | Wait for better-auth update | +| Middleware/proxy incompatibility | MEDIUM | HIGH | Use workaround temporarily | +| Cloudflare binding issues | LOW | HIGH | Test in preview | +| Turbopack build failures | LOW | MEDIUM | Revert with --webpack flag | +| React 19 component issues | LOW | MEDIUM | Already using React 19.1.0 | + +--- + +## References + +- [Next.js 16 Release](https://nextjs.org/blog/next-16) +- [Upgrade Guide](https://nextjs.org/docs/app/guides/upgrading/version-16) +- [better-auth Issue #5263](https://github.com/better-auth/better-auth/issues/5263) +- [OpenNext Issue #972](https://github.com/opennextjs/opennextjs-cloudflare/issues/972) +- [Codemod Tool](https://nextjs.org/docs/app/guides/codemod) + +--- + +**Last Updated:** 2025-11-08 +**Next Review:** 2025-12-08 diff --git a/docs/development-planning/README.md b/docs/development-planning/README.md new file mode 100644 index 0000000..77eb0be --- /dev/null +++ b/docs/development-planning/README.md @@ -0,0 +1,401 @@ +# Full-Stack Next.js + Cloudflare Architecture Documentation + +Complete reference documentation for building client applications with the three-layer architecture pattern. + +--- + +## 📚 Documentation Set + +This documentation package contains comprehensive guides for developing with the Next.js + Cloudflare stack using a three-layer component architecture. + +### Core Documents + +1. **[Architecture Overview](./architecture-overview.md)** - Start here + - Explains the three-layer architecture + - Directory structure + - Benefits and principles + - Evolution strategy + +2. **[Component Decision Framework](./component-decision-framework.md)** - For daily decisions + - Decision tree for component placement + - Detailed criteria for each layer + - Common scenarios with solutions + - Red flags and anti-patterns + +3. **[Pattern Library Plan](./pattern-library-plan.md)** - For building reusable patterns + - Prioritized list of patterns to build + - Detailed specifications for each pattern + - Implementation strategy + - Resources and inspiration + +4. **[Module Development Guide](./module-development-guide.md)** - For building features + - Step-by-step module creation + - Best practices + - Code examples + - Common patterns + +5. **[Architecture Quick Reference](./architecture-quick-reference.md)** - For quick lookups + - Fast decision trees + - Common tasks and commands + - Code snippets + - Troubleshooting + +--- + +## 🎯 How to Use This Documentation + +### For First-Time Setup + +1. Read **Architecture Overview** to understand the system +2. Skim **Component Decision Framework** to learn the rules +3. Reference **Quick Reference** while working + +### When Building a New Feature + +1. Use **Module Development Guide** for step-by-step instructions +2. Check **Component Decision Framework** when unsure about placement +3. Keep **Quick Reference** open for commands and snippets + +### When Building Reusable Patterns + +1. Consult **Pattern Library Plan** for pattern ideas +2. Follow **Component Decision Framework** for proper abstraction +3. Reference existing patterns in **Pattern Library Plan** + +### When Stuck or Confused + +1. Check **Quick Reference** decision trees +2. Review **Component Decision Framework** scenarios +3. Look at examples in **Module Development Guide** + +--- + +## 🏗️ Architecture Summary + +### The Three Layers + +``` +┌─────────────────────────────────────────────────┐ +│ Layer 3: Feature Modules │ +│ /modules/[feature]/ │ +│ • Business logic │ +│ • Server Actions │ +│ • Database access │ +│ • Feature-specific components │ +└─────────────────────────────────────────────────┘ + ↓ uses +┌─────────────────────────────────────────────────┐ +│ Layer 2: Composed Patterns │ +│ /components/composed/ │ +│ • Reusable UI patterns │ +│ • No business logic │ +│ • Used across 3+ features │ +│ • Prop-based configuration │ +└─────────────────────────────────────────────────┘ + ↓ uses +┌─────────────────────────────────────────────────┐ +│ Layer 1: UI Primitives │ +│ /components/ui/ │ +│ • shadcn/ui components │ +│ • Single-responsibility │ +│ • Maximum flexibility │ +│ • No opinions │ +└─────────────────────────────────────────────────┘ +``` + +### Quick Decision Guide + +**"Where should this component go?"** + +``` +Has business logic? + → YES: /modules/[feature]/components/ + → NO: Continue... + +Will be used in 3+ features? + → YES: /components/composed/[category]/ + → NO: Continue... + +Is it a shadcn component? + → YES: /components/ui/ + → NO: /components/shared/ or inline +``` + +--- + +## 🚀 Getting Started Checklist + +### Phase 1: Foundation Setup +- [ ] Read Architecture Overview +- [ ] Understand the three layers +- [ ] Review existing repo structure +- [ ] Create `/components/composed/` directory structure + +### Phase 2: Build First Feature +- [ ] Follow Module Development Guide +- [ ] Create database schema +- [ ] Build CRUD operations +- [ ] Create UI components +- [ ] Test thoroughly + +### Phase 3: Extract Patterns +- [ ] Identify repeated UI patterns (after 3rd use) +- [ ] Extract to `/components/composed/` +- [ ] Document the pattern +- [ ] Use in multiple features + +### Phase 4: Refine & Scale +- [ ] Build remaining priority patterns +- [ ] Document learnings +- [ ] Share with team +- [ ] Iterate based on usage + +--- + +## 📖 Document Quick Links + +### By Use Case + +**Building a new feature?** +→ [Module Development Guide](./module-development-guide.md) + +**Not sure where a component goes?** +→ [Component Decision Framework](./component-decision-framework.md) + +**Need to extract a reusable pattern?** +→ [Pattern Library Plan](./pattern-library-plan.md) + +**Looking for a quick answer?** +→ [Architecture Quick Reference](./architecture-quick-reference.md) + +**Want to understand the big picture?** +→ [Architecture Overview](./architecture-overview.md) + +--- + +## 💡 Key Principles + +1. **Build patterns as you need them** - Don't over-architect upfront +2. **Extract after 3rd use** - Prove the pattern before abstracting +3. **Server Components by default** - Only use 'use client' when needed +4. **Clear layer boundaries** - No business logic in patterns +5. **Consistent patterns** - Reuse rather than rebuild + +--- + +## 🛠️ Development Workflow + +### Daily Development + +```bash +# Terminal 1: Wrangler (for D1 access) +pnpm run wrangler:dev + +# Terminal 2: Next.js (with HMR) +pnpm run dev + +# Browser: http://localhost:3000 +``` + +### When You Need to... + +**Create a new feature module:** +```bash +mkdir -p src/modules/[feature]/{actions,components,hooks,models,schemas} +# Then follow Module Development Guide +``` + +**Add a database table:** +```bash +# Edit src/db/schema.ts +pnpm run db:generate:named "add_[table]" +pnpm run db:migrate:local +``` + +**Extract a reusable pattern:** +```bash +# 1. Identify the pattern (used 3+ times) +# 2. Create in /components/composed/[category]/ +# 3. Make it generic (props-based) +# 4. Document usage +# 5. Replace usage in features +``` + +**Deploy to production:** +```bash +pnpm run build:cf +pnpm run db:migrate:prod +pnpm run deploy +``` + +--- + +## 📝 Common Patterns Reference + +### Server Action Structure +```typescript +'use server'; +export async function myAction(input: Input): Promise> { + try { + const session = await auth(); // 1. Authenticate + if (!session?.user) return { success: false, error: 'Unauthorized' }; + + const validated = schema.parse(input); // 2. Validate + const result = await db.insert(...); // 3. Database operation + revalidatePath('/path'); // 4. Revalidate + + return { success: true, data: result }; // 5. Return + } catch (error) { + return { success: false, error: 'Message' }; + } +} +``` + +### Component with Server Action +```typescript +// Server Component (default) +export async function MyList() { + const result = await getItems(); + if (!result.success) return ; + return ; +} + +// Client Component (when needed) +'use client'; +export function MyForm() { + const form = useForm({ + resolver: zodResolver(schema), + }); + + const onSubmit = async (data) => { + const result = await createItem(data); + if (result.success) toast.success('Created!'); + }; + + return
...
; +} +``` + +--- + +## 🎓 Learning Path + +### Week 1-2: Foundation +- [ ] Understand the three-layer architecture +- [ ] Build your first feature module +- [ ] Create basic CRUD operations +- [ ] Add a simple page + +### Week 3-4: Patterns +- [ ] Identify repeated UI patterns +- [ ] Extract first reusable pattern (probably DataTable) +- [ ] Build PageHeader and EmptyState patterns +- [ ] Document your patterns + +### Week 5-6: Advanced +- [ ] Build form patterns (multi-step, fields) +- [ ] Add file upload with R2 +- [ ] Create view switcher (table/card/list) +- [ ] Optimize for mobile + +### Week 7-8: Polish +- [ ] Complete pattern library (priority patterns) +- [ ] Add loading/error states everywhere +- [ ] Improve accessibility +- [ ] Document everything + +--- + +## 🆘 Troubleshooting + +### "I don't know where to put this component" +→ Use the decision tree in [Component Decision Framework](./component-decision-framework.md) + +### "This pattern has business logic" +→ It's not a pattern, it's a feature component. Keep it in the module. + +### "I'm repeating code across features" +→ After 3rd use, extract to `/components/composed/`. See [Pattern Library Plan](./pattern-library-plan.md) + +### "My Server Action isn't updating the UI" +→ Did you call `revalidatePath()`? See [Module Development Guide](./module-development-guide.md) + +### "I need more examples" +→ Check the [Module Development Guide](./module-development-guide.md) for detailed examples + +--- + +## 🔄 Keeping Documentation Updated + +As your project evolves: + +1. **Document new patterns** in Pattern Library Plan +2. **Add examples** to Module Development Guide +3. **Update decision framework** if rules change +4. **Share learnings** with your team +5. **Refine based on experience** + +This documentation is a living guide that should evolve with your understanding and needs. + +--- + +## 📊 Success Metrics + +You'll know the architecture is working when: + +- ✅ You can build new features faster +- ✅ Components are easy to find +- ✅ Code duplication decreases +- ✅ New developers onboard quickly +- ✅ Bugs are isolated to modules +- ✅ Refactoring is straightforward +- ✅ Tests are easy to write + +--- + +## 🤝 Contributing Back + +If you discover: +- Better patterns +- Clearer explanations +- Useful examples +- Common gotchas + +Consider contributing back to the original template or sharing with the community. + +--- + +## 📞 Support + +When you need help: + +1. Check [Architecture Quick Reference](./architecture-quick-reference.md) first +2. Review relevant detailed guide +3. Look at existing code examples +4. Experiment in a feature branch +5. Ask your team or community + +--- + +## 🎯 Remember + +> "Build patterns as you need them, not speculatively. +> Extract after the 3rd use, not before. +> Keep layers separated, keep code clean." + +--- + +## 📅 Next Steps + +1. **Today:** Read Architecture Overview +2. **This Week:** Build your first module following Module Development Guide +3. **This Month:** Extract your first 3-5 reusable patterns +4. **This Quarter:** Have a comprehensive pattern library + +Good luck building amazing applications! 🚀 + +--- + +*Last Updated: 2025* +*For: Full-Stack Next.js + Cloudflare Template* +*Repository: github.com/jezweb/fullstack-next-cloudflare* diff --git a/docs/development-planning/architecture-overview.md b/docs/development-planning/architecture-overview.md new file mode 100644 index 0000000..a6fea6c --- /dev/null +++ b/docs/development-planning/architecture-overview.md @@ -0,0 +1,275 @@ +# Full-Stack Next.js + Cloudflare Architecture + +## Overview + +This architecture uses a three-layer component structure designed for building reusable, maintainable client applications on Cloudflare's edge infrastructure. + +## The Three-Layer Architecture + +### Layer 1: UI Primitives (`/components/ui/`) + +**Purpose:** Unstyled, unopinionated building blocks + +**What Lives Here:** +- shadcn/ui components (Button, Input, Dialog, Select, etc.) +- Single-responsibility components +- No business logic +- Maximum flexibility + +**Examples:** +- `button.tsx` +- `input.tsx` +- `dialog.tsx` +- `card.tsx` +- `select.tsx` + +**Rules:** +- Keep shadcn components as-is +- Don't add business logic +- These are imported by higher layers + +--- + +### Layer 2: Composed Patterns (`/components/composed/`) + +**Purpose:** Reusable UI patterns that solve common problems + +**What Lives Here:** +- Combinations of Layer 1 primitives +- Opinionated patterns without business logic +- Reusable across multiple features +- Configured via props + +**Organization:** +``` +/components/composed/ +├── data-display/ # Tables, cards, lists, view switchers +├── layouts/ # Page shells, headers, sidebars +├── forms/ # Multi-step forms, field patterns, actions +├── feedback/ # Empty states, loading, errors +├── media/ # File uploads, image galleries +└── navigation/ # Breadcrumbs, tabs, pagination +``` + +**Examples:** +- `DataTable` - sortable, filterable table with actions +- `PageHeader` - title + actions + breadcrumbs +- `EmptyState` - icon + message + action +- `FileUpload` - drag-drop with progress +- `MultiStepForm` - wizard-style form flow + +**Rules:** +- No direct database access +- No Server Actions +- Accept data via props +- Emit events via callbacks +- Must be reusable across 3+ features + +--- + +### Layer 3: Feature Modules (`/modules/`) + +**Purpose:** Complete features with business logic + +**What Lives Here:** +- Domain-specific features +- Business logic and data handling +- Server Actions +- Database schemas +- Feature-specific components + +**Module Structure:** +``` +/modules/[feature]/ +├── actions/ # Server Actions +├── components/ # Feature-specific UI +├── hooks/ # Feature hooks +├── models/ # Type definitions +├── schemas/ # Zod validation schemas +└── utils/ # Feature utilities +``` + +**Examples:** +- `auth` - Authentication flows +- `todos` - Todo CRUD operations +- `users` - User management +- `products` - Product catalog + +**Rules:** +- Can use both Layer 1 and Layer 2 +- Contains business logic +- Uses Server Actions for mutations +- Includes validation schemas + +--- + +## Data Flow + +``` +User Interaction + ↓ +Feature Module Component (Layer 3) + ↓ +Server Action (business logic) + ↓ +Database/API + ↑ +Feature Module Component + ↓ +Composed Pattern Component (Layer 2) + ↓ +UI Primitives (Layer 1) + ↓ +User sees result +``` + +--- + +## When to Create Each Layer + +### Create a UI Primitive when: +- ✅ It's a single, unopinionated building block +- ✅ shadcn/ui provides it +- ✅ It needs maximum flexibility +- ❌ Don't create your own primitives unless necessary + +### Create a Composed Pattern when: +- ✅ You're combining 2+ primitives repeatedly +- ✅ The pattern appears in 3+ different features +- ✅ It solves a common UI problem +- ✅ It has no business logic +- ❌ Don't create patterns speculatively + +### Create a Feature Module when: +- ✅ It represents a business domain +- ✅ It needs database access +- ✅ It has specific business rules +- ✅ It includes CRUD operations + +--- + +## Directory Structure + +``` +src/ +├── app/ # Next.js App Router +│ ├── (auth)/ # Auth pages +│ ├── api/ # API routes +│ ├── dashboard/ # Dashboard pages +│ └── globals.css +│ +├── components/ +│ ├── ui/ # Layer 1: shadcn primitives +│ │ ├── button.tsx +│ │ ├── input.tsx +│ │ └── ... +│ │ +│ ├── composed/ # Layer 2: Reusable patterns +│ │ ├── data-display/ +│ │ ├── layouts/ +│ │ ├── forms/ +│ │ ├── feedback/ +│ │ ├── media/ +│ │ └── navigation/ +│ │ +│ └── shared/ # One-off components +│ └── Logo.tsx +│ +├── modules/ # Layer 3: Features +│ ├── auth/ +│ ├── todos/ +│ ├── users/ +│ └── ... +│ +├── db/ # Database +│ ├── index.ts +│ └── schema.ts +│ +├── lib/ # Shared utilities +└── services/ # Business services +``` + +--- + +## Benefits of This Architecture + +1. **Clear Separation of Concerns** + - UI primitives are dumb and flexible + - Patterns are smart but generic + - Modules contain business logic + +2. **Reusability** + - Build once, use everywhere + - Easy to find components + - Reduces duplication + +3. **Maintainability** + - Changes to primitives affect everything (rare) + - Changes to patterns affect multiple features (carefully) + - Changes to modules are isolated (common) + +4. **Testability** + - Each layer can be tested independently + - Patterns can be tested without business logic + - Modules can be tested with mock patterns + +5. **Developer Experience** + - Clear mental model + - Easy to navigate + - Know where to put new code + +--- + +## Evolution Strategy + +### Phase 1: Foundation (Current) +- shadcn/ui components installed +- Basic feature modules (auth, todos) +- Initial layout components + +### Phase 2: Pattern Extraction (Next) +- Identify repeated UI patterns +- Extract to `/components/composed/` +- Document each pattern + +### Phase 3: Pattern Library (Future) +- Comprehensive pattern library +- Documentation site +- Storybook or similar + +### Phase 4: Optimization (Ongoing) +- Performance monitoring +- Code splitting +- Pattern refinement + +--- + +## Key Principles + +1. **Don't Over-Engineer Upfront** + - Build patterns as needs emerge + - Start in modules, extract to composed later + - Prove the pattern before abstracting + +2. **Maintain Clear Boundaries** + - No business logic in composed patterns + - No database access in components + - Server Actions only in feature modules + +3. **Prioritize Reusability** + - If used 3+ times, consider extracting + - If used once, keep it in the module + - Document the "why" for each pattern + +4. **Keep It Simple** + - Prefer composition over complexity + - Clear over clever + - Explicit over magic + +--- + +## Reference Documentation + +- [Component Decision Framework](./component-decision-framework.md) +- [Pattern Library Plan](./pattern-library-plan.md) +- [Module Development Guide](./module-development-guide.md) diff --git a/docs/development-planning/architecture-quick-reference.md b/docs/development-planning/architecture-quick-reference.md new file mode 100644 index 0000000..098bf95 --- /dev/null +++ b/docs/development-planning/architecture-quick-reference.md @@ -0,0 +1,508 @@ +# Architecture Quick Reference + +Fast lookup guide for component placement, patterns, and common tasks. + +--- + +## 🎯 Where Does This Go? + +### Component Placement Decision Tree + +``` +❓ What am I building? + +└─ 🎨 UI Building Block (Button, Input, Card) + → /components/ui/ + +└─ 🔄 Reusable UI Pattern (no business logic, used 3+ times) + → /components/composed/[category]/ + +└─ 🏢 Feature with Business Logic (CRUD, auth, data) + → /modules/[feature]/ + +└─ 🔧 One-off Component + → /components/shared/ or inline +``` + +--- + +## 📂 Directory Structure + +``` +src/ +├── app/ # Next.js routes +├── components/ +│ ├── ui/ # shadcn primitives +│ ├── composed/ # YOUR reusable patterns +│ │ ├── data-display/ +│ │ ├── layouts/ +│ │ ├── forms/ +│ │ ├── feedback/ +│ │ └── media/ +│ └── shared/ # One-offs +├── modules/ # Features with business logic +│ └── [feature]/ +│ ├── actions/ +│ ├── components/ +│ ├── hooks/ +│ ├── models/ +│ └── schemas/ +├── db/ # Database +├── lib/ # Shared utilities +└── services/ # Business services +``` + +--- + +## 🚦 Component Rules + +| Layer | Location | Business Logic? | Database? | Reused? | +|-------|----------|----------------|-----------|---------| +| **Primitives** | `/components/ui/` | ❌ | ❌ | ✅ Everywhere | +| **Patterns** | `/components/composed/` | ❌ | ❌ | ✅ 3+ features | +| **Features** | `/modules/[feature]/` | ✅ | ✅ | ❌ Feature-specific | +| **Shared** | `/components/shared/` | ❌ | ❌ | ❌ 1-2 uses | + +--- + +## 🛠️ Common Tasks + +### Create a New Feature Module + +```bash +# 1. Create directory structure +mkdir -p src/modules/[feature]/{actions,components,hooks,models,schemas} + +# 2. Add database schema in src/db/schema.ts +# 3. Generate migration +pnpm run db:generate:named "add_[feature]_table" + +# 4. Apply migration +pnpm run db:migrate:local + +# 5. Create files: +# - models/[entity].ts (TypeScript types) +# - schemas/[entity].schema.ts (Zod validation) +# - actions/create-[entity].ts (Server Action) +# - components/[Entity]List.tsx (UI) +``` + +### Create a Reusable Pattern + +```bash +# 1. Identify the pattern (used 3+ times?) +# 2. Choose category: +# - data-display (tables, cards, lists) +# - layouts (headers, sidebars) +# - forms (multi-step, fields) +# - feedback (empty, loading, errors) +# - media (uploads, galleries) + +# 3. Create component +touch src/components/composed/[category]/[Pattern].tsx + +# 4. Make it generic (props-based, no business logic) +# 5. Document usage +# 6. Use in features +``` + +### Add Database Table + +```typescript +// In src/db/schema.ts +export const myTable = sqliteTable('my_table', { + id: text('id').primaryKey().$defaultFn(() => createId()), + name: text('name').notNull(), + createdAt: integer('created_at', { mode: 'timestamp' }) + .notNull() + .$defaultFn(() => new Date()), +}); + +export type MyEntity = typeof myTable.$inferSelect; +export type NewMyEntity = typeof myTable.$inferInsert; +``` + +```bash +# Generate and apply +pnpm run db:generate:named "add_my_table" +pnpm run db:migrate:local +``` + +### Create Server Action + +```typescript +'use server'; + +import { db } from '@/db'; +import { auth } from '@/lib/auth'; +import { mySchema } from '../schemas/my.schema'; +import { revalidatePath } from 'next/cache'; + +export async function myAction(input: MyInput) { + try { + // 1. Auth + const session = await auth(); + if (!session?.user) { + return { success: false, error: 'Unauthorized' }; + } + + // 2. Validate + const validated = mySchema.parse(input); + + // 3. Database operation + const result = await db.insert(myTable).values(validated).returning(); + + // 4. Revalidate + revalidatePath('/path'); + + // 5. Return + return { success: true, data: result }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed' + }; + } +} +``` + +--- + +## 🎨 Pattern Priority List + +Build in this order as you need them: + +1. **DataTable** - sortable, filterable tables +2. **PageHeader** - title + actions + breadcrumbs +3. **EmptyState** - no data messaging +4. **LoadingState** - skeleton loaders +5. **FormField** - label + input + error +6. **FileUpload** - drag-drop with R2 +7. **CardView** - grid display +8. **ListView** - mobile-friendly lists +9. **ViewSwitcher** - toggle between views +10. **ConfirmDialog** - delete confirmations + +--- + +## 🔍 Decision Flowcharts + +### "Should I Extract This to a Pattern?" + +``` +Does it have business logic? +├─ YES → Keep in module +└─ NO → Continue + ↓ + Used in 3+ features? + ├─ YES → Extract to /components/composed/ + ├─ USED TWICE → Wait for 3rd use + └─ USED ONCE → Keep in module or /components/shared/ +``` + +### "Is This Client or Server Component?" + +``` +Does it need: +- useState/useEffect/event handlers +- Browser APIs +- Interactive form controls +├─ YES → 'use client' +└─ NO → Server Component (default) +``` + +### "Where Should This Type Live?" + +``` +Database schema type? +├─ YES → Export from /db/schema.ts +└─ NO → Continue + ↓ + Feature-specific? + ├─ YES → /modules/[feature]/models/ + └─ NO → Continue + ↓ + Pattern-related? + └─ YES → In the pattern file or /lib/types.ts +``` + +--- + +## 📝 Code Snippets + +### Server Action Response Type + +```typescript +type ActionResponse = + | { success: true; data: T; error?: never } + | { success: false; data?: never; error: string }; +``` + +### Zod Schema Pattern + +```typescript +import { z } from 'zod'; + +export const mySchema = z.object({ + name: z.string().min(1).max(100), + email: z.string().email(), + age: z.number().min(0).optional(), +}); + +export type MyInput = z.infer; +``` + +### React Hook Form Integration + +```typescript +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; + +const form = useForm({ + resolver: zodResolver(mySchema), + defaultValues: { name: '', email: '' }, +}); +``` + +### Server Component with Suspense + +```typescript +export async function MyList() { + const result = await getItems(); + + if (!result.success) { + return ; + } + + return ; +} + +// Wrap with Suspense +export function MyListWithSuspense() { + return ( + }> + + + ); +} +``` + +### Optimistic Updates + +```typescript +'use client'; + +import { useOptimistic } from 'react'; + +export function MyComponent({ items }) { + const [optimistic, addOptimistic] = useOptimistic( + items, + (state, newItem) => [...state, newItem] + ); + + const handleAdd = async (data) => { + addOptimistic(data); // Instant UI update + await createItem(data); // Actual mutation + }; + + return ; +} +``` + +--- + +## 🚨 Common Mistakes + +### ❌ Pattern with Business Logic + +```typescript +// ❌ BAD - pattern directly accessing database +export function UserTable() { + const users = await db.select().from(users); // ❌ NO! + return ; +} +``` + +```typescript +// ✅ GOOD - pattern receives data via props +export function DataTable({ data, columns }) { + return ...
; +} + +// Feature component handles data +export async function UserTable() { + const result = await getUsers(); // ✅ Server Action + return ; +} +``` + +### ❌ Module Importing from Another Module + +```typescript +// ❌ BAD - circular dependencies +import { getUserById } from '@/modules/users/actions'; + +export async function getOrder(id: string) { + const order = await db.select()...; + const user = await getUserById(order.userId); // ❌ Cross-module + return { order, user }; +} +``` + +```typescript +// ✅ GOOD - use database joins or services +export async function getOrder(id: string) { + const result = await db + .select() + .from(orders) + .leftJoin(users, eq(orders.userId, users.id)) // ✅ Join at DB level + .where(eq(orders.id, id)); + + return result; +} +``` + +### ❌ Forgetting to Revalidate + +```typescript +// ❌ BAD - no revalidation after mutation +export async function createItem(data) { + await db.insert(items).values(data); + return { success: true }; // ❌ Page won't update! +} +``` + +```typescript +// ✅ GOOD - revalidate affected paths +import { revalidatePath } from 'next/cache'; + +export async function createItem(data) { + await db.insert(items).values(data); + revalidatePath('/items'); // ✅ Refresh the page + return { success: true }; +} +``` + +--- + +## 📚 Key Files Reference + +| Purpose | File Location | +|---------|--------------| +| Database schemas | `/db/schema.ts` | +| Database connection | `/db/index.ts` | +| Auth configuration | `/lib/auth.ts` | +| Global styles | `/app/globals.css` | +| Cloudflare config | `wrangler.jsonc` | +| Environment vars (local) | `.dev.vars` | +| Dependencies | `package.json` | + +--- + +## 🔧 Useful Commands + +```bash +# Development +pnpm run dev # Next.js with HMR +pnpm run wrangler:dev # Wrangler (D1 access) +pnpm run dev:cf # Combined (no HMR) + +# Database +pnpm run db:generate # Generate migration +pnpm run db:generate:named "name" # Named migration +pnpm run db:migrate:local # Apply to local +pnpm run db:migrate:prod # Apply to production +pnpm run db:studio:local # Open Drizzle Studio +pnpm run db:inspect:local # Inspect schema + +# Deployment +pnpm run build:cf # Build for Cloudflare +pnpm run deploy # Deploy to production +pnpm run cf-typegen # Generate CF types + +# Cloudflare +pnpm run cf:secret # Add secret +wrangler r2 bucket create name # Create R2 bucket +wrangler d1 create name # Create D1 database +``` + +--- + +## 🎓 Learning Resources + +**Official Docs:** +- [Next.js App Router](https://nextjs.org/docs/app) +- [Cloudflare Workers](https://developers.cloudflare.com/workers/) +- [Drizzle ORM](https://orm.drizzle.team/) +- [shadcn/ui](https://ui.shadcn.com/) +- [React Hook Form](https://react-hook-form.com/) +- [Zod](https://zod.dev/) + +**Component Inspiration:** +- [shadcn/ui Pro Blocks](https://ui.shadcn.com/blocks) +- [Tremor](https://tremor.so/) +- [Magic UI](https://magicui.design/) +- [Radix UI](https://www.radix-ui.com/) + +--- + +## 💡 Pro Tips + +1. **Build patterns as you need them** - Don't build speculatively +2. **Start in modules, extract after 3rd use** - Prove the pattern first +3. **Server Components by default** - Add 'use client' only when needed +4. **Consistent response shapes** - `{ success, data, error }` +5. **Validate everything** - Use Zod for all inputs +6. **Revalidate after mutations** - Keep UI in sync +7. **Loading/error/empty states** - Always handle these three +8. **TypeScript everywhere** - No `any` types +9. **Document as you go** - Future you will thank you +10. **Test in development** - Don't wait for production + +--- + +## 🆘 When Stuck + +**Ask yourself:** +1. Does this have business logic? → Feature module +2. Is this used 3+ times? → Pattern +3. Is shadcn/ui enough? → Use it +4. Is this truly one-off? → Shared or inline + +**Still unsure?** +- Check: [Component Decision Framework](./component-decision-framework.md) +- Review: [Architecture Overview](./architecture-overview.md) +- Reference: [Module Development Guide](./module-development-guide.md) +- Look at: Existing modules for examples + +--- + +## 📋 Checklist: New Feature + +- [ ] Database schema created +- [ ] Migration generated and applied +- [ ] Types/models defined +- [ ] Zod schemas created +- [ ] Server Actions implemented (CRUD) +- [ ] Components created +- [ ] Page route added +- [ ] Loading states added +- [ ] Empty states added +- [ ] Error handling complete +- [ ] Tested in development +- [ ] Patterns extracted (if reusable) + +--- + +## 🎯 Remember + +**Three Layers:** +1. **UI Primitives** - Building blocks (shadcn) +2. **Composed Patterns** - Reusable patterns (your library) +3. **Feature Modules** - Business logic (your app) + +**Golden Rule:** +> If you've written it 3 times, extract it to a pattern. + +**Architecture Goal:** +> Clear boundaries, maximum reusability, minimum duplication. diff --git a/docs/development-planning/component-decision-framework.md b/docs/development-planning/component-decision-framework.md new file mode 100644 index 0000000..1bf42d0 --- /dev/null +++ b/docs/development-planning/component-decision-framework.md @@ -0,0 +1,389 @@ +# Component Decision Framework + +Quick reference guide for deciding where components should live in the architecture. + +--- + +## The Decision Tree + +``` +Start here: I need to create a component + ↓ + Does shadcn/ui already provide this? + ├─ YES → Use /components/ui/[component].tsx + └─ NO → Continue + ↓ + Does it have business logic or database access? + ├─ YES → /modules/[feature]/components/ + └─ NO → Continue + ↓ + Will it be used in 3+ different features? + ├─ YES → /components/composed/[category]/ + ├─ MAYBE → Start in module, extract later + └─ NO → Continue + ↓ + Is it truly one-off? + ├─ YES → /components/shared/ or inline + └─ NO → Reconsider if it's actually a pattern +``` + +--- + +## Quick Reference Table + +| Component Type | Location | Has Business Logic? | Reused Across Features? | Examples | +|---------------|----------|---------------------|------------------------|----------| +| UI Primitive | `/components/ui/` | ❌ No | ✅ Yes | Button, Input, Dialog | +| Composed Pattern | `/components/composed/` | ❌ No | ✅ Yes (3+) | DataTable, PageHeader, FileUpload | +| Feature Component | `/modules/[feature]/components/` | ✅ Yes | ❌ No | TodoList, UserProfile, ProductCard | +| Shared Component | `/components/shared/` | ❌ No | ❌ No | Logo, specific landing sections | + +--- + +## Detailed Decision Criteria + +### ✅ Put in `/components/ui/` when: + +**Characteristics:** +- Single, unopinionated building block +- Part of shadcn/ui library +- No business logic +- Maximum flexibility needed +- Used everywhere + +**Examples:** +- ✅ Button +- ✅ Input +- ✅ Dialog +- ✅ Card +- ✅ Select + +**Anti-examples (DON'T put here):** +- ❌ LoginForm (has auth logic) +- ❌ UserCard (feature-specific) +- ❌ DataTable (opinionated pattern) + +--- + +### ✅ Put in `/components/composed/` when: + +**Characteristics:** +- Combines 2+ UI primitives +- Solves a common UI pattern +- NO business logic or database access +- Reusable across 3+ features +- Configured via props +- Emits events via callbacks + +**Questions to ask:** +1. Does this solve a pattern I see repeatedly? +2. Can I use it in different features without modification? +3. Is it independent of business logic? +4. Would another developer expect to find this as a reusable pattern? + +**Examples:** +- ✅ DataTable with sorting/filtering +- ✅ PageHeader with breadcrumbs +- ✅ FileUpload with drag-drop +- ✅ EmptyState with icon and CTA +- ✅ MultiStepForm wizard +- ✅ ConfirmDialog with form + +**Anti-examples (DON'T put here):** +- ❌ TodoList (fetches todos from database) +- ❌ UserProfile (specific to user domain) +- ❌ ProductCheckout (has payment logic) +- ❌ AnalyticsDashboard (specific dashboard) + +**The "3+ Rule":** +If you're not sure, start in the feature module. After you use it in 3 different features, extract it to composed patterns. + +--- + +### ✅ Put in `/modules/[feature]/components/` when: + +**Characteristics:** +- Domain-specific component +- Uses Server Actions +- Accesses database or external APIs +- Contains business logic +- Validates with feature-specific schemas +- Tied to a specific domain model + +**Questions to ask:** +1. Does this component fetch or mutate data? +2. Is it specific to one business domain? +3. Does it use Server Actions from this module? +4. Would it make sense outside this feature? + +**Examples:** +- ✅ TodoList (fetches/displays todos) +- ✅ UserProfile (user-specific data) +- ✅ ProductCard (product-specific) +- ✅ OrderSummary (order-specific) +- ✅ CommentThread (comment-specific) + +**Structure within module:** +```typescript +/modules/todos/ +├── components/ +│ ├── TodoList.tsx // Main list component +│ ├── TodoItem.tsx // Individual item +│ ├── TodoForm.tsx // Create/edit form +│ └── TodoFilters.tsx // Filter controls +``` + +--- + +### ✅ Put in `/components/shared/` when: + +**Characteristics:** +- Used once or twice +- Doesn't fit a broader pattern +- Simple, one-off need +- Not worth extracting to a pattern yet + +**Examples:** +- ✅ Logo +- ✅ Specific landing page hero +- ✅ One-off marketing section +- ✅ Custom 404 page component + +**Warning:** Don't let this become a dumping ground. If you find yourself with many "shared" components, they're probably patterns waiting to be extracted. + +--- + +## Common Scenarios + +### Scenario 1: "I need a table to display users" + +**Analysis:** +- Displays data? → Feature component initially +- Will other features need tables? → Yes, extract pattern + +**Decision:** +1. Start: `/modules/users/components/UserTable.tsx` +2. After using in 3 features: Extract to `/components/composed/data-display/DataTable.tsx` +3. Keep: `/modules/users/components/UserTable.tsx` as a wrapper with user-specific logic + +**Result:** +```typescript +// Feature-specific wrapper +// /modules/users/components/UserTable.tsx +export function UserTable() { + const users = await getUsers(); // Server Action + + return ( + + ); +} + +// Generic pattern +// /components/composed/data-display/DataTable.tsx +export function DataTable({ data, columns, onRowClick }) { + // Generic table logic with sorting, filtering, pagination +} +``` + +--- + +### Scenario 2: "I need a form to create todos" + +**Analysis:** +- Has business logic (validation, submission)? → Yes +- Specific to todos? → Yes + +**Decision:** +Put in `/modules/todos/components/TodoForm.tsx` + +**Note:** If you later need a "ProductForm" and "UserForm" with similar patterns, extract the form PATTERN to `/components/composed/forms/FormContainer.tsx` + +--- + +### Scenario 3: "I need to show 'no results' message" + +**Analysis:** +- Will this be used everywhere? → Yes +- Does it have business logic? → No +- Is it a reusable pattern? → Yes + +**Decision:** +Put in `/components/composed/feedback/EmptyState.tsx` + +**Implementation:** +```typescript +// /components/composed/feedback/EmptyState.tsx +export function EmptyState({ + icon: Icon, + title, + description, + action, + actionLabel +}) { + return ( +
+ +

{title}

+

{description}

+ {action && } +
+ ); +} + +// Usage in feature +// /modules/todos/components/TodoList.tsx +{todos.length === 0 && ( + +)} +``` + +--- + +### Scenario 4: "I need a page layout with sidebar and header" + +**Analysis:** +- Used across multiple pages? → Yes +- No business logic? → Correct +- Clear reusable pattern? → Yes + +**Decision:** +Put in `/components/composed/layouts/DashboardLayout.tsx` + +--- + +### Scenario 5: "I need to upload files for R2" + +**Analysis:** +- Will multiple features need uploads? → Yes +- Has R2-specific logic? → Business logic in Server Action, UI in pattern + +**Decision:** +1. Pattern: `/components/composed/media/FileUpload.tsx` (UI only) +2. Logic: `/modules/[feature]/actions/upload.ts` (R2 Server Action) + +**Split:** +```typescript +// Pattern - handles UI only +// /components/composed/media/FileUpload.tsx +export function FileUpload({ onUpload, accept, maxSize }) { + // Drag-drop UI, validation, progress + // Calls the provided onUpload callback +} + +// Feature - handles business logic +// /modules/documents/actions/upload.ts +export async function uploadDocument(file: File) { + // R2 upload logic + // Database record creation + // Permission checks +} + +// Usage in feature +// /modules/documents/components/DocumentUploader.tsx +export function DocumentUploader() { + return ( + + ); +} +``` + +--- + +## Red Flags + +### 🚩 You're probably doing it wrong if: + +1. **Composed pattern has Server Actions** + - Move Server Actions to feature module + - Pass callbacks via props + +2. **Composed pattern imports from `/modules/`** + - Reverse the dependency + - Modules should import patterns, not vice versa + +3. **UI primitive has opinions about layout** + - Extract to composed pattern + - Keep primitives flexible + +4. **Feature component has no feature-specific logic** + - Move to composed patterns + - Make it generic + +5. **Everything goes in `/components/shared/`** + - Identify patterns + - Extract to composed + - Keep shared minimal + +6. **Creating patterns speculatively** + - Wait until you use it 3 times + - Don't abstract prematurely + - Prove the need first + +--- + +## Testing Your Decision + +Ask yourself: + +1. **Can I describe this component in one sentence?** + - If not, it might need to be split + +2. **Does this component do ONE thing?** + - Single Responsibility Principle applies + +3. **Could another project use this exact component?** + - If yes → Composed pattern + - If no → Feature component + +4. **Does it need to know about my database schema?** + - If yes → Feature component + - If no → Composed pattern or UI primitive + +5. **Am I repeating this pattern?** + - If yes (3+) → Extract to composed + - If no → Keep in feature module + +--- + +## Migration Path + +**When you realize a component is in the wrong place:** + +1. **Don't panic** - this is normal during development +2. **Document why you're moving it** - update this guide if needed +3. **Check dependencies** - what imports this component? +4. **Update imports** - use your IDE's refactor tools +5. **Test thoroughly** - especially if moving from module to composed + +**Common migrations:** +- Feature component → Composed pattern (after 3rd use) +- Shared component → Composed pattern (when pattern emerges) +- Inline component → Shared component → Composed pattern (natural evolution) + +--- + +## Summary Checklist + +Before creating a component, ask: + +- [ ] Does shadcn/ui provide this? → Use it +- [ ] Does it have business logic? → Feature module +- [ ] Will it be used 3+ times? → Composed pattern (or extract later) +- [ ] Is it truly one-off? → Shared or inline +- [ ] Can I clearly categorize it? → You're ready to build + +**When in doubt:** Start in the feature module, extract to composed patterns after the 3rd use. diff --git a/docs/development-planning/module-development-guide.md b/docs/development-planning/module-development-guide.md new file mode 100644 index 0000000..ebe6e42 --- /dev/null +++ b/docs/development-planning/module-development-guide.md @@ -0,0 +1,843 @@ +# Module Development Guide + +Complete guide for building feature modules in the Next.js + Cloudflare architecture. + +--- + +## What is a Feature Module? + +A feature module is a self-contained domain feature that includes: +- **Business logic** (Server Actions) +- **UI components** (feature-specific) +- **Data models** (TypeScript types) +- **Validation schemas** (Zod) +- **Database interactions** (via Drizzle) +- **Custom hooks** (feature-specific) + +--- + +## Module Structure + +``` +/modules/[feature-name]/ +├── actions/ # Server Actions (mutations & queries) +│ ├── create-[entity].ts +│ ├── update-[entity].ts +│ ├── delete-[entity].ts +│ └── get-[entities].ts +│ +├── components/ # Feature-specific UI components +│ ├── [Entity]List.tsx +│ ├── [Entity]Form.tsx +│ ├── [Entity]Card.tsx +│ └── [Entity]Details.tsx +│ +├── hooks/ # Feature-specific React hooks +│ ├── use-[entity].ts +│ └── use-[entity]-form.ts +│ +├── models/ # TypeScript type definitions +│ └── [entity].ts +│ +├── schemas/ # Zod validation schemas +│ └── [entity].schema.ts +│ +└── utils/ # Feature-specific utilities + └── [entity]-helpers.ts +``` + +--- + +## Step-by-Step Module Creation + +### Step 1: Define the Database Schema + +**File:** `/db/schema.ts` (or feature-specific schema file) + +```typescript +import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; +import { createId } from '@paralleldrive/cuid2'; + +export const products = sqliteTable('products', { + id: text('id') + .primaryKey() + .$defaultFn(() => createId()), + name: text('name').notNull(), + description: text('description'), + price: integer('price').notNull(), // Store as cents + status: text('status', { enum: ['draft', 'active', 'archived'] }) + .notNull() + .default('draft'), + userId: text('user_id').notNull(), + imageUrl: text('image_url'), + createdAt: integer('created_at', { mode: 'timestamp' }) + .notNull() + .$defaultFn(() => new Date()), + updatedAt: integer('updated_at', { mode: 'timestamp' }) + .notNull() + .$defaultFn(() => new Date()), +}); + +export type Product = typeof products.$inferSelect; +export type NewProduct = typeof products.$inferInsert; +``` + +**Then generate and apply migration:** +```bash +pnpm run db:generate:named "add_products_table" +pnpm run db:migrate:local +``` + +--- + +### Step 2: Create Type Models + +**File:** `/modules/products/models/product.ts` + +```typescript +import { Product, NewProduct } from '@/db/schema'; + +// Re-export database types +export type { Product, NewProduct }; + +// Add any additional types for the feature +export interface ProductWithUser extends Product { + user: { + id: string; + name: string; + email: string; + }; +} + +export interface ProductListItem { + id: string; + name: string; + price: number; + status: Product['status']; + imageUrl: string | null; + createdAt: Date; +} + +export type ProductStatus = Product['status']; + +export const PRODUCT_STATUSES: { value: ProductStatus; label: string }[] = [ + { value: 'draft', label: 'Draft' }, + { value: 'active', label: 'Active' }, + { value: 'archived', label: 'Archived' }, +]; +``` + +--- + +### Step 3: Create Validation Schemas + +**File:** `/modules/products/schemas/product.schema.ts` + +```typescript +import { z } from 'zod'; + +export const createProductSchema = z.object({ + name: z.string().min(1, 'Name is required').max(100), + description: z.string().max(500).optional(), + price: z.number().min(0, 'Price must be positive'), + status: z.enum(['draft', 'active', 'archived']).default('draft'), + imageUrl: z.string().url().optional().nullable(), +}); + +export const updateProductSchema = createProductSchema.partial().extend({ + id: z.string().cuid2(), +}); + +export const deleteProductSchema = z.object({ + id: z.string().cuid2(), +}); + +export const getProductSchema = z.object({ + id: z.string().cuid2(), +}); + +export const listProductsSchema = z.object({ + status: z.enum(['draft', 'active', 'archived']).optional(), + search: z.string().optional(), + limit: z.number().min(1).max(100).default(20), + offset: z.number().min(0).default(0), +}); + +// Infer types from schemas +export type CreateProductInput = z.infer; +export type UpdateProductInput = z.infer; +export type DeleteProductInput = z.infer; +export type GetProductInput = z.infer; +export type ListProductsInput = z.infer; +``` + +--- + +### Step 4: Create Server Actions + +**File:** `/modules/products/actions/create-product.ts` + +```typescript +'use server'; + +import { eq } from 'drizzle-orm'; +import { revalidatePath } from 'next/cache'; +import { db } from '@/db'; +import { products } from '@/db/schema'; +import { auth } from '@/lib/auth'; +import { + createProductSchema, + type CreateProductInput +} from '../schemas/product.schema'; + +export async function createProduct(input: CreateProductInput) { + try { + // 1. Authenticate user + const session = await auth(); + if (!session?.user) { + return { + success: false, + error: 'Unauthorized', + }; + } + + // 2. Validate input + const validated = createProductSchema.parse(input); + + // 3. Perform database operation + const [product] = await db + .insert(products) + .values({ + ...validated, + userId: session.user.id, + }) + .returning(); + + // 4. Revalidate relevant paths + revalidatePath('/dashboard/products'); + + // 5. Return success response + return { + success: true, + data: product, + }; + } catch (error) { + console.error('Failed to create product:', error); + + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to create product', + }; + } +} +``` + +**File:** `/modules/products/actions/get-products.ts` + +```typescript +'use server'; + +import { eq, like, and, desc } from 'drizzle-orm'; +import { db } from '@/db'; +import { products } from '@/db/schema'; +import { auth } from '@/lib/auth'; +import { + listProductsSchema, + type ListProductsInput +} from '../schemas/product.schema'; + +export async function getProducts(input: ListProductsInput = {}) { + try { + // 1. Authenticate user + const session = await auth(); + if (!session?.user) { + return { + success: false, + error: 'Unauthorized', + data: null, + }; + } + + // 2. Validate input + const validated = listProductsSchema.parse(input); + + // 3. Build query conditions + const conditions = [eq(products.userId, session.user.id)]; + + if (validated.status) { + conditions.push(eq(products.status, validated.status)); + } + + if (validated.search) { + conditions.push(like(products.name, `%${validated.search}%`)); + } + + // 4. Fetch data + const data = await db + .select() + .from(products) + .where(and(...conditions)) + .orderBy(desc(products.createdAt)) + .limit(validated.limit) + .offset(validated.offset); + + // 5. Return success response + return { + success: true, + data, + error: null, + }; + } catch (error) { + console.error('Failed to fetch products:', error); + + return { + success: false, + data: null, + error: error instanceof Error ? error.message : 'Failed to fetch products', + }; + } +} +``` + +**File:** `/modules/products/actions/index.ts` + +```typescript +// Barrel export for all actions +export { createProduct } from './create-product'; +export { getProducts } from './get-products'; +export { getProduct } from './get-product'; +export { updateProduct } from './update-product'; +export { deleteProduct } from './delete-product'; +``` + +--- + +### Step 5: Create Custom Hooks (Optional) + +**File:** `/modules/products/hooks/use-products.ts` + +```typescript +import { useOptimistic } from 'react'; +import { Product } from '../models/product'; + +export function useProducts(initialProducts: Product[]) { + const [optimisticProducts, setOptimisticProducts] = useOptimistic( + initialProducts, + (state, newProduct: Product) => [...state, newProduct] + ); + + return { + products: optimisticProducts, + addOptimisticProduct: setOptimisticProducts, + }; +} +``` + +**File:** `/modules/products/hooks/use-product-form.ts` + +```typescript +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { toast } from 'sonner'; +import { createProduct } from '../actions'; +import { createProductSchema, type CreateProductInput } from '../schemas/product.schema'; + +export function useProductForm() { + const [isSubmitting, setIsSubmitting] = useState(false); + + const form = useForm({ + resolver: zodResolver(createProductSchema), + defaultValues: { + name: '', + description: '', + price: 0, + status: 'draft', + imageUrl: null, + }, + }); + + const onSubmit = async (data: CreateProductInput) => { + setIsSubmitting(true); + + try { + const result = await createProduct(data); + + if (result.success) { + toast.success('Product created successfully'); + form.reset(); + return result.data; + } else { + toast.error(result.error); + } + } catch (error) { + toast.error('An unexpected error occurred'); + } finally { + setIsSubmitting(false); + } + }; + + return { + form, + isSubmitting, + onSubmit: form.handleSubmit(onSubmit), + }; +} +``` + +--- + +### Step 6: Create UI Components + +**File:** `/modules/products/components/ProductList.tsx` + +```typescript +import { Suspense } from 'react'; +import { getProducts } from '../actions'; +import { DataTable } from '@/components/composed/data-display/DataTable'; +import { productColumns } from './product-columns'; +import { ProductListSkeleton } from './ProductListSkeleton'; + +export async function ProductList() { + const result = await getProducts(); + + if (!result.success) { + return
Error: {result.error}
; + } + + return ( + + ); +} + +export function ProductListWithSuspense() { + return ( + }> + + + ); +} +``` + +**File:** `/modules/products/components/ProductForm.tsx` + +```typescript +'use client'; + +import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Button } from '@/components/ui/button'; +import { useProductForm } from '../hooks/use-product-form'; +import { PRODUCT_STATUSES } from '../models/product'; + +interface ProductFormProps { + onSuccess?: () => void; +} + +export function ProductForm({ onSuccess }: ProductFormProps) { + const { form, isSubmitting, onSubmit } = useProductForm(); + + const handleSubmit = async (data: any) => { + const result = await onSubmit(data); + if (result && onSuccess) { + onSuccess(); + } + }; + + return ( +
+ + ( + + Product Name + + + + + + )} + /> + + ( + + Description + +