From 549476274da179dd10c56fc25bbb081fcd8128ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CBeeRad=E2=80=9D?= Date: Sun, 8 Feb 2026 12:51:44 +1100 Subject: [PATCH] feat: separate guides into system and custom sections Add system/custom guide separation with section headers, lock icons, delete functionality for custom guides, and usage counter. Co-Authored-By: Claude Opus 4.6 --- app/api/guides/[name]/route.ts | 25 +++- src/components/panes/GuidesPane.tsx | 172 ++++++++++++++++++++++------ 2 files changed, 160 insertions(+), 37 deletions(-) diff --git a/app/api/guides/[name]/route.ts b/app/api/guides/[name]/route.ts index 224bc86..18359de 100644 --- a/app/api/guides/[name]/route.ts +++ b/app/api/guides/[name]/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { readGuide } from '@/services/guides/guideService'; +import { readGuide, deleteGuide } from '@/services/guides/guideService'; export const runtime = 'nodejs'; @@ -25,3 +25,26 @@ export async function GET( ); } } + +export async function DELETE( + _request: NextRequest, + { params }: { params: Promise<{ name: string }> } +) { + try { + const { name } = await params; + const result = deleteGuide(name); + if (!result.success) { + return NextResponse.json( + { success: false, error: result.error }, + { status: 400 } + ); + } + return NextResponse.json({ success: true, message: `Guide "${name}" deleted` }); + } catch (error) { + console.error('[API /guides/[name] DELETE] error:', error); + return NextResponse.json( + { success: false, error: error instanceof Error ? error.message : 'Failed to delete guide' }, + { status: 500 } + ); + } +} diff --git a/src/components/panes/GuidesPane.tsx b/src/components/panes/GuidesPane.tsx index 916396f..7b5c179 100644 --- a/src/components/panes/GuidesPane.tsx +++ b/src/components/panes/GuidesPane.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect } from 'react'; -import { ArrowLeft } from 'lucide-react'; +import { ArrowLeft, Lock, Trash2 } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import PaneHeader from './PaneHeader'; @@ -10,6 +10,7 @@ import type { BasePaneProps } from './types'; interface GuideMeta { name: string; description: string; + immutable: boolean; } interface Guide extends GuideMeta { @@ -27,6 +28,7 @@ export default function GuidesPane({ const [guides, setGuides] = useState([]); const [selectedGuide, setSelectedGuide] = useState(null); const [loading, setLoading] = useState(true); + const [deleting, setDeleting] = useState(null); useEffect(() => { fetchGuides(); @@ -62,10 +64,34 @@ export default function GuidesPane({ } }; + const handleDeleteGuide = async (name: string, e: React.MouseEvent) => { + e.stopPropagation(); + if (!confirm(`Delete guide "${name}"?`)) return; + + setDeleting(name); + try { + const res = await fetch(`/api/guides/${encodeURIComponent(name)}`, { method: 'DELETE' }); + const data = await res.json(); + if (data.success) { + fetchGuides(); + if (selectedGuide?.name === name) { + setSelectedGuide(null); + } + } + } catch (err) { + console.error('[GuidesPane] Failed to delete guide:', err); + } finally { + setDeleting(null); + } + }; + const handleBack = () => { setSelectedGuide(null); }; + const systemGuides = guides.filter(g => g.immutable); + const userGuides = guides.filter(g => !g.immutable); + return (
- {selectedGuide && ( + {selectedGuide ? (
- + + {selectedGuide.immutable && } {selectedGuide.name}
+ ) : ( + + {userGuides.length} of 10 custom guides + )}
@@ -188,39 +219,36 @@ export default function GuidesPane({ No guides found
) : ( - guides.map((guide) => ( - - )) + <> + {systemGuides.length > 0 && ( +
+ System Guides +
+ )} + {systemGuides.map((guide) => ( + + ))} + {userGuides.length > 0 && ( +
+ Custom Guides +
+ )} + {userGuides.map((guide) => ( + + ))} + )} )} @@ -228,3 +256,75 @@ export default function GuidesPane({ ); } + +function GuideCard({ + guide, + onSelect, + onDelete, + deleting, +}: { + guide: GuideMeta; + onSelect: (name: string) => void; + onDelete: (name: string, e: React.MouseEvent) => void; + deleting: string | null; +}) { + return ( + + )} + + ); +}