diff --git a/src/app/admin/manage/page.tsx b/src/app/admin/manage/page.tsx index 2d0e431..2709875 100644 --- a/src/app/admin/manage/page.tsx +++ b/src/app/admin/manage/page.tsx @@ -23,6 +23,31 @@ interface Folder { type Node = Post | Folder; +// Helper to get folder details +async function getFolderDetails(path: string): Promise<{ created: string, items: number, size: number, error?: string }> { + try { + const res = await fetch(`/api/admin/folders/details?path=${encodeURIComponent(path)}`); + if (!res.ok) throw new Error('API error'); + return await res.json(); + } catch (e) { + console.error('Error fetching folder details:', e); + return { created: '', items: 0, size: 0, error: 'Error loading details' }; + } +} + +// Helper to get post size and creation date +async function getPostSize(slug: string): Promise<{ size: number | null, created: string | null }> { + try { + const res = await fetch(`/api/admin/posts/size?slug=${encodeURIComponent(slug)}`); + if (!res.ok) throw new Error('API error'); + const data = await res.json(); + return { size: data.size, created: data.created }; + } catch (e) { + console.error('Error fetching post size:', e); + return { size: null, created: null }; + } +} + export default function ManagePage() { const [isAuthenticated, setIsAuthenticated] = useState(false); const [nodes, setNodes] = useState([]); @@ -33,6 +58,9 @@ export default function ManagePage() { }); const [draggedPost, setDraggedPost] = useState(null); const [dragOverFolder, setDragOverFolder] = useState(null); + const [folderDetails, setFolderDetails] = useState>({}); + const [deleteAllConfirm, setDeleteAllConfirm] = useState<{ show: boolean, folder: Folder | null }>({ show: false, folder: null }); + const [postSizes, setPostSizes] = useState>({}); const router = useRouter(); useEffect(() => { @@ -162,6 +190,30 @@ export default function ManagePage() { } }; + // Fetch folder details when currentNodes change + useEffect(() => { + async function fetchDetails() { + const details: Record = {}; + await Promise.all(currentNodes.filter(n => n.type === 'folder').map(async (folder: any) => { + details[folder.path] = await getFolderDetails(folder.path); + })); + setFolderDetails(details); + } + fetchDetails(); + }, [currentNodes]); + + // Fetch post sizes and creation dates when currentNodes change + useEffect(() => { + async function fetchSizes() { + const sizes: Record = {}; + await Promise.all(currentNodes.filter(n => n.type === 'post').map(async (post: any) => { + sizes[post.slug] = await getPostSize(post.slug); + })); + setPostSizes(sizes); + } + fetchSizes(); + }, [currentNodes]); + if (!isAuthenticated) { return null; // Will redirect in useEffect } @@ -237,6 +289,7 @@ export default function ManagePage() { key={node.name} className={`bg-white p-4 rounded-lg shadow hover:shadow-md transition-shadow cursor-pointer ${dragOverFolder === node.name ? 'ring-2 ring-blue-400' : ''}`} onClick={() => setCurrentPath([...currentPath, node.name])} + onDoubleClick={() => setDeleteAllConfirm({ show: true, folder: node })} onDragOver={e => { e.preventDefault(); setDragOverFolder(node.name); }} onDragLeave={() => setDragOverFolder(null)} onDrop={e => { @@ -250,6 +303,21 @@ export default function ManagePage() {

{node.name}

+
+ {folderDetails[node.path] ? ( + folderDetails[node.path].error ? ( + {folderDetails[node.path].error} + ) : ( + <> +
Created: {folderDetails[node.path].created || Loading...}
+
Items: {folderDetails[node.path].items}
+
Size: {folderDetails[node.path].size > 1024 ? `${(folderDetails[node.path].size/1024).toFixed(1)} KB` : `${folderDetails[node.path].size} B`}
+ + ) + ) : ( + Loading... + )} +
)} + + {/* Delete Full Folder Modal */} + {deleteAllConfirm.show && ( +
+
+

Delete Full Folder

+

+ Are you sure you want to delete the entire folder and all its contents? +
+ This cannot be undone! +

+
+ + +
+
+
+ )} ); diff --git a/src/app/api/admin/delete/route.ts b/src/app/api/admin/delete/route.ts index eb3548c..9411ca3 100644 --- a/src/app/api/admin/delete/route.ts +++ b/src/app/api/admin/delete/route.ts @@ -5,7 +5,7 @@ import path from 'path'; export async function POST(request: NextRequest) { try { const body = await request.json(); - const { path: itemPath, name, type } = body; + const { path: itemPath, name, type, recursive } = body; if (!name || !type) { return NextResponse.json( @@ -58,8 +58,8 @@ export async function POST(request: NextRequest) { }, { status: 404 }); } - // For folders, check if it's empty - if (type === 'folder') { + // For folders, check if it's empty unless recursive is true + if (type === 'folder' && !recursive) { try { const files = await fs.readdir(fullPath); if (files.length > 0) { diff --git a/src/app/api/admin/folders/details/route.ts b/src/app/api/admin/folders/details/route.ts new file mode 100644 index 0000000..59fca94 --- /dev/null +++ b/src/app/api/admin/folders/details/route.ts @@ -0,0 +1,40 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; + +const postsDirectory = path.join(process.cwd(), 'posts'); + +function getFolderStats(folderPath: string) { + const fullPath = path.join(postsDirectory, folderPath); + let items = 0; + let size = 0; + let created = ''; + try { + const stat = fs.statSync(fullPath); + created = stat.birthtime.toISOString(); + const walk = (dir: string) => { + const files = fs.readdirSync(dir); + for (const file of files) { + const filePath = path.join(dir, file); + const stats = fs.statSync(filePath); + if (stats.isDirectory()) { + walk(filePath); + } else { + items++; + size += stats.size; + } + } + }; + walk(fullPath); + } catch { + // ignore errors + } + return { created, items, size }; +} + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const folderPath = searchParams.get('path') || ''; + const stats = getFolderStats(folderPath); + return NextResponse.json(stats); +} \ No newline at end of file diff --git a/src/app/api/admin/posts/size/route.ts b/src/app/api/admin/posts/size/route.ts new file mode 100644 index 0000000..5ba6734 --- /dev/null +++ b/src/app/api/admin/posts/size/route.ts @@ -0,0 +1,18 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; + +const postsDirectory = path.join(process.cwd(), 'posts'); + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const slug = searchParams.get('slug'); + if (!slug) return NextResponse.json({ size: null, created: null }); + try { + const filePath = path.join(postsDirectory, `${slug}.md`); + const stat = fs.statSync(filePath); + return NextResponse.json({ size: stat.size, created: stat.birthtime.toISOString() }); + } catch { + return NextResponse.json({ size: null, created: null }); + } +} \ No newline at end of file