From 93f332e0272d93a63b71f203ba95077a105a7846 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Fri, 13 Mar 2026 12:32:39 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20add=20ScriptConfigurePane=20=E2=80=94?= =?UTF-8?q?=20configure=20mode=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../scripts/ScriptConfigurePane.tsx | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 frontend/src/components/scripts/ScriptConfigurePane.tsx diff --git a/frontend/src/components/scripts/ScriptConfigurePane.tsx b/frontend/src/components/scripts/ScriptConfigurePane.tsx new file mode 100644 index 00000000..ee107cc6 --- /dev/null +++ b/frontend/src/components/scripts/ScriptConfigurePane.tsx @@ -0,0 +1,213 @@ +import { useState } from 'react' +import { ArrowLeft, Terminal, Download, Loader2, AlertTriangle, Copy, Check } from 'lucide-react' +import { cn } from '@/lib/utils' +import { useScriptGeneratorStore } from '@/store/scriptGeneratorStore' +import { ScriptParameterForm } from './ScriptParameterForm' + +const COMPLEXITY_CLASSES = { + beginner: 'text-emerald-400 bg-emerald-400/10 border-emerald-400/20', + intermediate: 'text-amber-400 bg-amber-400/10 border-amber-400/20', + advanced: 'text-rose-500 bg-rose-500/10 border-rose-500/20', +} as const + +interface Props { + canGenerate: boolean + onBack: () => void +} + +export function ScriptConfigurePane({ canGenerate, onBack }: Props) { + const selectedTemplate = useScriptGeneratorStore(s => s.selectedTemplate) + const isLoadingDetail = useScriptGeneratorStore(s => s.isLoadingDetail) + const categories = useScriptGeneratorStore(s => s.categories) + const generatedScript = useScriptGeneratorStore(s => s.generatedScript) + const generationWarnings = useScriptGeneratorStore(s => s.generationWarnings) + const isGenerating = useScriptGeneratorStore(s => s.isGenerating) + const generateError = useScriptGeneratorStore(s => s.generateError) + const generate = useScriptGeneratorStore(s => s.generate) + + const [copied, setCopied] = useState(false) + + const handleCopy = async () => { + if (!generatedScript) return + try { + await navigator.clipboard.writeText(generatedScript) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch { + // silently fail + } + } + + const handleDownload = () => { + if (!generatedScript || !selectedTemplate) return + const blob = new Blob([generatedScript], { type: 'text/plain' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `${selectedTemplate.slug}.ps1` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + + // Loading state + if (isLoadingDetail) { + return ( +
+ +
+ +
+
+ ) + } + + // First-selection failure state + if (!selectedTemplate) { + return ( +
+ +
+ +

Failed to load template.

+
+
+ ) + } + + const categoryName = categories.find(c => c.id === selectedTemplate.category_id)?.name + const displayTags = selectedTemplate.tags.slice(0, 3) + const extraTagCount = selectedTemplate.tags.length - 3 + + return ( +
+ {/* Back button */} + + + {/* Template header */} +
+

+ {selectedTemplate.name} +

+ {selectedTemplate.description && ( +

{selectedTemplate.description}

+ )} +
+ {selectedTemplate.requires_elevation && ( + + ⚠ Elevated + + )} + + {selectedTemplate.complexity} + + {categoryName && ( + + {categoryName} + + )} + {displayTags.map(tag => ( + + {tag} + + ))} + {extraTagCount > 0 && ( + +{extraTagCount} + )} +
+
+ +
+ + {/* Parameter form */} + + + {/* Warnings */} + {generationWarnings.length > 0 && ( +
+
+ + Warnings +
+ {generationWarnings.map((w, i) => ( +

{w}

+ ))} +
+ )} + + {/* Action bar */} +
+ + + + +
+ + + + + + + +
+
+ + {/* Generate error */} + {generateError && ( +

{generateError}

+ )} +
+ ) +}