## Features Implemented ### 1. Event Rename Functionality - Add EventRenameDialog component with live slug preview - Create eventRenameService for safe event renaming - Add slug_redirects table for old URL redirects - Support optional email notification on rename - Fix date formatting in slug (YYYY-MM-DD format) ### 2. Optional Event Contact Fields - Add settings to make customer name/email/admin email optional - Create migration for field requirement settings - Update CreateEventPage forms to show "(optional)" labels - Fix boolean parsing in publicSettings.js ### 3. Photo Filtering & Export - Add PhotoFilterPanel with rating/likes/favorites/comments filters - Create PhotoExportMenu with ZIP/metadata/XMP export options - Add photoExportService with Lightroom XMP sidecar generation - Create photoFilterBuilder utility for query construction - Wire up photo selection to export button via onSelectionChange ### 4. Custom CSS Gallery Templates - Add CssTemplateEditor component with 3 template slots - Create cssSanitizer utility blocking XSS vectors - Add gallery CSS endpoint for template delivery - Integrate Custom CSS tab into Settings page - Include default "Elegant Dark" template ## Bug Fixes - Fix event rename date formatting (was showing full Date string) - Fix common.optional translation key missing in locales - Fix photo export button staying disabled when photos selected - Fix authService import missing in SettingsPage ## Documentation - Add comprehensive REFACTORING_PLAN.md for codebase improvement - Add test specification documents for all features - Add feature documentation for CSS templates ## Database Migrations - 049_add_slug_redirects.js - 050_add_optional_event_fields_settings.js - 051_add_photo_filter_indexes.js - 052_add_css_templates.js
67 lines
1.7 KiB
TypeScript
67 lines
1.7 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { cssTemplatesService } from '../services/cssTemplates.service';
|
|
|
|
/**
|
|
* Hook to load and inject custom CSS for a gallery
|
|
* @param slug - Gallery slug
|
|
* @returns Object with customCss content and loading state
|
|
*/
|
|
export function useGalleryCustomCss(slug: string) {
|
|
const [customCss, setCustomCss] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!slug) {
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
const loadCustomCss = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const css = await cssTemplatesService.getGalleryCss(slug);
|
|
setCustomCss(css);
|
|
} catch (err) {
|
|
console.error('Failed to load custom CSS:', err);
|
|
setError('Failed to load custom styles');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadCustomCss();
|
|
}, [slug]);
|
|
|
|
// Inject CSS into document
|
|
useEffect(() => {
|
|
if (!customCss) return;
|
|
|
|
// Remove any existing custom CSS
|
|
const existingStyle = document.getElementById('gallery-custom-css');
|
|
if (existingStyle) {
|
|
existingStyle.remove();
|
|
}
|
|
|
|
// Create and inject new style element
|
|
const styleElement = document.createElement('style');
|
|
styleElement.id = 'gallery-custom-css';
|
|
styleElement.textContent = customCss;
|
|
document.head.appendChild(styleElement);
|
|
|
|
// Cleanup on unmount or when CSS changes
|
|
return () => {
|
|
const existing = document.getElementById('gallery-custom-css');
|
|
if (existing) {
|
|
existing.remove();
|
|
}
|
|
};
|
|
}, [customCss]);
|
|
|
|
return { customCss, loading, error };
|
|
}
|
|
|
|
export default useGalleryCustomCss;
|