From 4c1d665f486c9ea45d44065b697c7a594a6ce3ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 22:24:41 +0000 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20address=205=20UX=20issues=20?= =?UTF-8?q?=E2=80=94=20billing,=20chat,=20templates,=20idea=20board,=20err?= =?UTF-8?q?or=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tighten free tier: 1 repo, 1 analysis/mo, 1 blueprint view, 200 credits (was 2/2/3/500) - Billing page: show all 4 plans (Free, Pro, Scale, BYOK) side-by-side for easy comparison - Pattern Analyzer → App Idea Chat: conversational UI with chat bubbles, starter prompts, follow-up questions, and optional codebase grounding; new /api/app-idea-chat route - Templates page: add Create Template button (multi-step modal to combine blueprints) - Idea Board: restyle with status-colored cards, icon stats strip, New Analysis CTA - Analysis detail: fix overly broad catch that called notFound() on subscription/views table errors, keeping free-plan defaults instead --- app/api/app-idea-chat/route.ts | 166 +++++++++++ app/dashboard/analyses/[id]/page.tsx | 11 +- app/dashboard/templates/page.tsx | 62 +++-- app/pricing/page.tsx | 6 +- components/billing-client.tsx | 193 ++++++++++--- components/create-template-modal.tsx | 335 ++++++++++++++++++++++ components/dashboard-header.tsx | 2 +- components/idea-board.tsx | 175 ++++++++---- components/pattern-analyzer.tsx | 400 +++++++++++++++------------ lib/stripe.ts | 8 +- 10 files changed, 1036 insertions(+), 322 deletions(-) create mode 100644 app/api/app-idea-chat/route.ts create mode 100644 components/create-template-modal.tsx diff --git a/app/api/app-idea-chat/route.ts b/app/api/app-idea-chat/route.ts new file mode 100644 index 0000000..5e2c64d --- /dev/null +++ b/app/api/app-idea-chat/route.ts @@ -0,0 +1,166 @@ +import { NextRequest, NextResponse } from 'next/server' +import { Anthropic } from '@anthropic-ai/sdk' +import { + getAnalysisById, + getRepositoriesForAnalysis, + getBlueprintsByAnalysis, + getFilesByRepository, +} from '@/lib/queries' +import { getAnthropicModel } from '@/lib/anthropic-model' +import { getCurrentUser } from '@/lib/auth' +import { deductCredits, CREDITS } from '@/lib/credits' + +const anthropic = new Anthropic() + +export interface AppIdeaSuggestion { + name: string + tagline: string + description: string + type: string + difficulty: 'easy' | 'medium' | 'hard' + estimatedEffort: string + suggestedStack: string[] + monetizationAngle: string + whyNow: string +} + +export interface AppIdeaChatResponse { + reply: string + suggestions: AppIdeaSuggestion[] + followUpQuestions: string[] +} + +export interface ChatMessage { + role: 'user' | 'assistant' + content: string +} + +export async function POST(request: NextRequest) { + try { + const user = await getCurrentUser() + if (!user) { + return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }) + } + + const { message, analysisId, history = [] } = (await request.json()) as { + message: string + analysisId?: string + history?: ChatMessage[] + } + + if (!message?.trim()) { + return NextResponse.json({ error: 'Message is required' }, { status: 400 }) + } + + const creditResult = await deductCredits( + user.id, + CREDITS.PATTERN_ANALYZER_COST, + 'pattern_analyzer', + { analysisId }, + ) + if (!creditResult.success) { + return NextResponse.json({ error: creditResult.error || 'Insufficient credits' }, { status: 402 }) + } + + // Optionally load codebase context + let codebaseContext = '' + if (analysisId) { + try { + const analysis = await getAnalysisById(analysisId) + if (analysis && analysis.status === 'complete') { + const [repositories, blueprints] = await Promise.all([ + getRepositoriesForAnalysis(analysisId), + getBlueprintsByAnalysis(analysisId), + ]) + + const allFiles = ( + await Promise.all(repositories.map((r) => getFilesByRepository(r.id))) + ).flat() + + const techCount: Record = {} + for (const file of allFiles) { + for (const tech of file.technologies) { + techCount[tech] = (techCount[tech] || 0) + 1 + } + } + const topTech = Object.entries(techCount) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10) + .map(([t]) => t) + + codebaseContext = ` +## Developer's codebase context +Repositories: ${repositories.map((r) => r.name).join(', ')} +Top technologies: ${topTech.join(', ')} +Total files: ${allFiles.length} +Existing blueprints: ${blueprints.slice(0, 5).map((b) => b.name).join(', ') || 'none yet'} +` + } + } catch { + // Codebase context optional — continue without it + } + } + + // Build conversation history for context + const conversationHistory = history.slice(-6).map((m) => ({ + role: m.role as 'user' | 'assistant', + content: m.content, + })) + + const systemPrompt = `You are an expert product strategist and startup advisor helping developers discover what apps to build. You're having a friendly, concise conversation to help them find the perfect project idea. + +${codebaseContext} + +When responding: +- Keep your reply conversational and under 100 words +- Suggest 2-4 concrete project ideas tailored to their request${codebaseContext ? ' and their codebase' : ''} +- Ask a relevant follow-up question to refine suggestions +- Be enthusiastic and actionable + +Always respond with valid JSON only (no markdown fences): +{ + "reply": "conversational response under 100 words", + "suggestions": [ + { + "name": "Project Name", + "tagline": "One punchy sentence", + "description": "2-3 sentences", + "type": "SaaS | CLI Tool | API | Dashboard | etc", + "difficulty": "easy | medium | hard", + "estimatedEffort": "e.g. 1–2 weeks", + "suggestedStack": ["tech1", "tech2"], + "monetizationAngle": "How to charge", + "whyNow": "Why this is timely" + } + ], + "followUpQuestions": ["Question 1?", "Question 2?"] +}` + + const messages: Array<{ role: 'user' | 'assistant'; content: string }> = [ + ...conversationHistory, + { role: 'user', content: message }, + ] + + const response = await anthropic.messages.create({ + model: getAnthropicModel(), + max_tokens: 2048, + system: systemPrompt, + messages, + }) + + const raw = response.content[0].type === 'text' ? response.content[0].text.trim() : '' + const jsonText = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/, '').trim() + + let parsed: AppIdeaChatResponse + try { + parsed = JSON.parse(jsonText) + } catch { + return NextResponse.json({ error: 'Failed to parse AI response' }, { status: 500 }) + } + + return NextResponse.json(parsed) + } catch (error) { + console.error('[app-idea-chat] error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/app/dashboard/analyses/[id]/page.tsx b/app/dashboard/analyses/[id]/page.tsx index c862830..abce427 100644 --- a/app/dashboard/analyses/[id]/page.tsx +++ b/app/dashboard/analyses/[id]/page.tsx @@ -33,19 +33,22 @@ export default async function AnalysisDetailPage({ getRepositoriesForAnalysis(id), getBlueprintsByAnalysis(id), ]) + } catch { + notFound() + } - if (user) { + if (user) { + try { const [subscription, viewedIds] = await Promise.all([ getSubscriptionByGithubId(user.github_id), getUserViewedBlueprintIds(user.id), ]) userPlan = subscription?.plan || 'free' viewedBlueprintIds = viewedIds - // Check if in trial via Stripe (subscription status = 'trialing') isTrialing = subscription?.status === 'trialing' + } catch { + // Subscription/views table not available yet — use free defaults } - } catch { - notFound() } if (!analysis) { diff --git a/app/dashboard/templates/page.tsx b/app/dashboard/templates/page.tsx index 38d764b..c3949ba 100644 --- a/app/dashboard/templates/page.tsx +++ b/app/dashboard/templates/page.tsx @@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { TemplateAssemblyCard } from '@/components/template-assembly-card' +import { CreateTemplateModal } from '@/components/create-template-modal' import { getAllTemplates, getFeaturedTemplates, type Template } from '@/lib/queries' export const dynamic = 'force-dynamic' @@ -42,29 +43,33 @@ async function TemplateHubContent() { if (setupRequired || !all.length) { return (
-
- - - -
-

