diff --git a/backend/scripts/README.md b/backend/scripts/README.md deleted file mode 100644 index d5fad50..0000000 --- a/backend/scripts/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# Storage Management Scripts - -## check-storage.js - -Checks the storage directory structure and verifies that photo files exist. - -### Usage: -```bash -# Check overall storage structure -node scripts/check-storage.js - -# Check specific event -node scripts/check-storage.js wedding-test-gallery-2025-07-14-1 -``` - -### What it checks: -- Storage directory structure and permissions -- Event directories -- Photo files existence -- Thumbnail files existence -- Database vs filesystem consistency - -## regenerate-thumbnails.js - -Regenerates missing thumbnails for photos in the database. - -### Usage: -```bash -# Regenerate all missing thumbnails -node scripts/regenerate-thumbnails.js - -# Regenerate thumbnails for specific event (by ID) -node scripts/regenerate-thumbnails.js 2 -``` - -### What it does: -- Scans photos in database -- Checks if thumbnails exist -- Generates missing thumbnails using Sharp -- Updates database with thumbnail paths -- Reports success/error statistics - -### Prerequisites: -- Node.js environment -- Database access -- Write permissions to storage directory -- Sharp library installed - -## Production Usage - -On your production server: - -1. First, check the storage structure: - ```bash - cd /path/to/picpeak/backend - NODE_ENV=production node scripts/check-storage.js wedding-test-gallery-2025-07-14-1 - ``` - -2. If thumbnails are missing, regenerate them: - ```bash - NODE_ENV=production node scripts/regenerate-thumbnails.js 2 - ``` - -Note: Replace `2` with the actual event ID from your database. - -## cleanup-thumbnails.js - -Cleans up temporary and orphaned thumbnail files. - -### Usage: -```bash -# Dry run - see what would be deleted -node scripts/cleanup-thumbnails.js --dry-run - -# Actually delete orphaned thumbnails -node scripts/cleanup-thumbnails.js -``` - -### What it does: -- Identifies temporary thumbnails (thumb_temp_*) -- Finds orphaned thumbnails not linked to any photo -- Removes unnecessary files to free up space -- Reports statistics on cleanup - -## diagnose-thumbnails.js - -Diagnoses why thumbnails might not be showing for a specific event. - -### Usage: -```bash -node scripts/diagnose-thumbnails.js 2 -``` - -### What it checks: -- Thumbnail paths in database vs filesystem -- Path format inconsistencies -- Missing thumbnail files -- Provides SQL to fix path issues - -### Common Issues: -1. **Path mismatch**: Database has wrong thumbnail path format -2. **Missing files**: Thumbnails were never generated -3. **Permission issues**: Web server can't read thumbnail files \ No newline at end of file diff --git a/frontend/src/components/common/AuthenticatedImage.tsx b/frontend/src/components/common/AuthenticatedImage.tsx index 372da01..cc6b8b9 100644 --- a/frontend/src/components/common/AuthenticatedImage.tsx +++ b/frontend/src/components/common/AuthenticatedImage.tsx @@ -62,9 +62,14 @@ export const AuthenticatedImage: React.FC = ({ let imageUrl = src; // Build full URL for the image - const fullImageUrl = imageUrl.startsWith('/') ? buildResourceUrl(imageUrl) : imageUrl; + // For API paths that start with /admin, we need to prepend /api + const fullImageUrl = imageUrl.startsWith('/admin') + ? buildResourceUrl(`/api${imageUrl}`) + : imageUrl.startsWith('/') + ? buildResourceUrl(imageUrl) + : imageUrl; - console.log('Fetching authenticated image:', fullImageUrl); + // console.log('Fetching authenticated image:', fullImageUrl); const response = await fetch(fullImageUrl, { headers: { 'Authorization': `Bearer ${token}` diff --git a/frontend/src/pages/admin/EventsListPage.tsx b/frontend/src/pages/admin/EventsListPage.tsx index 6c8bb19..5abd66a 100644 --- a/frontend/src/pages/admin/EventsListPage.tsx +++ b/frontend/src/pages/admin/EventsListPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useEffect, useRef } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { Plus, @@ -33,12 +33,47 @@ export const EventsListPage: React.FC = () => { const [selectedEvents, setSelectedEvents] = useState([]); // const [showFilters, setShowFilters] = useState(false); const [activeDropdown, setActiveDropdown] = useState(null); + const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number } | null>(null); const [showBulkArchiveModal, setShowBulkArchiveModal] = useState(false); // Get filter from URL const statusFilter = searchParams.get('filter') as 'active' | 'archived' | null; const isExpiringFilter = searchParams.get('filter') === 'expiring'; + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as HTMLElement; + if (!target.closest('.dropdown-container')) { + setActiveDropdown(null); + setDropdownPosition(null); + } + }; + + if (activeDropdown !== null) { + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + } + }, [activeDropdown]); + + // Update dropdown position on scroll/resize + useEffect(() => { + const handleScrollOrResize = () => { + if (activeDropdown !== null) { + setActiveDropdown(null); + setDropdownPosition(null); + } + }; + + window.addEventListener('scroll', handleScrollOrResize, true); + window.addEventListener('resize', handleScrollOrResize); + + return () => { + window.removeEventListener('scroll', handleScrollOrResize, true); + window.removeEventListener('resize', handleScrollOrResize); + }; + }, [activeDropdown]); + // Fetch events const { data, isLoading, error } = useQuery({ queryKey: ['admin-events', statusFilter], @@ -347,21 +382,38 @@ export const EventsListPage: React.FC = () => { {event.expires_at ? format(parseISO(event.expires_at), 'MMM d, yyyy') : 'N/A'} -
+
- {activeDropdown === event.id && ( -
+ {activeDropdown === event.id && dropdownPosition && ( +