Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
}
]
}
}
}
5 changes: 4 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand All @@ -21,7 +22,9 @@ const App: React.FC = () => {
<ThemeProvider>
<QueryClientProviders>
<BrowserRouter>
<AppRoutes />
<ErrorBoundary>
<AppRoutes />
</ErrorBoundary>
</BrowserRouter>
<GlobalLoader loading={loading} message={message} />
<InfoDialog
Expand Down
143 changes: 143 additions & 0 deletions frontend/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Component, ErrorInfo, ReactNode } from 'react';

interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
}

interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
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 (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
padding: '2rem',
textAlign: 'center',
backgroundColor: '#0a0a0a',
color: '#e5e5e5',
}}
>
<div
style={{
maxWidth: '480px',
padding: '2rem',
borderRadius: '12px',
backgroundColor: '#1a1a1a',
border: '1px solid #333',
}}
>
<h2
style={{
fontSize: '1.5rem',
fontWeight: 600,
marginBottom: '0.75rem',
color: '#f87171',
}}
>
Something went wrong
</h2>
<p
style={{
fontSize: '0.95rem',
color: '#a1a1aa',
marginBottom: '1.5rem',
lineHeight: 1.5,
}}
>
An unexpected error occurred. You can try again or reload the
application.
</p>
{this.state.error && (
<pre
style={{
fontSize: '0.75rem',
color: '#f87171',
backgroundColor: '#2a1a1a',
padding: '0.75rem',
borderRadius: '8px',
marginBottom: '1.5rem',
overflow: 'auto',
maxHeight: '120px',
textAlign: 'left',
}}
>
{this.state.error.message}
</pre>
)}
<div
style={{ display: 'flex', gap: '0.75rem', justifyContent: 'center' }}
>
<button
onClick={this.handleReset}
style={{
padding: '0.5rem 1.25rem',
borderRadius: '8px',
border: '1px solid #555',
backgroundColor: 'transparent',
color: '#e5e5e5',
cursor: 'pointer',
fontSize: '0.875rem',
}}
>
Try Again
</button>
<button
onClick={this.handleReload}
style={{
padding: '0.5rem 1.25rem',
borderRadius: '8px',
border: 'none',
backgroundColor: '#3b82f6',
color: '#fff',
cursor: 'pointer',
fontSize: '0.875rem',
}}
>
Reload App
</button>
</div>
</div>
</div>
);
}

return this.props.children;
}
}

export default ErrorBoundary;
11 changes: 5 additions & 6 deletions frontend/src/components/Media/ImageCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,7 +17,7 @@ interface ImageCardViewProps {
imageIndex?: number;
}

