fix: resolve all 8 pre-existing lint errors (closes #29)

Fixed @typescript-eslint/no-explicit-any (4 occurrences):
- FolderEditModal.tsx: proper error type checking instead of any
- StepForm.tsx: explicit union type for visibility select
- StepLibraryBrowser.tsx: explicit union types for stepType and sortBy selects

Fixed react-hooks/set-state-in-effect (1 occurrence):
- NodeEditorModal.tsx: replaced useEffect with direct state comparison

Fixed @typescript-eslint/no-unused-vars (3 occurrences):
- NodeEditorModal.tsx: removed unused useEffect import
- NodeEditorModal.tsx: added eslint-disable for intentionally destructured children
- usePermissions.ts: removed unused _tree parameter from canDeleteTree
- TreeLibraryPage.tsx: updated canDeleteTree call site

Fixed @typescript-eslint/no-empty-object-type (1 occurrence):
- types/step.ts: changed empty interface to type alias

Verification:
- npm run lint: 0 errors (9 warnings are intentional exhaustive-deps)
- npm run build: succeeds
- TypeScript compilation: passes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-06 17:38:38 -05:00
parent a674ba7bcb
commit 1897641082
7 changed files with 19 additions and 14 deletions

View File

@@ -159,8 +159,11 @@ export function FolderEditModal({
onClose()
// Dispatch event to refresh folder list
window.dispatchEvent(new Event('folder-changed'))
} catch (err: any) {
setError(err.response?.data?.detail || 'Failed to save folder')
} catch (err) {
const errorMessage = err instanceof Error && 'response' in err
? (err as { response?: { data?: { detail?: string } } }).response?.data?.detail
: undefined
setError(errorMessage || 'Failed to save folder')
} finally {
setIsSubmitting(false)
}

View File

@@ -356,7 +356,7 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
<select
id="visibility"
value={visibility}
onChange={(e) => setVisibility(e.target.value as any)}
onChange={(e) => setVisibility(e.target.value as 'private' | 'team' | 'public')}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
>
<option value="private">Private (only me)</option>

View File

@@ -164,7 +164,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
<select
aria-label="Filter by step type"
value={selectedStepType || ''}
onChange={(e) => setSelectedStepType((e.target.value as any) || undefined)}
onChange={(e) => setSelectedStepType((e.target.value as 'decision' | 'action' | 'solution') || undefined)}
className="rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
>
<option value="">All Types</option>
@@ -190,7 +190,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
<select
aria-label="Sort steps by"
value={sortBy}
onChange={(e) => setSortBy(e.target.value as any)}
onChange={(e) => setSortBy(e.target.value as 'recent' | 'popular' | 'highest_rated' | 'most_used')}
className="rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
>
<option value="recent">Most Recent</option>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react'
import { useState, useCallback } from 'react'
import { Modal } from '@/components/common/Modal'
import { useTreeEditorStore } from '@/store/treeEditorStore'
import { NodeFormDecision } from './NodeFormDecision'
@@ -18,12 +18,14 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
const nodeErrors = validationErrors.filter(e => e.nodeId === node.id)
// Local draft state - changes are NOT persisted until "Done" is clicked
// Reset draft when node ID changes (switching to a different node)
const [draft, setDraft] = useState<TreeStructure>(() => structuredClone(node))
const [lastNodeId, setLastNodeId] = useState(node.id)
// Reset draft when node changes (e.g., external update)
useEffect(() => {
if (node.id !== lastNodeId) {
setDraft(structuredClone(node))
}, [node.id]) // Only reset when switching to a different node
setLastNodeId(node.id)
}
const handleUpdate = useCallback((updates: Partial<TreeStructure>) => {
setDraft(prev => ({ ...prev, ...updates }))
@@ -33,6 +35,7 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
// Commit all draft changes to the store
// IMPORTANT: Exclude 'children' from the update - children are managed separately
// by addNode/deleteNode and we don't want to overwrite them with stale draft data
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { children, ...draftWithoutChildren } = draft
updateNode(node.id, draftWithoutChildren)
onClose()

View File

@@ -55,7 +55,7 @@ export function usePermissions() {
return false
},
canDeleteTree: (_tree: { author_id: string | null }) => {
canDeleteTree: () => {
if (!user) return false
return user.is_super_admin
},

View File

@@ -364,7 +364,7 @@ export function TreeLibraryPage() {
<Pencil className="h-4 w-4" />
</Link>
)}
{canDeleteTree({ author_id: tree.author_id }) && (
{canDeleteTree() && (
<button
type="button"
onClick={() => {

View File

@@ -119,6 +119,5 @@ export interface Rating {
user_name?: string
}
export interface Review extends Rating {
// Same as Rating, just an alias for clarity
}
// Review is an alias for Rating
export type Review = Rating