First Commit

This commit is contained in:
tejas.prakash 2025-08-14 12:20:48 +05:30
parent 3ad762e261
commit 7b10378ee4
44 changed files with 5542 additions and 144 deletions

21
components.json Normal file
View File

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

1072
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,19 +9,34 @@
"lint": "next lint"
},
"dependencies": {
"@next/font": "^14.2.15",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.539.0",
"next": "15.4.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"next": "15.4.6"
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.4.6",
"@eslint/eslintrc": "^3"
"tailwindcss": "^4",
"tw-animate-css": "^1.3.6",
"typescript": "^5"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
public/seo-blog-preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

View File

@ -0,0 +1,5 @@
import ArchitectureGenerator from "@/components/architecture/architecture-generator"
export default function ArchitecturePage() {
return <ArchitectureGenerator />
}

5
src/app/auth/page.tsx Normal file
View File

@ -0,0 +1,5 @@
import { AuthPage } from "@/components/auth/auth-page"
export default function AuthPageRoute() {
return <AuthPage />
}

View File

@ -0,0 +1,5 @@
import BusinessContextGenerator from "@/components/business-context/business-context-generator"
export default function BusinessContextPage() {
return <BusinessContextGenerator />
}

View File

@ -0,0 +1,5 @@
import { FeaturesPage } from "@/components/features/features-page"
export default function FeaturesPageRoute() {
return <FeaturesPage />
}

View File

@ -1,26 +1,122 @@
@import "tailwindcss";
@import "tw-animate-css";
:root {
--background: #ffffff;
--foreground: #171717;
}
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

View File

@ -1,34 +1,44 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import type React from "react"
import type { Metadata } from "next"
import { Poppins } from "next/font/google"
import { AuthProvider } from "@/contexts/auth-context"
import { AppLayout } from "@/components/layout/app-layout"
import "./globals.css"
const geistSans = Geist({
variable: "--font-geist-sans",
const poppins = Poppins({
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
weight: ["300", "400", "500", "600", "700"],
variable: "--font-poppins",
})
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
title: "Codenuk - AI-Powered Project Builder",
description: "Build scalable applications with AI-generated architecture and code",
generator: "v0.dev",
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
children: React.ReactNode
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<head>
<style>{`
html {
font-family: ${poppins.style.fontFamily};
--font-sans: ${poppins.variable};
}
`}</style>
</head>
<body className="font-sans antialiased">
<AuthProvider>
<AppLayout>
<main>{children}</main>
</AppLayout>
</AuthProvider>
</body>
</html>
);
)
}

View File

@ -1,103 +1,5 @@
import Image from "next/image";
import { redirect } from "next/navigation"
export default function Home() {
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
);
export default function HomePage() {
redirect("/project-builder")
}

View File

@ -0,0 +1,10 @@
import { Suspense } from "react"
import { MainDashboard } from "@/components/main-dashboard"
export default function ProjectBuilderPage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MainDashboard />
</Suspense>
)
}

View File

@ -0,0 +1,5 @@
import { TemplatesPage } from "@/components/templates/template-page"
export default function TemplatesPageRoute() {
return <TemplatesPage />
}

View File

@ -0,0 +1,191 @@
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { Code, Database, Server, Shield, Zap, Layers, GitBranch } from "lucide-react"
export default function ArchitectureGenerator() {
const [selectedArchitecture, setSelectedArchitecture] = useState<string>("")
const [projectName, setProjectName] = useState("")
const architecturePatterns = [
{
id: "monolithic",
name: "Monolithic Architecture",
description: "Single application with all components tightly coupled",
icon: Layers,
complexity: "Low",
bestFor: "Small to medium projects",
pros: ["Simple to develop", "Easy to deploy", "Lower initial cost"],
cons: ["Harder to scale", "Technology lock-in", "Difficult to maintain"],
},
{
id: "microservices",
name: "Microservices Architecture",
description: "Loosely coupled services that can be developed and deployed independently",
icon: GitBranch,
complexity: "High",
bestFor: "Large, complex applications",
pros: ["Independent scaling", "Technology diversity", "Easier maintenance"],
cons: ["Distributed complexity", "Network overhead", "Data consistency challenges"],
},
{
id: "serverless",
name: "Serverless Architecture",
description: "Event-driven, auto-scaling functions without server management",
icon: Zap,
complexity: "Medium",
bestFor: "Event-driven applications",
pros: ["Auto-scaling", "Pay-per-use", "No server management"],
cons: ["Cold start latency", "Vendor lock-in", "Limited execution time"],
},
{
id: "layered",
name: "Layered Architecture",
description: "Separation of concerns with distinct layers for different responsibilities",
icon: Database,
complexity: "Medium",
bestFor: "Business applications",
pros: ["Clear separation", "Easy to test", "Maintainable"],
cons: ["Performance overhead", "Tight coupling between layers"],
},
]
const generateArchitecture = () => {
if (!selectedArchitecture || !projectName) return
// Here you would typically call an API to generate the architecture
console.log(`Generating ${selectedArchitecture} architecture for ${projectName}`)
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{/* Header */}
<div className="text-center space-y-4 mb-12">
<h1 className="text-4xl font-bold text-gray-900">Architecture Generator</h1>
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
Generate optimal architecture patterns for your project based on requirements and scale
</p>
</div>
{/* Project Name Input */}
<div className="max-w-md mx-auto mb-8">
<label htmlFor="projectName" className="block text-sm font-medium text-gray-700 mb-2">
Project Name
</label>
<Input
id="projectName"
placeholder="Enter your project name"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
className="text-center"
/>
</div>
{/* Architecture Patterns */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
{architecturePatterns.map((pattern) => {
const Icon = pattern.icon
return (
<Card
key={pattern.id}
className={`cursor-pointer transition-all duration-300 hover:shadow-lg ${
selectedArchitecture === pattern.id
? "ring-2 ring-blue-500 bg-blue-50"
: "hover:border-blue-300"
}`}
onClick={() => setSelectedArchitecture(pattern.id)}
>
<CardHeader className="text-center pb-4">
<div className="mx-auto w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-3">
<Icon className="h-6 w-6 text-blue-600" />
</div>
<CardTitle className="text-lg">{pattern.name}</CardTitle>
<Badge variant="outline" className="w-fit mx-auto">
{pattern.complexity} Complexity
</Badge>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-sm text-gray-600 text-center">{pattern.description}</p>
<div className="text-center">
<p className="text-xs font-medium text-gray-700">Best for:</p>
<p className="text-xs text-gray-600">{pattern.bestFor}</p>
</div>
</CardContent>
</Card>
)
})}
</div>
{/* Generate Button */}
<div className="text-center">
<Button
onClick={generateArchitecture}
disabled={!selectedArchitecture || !projectName}
className="bg-blue-600 hover:bg-blue-700 px-8 py-3 text-lg"
>
<Code className="mr-2 h-5 w-5" />
Generate Architecture
</Button>
</div>
{/* Selected Architecture Details */}
{selectedArchitecture && (
<div className="mt-12 max-w-4xl mx-auto">
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Layers className="mr-2 h-5 w-5" />
Architecture Details
</CardTitle>
</CardHeader>
<CardContent>
{(() => {
const pattern = architecturePatterns.find(p => p.id === selectedArchitecture)
if (!pattern) return null
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h4 className="font-semibold text-green-700 mb-3 flex items-center">
<Shield className="mr-2 h-4 w-4" />
Advantages
</h4>
<ul className="space-y-2">
{pattern.pros.map((pro, index) => (
<li key={index} className="flex items-center text-sm text-gray-700">
<div className="w-2 h-2 bg-green-500 rounded-full mr-3"></div>
{pro}
</li>
))}
</ul>
</div>
<div>
<h4 className="font-semibold text-red-700 mb-3 flex items-center">
<Server className="mr-2 h-4 w-4" />
Considerations
</h4>
<ul className="space-y-2">
{pattern.cons.map((con, index) => (
<li key={index} className="flex items-center text-sm text-gray-700">
<div className="w-2 h-2 bg-red-500 rounded-full mr-3"></div>
{con}
</li>
))}
</ul>
</div>
</div>
)
})()}
</CardContent>
</Card>
</div>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,25 @@
"use client"
import { useState } from "react"
import { SignInForm } from "./signin-form"
import { SignUpForm } from "./signup-form"
export function AuthPage() {
const [isSignIn, setIsSignIn] = useState(true)
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-900 mb-2">Codenuk</h1>
<p className="text-gray-600">AI-Powered Project Builder</p>
</div>
{isSignIn ? (
<SignInForm onToggleMode={() => setIsSignIn(false)} />
) : (
<SignUpForm onToggleMode={() => setIsSignIn(true)} />
)}
</div>
</div>
)
}

View File

@ -0,0 +1,117 @@
"use client"
import type React from "react"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Eye, EyeOff, Loader2 } from "lucide-react"
import { useAuth } from "@/contexts/auth-context"
interface SignInFormProps {
onToggleMode: () => void
}
export function SignInForm({ onToggleMode }: SignInFormProps) {
const [showPassword, setShowPassword] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState("")
const [formData, setFormData] = useState({
email: "",
password: "",
})
const { login } = useAuth()
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
setIsLoading(true)
try {
const success = await login(formData.email, formData.password)
if (success) {
router.push("/")
} else {
setError("Invalid email or password")
}
} catch (err) {
setError("An error occurred. Please try again.")
} finally {
setIsLoading(false)
}
}
return (
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold">Sign In</CardTitle>
<CardDescription>Enter your credentials to access your account</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="Enter your email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
required
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
</div>
{/* Error Message */}
{error && (
<div className="text-red-600 text-sm text-center bg-red-50 p-2 rounded-md">
{error}
</div>
)}
<Button type="submit" className="w-full cursor-pointer" disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Signing In...
</>
) : (
"Sign In"
)}
</Button>
<div className="text-center">
<Button type="button" variant="link" onClick={onToggleMode} className="text-sm cursor-pointer">
Don&apos;t have an account? Sign up
</Button>
</div>
</form>
</CardContent>
</Card>
)
}

View File

@ -0,0 +1,159 @@
"use client"
import type React from "react"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Eye, EyeOff, Loader2 } from "lucide-react"
import { useAuth } from "@/contexts/auth-context"
interface SignUpFormProps {
onToggleMode: () => void
}
export function SignUpForm({ onToggleMode }: SignUpFormProps) {
const [showPassword, setShowPassword] = useState(false)
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState("")
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
confirmPassword: "",
})
const { signup } = useAuth()
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
if (formData.password !== formData.confirmPassword) {
setError("Passwords don't match")
return
}
setIsLoading(true)
try {
const success = await signup(formData.name, formData.email, formData.password)
if (success) {
router.push("/")
} else {
setError("Failed to create account. Please try again.")
}
} catch (err) {
setError("An error occurred. Please try again.")
} finally {
setIsLoading(false)
}
}
return (
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold">Sign Up</CardTitle>
<CardDescription>Create your account to get started</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Full Name</Label>
<Input
id="name"
type="text"
placeholder="Enter your full name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="Enter your email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Create a password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
required
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<div className="relative">
<Input
id="confirmPassword"
type={showConfirmPassword ? "text" : "password"}
placeholder="Confirm your password"
value={formData.confirmPassword}
onChange={(e) => setFormData({ ...formData, confirmPassword: e.target.value })}
required
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
>
{showConfirmPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
</div>
{/* Error Message */}
{error && (
<div className="text-red-600 text-sm text-center bg-red-50 p-2 rounded-md">
{error}
</div>
)}
<Button type="submit" className="w-full cursor-pointer" disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Creating Account...
</>
) : (
"Sign Up"
)}
</Button>
<div className="text-center">
<Button type="button" variant="link" onClick={onToggleMode} className="text-sm cursor-pointer">
Already have an account? Sign in
</Button>
</div>
</form>
</CardContent>
</Card>
)
}

