From 26b1ebc33cef7616815dfce65d4055444df07d53 Mon Sep 17 00:00:00 2001 From: Shannon Lockett <155769623+shazzar00ni@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:14:34 +0800 Subject: [PATCH] Consolidate dual ESLint config to flat config - Merge .eslintrc.cjs rules into eslint.config.js (ESLint 9+ flat config) - Add react-hooks plugin and rules - Add @typescript-eslint/no-explicit-any rule - Set no-unused-vars to error with argsIgnorePattern: ^_ - Delete legacy .eslintrc.cjs file - Fix react-hooks/set-state-in-effect violations by using lazy initializers - Update CONTRIBUTING.md with ESLint 9+ requirement --- .eslintrc.cjs | 17 ------------ CONTRIBUTING.md | 1 + eslint.config.js | 44 +++++++++++++++----------------- src/components/CookieConsent.tsx | 14 ++++------ src/components/UploadDemo.tsx | 26 +++++++++---------- src/lib/ThemeContext.tsx | 14 +++++----- 6 files changed, 45 insertions(+), 71 deletions(-) delete mode 100644 .eslintrc.cjs diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 382a4655..00000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], - '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - }, -}; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47429e57..5985bce1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,6 +9,7 @@ Thank you for your interest in contributing to DocuGen! This document provides g - Node.js 18+ - npm 9+ - Git +- ESLint 9+ (for local development, ESLint uses flat config) ### Development Setup diff --git a/eslint.config.js b/eslint.config.js index e24d1e26..41e482aa 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,9 +1,23 @@ import js from '@eslint/js'; import typescript from '@typescript-eslint/eslint-plugin'; import typescriptParser from '@typescript-eslint/parser'; +import reactHooks from 'eslint-plugin-react-hooks'; import reactRefresh from 'eslint-plugin-react-refresh'; import globals from 'globals'; +const browserGlobals = { + window: 'readonly', + document: 'readonly', + console: 'readonly', + localStorage: 'readonly', + navigator: 'readonly', + setTimeout: 'readonly', + File: 'readonly', + Element: 'readonly', + HTMLInputElement: 'readonly', + React: 'readonly', +}; + export default [ js.configs.recommended, { @@ -33,21 +47,9 @@ export default [ }, }, globals: { - window: 'readonly', - document: 'readonly', - console: 'readonly', - localStorage: 'readonly', - navigator: 'readonly', - setTimeout: 'readonly', - File: 'readonly', - Element: 'readonly', - HTMLInputElement: 'readonly', - React: 'readonly', + ...browserGlobals, }, }, - rules: { - 'no-unused-vars': 'warn', - }, }, { files: ['**/*.{ts,tsx}'], @@ -61,26 +63,20 @@ export default [ }, }, globals: { - window: 'readonly', - document: 'readonly', - console: 'readonly', - localStorage: 'readonly', - navigator: 'readonly', - setTimeout: 'readonly', - File: 'readonly', - Element: 'readonly', - HTMLInputElement: 'readonly', - React: 'readonly', + ...browserGlobals, }, }, plugins: { '@typescript-eslint': typescript, + 'react-hooks': reactHooks, 'react-refresh': reactRefresh, }, rules: { ...typescript.configs.recommended.rules, - '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + ...reactHooks.configs.recommended.rules, }, }, ]; diff --git a/src/components/CookieConsent.tsx b/src/components/CookieConsent.tsx index 194188f1..fa575e28 100644 --- a/src/components/CookieConsent.tsx +++ b/src/components/CookieConsent.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; import { Button } from './ui/Button'; @@ -10,18 +10,14 @@ import { Button } from './ui/Button'; * @returns Cookie consent banner component */ export function CookieConsent() { - const [showConsent, setShowConsent] = useState(false); - - useEffect(() => { + const [showConsent, setShowConsent] = useState(() => { try { const hasConsented = localStorage.getItem('docugen-cookie-consent'); - if (!hasConsented) { - setShowConsent(true); - } + return !hasConsented; } catch { - setShowConsent(true); + return true; } - }, []); + }); /** * Handles accepting all cookies. diff --git a/src/components/UploadDemo.tsx b/src/components/UploadDemo.tsx index 5172e428..31fb21e3 100644 --- a/src/components/UploadDemo.tsx +++ b/src/components/UploadDemo.tsx @@ -26,6 +26,19 @@ export function UploadDemo() { const [isComplete, setIsComplete] = useState(false); const fileInputRef = useRef(null); + /** + * Simulates the file upload process with a 2-second delay. + * Sets uploading state to true immediately, then transitions to complete state after timeout. + * Provides visual feedback during the upload process with progress indication. + */ + const simulateUpload = () => { + setIsUploading(true); + setTimeout(() => { + setIsUploading(false); + setIsComplete(true); + }, 2000); + }; + /** * Handles the drag over event for the dropzone. * Prevents default browser behavior and sets the dragging state to true @@ -81,19 +94,6 @@ export function UploadDemo() { } }, []); - /** - * Simulates the file upload process with a 2-second delay. - * Sets uploading state to true immediately, then transitions to complete state after timeout. - * Provides visual feedback during the upload process with progress indication. - */ - const simulateUpload = () => { - setIsUploading(true); - setTimeout(() => { - setIsUploading(false); - setIsComplete(true); - }, 2000); - }; - /** * Resets the upload demo to its initial state. * Clears the selected file and resets all state variables to allow for new uploads. diff --git a/src/lib/ThemeContext.tsx b/src/lib/ThemeContext.tsx index abd8ef2b..08bf84cf 100644 --- a/src/lib/ThemeContext.tsx +++ b/src/lib/ThemeContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useEffect, useState, type ReactNode } from 'react'; +import { createContext, useState, type ReactNode } from 'react'; export type Theme = 'dark' | 'light'; @@ -71,13 +71,11 @@ interface ThemeProviderProps { * ``` */ export function ThemeProvider({ children }: ThemeProviderProps) { - const [theme, setTheme] = useState('dark'); - - useEffect(() => { - const initialTheme = getInitialTheme(); - setTheme(initialTheme); - applyTheme(initialTheme); - }, []); + const [theme, setTheme] = useState(() => { + const initial = getInitialTheme(); + applyTheme(initial); + return initial; + }); const toggleTheme = () => { const newTheme = theme === 'dark' ? 'light' : 'dark';