Files
portfolio/app/admin/page.tsx
Dennis Konkol 9835bb810d 🚀 Complete Production Setup
 Features:
- Analytics Dashboard with real-time metrics
- Redis caching for performance optimization
- Import/Export functionality for projects
- Complete admin system with security
- Production-ready Docker setup

🔧 Technical:
- Removed Ghost CMS dependencies
- Added Redis container with caching
- Implemented API response caching
- Enhanced admin interface with analytics
- Optimized for dk0.dev domain

🛡️ Security:
- Admin authentication with Basic Auth
- Protected analytics endpoints
- Secure environment configuration

📊 Analytics:
- Performance metrics dashboard
- Project statistics visualization
- Real-time data with caching
- Umami integration for GDPR compliance

🎯 Production Ready:
- Multi-container Docker setup
- Health checks for all services
- Automatic restart policies
- Resource limits configured
- Ready for Nginx Proxy Manager
2025-09-05 21:35:54 +00:00

1578 lines
72 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
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,
Database,
BarChart3,
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: 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 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<string, unknown>;
// 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);
}, []);
const [projects, setProjects] = useState<Project[]>([]);
// 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<Project | null>(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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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 (
<div className="min-h-screen animated-bg">
<div className="max-w-7xl mx-auto px-4 py-20">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
className="mb-12"
>
<Link
href="/"
className="inline-flex items-center space-x-2 text-blue-400 hover:text-blue-300 transition-colors mb-6"
>
<ArrowLeft size={20} />
<span>Back to Home</span>
</Link>
<h1 className="text-5xl md:text-6xl font-bold mb-6 gradient-text">
Admin Dashboard
</h1>
<p className="text-xl text-gray-400 max-w-3xl">
Manage your projects with the built-in Markdown editor. Create, edit, and preview your content easily.
</p>
</motion.div>
{/* Control Buttons */}
<div className="flex justify-center gap-4 mb-6">
<motion.button
onClick={() => 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 ? (
<>
<ChevronRight size={20} />
<span>Show Projects</span>
</>
) : (
<>
<ChevronDown size={20} />
<span>Hide Projects</span>
</>
)}
</motion.button>
<motion.button
onClick={() => 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 }}
>
<FileText size={20} />
<span>Import/Export</span>
</motion.button>
<motion.button
onClick={() => 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 }}
>
<TrendingUp size={20} />
<span>Analytics</span>
</motion.button>
</div>
{/* Import/Export Section */}
{showImportExport && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="mb-8"
>
<ImportExport />
</motion.div>
)}
{/* Analytics Section */}
{showAnalytics && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="mb-8"
>
<AnalyticsDashboard />
</motion.div>
)}
<div className={`grid gap-8 ${isProjectsCollapsed ? 'grid-cols-1' : 'grid-cols-1 lg:grid-cols-3'}`}>
{/* Projects List */}
<div className={`${isProjectsCollapsed ? 'hidden' : 'lg:col-span-1'}`}>
<motion.div
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
className="glass-card p-6 rounded-2xl"
>
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-white">Projects</h2>
<button
onClick={resetForm}
className="p-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white transition-colors"
>
<Plus size={20} />
</button>
</div>
<div className="space-y-3">
{projects.map((project) => (
<div
key={project.id}
className={`p-3 rounded-lg cursor-pointer transition-all ${
selectedProject?.id === project.id
? 'bg-blue-600/20 border border-blue-500/50'
: 'bg-gray-800/30 hover:bg-gray-700/30'
}`}
onClick={() => handleEdit(project)}
>
<h3 className="font-medium text-white mb-1">{project.title}</h3>
<p className="text-sm text-gray-400">{project.description}</p>
<div className="flex items-center justify-between mt-2">
<span className="text-xs text-gray-500">{project.category}</span>
<div className="flex space-x-2">
<button
onClick={(e) => {
e.stopPropagation();
handleEdit(project);
}}
className="p-1 text-gray-400 hover:text-blue-400 transition-colors"
>
<Edit size={16} />
</button>
<button
onClick={(e) => {
e.stopPropagation();
handleDelete(project.id);
}}
className="p-1 text-gray-400 hover:text-red-400 transition-colors"
>
<Trash2 size={16} />
</button>
</div>
</div>
</div>
))}
</div>
</motion.div>
</div>
{/* Editor */}
<div className={`${isProjectsCollapsed ? 'lg:col-span-1' : 'lg:col-span-2'}`}>
<motion.div
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
className="glass-card p-6 rounded-2xl"
>
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-white">
{selectedProject ? 'Edit Project' : 'New Project'}
</h2>
<div className="flex space-x-3">
<button
onClick={() => setShowTemplates(!showTemplates)}
className={`px-4 py-2 rounded-lg transition-colors ${
showTemplates
? 'bg-purple-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
title="Project Templates"
>
<FileText size={20} />
</button>
<button
onClick={() => setIsPreview(!isPreview)}
className={`px-4 py-2 rounded-lg transition-colors ${
isPreview
? 'bg-blue-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
title="Toggle Preview"
>
<Eye size={20} />
</button>
<button
onClick={handleSave}
className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors flex items-center space-x-2"
title="Save Project"
>
<Save size={20} />
<span>Save</span>
</button>
{selectedProject && (
<button
onClick={() => {
setSelectedProject(null);
resetForm();
}}
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
title="Cancel Edit"
>
Cancel
</button>
)}
</div>
</div>
{/* Project Templates Modal */}
{showTemplates && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
onClick={() => setShowTemplates(false)}
>
<motion.div
initial={{ y: 20 }}
animate={{ y: 0 }}
className="bg-gray-800 rounded-2xl p-6 max-w-4xl w-full max-h-[80vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-6">
<h3 className="text-2xl font-bold text-white">🚀 Project Templates</h3>
<button
onClick={() => setShowTemplates(false)}
className="text-gray-400 hover:text-white transition-colors"
>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{Object.entries(projectTemplates).map(([key, template]) => (
<motion.div
key={key}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="bg-gray-700/50 rounded-xl p-6 border border-gray-600/50 hover:border-blue-500/50 transition-all cursor-pointer"
onClick={() => applyTemplate(key)}
>
<div className="text-center mb-4">
<div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-500 rounded-full flex items-center justify-center mx-auto mb-3">
<FileText size={24} className="text-white" />
</div>
<h4 className="text-lg font-semibold text-white mb-2">{template.title}</h4>
<p className="text-sm text-gray-400">{template.description}</p>
</div>
<div className="space-y-2 text-sm">
<div className="flex items-center justify-between">
<span className="text-gray-400">Difficulty:</span>
<span className={`px-2 py-1 rounded text-xs font-medium ${
template.difficulty === 'Beginner' ? 'bg-green-500/20 text-green-400' :
template.difficulty === 'Intermediate' ? 'bg-yellow-500/20 text-yellow-400' :
template.difficulty === 'Advanced' ? 'bg-orange-500/20 text-orange-400' :
'bg-red-500/20 text-red-400'
}`}>
{template.difficulty}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-400">Category:</span>
<span className="text-white">{template.category}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-400">Tech:</span>
<span className="text-white text-xs">{template.technologies}</span>
</div>
</div>
<button className="w-full mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
Use Template
</button>
</motion.div>
))}
</div>
<div className="mt-6 text-center">
<p className="text-sm text-gray-400">
Templates provide a starting point for your projects. Customize them as needed!
</p>
</div>
</motion.div>
</motion.div>
)}
{!isPreview ? (
<div className="space-y-6">
{/* Basic Info */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Title
</label>
<input
type="text"
value={formData.title}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Category
</label>
<select
value={formData.category}
onChange={(e) => setFormData({...formData, category: 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"
>
<option value="">Select category</option>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
</div>
</div>
{/* Links */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
GitHub URL (optional)
</label>
<input
type="url"
value={formData.github || ''}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Live Demo URL (optional)
</label>
<input
type="url"
value={formData.live || ''}
onChange={(e) => 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"
/>
</div>
</div>
{/* Project Image */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Project Image (optional)
</label>
<div className="flex items-center space-x-4">
<div className="w-24 h-24 bg-gradient-to-br from-gray-700 to-gray-800 rounded-xl border-2 border-dashed border-gray-600 flex items-center justify-center overflow-hidden">
{formData.imageUrl ? (
<img
src={formData.imageUrl}
alt="Project preview"
className="w-full h-full object-cover rounded-lg"
/>
) : (
<div className="text-center">
<span className="text-2xl font-bold text-white">
{formData.title ? formData.title.split(' ').map(word => word[0]).join('').toUpperCase() : 'P'}
</span>
</div>
)}
</div>
<div className="flex-1">
<input
type="file"
accept="image/*"
onChange={handleProjectImageUpload}
className="hidden"
id="project-image-upload"
/>
<label
htmlFor="project-image-upload"
className="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white cursor-pointer transition-colors"
>
<Upload size={16} className="mr-2" />
Choose Image
</label>
<p className="text-sm text-gray-400 mt-1">Upload a project image or use auto-generated initials</p>
</div>
</div>
</div>
{/* Advanced Project Details - 2.0 Features */}
<div className="bg-gradient-to-r from-gray-800/20 to-gray-700/20 p-6 rounded-xl border border-gray-600/30">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
<Settings size={20} className="mr-2 text-blue-400" />
Advanced Project Details
</h3>
{/* Difficulty & Time */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Difficulty Level
</label>
<select
value={formData.difficulty}
onChange={(e) => setFormData({...formData, difficulty: e.target.value as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert'})}
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"
>
<option value="Beginner">🟢 Beginner</option>
<option value="Intermediate">🟡 Intermediate</option>
<option value="Advanced">🟠 Advanced</option>
<option value="Expert">🔴 Expert</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Time to Complete
</label>
<input
type="text"
value={formData.timeToComplete}
onChange={(e) => 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"
/>
</div>
</div>
{/* Technologies & Color Scheme */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Technologies (comma-separated)
</label>
<input
type="text"
value={formData.technologies}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Color Scheme
</label>
<input
type="text"
value={formData.colorScheme}
onChange={(e) => 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"
/>
</div>
</div>
{/* Performance Metrics */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Lighthouse Score
</label>
<input
type="number"
min="0"
max="100"
value={formData.performance.lighthouse}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Bundle Size
</label>
<input
type="text"
value={formData.performance.bundleSize}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Load Time
</label>
<input
type="text"
value={formData.performance.loadTime}
onChange={(e) => 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"
/>
</div>
</div>
{/* Learning & Challenges */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Challenges Faced (comma-separated)
</label>
<textarea
value={formData.challenges}
onChange={(e) => setFormData({...formData, challenges: e.target.value})}
rows={3}
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 resize-none"
placeholder="Performance optimization, Responsive design, State management"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Lessons Learned (comma-separated)
</label>
<textarea
value={formData.lessonsLearned}
onChange={(e) => setFormData({...formData, lessonsLearned: e.target.value})}
rows={3}
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 resize-none"
placeholder="Advanced CSS techniques, Performance optimization, User experience design"
/>
</div>
</div>
{/* Future Improvements & Demo */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Future Improvements (comma-separated)
</label>
<textarea
value={formData.futureImprovements}
onChange={(e) => setFormData({...formData, futureImprovements: e.target.value})}
rows={3}
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 resize-none"
placeholder="AI integration, Advanced analytics, Real-time features"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Demo Video URL (optional)
</label>
<input
type="url"
value={formData.demoVideo}
onChange={(e) => setFormData({...formData, demoVideo: 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://youtube.com/watch?v=..."
/>
</div>
</div>
{/* Accessibility & Screenshots */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-center space-x-3">
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={formData.accessibility}
onChange={(e) => setFormData({...formData, accessibility: e.target.checked})}
className="w-4 h-4 text-blue-600 bg-gray-800 border-gray-700 rounded focus:ring-blue-500 focus:ring-2"
/>
<span className="text-sm text-gray-300">Accessibility Compliant</span>
</label>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Screenshots URLs (comma-separated)
</label>
<input
type="text"
value={formData.screenshots}
onChange={(e) => setFormData({...formData, screenshots: 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://example.com/screenshot1.jpg, https://example.com/screenshot2.jpg"
/>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Description
</label>
<textarea
value={formData.description}
onChange={(e) => setFormData({...formData, description: e.target.value})}
rows={3}
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 resize-none"
placeholder="Brief project description"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Tags (comma-separated)
</label>
<input
type="text"
value={formData.tags}
onChange={(e) => setFormData({...formData, tags: 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="Next.js, TypeScript, Tailwind CSS"
/>
</div>
<div className="flex items-center justify-between">
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={formData.featured}
onChange={(e) => setFormData({...formData, featured: e.target.checked})}
className="w-4 h-4 text-blue-600 bg-gray-800 border-gray-700 rounded focus:ring-blue-500 focus:ring-2"
/>
<span className="text-sm text-gray-300">Featured Project</span>
</label>
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={formData.published}
onChange={(e) => setFormData({...formData, published: e.target.checked})}
className="w-4 h-4 text-green-600 bg-gray-800 border-gray-700 rounded focus:ring-green-500 focus:ring-2"
/>
<span className="text-sm text-gray-300">Published</span>
</label>
</div>
{/* Markdown Editor with Live Preview */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Content (Markdown)
</label>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Editor */}
<div className="space-y-4">
{/* Image Upload - Moved to top */}
<div className="bg-gradient-to-r from-gray-800/30 to-gray-700/30 p-4 rounded-xl border border-gray-600/30 mb-4">
<div className="flex items-center justify-between mb-3">
<span className="text-sm font-medium text-gray-300">📁 Image Upload</span>
<span className="text-xs text-gray-500">Add images to your content</span>
</div>
<div
className="relative"
onDragOver={(e) => {
e.preventDefault();
e.currentTarget.classList.add('border-blue-500', 'bg-blue-500/10');
}}
onDragLeave={(e) => {
e.currentTarget.classList.remove('border-blue-500', 'bg-blue-500/10');
}}
onDrop={(e) => {
e.preventDefault();
e.currentTarget.classList.remove('border-blue-500', 'bg-blue-500/10');
const files = Array.from(e.dataTransfer.files);
files.forEach(file => {
if (file.type.startsWith('image/')) {
const imageUrl = URL.createObjectURL(file);
const textarea = document.getElementById('markdown-editor') as HTMLTextAreaElement;
if (textarea) {
const start = textarea.selectionStart;
const text = textarea.value;
const insertion = `\n![${file.name.replace(/\.[^/.]+$/, "")}](${imageUrl})\n`;
const newText = text.substring(0, start) + insertion + text.substring(start);
setMarkdownContent(newText);
}
}
});
}}
>
<label className="flex items-center justify-center space-x-3 p-4 bg-gray-700/50 hover:bg-gray-600/50 rounded-lg cursor-pointer transition-all duration-200 hover:scale-105 border-2 border-dashed border-gray-600/50 hover:border-blue-500/50 group">
<Upload size={20} className="text-gray-400 group-hover:text-blue-400 transition-colors" />
<span className="text-gray-300 font-medium group-hover:text-blue-400 transition-colors">Upload Images</span>
<input
type="file"
accept="image/*"
multiple
onChange={handleImageUpload}
className="hidden"
/>
</label>
</div>
<p className="text-xs text-gray-500 text-center mt-2">
🚀 Drag & drop images here or click to browse Images will be inserted at cursor position
</p>
</div>
{/* Enhanced Toolbar */}
<div className="bg-gradient-to-r from-gray-800/30 to-gray-700/30 p-4 rounded-xl border border-gray-600/30">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-300">Quick Actions</span>
<span className="text-xs text-gray-500">Click to insert</span>
</div>
{/* Text Formatting */}
<div className="mb-4">
<div className="text-xs text-gray-500 mb-2 uppercase tracking-wide">Text Formatting</div>
<div className="grid grid-cols-6 gap-2">
<button
onClick={() => insertMarkdown('h1')}
className="p-3 bg-gradient-to-br from-blue-600/50 to-blue-700/50 hover:from-blue-500/60 hover:to-blue-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-blue-500/50 shadow-lg"
title="Heading 1"
>
<span className="text-sm font-bold">H1</span>
</button>
<button
onClick={() => insertMarkdown('h2')}
className="p-3 bg-gradient-to-br from-purple-600/50 to-purple-700/50 hover:from-purple-500/60 hover:to-purple-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-purple-500/50 shadow-lg"
title="Heading 2"
>
<span className="text-sm font-bold">H2</span>
</button>
<button
onClick={() => insertMarkdown('bold')}
className="p-3 bg-gradient-to-br from-gray-700/50 to-gray-800/50 hover:from-gray-600/60 hover:to-gray-700/60 rounded-lg text-gray-300 hover:text-white transition-all duration-200 hover:scale-105 border border-gray-600/50 shadow-lg"
title="Bold"
>
<Bold size={16} />
</button>
<button
onClick={() => insertMarkdown('italic')}
className="p-3 bg-gradient-to-br from-gray-700/50 to-gray-800/50 hover:from-gray-600/60 hover:to-gray-700/60 rounded-lg text-gray-300 hover:text-white transition-all duration-200 hover:scale-105 border border-gray-600/50 shadow-lg"
title="Italic"
>
<Italic size={16} />
</button>
<button
onClick={() => insertMarkdown('code')}
className="p-3 bg-gradient-to-br from-gray-700/50 to-gray-800/50 hover:from-gray-600/60 hover:to-gray-700/60 rounded-lg text-gray-300 hover:text-white transition-all duration-200 hover:scale-105 border border-gray-600/50 shadow-lg"
title="Inline Code"
>
<Code size={16} />
</button>
<button
onClick={() => insertMarkdown('quote')}
className="p-3 bg-gradient-to-br from-gray-700/50 to-gray-800/50 hover:from-gray-600/60 hover:to-gray-700/60 rounded-lg text-gray-300 hover:text-white transition-all duration-200 hover:scale-105 border border-gray-600/50 shadow-lg"
title="Quote"
>
<Quote size={16} />
</button>
</div>
</div>
{/* Content Elements */}
<div className="mb-4">
<div className="text-xs text-gray-500 mb-2 uppercase tracking-wide">Content Elements</div>
<div className="grid grid-cols-5 gap-2">
<button
onClick={() => insertMarkdown('list')}
className="p-3 bg-gradient-to-br from-green-600/50 to-green-700/50 hover:from-green-500/60 hover:to-green-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-green-500/50 shadow-lg"
title="List Item"
>
<List size={16} />
</button>
<button
onClick={() => insertMarkdown('link')}
className="p-3 bg-gradient-to-br from-blue-600/50 to-blue-700/50 hover:from-blue-500/60 hover:to-blue-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-blue-500/50 shadow-lg"
title="Link"
>
<LinkIcon size={16} />
</button>
<button
onClick={() => insertMarkdown('image')}
className="p-3 bg-gradient-to-br from-purple-600/50 to-purple-700/50 hover:from-purple-500/60 hover:to-purple-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-purple-500/50 shadow-lg"
title="Image"
>
<ImageIcon size={16} />
</button>
<button
onClick={() => insertMarkdown('table')}
className="p-3 bg-gradient-to-br from-orange-600/50 to-orange-700/50 hover:from-orange-500/60 hover:to-orange-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-orange-500/50 shadow-lg"
title="Table"
>
<span className="text-sm font-bold">📊</span>
</button>
<button
onClick={() => insertMarkdown('emoji')}
className="p-3 bg-gradient-to-br from-pink-600/50 to-pink-700/50 hover:from-pink-500/60 hover:to-pink-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-pink-500/50 shadow-lg"
title="Emoji"
>
<Smile size={16} />
</button>
</div>
</div>
{/* Advanced Elements */}
<div className="mb-4">
<div className="text-xs text-gray-500 mb-2 uppercase tracking-wide">Advanced Elements</div>
<div className="grid grid-cols-4 gap-2">
<button
onClick={() => insertMarkdown('codeblock')}
className="p-3 bg-gradient-to-br from-indigo-600/50 to-indigo-700/50 hover:from-indigo-500/60 hover:to-indigo-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-indigo-500/50 shadow-lg"
title="Code Block"
>
<FileText size={16} />
</button>
<button
onClick={() => insertMarkdown('highlight')}
className="p-3 bg-gradient-to-br from-yellow-600/50 to-yellow-700/50 hover:from-yellow-500/60 hover:to-yellow-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-yellow-500/50 shadow-lg"
title="Highlight Text"
>
<Palette size={16} />
</button>
<button
onClick={() => insertMarkdown('spoiler')}
className="p-3 bg-gradient-to-br from-red-600/50 to-red-700/50 hover:from-red-500/60 hover:to-red-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-red-500/50 shadow-lg"
title="Spoiler"
>
<span className="text-sm font-bold">👁</span>
</button>
<button
onClick={() => insertMarkdown('callout')}
className="p-3 bg-gradient-to-br from-teal-600/50 to-teal-700/50 hover:from-teal-500/60 hover:to-teal-600/60 rounded-lg text-white transition-all duration-200 hover:scale-105 border border-teal-500/50 shadow-lg"
title="Callout Box"
>
<span className="text-sm font-bold">💡</span>
</button>
</div>
</div>
</div>
{/* Enhanced Textarea */}
<div className="relative">
<textarea
id="markdown-editor"
value={markdownContent}
onChange={(e) => setMarkdownContent(e.target.value)}
rows={20}
className="w-full px-6 py-4 bg-gray-800/50 border border-gray-600/50 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/50 resize-none font-mono text-sm leading-relaxed shadow-lg"
placeholder="✨ Write your project content in Markdown...&#10;&#10;# Start with a heading&#10;## Add subheadings&#10;- Create lists&#10;- Add **bold** and *italic* text&#10;- Include [links](url) and ![images](url)&#10;- Use `code` and code blocks"
/>
<div className="absolute top-4 right-4 text-xs text-gray-500 font-mono">
{markdownContent.length} chars
</div>
</div>
</div>
{/* Enhanced Live Preview */}
<div className="space-y-4 h-full flex flex-col">
<div className="flex items-center justify-between">
<div className="text-sm font-medium text-gray-300">Live Preview</div>
<div className="flex items-center space-x-2 text-xs text-gray-500">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<span>Real-time rendering</span>
</div>
</div>
<div className="flex-1 overflow-y-auto p-6 bg-gradient-to-br from-gray-800/40 to-gray-700/40 rounded-xl border border-gray-600/50 shadow-lg min-h-[32rem]">
<div className="markdown prose prose-invert max-w-none text-white">
{markdownContent ? (
<ReactMarkdown
components={{
h1: ({children}) => <h1 className="text-3xl font-bold text-white mb-4 bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">{children}</h1>,
h2: ({children}) => <h2 className="text-2xl font-semibold text-white mb-3 border-l-4 border-blue-500 pl-3">{children}</h2>,
h3: ({children}) => <h3 className="text-xl font-semibold text-white mb-2">{children}</h3>,
p: ({children}) => <p className="text-gray-300 mb-3 leading-relaxed">{children}</p>,
ul: ({children}) => <ul className="list-disc list-inside text-gray-300 mb-3 space-y-1 marker:text-blue-400">{children}</ul>,
ol: ({children}) => <ol className="list-decimal list-inside text-gray-300 mb-3 space-y-1 marker:text-purple-400">{children}</ol>,
li: ({children}) => <li className="text-gray-300">{children}</li>,
a: ({href, children}) => (
<a href={href} className="text-blue-400 hover:text-blue-300 underline transition-colors decoration-2 underline-offset-2" target="_blank" rel="noopener noreferrer">
{children}
</a>
),
code: ({children}) => <code className="bg-gray-700/80 text-blue-400 px-2 py-1 rounded-md text-sm font-mono border border-gray-600/50">{children}</code>,
pre: ({children}) => <pre className="bg-gray-800/80 p-4 rounded-lg overflow-x-auto mb-3 border border-gray-600/50 shadow-inner">{children}</pre>,
blockquote: ({children}) => <blockquote className="border-l-4 border-blue-500 pl-4 italic text-gray-300 mb-3 bg-blue-500/10 py-2 rounded-r-lg">{children}</blockquote>,
strong: ({children}) => <strong className="font-semibold text-white">{children}</strong>,
em: ({children}) => <em className="italic text-gray-300">{children}</em>
}}
>
{markdownContent}
</ReactMarkdown>
) : (
<div className="text-center text-gray-500 py-20">
<div className="text-6xl mb-4"></div>
<p className="text-lg font-medium">Start writing to see the preview</p>
<p className="text-sm">Your Markdown will appear here in real-time</p>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
) : (
/* Preview */
<div className="prose prose-invert max-w-none">
<div className="markdown" dangerouslySetInnerHTML={{ __html: markdownContent }} />
</div>
)}
</motion.div>
</div>
</div>
</div>
</div>
);
};
export default AdminPage;