View File

@ -0,0 +1,546 @@
"use client"
import React from "react"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Progress } from "@/components/ui/progress"
import { Badge } from "@/components/ui/badge"
import { Users, Server, DollarSign, Shield } from "lucide-react"
interface BusinessContext {
userScale: {
expectedUsers: string
growthRate: string
peakTraffic: string
globalReach: boolean
}
technical: {
performance: string
availability: string
security: string[]
integrations: string[]
}
business: {
model: string
revenue: string
budget: string
timeline: string
}
operational: {
team: string
maintenance: string
monitoring: string[]
compliance: string[]
}
}
export default function BusinessContextGenerator() {
const [currentSection, setCurrentSection] = useState(0)
const [context, setContext] = useState<BusinessContext>({
userScale: {
expectedUsers: "",
growthRate: "",
peakTraffic: "",
globalReach: false,
},
technical: {
performance: "",
availability: "",
security: [],
integrations: [],
},
business: {
model: "",
revenue: "",
budget: "",
timeline: "",
},
operational: {
team: "",
maintenance: "",
monitoring: [],
compliance: [],
},
})
const sections = [
{ title: "User Scale & Growth", icon: Users, color: "bg-blue-500" },
{ title: "Technical Requirements", icon: Server, color: "bg-green-500" },
{ title: "Business Model", icon: DollarSign, color: "bg-purple-500" },
{ title: "Operational Context", icon: Shield, color: "bg-orange-500" },
]
const progress = ((currentSection + 1) / sections.length) * 100
const updateContext = (section: keyof BusinessContext, field: string, value: any) => {
setContext((prev) => ({
...prev,
[section]: {
...prev[section],
[field]: value,
},
}))
}
const toggleArrayValue = (section: keyof BusinessContext, field: string, value: string) => {
setContext((prev) => {
const currentArray = (prev[section] as any)[field] || []
const newArray = currentArray.includes(value)
? currentArray.filter((item: string) => item !== value)
: [...currentArray, value]
return {
...prev,
[section]: {
...prev[section],
[field]: newArray,
},
}
})
}
const renderUserScaleSection = () => (
<div className="space-y-6">
<div>
<Label htmlFor="expectedUsers">Expected Number of Users</Label>
<Select
value={context.userScale.expectedUsers}
onValueChange={(value) => updateContext("userScale", "expectedUsers", value)}
>
<SelectTrigger>
<SelectValue placeholder="Select user range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1-100">1-100 users</SelectItem>
<SelectItem value="100-1000">100-1,000 users</SelectItem>
<SelectItem value="1000-10000">1,000-10,000 users</SelectItem>
<SelectItem value="10000-100000">10,000-100,000 users</SelectItem>
<SelectItem value="100000+">100,000+ users</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="growthRate">Expected Growth Rate</Label>
<Select
value={context.userScale.growthRate}
onValueChange={(value) => updateContext("userScale", "growthRate", value)}
>
<SelectTrigger>
<SelectValue placeholder="Select growth rate" />
</SelectTrigger>
<SelectContent>
<SelectItem value="slow">Slow (10-25% annually)</SelectItem>
<SelectItem value="moderate">Moderate (25-50% annually)</SelectItem>
<SelectItem value="fast">Fast (50-100% annually)</SelectItem>
<SelectItem value="explosive">Explosive (100%+ annually)</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="peakTraffic">Peak Traffic Expectations</Label>
<Select
value={context.userScale.peakTraffic}
onValueChange={(value) => updateContext("userScale", "peakTraffic", value)}
>
<SelectTrigger>
<SelectValue placeholder="Select peak traffic" />
</SelectTrigger>
<SelectContent>
<SelectItem value="2x">2x normal traffic</SelectItem>
<SelectItem value="5x">5x normal traffic</SelectItem>
<SelectItem value="10x">10x normal traffic</SelectItem>
<SelectItem value="50x">50x+ normal traffic</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="globalReach"
checked={context.userScale.globalReach}
onCheckedChange={(checked) => updateContext("userScale", "globalReach", checked)}
/>
<Label htmlFor="globalReach">Global user base (multiple regions)</Label>
</div>
</div>
)
const renderTechnicalSection = () => (
<div className="space-y-6">
<div>
<Label>Performance Requirements</Label>
<RadioGroup
value={context.technical.performance}
onValueChange={(value) => updateContext("technical", "performance", value)}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="basic" id="perf-basic" />
<Label htmlFor="perf-basic">Basic (3-5s load time)</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="good" id="perf-good" />
<Label htmlFor="perf-good">Good (1-3s load time)</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="excellent" id="perf-excellent" />
<Label htmlFor="perf-excellent">Excellent (&lt;1s load time)</Label>
</div>
</RadioGroup>
</div>
<div>
<Label>Availability Requirements</Label>
<RadioGroup
value={context.technical.availability}
onValueChange={(value) => updateContext("technical", "availability", value)}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="99" id="avail-99" />
<Label htmlFor="avail-99">99% uptime (8.76 hours downtime/year)</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="99.9" id="avail-999" />
<Label htmlFor="avail-999">99.9% uptime (8.76 hours downtime/year)</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="99.99" id="avail-9999" />
<Label htmlFor="avail-9999">99.99% uptime (52.56 minutes downtime/year)</Label>
</div>
</RadioGroup>
</div>
<div>
<Label>Security Requirements</Label>
<div className="grid grid-cols-2 gap-2 mt-2">
{[
"Authentication",
"Authorization",
"Data Encryption",
"GDPR Compliance",
"SOC2 Compliance",
"PCI Compliance",
].map((security) => (
<div key={security} className="flex items-center space-x-2">
<Checkbox
id={security}
checked={context.technical.security.includes(security)}
onCheckedChange={() => toggleArrayValue("technical", "security", security)}
/>
<Label htmlFor={security}>{security}</Label>
</div>
))}
</div>
</div>
<div>
<Label>Required Integrations</Label>
<div className="grid grid-cols-2 gap-2 mt-2">
{[
"Payment Processing",
"Email Service",
"SMS Service",
"Analytics",
"CRM",
"Social Media",
"File Storage",
"CDN",
].map((integration) => (
<div key={integration} className="flex items-center space-x-2">
<Checkbox
id={integration}
checked={context.technical.integrations.includes(integration)}
onCheckedChange={() => toggleArrayValue("technical", "integrations", integration)}
/>
<Label htmlFor={integration}>{integration}</Label>
</div>
))}
</div>
</div>
</div>
)
const renderBusinessSection = () => (
<div className="space-y-6">
<div>
<Label>Business Model</Label>
<RadioGroup value={context.business.model} onValueChange={(value) => updateContext("business", "model", value)}>
<div className="flex items-center space-x-2">
<RadioGroupItem value="saas" id="model-saas" />
<Label htmlFor="model-saas">SaaS (Subscription)</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="ecommerce" id="model-ecommerce" />
<Label htmlFor="model-ecommerce">E-commerce</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="marketplace" id="model-marketplace" />
<Label htmlFor="model-marketplace">Marketplace</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="freemium" id="model-freemium" />
<Label htmlFor="model-freemium">Freemium</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="enterprise" id="model-enterprise" />
<Label htmlFor="model-enterprise">Enterprise B2B</Label>
</div>
</RadioGroup>
</div>
<div>
<Label htmlFor="revenue">Expected Revenue (Year 1)</Label>
<Select value={context.business.revenue} onValueChange={(value) => updateContext("business", "revenue", value)}>
<SelectTrigger>
<SelectValue placeholder="Select revenue range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="0-10k">$0 - $10K</SelectItem>
<SelectItem value="10k-100k">$10K - $100K</SelectItem>
<SelectItem value="100k-1m">$100K - $1M</SelectItem>
<SelectItem value="1m+">$1M+</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="budget">Development Budget</Label>
<Select value={context.business.budget} onValueChange={(value) => updateContext("business", "budget", value)}>
<SelectTrigger>
<SelectValue placeholder="Select budget range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="0-5k">$0 - $5K</SelectItem>
<SelectItem value="5k-25k">$5K - $25K</SelectItem>
<SelectItem value="25k-100k">$25K - $100K</SelectItem>
<SelectItem value="100k+">$100K+</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="timeline">Launch Timeline</Label>
<Select
value={context.business.timeline}
onValueChange={(value) => updateContext("business", "timeline", value)}
>
<SelectTrigger>
<SelectValue placeholder="Select timeline" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1-3months">1-3 months</SelectItem>
<SelectItem value="3-6months">3-6 months</SelectItem>
<SelectItem value="6-12months">6-12 months</SelectItem>
<SelectItem value="12months+">12+ months</SelectItem>
</SelectContent>
</Select>
</div>
</div>
)
const renderOperationalSection = () => (
<div className="space-y-6">
<div>
<Label>Team Size</Label>
<RadioGroup
value={context.operational.team}
onValueChange={(value) => updateContext("operational", "team", value)}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="solo" id="team-solo" />
<Label htmlFor="team-solo">Solo developer</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="small" id="team-small" />
<Label htmlFor="team-small">Small team (2-5 people)</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="medium" id="team-medium" />
<Label htmlFor="team-medium">Medium team (5-15 people)</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="large" id="team-large" />
<Label htmlFor="team-large">Large team (15+ people)</Label>
</div>
</RadioGroup>
</div>
<div>
<Label>Maintenance Approach</Label>
<RadioGroup
value={context.operational.maintenance}
onValueChange={(value) => updateContext("operational", "maintenance", value)}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="minimal" id="maint-minimal" />
<Label htmlFor="maint-minimal">Minimal maintenance</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="regular" id="maint-regular" />
<Label htmlFor="maint-regular">Regular updates</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="continuous" id="maint-continuous" />
<Label htmlFor="maint-continuous">Continuous deployment</Label>
</div>
</RadioGroup>
</div>
<div>
<Label>Monitoring Requirements</Label>
<div className="grid grid-cols-2 gap-2 mt-2">
{[
"Error Tracking",
"Performance Monitoring",
"User Analytics",
"Security Monitoring",
"Uptime Monitoring",
"Log Management",
].map((monitoring) => (
<div key={monitoring} className="flex items-center space-x-2">
<Checkbox
id={monitoring}
checked={context.operational.monitoring.includes(monitoring)}
onCheckedChange={() => toggleArrayValue("operational", "monitoring", monitoring)}
/>
<Label htmlFor={monitoring}>{monitoring}</Label>
</div>
))}
</div>
</div>
<div>
<Label>Compliance Requirements</Label>
<div className="grid grid-cols-2 gap-2 mt-2">
{["GDPR", "CCPA", "HIPAA", "SOX", "PCI DSS", "ISO 27001"].map((compliance) => (
<div key={compliance} className="flex items-center space-x-2">
<Checkbox
id={compliance}
checked={context.operational.compliance.includes(compliance)}
onCheckedChange={() => toggleArrayValue("operational", "compliance", compliance)}
/>
<Label htmlFor={compliance}>{compliance}</Label>
</div>
))}
</div>
</div>
</div>
)
const renderSection = () => {
switch (currentSection) {
case 0:
return renderUserScaleSection()
case 1:
return renderTechnicalSection()
case 2:
return renderBusinessSection()
case 3:
return renderOperationalSection()
default:
return null
}
}
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">Business Context Generator</h1>
<p className="text-gray-600">
Help us understand your business requirements to generate the perfect architecture
</p>
</div>
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-700">Progress</span>
<span className="text-sm font-medium text-gray-700">
{currentSection + 1} of {sections.length}
</span>
</div>
<Progress value={progress} className="h-2" />
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 mb-8">
{sections.map((section, index) => {
const Icon = section.icon
return (
<Card
key={index}
className={`cursor-pointer transition-all ${
index === currentSection
? "ring-2 ring-blue-500 shadow-lg"
: index < currentSection
? "bg-green-50 border-green-200"
: "hover:shadow-md"
}`}
onClick={() => setCurrentSection(index)}
>
<CardContent className="p-4 text-center">
<div
className={`w-12 h-12 rounded-full ${section.color} flex items-center justify-center mx-auto mb-2`}
>
<Icon className="w-6 h-6 text-white" />
</div>
<h3 className="font-semibold text-sm">{section.title}</h3>
{index < currentSection && (
<Badge variant="secondary" className="mt-2">
Completed
</Badge>
)}
{index === currentSection && <Badge className="mt-2">Current</Badge>}
</CardContent>
</Card>
)
})}
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
{React.createElement(sections[currentSection].icon, { className: "w-5 h-5" })}
{sections[currentSection].title}
</CardTitle>
<CardDescription>
{currentSection === 0 && "Define your expected user base and growth patterns"}
{currentSection === 1 && "Specify technical performance and security requirements"}
{currentSection === 2 && "Outline your business model and financial expectations"}
{currentSection === 3 && "Configure operational and compliance requirements"}
</CardDescription>
</CardHeader>
<CardContent>{renderSection()}</CardContent>
</Card>
<div className="flex justify-between mt-8">
<Button
variant="outline"
onClick={() => setCurrentSection(Math.max(0, currentSection - 1))}
disabled={currentSection === 0}
>
Previous
</Button>
<Button
onClick={() => {
if (currentSection < sections.length - 1) {
setCurrentSection(currentSection + 1)
} else {
// Generate architecture
console.log("Business Context:", context)
}
}}
>
{currentSection === sections.length - 1 ? "Generate Architecture" : "Next"}
</Button>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,684 @@
"use client"
import type React from "react"
import { useState, useRef } from "react"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import {
Search,
Plus,
Trash2,
GripVertical,
Database,
Shield,
CreditCard,
BarChart3,
Bell,
Mail,
Globe,
Smartphone,
Zap,
Settings,
FileText,
ImageIcon,
Video,
MessageSquare,
Star,
Code,
Palette,
Layers,
Save,
Download,
} from "lucide-react"
import { ArrowRight } from "lucide-react" // Added import for ArrowRight
interface Feature {
id: string
name: string
description: string
category: string
icon: any
complexity: number
timeImpact: string
dependencies: string[]
conflicts: string[]
techStack: string[]
businessQuestions: string[]
isCore?: boolean
isPopular?: boolean
}
interface SelectedFeature extends Feature {
order: number
customConfig?: any
}
export function FeaturesPage() {
const [activeTab, setActiveTab] = useState("browse")
const [selectedCategory, setSelectedCategory] = useState("all")
const [searchQuery, setSearchQuery] = useState("")
const [selectedFeatures, setSelectedFeatures] = useState<SelectedFeature[]>([])
const [draggedFeature, setDraggedFeature] = useState<Feature | null>(null)
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null)
const dropZoneRef = useRef<HTMLDivElement>(null)
const features: Feature[] = [
// Core Features
{
id: "user-auth",
name: "User Authentication",
description: "Secure user registration, login, and session management",
category: "core",
icon: Shield,
complexity: 3,
timeImpact: "2-3 days",
dependencies: [],
conflicts: [],
techStack: ["NextAuth.js", "JWT", "OAuth"],
businessQuestions: [
"How many users do you expect in the first year?",
"Do you need social login (Google, Facebook)?",
"Will you have different user roles?",
],
isCore: true,
isPopular: true,
},
{
id: "database",
name: "Database Integration",
description: "Data storage and management system",
category: "core",
icon: Database,
complexity: 4,
timeImpact: "3-5 days",
dependencies: [],
conflicts: [],
techStack: ["PostgreSQL", "Prisma", "Redis"],
businessQuestions: [
"What's your expected data volume?",
"Do you need real-time data updates?",
"What's your data backup strategy?",
],
isCore: true,
isPopular: true,
},
{
id: "api-management",
name: "API Management",
description: "RESTful API with rate limiting and documentation",
category: "core",
icon: Code,
complexity: 3,
timeImpact: "2-4 days",
dependencies: ["database"],
conflicts: [],
techStack: ["Next.js API Routes", "Swagger", "Rate Limiting"],
businessQuestions: [
"Will you have external API integrations?",
"Do you need API versioning?",
"What's your expected API call volume?",
],
isCore: true,
},
// Business Features
{
id: "payment-processing",
name: "Payment Processing",
description: "Secure payment handling with multiple providers",
category: "business",
icon: CreditCard,
complexity: 5,
timeImpact: "1-2 weeks",
dependencies: ["user-auth", "database"],
conflicts: [],
techStack: ["Stripe", "PayPal", "Webhook Handling"],
businessQuestions: [
"What payment methods do you need?",
"What's your expected transaction volume?",
"Do you need subscription billing?",
],
isPopular: true,
},
{
id: "analytics",
name: "Analytics & Tracking",
description: "User behavior tracking and business metrics",
category: "business",
icon: BarChart3,
complexity: 3,
timeImpact: "2-3 days",
dependencies: [],
conflicts: [],
techStack: ["Google Analytics", "Mixpanel", "Custom Events"],
businessQuestions: [
"What metrics are most important to track?",
"Do you need real-time analytics?",
"What's your privacy policy regarding data?",
],
isPopular: true,
},
{
id: "notifications",
name: "Notification System",
description: "Email, SMS, and push notifications",
category: "business",
icon: Bell,
complexity: 4,
timeImpact: "3-5 days",
dependencies: ["user-auth"],
conflicts: [],
techStack: ["SendGrid", "Twilio", "Push API"],
businessQuestions: [
"What types of notifications do you need?",
"How frequently will you send notifications?",
"Do you need notification preferences?",
],
},
// UI/UX Features
{
id: "responsive-design",
name: "Responsive Design",
description: "Mobile-first responsive layout",
category: "ui",
icon: Smartphone,
complexity: 2,
timeImpact: "1-2 days",
dependencies: [],
conflicts: [],
techStack: ["Tailwind CSS", "CSS Grid", "Flexbox"],
businessQuestions: ["What devices will your users primarily use?", "Do you need a mobile app later?"],
isCore: true,
},
{
id: "dark-mode",
name: "Dark Mode",
description: "Toggle between light and dark themes",
category: "ui",
icon: Palette,
complexity: 2,
timeImpact: "1 day",
dependencies: ["responsive-design"],
conflicts: [],
techStack: ["CSS Variables", "Theme Provider"],
businessQuestions: ["Is this important for your user base?"],
},
{
id: "animations",
name: "Animations & Transitions",
description: "Smooth animations and micro-interactions",
category: "ui",
icon: Zap,
complexity: 3,
timeImpact: "2-3 days",
dependencies: ["responsive-design"],
conflicts: [],
techStack: ["Framer Motion", "CSS Animations"],
businessQuestions: ["What's your performance priority?"],
},
// Content Features
{
id: "content-management",
name: "Content Management",
description: "CMS for managing dynamic content",
category: "content",
icon: FileText,
complexity: 4,
timeImpact: "1 week",
dependencies: ["database"],
conflicts: [],
techStack: ["Sanity", "Strapi", "MDX"],
businessQuestions: ["Who will manage content?", "How often will content change?"],
},
{
id: "media-upload",
name: "Media Upload",
description: "File and image upload with optimization",
category: "content",
icon: ImageIcon, // Updated from Image to ImageIcon
complexity: 3,
timeImpact: "2-3 days",
dependencies: ["database"],
conflicts: [],
techStack: ["Cloudinary", "AWS S3", "Image Optimization"],
businessQuestions: ["What file types do you need?", "What's your storage budget?"],
},
{
id: "video-streaming",
name: "Video Streaming",
description: "Video upload and streaming capabilities",
category: "content",
icon: Video,
complexity: 5,
timeImpact: "1-2 weeks",
dependencies: ["media-upload"],
conflicts: [],
techStack: ["Vimeo API", "YouTube API", "Video.js"],
businessQuestions: ["What video quality do you need?", "Expected video volume?"],
},
// Communication Features
{
id: "chat-system",
name: "Real-time Chat",
description: "Live messaging and communication",
category: "communication",
icon: MessageSquare,
complexity: 4,
timeImpact: "1 week",
dependencies: ["user-auth", "database"],
conflicts: [],
techStack: ["WebSockets", "Socket.io", "Real-time DB"],
businessQuestions: ["How many concurrent users?", "Do you need group chats?"],
},
{
id: "email-integration",
name: "Email Integration",
description: "Email sending and template management",
category: "communication",
icon: Mail,
complexity: 3,
timeImpact: "2-3 days",
dependencies: [],
conflicts: [],
techStack: ["SendGrid", "Mailgun", "Email Templates"],
businessQuestions: ["What types of emails will you send?", "Expected email volume?"],
},
// Advanced Features
{
id: "ai-integration",
name: "AI Integration",
description: "Machine learning and AI-powered features",
category: "advanced",
icon: Layers,
complexity: 5,
timeImpact: "1-3 weeks",
dependencies: ["api-management"],
conflicts: [],
techStack: ["OpenAI API", "TensorFlow", "Custom Models"],
businessQuestions: ["What AI capabilities do you need?", "What's your AI budget?", "Do you have training data?"],
},
{
id: "real-time",
name: "Real-time Features",
description: "Live updates and collaboration",
category: "advanced",
icon: Zap,
complexity: 4,
timeImpact: "1 week",
dependencies: ["database"],
conflicts: [],
techStack: ["WebSockets", "Socket.io", "Redis Pub/Sub"],
businessQuestions: ["How many concurrent users?", "What needs to be real-time?"],
},
]
const categories = [
{ id: "all", name: "All Features", icon: Globe, count: features.length },
{ id: "core", name: "Core", icon: Settings, count: features.filter((f) => f.category === "core").length },
{
id: "business",
name: "Business",
icon: BarChart3,
count: features.filter((f) => f.category === "business").length,
},
{ id: "ui", name: "UI/UX", icon: Palette, count: features.filter((f) => f.category === "ui").length },
{ id: "content", name: "Content", icon: FileText, count: features.filter((f) => f.category === "content").length },
{
id: "communication",
name: "Communication",
icon: MessageSquare,
count: features.filter((f) => f.category === "communication").length,
},
{
id: "advanced",
name: "Advanced",
icon: Layers,
count: features.filter((f) => f.category === "advanced").length,
},
]
const filteredFeatures = features.filter((feature) => {
const matchesCategory = selectedCategory === "all" || feature.category === selectedCategory
const matchesSearch =
feature.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
feature.description.toLowerCase().includes(searchQuery.toLowerCase())
const notAlreadySelected = !selectedFeatures.some((sf) => sf.id === feature.id)
return matchesCategory && matchesSearch && notAlreadySelected
})
const getComplexityColor = (complexity: number) => {
if (complexity <= 2) return "bg-green-100 text-green-800"
if (complexity <= 3) return "bg-yellow-100 text-yellow-800"
return "bg-red-100 text-red-800"
}
const getComplexityLabel = (complexity: number) => {
if (complexity <= 2) return "Simple"
if (complexity <= 3) return "Moderate"
return "Complex"
}
const handleDragStart = (feature: Feature) => {
setDraggedFeature(feature)
}
const handleDragOver = (e: React.DragEvent, index?: number) => {
e.preventDefault()
if (typeof index === "number") {
setDragOverIndex(index)
}
}
const handleDrop = (e: React.DragEvent, index?: number) => {
e.preventDefault()
if (!draggedFeature) return
const newFeature: SelectedFeature = {
...draggedFeature,
order: typeof index === "number" ? index : selectedFeatures.length,
}
if (typeof index === "number") {
const newFeatures = [...selectedFeatures]
newFeatures.splice(index, 0, newFeature)
// Update order for all features
newFeatures.forEach((f, i) => (f.order = i))
setSelectedFeatures(newFeatures)
} else {
setSelectedFeatures([...selectedFeatures, newFeature])
}
setDraggedFeature(null)
setDragOverIndex(null)
}
const removeFeature = (featureId: string) => {
setSelectedFeatures(selectedFeatures.filter((f) => f.id !== featureId))
}
const reorderFeatures = (fromIndex: number, toIndex: number) => {
const newFeatures = [...selectedFeatures]
const [movedFeature] = newFeatures.splice(fromIndex, 1)
newFeatures.splice(toIndex, 0, movedFeature)
// Update order for all features
newFeatures.forEach((f, i) => (f.order = i))
setSelectedFeatures(newFeatures)
}
const calculateTotalComplexity = () => {
return selectedFeatures.reduce((total, feature) => total + feature.complexity, 0)
}
const calculateEstimatedTime = () => {
const totalDays = selectedFeatures.reduce((total, feature) => {
const days = feature.timeImpact.match(/(\d+)/g)?.map(Number) || [1]
return total + Math.max(...days)
}, 0)
if (totalDays < 7) return `${totalDays} days`
if (totalDays < 30) return `${Math.ceil(totalDays / 7)} weeks`
return `${Math.ceil(totalDays / 30)} months`
}
const FeatureCard = ({ feature, isDraggable = true }: { feature: Feature; isDraggable?: boolean }) => {
const Icon = feature.icon
return (
<Card
className={`group hover:shadow-lg transition-all duration-300 border-2 hover:border-blue-200 ${
isDraggable ? "cursor-grab active:cursor-grabbing" : ""
}`}
draggable={isDraggable}
onDragStart={() => isDraggable && handleDragStart(feature)}
>
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="flex items-center space-x-3">
<div className="p-2 bg-blue-100 rounded-lg">
<Icon className="h-5 w-5 text-blue-600" />
</div>
<div className="space-y-1">
<CardTitle className="text-lg group-hover:text-blue-600 transition-colors">{feature.name}</CardTitle>
<div className="flex items-center space-x-2">
<Badge className={getComplexityColor(feature.complexity)}>
{getComplexityLabel(feature.complexity)}
</Badge>
{feature.isCore && <Badge variant="secondary">Core</Badge>}
{feature.isPopular && (
<Badge variant="outline" className="text-yellow-600 border-yellow-300">
<Star className="h-3 w-3 mr-1 fill-current" />
Popular
</Badge>
)}
</div>
</div>
</div>
{isDraggable && <GripVertical className="h-5 w-5 text-gray-400 opacity-0 group-hover:opacity-100" />}
</div>
<p className="text-gray-600 text-sm mt-2">{feature.description}</p>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center justify-between text-sm text-gray-500">
<span>Time Impact: {feature.timeImpact}</span>
<span>Complexity: {feature.complexity}/5</span>
</div>
<div>
<h4 className="font-medium text-sm text-gray-700 mb-2">Tech Stack:</h4>
<div className="flex flex-wrap gap-1">
{feature.techStack.slice(0, 3).map((tech, index) => (
<Badge key={index} variant="outline" className="text-xs">
{tech}
</Badge>
))}
{feature.techStack.length > 3 && (
<Badge variant="outline" className="text-xs">
+{feature.techStack.length - 3}
</Badge>
)}
</div>
</div>
{feature.dependencies.length > 0 && (
<div className="p-2 bg-yellow-50 border border-yellow-200 rounded text-sm">
<span className="font-medium text-yellow-800">Dependencies:</span>
<span className="text-yellow-700 ml-1">
{feature.dependencies
.map((depId) => {
const dep = features.find((f) => f.id === depId)
return dep?.name
})
.join(", ")}
</span>
</div>
)}
</CardContent>
</Card>
)
}
const SelectedFeatureCard = ({ feature, index }: { feature: SelectedFeature; index: number }) => {
const Icon = feature.icon
return (
<Card
className="group border-2 border-blue-200 bg-blue-50 hover:shadow-lg transition-all duration-300"
draggable
onDragStart={() => handleDragStart(feature)}
onDragOver={(e) => handleDragOver(e, index)}
onDrop={(e) => handleDrop(e, index)}
>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<GripVertical className="h-4 w-4 text-gray-400 cursor-grab" />
<div className="p-2 bg-blue-200 rounded-lg">
<Icon className="h-4 w-4 text-blue-700" />
</div>
<div>
<h4 className="font-medium text-blue-900">{feature.name}</h4>
<p className="text-sm text-blue-700">{feature.timeImpact}</p>
</div>
</div>
<div className="flex items-center space-x-2">
<Badge className={getComplexityColor(feature.complexity)}>{feature.complexity}</Badge>
<Button
size="sm"
variant="ghost"
onClick={() => removeFeature(feature.id)}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
)
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Features Library */}
<div className="lg:col-span-2 space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Features Library</h2>
<div className="flex items-center space-x-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="Search features..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 w-80"
/>
</div>
</div>
</div>
{/* Category Filters */}
<div className="flex flex-wrap gap-3">
{categories.map((category) => {
const Icon = category.icon
return (
<button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={`flex items-center space-x-2 px-4 py-2 rounded-lg border transition-all ${
selectedCategory === category.id
? "bg-blue-600 text-white border-blue-600"
: "bg-white text-gray-700 border-gray-200 hover:border-blue-300"
}`}
>
<Icon className="h-4 w-4" />
<span className="font-medium">{category.name}</span>
<Badge variant="secondary" className="ml-1">
{category.count}
</Badge>
</button>
)
})}
</div>
{/* Features Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{filteredFeatures.map((feature) => (
<FeatureCard key={feature.id} feature={feature} />
))}
</div>
</div>
{/* Selected Features Panel */}
<div className="lg:col-span-1">
<Card className="sticky top-8">
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Selected Features</span>
<Badge variant="secondary">{selectedFeatures.length}</Badge>
</CardTitle>
<p className="text-sm text-gray-600">Drag features here to build your project</p>
</CardHeader>
<CardContent className="space-y-4">
{/* Project Summary */}
<div className="p-4 bg-gray-50 rounded-lg space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-600">Total Complexity:</span>
<Badge
variant={
calculateTotalComplexity() > 20
? "destructive"
: calculateTotalComplexity() > 15
? "default"
: "secondary"
}
>
{calculateTotalComplexity()}/50
</Badge>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">Estimated Time:</span>
<span className="font-medium">{calculateEstimatedTime()}</span>
</div>
</div>
{/* Drop Zone */}
<div
ref={dropZoneRef}
className={`min-h-[200px] border-2 border-dashed rounded-lg p-4 transition-all ${
draggedFeature
? "border-blue-400 bg-blue-50"
: selectedFeatures.length === 0
? "border-gray-300"
: "border-transparent"
}`}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
{selectedFeatures.length === 0 ? (
<div className="text-center py-12">
<Plus className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500 text-sm">
Drag features from the library to start building your project
</p>
</div>
) : (
<div className="space-y-3">
{selectedFeatures
.sort((a, b) => a.order - b.order)
.map((feature, index) => (
<SelectedFeatureCard key={feature.id} feature={feature} index={index} />
))}
</div>
)}
</div>
{/* Actions */}
{selectedFeatures.length > 0 && (
<div className="space-y-2">
<Button className="w-full bg-blue-600 hover:bg-blue-700">
Continue to Business Context
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
<Button variant="outline" className="w-full bg-transparent" onClick={() => setSelectedFeatures([])}>
Clear All Features
</Button>
</div>
)}
</CardContent>
</Card>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,49 @@
"use client"
import { useAuth } from "@/contexts/auth-context"
import Header from "@/components/navigation/header"
import { usePathname } from "next/navigation"
interface AppLayoutProps {
children: React.ReactNode
}
export function AppLayout({ children }: AppLayoutProps) {
const { isAuthenticated, isLoading } = useAuth()
const pathname = usePathname()
// Don't show header on auth pages
const isAuthPage = pathname === "/auth"
// Show loading state while checking auth
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
</div>
)
}
// For auth pages, don't show header
if (isAuthPage) {
return <>{children}</>
}
// For authenticated users on other pages, show header
if (isAuthenticated) {
return (
<>
<Header />
{children}
</>
)
}
// For unauthenticated users on non-auth pages, redirect to auth
return (
<>
<Header />
{children}
</>
)
}

