From 5a4d7e0ba9296f7da6712693720b8940a0d9e986 Mon Sep 17 00:00:00 2001 From: Hammer Date: Fri, 30 Jan 2026 03:00:17 +0000 Subject: [PATCH] fix: resolve ESLint errors for CI - Remove unused imports (Flag, Tag, Hash, User, FolderPlus, Check, Plus, Link, cn, formatDate, getPriorityLabel) - Remove unused variable (inbox in Sidebar) - Fix empty catch block with comment - Replace any types with proper Mock/Record types in tests - Suppress set-state-in-effect for intentional form state sync - Remove unused get parameter from zustand store --- dist/index.html | 2 +- src/components/AddTask.tsx | 2 +- src/components/CompletedSection.tsx | 1 - src/components/Sidebar.tsx | 5 ++--- src/components/TaskDetail.tsx | 6 ++++-- src/lib/__tests__/api.test.ts | 28 +++++++++++++--------------- src/pages/Board.tsx | 3 ++- src/pages/Login.tsx | 2 +- src/pages/Project.tsx | 2 +- src/stores/auth.ts | 2 +- 10 files changed, 26 insertions(+), 27 deletions(-) diff --git a/dist/index.html b/dist/index.html index 9e15ea1..db73129 100644 --- a/dist/index.html +++ b/dist/index.html @@ -6,7 +6,7 @@ Todo App - + diff --git a/src/components/AddTask.tsx b/src/components/AddTask.tsx index 13d52b5..995ae50 100644 --- a/src/components/AddTask.tsx +++ b/src/components/AddTask.tsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect } from 'react'; -import { Plus, Calendar, Flag, Tag, X } from 'lucide-react'; +import { Plus, Calendar, X } from 'lucide-react'; import type { Priority } from '@/types'; import { cn, getPriorityColor } from '@/lib/utils'; import { useTasksStore } from '@/stores/tasks'; diff --git a/src/components/CompletedSection.tsx b/src/components/CompletedSection.tsx index 37824e8..4b35047 100644 --- a/src/components/CompletedSection.tsx +++ b/src/components/CompletedSection.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; import { ChevronRight, ChevronDown, Check } from 'lucide-react'; import type { Task } from '@/types'; -import { cn } from '@/lib/utils'; import { useTasksStore } from '@/stores/tasks'; interface CompletedSectionProps { diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 9dc3b0a..c6ef3eb 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react'; import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Inbox, Calendar, CalendarDays, Plus, ChevronDown, ChevronRight, - Hash, Settings, LogOut, User, FolderPlus, Tag, X, Check, + Settings, LogOut, Tag, X, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; import { cn } from '@/lib/utils'; @@ -56,7 +56,7 @@ export function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { setIsCollapsed(next); try { localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(next)); - } catch {} + } catch { /* ignore localStorage errors */ } }; const handleNavClick = () => { @@ -94,7 +94,6 @@ export function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { navigate('/login'); }; - const inbox = projects.find(p => p.isInbox); const regularProjects = projects.filter(p => !p.isInbox); const navItems = [ diff --git a/src/components/TaskDetail.tsx b/src/components/TaskDetail.tsx index a0b3784..755b4ec 100644 --- a/src/components/TaskDetail.tsx +++ b/src/components/TaskDetail.tsx @@ -4,7 +4,7 @@ import { Tag, MessageSquare, ChevronRight, AlertCircle, Layers } from 'lucide-react'; import type { Task, Priority, Comment, Section } from '@/types'; -import { cn, formatDate, getPriorityColor, getPriorityLabel } from '@/lib/utils'; +import { cn, getPriorityColor } from '@/lib/utils'; import { useTasksStore } from '@/stores/tasks'; import { api } from '@/lib/api'; @@ -29,7 +29,9 @@ export function TaskDetail({ task, onClose }: TaskDetailProps) { const titleRef = useRef(null); const panelRef = useRef(null); + // Sync local state from task prop - intentional setState in effect for controlled form useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect -- syncing local form state from prop setTitle(task.title); setDescription(task.description || ''); setDueDate(task.dueDate ? task.dueDate.slice(0, 10) : ''); @@ -50,7 +52,7 @@ export function TaskDetail({ task, onClose }: TaskDetailProps) { if (projectId) { const proj = projects.find(p => p.id === projectId); if (proj?.sections) { - setAvailableSections(proj.sections); + setAvailableSections(proj.sections); // eslint-disable-line react-hooks/set-state-in-effect } else { api.getProject(projectId).then((p) => { setAvailableSections(p.sections || []); diff --git a/src/lib/__tests__/api.test.ts b/src/lib/__tests__/api.test.ts index d29cd64..cb1e849 100644 --- a/src/lib/__tests__/api.test.ts +++ b/src/lib/__tests__/api.test.ts @@ -1,25 +1,23 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; +import type { Mock } from 'vitest'; // We need to test the ApiClient class, so we import the module fresh // The api.ts uses import.meta.env.PROD which is false in test // So API_BASE = '/api' and AUTH_BASE = '' -let ApiClient: any; -let api: any; +let api: Record unknown>; beforeEach(async () => { vi.resetModules(); vi.stubGlobal('fetch', vi.fn()); const mod = await import('@/lib/api'); - api = mod.api; - // Get the class from the singleton - ApiClient = (api as any).constructor; + api = mod.api as unknown as Record unknown>; }); -function mockFetchResponse(data: any, ok = true, status = 200) { - (fetch as any).mockResolvedValueOnce({ +function mockFetchResponse(data: unknown, ok = true, _status = 200) { + (fetch as Mock).mockResolvedValueOnce({ ok, - status, + status: _status, json: () => Promise.resolve(data), }); } @@ -42,7 +40,7 @@ describe('ApiClient', () => { }); it('throws on failed login', async () => { - (fetch as any).mockResolvedValueOnce({ + (fetch as Mock).mockResolvedValueOnce({ ok: false, json: () => Promise.resolve({ message: 'Invalid credentials' }), }); @@ -78,7 +76,7 @@ describe('ApiClient', () => { }); it('returns null when not authenticated', async () => { - (fetch as any).mockResolvedValueOnce({ + (fetch as Mock).mockResolvedValueOnce({ ok: false, json: () => Promise.resolve({}), }); @@ -88,7 +86,7 @@ describe('ApiClient', () => { }); it('returns null on network error', async () => { - (fetch as any).mockRejectedValueOnce(new Error('Network error')); + (fetch as Mock).mockRejectedValueOnce(new Error('Network error')); const result = await api.getSession(); expect(result).toBeNull(); @@ -165,7 +163,7 @@ describe('ApiClient', () => { await api.getTasks({ projectId: 'proj1', completed: false, today: true }); - const calledUrl = (fetch as any).mock.calls[0][0] as string; + const calledUrl = (fetch as Mock).mock.calls[0][0] as string; expect(calledUrl).toContain('/api/tasks?'); expect(calledUrl).toContain('projectId=proj1'); expect(calledUrl).toContain('completed=false'); @@ -259,14 +257,14 @@ describe('ApiClient', () => { await api.getProjects(); - const headers = (fetch as any).mock.calls[0][1].headers; + const headers = (fetch as Mock).mock.calls[0][1].headers; expect(headers['Authorization']).toBeUndefined(); }); }); describe('Error handling', () => { it('throws error with message from non-200 response', async () => { - (fetch as any).mockResolvedValueOnce({ + (fetch as Mock).mockResolvedValueOnce({ ok: false, json: () => Promise.resolve({ error: 'Not found' }), }); @@ -275,7 +273,7 @@ describe('ApiClient', () => { }); it('throws "Request failed" when error body is unparseable', async () => { - (fetch as any).mockResolvedValueOnce({ + (fetch as Mock).mockResolvedValueOnce({ ok: false, json: () => Promise.reject(new Error('parse error')), }); diff --git a/src/pages/Board.tsx b/src/pages/Board.tsx index 7e71706..46d5c10 100644 --- a/src/pages/Board.tsx +++ b/src/pages/Board.tsx @@ -16,7 +16,7 @@ interface BoardViewProps { } function TaskCard({ task, muted }: { task: Task; muted?: boolean }) { - const { toggleComplete, setSelectedTask } = useTasksStore(); + const { setSelectedTask } = useTasksStore(); const overdue = !task.isCompleted && isOverdue(task.dueDate); return ( @@ -84,6 +84,7 @@ function EditableHeader({ title, count, sectionId, + // eslint-disable-next-line @typescript-eslint/no-unused-vars projectId, onRename, onDelete, diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 9cdfb37..7513f65 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { useNavigate, Link } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useAuthStore } from '@/stores/auth'; export function LoginPage() { diff --git a/src/pages/Project.tsx b/src/pages/Project.tsx index 28efcef..862fb7e 100644 --- a/src/pages/Project.tsx +++ b/src/pages/Project.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, useRef } from 'react'; import { useParams, useNavigate, useLocation } from 'react-router-dom'; -import { LayoutList, LayoutGrid, Plus, Pencil, Trash2, AlertCircle } from 'lucide-react'; +import { LayoutList, LayoutGrid, Pencil, Trash2, AlertCircle } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useTasksStore } from '@/stores/tasks'; import { TaskItem } from '@/components/TaskItem'; diff --git a/src/stores/auth.ts b/src/stores/auth.ts index 1390a98..1fc030c 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -17,7 +17,7 @@ interface AuthState { export const useAuthStore = create()( persist( - (set, get) => ({ + (set) => ({ user: null, isLoading: true, isAuthenticated: false,