"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: _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: _node, ...props }: { node?: unknown; src?: string; alt?: string; }) => { // Validate image URLs const src = props.src || ""; const isSafe = src && !src.startsWith("javascript:") && !src.startsWith("data:"); // eslint-disable-next-line @next/next/no-img-element 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