diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index d57fbde7..6e48061c 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -1,6 +1,11 @@ { - "extends": ["react-app", "eslint-config-prettier"], - "ignorePatterns": ["src-tauri/target"], + "extends": [ + "react-app", + "eslint-config-prettier" + ], + "ignorePatterns": [ + "src-tauri/target" + ], "rules": { // Accessibility rules "jsx-a11y/click-events-have-key-events": "off", @@ -11,11 +16,17 @@ "jsx-a11y/anchor-has-content": "off", "jsx-a11y/media-has-caption": "off", // React Hook rules - "react-hooks/exhaustive-deps": "off", + "react-hooks/exhaustive-deps": "warn", // Temporarily turn warnings into errors to suppress them "no-warning-comments": [ "error", - { "terms": ["todo", "fixme"], "location": "anywhere" } + { + "terms": [ + "todo", + "fixme" + ], + "location": "anywhere" + } ] } -} +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2c5b6bdd..d176aa29 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,6 +6,7 @@ import { ThemeProvider } from '@/contexts/ThemeContext'; import QueryClientProviders from '@/config/QueryClientProvider'; import { GlobalLoader } from './components/Loader/GlobalLoader'; import { InfoDialog } from './components/Dialog/InfoDialog'; +import ErrorBoundary from './components/ErrorBoundary'; import { useSelector } from 'react-redux'; import { RootState } from './app/store'; const App: React.FC = () => { @@ -21,7 +22,9 @@ const App: React.FC = () => { - + + + { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + console.error('ErrorBoundary caught an error:', error, errorInfo); + } + + handleReset = (): void => { + this.setState({ hasError: false, error: null }); + }; + + handleReload = (): void => { + window.location.reload(); + }; + + render(): ReactNode { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( +
+
+

+ Something went wrong +

+

+ An unexpected error occurred. You can try again or reload the + application. +

+ {this.state.error && ( +
+                                {this.state.error.message}
+                            
+ )} +
+ + +
+
+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/frontend/src/components/Media/ImageCard.tsx b/frontend/src/components/Media/ImageCard.tsx index 0cc6a715..f8cdd0a9 100644 --- a/frontend/src/components/Media/ImageCard.tsx +++ b/frontend/src/components/Media/ImageCard.tsx @@ -2,7 +2,7 @@ import { AspectRatio } from '@/components/ui/aspect-ratio'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { Check, Heart } from 'lucide-react'; -import { useCallback, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Image } from '@/types/Media'; import { ImageTags } from './ImageTags'; import { convertFileSrc } from '@tauri-apps/api/core'; @@ -17,7 +17,7 @@ interface ImageCardViewProps { imageIndex?: number; } -export function ImageCard({ +export const ImageCard = React.memo(function ImageCard({ image, className, isSelected = false, @@ -72,11 +72,10 @@ export function ImageCard({ ); -}; +}); diff --git a/frontend/src/components/Media/ZoomControls.tsx b/frontend/src/components/Media/ZoomControls.tsx index 9a4eeaff..27dd417c 100644 --- a/frontend/src/components/Media/ZoomControls.tsx +++ b/frontend/src/components/Media/ZoomControls.tsx @@ -9,7 +9,7 @@ interface ZoomControlsProps { showThumbnails: boolean; } -export const ZoomControls: React.FC = ({ +export const ZoomControls: React.FC = React.memo(({ onZoomIn, onZoomOut, onRotate, @@ -18,9 +18,8 @@ export const ZoomControls: React.FC = ({ }) => { return (
); -}; +}); diff --git a/frontend/src/hooks/Sortimage.ts b/frontend/src/hooks/Sortimage.ts index b1705219..5fa0d358 100644 --- a/frontend/src/hooks/Sortimage.ts +++ b/frontend/src/hooks/Sortimage.ts @@ -42,7 +42,7 @@ function useSortedImages(data: any): Image[] { const sortedImages = parseAndSortImageData(data); setSortedImages(sortedImages); - }, []); + }, [data]); return sortedImages; } diff --git a/frontend/src/hooks/useFolderOperations.tsx b/frontend/src/hooks/useFolderOperations.tsx index ff747ff0..f1579713 100644 --- a/frontend/src/hooks/useFolderOperations.tsx +++ b/frontend/src/hooks/useFolderOperations.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { usePictoMutation, usePictoQuery } from '@/hooks/useQueryExtension'; import { @@ -144,20 +144,20 @@ export const useFolderOperations = () => { /** * Toggle AI tagging for a folder */ - const toggleAITagging = (folder: FolderDetails) => { + const toggleAITagging = useCallback((folder: FolderDetails) => { if (folder.AI_Tagging) { disableAITaggingMutation.mutate(folder.folder_id); } else { enableAITaggingMutation.mutate(folder.folder_id); } - }; + }, [enableAITaggingMutation, disableAITaggingMutation]); /** * Delete a folder */ - const deleteFolder = (folderId: string) => { + const deleteFolder = useCallback((folderId: string) => { deleteFolderMutation.mutate(folderId); - }; + }, [deleteFolderMutation]); return { // Data