Template Assembly Hub

-

Pre-built project combinations ready to assemble

+
+
+ + + +
+

Template Assembly Hub

+

Pre-built project combinations ready to assemble

+
+
-

No templates available yet

+

No templates yet

- Templates will be generated once you run an analysis on your repositories. + Create a template by combining blueprints from your analyses, or run an analysis to discover new blueprints.

- +
+ + +
) @@ -85,18 +90,21 @@ async function TemplateHubContent() {
{/* Header */}
-
- - - -
-

Template Assembly Hub

-

- Start building today from code you already have. Pre-configured templates combine your best pieces. -

+
+
+ + + +
+

Template Assembly Hub

+

+ Start building today from code you already have. Pre-configured templates combine your best pieces. +

+
+
{/* Feature Cards */} diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx index 8f26172..e0c8e70 100644 --- a/app/pricing/page.tsx +++ b/app/pricing/page.tsx @@ -15,9 +15,9 @@ const plans = [ description: 'Explore your codebase, no card needed', credits: PLANS.free.credits_per_month, features: [ - `Up to ${PLANS.free.repos_limit} repositories`, - `${PLANS.free.analyses_per_month} analyses per month`, - `${PLANS.free.blueprints_viewable} blueprint views`, + `${PLANS.free.repos_limit} repository`, + `${PLANS.free.analyses_per_month} analysis per month`, + `${PLANS.free.blueprints_viewable} blueprint view`, `${PLANS.free.credits_per_month} credits to start`, 'AI-powered app blueprints', 'Gap discovery & analysis', diff --git a/components/billing-client.tsx b/components/billing-client.tsx index 743ebc3..f8b6377 100644 --- a/components/billing-client.tsx +++ b/components/billing-client.tsx @@ -15,6 +15,8 @@ import { ExternalLink, Zap, Crown, + Rocket, + Key, } from 'lucide-react' interface BillingClientProps { @@ -32,13 +34,61 @@ interface BillingClientProps { isTrialing?: boolean } +const PLAN_CONFIGS = [ + { + id: 'free', + name: 'Free', + price: '$0', + period: 'forever', + description: 'Try it out', + icon: Zap, + features: ['1 repository', '1 analysis/month', '1 blueprint view', '200 credits'], + cta: null, + ctaHref: null, + }, + { + id: 'pro', + name: 'Pro', + price: '$19', + period: '/mo', + description: '7-day free trial', + icon: Crown, + features: ['Unlimited repos', 'Unlimited analyses', '3,000 credits/month', 'Scaffold generation', 'App Idea Chat', 'Build This App'], + cta: 'Start Free Trial', + ctaHref: null, // handled by handleUpgrade + highlighted: true, + }, + { + id: 'scale', + name: 'Scale', + price: '$49', + period: '/mo', + description: 'Power users & teams', + icon: Rocket, + features: ['Everything in Pro', '12,000 credits/month', 'Highest priority AI', 'Early access', 'Dedicated support'], + cta: 'Get Scale', + ctaHref: 'https://buy.stripe.com/3cIcN65VJ55g6nC9gkbjW00', + }, + { + id: 'byok', + name: 'BYOK', + price: '$9', + period: '/mo', + description: 'Your own API key', + icon: Key, + features: ['Unlimited repos', 'Unlimited analyses', 'Use own Anthropic/OpenAI key', 'No per-credit billing', 'Up to 90% cheaper'], + cta: 'Set Up BYOK', + ctaHref: '/dashboard/settings', + }, +] as const + export function BillingClient({ plan, planName, analysesUsed, analysesLimit, blueprintsUsed = 0, - blueprintsLimit = 2, + blueprintsLimit = 1, reposLimit, status, currentPeriodEnd, @@ -56,18 +106,14 @@ export function BillingClient({ const handleUpgrade = async () => { setCheckoutLoading(true) try { - console.log('[v0] Starting checkout request') const res = await fetch('/api/stripe/checkout', { method: 'POST' }) - console.log('[v0] Checkout response status:', res.status) const data = await res.json().catch(() => ({ error: 'Unexpected server error' })) - console.log('[v0] Checkout response data:', data) if (res.ok && data.url) { window.location.href = data.url } else { alert(data.error || 'Billing is not available right now. Please try again later.') } - } catch (error) { - console.error('[v0] Checkout error:', error) + } catch { alert('Could not connect to billing. Please check your connection and try again.') } finally { setCheckoutLoading(false) @@ -82,7 +128,7 @@ export function BillingClient({ if (res.ok && data.url) { window.location.href = data.url } else { - alert(data.error || 'Billing portal is not available right now. Please try again later.') + alert(data.error || 'Billing portal is not available right now.') } } catch { alert('Could not connect to billing. Please check your connection and try again.') @@ -98,7 +144,7 @@ export function BillingClient({

Manage your subscription and usage

- {/* Current plan */} + {/* Current plan + usage */}
@@ -136,13 +182,13 @@ export function BillingClient({
- {isPaid ? 'Unlimited repositories' : `Up to ${reposLimit} repositories`} + {isPaid ? 'Unlimited repositories' : `Up to ${reposLimit} repositor${reposLimit === 1 ? 'y' : 'ies'}`}
- {isPaid ? 'Unlimited analyses' : `${analysesLimit} analyses per month`} + {isPaid ? 'Unlimited analyses' : `${analysesLimit} analysis per month`}
{isPaid && ( @@ -162,20 +208,12 @@ export function BillingClient({
{isPaid && hasStripeCustomer ? ( ) : !isPaid ? ( ) : null} @@ -200,15 +238,11 @@ export function BillingClient({ {analysesLimit > 0 ? ( ) : ( -
-
-
+
)} {!isPaid && analysesLimit > 0 && usagePercent >= 80 && (

- {usagePercent >= 100 - ? 'You\'ve reached your monthly limit.' - : 'Approaching your monthly limit.'} + {usagePercent >= 100 ? "You've reached your monthly limit." : 'Approaching your monthly limit.'} {' '}

- {/* Blueprint views for free users */} {!isPaid && blueprintsLimit > 0 && (
@@ -245,11 +278,10 @@ export function BillingClient({

Unlock unlimited analyses

- Pro gives you unlimited analyses, unlimited repos, scaffold generation, and priority AI. + Pro gives you unlimited analyses, repos, scaffold generation, and priority AI.

@@ -259,28 +291,105 @@ export function BillingClient({
- {/* Credits Section for paid users */} + {/* Credits for paid users */} {isPaid && userId && (

Credits & Usage

-

Track your credits and see how they're used

+

Track your credits and see how they're used

)} - {/* Compare plans */} - {!isPaid && ( -
- + {/* All plans comparison */} +
+
+

All Plans

+

Compare plans and upgrade when you're ready

- )} +
+ {PLAN_CONFIGS.map((p) => { + const PlanIcon = p.icon + const isCurrent = plan === p.id + const highlighted = 'highlighted' in p && p.highlighted + + return ( + + {isCurrent && ( +
+ + Current Plan + +
+ )} + {highlighted && !isCurrent && ( +
+ + Most Popular + +
+ )} + +
+
+ +
+

{p.name}

+

{p.description}

+
+ +
+ {p.price} + {p.period} +
+ +
    + {p.features.map((feature) => ( +
  • + + + + {feature} +
  • + ))} +
+ + {!isCurrent && p.cta && ( + p.ctaHref ? ( + + ) : ( + + ) + )} + {isCurrent && ( +
+ ✓ Your current plan +
+ )} +
+ ) + })} +
+
) } diff --git a/components/create-template-modal.tsx b/components/create-template-modal.tsx new file mode 100644 index 0000000..05166bf --- /dev/null +++ b/components/create-template-modal.tsx @@ -0,0 +1,335 @@ +'use client' + +import { useState, useEffect } from 'react' +import { Button } from '@/components/ui/button' +import { Card } from '@/components/ui/card' +import { Input } from '@/components/ui/input' +import { Badge } from '@/components/ui/badge' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogDescription, +} from '@/components/ui/dialog' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Plus, Loader2, Sparkles, Check } from 'lucide-react' + +interface BlueprintOption { + id: string + name: string + description?: string + technologies: string[] +} + +interface AnalysisOption { + id: string + name: string + status: string +} + +export function CreateTemplateModal({ onCreated }: { onCreated?: () => void }) { + const [open, setOpen] = useState(false) + const [step, setStep] = useState<'select-analysis' | 'select-blueprints' | 'name'>('select-analysis') + const [analyses, setAnalyses] = useState([]) + const [blueprints, setBlueprints] = useState([]) + const [selectedAnalysisId, setSelectedAnalysisId] = useState('') + const [selectedBlueprintIds, setSelectedBlueprintIds] = useState([]) + const [templateName, setTemplateName] = useState('') + const [templateDescription, setTemplateDescription] = useState('') + const [loadingAnalyses, setLoadingAnalyses] = useState(false) + const [loadingBlueprints, setLoadingBlueprints] = useState(false) + const [creating, setCreating] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { + if (open) { + fetchAnalyses() + } + }, [open]) + + const fetchAnalyses = async () => { + setLoadingAnalyses(true) + try { + const res = await fetch('/api/analyses') + if (!res.ok) throw new Error('Failed to load analyses') + const data: AnalysisOption[] = await res.json() + setAnalyses(data.filter((a) => a.status === 'complete')) + } catch { + setError('Could not load analyses') + } finally { + setLoadingAnalyses(false) + } + } + + const fetchBlueprints = async (analysisId: string) => { + setLoadingBlueprints(true) + setBlueprints([]) + setSelectedBlueprintIds([]) + try { + const res = await fetch(`/api/analyses/${analysisId}/blueprints`) + if (res.ok) { + const data: BlueprintOption[] = await res.json() + setBlueprints(data) + } else { + // Fallback: use analysis detail endpoint + const res2 = await fetch(`/api/analyses/${analysisId}`) + if (res2.ok) { + const analysis = await res2.json() + setBlueprints(analysis.blueprints || []) + } + } + } catch { + setError('Could not load blueprints for this analysis') + } finally { + setLoadingBlueprints(false) + } + } + + const handleAnalysisSelect = async (id: string) => { + setSelectedAnalysisId(id) + setError(null) + await fetchBlueprints(id) + setStep('select-blueprints') + } + + const toggleBlueprint = (id: string) => { + setSelectedBlueprintIds((prev) => + prev.includes(id) ? prev.filter((b) => b !== id) : [...prev, id], + ) + } + + const handleCreate = async () => { + if (!templateName.trim()) { + setError('Please enter a template name') + return + } + if (selectedBlueprintIds.length < 2) { + setError('Select at least 2 blueprints to combine') + return + } + + setCreating(true) + setError(null) + try { + const res = await fetch('/api/templates/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: templateName.trim(), + description: templateDescription.trim() || undefined, + blueprintIds: selectedBlueprintIds, + tier: 'standard', + }), + }) + if (!res.ok) { + const data = await res.json() + throw new Error(data.error || 'Failed to create template') + } + setOpen(false) + reset() + onCreated?.() + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to create template') + } finally { + setCreating(false) + } + } + + const reset = () => { + setStep('select-analysis') + setSelectedAnalysisId('') + setSelectedBlueprintIds([]) + setTemplateName('') + setTemplateDescription('') + setBlueprints([]) + setError(null) + } + + const handleClose = () => { + setOpen(false) + reset() + } + + return ( + <> + + + (v ? setOpen(true) : handleClose())}> + + + + + Create Template + + + Combine blueprints from a completed analysis into a reusable template. + + + +
+ {/* Step 1: Select analysis */} + {step === 'select-analysis' && ( +
+

Pick a completed analysis

+ {loadingAnalyses ? ( +
+ + Loading analyses... +
+ ) : analyses.length === 0 ? ( + +

+ No completed analyses found. Run an analysis first to generate blueprints. +

+
+ ) : ( +
+ {analyses.map((a) => ( + + ))} +
+ )} +
+ )} + + {/* Step 2: Select blueprints */} + {step === 'select-blueprints' && ( +
+
+

Select blueprints to combine

+ + {selectedBlueprintIds.length} selected (min 2) + +
+ + {loadingBlueprints ? ( +
+ + Loading blueprints... +
+ ) : blueprints.length === 0 ? ( + +

+ No blueprints found for this analysis. Run the analysis again or pick a different one. +

+
+ ) : ( +
+ {blueprints.map((bp) => { + const selected = selectedBlueprintIds.includes(bp.id) + return ( + + ) + })} +
+ )} + + {selectedBlueprintIds.length >= 2 && ( + + )} + + +
+ )} + + {/* Step 3: Name the template */} + {step === 'name' && ( +
+
+ + setTemplateName(e.target.value)} + /> +
+
+ + setTemplateDescription(e.target.value)} + /> +
+
+ Combining {selectedBlueprintIds.length} blueprints +
+ +
+ )} + + {error && ( +

{error}

+ )} +
+ + {step === 'name' && ( + + + + + )} +
+
+ + ) +} diff --git a/components/dashboard-header.tsx b/components/dashboard-header.tsx index a7f07e3..3b0b18e 100644 --- a/components/dashboard-header.tsx +++ b/components/dashboard-header.tsx @@ -16,7 +16,7 @@ const navItems = [ { href: '/dashboard/repositories', label: 'Repositories', icon: FolderGit2 }, { href: '/dashboard/analyses', label: 'Analyses', icon: Sparkles }, { href: '/dashboard/idea-board', label: 'Idea Board', icon: LayoutGrid }, - { href: '/dashboard/pattern-analyzer', label: 'Pattern Analyzer', icon: Cpu }, + { href: '/dashboard/pattern-analyzer', label: 'App Idea Chat', icon: Cpu }, { href: '/dashboard/billing', label: 'Billing', icon: CreditCard }, ] diff --git a/components/idea-board.tsx b/components/idea-board.tsx index 6f73b12..18a9555 100644 --- a/components/idea-board.tsx +++ b/components/idea-board.tsx @@ -16,6 +16,7 @@ import { Search, RefreshCw, LayoutGrid, + Plus, type LucideIcon, } from 'lucide-react' import type { Analysis } from '@/lib/queries' @@ -24,36 +25,41 @@ type StatusFilter = 'all' | Analysis['status'] const STATUS_META: Record< Analysis['status'], - { label: string; color: string; badgeClass: string; icon: LucideIcon } + { label: string; color: string; badgeClass: string; cardBorder: string; icon: LucideIcon } > = { pending: { label: 'Pending', color: 'text-muted-foreground', - badgeClass: 'bg-muted text-muted-foreground', + badgeClass: 'bg-muted text-muted-foreground border-0', + cardBorder: 'border-border/60', icon: Clock, }, scanning: { label: 'Scanning', - color: 'text-chart-1', - badgeClass: 'bg-chart-1/10 text-chart-1', + color: 'text-blue-500', + badgeClass: 'bg-blue-500/10 text-blue-500 border-0', + cardBorder: 'border-blue-500/30', icon: Loader2, }, analyzing: { label: 'Analyzing', color: 'text-chart-2', - badgeClass: 'bg-chart-2/10 text-chart-2', + badgeClass: 'bg-chart-2/10 text-chart-2 border-0', + cardBorder: 'border-chart-2/30', icon: Sparkles, }, complete: { label: 'Complete', color: 'text-chart-1', - badgeClass: 'bg-chart-1/10 text-chart-1', + badgeClass: 'bg-chart-1/10 text-chart-1 border-0', + cardBorder: 'border-chart-1/30', icon: CheckCircle2, }, failed: { label: 'Failed', color: 'text-destructive', - badgeClass: 'bg-destructive/10 text-destructive', + badgeClass: 'bg-destructive/10 text-destructive border-0', + cardBorder: 'border-destructive/30', icon: XCircle, }, } @@ -76,11 +82,17 @@ function AnalysisCard({ analysis }: { analysis: Analysis }) { : 0 return ( - +
-
- +
+

{analysis.name}

@@ -94,9 +106,6 @@ function AnalysisCard({ analysis }: { analysis: Analysis }) {
- {meta.label}
@@ -105,11 +114,13 @@ function AnalysisCard({ analysis }: { analysis: Analysis }) {
{analysis.analyzed_files} / {analysis.total_files} files - {progress}% + {progress}%
@@ -117,12 +128,14 @@ function AnalysisCard({ analysis }: { analysis: Analysis }) { )} {analysis.error_message && ( -

{analysis.error_message}

+

+ {analysis.error_message} +

)} - @@ -173,50 +186,87 @@ export function IdeaBoard() { {} as Record, ) + const completeCount = counts['complete'] || 0 + const failedCount = counts['failed'] || 0 + const inProgressCount = (counts['scanning'] || 0) + (counts['analyzing'] || 0) + return (
{/* Header */}
-
- +
+
+ +

Idea Board

-

All your analyses at a glance — track progress and review results.

+

All your analyses at a glance — track progress and review results.

+
+
+ +
-
{/* Stats strip */} {!loading && analyses.length > 0 && ( -
- {(Object.keys(STATUS_META) as Analysis['status'][]).map((status) => { - const meta = STATUS_META[status] - const count = counts[status] || 0 - if (count === 0) return null - return ( - - ) - })} +
+ setStatusFilter(statusFilter === 'complete' ? 'all' : 'complete')} + > +
+
+ +
+
+

{completeCount}

+

Complete

+
+
+
+ 0 ? 'hover:shadow-sm' : 'opacity-60'}`} + onClick={() => inProgressCount > 0 && setStatusFilter('scanning')} + > +
+
+ +
+
+

{inProgressCount}

+

In Progress

+
+
+
+ 0 ? 'hover:shadow-sm' : 'opacity-60'}`} + onClick={() => failedCount > 0 && setStatusFilter(statusFilter === 'failed' ? 'all' : 'failed')} + > +
+
+ +
+
+

{failedCount}

+

Failed

+
+
+
)} @@ -239,11 +289,11 @@ export function IdeaBoard() { className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${ statusFilter === f.value ? 'bg-foreground text-background' - : 'bg-muted text-muted-foreground hover:text-foreground' + : 'bg-muted text-muted-foreground hover:text-foreground hover:bg-muted/80' }`} > {f.label} - {f.value !== 'all' && counts[f.value] != null && ( + {f.value !== 'all' && counts[f.value] != null && counts[f.value] > 0 && ( {counts[f.value]} )} @@ -254,37 +304,40 @@ export function IdeaBoard() { {/* Content */} {loading && (
- +
+ +

Loading analyses...

+
)} {error && ( - +

Failed to load analyses

{error}

- +
)} {!loading && !error && filtered.length === 0 && ( - +
+ +

{analyses.length === 0 ? 'No analyses yet' : 'No matches'}

-

+

{analyses.length === 0 - ? 'Run your first analysis to see it appear here.' + ? 'Run your first analysis to discover apps you can build from your existing code.' : 'Try adjusting your search or filter.'}

{analyses.length === 0 && ( )} diff --git a/components/pattern-analyzer.tsx b/components/pattern-analyzer.tsx index 3eae444..0c09371 100644 --- a/components/pattern-analyzer.tsx +++ b/components/pattern-analyzer.tsx @@ -1,9 +1,10 @@ 'use client' -import { useState } from 'react' +import { useState, useRef, useEffect } from 'react' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' +import { Input } from '@/components/ui/input' import { Select, SelectContent, @@ -12,20 +13,20 @@ import { SelectValue, } from '@/components/ui/select' import { - Cpu, + MessageSquare, Sparkles, Loader2, + Send, Lightbulb, Clock, TrendingUp, - Zap, - Tag, ChevronDown, ChevronUp, - AlertCircle, + Bot, + User, } from 'lucide-react' import type { Analysis } from '@/lib/queries' -import type { PatternAnalyzerResult, ProjectSuggestion } from '@/app/api/pattern-analyzer/route' +import type { AppIdeaSuggestion, AppIdeaChatResponse, ChatMessage } from '@/app/api/app-idea-chat/route' const DIFFICULTY_META = { easy: { label: 'Easy', class: 'bg-chart-1/10 text-chart-1' }, @@ -33,44 +34,47 @@ const DIFFICULTY_META = { hard: { label: 'Hard', class: 'bg-destructive/10 text-destructive' }, } -function SuggestionCard({ suggestion }: { suggestion: ProjectSuggestion }) { +const STARTER_PROMPTS = [ + 'I want to build a developer tool', + 'I want to create a SaaS business', + 'I want to build something with AI', + 'I need a quick side project to ship', +] + +function SuggestionCard({ suggestion }: { suggestion: AppIdeaSuggestion }) { const [expanded, setExpanded] = useState(false) const diff = DIFFICULTY_META[suggestion.difficulty] ?? DIFFICULTY_META.medium return ( - -
+ +
-

{suggestion.name}

- - {suggestion.type} - +

{suggestion.name}

+ {suggestion.type}
-

{suggestion.tagline}

+

{suggestion.tagline}

{diff.label}
-

{suggestion.description}

+

{suggestion.description}

-
-
- +
+
+ {suggestion.estimatedEffort}
-
- +
+ {suggestion.monetizationAngle}
{suggestion.suggestedStack.length > 0 && ( -
+
{suggestion.suggestedStack.map((tech) => ( - - {tech} - + {tech} ))}
)} @@ -79,113 +83,184 @@ function SuggestionCard({ suggestion }: { suggestion: ProjectSuggestion }) { onClick={() => setExpanded((v) => !v)} className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors" > - {expanded ? : } - {expanded ? 'Less detail' : 'More detail'} + {expanded ? : } + {expanded ? 'Less detail' : 'Why now'} {expanded && ( -
- {suggestion.detectedPatterns.length > 0 && ( -
-

- - Inspired by -

-
    - {suggestion.detectedPatterns.map((p) => ( -
  • - - {p} -
  • - ))} -
-
- )} -
-

- - Why now -

-

{suggestion.whyNow}

-
+
+ {suggestion.whyNow}
)} ) } +interface ChatBubbleProps { + message: ChatMessage & { suggestions?: AppIdeaSuggestion[]; followUpQuestions?: string[] } + onFollowUp?: (q: string) => void +} + +function ChatBubble({ message, onFollowUp }: ChatBubbleProps) { + const isUser = message.role === 'user' + + return ( +
+
+ {isUser ? : } +
+
+
+ {message.content} +
+ + {!isUser && message.suggestions && message.suggestions.length > 0 && ( +
+ {message.suggestions.map((s) => ( + + ))} +
+ )} + + {!isUser && message.followUpQuestions && message.followUpQuestions.length > 0 && ( +
+ {message.followUpQuestions.map((q) => ( + + ))} +
+ )} +
+
+ ) +} + interface PatternAnalyzerProps { completedAnalyses: Analysis[] } +type FullChatMessage = ChatMessage & { + suggestions?: AppIdeaSuggestion[] + followUpQuestions?: string[] +} + export function PatternAnalyzer({ completedAnalyses }: PatternAnalyzerProps) { - const [selectedAnalysisId, setSelectedAnalysisId] = useState( - completedAnalyses[0]?.id ?? '', - ) - const [result, setResult] = useState(null) + const [messages, setMessages] = useState([ + { + role: 'assistant', + content: + "Hi! I'm your App Idea advisor. Tell me what kind of app you want to build — your tech stack preferences, target audience, or problem you want to solve — and I'll suggest the best project ideas for you.", + suggestions: [], + followUpQuestions: STARTER_PROMPTS, + }, + ]) + const [input, setInput] = useState('') const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) + const [selectedAnalysisId, setSelectedAnalysisId] = useState('') + const bottomRef = useRef(null) - const handleScan = async () => { - if (!selectedAnalysisId) return + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [messages]) + + const sendMessage = async (text: string) => { + if (!text.trim() || loading) return + + const userMessage: FullChatMessage = { role: 'user', content: text } + const updatedMessages = [...messages, userMessage] + setMessages(updatedMessages) + setInput('') setLoading(true) - setError(null) - setResult(null) try { - const res = await fetch('/api/pattern-analyzer', { + const history = updatedMessages + .filter((m) => !m.suggestions?.length && !m.followUpQuestions?.length) + .slice(-8) + .map(({ role, content }) => ({ role, content })) + + const res = await fetch('/api/app-idea-chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ analysisId: selectedAnalysisId }), + body: JSON.stringify({ + message: text, + analysisId: selectedAnalysisId || undefined, + history, + }), }) if (!res.ok) { const data = await res.json() - throw new Error(data.error || 'Scan failed') + throw new Error(data.error || 'Request failed') } - const data: PatternAnalyzerResult = await res.json() - setResult(data) + const data: AppIdeaChatResponse = await res.json() + + setMessages((prev) => [ + ...prev, + { + role: 'assistant', + content: data.reply, + suggestions: data.suggestions, + followUpQuestions: data.followUpQuestions, + }, + ]) } catch (err) { - setError(err instanceof Error ? err.message : 'Scan failed') + setMessages((prev) => [ + ...prev, + { + role: 'assistant', + content: + err instanceof Error ? err.message : 'Something went wrong. Please try again.', + }, + ]) } finally { setLoading(false) } } - const noAnalyses = completedAnalyses.length === 0 - return ( -
+
{/* Header */} -
+
- -

Pattern Analyzer

+ +

App Idea Chat

-

- Scan your analyzed codebase to surface hidden patterns and get AI-generated project suggestions - tailored to your tech stack. +

+ Describe what you want to build and get tailored project ideas — optionally grounded in your codebase.

- {/* Control panel */} - -

Select a completed analysis to scan

- - {noAnalyses ? ( -
- - No completed analyses yet. Run an analysis first, then come back here. -
- ) : ( -
-
+ {/* Codebase selector */} + {completedAnalyses.length > 0 && ( +
+ +
+
+ + Ground ideas in your codebase +
- -
- )} - - - {/* Error */} - {error && ( - -
- - Scan failed -
-

{error}

-
- )} - - {/* Loading skeleton */} - {loading && ( -
- {[1, 2, 3].map((i) => ( - -
-
-
- - ))} +
)} - {/* Results */} - {result && !loading && ( -
- {/* Detected patterns */} - {result.patterns.length > 0 && ( -
-

- - Detected Patterns -

-
- {result.patterns.map((p) => ( - - {p} - - ))} -
-
- )} - - {/* Top technologies */} - {result.topTechnologies.length > 0 && ( -
-

- - Top Technologies -

-
- {result.topTechnologies.map((t) => ( - - {t} - - ))} -
-
- )} + {/* Chat messages */} +
+ {messages.map((msg, i) => ( + { + setInput(q) + sendMessage(q) + }} + /> + ))} + {loading && ( +
+
+ +
+
+ +
+
+ )} +
+
- {/* Project suggestions */} - {result.suggestions.length > 0 && ( -
-
- -

Project Suggestions

- - {result.suggestions.length} ideas - -
-
- {result.suggestions.map((s) => ( - - ))} -
-
- )} + {/* Input */} +
+
+ setInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + sendMessage(input) + } + }} + placeholder="Describe the kind of app you want to build..." + disabled={loading} + className="flex-1" + /> +
- )} +

+ + Each message costs credits. Be specific for better results. +

+
) } diff --git a/lib/stripe.ts b/lib/stripe.ts index 5d0ef55..f7abb4b 100644 --- a/lib/stripe.ts +++ b/lib/stripe.ts @@ -26,11 +26,11 @@ export function getStripe(): Stripe { export const PLANS = { free: { name: 'Free', - analyses_per_month: 2, - blueprints_viewable: 3, - repos_limit: 2, + analyses_per_month: 1, + blueprints_viewable: 1, + repos_limit: 1, price_monthly: 0, - credits_per_month: 500, + credits_per_month: 200, ai_provider: 'builtin' as const, description: 'Explore the basics, no card needed', }, From 5294d56110762f430156f320d26b7be2c77b4e73 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 22:51:19 +0000 Subject: [PATCH 2/6] feat: unify header style across all pages to match homepage - DashboardHeader: switch to black/cyan cyberpunk theme (bg-black/95, border-cyan-500/20, RepoFuseLogo3D, mono tracking-wider nav links, cyan ring on avatar, cyan-styled auth buttons) - Dashboard layout: bg-black text-white to match homepage - Pricing page: same black/cyan header with RepoFuseLogo3D; plan cards and credit table restyled for dark background (gray-900 cards, cyan accents, white text) --- app/dashboard/layout.tsx | 2 +- app/pricing/page.tsx | 97 ++++++++++++++++----------------- components/dashboard-header.tsx | 57 +++++++++---------- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx index 4d400cf..c5c8818 100644 --- a/app/dashboard/layout.tsx +++ b/app/dashboard/layout.tsx @@ -14,7 +14,7 @@ export default async function DashboardLayout({ } return ( -
+
{children} diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx index e0c8e70..ac13d91 100644 --- a/app/pricing/page.tsx +++ b/app/pricing/page.tsx @@ -2,7 +2,7 @@ import Link from 'next/link' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Check, ArrowRight, Sparkles, Github, Crown, Zap, Rocket, Key } from 'lucide-react' -import { RepoFuseLogo } from '@/components/repofuse-logo' +import { RepoFuseLogo3D } from '@/components/repofuse-logo-3d' import { PLANS } from '@/lib/stripe' import { CREDITS } from '@/lib/credits' @@ -108,21 +108,21 @@ const creditCosts = [ export default function PricingPage() { return ( -
+
{/* Header */} -
-
- - +
+
+ + @@ -132,14 +132,14 @@ export default function PricingPage() {
{/* Hero */}
-
- +
+ Simple, transparent pricing
-

+

Pick your plan

-

+

Start free and upgrade when you need more power — unlimited repos, more credits, and the ability to build and push real apps.

@@ -149,35 +149,35 @@ export default function PricingPage() { {plans.map((plan) => { const PlanIcon = plan.icon return ( - {plan.highlighted && (
- + Most Popular
)}
-
- +
+
-

{plan.name}

-

{plan.description}

+

{plan.name}

+

{plan.description}

- {plan.price} - {plan.period} + {plan.price} + {plan.period} {plan.credits != null && ( -

+

{plan.credits.toLocaleString()} credits/mo

)} @@ -186,26 +186,25 @@ export default function PricingPage() {
    {plan.features.map((feature) => (
  • - - + + - {feature} + {feature}
  • ))}
- - + {plan.cta} + +
) })}
@@ -213,24 +212,24 @@ export default function PricingPage() { {/* Credit costs table */}
-

How credits work

-

- Each action costs credits. Pro gets 3,000/mo, Scale gets 12,000/mo — unused credits don't roll over. +

How credits work

+

+ Each action costs credits. Pro gets 3,000/mo, Scale gets 12,000/mo — unused credits don't roll over.

{creditCosts.map((item) => ( - +
{item.icon}
-

{item.action}

-

{item.cost}

-

credits

- +

{item.action}

+

{item.cost}

+

credits

+
))}
-

+

All plans include read-only repository access. Your code is never stored — we only analyze file structures and patterns.

diff --git a/components/dashboard-header.tsx b/components/dashboard-header.tsx index 3b0b18e..c4d769a 100644 --- a/components/dashboard-header.tsx +++ b/components/dashboard-header.tsx @@ -2,9 +2,9 @@ import Link from 'next/link' import { usePathname } from 'next/navigation' -import { Github, BarChart3, FolderGit2, Sparkles, CreditCard, LayoutGrid, Cpu } from 'lucide-react' +import { Github, BarChart3, FolderGit2, Sparkles, CreditCard, LayoutGrid, MessageSquare } from 'lucide-react' import { cn } from '@/lib/utils' -import { RepoFuseLogo } from '@/components/repofuse-logo' +import { RepoFuseLogo3D } from '@/components/repofuse-logo-3d' import type { AuthUser } from '@/lib/auth' interface DashboardHeaderProps { @@ -13,10 +13,10 @@ interface DashboardHeaderProps { const navItems = [ { href: '/dashboard', label: 'Overview', icon: BarChart3 }, - { href: '/dashboard/repositories', label: 'Repositories', icon: FolderGit2 }, + { href: '/dashboard/repositories', label: 'Repos', icon: FolderGit2 }, { href: '/dashboard/analyses', label: 'Analyses', icon: Sparkles }, { href: '/dashboard/idea-board', label: 'Idea Board', icon: LayoutGrid }, - { href: '/dashboard/pattern-analyzer', label: 'App Idea Chat', icon: Cpu }, + { href: '/dashboard/pattern-analyzer', label: 'App Idea Chat', icon: MessageSquare }, { href: '/dashboard/billing', label: 'Billing', icon: CreditCard }, ] @@ -25,11 +25,11 @@ export function DashboardHeader({ user }: DashboardHeaderProps) { return ( <> -
-
-
- - +
+
+
+ +
-
+
{user ? (
{user.github_avatar_url && ( {user.github_username} )} -
-

@{user.github_username}

+
+

@{user.github_username}

Sign out @@ -80,19 +80,19 @@ export function DashboardHeader({ user }: DashboardHeaderProps) {
- Connect GitHub + GitHub - Connect GitLab + GitLab
)} @@ -100,8 +100,9 @@ export function DashboardHeader({ user }: DashboardHeaderProps) {
-
diff --git a/components/dashboard-header.tsx b/components/dashboard-header.tsx index c4d769a..7e22db4 100644 --- a/components/dashboard-header.tsx +++ b/components/dashboard-header.tsx @@ -78,14 +78,14 @@ export function DashboardHeader({ user }: DashboardHeaderProps) {
) : ( )}
From 0ebde0c09e37389f9cb4ef1f8d158b1b1a812201 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 03:41:03 +0000 Subject: [PATCH 5/6] Fix hero section blank space on mobile by reordering columns On mobile, the left column (text) was pushed down by 96px of padding before showing any content. Fix: reduce section/container top padding and use CSS order-* classes so the terminal window appears first above the text on small screens, matching the visual hierarchy on desktop (left-first layout preserved via lg:order-1 / lg:order-2). https://claude.ai/code/session_01JcAQpzFeBtwmK6ZrXKZ8mx --- app/page.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 240d19b..bc3e611 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -86,7 +86,7 @@ export default async function HomePage({ searchParams }: { searchParams: Promise {/* Hero Section */}
-
+
{/* Animated grid background */}
-
-
- {/* Left content */} -
+
+
+ {/* Left content — appears second on mobile, first on desktop */} +
{/* Badge */}
@@ -152,8 +152,8 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
- {/* Right: Terminal window */} -
+ {/* Right: Terminal window — appears first on mobile */} +
{/* Terminal header */}
From 58157fa33d042baba4aa1d1343b228f898727114 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 04:07:04 +0000 Subject: [PATCH 6/6] Redesign homepage to centered single-column MyClaw-style layout - Simplified header: logo + name, desktop nav, single cyan "Get Started" button; NavDropdown shown in mobile header - Hero: centered single column instead of 2-col grid; trust badge with Zap icon + divider; large two-line headline (white/cyan); single full-width-on-mobile CTA button; avatar stack social proof - Terminal preview moved below CTA, full-width card with window chrome and Online badge - Metrics, How It Works, Features, bottom CTA, footer all updated to match clean dark style (white/5 borders, rounded-2xl cards) - Background updated to #0a0a0f for warmer deep-dark feel https://claude.ai/code/session_01JcAQpzFeBtwmK6ZrXKZ8mx --- app/page.tsx | 399 ++++++++++++++++++++++----------------------------- 1 file changed, 169 insertions(+), 230 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index bc3e611..7a571e9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,8 +2,7 @@ import Link from 'next/link' import { Button } from '@/components/ui/button' import { RepoFuseLogo3D } from '@/components/repofuse-logo-3d' import { NavDropdown } from '@/components/nav-dropdown' -import { Github, ArrowRight, AlertCircle, Shield, Zap, GitBranch, Rocket, Code2, Sparkles } from 'lucide-react' -import { LaunchSignupModal } from '@/components/launch-signup-modal' +import { Github, ArrowRight, AlertCircle, Zap } from 'lucide-react' const ERROR_MESSAGES: Record = { auth_required: 'You must sign in to access the dashboard.', @@ -22,7 +21,7 @@ export default async function HomePage({ searchParams }: { searchParams: Promise const errorMessage = error ? ERROR_MESSAGES[error] ?? 'An unexpected error occurred.' : null return ( -
+
{errorMessage && (
@@ -30,165 +29,132 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
)} - {/* Animated scanlines overlay */} -
- {/* Noise overlay */} -
{/* Header */} -
+
- - + + + RepoFuse - -
{/* Hero Section */}
-
- {/* Animated grid background */} -
+
+ {/* Subtle radial glow */} +
+
- {/* Glowing orbs */} -
-
-
+
+ {/* Trust badge */} +
+ + + AI-POWERED + + + The #1 Repo Intelligence Platform +
-
-
- {/* Left content — appears second on mobile, first on desktop */} -
- {/* Badge */} -
- - Now in Public Beta -
- - {/* Heading */} -
-

- Everything your - - repos - have been - - - waiting - for - -

-
- - {/* Subheading */} -

- RepoFuse scans your connected GitHub or GitLab repos and surfaces buildable project ideas, detects hidden potential, and turns scattered code into your next big launch — automatically. -

+ {/* Headline */} +

+ Your repos are hiding + buildable apps +

- {/* CTA Buttons */} -
- - - - -
+ {/* Subheading */} +

+ RepoFuse scans your GitHub and GitLab repos, surfaces project ideas, and turns scattered code into your next launch —{' '} + automatically. +

- {/* Social proof */} -
- 2,400+ developers already on the waitlist -
+ {/* Primary CTA */} + + + Scan My Repos Free + + + {/* Sub-text */} +

+ Connect in seconds. No credit card required. +

+ + {/* Social proof */} +
+
+ {['#22d3ee','#a78bfa','#fb923c','#4ade80'].map((c, i) => ( +
+ ))}
+ 2,400+ developers already scanning +
- {/* Right: Terminal window — appears first on mobile */} -
-
- {/* Terminal header */} -
-
-
-
- repofuse — repo-scanner -
+ {/* Terminal preview */} +
+ {/* Window chrome */} +
+
+
+
+ RepoFuse Dashboard +
+ + Online +
+
- {/* Terminal body */} -
-
- $ - repofuse scan --org DealPatrol -
-
-
▸ Connecting to GitHub API...
-
✓ Found 14 repositories
-
▸ Analyzing code patterns...
-
-
-
📦 repo-app-architect
-
⚡ 3 buildable ideas detected
-
→ SaaS: AI Code Review Tool
-
→ Tool: Repo Health Dashboard
-
→ API: Webhook Automation Kit
-
-
-
▸ Generating project briefs...
-
-
- $ - -
-
+ {/* Content */} +
+
+ $ + repofuse scan --org DealPatrol --all-repos +
+
+
▸ Connecting to GitHub API...
+
✓ Found 14 repositories
+
▸ Analyzing code patterns & dependencies...
+
+
+
📦 repo-app-architect
+
⚡ 3 buildable ideas detected
+
→ SaaS: AI Code Review Tool
+
→ Tool: Repo Health Dashboard
+
→ API: Webhook Automation Kit
+
+
▸ Generating full project briefs...
+
+ $ +
@@ -196,69 +162,49 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
{/* Metrics Strip */} -
+
-
-
-

12k+

-

Repos Scanned

-
-
-

4.1k

-

Ideas Found

-
-
-

89%

-

Code Reuse

-
-
-

<30s

-

Analysis Time

-
+
+ {[ + { val: '12k+', label: 'Repos Scanned', color: 'text-cyan-400' }, + { val: '4.1k', label: 'Ideas Found', color: 'text-orange-400' }, + { val: '89%', label: 'Code Reuse', color: 'text-purple-400' }, + { val: '<30s', label: 'Analysis Time', color: 'text-cyan-400' }, + ].map((m) => ( +
+

{m.val}

+

{m.label}

+
+ ))}
{/* How It Works */} -
-
-
-
- +
+
+
+
HOW IT WORKS
-

Four steps to
- - buildable blueprints - +

+ Four steps to
+ buildable blueprints

-
- {/* Connecting line */} -
- +
{[ { num: '01', title: 'Connect', icon: '🔗', desc: 'OAuth in one click. Read-only access to your repos.' }, { num: '02', title: 'Scan', icon: '⚡', desc: 'AI analyzes structure, patterns, and dependencies.' }, { num: '03', title: 'Discover', icon: '💡', desc: 'Ideas surface instantly, ranked by viability.' }, - { num: '04', title: 'Build', icon: '🚀', desc: 'Get full briefs, stack recs, and MVP roadmaps.' } + { num: '04', title: 'Build', icon: '🚀', desc: 'Get full briefs, stack recs, and MVP roadmaps.' }, ].map((step, i) => ( -
-
- {/* Step number background */} -
-
- {step.num} -
-
-

{step.title}

-

{step.desc}

-
+
+
{step.icon}
+
{step.num}
+

{step.title}

+

{step.desc}

))}
@@ -267,78 +213,71 @@ export default async function HomePage({ searchParams }: { searchParams: Promise {/* Feature Grid */}
-
-
-
- +
+
+
FEATURES
-

Everything your repos
- - have been waiting for - +

+ Everything your repos
+ have been waiting for

-
+
{[ { icon: '⚡', title: 'AI Repo Scanner', desc: 'Deep analysis of your codebase structure and patterns in seconds.' }, { icon: '💡', title: 'Idea Surfacer', desc: 'Turns existing code into ranked, buildable project ideas.' }, { icon: '🔗', title: 'Multi-Repo Fusion', desc: 'Cross-reference patterns across all your repos simultaneously.' }, { icon: '📊', title: 'Health Dashboard', desc: 'Live metrics on code quality and technical debt.' }, { icon: '📋', title: 'Launch Briefs', desc: 'AI-generated product briefs for every detected idea.' }, - { icon: '🔒', title: 'Private by Default', desc: 'Your code never leaves your control.' } - ].map((feature, i) => ( -
-
{feature.icon}
-

{feature.title}

-

{feature.desc}

-
+ { icon: '🔒', title: 'Private by Default', desc: 'Your code never leaves your control.' }, + ].map((f, i) => ( +
+
{f.icon}
+

{f.title}

+

{f.desc}

))}
- {/* CTA Section */} -
-
-

+ {/* Bottom CTA */} +
+
+

Your next product is
- - already in your repos - + already in your repos

-

+

Join 2,400+ developers who've stopped guessing and started shipping.

- -

- no credit card required · read-only access + + + Start Scanning Now + + +

+ no credit card · read-only access · cancel anytime

{/* Footer */} -