View File

@ -0,0 +1,806 @@
"use client"
import { useState } from "react"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { ArrowRight, Plus, Globe, BarChart3, Zap, Code, Search, Star, Clock, Users, Layers } from "lucide-react"
interface Template {
id: string
title: string
description: string
category: string
features: string[]
complexity: number
timeEstimate: string
techStack: string[]
popularity?: number
lastUpdated?: string
}
function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => void }) {
const [selectedCategory, setSelectedCategory] = useState("all")
const [searchQuery, setSearchQuery] = useState("")
const templates: Template[] = [
// Marketing Templates (10)
{
id: "marketing-website",
title: "Marketing Website",
description: "Professional marketing site with CMS and lead generation",
category: "marketing",
features: ["Content Management", "Contact Forms", "SEO Optimization", "Analytics Integration"],
complexity: 2,
timeEstimate: "1-2 weeks",
techStack: ["Next.js", "Sanity CMS", "Tailwind CSS", "Vercel"],
popularity: 95,
lastUpdated: "2024-01-15",
},
{
id: "landing-page",
title: "Landing Page",
description: "High-converting landing page with A/B testing capabilities",
category: "marketing",
features: ["A/B Testing", "Conversion Tracking", "Lead Capture", "Mobile Optimization"],
complexity: 2,
timeEstimate: "3-5 days",
techStack: ["Next.js", "Tailwind CSS", "Google Analytics", "Mailchimp"],
popularity: 88,
lastUpdated: "2024-01-10",
},
{
id: "blog-platform",
title: "Blog Platform",
description: "Content-rich blog with SEO optimization and social sharing",
category: "marketing",
features: ["Content Management", "SEO Tools", "Social Sharing", "Comment System"],
complexity: 3,
timeEstimate: "1-2 weeks",
techStack: ["Next.js", "MDX", "Tailwind CSS", "Disqus"],
popularity: 82,
lastUpdated: "2024-01-12",
},
{
id: "portfolio-site",
title: "Portfolio Website",
description: "Personal or agency portfolio with project showcase",
category: "marketing",
features: ["Project Gallery", "Contact Forms", "Blog", "Responsive Design"],
complexity: 2,
timeEstimate: "1-2 weeks",
techStack: ["Next.js", "MDX", "Tailwind CSS", "Framer Motion"],
popularity: 79,
lastUpdated: "2024-01-08",
},
{
id: "agency-website",
title: "Agency Website",
description: "Full-service agency site with team profiles and case studies",
category: "marketing",
features: ["Team Profiles", "Case Studies", "Service Pages", "Client Testimonials"],
complexity: 3,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "Strapi", "Tailwind CSS", "Framer Motion"],
popularity: 76,
lastUpdated: "2024-01-14",
},
{
id: "event-website",
title: "Event Website",
description: "Event promotion site with registration and ticketing",
category: "marketing",
features: ["Event Registration", "Ticketing", "Speaker Profiles", "Schedule Management"],
complexity: 4,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "Stripe", "Calendar API", "Email Integration"],
popularity: 73,
lastUpdated: "2024-01-11",
},
{
id: "restaurant-website",
title: "Restaurant Website",
description: "Restaurant site with menu, reservations, and online ordering",
category: "marketing",
features: ["Menu Display", "Online Reservations", "Order System", "Location Info"],
complexity: 3,
timeEstimate: "1-2 weeks",
techStack: ["Next.js", "Reservation API", "Payment Processing", "Google Maps"],
popularity: 70,
lastUpdated: "2024-01-09",
},
{
id: "nonprofit-website",
title: "Nonprofit Website",
description: "Nonprofit organization site with donation and volunteer management",
category: "marketing",
features: ["Donation Processing", "Volunteer Registration", "Event Management", "Impact Tracking"],
complexity: 3,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "Stripe", "Volunteer API", "Analytics"],
popularity: 67,
lastUpdated: "2024-01-13",
},
{
id: "real-estate-website",
title: "Real Estate Website",
description: "Property listing site with search and contact features",
category: "marketing",
features: ["Property Listings", "Search Filters", "Contact Forms", "Virtual Tours"],
complexity: 4,
timeEstimate: "2-4 weeks",
techStack: ["Next.js", "Property API", "Map Integration", "Image Gallery"],
popularity: 64,
lastUpdated: "2024-01-07",
},
{
id: "personal-brand",
title: "Personal Brand Site",
description: "Personal branding site for professionals and creators",
category: "marketing",
features: ["About Page", "Services", "Testimonials", "Contact Integration"],
complexity: 2,
timeEstimate: "1 week",
techStack: ["Next.js", "Tailwind CSS", "Contact Forms", "Social Links"],
popularity: 61,
lastUpdated: "2024-01-06",
},
// Software Templates (10)
{
id: "saas-platform",
title: "SaaS Platform",
description: "Complete SaaS application with user management, billing, and analytics",
category: "software",
features: ["User Authentication", "Payment Processing", "Analytics Integration", "API Management"],
complexity: 5,
timeEstimate: "4-6 weeks",
techStack: ["Next.js", "PostgreSQL", "Stripe", "NextAuth.js"],
popularity: 92,
lastUpdated: "2024-01-15",
},
{
id: "dashboard-app",
title: "Analytics Dashboard",
description: "Data visualization dashboard with real-time updates",
category: "software",
features: ["Data Visualization", "Real-time Updates", "User Authentication", "Export Features"],
complexity: 4,
timeEstimate: "3-4 weeks",
techStack: ["Next.js", "Chart.js", "WebSockets", "PostgreSQL"],
popularity: 89,
lastUpdated: "2024-01-14",
},
{
id: "mobile-app",
title: "Mobile App (PWA)",
description: "Progressive web app with mobile-first design",
category: "software",
features: ["Offline Support", "Push Notifications", "Mobile Optimization", "App Install"],
complexity: 4,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "PWA", "Service Workers", "Push API"],
popularity: 86,
lastUpdated: "2024-01-13",
},
{
id: "project-management",
title: "Project Management Tool",
description: "Team collaboration and project tracking application",
category: "software",
features: ["Task Management", "Team Collaboration", "Time Tracking", "Reporting"],
complexity: 5,
timeEstimate: "4-5 weeks",
techStack: ["Next.js", "PostgreSQL", "Real-time Updates", "File Upload"],
popularity: 83,
lastUpdated: "2024-01-12",
},
{
id: "crm-system",
title: "CRM System",
description: "Customer relationship management with sales pipeline",
category: "software",
features: ["Contact Management", "Sales Pipeline", "Email Integration", "Reporting"],
complexity: 5,
timeEstimate: "3-5 weeks",
techStack: ["Next.js", "PostgreSQL", "Email API", "Calendar Integration"],
popularity: 80,
lastUpdated: "2024-01-11",
},
{
id: "inventory-management",
title: "Inventory Management",
description: "Stock tracking and warehouse management system",
category: "software",
features: ["Stock Tracking", "Barcode Scanning", "Supplier Management", "Reports"],
complexity: 4,
timeEstimate: "3-4 weeks",
techStack: ["Next.js", "PostgreSQL", "Barcode API", "PDF Generation"],
popularity: 77,
lastUpdated: "2024-01-10",
},
{
id: "learning-platform",
title: "Learning Management System",
description: "Online education platform with courses and assessments",
category: "software",
features: ["Course Management", "Video Streaming", "Assessments", "Progress Tracking"],
complexity: 5,
timeEstimate: "4-6 weeks",
techStack: ["Next.js", "Video API", "PostgreSQL", "Payment Processing"],
popularity: 74,
lastUpdated: "2024-01-09",
},
{
id: "booking-system",
title: "Booking System",
description: "Appointment and reservation management platform",
category: "software",
features: ["Calendar Integration", "Payment Processing", "Notifications", "Customer Management"],
complexity: 4,
timeEstimate: "2-4 weeks",
techStack: ["Next.js", "Calendar API", "Stripe", "Email Integration"],
popularity: 71,
lastUpdated: "2024-01-08",
},
{
id: "chat-application",
title: "Chat Application",
description: "Real-time messaging platform with file sharing",
category: "software",
features: ["Real-time Messaging", "File Sharing", "Group Chats", "User Presence"],
complexity: 4,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "WebSockets", "File Storage", "Real-time DB"],
popularity: 68,
lastUpdated: "2024-01-07",
},
{
id: "api-platform",
title: "API Platform",
description: "RESTful API with documentation and rate limiting",
category: "software",
features: ["API Documentation", "Rate Limiting", "Authentication", "Monitoring"],
complexity: 4,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "Swagger", "Redis", "Monitoring Tools"],
popularity: 65,
lastUpdated: "2024-01-06",
},
// SEO Templates (10)
{
id: "seo-optimized-blog",
title: "SEO-Optimized Blog",
description: "Blog platform with advanced SEO features and schema markup",
category: "seo",
features: ["Schema Markup", "Meta Optimization", "Sitemap Generation", "Performance Optimization"],
complexity: 3,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "SEO Tools", "Schema.org", "Google Search Console"],
popularity: 90,
lastUpdated: "2024-01-15",
},
{
id: "local-business-site",
title: "Local Business Website",
description: "Local SEO optimized site with Google My Business integration",
category: "seo",
features: ["Local SEO", "Google My Business", "Review Management", "Location Pages"],
complexity: 3,
timeEstimate: "1-2 weeks",
techStack: ["Next.js", "Google APIs", "Review APIs", "Local Schema"],
popularity: 87,
lastUpdated: "2024-01-14",
},
{
id: "ecommerce-seo",
title: "E-commerce SEO Site",
description: "Product-focused e-commerce with advanced SEO optimization",
category: "seo",
features: ["Product Schema", "Category Optimization", "Review Rich Snippets", "Performance"],
complexity: 4,
timeEstimate: "3-4 weeks",
techStack: ["Next.js", "Product APIs", "Review Systems", "CDN"],
popularity: 84,
lastUpdated: "2024-01-13",
},
{
id: "news-website",
title: "News Website",
description: "News platform with article SEO and AMP support",
category: "seo",
features: ["Article Schema", "AMP Support", "Breaking News", "Social Sharing"],
complexity: 4,
timeEstimate: "2-4 weeks",
techStack: ["Next.js", "AMP", "News APIs", "Social APIs"],
popularity: 81,
lastUpdated: "2024-01-12",
},
{
id: "directory-website",
title: "Business Directory",
description: "Local business directory with search and listings",
category: "seo",
features: ["Business Listings", "Search Optimization", "Category Pages", "Review System"],
complexity: 4,
timeEstimate: "3-4 weeks",
techStack: ["Next.js", "Search APIs", "Location Services", "Review APIs"],
popularity: 78,
lastUpdated: "2024-01-11",
},
{
id: "recipe-website",
title: "Recipe Website",
description: "Recipe platform with rich snippets and cooking schema",
category: "seo",
features: ["Recipe Schema", "Nutrition Info", "Cooking Times", "User Ratings"],
complexity: 3,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "Recipe APIs", "Nutrition APIs", "Rating System"],
popularity: 75,
lastUpdated: "2024-01-10",
},
{
id: "job-board",
title: "Job Board",
description: "Job listing platform with structured data for search engines",
category: "seo",
features: ["Job Schema", "Search Filters", "Application Tracking", "Company Profiles"],
complexity: 4,
timeEstimate: "3-4 weeks",
techStack: ["Next.js", "Job APIs", "Application System", "Company APIs"],
popularity: 72,
lastUpdated: "2024-01-09",
},
{
id: "review-website",
title: "Review Website",
description: "Product/service review platform with rich snippets",
category: "seo",
features: ["Review Schema", "Rating System", "Comparison Tools", "User Profiles"],
complexity: 4,
timeEstimate: "2-4 weeks",
techStack: ["Next.js", "Review APIs", "Rating System", "Comparison Tools"],
popularity: 69,
lastUpdated: "2024-01-08",
},
{
id: "travel-website",
title: "Travel Website",
description: "Travel guide with location-based SEO and booking integration",
category: "seo",
features: ["Location Schema", "Travel Guides", "Booking Integration", "Photo Galleries"],
complexity: 4,
timeEstimate: "3-4 weeks",
techStack: ["Next.js", "Travel APIs", "Booking APIs", "Map Integration"],
popularity: 66,
lastUpdated: "2024-01-07",
},
{
id: "healthcare-website",
title: "Healthcare Website",
description: "Medical practice website with health-focused SEO",
category: "seo",
features: ["Medical Schema", "Appointment Booking", "Health Articles", "Doctor Profiles"],
complexity: 3,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "Medical APIs", "Booking System", "Content Management"],
popularity: 63,
lastUpdated: "2024-01-06",
},
]
const categories = [
{ id: "all", name: "All Templates", icon: Globe, count: templates.length },
{
id: "marketing",
name: "Marketing & Branding",
icon: Zap,
count: templates.filter((t) => t.category === "marketing").length,
},
{
id: "software",
name: "Software & Tools",
icon: Code,
count: templates.filter((t) => t.category === "software").length,
},
{ id: "seo", name: "SEO & Content", icon: BarChart3, count: templates.filter((t) => t.category === "seo").length },
]
const filteredTemplates = templates.filter((template) => {
const matchesCategory = selectedCategory === "all" || template.category === selectedCategory
const matchesSearch =
template.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.features.some((feature) => feature.toLowerCase().includes(searchQuery.toLowerCase()))
return matchesCategory && matchesSearch
})
const getComplexityColor = (complexity: number) => {
if (complexity <= 2) return "bg-green-100 text-green-800"
if (complexity <= 3) return "bg-yellow-100 text-yellow-800"
return "bg-red-100 text-red-800"
}
const getComplexityLabel = (complexity: number) => {
if (complexity <= 2) return "Simple"
if (complexity <= 3) return "Moderate"
return "Complex"
}
return (
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<div className="text-center space-y-3">
<h1 className="text-4xl font-bold text-gray-900">Choose Your Project Template</h1>
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
Select from our comprehensive library of professionally designed templates
</p>
</div>
{/* Search and Filter */}
<div className="space-y-4">
<div className="max-w-2xl mx-auto relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<Input
placeholder="Search templates, features, or technologies..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 h-12 text-lg border-2 border-gray-200 hover:border-blue-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 rounded-xl shadow-sm transition-all duration-300"
/>
</div>
{/* Category Filters */}
<div className="flex flex-wrap justify-center gap-3">
{categories.map((category) => {
const Icon = category.icon
return (
<button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={`flex items-center space-x-3 px-6 py-4 rounded-xl border-2 transition-all duration-300 hover:scale-105 ${
selectedCategory === category.id
? "bg-gradient-to-r from-blue-600 to-blue-700 text-white border-blue-600 shadow-lg"
: "bg-white text-gray-700 border-gray-200 hover:border-blue-300 hover:shadow-md hover:bg-blue-50"
}`}
>
<div className={`p-2 rounded-lg ${
selectedCategory === category.id
? "bg-white/20"
: "bg-blue-100 text-blue-600"
}`}>
<Icon className="h-5 w-5" />
</div>
<div className="text-left">
<div className="font-semibold">{category.name}</div>
<div className={`text-sm ${selectedCategory === category.id ? "opacity-90" : "opacity-75"}`}>
{category.count} templates
</div>
</div>
</button>
)
})}
</div>
</div>
{/* Templates Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredTemplates.map((template) => (
<Card
key={template.id}
className="group cursor-pointer transition-all duration-300 hover:scale-[1.02] bg-white border border-gray-300 hover:border-blue-400 rounded-xl shadow-md hover:shadow-xl overflow-hidden"
>
{/* Card Header with gradient background */}
<div className="bg-white px-4 py-4 border-b border-gray-100">
<div className="flex items-start justify-between mb-2">
<div className="space-y-2 flex-1">
<CardTitle className="text-xl font-bold text-gray-900 group-hover:text-blue-700 transition-colors line-clamp-2">
{template.title}
</CardTitle>
<div className="flex items-center space-x-2">
<Badge className={`${getComplexityColor(template.complexity)} font-medium px-3 py-1 rounded-full`}>
{getComplexityLabel(template.complexity)}
</Badge>
{template.popularity && (
<div className="flex items-center space-x-1 bg-white/70 px-2 py-1 rounded-full">
<Star className="h-4 w-4 text-yellow-500 fill-current" />
<span className="text-sm font-semibold text-gray-700">{template.popularity}%</span>
</div>
)}
</div>
</div>
</div>
<p className="text-gray-700 text-sm leading-relaxed font-medium">{template.description}</p>
</div>
<CardContent className="p-4 flex flex-col h-full">
<div className="flex-1 space-y-4">
{/* Stats Row */}
<div className="flex items-center justify-between text-sm text-gray-600 bg-gray-50 px-3 py-2 rounded-lg">
<div className="flex items-center space-x-2">
<Clock className="h-4 w-4 text-blue-500" />
<span className="font-medium">{template.timeEstimate}</span>
</div>
<div className="flex items-center space-x-2">
<Layers className="h-4 w-4 text-green-500" />
<span className="font-medium">{template.features.length} features</span>
</div>
</div>
{/* Features Section */}
<div className="space-y-2">
<div>
<h4 className="font-semibold text-sm text-gray-800 mb-1 flex items-center">
<span className="w-2 h-2 bg-blue-500 rounded-full mr-2"></span>
Key Features
</h4>
<div className="flex flex-wrap gap-1">
{template.features.slice(0, 3).map((feature, index) => (
<Badge key={index} variant="outline" className="text-xs bg-blue-50 border-blue-200 text-blue-700 px-3 py-2 rounded-full">
{feature}
</Badge>
))}
{template.features.length > 3 && (
<Badge variant="outline" className="text-xs bg-gray-50 border-gray-200 text-gray-600 px-3 py-2 rounded-full">
+{template.features.length - 3} more
</Badge>
)}
</div>
</div>
{/* Tech Stack Section */}
<div>
<h4 className="font-semibold text-sm text-gray-800 mb-1 flex items-center">
<span className="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
Tech Stack
</h4>
<div className="flex flex-wrap gap-1">
{template.techStack.slice(0, 3).map((tech, index) => (
<Badge key={index} variant="secondary" className="text-xs bg-green-50 text-green-700 px-3 py-1 rounded-full font-medium">
{tech}
</Badge>
))}
{template.techStack.length > 3 && (
<Badge variant="secondary" className="text-xs bg-gray-50 text-gray-600 px-3 py-1 rounded-full">
+{template.techStack.length - 3}
</Badge>
)}
</div>
</div>
</div>
</div>
{/* Action Button - Always at bottom */}
<div className="mt-4">
<Button
onClick={() => onNext(template)}
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-semibold py-2 rounded-lg shadow-lg hover:shadow-xl transition-all duration-300 group-hover:shadow-2xl"
>
<span className="flex items-center justify-center cursor-pointer">
Select Template
<ArrowRight className="ml-2 h-4 w-4 group-hover:translate-x-1 transition-transform" />
</span>
</Button>
</div>
</CardContent>
</Card>
))}
</div>
{/* Custom Template Option */}
<Card className="group border-dashed border-2 border-gray-300 hover:border-blue-400 transition-all duration-300 hover:scale-[1.02] bg-gradient-to-br from-gray-50 to-blue-50 overflow-hidden">
<CardContent className="text-center py-16 px-8">
<div className="w-20 h-20 bg-gradient-to-br from-blue-100 to-indigo-100 rounded-full flex items-center justify-center mx-auto mb-6 group-hover:from-blue-200 group-hover:to-indigo-200 transition-all duration-300">
<Plus className="h-10 w-10 text-blue-600 group-hover:text-blue-700 transition-colors" />
</div>
<h3 className="text-2xl font-bold text-gray-800 mb-3 group-hover:text-blue-700 transition-colors">Create Custom Template</h3>
<p className="text-gray-600 mb-8 max-w-md mx-auto text-lg leading-relaxed">
Don&apos;t see what you need? Create a custom project type with your specific requirements and tech stack.
</p>
<Button
variant="outline"
className="border-2 border-blue-300 text-blue-700 hover:bg-blue-50 bg-white hover:border-blue-400 px-8 py-3 text-lg font-semibold rounded-lg shadow-md hover:shadow-lg transition-all duration-300 group-hover:shadow-xl"
>
<Plus className="mr-2 h-5 w-5" />
Create Custom Template
</Button>
</CardContent>
</Card>
{/* Results Summary */}
{searchQuery && (
<div className="text-center py-4">
<p className="text-gray-600">
Showing {filteredTemplates.length} template{filteredTemplates.length !== 1 ? "s" : ""}
{searchQuery && ` matching "${searchQuery}"`}
</p>
</div>
)}
</div>
)
}
// Feature Selection Step Component
function FeatureSelectionStep({
template,
onNext,
onBack,
}: { template: Template; onNext: () => void; onBack: () => void }) {
return (
<div className="max-w-7xl mx-auto space-y-8">
{/* Header */}
<div className="text-center space-y-4">
<h1 className="text-4xl font-bold text-gray-900">Select Features for {template.title}</h1>
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
Choose the features that best fit your project requirements.
</p>
</div>
{/* Features List */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{template.features.map((feature, index) => (
<Card
key={index}
className="hover:shadow-xl transition-all duration-300 cursor-pointer group border-2 hover:border-blue-200"
>
<CardHeader className="pb-4">
<CardTitle className="text-xl group-hover:text-blue-600 transition-colors line-clamp-2">
{feature}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-gray-600 text-sm leading-relaxed">
This feature enhances your project with {feature} capabilities.
</p>
</CardContent>
</Card>
))}
</div>
{/* Navigation Buttons */}
<div className="text-center py-4">
<div className="space-x-4">
<Button variant="outline" onClick={onBack}>
Back
</Button>
<Button onClick={onNext}>Continue</Button>
</div>
</div>
</div>
)
}
// Main Dashboard Component
export function MainDashboard() {
const [currentStep, setCurrentStep] = useState(1)
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null)
const steps = [
{ id: 1, name: "Project Type", description: "Choose template" },
{ id: 2, name: "Features", description: "Select features" },
{ id: 3, name: "Business Context", description: "Define requirements" },
{ id: 4, name: "Generate", description: "Create project" },
{ id: 5, name: "Architecture", description: "Review & deploy" },
]
const renderStep = () => {
switch (currentStep) {
case 1:
return (
<TemplateSelectionStep
onNext={(template) => {
setSelectedTemplate(template)
setCurrentStep(2)
}}
/>
)
case 2:
return selectedTemplate ? (
<FeatureSelectionStep
template={selectedTemplate}
onNext={() => setCurrentStep(3)}
onBack={() => setCurrentStep(1)}
/>
) : null
case 3:
return (
<div className="text-center py-20">
<h2 className="text-2xl font-bold mb-4">Business Context Step</h2>
<p className="text-gray-600 mb-8">Coming soon - Define your business requirements and scaling needs</p>
<div className="space-x-4">
<Button variant="outline" onClick={() => setCurrentStep(2)}>
Back
</Button>
<Button onClick={() => setCurrentStep(4)}>Continue</Button>
</div>
</div>
)
case 4:
return (
<div className="text-center py-20">
<h2 className="text-2xl font-bold mb-4">Generate Step</h2>
<p className="text-gray-600 mb-8">Coming soon - Generate your project architecture and code</p>
<div className="space-x-4">
<Button variant="outline" onClick={() => setCurrentStep(3)}>
Back
</Button>
<Button onClick={() => setCurrentStep(5)}>Continue</Button>
</div>
</div>
)
case 5:
return (
<div className="text-center py-20">
<h2 className="text-2xl font-bold mb-4">Architecture Step</h2>
<p className="text-gray-600 mb-8">Coming soon - Review architecture and deploy your project</p>
<div className="space-x-4">
<Button variant="outline" onClick={() => setCurrentStep(4)}>
Back
</Button>
<Button>Deploy Project</Button>
</div>
</div>
)
default:
return null
}
}
return (
<div className="min-h-screen bg-white">
{/* Progress Steps */}
<div className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="py-4">
<nav className="flex justify-center">
<ol className="flex items-center space-x-8">
{steps.map((step, index) => (
<li key={step.id} className="flex items-center">
<div className="flex items-center">
<div
className={`flex items-center justify-center w-10 h-10 rounded-full border-2 transition-all ${
currentStep >= step.id
? "bg-blue-600 border-blue-600 text-white shadow-lg"
: currentStep === step.id - 1
? "border-blue-300 text-blue-600"
: "border-gray-300 text-gray-500"
}`}
>
<span className="text-sm font-semibold">{step.id}</span>
</div>
<div className="ml-4">
<p
className={`text-sm font-semibold ${
currentStep >= step.id ? "text-blue-600" : "text-gray-500"
}`}
>
{step.name}
</p>
<p className="text-xs text-gray-500">{step.description}</p>
</div>
</div>
{index < steps.length - 1 && (
<ArrowRight
className={`ml-8 h-5 w-5 ${currentStep > step.id ? "text-blue-600" : "text-gray-400"}`}
/>
)}
</li>
))}
</ol>
</nav>
</div>
</div>
</div>
{/* Main Content */}
<main className="py-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">{renderStep()}</div>
</main>
</div>
)
}

