diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md new file mode 100644 index 0000000..f6f477d --- /dev/null +++ b/DEPENDENCIES.md @@ -0,0 +1,249 @@ +# 📦 Required Dependencies for MVP + +## Install AI SDKs + +```bash +# OpenAI for text generation and DALL-E images +bun add openai + +# Alternative: Anthropic Claude (optional) +bun add @anthropic-ai/sdk + +# For image uploads/storage +bun add @vercel/blob # or use AWS S3, Cloudinary +``` + +## Install UI Dependencies (if not already installed) + +```bash +# These should already be in your package.json, but just in case: +bun add clsx tailwind-merge class-variance-authority + +# For forms +bun add react-hook-form zod @hookform/resolvers + +# For toasts/notifications +bun add sonner + +# For copy-to-clipboard +bun add copy-to-clipboard +``` + +## Update package.json scripts + +Add these to your `package.json` if missing: + +```json +{ + "scripts": { + "dev": "next dev --turbopack", + "build": "prisma generate && next build --turbopack", + "start": "next start", + "postinstall": "prisma generate", + + // Keep all your existing db scripts... + } +} +``` + +## Environment Variables Checklist + +Create `.env.development.local` with: + +```bash +# ===== REQUIRED FOR MVP ===== + +# Database (Neon) +DATABASE_URL="postgresql://user:pass@host/db?sslmode=require" + +# Clerk Authentication +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_xxx" +CLERK_SECRET_KEY="sk_test_xxx" +NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in" +NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up" +NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/dashboard" +NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/onboarding" + +# OpenAI (for text + images via DALL-E) +OPENAI_API_KEY="sk-proj-xxx" +OPENAI_ORG_ID="org-xxx" # Optional + +# ===== OPTIONAL (Add later) ===== + +# Stability AI (alternative to DALL-E) +STABILITY_API_KEY="sk-xxx" + +# Anthropic Claude (alternative to OpenAI) +ANTHROPIC_API_KEY="sk-ant-xxx" + +# Paddle (for billing - set up later) +PADDLE_API_KEY="xxx" +PADDLE_VENDOR_ID="xxx" +PADDLE_WEBHOOK_SECRET="xxx" +NEXT_PUBLIC_PADDLE_ENVIRONMENT="sandbox" + +# Storage (optional - for image uploads) +BLOB_READ_WRITE_TOKEN="xxx" # Vercel Blob +# OR +AWS_ACCESS_KEY_ID="xxx" +AWS_SECRET_ACCESS_KEY="xxx" +AWS_REGION="us-east-1" +AWS_S3_BUCKET="your-bucket" + +# ===== OPTIONAL MONITORING ===== + +# Sentry (error tracking) +SENTRY_DSN="https://xxx@xxx.ingest.sentry.io/xxx" + +# PostHog (analytics) +NEXT_PUBLIC_POSTHOG_KEY="phc_xxx" +NEXT_PUBLIC_POSTHOG_HOST="https://app.posthog.com" + +# App Configuration +NEXT_PUBLIC_APP_URL="http://localhost:3000" +NODE_ENV="development" +``` + +## API Keys - Where to Get Them + +### 1. OpenAI +``` +1. Go to https://platform.openai.com/ +2. Sign up / Log in +3. Go to API Keys section +4. Create new secret key +5. Add at least $5 credit to your account +``` + +### 2. Clerk (You probably have this) +``` +1. Go to https://clerk.com/ +2. Create app +3. Get keys from Dashboard > API Keys +``` + +### 3. Neon Database (You probably have this) +``` +1. Go to https://neon.tech/ +2. Create project +3. Copy connection string +``` + +### 4. Paddle (For billing - optional for MVP) +``` +1. Go to https://paddle.com/ +2. Sign up for sandbox account +3. Get API keys from Developer Tools +4. Set up 3 products: FREE, PRO, BUSINESS +``` + +## Quick Installation + +```bash +# In your project root +cd ~/Documents/ThinkTapFast/app + +# Install AI dependencies +bun add openai + +# Install form handling +bun add react-hook-form zod @hookform/resolvers + +# Install notifications +bun add sonner + +# Setup database +bun run db:setup + +# Run migrations if any +bun run db:migrate + +# Start dev server +bun dev +``` + +## Test OpenAI Connection + +Create a test file to verify your API key works: + +```typescript +// test-openai.ts +import OpenAI from 'openai'; + +const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}); + +async function test() { + try { + const completion = await openai.chat.completions.create({ + model: "gpt-3.5-turbo", + messages: [ + { role: "user", content: "Say hello!" } + ], + }); + + console.log('✅ OpenAI connected!'); + console.log('Response:', completion.choices[0].message.content); + } catch (error) { + console.error('❌ OpenAI error:', error); + } +} + +test(); +``` + +Run it: +```bash +bunx tsx test-openai.ts +``` + +If you see "✅ OpenAI connected!" you're good to go! + +## Common Issues + +### Issue: "Module not found: Can't resolve 'openai'" +**Solution:** `bun add openai` + +### Issue: "Invalid API key" +**Solution:** Check your `.env.development.local` has correct `OPENAI_API_KEY` + +### Issue: "Insufficient credits" +**Solution:** Add $5+ to your OpenAI account + +### Issue: "Database connection failed" +**Solution:** +```bash +# Test connection +bunx prisma db push +# If fails, check DATABASE_URL in .env +``` + +### Issue: "Clerk not working" +**Solution:** +```bash +# Make sure you have all Clerk env vars +# Check middleware.ts is properly configured +``` + +## Cost Estimates (MVP Testing) + +**OpenAI Costs:** +- GPT-4: ~$0.03 per 1000 tokens (~$0.01-0.05 per generation) +- GPT-3.5-turbo: ~$0.002 per 1000 tokens (~$0.001 per generation) +- DALL-E 3: ~$0.04 per image + +**Recommendation for testing:** +- Use GPT-3.5-turbo initially (10x cheaper) +- Switch to GPT-4 for production +- Budget $10-20 for MVP development testing + +## Next Steps + +1. ✅ Install dependencies: `bun add openai` +2. ✅ Get OpenAI API key and add credit +3. ✅ Update `.env.development.local` +4. ✅ Test connection with test script +5. ✅ Run `bun dev` and start building! + +You're ready to build! 🚀 diff --git a/MVP-PLAN.md b/MVP-PLAN.md new file mode 100644 index 0000000..ba1bdd7 --- /dev/null +++ b/MVP-PLAN.md @@ -0,0 +1,370 @@ +# 🚀 ThinkTapFast MVP Implementation Plan +## Build a Working Version in Days - Scalable Architecture + +--- + +## 🎯 **MVP GOAL**: Ship a functional AI content generation SaaS in **5-7 days** + +This plan focuses on **core features only** with a scalable foundation for future additions. + +--- + +## ✅ **Phase 1: Core Infrastructure (Day 1-2)** + +### 1.1 Database & Auth ✅ **(Already Done!)** +- [x] Prisma schema with all tables +- [x] Clerk authentication integrated +- [x] ABAC permission system +- [x] Database seeding scripts +- [x] Middleware for auth + +### 1.2 What's Missing - Quick Fixes Needed +- [ ] **Fix API routes** - Most `/api/v1/` routes are empty folders +- [ ] **Create .env.example** with all required variables +- [ ] **Test database connection** and seed with `bun run db:setup` + +--- + +## 🔥 **Phase 2: MVP Core Features (Day 3-4)** + +### 2.1 Basic Dashboard (Priority 1) +Create a simple, functional dashboard: + +**Files to Create:** +- `app/(dashboard)/page.tsx` - Main dashboard (currently just returns Home) +- `app/(dashboard)/layout.tsx` - Dashboard layout with navigation +- `components/dashboard/stats-card.tsx` - Display usage stats +- `components/dashboard/recent-content.tsx` - Show recent generated content +- `components/dashboard/quick-actions.tsx` - Quick generate buttons + +**Implementation:** +```typescript +// Simple dashboard showing: +// 1. Monthly usage stats (content generated, credits used) +// 2. Recent 5 content items +// 3. Quick action buttons (New Text, New Image, etc.) +// 4. Plan status banner +``` + +### 2.2 Content Generation (Priority 1) - **THE CORE FEATURE** +**Critical API Routes to Build:** + +1. **Text Generation** `/api/v1/content/generate/route.ts` + ```typescript + // POST /api/v1/content/generate + // Input: { prompt, type: 'text', tone, length } + // Output: Generated text content + // Provider: OpenAI GPT-4 or Claude API + ``` + +2. **Image Generation** `/api/v1/images/generate/route.ts` + ```typescript + // POST /api/v1/images/generate + // Input: { prompt, style, size } + // Output: Generated image URL + // Provider: DALL-E 3 or Stability AI + ``` + +3. **Save Content** (Use existing server action) + - Already have `createContent` in `server/actions/content/content.actions.ts` + - Just needs frontend integration + +**UI Components to Create:** +- `features/composer/components/content-generator.tsx` - Main generation interface +- `features/composer/components/prompt-input.tsx` - Prompt textarea with suggestions +- `features/composer/components/generation-settings.tsx` - Tone, length, style options +- `features/composer/components/content-preview.tsx` - Show generated content +- `features/composer/components/regenerate-button.tsx` - Try again functionality + +### 2.3 Content Management (Priority 2) +**Pages to Create:** +- `app/(dashboard)/content/page.tsx` - List all user's content +- `app/(dashboard)/content/[id]/page.tsx` - View/edit single content + +**Components:** +- `components/content/content-list.tsx` - Table/grid of content +- `components/content/content-card.tsx` - Individual content item +- `components/content/delete-button.tsx` - Delete with confirmation + +--- + +## 🎨 **Phase 3: Essential UI/UX (Day 5)** + +### 3.1 Navigation & Layout +- [ ] **Sidebar Navigation** with: + - Dashboard + - Generate Content + - My Content + - Settings + - Billing (link to Paddle) +- [ ] **Top Bar** with: + - Usage indicator (X/10 content for FREE plan) + - User menu (profile, logout) + - Plan badge + +### 3.2 Onboarding Flow +- [ ] `app/onboarding/page.tsx` - Simple 3-step onboarding: + 1. Welcome message + 2. Create first project + 3. Generate first content + +### 3.3 Settings Page (Basic) +- [ ] `app/(dashboard)/settings/page.tsx` + - Display user info + - Show API key (for PRO+ users) + - Link to billing portal + +--- + +## 💳 **Phase 4: Billing Integration (Day 6)** + +### 4.1 Paddle Setup +- [ ] Create Paddle account and get API keys +- [ ] Set up 3 products: FREE (no charge), PRO ($29/mo), BUSINESS ($99/mo) +- [ ] Add to environment variables + +### 4.2 Billing Routes +- [ ] `/api/v1/billing/create-checkout/route.ts` - Start Paddle checkout +- [ ] `/api/v1/billing/webhook/route.ts` - Handle Paddle webhooks +- [ ] `/api/webhooks/paddle/route.ts` - Update user plans + +### 4.3 Usage Tracking +- [ ] Create `lib/usage-tracker.ts` - Track content generation +- [ ] Middleware to check plan limits before generation +- [ ] Display usage in dashboard + +--- + +## 🔌 **Phase 5: API for Developers (Day 7)** + +### 5.1 External API (Basic) +**Only 2 endpoints for MVP:** + +1. **Generate Content** `/api/v1/generate/route.ts` + ```typescript + // POST /api/v1/generate + // Headers: Authorization: Bearer + // Body: { prompt, type, settings } + // Returns: Generated content + ``` + +2. **Get Content** `/api/v1/content/[id]/route.ts` + ```typescript + // GET /api/v1/content/:id + // Returns: Content details + ``` + +### 5.2 API Key Management +- [ ] Generate API keys for users (PRO+ only) +- [ ] Store in database with rate limits +- [ ] Simple rate limiting middleware + +--- + +## 📊 **What to SKIP for MVP** (Add Later) + +### ❌ Skip These (Not Essential): +- [ ] ~~Advanced analytics~~ - Add after MVP +- [ ] ~~Team collaboration~~ - Single user focus first +- [ ] ~~Multiple workspaces~~ - One workspace per user for MVP +- [ ] ~~Brand voice training~~ - Skip AI customization initially +- [ ] ~~Content scheduling~~ - Manual publishing only +- [ ] ~~PDF/CSV export~~ - Just copy-to-clipboard for now +- [ ] ~~Voice/Video generation~~ - Text and images only for MVP +- [ ] ~~Template marketplace~~ - Basic prompts only +- [ ] ~~Advanced permissions~~ - Basic role system is enough + +--- + +## 🗂️ **File Structure for MVP** + +``` +app/ +├── (dashboard)/ +│ ├── page.tsx ← Dashboard home [CREATE] +│ ├── layout.tsx ← Dashboard layout [CREATE] +│ ├── content/ +│ │ ├── page.tsx ← Content list [CREATE] +│ │ ├── [id]/page.tsx ← Single content [CREATE] +│ │ └── new/page.tsx ← Generate new [CREATE] +│ ├── settings/ +│ │ └── page.tsx ← User settings [CREATE] +│ └── billing/ +│ └── page.tsx ← Billing page [CREATE] +├── api/v1/ +│ ├── generate/route.ts ← Main generation endpoint [CREATE] +│ ├── content/ +│ │ ├── [id]/route.ts ← Get content [CREATE] +│ │ └── generate/route.ts ← Generate via API [CREATE] +│ ├── images/ +│ │ └── generate/route.ts ← Image generation [CREATE] +│ └── billing/ +│ ├── checkout/route.ts ← Create checkout [CREATE] +│ └── webhook/route.ts ← Handle webhooks [CREATE] +└── onboarding/ + └── page.tsx ← Onboarding flow [CREATE] + +components/ +├── dashboard/ +│ ├── stats-card.tsx ← Usage stats [CREATE] +│ ├── recent-content.tsx ← Recent items [CREATE] +│ └── quick-actions.tsx ← Quick buttons [CREATE] +├── content/ +│ ├── content-list.tsx ← Content table [CREATE] +│ ├── content-card.tsx ← Content item [CREATE] +│ └── delete-button.tsx ← Delete UI [CREATE] +└── layout/ + ├── sidebar.tsx ← Main navigation [CREATE] + ├── topbar.tsx ← Top bar [CREATE] + └── usage-badge.tsx ← Usage indicator [CREATE] + +features/composer/ +└── components/ + ├── content-generator.tsx ← Main generator UI [CREATE] + ├── prompt-input.tsx ← Prompt textarea [CREATE] + ├── generation-settings.tsx ← Settings panel [CREATE] + └── content-preview.tsx ← Preview area [CREATE] + +lib/ +├── ai/ +│ ├── openai-client.ts ← OpenAI integration [CREATE] +│ ├── image-generator.ts ← Image generation [CREATE] +│ └── text-generator.ts ← Text generation [CREATE] +├── usage-tracker.ts ← Track usage [CREATE] +├── rate-limiter.ts ← Rate limiting [CREATE] +└── paddle-client.ts ← Paddle integration [CREATE] +``` + +--- + +## 🔑 **Environment Variables Needed** + +Create `.env.example`: + +```bash +# Database +DATABASE_URL="postgresql://..." + +# Clerk Auth +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= +NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in +NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up + +# AI Providers +OPENAI_API_KEY= # For text generation +OPENAI_ORG_ID= # Optional +STABILITY_API_KEY= # For image generation (or use DALL-E) + +# Paddle Billing +PADDLE_API_KEY= +PADDLE_VENDOR_ID= +PADDLE_WEBHOOK_SECRET= +NEXT_PUBLIC_PADDLE_ENVIRONMENT=sandbox # or 'production' + +# App Config +NEXT_PUBLIC_APP_URL=http://localhost:3000 +NODE_ENV=development +``` + +--- + +## 🎯 **Success Criteria for MVP** + +After 7 days, you should have: + +✅ **User can:** +1. Sign up and log in with Clerk +2. See a dashboard with usage stats +3. Generate text content with AI (using prompt) +4. Generate images with AI (using prompt) +5. View all their generated content +6. Delete content they created +7. Upgrade to PRO plan via Paddle +8. Get an API key (PRO users) +9. Use API to generate content externally + +✅ **System can:** +1. Track usage per user/organization +2. Enforce plan limits (10 content/month for FREE) +3. Accept payments via Paddle +4. Rate limit API requests +5. Store and retrieve content from database + +--- + +## 📈 **Post-MVP Scaling Roadmap** + +### **v1.1 - Enhanced Features** (Week 2-3) +- Add PDF export +- Add content templates +- Add tone/style presets +- Improve UI/UX + +### **v1.2 - Team Features** (Week 4-6) +- Multi-user workspaces +- Team member invitations +- Role-based permissions +- Shared content libraries + +### **v1.3 - Advanced AI** (Week 7-10) +- Brand voice training +- Voice generation (TTS) +- Video generation +- Content scheduling + +### **v2.0 - Enterprise** (Month 3-4) +- Advanced analytics +- White-label options +- Custom integrations +- AI agents/workflows + +--- + +## 🚦 **Next Steps - Start Here** + +### **Immediate Actions (Today):** + +1. **Create `.env.example`** with all variables +2. **Test database** - Run `bun run db:setup` +3. **Sign up for accounts:** + - OpenAI API (for text) + - Stability AI or DALL-E (for images) + - Paddle (for billing) + +4. **Start with Day 1 tasks:** + - Create basic dashboard layout + - Build content generation UI + - Integrate OpenAI API for text generation + +--- + +## 💡 **Development Tips** + +1. **Start Simple** - Don't over-engineer, get it working first +2. **Use Existing Code** - You already have auth, DB, and permissions +3. **Copy-Paste Smart** - Use shadcn components, don't build from scratch +4. **Test Locally** - Use FREE plan limits while testing +5. **Deploy Early** - Push to Vercel after Day 3 to test in production + +--- + +## 🎓 **Remember:** + +> "A working simple product is better than a perfect complex one that doesn't exist." + +**Focus on these 3 things ONLY:** +1. ✅ Generate content with AI +2. ✅ Show content to users +3. ✅ Accept payments + +Everything else can wait! 🚀 + +--- + +**Questions? Check:** +- `docs/SERVER-ACTIONS.md` - How to use server actions +- `docs/ABAC-GUIDE.md` - Permission system +- `docs/DATABASE-SEEDING.md` - Database setup + +**Need help? The codebase is solid, just needs the UI and AI integration!** diff --git a/QUICK-START.md b/QUICK-START.md new file mode 100644 index 0000000..b4b289c --- /dev/null +++ b/QUICK-START.md @@ -0,0 +1,651 @@ +# 🏃 Quick Start - Build MVP Today + +## 📋 What You Have vs What You Need + +### ✅ **Already Built (90% Backend Done!)** +- Authentication (Clerk) +- Database schema (Prisma) +- Permission system (ABAC) +- Server actions for content CRUD +- Middleware for auth +- Docker setup +- Deployment configs + +### ❌ **What's Missing (UI + AI Integration)** +- Dashboard pages +- Content generation UI +- AI provider integrations (OpenAI, Stability AI) +- Billing integration (Paddle) +- API routes for `/api/v1/` + +--- + +## 🎯 **TODAY'S MISSION: Get Content Generation Working** + +### Step 1: Setup Environment (15 min) + +```bash +# 1. Copy environment template +cp .env.development .env.development.local + +# 2. Add these required keys to .env.development.local: + +# Clerk (you probably have these) +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx +CLERK_SECRET_KEY=sk_test_xxx + +# OpenAI (sign up at platform.openai.com) +OPENAI_API_KEY=sk-xxx + +# Stability AI (sign up at stability.ai) +STABILITY_API_KEY=sk-xxx + +# 3. Setup database +bun run db:setup # This creates tables + seeds test data + +# 4. Start dev server +bun dev +``` + +### Step 2: Create AI Clients (30 min) + +Create these 3 files: + +#### File 1: `lib/ai/openai-client.ts` +```typescript +import OpenAI from 'openai'; + +const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}); + +export async function generateText(prompt: string, options?: { + tone?: 'professional' | 'casual' | 'friendly' | 'formal'; + length?: 'short' | 'medium' | 'long'; +}) { + const systemPrompt = `You are a helpful AI content generator. +Tone: ${options?.tone || 'professional'} +Length: ${options?.length || 'medium'}`; + + const completion = await openai.chat.completions.create({ + model: "gpt-4", + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: prompt } + ], + temperature: 0.7, + max_tokens: options?.length === 'short' ? 500 : options?.length === 'long' ? 2000 : 1000, + }); + + return completion.choices[0].message.content; +} +``` + +#### File 2: `lib/ai/image-generator.ts` +```typescript +import OpenAI from 'openai'; + +const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}); + +export async function generateImage(prompt: string, options?: { + style?: 'natural' | 'digital-art' | 'photographic'; + size?: '1024x1024' | '1792x1024' | '1024x1792'; +}) { + const response = await openai.images.generate({ + model: "dall-e-3", + prompt: prompt, + size: options?.size || '1024x1024', + quality: "standard", + n: 1, + }); + + return response.data[0].url; +} +``` + +#### File 3: `lib/usage-tracker.ts` +```typescript +import { db } from '@/server/db/client'; + +export async function trackUsage(orgId: string, type: 'text' | 'image') { + const now = new Date(); + const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); + const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0); + + // Find or create usage record for this month + let usage = await db.usage.findFirst({ + where: { + orgId, + periodStart: { lte: now }, + periodEnd: { gte: now }, + }, + }); + + if (!usage) { + usage = await db.usage.create({ + data: { + orgId, + periodStart: monthStart, + periodEnd: monthEnd, + tokensIn: 0, + tokensOut: 0, + itemsGenerated: 0, + }, + }); + } + + // Increment count + await db.usage.update({ + where: { id: usage.id }, + data: { itemsGenerated: { increment: 1 } }, + }); + + return usage; +} + +export async function canGenerate(orgId: string): Promise<{ allowed: boolean; limit: number; used: number }> { + const org = await db.organization.findUnique({ + where: { id: orgId }, + select: { plan: true }, + }); + + const limits = { + FREE: 10, + PRO: 1000, + BUSINESS: 999999, + AGENCY: 999999, + CUSTOM: 999999, + }; + + const limit = limits[org?.plan || 'FREE']; + + const now = new Date(); + const usage = await db.usage.findFirst({ + where: { + orgId, + periodStart: { lte: now }, + periodEnd: { gte: now }, + }, + }); + + const used = usage?.itemsGenerated || 0; + + return { + allowed: used < limit, + limit, + used, + }; +} +``` + +### Step 3: Create Generation API Route (20 min) + +#### File: `app/api/v1/content/generate/route.ts` +```typescript +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { generateText } from '@/lib/ai/openai-client'; +import { generateImage } from '@/lib/ai/image-generator'; +import { canGenerate, trackUsage } from '@/lib/usage-tracker'; +import { createContent } from '@/server/actions/content/content.actions'; +import { db } from '@/server/db/client'; + +export async function POST(req: NextRequest) { + try { + // Auth check + const { userId } = await auth(); + if (!userId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + // Get user's organization + const user = await db.user.findUnique({ + where: { clerkId: userId }, + include: { memberships: { include: { organization: true } } }, + }); + + if (!user || user.memberships.length === 0) { + return NextResponse.json({ error: 'No organization found' }, { status: 400 }); + } + + const orgId = user.memberships[0].organizationId; + + // Check usage limits + const usageCheck = await canGenerate(orgId); + if (!usageCheck.allowed) { + return NextResponse.json({ + error: 'Monthly limit reached', + limit: usageCheck.limit, + used: usageCheck.used, + }, { status: 429 }); + } + + // Parse request + const body = await req.json(); + const { prompt, type, tone, length, style, size } = body; + + let generatedContent: string | null = null; + let imageUrl: string | null = null; + + // Generate based on type + if (type === 'text') { + generatedContent = await generateText(prompt, { tone, length }); + } else if (type === 'image') { + imageUrl = await generateImage(prompt, { style, size }); + } else { + return NextResponse.json({ error: 'Invalid type' }, { status: 400 }); + } + + // Get default project + const project = await db.project.findFirst({ + where: { orgId }, + orderBy: { createdAt: 'asc' }, + }); + + // Save to database + const content = await createContent({ + kind: type, + input: { prompt, tone, length, style, size }, + output: { + versions: [{ + id: 1, + text: generatedContent, + imageUrl, + timestamp: new Date().toISOString(), + }], + }, + projectId: project?.id, + }); + + // Track usage + await trackUsage(orgId, type); + + return NextResponse.json({ + success: true, + content: { + id: content.id, + text: generatedContent, + imageUrl, + createdAt: content.createdAt, + }, + }); + } catch (error) { + console.error('Generation error:', error); + return NextResponse.json({ + error: 'Generation failed', + details: error instanceof Error ? error.message : 'Unknown error', + }, { status: 500 }); + } +} +``` + +### Step 4: Create Simple Dashboard (30 min) + +#### File: `app/(dashboard)/layout.tsx` +```typescript +import { requireAuth } from '@/server/auth/clerk-helpers'; +import Link from 'next/link'; + +export default async function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + const user = await requireAuth(); + + return ( +
+ {/* Sidebar */} + + + {/* Main content */} +
+ {children} +
+
+ ); +} +``` + +#### File: `app/(dashboard)/page.tsx` +```typescript +import { requireAuth } from '@/server/auth/clerk-helpers'; +import { db } from '@/server/db/client'; +import Link from 'next/link'; + +export default async function DashboardPage() { + const user = await requireAuth(); + + const org = user.memberships[0]?.organization; + const orgId = org?.id; + + // Get usage stats + const now = new Date(); + const usage = await db.usage.findFirst({ + where: { + orgId, + periodStart: { lte: now }, + periodEnd: { gte: now }, + }, + }); + + // Get recent content + const recentContent = await db.content.findMany({ + where: { + project: { orgId } + }, + take: 5, + orderBy: { createdAt: 'desc' }, + }); + + const limits = { + FREE: 10, + PRO: 1000, + BUSINESS: 'Unlimited', + AGENCY: 'Unlimited', + }; + + return ( +
+
+

Welcome back, {user.name}!

+

Plan: {org?.plan}

+
+ + {/* Usage Stats */} +
+
+

Content Generated

+

{usage?.itemsGenerated || 0}

+

+ of {limits[org?.plan || 'FREE']} this month +

+
+ +
+

Projects

+

+ {await db.project.count({ where: { orgId } })} +

+
+ +
+

Team Members

+

{org?.memberCount || 1}

+
+
+ + {/* Quick Actions */} +
+

Quick Actions

+
+ + 📝 +

Generate Text

+ + + 🎨 +

Generate Image

+ +
+
+ + {/* Recent Content */} +
+

Recent Content

+ {recentContent.length === 0 ? ( +

No content yet. Start generating!

+ ) : ( +
    + {recentContent.map(item => ( +
  • +
    + {item.kind} + + {new Date(item.createdAt).toLocaleDateString()} + +
    + + View + +
  • + ))} +
+ )} +
+
+ ); +} +``` + +### Step 5: Create Content Generator UI (45 min) + +#### File: `app/(dashboard)/content/new/page.tsx` +```typescript +'use client'; + +import { useState } from 'react'; +import { useSearchParams } from 'next/navigation'; + +export default function NewContentPage() { + const searchParams = useSearchParams(); + const initialType = searchParams.get('type') || 'text'; + + const [type, setType] = useState(initialType); + const [prompt, setPrompt] = useState(''); + const [tone, setTone] = useState('professional'); + const [length, setLength] = useState('medium'); + const [loading, setLoading] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(''); + + async function handleGenerate() { + setLoading(true); + setError(''); + setResult(null); + + try { + const response = await fetch('/api/v1/content/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ prompt, type, tone, length }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Generation failed'); + } + + setResult(data.content); + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + } finally { + setLoading(false); + } + } + + return ( +
+

Generate Content

+ + {/* Type Selector */} +
+ + +
+ + {/* Prompt Input */} +
+ +