feat: add per-event hero logo customization options

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.
This commit is contained in:
Paul Nothaft
2026-01-22 13:54:23 +01:00
parent f8881d5bd6
commit 0790a1ddad
11 changed files with 353 additions and 27 deletions
+24 -4
View File
@@ -158,7 +158,11 @@ router.post('/', adminAuth, requirePermission('events.create'), [
body('disable_right_click').optional().isBoolean(),
body('watermark_downloads').optional().isBoolean(),
body('watermark_text').optional().trim(),
body('css_template_id').optional({ nullable: true, checkFalsy: true }).isInt()
body('css_template_id').optional({ nullable: true, checkFalsy: true }).isInt(),
// Hero logo settings
body('hero_logo_visible').optional().isBoolean(),
body('hero_logo_size').optional().isIn(['small', 'medium', 'large', 'xlarge']),
body('hero_logo_position').optional().isIn(['top', 'center', 'bottom'])
], async (req, res) => {
try {
logger.debug('Create event request body', { body: req.body });
@@ -197,7 +201,11 @@ router.post('/', adminAuth, requirePermission('events.create'), [
moderate_comments = true,
show_feedback_to_guests = true,
// CSS Template
css_template_id = null
css_template_id = null,
// Hero logo settings
hero_logo_visible = true,
hero_logo_size = 'medium',
hero_logo_position = 'top'
} = req.body;
const customerName = getCustomerNameFromPayload(req.body);
@@ -335,7 +343,10 @@ router.post('/', adminAuth, requirePermission('events.create'), [
watermark_downloads: formatBoolean(watermark_downloads !== undefined ? watermark_downloads : false),
watermark_text,
require_password: formatBoolean(requirePassword),
css_template_id: css_template_id || null
css_template_id: css_template_id || null,
hero_logo_visible: formatBoolean(hero_logo_visible !== undefined ? hero_logo_visible : true),
hero_logo_size: hero_logo_size || 'medium',
hero_logo_position: hero_logo_position || 'top'
}).returning('id');
// Handle both PostgreSQL (returns array of objects) and SQLite (returns array of IDs)
@@ -612,7 +623,11 @@ router.put('/:id', adminAuth, requirePermission('events.edit'), [
}
return true;
}),
body('css_template_id').optional({ nullable: true, checkFalsy: true }).isInt()
body('css_template_id').optional({ nullable: true, checkFalsy: true }).isInt(),
// Hero logo settings
body('hero_logo_visible').optional().isBoolean(),
body('hero_logo_size').optional().isIn(['small', 'medium', 'large', 'xlarge']),
body('hero_logo_position').optional().isIn(['top', 'center', 'bottom'])
], async (req, res) => {
try {
const errors = validationResult(req);
@@ -724,6 +739,11 @@ router.put('/:id', adminAuth, requirePermission('events.edit'), [
updates.password_hash = await bcrypt.hash(crypto.randomBytes(32).toString('hex'), getBcryptRounds());
}
// Format hero logo settings if provided
if (Object.prototype.hasOwnProperty.call(updates, 'hero_logo_visible')) {
updates.hero_logo_visible = formatBoolean(updates.hero_logo_visible);
}
// Update event
await db('events')
.where('id', id)
+11 -2
View File
@@ -115,7 +115,10 @@ router.get('/:slug/info', async (req, res) => {
'require_password',
'color_theme',
'enable_devtools_protection',
'use_canvas_rendering'
'use_canvas_rendering',
'hero_logo_visible',
'hero_logo_size',
'hero_logo_position'
)
.first();
@@ -162,7 +165,10 @@ router.get('/:slug/info', async (req, res) => {
watermark_downloads: event.watermark_downloads === true || event.watermark_downloads === 1 || event.watermark_downloads === '1',
watermark_text: event.watermark_text,
enable_devtools_protection: event.enable_devtools_protection === true || event.enable_devtools_protection === 1 || event.enable_devtools_protection === '1',
use_canvas_rendering: event.use_canvas_rendering === true || event.use_canvas_rendering === 1 || event.use_canvas_rendering === '1'
use_canvas_rendering: event.use_canvas_rendering === true || event.use_canvas_rendering === 1 || event.use_canvas_rendering === '1',
hero_logo_visible: event.hero_logo_visible !== false && event.hero_logo_visible !== 0 && event.hero_logo_visible !== '0',
hero_logo_size: event.hero_logo_size || 'medium',
hero_logo_position: event.hero_logo_position || 'top'
});
} catch (error) {
console.error('Error fetching gallery info:', error);
@@ -332,6 +338,9 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => {
watermark_text: req.event.watermark_text,
enable_devtools_protection: req.event.enable_devtools_protection === true,
use_canvas_rendering: req.event.use_canvas_rendering === true,
hero_logo_visible: req.event.hero_logo_visible !== false && req.event.hero_logo_visible !== 0 && req.event.hero_logo_visible !== '0',
hero_logo_size: req.event.hero_logo_size || 'medium',
hero_logo_position: req.event.hero_logo_position || 'top',
...protectionSettings
},
categories: categories,