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
395 changes: 158 additions & 237 deletions app/api/stripe/webhook/route.ts

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ export const metadata: Metadata = {
export const viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 1,
}

export default function RootLayout({
Expand Down
66 changes: 29 additions & 37 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Button } from '@/components/ui/button'
import { RepoFuseLogo3D } from '@/components/repofuse-logo-3d'
import { NavDropdown } from '@/components/nav-dropdown'
import { Github, ArrowRight, AlertCircle, Zap } from 'lucide-react'
import { ImpactStats } from '@/components/impact-stats'
import { Testimonials } from '@/components/testimonials'

const ERROR_MESSAGES: Record<string, string> = {
auth_required: 'You must sign in to access the dashboard.',
Expand Down Expand Up @@ -35,6 +37,26 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
backgroundSize: '256px'
}} />

{/* Post-launch offer */}
<div className="relative z-30 border-b border-white/10 bg-white/5 px-4 py-4 text-center">
<div className="max-w-3xl mx-auto flex flex-col sm:flex-row items-center justify-center gap-4 sm:gap-8">
<RepoFuseLogo3D className="h-12 w-12 shrink-0" />
<div className="space-y-1">
<p className="text-base sm:text-lg font-bold text-white">
Now live — start free today
</p>
<p className="text-sm text-gray-400">
<span className="text-cyan-400 font-semibold">14-day Pro trial</span>
{' · '}
free tier includes repo scans and blueprint previews
</p>
</div>
<Button size="sm" className="bg-cyan-500 hover:bg-cyan-400 text-black font-bold shrink-0" asChild>
<Link href="/pricing">View pricing</Link>
</Button>
</div>
</div>

{/* Header */}
<header className="sticky top-0 z-50 border-b border-white/5 bg-[#0a0a0f]/95 backdrop-blur-xl">
<div className="container mx-auto px-4 sm:px-6 py-3 flex items-center justify-between">
Expand Down Expand Up @@ -65,15 +87,12 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
</div>
</header>

{/* Hero Section */}
<main>
<section className="relative overflow-hidden pt-16 pb-12 sm:pt-20 sm:pb-16">
{/* Subtle radial glow */}
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[500px] bg-cyan-500/8 rounded-full blur-3xl -z-10 pointer-events-none" />
<div className="absolute bottom-0 left-1/4 w-64 h-64 bg-cyan-500/5 rounded-full blur-3xl -z-10 pointer-events-none" />

<div className="container mx-auto px-4 max-w-3xl text-center">
{/* Trust badge */}
<div className="inline-flex items-center gap-3 px-4 py-2.5 rounded-full bg-white/5 border border-white/10 text-sm text-gray-300 mb-10">
<span className="flex items-center gap-1.5 text-cyan-400 font-semibold">
<Zap className="h-3.5 w-3.5" />
Expand All @@ -83,19 +102,16 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
The #1 Repo Intelligence Platform
</div>

{/* Headline */}
<h1 className="text-5xl sm:text-6xl md:text-7xl font-black tracking-tight leading-[1.05] mb-6">
<span className="block text-white">Your repos are hiding</span>
<span className="block text-cyan-400 mt-2">buildable apps</span>
</h1>

{/* Subheading */}
<p className="text-lg sm:text-xl text-gray-400 max-w-xl mx-auto leading-relaxed mb-10">
RepoFuse scans your GitHub and GitLab repos, surfaces project ideas, and turns scattered code into your next launch —{' '}
<strong className="text-gray-200 font-semibold">automatically.</strong>
</p>

{/* Primary CTA */}
<a
href="/api/auth/github/login"
className="inline-flex items-center justify-center gap-2.5 w-full sm:w-auto sm:min-w-72 bg-cyan-500 hover:bg-cyan-400 active:bg-cyan-600 text-black font-bold text-lg px-8 py-4 rounded-2xl transition-all shadow-xl shadow-cyan-500/30 hover:shadow-cyan-400/40 hover:-translate-y-0.5"
Expand All @@ -104,24 +120,20 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
Scan My Repos Free
</a>

