diff --git a/README-DATABASE.md b/README-DATABASE.md new file mode 100644 index 0000000..ecb8b43 --- /dev/null +++ b/README-DATABASE.md @@ -0,0 +1,228 @@ +# 🗄️ Portfolio Database Setup + +Dieses Portfolio verwendet **PostgreSQL mit Prisma ORM** für maximale Performance und Skalierbarkeit. + +## 🚀 Warum PostgreSQL + Prisma? + +- **🏃‍♂️ Hohe Performance**: Kann tausende User gleichzeitig bedienen +- **📈 Einfache Skalierung**: Von lokal zu Cloud ohne Code-Änderungen +- **🔧 TypeScript-First**: Vollständige Type-Sicherheit und Auto-completion +- **💾 Robuste Datenbank**: ACID, Transaktionen, Indizes für optimale Performance +- **🔄 Einfache Migration**: Einfache Updates und Schema-Änderungen + +## 📋 Voraussetzungen + +- Node.js 18+ +- npm oder yarn +- PostgreSQL (wird automatisch installiert) + +## 🛠️ Schnellstart (Automatisch) + +```bash +# 1. Repository klonen +git clone +cd my_portfolio + +# 2. Automatische Datenbank-Einrichtung +npm run db:setup +``` + +Das Skript installiert automatisch: +- ✅ PostgreSQL +- ✅ Datenbank und Benutzer +- ✅ Prisma Client +- ✅ Beispieldaten +- ✅ Umgebungsvariablen + +## 🔧 Manuelle Einrichtung + +### 1. PostgreSQL installieren + +**Ubuntu/Debian:** +```bash +sudo apt-get update +sudo apt-get install postgresql postgresql-contrib +``` + +**macOS:** +```bash +brew install postgresql +brew services start postgresql +``` + +**Windows:** +- [PostgreSQL Download](https://www.postgresql.org/download/windows/) + +### 2. Datenbank einrichten + +```bash +# PostgreSQL starten +sudo systemctl start postgresql # Linux +brew services start postgresql # macOS + +# Datenbank und Benutzer erstellen +sudo -u postgres psql +CREATE DATABASE portfolio_db; +CREATE USER portfolio_user WITH PASSWORD 'portfolio_pass'; +GRANT ALL PRIVILEGES ON DATABASE portfolio_db TO portfolio_user; +ALTER USER portfolio_user WITH SUPERUSER; +\q +``` + +### 3. Umgebungsvariablen + +Erstelle `.env.local`: +```env +DATABASE_URL="postgresql://portfolio_user:portfolio_pass@localhost:5432/portfolio_db?schema=public" +NEXTAUTH_SECRET="your-secret-key-here" +NEXTAUTH_URL="http://localhost:3000" +``` + +### 4. Dependencies installieren + +```bash +npm install +npx prisma generate +npx prisma db push +npx prisma db seed +``` + +## 🎯 Verfügbare Befehle + +```bash +# Datenbank verwalten +npm run db:setup # Vollständige Einrichtung +npm run db:generate # Prisma Client generieren +npm run db:push # Schema zur Datenbank pushen +npm run db:seed # Beispieldaten einfügen +npm run db:studio # Datenbank-Interface öffnen +npm run db:reset # Datenbank zurücksetzen + +# Entwicklung +npm run dev # Entwicklungsserver starten +npm run build # Produktions-Build +npm run start # Produktions-Server starten +``` + +## 🗄️ Datenbank-Schema + +### Projects +- **Basis**: Titel, Beschreibung, Inhalt, Tags +- **Metadaten**: Kategorie, Schwierigkeit, Datum +- **Performance**: Lighthouse Score, Bundle Size, Load Time +- **Analytics**: Views, Likes, Shares +- **Erweiterte Features**: Technologien, Herausforderungen, Lektionen + +### Analytics +- **PageViews**: Seitenaufrufe mit IP und User-Agent +- **UserInteractions**: Likes, Shares, Bookmarks, Kommentare + +## 📊 Performance-Features + +- **Indizes** auf allen wichtigen Feldern +- **Pagination** für große Datenmengen +- **Caching** für häufige Abfragen +- **Optimierte Queries** mit Prisma +- **Real-time Updates** möglich + +## 🔄 Migration & Updates + +```bash +# Schema ändern +npx prisma db push + +# Bei Breaking Changes +npx prisma migrate dev --name update_schema + +# Produktion +npx prisma migrate deploy +``` + +## 🌐 Deployment + +### Lokal zu Cloud Migration + +1. **Datenbank exportieren:** +```bash +pg_dump portfolio_db > backup.sql +``` + +2. **Cloud-Datenbank einrichten** (z.B. Supabase, PlanetScale, AWS RDS) + +3. **Umgebungsvariablen aktualisieren:** +```env +DATABASE_URL="postgresql://user:pass@host:5432/db?schema=public" +``` + +4. **Schema pushen:** +```bash +npx prisma db push +``` + +## 🚨 Troubleshooting + +### PostgreSQL startet nicht +```bash +# Linux +sudo systemctl status postgresql +sudo systemctl start postgresql + +# macOS +brew services list +brew services restart postgresql +``` + +### Verbindungsfehler +```bash +# PostgreSQL Status prüfen +sudo -u postgres psql -c "SELECT version();" + +# Verbindung testen +psql -h localhost -U portfolio_user -d portfolio_db +``` + +### Prisma Fehler +```bash +# Client neu generieren +npx prisma generate + +# Datenbank zurücksetzen +npx prisma db push --force-reset +``` + +## 📈 Monitoring & Wartung + +### Datenbank-Status +```bash +# Größe prüfen +psql -U portfolio_user -d portfolio_db -c "SELECT pg_size_pretty(pg_database_size('portfolio_db'));" + +# Performance-Statistiken +psql -U portfolio_user -d portfolio_db -c "SELECT * FROM pg_stat_database;" +``` + +### Backup & Restore +```bash +# Backup erstellen +pg_dump -U portfolio_user portfolio_db > backup_$(date +%Y%m%d).sql + +# Backup wiederherstellen +psql -U portfolio_user -d portfolio_db < backup_20241201.sql +``` + +## 🎉 Nächste Schritte + +1. **Datenbank starten**: `npm run db:setup` +2. **Entwicklungsserver starten**: `npm run dev` +3. **Admin-Bereich öffnen**: http://localhost:3000/admin +4. **Projekte verwalten** und dein Portfolio erweitern! + +## 📚 Weitere Ressourcen + +- [Prisma Dokumentation](https://www.prisma.io/docs) +- [PostgreSQL Dokumentation](https://www.postgresql.org/docs/) +- [Next.js API Routes](https://nextjs.org/docs/api-routes/introduction) + +--- + +**Fragen oder Probleme?** Erstelle ein Issue oder kontaktiere mich! 🚀 diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 944e7d1..9132f28 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -22,13 +22,50 @@ import { Palette, Smile, FileText, - Download, - Upload as UploadIcon, Settings, - Smartphone + Database, + BarChart3 } from 'lucide-react'; import Link from 'next/link'; import ReactMarkdown from 'react-markdown'; +// API functions to replace direct Prisma usage +const apiService = { + async getAllProjects() { + const response = await fetch('/api/projects'); + if (!response.ok) throw new Error('Failed to fetch projects'); + return response.json(); + }, + + async createProject(data: any) { + const response = await fetch('/api/projects', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + if (!response.ok) throw new Error('Failed to create project'); + return response.json(); + }, + + async updateProject(id: number, data: any) { + const response = await fetch(`/api/projects/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + if (!response.ok) throw new Error('Failed to update project'); + return response.json(); + }, + + async deleteProject(id: number) { + const response = await fetch(`/api/projects/${id}`, { + method: 'DELETE' + }); + if (!response.ok) throw new Error('Failed to delete project'); + return response.json(); + } +}; +import AdminDashboard from '@/components/AdminDashboard'; +import { useToast } from '@/components/Toast'; interface Project { id: number; @@ -46,11 +83,33 @@ interface Project { metaDescription?: string; keywords?: string; ogImage?: string; - schema?: any; + schema?: Record; + // New 2.0 features + difficulty: 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert'; + timeToComplete?: string; + technologies: string[]; + challenges: string[]; + lessonsLearned: string[]; + futureImprovements: string[]; + demoVideo?: string; + screenshots: string[]; + colorScheme: string; + accessibility: boolean; + performance: { + lighthouse: number; + bundleSize: string; + loadTime: string; + }; + analytics: { + views: number; + likes: number; + shares: number; + }; } const AdminPage = () => { const [mounted, setMounted] = useState(false); + const { showProjectSaved, showProjectDeleted, showError, showWarning } = useToast(); useEffect(() => { setMounted(true); @@ -58,34 +117,25 @@ const AdminPage = () => { const [projects, setProjects] = useState([]); - // Load projects from localStorage on mount + // Load projects from database on mount useEffect(() => { - const savedProjects = localStorage.getItem('portfolio-projects'); - if (savedProjects) { - setProjects(JSON.parse(savedProjects)); - } else { - // Default projects if none exist - const defaultProjects: Project[] = [ - { - id: 1, - title: "Portfolio Website", - description: "A modern, responsive portfolio website built with Next.js, TypeScript, and Tailwind CSS.", - content: "# Portfolio Website\n\nThis is my personal portfolio website built with modern web technologies. The site features a dark theme with glassmorphism effects and smooth animations.\n\n## Features\n\n- **Responsive Design**: Works perfectly on all devices\n- **Dark Theme**: Modern dark mode with glassmorphism effects\n- **Animations**: Smooth animations powered by Framer Motion\n- **Markdown Support**: Projects are written in Markdown for easy editing\n- **Performance**: Optimized for speed and SEO\n\n## Technologies Used\n\n- Next.js 15\n- TypeScript\n- Tailwind CSS\n- Framer Motion\n- React Markdown\n\n## Development Process\n\nThe website was designed with a focus on user experience and performance. I used modern CSS techniques like CSS Grid, Flexbox, and custom properties to create a responsive layout.\n\n## Future Improvements\n\n- Add blog functionality\n- Implement project filtering\n- Add more interactive elements\n- Optimize for Core Web Vitals\n\n## Links\n\n- [Live Demo](https://dki.one)\n- [GitHub Repository](https://github.com/Denshooter/portfolio)", - tags: ["Next.js", "TypeScript", "Tailwind CSS", "Framer Motion"], - featured: true, - category: "Web Development", - date: "2024" - } - ]; - setProjects(defaultProjects); - localStorage.setItem('portfolio-projects', JSON.stringify(defaultProjects)); - } + loadProjects(); }, []); + const loadProjects = async () => { + try { + const result = await apiService.getAllProjects(); + setProjects(result.projects); + } catch (error) { + console.error('Error loading projects from database:', error); + } + }; + const [selectedProject, setSelectedProject] = useState(null); const [isPreview, setIsPreview] = useState(false); const [isProjectsCollapsed, setIsProjectsCollapsed] = useState(false); + const [showTemplates, setShowTemplates] = useState(false); const [formData, setFormData] = useState({ title: '', description: '', @@ -96,7 +146,28 @@ const AdminPage = () => { github: '', live: '', published: true, - imageUrl: '' + imageUrl: '', + // New 2.0 fields + difficulty: 'Intermediate' as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert', + timeToComplete: '', + technologies: '', + challenges: '', + lessonsLearned: '', + futureImprovements: '', + demoVideo: '', + screenshots: '', + colorScheme: 'Dark', + accessibility: true, + performance: { + lighthouse: 90, + bundleSize: '50KB', + loadTime: '1.5s' + }, + analytics: { + views: 0, + likes: 0, + shares: 0 + } }); const [markdownContent, setMarkdownContent] = useState(''); @@ -123,39 +194,16 @@ const AdminPage = () => { return null; } - const handleSave = () => { + const handleSave = async () => { if (!formData.title || !formData.description || !markdownContent || !formData.category) { - alert('Please fill in all required fields!'); + showWarning('Pflichtfelder fehlen', 'Bitte fülle alle erforderlichen Felder aus.'); return; } try { if (selectedProject) { - // Update existing project - const updatedProjects = projects.map(p => - p.id === selectedProject.id - ? { - ...p, - title: formData.title, - description: formData.description, - content: markdownContent, - tags: formData.tags ? formData.tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [], - category: formData.category, - featured: formData.featured, - github: formData.github || undefined, - live: formData.live || undefined, - published: formData.published, - imageUrl: formData.imageUrl || undefined - } - : p - ); - setProjects(updatedProjects); - localStorage.setItem('portfolio-projects', JSON.stringify(updatedProjects)); - console.log('Project updated successfully:', selectedProject.id); - } else { - // Create new project - const newProject: Project = { - id: Math.floor(Math.random() * 1000000), + // Update existing project in database + const projectData = { title: formData.title, description: formData.description, content: markdownContent, @@ -166,19 +214,62 @@ const AdminPage = () => { live: formData.live || undefined, published: formData.published, imageUrl: formData.imageUrl || undefined, - date: new Date().getFullYear().toString() + difficulty: formData.difficulty, + timeToComplete: formData.timeToComplete || undefined, + technologies: formData.technologies ? formData.technologies.split(',').map(tech => tech.trim()).filter(tech => tech) : [], + challenges: formData.challenges ? formData.challenges.split(',').map(challenge => challenge.trim()).filter(challenge => challenge) : [], + lessonsLearned: formData.lessonsLearned ? formData.lessonsLearned.split(',').map(lesson => lesson.trim()).filter(lesson => lesson) : [], + futureImprovements: formData.futureImprovements ? formData.futureImprovements.split(',').map(improvement => improvement.trim()).filter(improvement => improvement) : [], + demoVideo: formData.demoVideo || undefined, + screenshots: formData.screenshots ? formData.screenshots.split(',').map(screenshot => screenshot.trim()).filter(screenshot => screenshot) : [], + colorScheme: formData.colorScheme, + accessibility: formData.accessibility, + performance: formData.performance, + analytics: formData.analytics }; - const updatedProjects = [...projects, newProject]; - setProjects(updatedProjects); - localStorage.setItem('portfolio-projects', JSON.stringify(updatedProjects)); - console.log('New project created successfully:', newProject.id); + + await apiService.updateProject(selectedProject.id, projectData); + console.log('Project updated successfully in database:', selectedProject.id); + showProjectSaved(formData.title); + } else { + // Create new project in database + const projectData = { + title: formData.title, + description: formData.description, + content: markdownContent, + tags: formData.tags ? formData.tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [], + category: formData.category, + featured: formData.featured, + github: formData.github || undefined, + live: formData.live || undefined, + published: formData.published, + imageUrl: formData.imageUrl || undefined, + date: new Date().getFullYear().toString(), + difficulty: formData.difficulty, + timeToComplete: formData.timeToComplete || undefined, + technologies: formData.technologies ? formData.technologies.split(',').map(tech => tech.trim()).filter(tech => tech) : [], + challenges: formData.challenges ? formData.challenges.split(',').map(challenge => challenge.trim()).filter(challenge => challenge) : [], + lessonsLearned: formData.lessonsLearned ? formData.lessonsLearned.split(',').map(lesson => lesson.trim()).filter(lesson => lesson) : [], + futureImprovements: formData.futureImprovements ? formData.futureImprovements.split(',').map(improvement => improvement.trim()).filter(improvement => improvement) : [], + demoVideo: formData.demoVideo || undefined, + screenshots: formData.screenshots ? formData.screenshots.split(',').map(screenshot => screenshot.trim()).filter(screenshot => screenshot) : [], + colorScheme: formData.colorScheme, + accessibility: formData.accessibility, + performance: formData.performance, + analytics: formData.analytics + }; + + await apiService.createProject(projectData); + console.log('New project created successfully in database'); + showProjectSaved(formData.title); } + // Reload projects from database + await loadProjects(); resetForm(); - alert('Project saved successfully!'); } catch (error) { console.error('Error saving project:', error); - alert('Error saving project. Please try again.'); + showError('Fehler beim Speichern', 'Das Projekt konnte nicht gespeichert werden. Bitte versuche es erneut.'); } }; @@ -195,17 +286,47 @@ const AdminPage = () => { github: project.github || '', live: project.live || '', published: project.published !== undefined ? project.published : true, - imageUrl: project.imageUrl || '' + imageUrl: project.imageUrl || '', + // New 2.0 fields + difficulty: project.difficulty || 'Intermediate' as const, + timeToComplete: project.timeToComplete || '', + technologies: project.technologies ? project.technologies.join(', ') : '', + challenges: project.challenges ? project.challenges.join(', ') : '', + lessonsLearned: project.lessonsLearned ? project.lessonsLearned.join(', ') : '', + futureImprovements: project.futureImprovements ? project.futureImprovements.join(', ') : '', + demoVideo: project.demoVideo || '', + screenshots: project.screenshots ? project.screenshots.join(', ') : '', + colorScheme: project.colorScheme || 'Dark', + accessibility: project.accessibility !== undefined ? project.accessibility : true, + performance: project.performance || { + lighthouse: 90, + bundleSize: '50KB', + loadTime: '1.5s' + }, + analytics: project.analytics || { + views: 0, + likes: 0, + shares: 0 + } }); setMarkdownContent(project.content); setIsPreview(false); }; - const handleDelete = (projectId: number) => { + const handleDelete = async (projectId: number) => { if (confirm('Are you sure you want to delete this project?')) { - const updatedProjects = projects.filter(p => p.id !== projectId); - setProjects(updatedProjects); - localStorage.setItem('portfolio-projects', JSON.stringify(updatedProjects)); + try { + const project = projects.find(p => p.id === projectId); + await apiService.deleteProject(projectId); + await loadProjects(); // Reload from database + console.log('Project deleted successfully from database:', projectId); + if (project) { + showProjectDeleted(project.title); + } + } catch (error) { + console.error('Error deleting project:', error); + showError('Fehler beim Löschen', 'Das Projekt konnte nicht gelöscht werden. Bitte versuche es erneut.'); + } } }; @@ -222,7 +343,28 @@ const AdminPage = () => { github: '', live: '', published: true, - imageUrl: '' + imageUrl: '', + // New 2.0 fields + difficulty: 'Intermediate' as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert', + timeToComplete: '', + technologies: '', + challenges: '', + lessonsLearned: '', + futureImprovements: '', + demoVideo: '', + screenshots: '', + colorScheme: 'Dark', + accessibility: true, + performance: { + lighthouse: 90, + bundleSize: '50KB', + loadTime: '1.5s' + }, + analytics: { + views: 0, + likes: 0, + shares: 0 + } }); setMarkdownContent(''); setIsPreview(false); @@ -280,6 +422,26 @@ const AdminPage = () => { insertion = `| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |`; cursorOffset = 0; break; + case 'emoji': + insertion = `😊 ${text.substring(start, end) || 'Add your text here'}`; + cursorOffset = 2; + break; + case 'codeblock': + insertion = `\`\`\`javascript\n${text.substring(start, end) || '// Your code here'}\n\`\`\``; + cursorOffset = 0; + break; + case 'highlight': + insertion = `==${text.substring(start, end) || 'highlighted text'}==`; + cursorOffset = 2; + break; + case 'spoiler': + insertion = `||${text.substring(start, end) || 'spoiler text'}||`; + cursorOffset = 2; + break; + case 'callout': + insertion = `> [!NOTE]\n> ${text.substring(start, end) || 'This is a callout box'}`; + cursorOffset = 0; + break; } const newText = text.substring(0, start) + insertion + text.substring(end); @@ -343,6 +505,142 @@ const AdminPage = () => { } }; + // Project Templates + const projectTemplates = { + 'Web App': { + title: 'Web Application', + description: 'A modern web application with responsive design and advanced features.', + content: `# Web Application + +## 🚀 Overview +A modern web application built with cutting-edge technologies. + +## ✨ Features +- **Responsive Design**: Works perfectly on all devices +- **Modern UI/UX**: Beautiful and intuitive user interface +- **Performance**: Optimized for speed and efficiency +- **Security**: Built with security best practices + +## 🛠️ Technologies Used +- Frontend Framework +- Backend Technology +- Database +- Additional Tools + +## 📱 Screenshots +![App Screenshot](screenshot-url) + +## 🔗 Links +- [Live Demo](demo-url) +- [GitHub Repository](github-url) + +## 📈 Future Improvements +- Feature 1 +- Feature 2 +- Feature 3`, + difficulty: 'Intermediate' as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert', + category: 'Web Application', + technologies: 'React, Node.js, MongoDB', + colorScheme: 'Modern and Clean' + }, + 'Mobile App': { + title: 'Mobile Application', + description: 'A cross-platform mobile application with native performance.', + content: `# Mobile Application + +## 📱 Overview +A cross-platform mobile application that delivers native performance. + +## ✨ Features +- **Cross-Platform**: Works on iOS and Android +- **Native Performance**: Optimized for mobile devices +- **Offline Support**: Works without internet connection +- **Push Notifications**: Keep users engaged + +## 🛠️ Technologies Used +- React Native / Flutter +- Backend API +- Database +- Cloud Services + +## 📸 Screenshots +![Mobile Screenshot](screenshot-url) + +## 🔗 Links +- [App Store](app-store-url) +- [Play Store](play-store-url) +- [GitHub Repository](github-url) + +## 📈 Future Improvements +- Feature 1 +- Feature 2 +- Feature 3`, + difficulty: 'Advanced' as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert', + category: 'Mobile App', + technologies: 'React Native, Firebase, Node.js', + colorScheme: 'Mobile-First Design' + }, + 'API Service': { + title: 'API Service', + description: 'A robust and scalable API service with comprehensive documentation.', + content: `# API Service + +## 🔌 Overview +A robust and scalable API service that powers multiple applications. + +## ✨ Features +- **RESTful Design**: Follows REST API best practices +- **Authentication**: Secure JWT-based authentication +- **Rate Limiting**: Prevents abuse and ensures stability +- **Comprehensive Docs**: Complete API documentation +- **Testing**: Extensive test coverage + +## 🛠️ Technologies Used +- Backend Framework +- Database +- Authentication +- Testing Tools + +## 📚 API Endpoints +\`\`\` +GET /api/users +POST /api/users +PUT /api/users/:id +DELETE /api/users/:id +\`\`\` + +## 🔗 Links +- [API Documentation](docs-url) +- [GitHub Repository](github-url) + +## 📈 Future Improvements +- Feature 1 +- Feature 2 +- Feature 3`, + difficulty: 'Intermediate' as const, + category: 'API Development', + technologies: 'Node.js, Express, MongoDB', + colorScheme: 'Professional and Clean' + } + }; + + const applyTemplate = (templateKey: string) => { + const template = projectTemplates[templateKey as keyof typeof projectTemplates]; + if (template) { + setFormData({ + ...formData, + title: template.title, + description: template.description, + category: template.category, + difficulty: template.difficulty, + technologies: template.technologies, + colorScheme: template.colorScheme + }); + setMarkdownContent(template.content); + setShowTemplates(false); + } + }; + return (
@@ -466,6 +764,17 @@ const AdminPage = () => { {selectedProject ? 'Edit Project' : 'New Project'}
+
+ {/* Project Templates Modal */} + {showTemplates && ( + setShowTemplates(false)} + > + e.stopPropagation()} + > +
+

🚀 Project Templates

+ +
+ +
+ {Object.entries(projectTemplates).map(([key, template]) => ( + applyTemplate(key)} + > +
+
+ +
+

{template.title}

+

{template.description}

+
+ +
+
+ Difficulty: + + {template.difficulty} + +
+
+ Category: + {template.category} +
+
+ Tech: + {template.technologies} +
+
+ + +
+ ))} +
+ +
+

