Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Claude Code Task Management Guide

## Documentation Available

📚 **Project Documentation**: Check the documentation files in this directory for project-specific setup instructions and guides.
**Project Tasks**: Check the tasks directory in documentation/tasks for the list of tasks to be completed. Use the CLI commands below to interact with them.

## MANDATORY Task Management Workflow

🚨 **YOU MUST FOLLOW THIS EXACT WORKFLOW - NO EXCEPTIONS** 🚨

### **STEP 1: DISCOVER TASKS (MANDATORY)**
You MUST start by running this command to see all available tasks:
```bash
task-manager list-tasks
```

### **STEP 2: START EACH TASK (MANDATORY)**
Before working on any task, you MUST mark it as started:
```bash
task-manager start-task <task_id>
```

### **STEP 3: COMPLETE EACH TASK (MANDATORY)**
After finishing implementation, you MUST mark the task as completed:
```bash
task-manager complete-task <task_id> "Brief description of what was implemented"
```

## Task Files Location

📁 **Task Data**: Your tasks are organized in the `documentation/tasks/` directory:
- Task JSON files contain complete task information
- Use ONLY the `task-manager` commands listed above
- Follow the mandatory workflow sequence for each task

## MANDATORY Task Workflow Sequence

🔄 **For EACH individual task, you MUST follow this sequence:**

1. 📋 **DISCOVER**: `task-manager list-tasks` (first time only)
2. 🚀 **START**: `task-manager start-task <task_id>` (mark as in progress)
3. 💻 **IMPLEMENT**: Do the actual coding/implementation work
4. ✅ **COMPLETE**: `task-manager complete-task <task_id> "What was done"`
5. 🔁 **REPEAT**: Go to next task (start from step 2)

## Task Status Options

- `pending` - Ready to work on
- `in_progress` - Currently being worked on
- `completed` - Successfully finished
- `blocked` - Cannot proceed (waiting for dependencies)
- `cancelled` - No longer needed

## CRITICAL WORKFLOW RULES

❌ **NEVER skip** the `task-manager start-task` command
❌ **NEVER skip** the `task-manager complete-task` command
❌ **NEVER work on multiple tasks simultaneously**
✅ **ALWAYS complete one task fully before starting the next**
✅ **ALWAYS provide completion details in the complete command**
✅ **ALWAYS follow the exact 3-step sequence: list → start → complete**

## Final Requirements

🚨 **CRITICAL**: Your work is not complete until you have:
1. ✅ Completed ALL tasks using the mandatory workflow
2. ✅ Committed all changes with comprehensive commit messages
3. ✅ Created a pull request with proper description

Remember: The task management workflow is MANDATORY, not optional!
165 changes: 165 additions & 0 deletions app/api/waitlist/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@supabase/supabase-js'
import { z } from 'zod'

// Initialize Supabase client
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

// Rate limiting store (in production, use Redis or similar)
const rateLimitStore = new Map<string, { count: number; resetTime: number }>()

// Rate limit configuration
const RATE_LIMIT = {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
}

const waitlistSchema = z.object({
email: z.string().email('Invalid email address'),
})

function getClientIP(request: NextRequest): string {
const forwarded = request.headers.get('x-forwarded-for')
const realIP = request.headers.get('x-real-ip')

if (forwarded) {
return forwarded.split(',')[0].trim()
}

if (realIP) {
return realIP.trim()
}

return 'unknown'
}

function checkRateLimit(ip: string): boolean {
const now = Date.now()
const entry = rateLimitStore.get(ip)

if (!entry) {
rateLimitStore.set(ip, { count: 1, resetTime: now + RATE_LIMIT.windowMs })
return true
}

if (now > entry.resetTime) {
rateLimitStore.set(ip, { count: 1, resetTime: now + RATE_LIMIT.windowMs })
return true
}

if (entry.count >= RATE_LIMIT.max) {
return false
}

entry.count += 1
return true
}