{/* Sub-text */}
<p className="text-sm text-gray-500 mt-4">
Connect in seconds. No credit card required.
</p>

{/* Social proof */}
<div className="flex items-center justify-center gap-2 mt-6 text-xs text-gray-500">
<div className="flex -space-x-1">
{['#22d3ee','#a78bfa','#fb923c','#4ade80'].map((c, i) => (
{['#22d3ee', '#a78bfa', '#fb923c', '#4ade80'].map((c, i) => (
<div key={i} className="w-6 h-6 rounded-full border-2 border-[#0a0a0f]" style={{ backgroundColor: c + '60' }} />
))}
</div>
<span><strong className="text-cyan-400">2,400+</strong> developers already scanning</span>
<span>Join developers turning repos into shippable products</span>
</div>

{/* Terminal preview */}
<div className="mt-14 text-left rounded-2xl overflow-hidden border border-white/10 bg-[#0d0d14] shadow-2xl shadow-black/60">
{/* Window chrome */}
<div className="flex items-center gap-2 px-5 py-3.5 border-b border-white/8 bg-white/3">
<div className="w-3 h-3 rounded-full bg-red-500/80" />
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
Expand All @@ -133,7 +145,6 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
</div>
</div>

{/* Content */}
<div className="p-6 font-mono text-sm space-y-3">
<div className="flex gap-3 text-gray-400">
<span className="text-cyan-500">$</span>
Expand Down Expand Up @@ -161,26 +172,8 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
</div>
</section>

{/* Metrics Strip */}
<section className="border-y border-white/5 py-12">
<div className="container mx-auto px-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 max-w-3xl mx-auto text-center">
{[
{ 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) => (
<div key={m.label} className="space-y-1">
<p className={`text-3xl md:text-4xl font-black tabular-nums ${m.color}`}>{m.val}</p>
<p className="text-xs text-gray-500 uppercase tracking-widest">{m.label}</p>
</div>
))}
</div>
</div>
</section>
<ImpactStats variant="marketing" />

{/* How It Works */}
<section id="how" className="py-20 border-b border-white/5">
<div className="container mx-auto px-4 max-w-5xl">
<div className="text-center mb-14">
Expand Down Expand Up @@ -211,7 +204,6 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
</div>
</section>

{/* Feature Grid */}
<section id="features" className="py-20">
<div className="container mx-auto px-4 max-w-5xl">
<div className="text-center mb-14">
Expand Down Expand Up @@ -243,15 +235,16 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
</div>
</section>

{/* Bottom CTA */}
<Testimonials variant="marketing" />

<section className="py-20 border-t border-white/5">
<div className="container mx-auto px-4 max-w-xl text-center">
<h2 className="text-4xl md:text-5xl font-black text-white mb-4">
Your next product is<br />
<span className="text-cyan-400">already in your repos</span>
</h2>
<p className="text-lg text-gray-400 mb-8">
Join 2,400+ developers who've stopped guessing and started shipping.
Connect your repos and get your first blueprint in minutes.
</p>
<a
href="/api/auth/github/login"
Expand All @@ -268,10 +261,9 @@ export default async function HomePage({ searchParams }: { searchParams: Promise
</section>
</main>

{/* Footer */}
<footer className="border-t border-white/5 py-8">
<div className="container mx-auto px-4 flex flex-col sm:flex-row items-center justify-between gap-3 text-xs text-gray-600">
<span>© 2025 RepoFuse. Built by developers, for developers.</span>
<span>© 2026 RepoFuse. Built by developers, for developers.</span>
<div className="flex items-center gap-4">
<Link href="/pricing" className="hover:text-gray-400 transition-colors">Pricing</Link>
<Link href="/dashboard" className="hover:text-gray-400 transition-colors">Dashboard</Link>
Expand Down
5 changes: 4 additions & 1 deletion app/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ export default function PricingPage() {
{/* Header */}
<header className="sticky top-0 z-50 border-b border-cyan-500/20 bg-black/95 backdrop-blur-xl">
<div className="container mx-auto px-6 py-3 flex items-center justify-between">
<Link href="/" className="flex items-center mt-5">
<Link href="/" className="flex items-center gap-2 mt-5">
<RepoFuseLogo3D className="h-10 w-10" />
<span className="text-sm font-mono font-semibold tracking-widest text-cyan-400/80 uppercase hidden sm:inline">
RepoFuse
</span>
</Link>
<nav className="flex items-center gap-4">
<Link href="/dashboard" className="text-xs font-mono tracking-widest text-cyan-400/60 hover:text-cyan-300 transition-colors uppercase">
Expand Down
149 changes: 95 additions & 54 deletions components/dashboard-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,120 @@

import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { Github, BarChart3, FolderGit2, Sparkles, CreditCard, LayoutGrid, MessageSquare } from 'lucide-react'
import { Github, ChevronDown, Lock } from 'lucide-react'
import { cn } from '@/lib/utils'
import { RepoFuseLogo3D } from '@/components/repofuse-logo-3d'
import type { AuthUser } from '@/lib/auth'
import {
PRIMARY_DASHBOARD_NAV,
SECONDARY_DASHBOARD_NAV,
type DashboardNavItem,
} from '@/lib/dashboard-nav'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Button } from '@/components/ui/button'

interface DashboardHeaderProps {
user: AuthUser | null
}

const navItems = [
{ href: '/dashboard', label: 'Overview', icon: BarChart3 },
{ 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: MessageSquare },
{ href: '/dashboard/billing', label: 'Billing', icon: CreditCard },
]
function isNavActive(pathname: string, href: string) {
if (href === '/dashboard') return pathname === '/dashboard'
if (href === '/dashboard/templates/browse') {
return pathname.startsWith('/dashboard/templates')
}
return pathname === href || pathname.startsWith(`${href}/`)
}

function NavLink({
item,
pathname,
compact,
}: {
item: DashboardNavItem
pathname: string
compact?: boolean
}) {
const active = isNavActive(pathname, item.href)
return (
<Link
href={item.href}
className={cn(
'flex items-center gap-2 rounded-lg text-xs font-mono tracking-wider uppercase transition-all duration-200',
compact ? 'px-3 py-1.5 whitespace-nowrap' : 'px-3 py-2',
active
? 'bg-cyan-950/50 text-cyan-300 border border-cyan-500/30'
: 'text-cyan-400/50 hover:text-cyan-300 hover:bg-cyan-950/30 border border-transparent hover:border-cyan-500/20',
)}
>
<item.icon className={cn('shrink-0', compact ? 'h-3 w-3' : 'h-3.5 w-3.5')} />
{item.label}
{item.isPro && (
<Lock className="h-3 w-3 text-orange-400/80 shrink-0" aria-label="Pro feature" />
)}
</Link>
)
}

export function DashboardHeader({ user }: DashboardHeaderProps) {
const pathname = usePathname()
const secondaryActive = SECONDARY_DASHBOARD_NAV.some((item) => isNavActive(pathname, item.href))

return (
<>
<header className="sticky top-0 z-50 border-b border-cyan-500/20 bg-black/95 backdrop-blur-xl">
<div className="container mx-auto px-4 sm:px-6 py-3 flex items-center justify-between">
<div className="flex items-center gap-6 lg:gap-8">
<Link href="/" className="flex items-center flex-shrink-0 mt-5">
<div className="container mx-auto px-4 sm:px-6 py-3 flex items-center justify-between gap-4">
<div className="flex items-center gap-4 lg:gap-6 min-w-0">
<Link href="/" className="flex items-center shrink-0 mt-5">
<RepoFuseLogo3D className="h-10 w-10" />
</Link>

<nav className="hidden md:flex items-center gap-1">
{navItems.map((item) => {
const isActive =
pathname === item.href ||
(item.href !== '/dashboard' && pathname.startsWith(item.href))
return (
<Link
key={item.href}
href={item.href}
<nav className="hidden xl:flex items-center gap-0.5 min-w-0">
{PRIMARY_DASHBOARD_NAV.map((item) => (
<NavLink key={item.href} item={item} pathname={pathname} />
))}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className={cn(
'flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-mono tracking-wider uppercase transition-all duration-200',
isActive
'gap-1 text-xs font-mono tracking-wider uppercase',
secondaryActive
? 'bg-cyan-950/50 text-cyan-300 border border-cyan-500/30'
: 'text-cyan-400/50 hover:text-cyan-300 hover:bg-cyan-950/30',
)}
>
<item.icon className="h-3.5 w-3.5" />
{item.label}
</Link>
)
})}
More
<ChevronDown className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56 border-cyan-500/20 bg-black/95">
<DropdownMenuLabel className="text-xs font-mono uppercase tracking-widest text-cyan-500/80">
Workspace
</DropdownMenuLabel>
<DropdownMenuSeparator className="bg-cyan-500/20" />
{SECONDARY_DASHBOARD_NAV.map((item) => (
<DropdownMenuItem key={item.href} asChild>
<Link href={item.href} className="flex items-center gap-2 cursor-pointer">
<item.icon className="h-4 w-4" />
<span className="flex-1">{item.label}</span>
{item.isPro && <Lock className="h-3 w-3 text-orange-400" />}
</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</nav>
</div>

<div className="flex items-center gap-3">
<div className="flex items-center gap-3 shrink-0">
{user ? (
<div className="flex items-center gap-3">
{user.github_avatar_url && (
Expand Down Expand Up @@ -89,8 +148,8 @@ export function DashboardHeader({ user }: DashboardHeaderProps) {
href="/api/auth/gitlab/login"
className="flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-mono text-cyan-400/60 hover:text-cyan-300 hover:bg-cyan-950/30 transition-all border border-transparent hover:border-cyan-500/20"
>
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M23.6 6.15 12 0 .4 6.15a.88.88 0 0 0-.3 1.1l1.88 5.77H0v4.27h2.58l2.4 7.38a.88.88 0 0 0 .83.56h12.38a.88.88 0 0 0 .83-.56l2.4-7.38H24v-4.27h-2.08l1.88-5.77a.88.88 0 0 0-.28-1.1z"/>
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
<path d="M23.6 6.15 12 0 .4 6.15a.88.88 0 0 0-.3 1.1l1.88 5.77H0v4.27h2.58l2.4 7.38a.88.88 0 0 0 .83.56h12.38a.88.88 0 0 0 .83-.56l2.4-7.38H24v-4.27h-2.08l1.88-5.77a.88.88 0 0 0-.28-1.1z" />
</svg>
<span className="hidden sm:inline">GitLab</span>
</a>
Expand All @@ -100,29 +159,11 @@ export function DashboardHeader({ user }: DashboardHeaderProps) {
</div>
</header>

{/* Mobile nav */}
<nav className="md:hidden border-b border-cyan-500/10 bg-black/90 backdrop-blur-sm">
<div className="container mx-auto px-4 flex items-center gap-1 overflow-x-auto py-2 no-scrollbar">
{navItems.map((item) => {
const isActive =
pathname === item.href ||
(item.href !== '/dashboard' && pathname.startsWith(item.href))
return (
<Link
key={item.href}
href={item.href}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-mono uppercase whitespace-nowrap transition-all',
isActive
? 'bg-cyan-950/50 text-cyan-300 border border-cyan-500/30'
: 'text-cyan-400/50 hover:text-cyan-300 hover:bg-cyan-950/30',
)}
>
<item.icon className="h-3 w-3" />
{item.label}
</Link>
)
})}
<nav className="xl:hidden border-b border-cyan-500/10 bg-black/90 backdrop-blur-sm">
<div className="container mx-auto px-4 flex items-center gap-1 overflow-x-auto py-2">
{[...PRIMARY_DASHBOARD_NAV, ...SECONDARY_DASHBOARD_NAV].map((item) => (
<NavLink key={item.href} item={item} pathname={pathname} compact />
))}
</div>
</nav>
</>
Expand Down
Loading
Loading