View File

@ -0,0 +1,152 @@
"use client"
import { useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Badge } from "@/components/ui/badge"
import { Bell, Settings, LogOut, User, Menu, X } from "lucide-react"
import { useAuth } from "@/contexts/auth-context"
const navigation = [
{ name: "Project Builder", href: "/", current: false },
{ name: "Templates", href: "/templates", current: false },
{ name: "Features", href: "/features", current: false },
{ name: "Business Context", href: "/business-context", current: false },
{ name: "Architecture", href: "/architecture", current: false },
]
export default function Header() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const pathname = usePathname()
const { user, logout } = useAuth()
return (
<header className="bg-white shadow-sm border-b">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 justify-between items-center">
{/* Logo */}
<div className="flex items-center">
<Link href="/" className="flex items-center space-x-2">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">C</span>
</div>
<span className="text-xl font-bold text-gray-900">Codenuk</span>
</Link>
</div>
{/* Desktop Navigation */}
<nav className="hidden md:flex space-x-8">
{navigation.map((item) => (
<Link
key={item.name}
href={item.href}
className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
pathname === item.href
? "bg-blue-100 text-blue-700"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-50"
}`}
>
{item.name}
</Link>
))}
</nav>
{/* Right side */}
<div className="flex items-center space-x-4">
{/* Auth Button or User Menu */}
{!user ? (
<Link href="/auth">
<Button variant="outline" size="sm">
Sign In
</Button>
</Link>
) : (
<>
{/* Notifications */}
<Button variant="ghost" size="sm" className="relative">
<Bell className="h-5 w-5" />
<Badge className="absolute -top-1 -right-1 h-5 w-5 rounded-full p-0 flex items-center justify-center text-xs">
3
</Badge>
</Button>
</>
)}
{/* User Menu - Only show when user is authenticated */}
{user && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full cursor-pointer">
<Avatar className="h-8 w-8">
<AvatarImage src={user.avatar || "/avatars/01.png"} alt={user.name} />
<AvatarFallback>{user.name.charAt(0).toUpperCase()}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">{user.name}</p>
<p className="text-xs leading-none text-muted-foreground">{user.email}</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<User className="mr-2 h-4 w-4" />
<span>Profile</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Settings className="mr-2 h-4 w-4" />
<span>Settings</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={logout}>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
{/* Mobile menu button */}
<Button variant="ghost" size="sm" className="md:hidden" onClick={() => setMobileMenuOpen(!mobileMenuOpen)}>
{mobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</Button>
</div>
</div>
{/* Mobile Navigation */}
{mobileMenuOpen && (
<div className="md:hidden">
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3 border-t">
{navigation.map((item) => (
<Link
key={item.name}
href={item.href}
className={`block px-3 py-2 rounded-md text-base font-medium transition-colors ${
pathname === item.href
? "bg-blue-100 text-blue-700"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-50"
}`}
onClick={() => setMobileMenuOpen(false)}
>
{item.name}
</Link>
))}
</div>
</div>
)}
</div>
</header>
)
}

View File

@ -0,0 +1,414 @@
"use client"
import { useState } from "react"
import Link from "next/link"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Search,
Star,
Clock,
Plus,
Copy,
Globe,
BarChart3,
Zap,
Code,
ArrowRight,
Download,
Eye,
Heart,
Share2,
} from "lucide-react"
interface Template {
id: string
title: string
description: string
category: string
features: string[]
complexity: number
timeEstimate: string
techStack: string[]
popularity?: number
lastUpdated?: string
downloads?: number
likes?: number
author?: string
isPublic?: boolean
preview?: string
}
export function TemplatesPage() {
const [activeTab, setActiveTab] = useState("browse")
const [selectedCategory, setSelectedCategory] = useState("all")
const [searchQuery, setSearchQuery] = useState("")
const [sortBy, setSortBy] = useState("popularity")
const templates: Template[] = [
// Marketing Templates
{
id: "marketing-website",
title: "Marketing Website",
description: "Professional marketing site with CMS and lead generation",
category: "marketing",
features: ["Content Management", "Contact Forms", "SEO Optimization", "Analytics Integration"],
complexity: 2,
timeEstimate: "1-2 weeks",
techStack: ["Next.js", "Sanity CMS", "Tailwind CSS", "Vercel"],
popularity: 95,
lastUpdated: "2024-01-15",
downloads: 1250,
likes: 89,
author: "Codenuk Team",
isPublic: true,
preview: "/marketing-website-preview.png",
},
{
id: "landing-page",
title: "Landing Page",
description: "High-converting landing page with A/B testing capabilities",
category: "marketing",
features: ["A/B Testing", "Conversion Tracking", "Lead Capture", "Mobile Optimization"],
complexity: 2,
timeEstimate: "3-5 days",
techStack: ["Next.js", "Tailwind CSS", "Google Analytics", "Mailchimp"],
popularity: 88,
lastUpdated: "2024-01-10",
downloads: 980,
likes: 76,
author: "Codenuk Team",
isPublic: true,
preview: "/landing-page-preview.png",
},
{
id: "blog-platform",
title: "Blog Platform",
description: "Content-rich blog with SEO optimization and social sharing",
category: "marketing",
features: ["Content Management", "SEO Tools", "Social Sharing", "Comment System"],
complexity: 3,
timeEstimate: "1-2 weeks",
techStack: ["Next.js", "MDX", "Tailwind CSS", "Disqus"],
popularity: 82,
lastUpdated: "2024-01-12",
downloads: 756,
likes: 64,
author: "Community",
isPublic: true,
preview: "/blog-platform-preview.png",
},
// Software Templates
{
id: "saas-platform",
title: "SaaS Platform",
description: "Complete SaaS application with user management, billing, and analytics",
category: "software",
features: ["User Authentication", "Payment Processing", "Analytics Integration", "API Management"],
complexity: 5,
timeEstimate: "4-6 weeks",
techStack: ["Next.js", "PostgreSQL", "Stripe", "NextAuth.js"],
popularity: 92,
lastUpdated: "2024-01-15",
downloads: 2100,
likes: 156,
author: "Codenuk Team",
isPublic: true,
preview: "/saas-platform-preview.png",
},
{
id: "dashboard-app",
title: "Analytics Dashboard",
description: "Data visualization dashboard with real-time updates",
category: "software",
features: ["Data Visualization", "Real-time Updates", "User Authentication", "Export Features"],
complexity: 4,
timeEstimate: "3-4 weeks",
techStack: ["Next.js", "Chart.js", "WebSockets", "PostgreSQL"],
popularity: 89,
lastUpdated: "2024-01-14",
downloads: 1890,
likes: 134,
author: "Community",
isPublic: true,
preview: "/analytics-dashboard-preview.png",
},
// SEO Templates
{
id: "seo-optimized-blog",
title: "SEO-Optimized Blog",
description: "Blog platform with advanced SEO features and schema markup",
category: "seo",
features: ["Schema Markup", "Meta Optimization", "Sitemap Generation", "Performance Optimization"],
complexity: 3,
timeEstimate: "2-3 weeks",
techStack: ["Next.js", "SEO Tools", "Schema.org", "Google Search Console"],
popularity: 90,
lastUpdated: "2024-01-15",
downloads: 1456,
likes: 98,
author: "SEO Expert",
isPublic: true,
preview: "/seo-blog-preview.png",
},
]
const categories = [
{ id: "all", name: "All Templates", icon: Globe, count: templates.length },
{
id: "marketing",
name: "Marketing",
icon: Zap,
count: templates.filter((t) => t.category === "marketing").length,
},
{ id: "software", name: "Software", icon: Code, count: templates.filter((t) => t.category === "software").length },
{ id: "seo", name: "SEO", icon: BarChart3, count: templates.filter((t) => t.category === "seo").length },
]
const filteredTemplates = templates.filter((template) => {
const matchesCategory = selectedCategory === "all" || template.category === selectedCategory
const matchesSearch =
template.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.features.some((feature) => feature.toLowerCase().includes(searchQuery.toLowerCase()))
return matchesCategory && matchesSearch
})
const sortedTemplates = [...filteredTemplates].sort((a, b) => {
switch (sortBy) {
case "popularity":
return (b.popularity || 0) - (a.popularity || 0)
case "downloads":
return (b.downloads || 0) - (a.downloads || 0)
case "recent":
return new Date(b.lastUpdated || "").getTime() - new Date(a.lastUpdated || "").getTime()
case "likes":
return (b.likes || 0) - (a.likes || 0)
default:
return 0
}
})
const getComplexityColor = (complexity: number) => {
if (complexity <= 2) return "bg-green-100 text-green-800"
if (complexity <= 3) return "bg-yellow-100 text-yellow-800"
return "bg-red-100 text-red-800"
}
const getComplexityLabel = (complexity: number) => {
if (complexity <= 2) return "Simple"
if (complexity <= 3) return "Moderate"
return "Complex"
}
const TemplateCard = ({ template }: { template: Template }) => (
<Card className="group hover:shadow-xl transition-all duration-300 border-2 hover:border-blue-200">
<div className="relative">
<Image
src={template.preview || "/placeholder.svg"}
alt={`${template.title} preview`}
width={400}
height={192}
className="w-full h-48 object-cover rounded-t-lg"
/>
<div className="absolute top-3 right-3 flex space-x-2">
<Button size="sm" variant="secondary" className="opacity-0 group-hover:opacity-100 transition-opacity">
<Eye className="h-4 w-4" />
</Button>
<Button size="sm" variant="secondary" className="opacity-0 group-hover:opacity-100 transition-opacity">
<Heart className="h-4 w-4" />
</Button>
</div>
</div>
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="space-y-2 flex-1">
<CardTitle className="text-lg group-hover:text-blue-600 transition-colors">{template.title}</CardTitle>
<div className="flex items-center space-x-2">
<Badge className={getComplexityColor(template.complexity)}>
{getComplexityLabel(template.complexity)}
</Badge>
<div className="flex items-center space-x-1 text-sm text-gray-500">
<Star className="h-4 w-4 text-yellow-500 fill-current" />
<span>{template.popularity}%</span>
</div>
</div>
</div>
</div>
<p className="text-gray-600 text-sm">{template.description}</p>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between text-sm text-gray-500">
<div className="flex items-center space-x-1">
<Clock className="h-4 w-4" />
<span>{template.timeEstimate}</span>
</div>
<div className="flex items-center space-x-1">
<Download className="h-4 w-4" />
<span>{template.downloads}</span>
</div>
</div>
<div className="space-y-3">
<div>
<h4 className="font-medium text-sm text-gray-700 mb-2">Features:</h4>
<div className="flex flex-wrap gap-1">
{template.features.slice(0, 2).map((feature, index) => (
<Badge key={index} variant="outline" className="text-xs">
{feature}
</Badge>
))}
{template.features.length > 2 && (
<Badge variant="outline" className="text-xs">
+{template.features.length - 2} more
</Badge>
)}
</div>
</div>
<div>
<h4 className="font-medium text-sm text-gray-700 mb-2">Tech Stack:</h4>
<div className="flex flex-wrap gap-1">
{template.techStack.slice(0, 3).map((tech, index) => (
<Badge key={index} variant="secondary" className="text-xs">
{tech}
</Badge>
))}
</div>
</div>
</div>
<div className="flex space-x-2 pt-2">
<Button className="flex-1 bg-blue-600 hover:bg-blue-700">
Use Template
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
<Button variant="outline" size="sm">
<Copy className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm">
<Share2 className="h-4 w-4" />
</Button>
</div>
<div className="flex items-center justify-between text-xs text-gray-500 pt-2 border-t">
<span>by {template.author}</span>
<span>{template.lastUpdated}</span>
</div>
</CardContent>
</Card>
)
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-8">
<div className="flex items-center justify-between">
<TabsList className="grid w-full max-w-md grid-cols-3">
<TabsTrigger value="browse">Browse Templates</TabsTrigger>
<TabsTrigger value="my-templates">My Templates</TabsTrigger>
<TabsTrigger value="favorites">Favorites</TabsTrigger>
</TabsList>
<div className="flex items-center space-x-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="Search templates..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 w-80"
/>
</div>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md text-sm"
>
<option value="popularity">Most Popular</option>
<option value="downloads">Most Downloaded</option>
<option value="recent">Recently Updated</option>
<option value="likes">Most Liked</option>
</select>
</div>
</div>
<TabsContent value="browse" className="space-y-8">
{/* Category Filters */}
<div className="flex flex-wrap gap-4">
{categories.map((category) => {
const Icon = category.icon
return (
<button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={`flex items-center space-x-2 px-4 py-2 rounded-lg border transition-all ${
selectedCategory === category.id
? "bg-blue-600 text-white border-blue-600"
: "bg-white text-gray-700 border-gray-200 hover:border-blue-300"
}`}
>
<Icon className="h-4 w-4" />
<span className="font-medium">{category.name}</span>
<Badge variant="secondary" className="ml-1">
{category.count}
</Badge>
</button>
)
})}
</div>
{/* Templates Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{sortedTemplates.map((template) => (
<TemplateCard key={template.id} template={template} />
))}
</div>
{/* Results Summary */}
<div className="text-center py-8">
<p className="text-gray-600">
Showing {sortedTemplates.length} template{sortedTemplates.length !== 1 ? "s" : ""}
{searchQuery && ` matching "${searchQuery}"`}
</p>
</div>
</TabsContent>
<TabsContent value="my-templates" className="space-y-8">
<div className="text-center py-20">
<Plus className="h-16 w-16 text-gray-400 mx-auto mb-6" />
<h3 className="text-2xl font-semibold text-gray-700 mb-3">No Templates Yet</h3>
<p className="text-gray-500 mb-6 max-w-md mx-auto">
Create your first template to get started. Templates you create will appear here.
</p>
<Button className="bg-blue-600 hover:bg-blue-700">
<Plus className="mr-2 h-5 w-5" />
Create Your First Template
</Button>
</div>
</TabsContent>
<TabsContent value="favorites" className="space-y-8">
<div className="text-center py-20">
<Heart className="h-16 w-16 text-gray-400 mx-auto mb-6" />
<h3 className="text-2xl font-semibold text-gray-700 mb-3">No Favorites Yet</h3>
<p className="text-gray-500 mb-6 max-w-md mx-auto">
Heart templates you like to save them here for quick access later.
</p>
<Button variant="outline" onClick={() => setActiveTab("browse")}>
Browse Templates
</Button>
</div>
</TabsContent>
</Tabs>
</div>
</div>
)
}

View File

@ -0,0 +1,53 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
function Avatar({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
)
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
{...props}
/>
)
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
className
)}
{...props}
/>
)
}
export { Avatar, AvatarImage, AvatarFallback }

View File

@ -0,0 +1,46 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

View File

@ -0,0 +1,59 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }

View File

@ -0,0 +1,92 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

View File

@ -0,0 +1,32 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }

View File

@ -0,0 +1,257 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function DropdownMenu({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
)
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
)
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
)
}
function DropdownMenuGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
variant?: "default" | "destructive"
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
)
}
function DropdownMenuRadioGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
)
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
)
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className
)}
{...props}
/>
)
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className
)}
{...props}
/>
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
)
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...props}
/>
)
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}

View File

@ -0,0 +1,21 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{...props}
/>
)
}
export { Input }

View File

@ -0,0 +1,24 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cn } from "@/lib/utils"
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props}
/>
)
}
export { Label }

View File

@ -0,0 +1,31 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
function Progress({
className,
value,
...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return (
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="bg-primary h-full w-full flex-1 transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
)
}
export { Progress }

View File

@ -0,0 +1,45 @@
"use client"
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn("grid gap-3", className)}
{...props}
/>
)
}
function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="relative flex items-center justify-center"
>
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
}
export { RadioGroup, RadioGroupItem }

View File

@ -0,0 +1,185 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "popper",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

View File

@ -0,0 +1,66 @@
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@ -0,0 +1,138 @@
"use client"
import React, { createContext, useContext, useState, useEffect, ReactNode } from "react"
interface User {
id: string
name: string
email: string
avatar?: string
}
interface AuthContextType {
user: User | null
isAuthenticated: boolean
isLoading: boolean
login: (email: string, password: string) => Promise<boolean>
signup: (name: string, email: string, password: string) => Promise<boolean>
logout: () => void
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider")
}
return context
}
interface AuthProviderProps {
children: ReactNode
}
export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = useState<User | null>(null)
const [isLoading, setIsLoading] = useState(true)
// Check if user is logged in on mount
useEffect(() => {
const checkAuth = () => {
// Check localStorage for user data
const userData = localStorage.getItem("codenuk_user")
if (userData) {
try {
const user = JSON.parse(userData)
setUser(user)
} catch (error) {
console.error("Error parsing user data:", error)
localStorage.removeItem("codenuk_user")
}
}
setIsLoading(false)
}
checkAuth()
}, [])
const login = async (email: string, password: string): Promise<boolean> => {
try {
setIsLoading(true)
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000))
// For demo purposes, accept any email/password combination
if (email && password) {
const user: User = {
id: "1",
name: email.split("@")[0], // Use email prefix as name
email: email,
avatar: "/avatars/01.png"
}
setUser(user)
localStorage.setItem("codenuk_user", JSON.stringify(user))
return true
}
return false
} catch (error) {
console.error("Login error:", error)
return false
} finally {
setIsLoading(false)
}
}
const signup = async (name: string, email: string, password: string): Promise<boolean> => {
try {
setIsLoading(true)
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000))
// For demo purposes, create user if all fields are provided
if (name && email && password) {
const user: User = {
id: "1",
name: name,
email: email,
avatar: "/avatars/01.png"
}
setUser(user)
localStorage.setItem("codenuk_user", JSON.stringify(user))
return true
}
return false
} catch (error) {
console.error("Signup error:", error)
return false
} finally {
setIsLoading(false)
}
}
const logout = () => {
setUser(null)
localStorage.removeItem("codenuk_user")
}
const value: AuthContextType = {
user,
isAuthenticated: !!user,
isLoading,
login,
signup,
logout
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}

6
src/lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

30
src/types/feature.ts Normal file
View File

@ -0,0 +1,30 @@
import { LucideIcon } from "lucide-react"
export interface Feature {
id: string
name: string
description: string
category: string
icon: LucideIcon
complexity: number
timeImpact: string
dependencies: string[]
conflicts: string[]
techStack: string[]
businessQuestions: string[]
isCore?: boolean
isPopular?: boolean
}
export interface SelectedFeature extends Feature {
order: number
customConfig?: Record<string, unknown>
}
export interface FeatureCategory {
id: string
name: string
icon: LucideIcon
count: number
}

27
src/types/template.ts Normal file
View File

@ -0,0 +1,27 @@
import { LucideIcon } from "lucide-react"
export interface Template {
id: string
title: string
description: string
category: string
features: string[]
complexity: number
timeEstimate: string
techStack: string[]
popularity?: number
lastUpdated?: string
downloads?: number
likes?: number
author?: string
isPublic?: boolean
preview?: string
}
export interface TemplateCategory {
id: string
name: string
icon: LucideIcon
count: number
}