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
42 changes: 29 additions & 13 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 28 additions & 5 deletions frontend/src/components/Media/MediaInfoPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { open } from '@tauri-apps/plugin-shell';
import {
X,
Expand Down Expand Up @@ -26,6 +26,19 @@ export const MediaInfoPanel: React.FC<MediaInfoPanelProps> = ({
currentIndex,
totalImages,
}) => {
const [isVisible, setIsVisible] = useState(show);

useEffect(() => {
if (show) {
setIsVisible(true);
}
}, [show]);
Comment on lines +29 to +35
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 | 🔴 Critical

Critical: Handle show becoming false to prevent stuck panel.

The current effect only sets isVisible to true when show becomes true, but doesn't handle the case when show becomes false. This creates a critical desynchronization bug:

Scenario: If the parent component sets show={false} directly (without triggering handleClose), the panel will become stuck visible because:

  1. show becomes false (from parent)
  2. isVisible remains true (no effect updates it)
  3. The rendering guard on line 74 still renders the component (only returns null when BOTH are false)
  4. The panel displays at full opacity indefinitely (line 84 uses isVisible to control visibility classes)

Impact: The panel cannot be closed if the parent updates show directly, breaking the close functionality in certain usage patterns.

🔎 Proposed fix
  useEffect(() => {
    if (show) {
      setIsVisible(true);
+   } else {
+     // Animate out when parent sets show to false
+     setIsVisible(false);
    }
  }, [show]);

Alternatively, if you want to preserve the exit animation when the parent closes directly:

  useEffect(() => {
    if (show) {
      setIsVisible(true);
+   } else {
+     // Trigger exit animation, but let parent control final unmount
+     const timer = setTimeout(() => setIsVisible(false), 0);
+     return () => clearTimeout(timer);
    }
  }, [show]);
📝 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
const [isVisible, setIsVisible] = useState(show);
useEffect(() => {
if (show) {
setIsVisible(true);
}
}, [show]);
const [isVisible, setIsVisible] = useState(show);
useEffect(() => {
if (show) {
setIsVisible(true);
} else {
// Animate out when parent sets show to false
setIsVisible(false);
}
}, [show]);
🤖 Prompt for AI Agents
In @frontend/src/components/Media/MediaInfoPanel.tsx around lines 29 - 35, The
effect watching show only sets isVisible true and never responds to show
becoming false, which can leave the panel stuck open; update the useEffect that
references show to setIsVisible(show) (or when preserving exit animation, set
isVisible true on show true and on show false start the exit animation by
calling setIsVisible(false) after the animation timeout) so isVisible always
follows the parent-driven show state; adjust logic around the rendering guard
(the condition that returns null when both show and isVisible are false) to
remain correct with the new effect and keep references to isVisible,
setIsVisible, and the useEffect callback.


const handleClose = () => {
setIsVisible(false);
setTimeout(onClose, 300); // must match animation duration
};

const getFormattedDate = () => {
if (currentImage?.metadata?.date_created) {
return new Date(currentImage.metadata.date_created).toLocaleDateString(
Expand Down Expand Up @@ -58,19 +71,29 @@ export const MediaInfoPanel: React.FC<MediaInfoPanelProps> = ({
}
};

if (!show) return null;
if (!show && !isVisible) return null;


return (
<div className="animate-in slide-in-from-left absolute top-10 left-6 z-50 w-[350px] rounded-xl border border-white/10 bg-black/60 p-6 shadow-xl backdrop-blur-lg transition-all duration-300">
<div
className={`
absolute top-10 left-6 z-50 w-[350px]
rounded-xl border border-white/10 bg-black/60 p-6
shadow-xl backdrop-blur-lg
transform transition-all duration-300 ease-in-out
${isVisible ? 'opacity-100 translate-x-0' : 'opacity-0 -translate-x-6'}
`}
>
<div className="mb-4 flex items-center justify-between border-b border-white/10 pb-3">
<h3 className="text-xl font-medium text-white">Image Details</h3>
<button
onClick={onClose}
onClick={handleClose}
className="text-white/70 hover:text-white"
aria-label="Close info panel"
>
<X className="h-5 w-5" />
</button>

</div>

<div className="space-y-4 text-sm">
Expand Down Expand Up @@ -106,7 +129,7 @@ export const MediaInfoPanel: React.FC<MediaInfoPanelProps> = ({
<div className="min-w-0 flex-1">
<p className="text-xs text-white/50">Location</p>
{currentImage?.metadata?.latitude &&
currentImage?.metadata?.longitude ? (
currentImage?.metadata?.longitude ? (
<button
type="button"
onClick={handleLocationClick}
Expand Down
57 changes: 57 additions & 0 deletions frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect, useState } from 'react';

type ScrollToTopButtonProps = {
scrollContainerRef?: React.RefObject<HTMLElement | null>;
threshold?: number;
};

export const ScrollToTopButton = ({
scrollContainerRef,
threshold = 300,
}: ScrollToTopButtonProps) => {
const [visible, setVisible] = useState(false);

useEffect(() => {
const scroller = scrollContainerRef?.current || window;

const handleScroll = () => {
const scrollTop =
scroller instanceof Window
? window.scrollY
: scroller.scrollTop;

setVisible(scrollTop > threshold);
};

scroller.addEventListener('scroll', handleScroll);
return () => scroller.removeEventListener('scroll', handleScroll);
}, [scrollContainerRef, threshold]);
Comment on lines +14 to +28
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

🧩 Analysis chain

🏁 Script executed:

cat frontend/tsconfig.json | jq '.compilerOptions | {strict, strictNullChecks, strictFunctionTypes}'

Repository: AOSSIE-Org/PictoPy

Length of output: 119


🏁 Script executed:

cat -n frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 1821


🏁 Script executed:

cat frontend/tsconfig.json

Repository: AOSSIE-Org/PictoPy

Length of output: 804


🏁 Script executed:

# Check the actual types being used - both Window and HTMLElement should inherit from EventTarget
# which has addEventListener and removeEventListener
rg -A 5 "addEventListener" frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 307


Fix dependency array to track actual ref value changes.

The dependency array should include scrollContainerRef?.current instead of scrollContainerRef. Since scrollContainerRef is a ref object with stable identity, changes to its .current property won't trigger a re-run of the effect. If the container element changes after mount, the scroll event listener won't be updated.

The type safety concern about incompatible addEventListener signatures is not applicable here—both Window and HTMLElement implement the EventTarget interface with compatible method signatures. With "strict": true enabled in your tsconfig, TypeScript will catch actual type errors.

Update line 28 to: }, [scrollContainerRef?.current, threshold]);

🤖 Prompt for AI Agents
In @frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx around lines
14 - 28, The effect that attaches the scroll listener uses scrollContainerRef
but doesn't depend on its .current, so replacing scrollContainerRef?.current
into the dependency array will ensure the effect reruns when the actual DOM
container changes; update the useEffect dependency list to include
scrollContainerRef?.current and threshold so the listener attached by
handleScroll (which reads scroller = scrollContainerRef?.current || window and
calls setVisible based on scrollTop) is cleaned up and re-attached for the new
element.


const scrollToTop = () => {
const scroller = scrollContainerRef?.current;

if (scroller) {
scroller.scrollTo({ top: 0, behavior: 'smooth' });
} else {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
};

if (!visible) return null;

return (
<button
onClick={scrollToTop}
aria-label="Scroll to top"
className="
fixed top-35 right-15 z-50
Comment on lines +46 to +47
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

🧩 Analysis chain

🏁 Script executed:

# Find and examine Tailwind configuration
find . -type f \( -name "tailwind.config.*" -o -name "tailwind.config.js" -o -name "tailwind.config.ts" \) 2>/dev/null

Repository: AOSSIE-Org/PictoPy

Length of output: 121


🏁 Script executed:

# Look at the ScrollToTopButton component file
find . -path "*/frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx" -type f

Repository: AOSSIE-Org/PictoPy

Length of output: 127


🏁 Script executed:

# Search for custom spacing definitions across config files
find . -type f \( -name "*.config.*" -o -name "postcss.config.*" \) | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 321


🏁 Script executed:

# Read the ScrollToTopButton component
cat -n ./frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx

Repository: AOSSIE-Org/PictoPy

Length of output: 1821


🏁 Script executed:

# Search for Tailwind config in frontend directory
find ./frontend -name "tailwind.config.*" -o -name "*tailwind*" -type f 2>/dev/null | grep -v node_modules

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Look for any CSS configuration or custom Tailwind setup in frontend
find ./frontend -type f \( -name "*.config.*" -o -name "vite.config.*" \) | xargs ls -la

Repository: AOSSIE-Org/PictoPy

Length of output: 285


🏁 Script executed:

# Check if there's a root-level tailwind config
find . -maxdepth 2 -name "tailwind.config.*" -type f

Repository: AOSSIE-Org/PictoPy

Length of output: 121


🏁 Script executed:

# Read the frontend vite.config to see if Tailwind is configured there
cat ./frontend/vite.config.ts

Repository: AOSSIE-Org/PictoPy

Length of output: 869


🏁 Script executed:

# Check frontend package.json for Tailwind setup
cat ./frontend/package.json | grep -A 5 -B 5 tailwind || echo "Not found in package.json"

Repository: AOSSIE-Org/PictoPy

Length of output: 1169


🏁 Script executed:

# Read one of the found tailwind configs to understand the structure
cat ./landing-page/tailwind.config.js

Repository: AOSSIE-Org/PictoPy

Length of output: 1477


🏁 Script executed:

# Search the entire frontend directory for any tailwind config file
find ./frontend -type f -name "tailwind.config.*"

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Search for top-35 or right-15 definitions in the codebase
rg "top-35|right-15" --type-list | head -5
rg "top-35|right-15"

Repository: AOSSIE-Org/PictoPy

Length of output: 567


🏁 Script executed:

# Check if there's a CSS file defining these classes
find ./frontend -type f \( -name "*.css" -o -name "*.scss" \) | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 84


🏁 Script executed:

# Check the CSS file to see if custom classes are defined there
cat ./frontend/src/App.css

Repository: AOSSIE-Org/PictoPy

Length of output: 7941


🏁 Script executed:

# Also check for any tailwind directives or custom CSS in the main index/entry files
find ./frontend/src -maxdepth 1 -type f \( -name "*.css" -o -name "main.*" -o -name "index.*" \)

Repository: AOSSIE-Org/PictoPy

Length of output: 108


Replace non-standard Tailwind spacing classes with valid alternatives.

The classes top-35 and right-15 are not standard Tailwind spacing values and are not defined in your Tailwind configuration. These classes will not be generated and the button positioning will fail silently.

Use standard Tailwind spacing increments (0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96) instead.

🎨 Example fix
      className="
-       fixed top-35 right-15 z-50
+       fixed bottom-20 right-6 z-50
        rounded-full bg-primary p-3
        text-white shadow-lg
        transition-all duration-300
        hover:scale-110 hover:shadow-xl
      "

(Using bottom-20 right-6 for typical scroll-to-top button placement; adjust as needed.)

📝 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
className="
fixed top-35 right-15 z-50
className="
fixed bottom-20 right-6 z-50
rounded-full bg-primary p-3
text-white shadow-lg
transition-all duration-300
hover:scale-110 hover:shadow-xl
"
🤖 Prompt for AI Agents
In @frontend/src/components/ScrollToTopButton/ScrollToTopButton.tsx around lines
46 - 47, In ScrollToTopButton's className (the string containing "fixed top-35
right-15") replace the non-standard Tailwind spacing classes with valid Tailwind
spacing tokens (e.g., use bottom-20 right-6 or top-9 right-4 depending on
desired placement) so the positioning utilities are generated; update the
className in the ScrollToTopButton component string to a combination of standard
classes like "fixed bottom-20 right-6 z-50" (or your chosen valid alternatives).

rounded-full bg-primary p-3
text-white shadow-lg
transition-all duration-300
hover:scale-110 hover:shadow-xl
"
>
</button>
);
};
6 changes: 5 additions & 1 deletion frontend/src/pages/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { fetchAllImages } from '@/api/api-functions';
import { RootState } from '@/app/store';
import { EmptyGalleryState } from '@/components/EmptyStates/EmptyGalleryState';
import { useMutationFeedback } from '@/hooks/useMutationFeedback';
import { ScrollToTopButton } from '@/components/ScrollToTopButton/ScrollToTopButton';

export const Home = () => {
const dispatch = useDispatch();
Expand Down Expand Up @@ -48,7 +49,7 @@ export const Home = () => {
const title =
isSearchActive && images.length > 0
? `Face Search Results (${images.length} found)`
: 'Image Gallery';
: 'Image Gallery';

return (
<div className="relative flex h-full flex-col pr-6">
Expand Down Expand Up @@ -78,6 +79,9 @@ export const Home = () => {
className="absolute top-0 right-0 h-full w-4"
/>
)}

<ScrollToTopButton scrollContainerRef={scrollableRef} />
</div>

);
};