diff --git a/posts/created-using-admingui.md b/posts/created-using-admingui.md deleted file mode 100644 index d0f1c04..0000000 --- a/posts/created-using-admingui.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: created using adminGUI -date: '2025-06-16' -tags: - - admin -summary: yeaa ---- -**this was created using the admin page** diff --git a/posts/uploadtest.md b/posts/uploadtest.md new file mode 100644 index 0000000..d2c0870 --- /dev/null +++ b/posts/uploadtest.md @@ -0,0 +1,12 @@ +--- +title: "Upload Test" +date: "2025-05-16" +tags: ["test", "feature"] +summary: "A test post to demonstrate uploads" +--- + +# Upload Tests + +testing the uploading features + +![Markdown](https://img.shields.io/badge/markdown-%23000000.svg?style=for-the-badge&logo=markdown&logoColor=white) diff --git a/src/app/admin/manage/page.tsx b/src/app/admin/manage/page.tsx new file mode 100644 index 0000000..d93228c --- /dev/null +++ b/src/app/admin/manage/page.tsx @@ -0,0 +1,268 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; + +interface Post { + type: 'post'; + slug: string; + title: string; + date: string; + tags: string[]; + summary: string; + content: string; +} + +interface Folder { + type: 'folder'; + name: string; + path: string; + children: (Post | Folder)[]; +} + +type Node = Post | Folder; + +export default function ManagePage() { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [nodes, setNodes] = useState([]); + const [currentPath, setCurrentPath] = useState([]); + const [deleteConfirm, setDeleteConfirm] = useState<{ show: boolean; item: Node | null }>({ + show: false, + item: null, + }); + const router = useRouter(); + + useEffect(() => { + // Check if already authenticated + const auth = localStorage.getItem('adminAuth'); + if (auth === 'true') { + setIsAuthenticated(true); + loadContent(); + } else { + router.push('/admin'); + } + }, []); + + const loadContent = async () => { + try { + const response = await fetch('/api/posts'); + const data = await response.json(); + setNodes(data); + } catch (error) { + console.error('Error loading content:', error); + } + }; + + const handleLogout = () => { + setIsAuthenticated(false); + localStorage.removeItem('adminAuth'); + router.push('/admin'); + }; + + // Get current directory contents + const getCurrentNodes = (): Node[] => { + let currentNodes: Node[] = nodes; + for (const segment of currentPath) { + const folder = currentNodes.find( + (n) => n.type === 'folder' && n.name === segment + ) as Folder | undefined; + if (folder) { + currentNodes = folder.children; + } else { + break; + } + } + return currentNodes; + }; + + const currentNodes = getCurrentNodes(); + + // Breadcrumbs + const breadcrumbs = [ + { name: 'Root', path: [] }, + ...currentPath.map((name, idx) => ({ + name, + path: currentPath.slice(0, idx + 1), + })), + ]; + + const handleDelete = async (node: Node) => { + setDeleteConfirm({ show: true, item: node }); + }; + + const confirmDelete = async () => { + if (!deleteConfirm.item) return; + + try { + const itemPath = currentPath.join('/'); + const itemName = deleteConfirm.item.type === 'folder' ? deleteConfirm.item.name : deleteConfirm.item.slug; + + console.log('Deleting item:', { + path: itemPath, + name: itemName, + type: deleteConfirm.item.type + }); + + const response = await fetch('/api/admin/delete', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + path: itemPath, + name: itemName, + type: deleteConfirm.item.type, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to delete item'); + } + + console.log('Delete successful:', data); + await loadContent(); + setDeleteConfirm({ show: false, item: null }); + } catch (error) { + console.error('Error deleting item:', error); + alert(error instanceof Error ? error.message : 'Error deleting item'); + } + }; + + if (!isAuthenticated) { + return null; // Will redirect in useEffect + } + + return ( +
+
+
+
+

Manage Content

+ + Back to Admin + +
+ +
+ + {/* Breadcrumbs with back button */} +
+ {currentPath.length > 0 && ( + + )} +
+ {breadcrumbs.map((crumb, index) => ( +
+ {index > 0 && /} + +
+ ))} +
+
+ + {/* Content List */} +
+ {currentNodes.map((node) => ( +
+
+
+

+ {node.type === 'folder' ? node.name : node.title} +

+ {node.type === 'post' && ( +

{node.date}

+ )} +
+ +
+
+ ))} +
+ + {/* Delete Confirmation Modal */} + {deleteConfirm.show && ( +
+
+

Confirm Delete

+

+ Are you sure you want to delete {deleteConfirm.item?.type === 'folder' ? 'folder' : 'post'} "{deleteConfirm.item?.type === 'folder' ? deleteConfirm.item.name : deleteConfirm.item?.title}"? +

+
+ + +
+
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 811c1b5..f67b67f 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/navigation'; +import Link from 'next/link'; interface Post { slug: string; @@ -46,6 +47,11 @@ export default function AdminPage() { }); const [newFolderName, setNewFolderName] = useState(''); const [isDragging, setIsDragging] = useState(false); + const [deleteConfirm, setDeleteConfirm] = useState<{ show: boolean; item: Node | null }>({ + show: false, + item: null, + }); + const [showManageContent, setShowManageContent] = useState(false); const router = useRouter(); useEffect(() => { @@ -220,12 +226,56 @@ export default function AdminPage() { loadContent(); }, [currentPath]); - if (!isAuthenticated) { - return ( -
-
-

Admin Login

-
+ const handleDelete = async (node: Node) => { + setDeleteConfirm({ show: true, item: node }); + }; + + const confirmDelete = async () => { + if (!deleteConfirm.item) return; + + try { + const itemPath = currentPath.join('/'); + const itemName = deleteConfirm.item.type === 'folder' ? deleteConfirm.item.name : deleteConfirm.item.slug; + + console.log('Deleting item:', { + path: itemPath, + name: itemName, + type: deleteConfirm.item.type + }); + + const response = await fetch('/api/admin/delete', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + path: itemPath, + name: itemName, + type: deleteConfirm.item.type, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || 'Failed to delete item'); + } + + console.log('Delete successful:', data); + await loadContent(); + setDeleteConfirm({ show: false, item: null }); + } catch (error) { + console.error('Error deleting item:', error); + alert(error instanceof Error ? error.message : 'Error deleting item'); + } + }; + + return ( +
+ {!isAuthenticated ? ( +
+

Admin Login

+
@@ -248,190 +298,230 @@ export default function AdminPage() { id="password" value={password} onChange={(e) => setPassword(e.target.value)} - className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" + className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" required />
-
- ); - } + ) : ( +
+
+

Admin Dashboard

+ +
- return ( -
-
-
-

Admin Dashboard

- -
- - {/* Breadcrumb Navigation */} - - - {/* Create Folder Form */} -
-

Create New Folder

-
- setNewFolderName(e.target.value)} - placeholder="Folder name" - className="flex-1 rounded-md border border-gray-300 px-3 py-2" - required - /> - -
-
- - {/* Drag and Drop Zone */} -
-
-

Drag and drop Markdown files here

-

Files will be uploaded to: {currentPath.join('/') || 'root'}

-
-
- - {/* Create Post Form */} -
-

Create New Post

-
-
- - setNewPost({ ...newPost, title: e.target.value })} - className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" - required - /> -
-
- - setNewPost({ ...newPost, date: e.target.value })} - className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" - required - /> -
-
- - setNewPost({ ...newPost, tags: e.target.value })} - className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2" - placeholder="tag1, tag2, tag3" - /> -
-
- -