fix: surface errors and polish UX across Step Library and Batch pages (#92)
- StepLibraryPage: replace silent console.error with toast.error on edit/save failures; replace manual setTimeout toast with toast.success helper - BatchStatusPage: add error state with retry button for failed session loads instead of showing ambiguous "No sessions found" - StepDetailModal: guard clipboard copy against browser denial (no false "Copied!" state on permission error); fix dead "See all reviews" button with inline expand/collapse toggle - StepLibraryBrowser: add "Try again" retry button to error state; retry increments a counter that re-triggers both load effects Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #92.
This commit is contained in:
@@ -29,6 +29,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [copiedCommandIndex, setCopiedCommandIndex] = useState<number | null>(null)
|
||||
const [showAllReviews, setShowAllReviews] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const loadStepDetails = async () => {
|
||||
@@ -53,9 +54,13 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
||||
}, [stepId])
|
||||
|
||||
const handleCopyCommand = async (command: string, index: number) => {
|
||||
await navigator.clipboard.writeText(command)
|
||||
setCopiedCommandIndex(index)
|
||||
setTimeout(() => setCopiedCommandIndex(null), 2000)
|
||||
try {
|
||||
await navigator.clipboard.writeText(command)
|
||||
setCopiedCommandIndex(index)
|
||||
setTimeout(() => setCopiedCommandIndex(null), 2000)
|
||||
} catch {
|
||||
// Clipboard access denied — don't show false "Copied!" state
|
||||
}
|
||||
}
|
||||
|
||||
const handleInsert = () => {
|
||||
@@ -66,7 +71,8 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
||||
|
||||
const Icon = step ? stepTypeIcons[step.step_type as keyof typeof stepTypeIcons] : HelpCircle
|
||||
const hasRating = step && step.rating_count > 0
|
||||
const topReviews = reviews.filter(r => r.review_text).slice(0, 3)
|
||||
const allTextReviews = reviews.filter(r => r.review_text)
|
||||
const visibleReviews = showAllReviews ? allTextReviews : allTextReviews.slice(0, 3)
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
||||
@@ -228,18 +234,21 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
||||
)}
|
||||
|
||||
{/* Reviews */}
|
||||
{topReviews.length > 0 && (
|
||||
{visibleReviews.length > 0 && (
|
||||
<div>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-foreground">Reviews</h3>
|
||||
{reviews.length > 3 && (
|
||||
<button className="text-xs text-muted-foreground hover:text-foreground hover:underline">
|
||||
See all {reviews.length} reviews
|
||||
{allTextReviews.length > 3 && (
|
||||
<button
|
||||
onClick={() => setShowAllReviews(v => !v)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
|
||||
>
|
||||
{showAllReviews ? 'Show less' : `See all ${allTextReviews.length} reviews`}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{topReviews.map(review => (
|
||||
{visibleReviews.map(review => (
|
||||
<div key={review.id} className="rounded-lg border border-border bg-accent/50 p-3">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user