'use client'; import React, { useState, useEffect, useRef, useCallback, Suspense } from 'react'; import { useSearchParams } from 'next/navigation'; import { motion, AnimatePresence } from 'framer-motion'; import ReactMarkdown from 'react-markdown'; import { ArrowLeft, Save, Eye, X, Bold, Italic, Code, Image, Link, List, ListOrdered, Quote, Hash, Loader2, ExternalLink, Github, Tag } from 'lucide-react'; interface Project { id: string; title: string; description: string; content?: string; category: string; tags?: string[]; featured: boolean; published: boolean; github?: string; live?: string; image?: string; createdAt: string; updatedAt: string; } function EditorPageContent() { const searchParams = useSearchParams(); const projectId = searchParams.get('id'); const contentRef = useRef(null); const [, setProject] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isCreating, setIsCreating] = useState(!projectId); const [showPreview, setShowPreview] = useState(false); const [isTyping, setIsTyping] = useState(false); // Form state const [formData, setFormData] = useState({ title: '', description: '', content: '', category: 'web', tags: [] as string[], featured: false, published: false, github: '', live: '', image: '' }); const loadProject = useCallback(async (id: string) => { try { const response = await fetch('/api/projects'); if (response.ok) { const data = await response.json(); const foundProject = data.projects.find((p: Project) => p.id.toString() === id); if (foundProject) { setProject(foundProject); setFormData({ title: foundProject.title || '', description: foundProject.description || '', content: foundProject.content || '', category: foundProject.category || 'web', tags: foundProject.tags || [], featured: foundProject.featured || false, published: foundProject.published || false, github: foundProject.github || '', live: foundProject.live || '', image: foundProject.image || '' }); } } else { if (process.env.NODE_ENV === 'development') { console.error('Failed to fetch projects:', response.status); } } } catch (error) { if (process.env.NODE_ENV === 'development') { console.error('Error loading project:', error); } } }, []); // Check authentication and load project useEffect(() => { const init = async () => { try { // Check auth const authStatus = sessionStorage.getItem('admin_authenticated'); const sessionToken = sessionStorage.getItem('admin_session_token'); if (authStatus === 'true' && sessionToken) { setIsAuthenticated(true); // Load project if editing if (projectId) { await loadProject(projectId); } else { setIsCreating(true); } } else { setIsAuthenticated(false); } } catch (error) { if (process.env.NODE_ENV === 'development') { console.error('Error in init:', error); } setIsAuthenticated(false); } finally { setIsLoading(false); } }; init(); }, [projectId, loadProject]); const handleSave = async () => { try { setIsSaving(true); // Validate required fields if (!formData.title.trim()) { alert('Please enter a project title'); return; } if (!formData.description.trim()) { alert('Please enter a project description'); return; } const url = projectId ? `/api/projects/${projectId}` : '/api/projects'; const method = projectId ? 'PUT' : 'POST'; // Prepare data for saving - only include fields that exist in the database schema const saveData = { title: formData.title.trim(), description: formData.description.trim(), content: formData.content.trim(), category: formData.category, tags: formData.tags, github: formData.github.trim() || null, live: formData.live.trim() || null, imageUrl: formData.image.trim() || null, published: formData.published, featured: formData.featured, // Add required fields that might be missing date: new Date().toISOString().split('T')[0] // Current date in YYYY-MM-DD format }; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json', 'x-admin-request': 'true' }, body: JSON.stringify(saveData) }); if (response.ok) { const savedProject = await response.json(); // Update local state with the saved project data setProject(savedProject); setFormData(prev => ({ ...prev, title: savedProject.title || '', description: savedProject.description || '', content: savedProject.content || '', category: savedProject.category || 'web', tags: savedProject.tags || [], featured: savedProject.featured || false, published: savedProject.published || false, github: savedProject.github || '', live: savedProject.live || '', image: savedProject.imageUrl || '' })); // Show success and redirect alert('Project saved successfully!'); setTimeout(() => { window.location.href = '/manage'; }, 1000); } else { const errorData = await response.json(); if (process.env.NODE_ENV === 'development') { console.error('Error saving project:', response.status, errorData); } alert(`Error saving project: ${errorData.error || 'Unknown error'}`); } } catch (error) { if (process.env.NODE_ENV === 'development') { console.error('Error saving project:', error); } alert(`Error saving project: ${error instanceof Error ? error.message : 'Unknown error'}`); } finally { setIsSaving(false); } }; const handleInputChange = (field: string, value: string | boolean | string[]) => { setFormData(prev => ({ ...prev, [field]: value })); }; const handleTagsChange = (tagsString: string) => { const tags = tagsString.split(',').map(tag => tag.trim()).filter(tag => tag); setFormData(prev => ({ ...prev, tags })); }; // Markdown components for react-markdown with security const markdownComponents = { a: ({ node, ...props }: { node?: unknown; href?: string; children?: React.ReactNode }) => { // Validate URLs to prevent javascript: and data: protocols const href = props.href || ''; const isSafe = href && !href.startsWith('javascript:') && !href.startsWith('data:'); return ( ); }, img: ({ node, ...props }: { node?: unknown; src?: string; alt?: string }) => { // Validate image URLs const src = props.src || ''; const isSafe = src && !src.startsWith('javascript:') && !src.startsWith('data:'); return isSafe ? {props.alt : null; }, }; // Rich text editor functions const insertFormatting = (format: string) => { const content = contentRef.current; if (!content) return; const selection = window.getSelection(); if (!selection || selection.rangeCount === 0) return; const range = selection.getRangeAt(0); let newText = ''; switch (format) { case 'bold': newText = `**${selection.toString() || 'bold text'}**`; break; case 'italic': newText = `*${selection.toString() || 'italic text'}*`; break; case 'code': newText = `\`${selection.toString() || 'code'}\``; break; case 'h1': newText = `# ${selection.toString() || 'Heading 1'}`; break; case 'h2': newText = `## ${selection.toString() || 'Heading 2'}`; break; case 'h3': newText = `### ${selection.toString() || 'Heading 3'}`; break; case 'list': newText = `- ${selection.toString() || 'List item'}`; break; case 'orderedList': newText = `1. ${selection.toString() || 'List item'}`; break; case 'quote': newText = `> ${selection.toString() || 'Quote'}`; break; case 'link': const url = prompt('Enter URL:'); if (url) { newText = `[${selection.toString() || 'link text'}](${url})`; } break; case 'image': const imageUrl = prompt('Enter image URL:'); if (imageUrl) { newText = `![${selection.toString() || 'alt text'}](${imageUrl})`; } break; } if (newText) { range.deleteContents(); range.insertNode(document.createTextNode(newText)); // Update form data setFormData(prev => ({ ...prev, content: content.textContent || '' })); } }; if (isLoading) { return (

Loading Editor

Preparing your workspace...

); } if (!isAuthenticated) { return (

Access Denied

You need to be logged in to access the editor.

); } return (
{/* Header */}

{isCreating ? 'Create New Project' : `Edit: ${formData.title || 'Untitled'}`}

{/* Editor Content */}
{/* Floating particles background */}
{[...Array(20)].map((_, i) => (
))}
{/* Main Editor */}
{/* Project Title */} handleInputChange('title', e.target.value)} className="w-full text-3xl font-bold form-input-enhanced focus:outline-none p-4 rounded-lg" placeholder="Enter project title..." /> {/* Rich Text Toolbar */}
{/* Content Editor */}

Content

{ const target = e.target as HTMLDivElement; setIsTyping(true); setFormData(prev => ({ ...prev, content: target.textContent || '' })); }} onBlur={() => { setIsTyping(false); }} suppressContentEditableWarning={true} data-placeholder="Start writing your project content..." > {!isTyping ? formData.content : undefined}

Supports Markdown formatting. Use the toolbar above or type directly.

{/* Description */}

Description