diff --git a/DEV-SETUP.md b/DEV-SETUP.md new file mode 100644 index 0000000..1badacc --- /dev/null +++ b/DEV-SETUP.md @@ -0,0 +1,239 @@ +# 🚀 Development Environment Setup + +This document explains how to set up and use the development environment for the portfolio project. + +## ✨ Features + +- **Automatic Database Setup**: PostgreSQL and Redis start automatically +- **Hot Reload**: Next.js development server with hot reload +- **Database Integration**: Real database integration for email management +- **Modern Admin Dashboard**: Completely redesigned admin interface +- **Minimal Setup**: Only essential services for fast development + +## 🛠️ Quick Start + +### Prerequisites + +- Node.js 18+ +- Docker & Docker Compose +- npm or yarn + +### 1. Install Dependencies + +```bash +npm install +``` + +### 2. Start Development Environment + +#### Option A: Full Development Environment (with Docker) +```bash +npm run dev +``` + +This single command will: +- Start PostgreSQL database +- Start Redis cache +- Start Next.js development server +- Set up all environment variables + +#### Option B: Simple Development Mode (without Docker) +```bash +npm run dev:simple +``` + +This starts only the Next.js development server without Docker services. Use this if you don't have Docker installed or want a faster startup. + +### 3. Access Services + +- **Portfolio**: http://localhost:3000 +- **Admin Dashboard**: http://localhost:3000/admin +- **PostgreSQL**: localhost:5432 +- **Redis**: localhost:6379 + +## 📧 Email Testing + +The development environment supports email functionality: + +1. Send emails through the contact form or admin panel +2. Emails are sent directly (configure SMTP in production) +3. Check console logs for email debugging + +## 🗄️ Database + +### Development Database + +- **Host**: localhost:5432 +- **Database**: portfolio_dev +- **User**: portfolio_user +- **Password**: portfolio_dev_pass + +### Database Commands + +```bash +# Generate Prisma client +npm run db:generate + +# Push schema changes +npm run db:push + +# Seed database with sample data +npm run db:seed + +# Open Prisma Studio +npm run db:studio + +# Reset database +npm run db:reset +``` + +## 🎨 Admin Dashboard + +The new admin dashboard includes: + +- **Overview**: Statistics and recent activity +- **Projects**: Manage portfolio projects +- **Emails**: Handle contact form submissions with beautiful templates +- **Analytics**: View performance metrics +- **Settings**: Import/export functionality + +### Email Templates + +Three beautiful email templates are available: + +1. **Welcome Template** (Green): Friendly greeting with portfolio links +2. **Project Template** (Purple): Professional project discussion response +3. **Quick Template** (Orange): Fast acknowledgment response + +## 🔧 Environment Variables + +Create a `.env.local` file: + +```env +# Development Database +DATABASE_URL="postgresql://portfolio_user:portfolio_dev_pass@localhost:5432/portfolio_dev?schema=public" + +# Redis +REDIS_URL="redis://localhost:6379" + +# Email (for production) +MY_EMAIL=contact@dk0.dev +MY_PASSWORD=your-email-password + +# Application +NEXT_PUBLIC_BASE_URL=http://localhost:3000 +NODE_ENV=development +``` + +## 🛑 Stopping the Environment + +Use Ctrl+C to stop all services, or: + +```bash +# Stop Docker services only +npm run docker:dev:down +``` + +## 🐳 Docker Commands + +```bash +# Start only database services +npm run docker:dev + +# Stop database services +npm run docker:dev:down + +# View logs +docker compose -f docker-compose.dev.minimal.yml logs -f +``` + +## 📁 Project Structure + +``` +├── docker-compose.dev.minimal.yml # Minimal development services +├── scripts/ +│ ├── dev-minimal.js # Main development script +│ ├── dev-simple.js # Simple development script +│ ├── setup-database.js # Database setup script +│ └── init-db.sql # Database initialization +├── app/ +│ ├── admin/ # Admin dashboard +│ ├── api/ +│ │ ├── contacts/ # Contact management API +│ │ └── email/ # Email sending API +│ └── components/ +│ ├── ModernAdminDashboard.tsx +│ ├── EmailManager.tsx +│ └── EmailResponder.tsx +└── prisma/ + └── schema.prisma # Database schema +``` + +## 🚨 Troubleshooting + +### Docker Compose Not Found + +If you get the error `spawn docker compose ENOENT`: + +```bash +# Try the simple dev mode instead +npm run dev:simple + +# Or install Docker Desktop +# Download from: https://www.docker.com/products/docker-desktop +``` + +### Port Conflicts + +If ports are already in use: + +```bash +# Check what's using the ports +lsof -i :3000 +lsof -i :5432 +lsof -i :6379 + +# Kill processes if needed +kill -9 +``` + +### Database Connection Issues + +```bash +# Restart database services +npm run docker:dev:down +npm run docker:dev + +# Check database status +docker compose -f docker-compose.dev.minimal.yml ps +``` + +### Email Not Working + +1. Verify environment variables +2. Check browser console for errors +3. Ensure SMTP is configured for production + +## 🎯 Production Deployment + +For production deployment, use: + +```bash +npm run build +npm run start +``` + +The production environment uses the production Docker Compose configuration. + +## 📝 Notes + +- The development environment automatically creates sample data +- Database changes are persisted in Docker volumes +- Hot reload works for all components and API routes +- Minimal setup for fast development startup + +## 🔗 Links + +- **Portfolio**: https://dk0.dev +- **Admin**: https://dk0.dev/admin +- **GitHub**: https://github.com/denniskonkol/portfolio diff --git a/README-DATABASE.md b/README-DATABASE.md deleted file mode 100644 index ecb8b43..0000000 --- a/README-DATABASE.md +++ /dev/null @@ -1,228 +0,0 @@ -# 🗄️ 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/README.md b/README.md index 4732bc0..a37651f 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,58 @@ # Dennis Konkol Portfolio - Modern Dark Theme -Ein modernes, responsives Portfolio mit dunklem Design, coolen Animationen und einem integrierten Markdown-Editor. +Ein modernes, responsives Portfolio mit dunklem Design, coolen Animationen und einem integrierten Admin-Dashboard. -## Features +## ✨ Features - **Dunkles Theme** mit Glassmorphism-Effekten - **Responsive Design** für alle Geräte - **Smooth Animationen** mit Framer Motion -- **Markdown-Editor** für Projekte - **Admin Dashboard** für Content-Management +- **E-Mail-System** mit schönen Templates +- **Analytics Dashboard** mit Performance-Metriken +- **Redis Caching** für optimale Performance -## Technologien +## 🛠️ Technologien -- Next.js 15 mit App Router -- TypeScript für Type Safety -- Tailwind CSS für Styling -- Framer Motion für Animationen -- React Markdown für Content +- **Frontend**: Next.js 15, TypeScript, Tailwind CSS, Framer Motion +- **Backend**: PostgreSQL, Redis, Prisma ORM +- **Deployment**: Docker, Nginx +- **Analytics**: Umami Analytics -## Installation +## 🚀 Quick Start +```bash +# Dependencies installieren npm install + +# Development Environment starten npm run dev +``` -## Verwendung +## 📁 Verfügbare Scripts -- `/` - Homepage -- `/projects` - Alle Projekte -- `/admin` - Admin Dashboard mit Markdown-Editor +```bash +npm run dev # Vollständiges Dev-Environment (Docker + Next.js) +npm run dev:simple # Nur Next.js (ohne Docker) +npm run build # Production Build +npm run start # Production Server +``` + +## 🌐 URLs + +- **Portfolio**: http://localhost:3000 +- **Admin Dashboard**: http://localhost:3000/admin +- **PostgreSQL**: localhost:5432 +- **Redis**: localhost:6379 + +## 📖 Dokumentation + +- [Development Setup](DEV-SETUP.md) - Detaillierte Setup-Anleitung +- [Deployment Guide](DEPLOYMENT.md) - Production Deployment +- [Analytics](ANALYTICS.md) - Analytics und Performance + +## 🔗 Links + +- **Live Portfolio**: https://dk0.dev +- **Admin Dashboard**: https://dk0.dev/admin +- **GitHub**: https://github.com/denniskonkol/portfolio diff --git a/app/__tests__/api/email.test.tsx b/app/__tests__/api/email.test.tsx index 833cbbb..afc1d48 100644 --- a/app/__tests__/api/email.test.tsx +++ b/app/__tests__/api/email.test.tsx @@ -18,7 +18,7 @@ afterAll(() => { beforeEach(() => { nodemailermock.mock.reset(); - process.env.MY_EMAIL = 'test@dki.one'; + process.env.MY_EMAIL = 'test@dk0.dev'; process.env.MY_PASSWORD = 'test-password'; }); @@ -42,7 +42,7 @@ describe('POST /api/email', () => { const sentEmails = nodemailermock.mock.getSentMail(); expect(sentEmails.length).toBe(1); - expect(sentEmails[0].to).toBe('contact@dki.one'); + expect(sentEmails[0].to).toBe('contact@dk0.dev'); expect(sentEmails[0].text).toContain('Hello! This is a test message.'); }); diff --git a/app/__tests__/components/Header.test.tsx b/app/__tests__/components/Header.test.tsx index 7b7fc48..e9c1108 100644 --- a/app/__tests__/components/Header.test.tsx +++ b/app/__tests__/components/Header.test.tsx @@ -5,7 +5,8 @@ import '@testing-library/jest-dom'; describe('Header', () => { it('renders the header', () => { render(
); - expect(screen.getByText('DK')).toBeInTheDocument(); + expect(screen.getByText('dk')).toBeInTheDocument(); + expect(screen.getByText('0')).toBeInTheDocument(); const aboutButtons = screen.getAllByText('About'); expect(aboutButtons.length).toBeGreaterThan(0); diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 24a9200..bac1b11 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,1577 +1,9 @@ "use client"; -import { useState, useEffect } from 'react'; -import { motion } from 'framer-motion'; -import Image from 'next/image'; -import { - Save, - Eye, - Plus, - Edit, - Trash2, - Upload, - Bold, - Italic, - List, - Link as LinkIcon, - Image as ImageIcon, - Code, - Quote, - ArrowLeft, - ChevronDown, - ChevronRight, - Palette, - Smile, - FileText, - Settings, - TrendingUp -} 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: Record) { - 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: Record) { - 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 ImportExport from '@/components/ImportExport'; -import AnalyticsDashboard from '@/components/AnalyticsDashboard'; -import { useToast } from '@/components/Toast'; - -interface Project { - id: number; - title: string; - description: string; - content: string; - tags: string[]; - featured: boolean; - category: string; - date: string; - github?: string; - live?: string; - published: boolean; - imageUrl?: string; - metaDescription?: string; - keywords?: string; - ogImage?: string; - 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; - }; -} +import ModernAdminDashboard from '@/components/ModernAdminDashboard'; const AdminPage = () => { - const [mounted, setMounted] = useState(false); - const { showProjectSaved, showProjectDeleted, showError, showWarning } = useToast(); - - useEffect(() => { - setMounted(true); - }, []); - - const [projects, setProjects] = useState([]); - - // Load projects from database on mount - useEffect(() => { - 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 [showImportExport, setShowImportExport] = useState(false); - const [showAnalytics, setShowAnalytics] = useState(false); - const [formData, setFormData] = useState({ - title: '', - description: '', - content: '', - tags: '', - category: '', - featured: false, - github: '', - live: '', - published: true, - 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(''); - - const categories = [ - "Web Development", - "Full-Stack", - "Web Application", - "Mobile App", - "Desktop App", - "API Development", - "Database Design", - "DevOps", - "UI/UX Design", - "Game Development", - "Machine Learning", - "Data Science", - "Blockchain", - "IoT", - "Cybersecurity" - ]; - - if (!mounted) { - return null; - } - - const handleSave = async () => { - if (!formData.title || !formData.description || !markdownContent || !formData.category) { - showWarning('Pflichtfelder fehlen', 'Bitte fülle alle erforderlichen Felder aus.'); - return; - } - - try { - if (selectedProject) { - // Update existing 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, - 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.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(); - } catch (error) { - console.error('Error saving project:', error); - showError('Fehler beim Speichern', 'Das Projekt konnte nicht gespeichert werden. Bitte versuche es erneut.'); - } - }; - - const handleEdit = (project: Project) => { - console.log('Editing project:', project); - setSelectedProject(project); - setFormData({ - title: project.title, - description: project.description, - content: project.content, - tags: project.tags.join(', '), - category: project.category, - featured: project.featured, - github: project.github || '', - live: project.live || '', - published: project.published !== undefined ? project.published : true, - 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 = async (projectId: number) => { - if (confirm('Are you sure you want to delete this project?')) { - 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.'); - } - } - }; - - const resetForm = () => { - console.log('Resetting form'); - setSelectedProject(null); - setFormData({ - title: '', - description: '', - content: '', - tags: '', - category: '', - featured: false, - github: '', - live: '', - published: true, - 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); - }; - - const insertMarkdown = (type: string) => { - const textarea = document.getElementById('markdown-editor') as HTMLTextAreaElement; - if (!textarea) return; - - const start = textarea.selectionStart; - const end = textarea.selectionEnd; - const text = textarea.value; - - let insertion = ''; - let cursorOffset = 0; - - switch (type) { - case 'h1': - insertion = `# ${text.substring(start, end) || 'Heading'}`; - cursorOffset = 2; - break; - case 'h2': - insertion = `## ${text.substring(start, end) || 'Heading'}`; - cursorOffset = 3; - break; - case 'bold': - insertion = `**${text.substring(start, end) || 'bold text'}**`; - cursorOffset = 2; - break; - case 'italic': - insertion = `*${text.substring(start, end) || 'italic text'}*`; - cursorOffset = 1; - break; - case 'list': - insertion = `- ${text.substring(start, end) || 'list item'}`; - cursorOffset = 2; - break; - case 'link': - insertion = `[${text.substring(start, end) || 'link text'}](url)`; - cursorOffset = 3; - break; - case 'image': - insertion = `![alt text](image-url)`; - cursorOffset = 9; - break; - case 'code': - insertion = `\`${text.substring(start, end) || 'code'}\``; - cursorOffset = 1; - break; - case 'quote': - insertion = `> ${text.substring(start, end) || 'quote text'}`; - cursorOffset = 2; - break; - case 'table': - 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); - setMarkdownContent(newText); - - // Set cursor position and select the placeholder text for easy editing - setTimeout(() => { - textarea.focus(); - if (type === 'h1' || type === 'h2') { - // For headings, select the placeholder text so user can type directly - const placeholderStart = start + (type === 'h1' ? 2 : 3); - const placeholderEnd = start + insertion.length; - textarea.setSelectionRange(placeholderStart, placeholderEnd); - } else { - // For other elements, position cursor appropriately - textarea.setSelectionRange(start + insertion.length - cursorOffset, start + insertion.length - cursorOffset); - } - }, 0); - }; - - const handleProjectImageUpload = (e: React.ChangeEvent) => { - const files = e.target.files; - if (!files || files.length === 0) return; - - const file = files[0]; - if (file) { - // Simulate image upload - in production you'd upload to a real service - const imageUrl = URL.createObjectURL(file); - setFormData(prev => ({ ...prev, imageUrl })); - } - }; - - const handleImageUpload = (e: React.ChangeEvent) => { - const files = e.target.files; - if (!files || files.length === 0) return; - - const file = files[0]; - if (file) { - // Create a more descriptive image URL for better organization - const imageName = file.name.replace(/\.[^/.]+$/, ""); // Remove file extension - const imageUrl = URL.createObjectURL(file); - - const textarea = document.getElementById('markdown-editor') as HTMLTextAreaElement; - if (!textarea) return; - - const start = textarea.selectionStart; - const text = textarea.value; - - // Insert image with better alt text and a newline for spacing - const insertion = `\n![${imageName}](${imageUrl})\n`; - - const newText = text.substring(0, start) + insertion + text.substring(start); - setMarkdownContent(newText); - - // Focus back to textarea and position cursor after the image - setTimeout(() => { - textarea.focus(); - const newCursorPos = start + insertion.length; - textarea.setSelectionRange(newCursorPos, newCursorPos); - }, 0); - } - }; - - // 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 ( -
-
- {/* Header */} - - - - Back to Home - - -

- Admin Dashboard -

-

- Manage your projects with the built-in Markdown editor. Create, edit, and preview your content easily. -

-
- - {/* Control Buttons */} -
- setIsProjectsCollapsed(!isProjectsCollapsed)} - className="flex items-center space-x-2 px-6 py-3 bg-gradient-to-r from-gray-700 to-gray-800 hover:from-gray-600 hover:to-gray-700 rounded-xl text-white transition-all duration-200 hover:scale-105 border border-gray-600/50 shadow-lg" - title={isProjectsCollapsed ? "Show Projects" : "Hide Projects"} - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.95 }} - > - {isProjectsCollapsed ? ( - <> - - Show Projects - - ) : ( - <> - - Hide Projects - - )} - - - setShowImportExport(!showImportExport)} - className="flex items-center space-x-2 px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-500 hover:to-blue-600 rounded-xl text-white transition-all duration-200 hover:scale-105 border border-blue-500/50 shadow-lg" - title="Import & Export Projects" - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.95 }} - > - - Import/Export - - - setShowAnalytics(!showAnalytics)} - className="flex items-center space-x-2 px-6 py-3 bg-gradient-to-r from-green-600 to-green-700 hover:from-green-500 hover:to-green-600 rounded-xl text-white transition-all duration-200 hover:scale-105 border border-green-500/50 shadow-lg" - title="Analytics Dashboard" - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.95 }} - > - - Analytics - -
- - {/* Import/Export Section */} - {showImportExport && ( - - - - )} - - {/* Analytics Section */} - {showAnalytics && ( - - - - )} - -
- {/* Projects List */} -
- -
-

Projects

- -
- -
- {projects.map((project) => ( -
handleEdit(project)} - > -

{project.title}

-

{project.description}

-
- {project.category} -
- - -
-
-
- ))} -
-
-
- - {/* Editor */} -
- -
-

- {selectedProject ? 'Edit Project' : 'New Project'} -

-
- - - - {selectedProject && ( - - )} -
-
- - {/* 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 */} -
-
- - setFormData({...formData, title: 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="Project title" - /> -
- -
- - -
-
- - {/* Links */} -
-
- - setFormData({...formData, github: 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="https://github.com/username/repo" - /> -
- -
- - setFormData({...formData, live: 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="https://demo.example.com" - /> -
-
- - {/* Project Image */} -
- -
-
- {formData.imageUrl ? ( - Project preview - ) : ( -
- - {formData.title ? formData.title.split(' ').map(word => word[0]).join('').toUpperCase() : 'P'} - -
- )} -
-
- - -

Upload a project image or use auto-generated initials

-
-
-
- - {/* 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 */} -
-
- -