export async function POST(request: NextRequest) {
try {
const ip = getClientIP(request)

// Check rate limit
if (!checkRateLimit(ip)) {
return NextResponse.json(
{ error: 'Too many requests. Please try again later.' },
{ status: 429 }
)
}

const body = await request.json()
const validation = waitlistSchema.safeParse(body)

if (!validation.success) {
return NextResponse.json(
{ error: validation.error.errors[0].message },
{ status: 400 }
)
}

const { email } = validation.data

// Check if email already exists
const { data: existingEmail } = await supabase
.from('waitlist')
.select('email')
.eq('email', email)
.single()

if (existingEmail) {
return NextResponse.json(
{ error: 'This email is already on the waitlist.' },
{ status: 409 }
)
}

// Add to waitlist
const { data, error } = await supabase
.from('waitlist')
.insert([{ email }])
.select()
.single()

if (error) {
console.error('Error adding to waitlist:', error)
return NextResponse.json(
{ error: 'Failed to add to waitlist. Please try again.' },
{ status: 500 }
)
}

return NextResponse.json(
{ message: 'Successfully added to waitlist', data },
{ status: 201 }
)

} catch (error) {
console.error('Waitlist API error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const email = searchParams.get('email')

if (!email) {
return NextResponse.json(
{ error: 'Email parameter is required' },
{ status: 400 }
)
}

if (!z.string().email().safeParse(email).success) {
return NextResponse.json(
{ error: 'Invalid email format' },
{ status: 400 }
)
}

const { data } = await supabase
.from('waitlist')
.select('email')
.eq('email', email)
.single()

return NextResponse.json(
{ exists: !!data },
{ status: 200 }
)

} catch (error) {
console.error('Error checking waitlist:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
5 changes: 5 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Download, Loader2, X } from 'lucide-react'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { useLocalStorage } from 'usehooks-ts'
import { formatDistanceToNow } from 'date-fns'
import { WaitlistSignup } from '@/components/waitlist-signup'

interface Report {
url: string
Expand Down Expand Up @@ -137,6 +138,10 @@ export default function Home() {
<AlertDescription>{error}</AlertDescription>
</Alert>
)}

<div className="mt-16 w-full max-w-2xl">
<WaitlistSignup />
</div>
</div>

{reports.length > 0 && (
Expand Down
116 changes: 116 additions & 0 deletions components/waitlist-signup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"use client"

import { useState } from "react"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { toast } from "sonner"
import { Loader2, Mail } from "lucide-react"

const waitlistFormSchema = z.object({
email: z.string().email("Please enter a valid email address"),
})

type WaitlistFormValues = z.infer<typeof waitlistFormSchema>

export function WaitlistSignup() {
const [isSubmitting, setIsSubmitting] = useState(false)

const form = useForm<WaitlistFormValues>({
resolver: zodResolver(waitlistFormSchema),
defaultValues: {
email: "",
},
})

async function onSubmit(values: WaitlistFormValues) {
setIsSubmitting(true)

try {
const response = await fetch("/api/waitlist", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
})

const data = await response.json()

if (!response.ok) {
throw new Error(data.error || "Failed to join waitlist")
}

toast.success("You're on the waitlist!", {
description: "We'll notify you when CodeGuide is ready.",
})

form.reset()
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Something went wrong"
toast.error("Error joining waitlist", {
description: errorMessage,
})
} finally {
setIsSubmitting(false)
}
}

return (
<Card className="w-full max-w-md mx-auto">
<CardHeader className="text-center">
<CardTitle className="text-2xl">Join the Waitlist</CardTitle>
<CardDescription>
Be the first to know when CodeGuide launches. Get early access and exclusive updates.
</CardDescription>
</CardHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<CardContent className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email address</FormLabel>
<FormControl>
<Input
type="email"
placeholder="your@email.com"
{...field}
className="h-11"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
<CardFooter>
<Button
type="submit"
className="w-full h-11"
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Joining waitlist...
</>
) : (
<>
<Mail className="mr-2 h-4 w-4" />
Join Waitlist
</>
)}
</Button>
</CardFooter>
</form>
</Form>
</Card>
)
}
9 changes: 9 additions & 0 deletions documentation/app_flowchart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
flowchart TD
A[User Opens App] --> B[Clerk Auth Gate]
B --> C[URL Submission Form]
C --> D[POST to api analyze]
D --> E[JinaAI Fetch Content]
E --> F[OpenAI Analyze Content]
F --> G[API Returns Markdown]
G --> H[Render AnalysisResult]
H --> I[Download Markdown Report]
Loading