Add Google Photos-style justified row layout as a mode within masonry:
- Add masonryMode setting: 'columns' (Pinterest) or 'rows' (Google Photos)
- Create justifiedLayoutCalculator utility for row-based layouts
- Extract and store image dimensions on upload for layout calculations
- Include width/height in gallery API response
- Add row height and last row behavior controls to theme customizer
- Support responsive container width detection with ResizeObserver
Photos in rows mode maintain their aspect ratios while filling
horizontal rows at a consistent height. The number of photos per
row is automatically calculated based on target row height and
photo dimensions.
Closes#146
Add event-level custom logo upload/delete endpoints and UI, allowing
per-event logos to override the global branding logo in gallery views.
Also fixes several bugs discovered during testing:
- fix: category_id 'individual' parsed as NaN causing photo upload failures
- fix: gallery auth race condition where photos query fired before token stored
- fix: gallery-photos query not invalidated after favorite/like mutations
- fix: e2e test race conditions with View Gallery button detachment
Add null checks for expires_at and event_date fields to prevent
TypeError when calling parseISO() on null values. This fixes crashes
that occurred after making event dates optional.
- AdminDashboard: skip events with null expires_at in expiring filter
- GalleryPage: handle null expires_at in expiration calculation
- GalleryView: make daysUntilExpiration nullable with explicit checks
- EventDetailsPage: return null from safeParseDate for null inputs
The "Enable watermark on photos" checkbox in Settings > General > Feature
Toggles was not connected to any backend logic - it stored a setting that
was never read or used. The actual working watermark functionality exists
in Settings > Branding.
This removes the dead toggle to eliminate user confusion (fixes#140).
Add configurable hero logo settings for individual events:
- Logo visibility toggle (show/hide in hero section)
- Logo size options (small, medium, large, xlarge)
- Logo position options (top, center, bottom)
Changes include:
- Database migration for hero_logo_visible, hero_logo_size, hero_logo_position fields
- Backend routes updated to handle new settings
- Frontend admin page with logo customization controls
- HeroGalleryLayout component with dynamic logo rendering
- i18n translations for EN and DE
Also updates .gitignore to exclude test files and artifacts.
Implements GitHub issue #139 - allows users to create and manage custom
event types beyond the default presets (wedding, birthday, corporate, other).
Backend:
- Add event_types table migration with default system types
- Create eventTypeService for CRUD operations with legacy fallback
- Add adminEventTypes routes with full REST API
- Update event validation to use dynamic event types
- Update slug generation to use custom slug_prefix
Frontend:
- Add EventTypesPage with full CRUD admin interface
- Add eventTypes.service.ts API client
- Update CreateEventPage to fetch types dynamically
- Add Event Types navigation in admin sidebar
- Add i18n translations (EN/DE)
Backward compatible: existing galleries continue to work, legacy types
accepted even if database is empty via fallback mechanisms.
Added optional chaining when accessing req.body.password in the
resend-email endpoint to handle cases where req.body is undefined.
This prevented the "Cannot read properties of undefined" error.
Fixes#137
The ThemeCustomizerEnhanced component stored customCss in a separate
local state that was never propagated to the parent component when
hideActions was true (used in both CreateEventPage and EventDetailsPage).
Changes:
- handleChange() now includes customCss when propagating theme changes
- CSS textarea onChange now propagates customCss to parent in preview mode
- handlePresetSelect() clears customCss when selecting a preset
Fixes#136
Addresses GitHub issue #132 - enables filtering client feedback and
exporting filenames for use in Lightroom.
Changes:
- Add original_filename column to photos table via migration
- Store original filename during photo upload
- Fix export service column name mismatches (path, size_bytes, uploaded_at)
- Fix table name (photo_categories instead of categories)
- Fix toFixed() calls to handle string ratings from database
Export formats available:
- TXT with comma separator (for Lightroom Library Filter)
- CSV with full metadata
- JSON for automation
- XMP sidecar files (for Lightroom/Bridge/Capture One)
- Fix password minimum length validation: frontend now correctly requires
12 characters to match backend validation (was incorrectly checking for 8)
- Fix translation key references in AcceptInvitePage to use correct paths
(e.g., acceptInvitation.errors.* instead of acceptInvitation.*)
- Add missing translations for both EN and DE:
- contactAdminMessage
- passwordsMatch
- alreadyHaveAccount
- signIn
Fixes#129
The invitation email was generating links to /admin/accept-invite/{token}
but the frontend route is configured at /invite/{token}. This caused
invited users to see a blank page when clicking the email link.
Fixes#129
Ensures STORAGE_PATH environment variable is explicitly set in
production deployments to prevent path resolution issues when
serving thumbnails and other storage-related operations.
Fixed inconsistent storage path fallbacks that caused 500 errors when
serving thumbnails. The paths were using '../../storage' (2 levels up)
instead of '../../../storage' (3 levels up) when STORAGE_PATH env var
is not set.
Affected files:
- backend/src/routes/gallery.js
- backend/src/services/photoService.js
- backend/src/services/eventService.js
The gallery /photos and /info endpoints were not returning the
allow_user_uploads field, causing the upload button to never show
in the frontend since the value was always undefined/false.
- Remove hidden sm:block wrapper hiding upload button on mobile for Grid layout
- Add upload button to GallerySidebar using existing allowUploads/onUploadClick props
- Ensures upload button is visible in both header and sidebar on all devices
Closes#113
- Remove hidden sm:block wrapper hiding upload button on mobile for Grid layout
- Add upload button to GallerySidebar using existing allowUploads/onUploadClick props
- Ensures upload button is visible in both header and sidebar on all devices
Closes#113
Add global settings to make event_date and expiration optional when
creating galleries. This supports non-event use cases like portraits,
corporate shoots, etc.
New features:
- Settings toggles in Settings → Event Creation tab
- "Require event date" checkbox with warning about random URL identifiers
- "Require expiration date" checkbox with warning about manual archiving
- Galleries without date use random hex suffix in slug (e.g. portrait-smith-a1b2c3)
- Galleries without expiration never expire (stay active until archived)
Backend changes:
- New migration for settings and nullable columns
- Conditional validation based on settings
- Updated slug generation with random suffix fallback
- Updated expiration checker to skip null expires_at
- Updated gallery access control for null expiration
Frontend changes:
- New checkboxes in EventsTab with warnings
- Conditional event date field (shows optional label)
- No Expiration message when expiration disabled
- Updated types for nullable event_date and expires_at
Closes#118
Document the API_URL environment variable that is used for constructing
URLs for assets (logos, images) in email notifications. Without this
setting, the system defaults to http://localhost:3001 which causes
broken images in production emails.
Added to both root and backend .env.example files with clear
documentation about its purpose and importance.