User-facing text updated across page title, sidebar, command palette, quick launch, quick actions, post-step modal, empty state, and procedural editor. File paths/component names unchanged — those will be renamed when the full Solutions Library feature ships. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
176 lines
5.7 KiB
TypeScript
176 lines
5.7 KiB
TypeScript
import { useState } from 'react'
|
|
import { Bookmark, Trash2 } from 'lucide-react'
|
|
import { PageMeta } from '@/components/common/PageMeta'
|
|
import { Button } from '@/components/ui/Button'
|
|
import { useAuthStore } from '@/store/authStore'
|
|
import { usePermissions } from '@/hooks/usePermissions'
|
|
import { stepsApi } from '@/api/steps'
|
|
import { StepLibraryBrowser } from '@/components/step-library/StepLibraryBrowser'
|
|
import { StepFormModal } from '@/components/step-library/StepFormModal'
|
|
import { toast } from '@/lib/toast'
|
|
import type { Step, StepListItem } from '@/types/step'
|
|
|
|
export default function StepLibraryPage() {
|
|
const user = useAuthStore((s) => s.user)
|
|
const { canCreateSteps } = usePermissions()
|
|
|
|
// Create/edit modal state
|
|
const [createOpen, setCreateOpen] = useState(false)
|
|
const [editingStep, setEditingStep] = useState<Step | null>(null)
|
|
|
|
// Delete confirmation state
|
|
const [deletingStep, setDeletingStep] = useState<StepListItem | null>(null)
|
|
const [isDeleting, setIsDeleting] = useState(false)
|
|
const [deleteError, setDeleteError] = useState<string | null>(null)
|
|
|
|
// Increment to trigger StepLibraryBrowser reload
|
|
const [refreshKey, setRefreshKey] = useState(0)
|
|
const refresh = () => setRefreshKey(k => k + 1)
|
|
|
|
// Fetch full step before opening edit modal (StepListItem lacks content)
|
|
const handleEdit = async (step: StepListItem) => {
|
|
try {
|
|
const full = await stepsApi.get(step.id)
|
|
setEditingStep(full)
|
|
} catch {
|
|
toast.error('Failed to load step for editing')
|
|
}
|
|
}
|
|
|
|
const handleDeleteRequest = (step: StepListItem) => {
|
|
setDeletingStep(step)
|
|
}
|
|
|
|
const handleDeleteConfirm = async () => {
|
|
if (!deletingStep) return
|
|
setIsDeleting(true)
|
|
setDeleteError(null)
|
|
try {
|
|
await stepsApi.delete(deletingStep.id)
|
|
setDeletingStep(null)
|
|
refresh()
|
|
} catch (err) {
|
|
console.error('Failed to delete step:', err)
|
|
setDeleteError('Failed to delete step. Please try again.')
|
|
} finally {
|
|
setIsDeleting(false)
|
|
}
|
|
}
|
|
|
|
const handleSave = async (step: StepListItem) => {
|
|
try {
|
|
const full = await stepsApi.get(step.id)
|
|
await stepsApi.create({
|
|
title: full.title,
|
|
step_type: full.step_type,
|
|
content: full.content,
|
|
visibility: 'private',
|
|
category_id: full.category_id,
|
|
tags: full.tags,
|
|
})
|
|
toast.success(`"${full.title}" saved to My Steps`)
|
|
refresh()
|
|
} catch {
|
|
toast.error('Failed to save step to your library')
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const handleFormSuccess = (_step: Step) => {
|
|
setCreateOpen(false)
|
|
setEditingStep(null)
|
|
refresh()
|
|
}
|
|
|
|
const handleCloseModal = () => {
|
|
setCreateOpen(false)
|
|
setEditingStep(null)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<PageMeta title="Solutions Library" />
|
|
<div className="flex h-full flex-col">
|
|
{/* Page Header */}
|
|
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
|
<div className="flex items-center gap-3">
|
|
<span title="Solutions Library">
|
|
<Bookmark className="h-6 w-6 text-muted-foreground" />
|
|
</span>
|
|
<div>
|
|
<h1 className="text-xl font-bold font-heading text-foreground">Solutions Library</h1>
|
|
<p className="text-sm text-muted-foreground">Reusable solutions from your team's troubleshooting sessions</p>
|
|
</div>
|
|
</div>
|
|
{canCreateSteps && (
|
|
<Button onClick={() => setCreateOpen(true)}>
|
|
+ Create Step
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Browser fills remaining height */}
|
|
<div className="flex-1 overflow-hidden">
|
|
<StepLibraryBrowser
|
|
onEdit={(step) => { handleEdit(step) }}
|
|
onDelete={handleDeleteRequest}
|
|
onSave={handleSave}
|
|
currentUserId={user?.id}
|
|
refreshKey={refreshKey}
|
|
showCreateButton={false}
|
|
/>
|
|
</div>
|
|
|
|
{/* Create / Edit Modal */}
|
|
<StepFormModal
|
|
isOpen={createOpen || !!editingStep}
|
|
onClose={handleCloseModal}
|
|
onSuccess={handleFormSuccess}
|
|
editingStep={editingStep}
|
|
/>
|
|
|
|
{/* Delete Confirmation Dialog */}
|
|
{deletingStep && (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4">
|
|
<div className="w-full max-w-sm rounded-xl bg-card border border-border p-6 shadow-lg">
|
|
<div className="mb-4 flex items-center gap-3">
|
|
<div className="rounded-full bg-red-400/10 p-2">
|
|
<Trash2 className="h-5 w-5 text-red-400" />
|
|
</div>
|
|
<h2 className="text-base font-semibold text-foreground">Delete Step</h2>
|
|
</div>
|
|
<p className="mb-2 text-sm text-muted-foreground">
|
|
Are you sure you want to delete{' '}
|
|
<span className="font-medium text-foreground">"{deletingStep.title}"</span>?
|
|
</p>
|
|
<p className="mb-6 text-xs text-muted-foreground">This cannot be undone.</p>
|
|
{deleteError && (
|
|
<p className="mb-4 text-sm text-red-400">{deleteError}</p>
|
|
)}
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="secondary"
|
|
onClick={() => { setDeletingStep(null); setDeleteError(null) }}
|
|
disabled={isDeleting}
|
|
className="flex-1"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={handleDeleteConfirm}
|
|
loading={isDeleting}
|
|
className="flex-1"
|
|
>
|
|
{isDeleting ? 'Deleting...' : 'Delete'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
</>
|
|
)
|
|
}
|