+ Templates provide a starting point for your projects. Customize them as needed! +

+
+
+
+ )} + {!isPreview ? (
{/* Basic Info */} @@ -604,6 +993,214 @@ const AdminPage = () => {
+ {/* Advanced Project Details - 2.0 Features */} +
+

+ + Advanced Project Details +

+ + {/* Difficulty & Time */} +
+
+ + +
+ +
+ + setFormData({...formData, timeToComplete: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g., 2-3 weeks, 1 month" + /> +
+
+ + {/* Technologies & Color Scheme */} +
+
+ + setFormData({...formData, technologies: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="React, TypeScript, Node.js" + /> +
+ +
+ + setFormData({...formData, colorScheme: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g., Dark theme, Light theme, Colorful" + /> +
+
+ + {/* Performance Metrics */} +
+
+ + setFormData({ + ...formData, + performance: {...formData.performance, lighthouse: parseInt(e.target.value)} + })} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="90" + /> +
+ +
+ + setFormData({ + ...formData, + performance: {...formData.performance, bundleSize: e.target.value} + })} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="45KB" + /> +
+ +
+ + setFormData({ + ...formData, + performance: {...formData.performance, loadTime: e.target.value} + })} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="1.2s" + /> +
+
+ + {/* Learning & Challenges */} +
+
+ +