'use client'; import React, { useState, useEffect, useRef, useCallback, Suspense } from 'react'; import { useSearchParams } from 'next/navigation'; import { motion, AnimatePresence } from 'framer-motion'; 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 { console.log('Fetching projects...'); const response = await fetch('/api/projects'); if (response.ok) { const data = await response.json(); console.log('Projects loaded:', data); const foundProject = data.projects.find((p: Project) => p.id.toString() === id); console.log('Found project:', foundProject); 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 || '' }); console.log('Form data set for project:', foundProject.title); } else { console.log('Project not found with ID:', id); } } else { console.error('Failed to fetch projects:', response.status); } } catch (error) { 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'); console.log('Editor Auth check:', { authStatus, hasSessionToken: !!sessionToken, projectId }); if (authStatus === 'true' && sessionToken) { console.log('User is authenticated'); setIsAuthenticated(true); // Load project if editing if (projectId) { console.log('Loading project with ID:', projectId); await loadProject(projectId); } else { console.log('Creating new project'); setIsCreating(true); } } else { console.log('User not authenticated'); setIsAuthenticated(false); } } catch (error) { 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 }; console.log('Saving project:', { url, method, saveData }); 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(); console.log('Project saved successfully:', savedProject); // 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(); console.error('Error saving project:', response.status, errorData); alert(`Error saving project: ${errorData.error || 'Unknown error'}`); } } catch (error) { 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 })); }; // Simple markdown to HTML converter const parseMarkdown = (text: string) => { if (!text) return ''; return text // Headers .replace(/^### (.*$)/gim, '

$1

') .replace(/^## (.*$)/gim, '

$1

') .replace(/^# (.*$)/gim, '

$1

') // Bold .replace(/\*\*(.*?)\*\*/g, '$1') // Italic .replace(/\*(.*?)\*/g, '$1') // Code blocks .replace(/```([\s\S]*?)```/g, '
$1
') // Inline code .replace(/`(.*?)`/g, '$1') // Links .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') // Images .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1') // Ensure all images have alt attributes .replace(/]*?)(?:\s+alt\s*=\s*["'][^"']*["'])?([^>]*?)>/g, (match, before, after) => { if (match.includes('alt=')) return match; return ``; }) // Lists .replace(/^\* (.*$)/gim, '
  • $1
  • ') .replace(/^- (.*$)/gim, '
  • $1
  • ') .replace(/^(\d+)\. (.*$)/gim, '
  • $2
  • ') // Blockquotes .replace(/^> (.*$)/gim, '
    $1
    ') // Line breaks .replace(/\n/g, '
    '); }; // 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