diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4621226..228b448 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,8 +1,8 @@ -import { BrowserRouter, Routes, Route, Navigate, useNavigate } from 'react-router-dom'; +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { AuthProvider, useAuth } from './contexts/AuthContext'; import { LoginPage } from './pages/LoginPage'; import { SignupPage } from './pages/SignupPage'; -import { Playground } from './pages/Playground'; +import { LandingPage } from './pages/LandingPage'; import { Dashboard } from './components/Dashboard'; function ProtectedRoute({ children }: { children: React.ReactNode }) { @@ -10,8 +10,8 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) { if (loading) { return ( -
-
Loading...
+
+
Loading...
); } @@ -23,8 +23,7 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) { return <>{children}; } -function PlaygroundWrapper() { - const navigate = useNavigate(); +function LandingWrapper() { const { user } = useAuth(); // If user is logged in, redirect to dashboard @@ -32,7 +31,7 @@ function PlaygroundWrapper() { return ; } - return navigate('/signup')} />; + return ; } function AppRoutes() { @@ -40,8 +39,8 @@ function AppRoutes() { return ( - {/* Playground is the new landing page */} - } /> + {/* New Landing Page */} + } /> - {/* Legacy route redirect */} + {/* Fallback */} } /> ); diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx new file mode 100644 index 0000000..2d3fbf7 --- /dev/null +++ b/frontend/src/components/ui/badge.tsx @@ -0,0 +1,40 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + success: + "border-transparent bg-green-500/10 text-green-500 border-green-500/20", + glow: + "border-transparent bg-blue-500/10 text-blue-400 border-blue-500/20", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx new file mode 100644 index 0000000..7d21000 --- /dev/null +++ b/frontend/src/pages/LandingPage.tsx @@ -0,0 +1,563 @@ +import { useState, useEffect, useRef } from 'react' +import { useNavigate } from 'react-router-dom' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Card } from '@/components/ui/card' +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' +import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism' +import { API_URL } from '../config/api' +import type { SearchResult } from '../types' + +// Icons +const SearchIcon = () => ( + + + +) + +const ZapIcon = () => ( + + + +) + +const GitHubIcon = () => ( + + + +) + +const SparklesIcon = () => ( + + + +) + +const ArrowRightIcon = () => ( + + + +) + +const CheckIcon = () => ( + + + +) + +// Demo repos +const DEMO_REPOS = [ + { id: 'flask', name: 'Flask', icon: '🐍', color: 'from-green-500/20 to-green-600/20 border-green-500/30' }, + { id: 'fastapi', name: 'FastAPI', icon: '⚡', color: 'from-teal-500/20 to-teal-600/20 border-teal-500/30' }, + { id: 'express', name: 'Express', icon: '🟢', color: 'from-yellow-500/20 to-yellow-600/20 border-yellow-500/30' }, +] + +const EXAMPLE_QUERIES = [ + 'authentication middleware', + 'error handling', + 'database connection', +] + +// Scroll animation hook +function useScrollAnimation() { + const ref = useRef(null) + const [isVisible, setIsVisible] = useState(false) + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true) + } + }, + { threshold: 0.1, rootMargin: '0px 0px -50px 0px' } + ) + + if (ref.current) { + observer.observe(ref.current) + } + + return () => observer.disconnect() + }, []) + + return { ref, isVisible } +} + +// Animated section wrapper +function AnimatedSection({ children, className = '' }: { children: React.ReactNode; className?: string }) { + const { ref, isVisible } = useScrollAnimation() + + return ( +
+ {children} +
+ ) +} + +export function LandingPage() { + const navigate = useNavigate() + const [query, setQuery] = useState('') + const [selectedRepo, setSelectedRepo] = useState(DEMO_REPOS[0].id) + const [results, setResults] = useState([]) + const [loading, setLoading] = useState(false) + const [searchTime, setSearchTime] = useState(null) + const [searchCount, setSearchCount] = useState(0) + const [hasSearched, setHasSearched] = useState(false) + const [availableRepos, setAvailableRepos] = useState([]) + + const FREE_LIMIT = 5 + const remaining = FREE_LIMIT - searchCount + + useEffect(() => { + fetch(`${API_URL}/api/playground/repos`) + .then(res => res.json()) + .then(data => { + const available = data.repos?.filter((r: any) => r.available).map((r: any) => r.id) || [] + setAvailableRepos(available) + }) + .catch(console.error) + }, []) + + const handleSearch = async (searchQuery?: string) => { + const q = searchQuery || query + if (!q.trim() || loading || searchCount >= FREE_LIMIT) return + + setLoading(true) + setHasSearched(true) + const startTime = Date.now() + + try { + const response = await fetch(`${API_URL}/api/playground/search`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: q, demo_repo: selectedRepo, max_results: 10 }) + }) + const data = await response.json() + if (response.ok) { + setResults(data.results || []) + setSearchTime(Date.now() - startTime) + setSearchCount(prev => prev + 1) + } + } catch (error) { + console.error('Search error:', error) + } finally { + setLoading(false) + } + } + + return ( +
+ {/* Navigation */} + + + {/* ============ HERO SECTION ============ */} +
+
+
+ + AI-powered code search +
+ +

+ grep returned + 847 results. +
+ + Find the one that matters. + +

+ +

+ Search any codebase by meaning, not keywords. +

+ + {/* Repo Selector */} +
+ {DEMO_REPOS.map(repo => { + const isAvailable = availableRepos.includes(repo.id) + const isSelected = selectedRepo === repo.id + return ( + + ) + })} +
+ + {/* Search Box */} +
+
+
+
{ e.preventDefault(); handleSearch(); }} className="flex items-center gap-3"> +
+
+ setQuery(e.target.value)} + placeholder="Search for authentication, error handling..." + className="flex-1 bg-transparent text-white placeholder:text-gray-500 focus:outline-none text-base py-3" + autoFocus + /> +
+ +
+
+
+ + {/* Trust Indicators */} +
+
~100ms
+
+ No signup required +
+ {remaining} free searches +
+ + {/* Example Queries */} + {!hasSearched && ( +
+ Try: + {EXAMPLE_QUERIES.map(q => ( + + ))} +
+ )} +
+
+ + {/* ============ RESULTS SECTION (if searched) ============ */} + {hasSearched && ( +
+
+
+
+ {results.length} results + {searchTime && <>{searchTime}ms} +
+ {remaining > 0 && remaining < FREE_LIMIT && ( +
{remaining} remaining
+ )} +
+ + {searchCount >= FREE_LIMIT && ( + +

You've used all free searches

+

Sign up to get unlimited searches and index your own repos.

+ +
+ )} + +
+ {results.map((result, idx) => ( + +
+
+
+

{result.name}

+ {result.type.replace('_', ' ')} +
+

{result.file_path.split('/').slice(-2).join('/')}

+
+
+
{(result.score * 100).toFixed(0)}%
+
match
+
+
+ + {result.code} + +
+ ))} +
+ + {results.length === 0 && !loading && ( +
+
🔍
+

No results found

+

Try a different query

+
+ )} +
+
+ )} + + {/* ============ STORY SECTIONS (only before search) ============ */} + {!hasSearched && ( + <> + {/* THE PROBLEM */} +
+ +
+
+ The Problem +

You've been here before

+

+ New codebase. 50,000 lines. You need to find where authentication happens. +

+
+ + {/* Terminal visualization */} +
+
+
+
+
+ terminal +
+
+
$ grep -r "auth" ./src
+
+
src/components/AuthButton.tsx: // auth button component
+
src/utils/auth.ts: export const authConfig = ...
+
src/pages/auth/login.tsx: function AuthLogin() ...
+
src/middleware/auth.ts: // TODO: add auth
+
src/api/auth/callback.ts: const authCallback = ...
+
... 842 more results
+
+
+ 847 results. Which one handles the actual authentication logic? +
+
+
+
+ +
+ + {/* THE SOLUTION */} +
+ +
+
+ The Solution +

Search by meaning, not keywords

+

+ Ask for "authentication logic" and get the function that actually handles it. +

+
+ + {/* CodeIntel visualization */} +
+
+
+
+ CI +
+ CodeIntel +
+ 1 result • 89ms +
+
+
+
+
+ authenticate_user + function +
+ src/auth/handlers.py +
+
+
94%
+
match
+
+
+
{`def authenticate_user(credentials: dict) -> User:
+    """Main authentication logic - validates credentials
+    and returns authenticated user or raises AuthError."""
+    user = db.get_user(credentials['email'])
+    if not verify_password(credentials['password'], user.hash):
+        raise AuthError("Invalid credentials")
+    return create_session(user)`}
+
+
+
+
+
+ + {/* HOW IT WORKS */} +
+ +
+
+ How It Works +

Three steps to code clarity

+
+ +
+
+
+ 1 +
+

Index your repo

+

Connect your GitHub repo. We analyze and embed every function, class, and module.

+
+ +
+
+ 2 +
+

Search by meaning

+

Ask natural questions. "Where is payment handled?" "Show me error boundaries."

+
+ +
+
+ 3 +
+

Get precise results

+

Not 847 matches. The exact functions you need, ranked by relevance.

+
+
+
+
+
+ + {/* FEATURES */} +
+ +
+
+

Built for developers

+
+ +
+
+
+
+ 🔌 +
+
+

MCP Integration

+

Works with Claude, Cursor, and any MCP-compatible AI. Search code directly from your assistant.

+
+
+
+ +
+
+
+ +
+
+

Lightning Fast

+

Sub-100ms responses with Redis caching. Semantic search shouldn't slow you down.

+
+
+
+ +
+
+
+ 📊 +
+
+

Code Intelligence

+

Understand dependencies, analyze coding patterns, and see impact before you change.

+
+
+
+ +
+
+
+ 🔓 +
+
+

Open Source

+

Self-host for private repos. Inspect the code. Contribute improvements. No vendor lock-in.

+
+
+
+
+
+
+
+ + {/* FINAL CTA */} +
+ +
+

Ready to understand your codebase?

+

Start searching for free. No credit card required.

+
+ + + + Star on GitHub + +
+
+
+
+ + )} + + {/* FOOTER */} + +
+ ) +}