export function ImageCard({
export const ImageCard = React.memo(function ImageCard({
image,
className,
isSelected = false,
Expand Down Expand Up @@ -72,11 +72,10 @@ export function ImageCard({
<Button
variant="ghost"
size="icon"
className={`cursor-pointer rounded-full p-2.5 text-white transition-all duration-300 ${
image.isFavourite
className={`cursor-pointer rounded-full p-2.5 text-white transition-all duration-300 ${image.isFavourite
? 'bg-rose-500/80 hover:bg-rose-600 hover:shadow-lg'
: 'bg-white/10 hover:bg-white/20 hover:shadow-lg'
}`}
}`}
onClick={(e) => {
console.log(image);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove debug console.log before merging.

This statement logs the full image object (containing file paths and metadata) to the console on every favourite-button click. It's a debug artifact that should not ship.

🐛 Proposed fix
             onClick={(e) => {
-              console.log(image);
               e.stopPropagation();
               handleToggleFavourite();
             }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(image);
onClick={(e) => {
e.stopPropagation();
handleToggleFavourite();
}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/Media/ImageCard.tsx` at line 80, Remove the debug
console.log that prints the entire image object in the ImageCard component;
locate the log statement (console.log(image)) inside the ImageCard's
favorite-button click handler (e.g., onFavoriteToggle / handleFavoriteClick) and
delete it so sensitive file paths/metadata are no longer emitted to the console
before merging.

e.stopPropagation();
Expand All @@ -102,4 +101,4 @@ export function ImageCard({
</div>
</div>
);
}
});
18 changes: 8 additions & 10 deletions frontend/src/components/Media/MediaThumbnails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface MediaThumbnailsProps {
type?: string;
}

export const MediaThumbnails: React.FC<MediaThumbnailsProps> = ({
export const MediaThumbnails: React.FC<MediaThumbnailsProps> = React.memo(({
images,
currentIndex,
showThumbnails,
Expand Down Expand Up @@ -95,9 +95,8 @@ export const MediaThumbnails: React.FC<MediaThumbnailsProps> = ({
<div className="absolute bottom-0 w-full">
<div
ref={scrollContainerRef}
className={`flex w-full items-center gap-2 overflow-x-auto bg-black/70 py-3 backdrop-blur-md transition-all duration-300 ${
showThumbnails ? 'opacity-100' : 'opacity-0'
} px-[calc(50%-3.5rem)]`}
className={`flex w-full items-center gap-2 overflow-x-auto bg-black/70 py-3 backdrop-blur-md transition-all duration-300 ${showThumbnails ? 'opacity-100' : 'opacity-0'
} px-[calc(50%-3.5rem)]`}
>
{images.map((image, index) => (
<div
Expand All @@ -110,11 +109,10 @@ export const MediaThumbnails: React.FC<MediaThumbnailsProps> = ({
}
}}
onClick={() => onThumbnailClick(index)}
className={`relative h-20 w-28 flex-shrink-0 overflow-hidden rounded-lg ${
index === currentIndex
? 'ring-2 ring-blue-500 ring-offset-1 ring-offset-black'
: 'opacity-70 hover:opacity-100'
} cursor-pointer transition-all duration-200 hover:scale-105`}
className={`relative h-20 w-28 flex-shrink-0 overflow-hidden rounded-lg ${index === currentIndex
? 'ring-2 ring-blue-500 ring-offset-1 ring-offset-black'
: 'opacity-70 hover:opacity-100'
} cursor-pointer transition-all duration-200 hover:scale-105`}
>
<img
src={convertFileSrc(image.thumbnailPath) || '/placeholder.svg'}
Expand All @@ -131,4 +129,4 @@ export const MediaThumbnails: React.FC<MediaThumbnailsProps> = ({
</div>
</div>
);
};
});
4 changes: 2 additions & 2 deletions frontend/src/components/Media/NavigationButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface NavigationButtonsProps {
onNext: () => void;
}

export const NavigationButtons: React.FC<NavigationButtonsProps> = ({
export const NavigationButtons: React.FC<NavigationButtonsProps> = React.memo(({
onPrevious,
onNext,
}) => {
Expand All @@ -33,4 +33,4 @@ export const NavigationButtons: React.FC<NavigationButtonsProps> = ({
</button>
</>
);
};
});
9 changes: 4 additions & 5 deletions frontend/src/components/Media/ZoomControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface ZoomControlsProps {
showThumbnails: boolean;
}

export const ZoomControls: React.FC<ZoomControlsProps> = ({
export const ZoomControls: React.FC<ZoomControlsProps> = React.memo(({
onZoomIn,
onZoomOut,
onRotate,
Expand All @@ -18,9 +18,8 @@ export const ZoomControls: React.FC<ZoomControlsProps> = ({
}) => {
return (
<div
className={`absolute ${
showThumbnails ? 'bottom-32' : 'bottom-12'
} left-1/2 z-10 flex -translate-x-1/2 transform flex-col gap-4 rounded-xl bg-black/50 p-3 backdrop-blur-md transition-all duration-300`}
className={`absolute ${showThumbnails ? 'bottom-32' : 'bottom-12'
} left-1/2 z-10 flex -translate-x-1/2 transform flex-col gap-4 rounded-xl bg-black/50 p-3 backdrop-blur-md transition-all duration-300`}
>
<div className="flex gap-2">
<button
Expand Down Expand Up @@ -60,4 +59,4 @@ export const ZoomControls: React.FC<ZoomControlsProps> = ({
</div>
</div>
);
};
});
2 changes: 1 addition & 1 deletion frontend/src/hooks/Sortimage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function useSortedImages(data: any): Image[] {
const sortedImages = parseAndSortImageData(data);

setSortedImages(sortedImages);
}, []);
}, [data]);

return sortedImages;
}
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/hooks/useFolderOperations.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand Down