Enhance email templates with clickable links, branding, and improved styling
- Add clickable gallery links in all email templates - Include application logo in email header and footer (custom or PicPeak default) - Redesign emails with professional styling matching gallery login page - Gray background with white content box - PicPeak green header with centered logo - Clean typography and proper spacing - Responsive design for mobile devices - Styled call-to-action buttons - Footer with branding and copyright - Update email processor to fetch branding settings dynamically - Use proper API URLs for logo images in emails 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -96,7 +96,8 @@ router.get('/:slug/info', async (req, res) => {
|
||||
expires_at: event.expires_at,
|
||||
is_active: event.is_active,
|
||||
is_expired: !event.is_active || new Date(event.expires_at) < new Date(),
|
||||
requires_password: true
|
||||
requires_password: true,
|
||||
color_theme: event.color_theme
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching gallery info:', error);
|
||||
@@ -142,7 +143,8 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => {
|
||||
event_date: req.event.event_date,
|
||||
welcome_message: req.event.welcome_message,
|
||||
color_theme: req.event.color_theme,
|
||||
expires_at: req.event.expires_at
|
||||
expires_at: req.event.expires_at,
|
||||
hero_photo_id: req.event.hero_photo_id
|
||||
},
|
||||
categories: categories.map(cat => ({
|
||||
id: cat.id,
|
||||
@@ -222,12 +224,26 @@ router.get('/:slug/download/:photoId', verifyGalleryAccess, async (req, res) =>
|
||||
// Download all photos as ZIP
|
||||
router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => {
|
||||
try {
|
||||
const photos = await db('photos').where('event_id', req.event.id);
|
||||
// Fetch photos with category information
|
||||
const photos = await db('photos')
|
||||
.leftJoin('photo_categories', 'photos.category_id', 'photo_categories.id')
|
||||
.where('photos.event_id', req.event.id)
|
||||
.select(
|
||||
'photos.*',
|
||||
'photo_categories.name as category_name',
|
||||
'photo_categories.slug as category_slug'
|
||||
)
|
||||
.orderBy('photo_categories.name', 'asc')
|
||||
.orderBy('photos.uploaded_at', 'desc');
|
||||
|
||||
if (photos.length === 0) {
|
||||
return res.status(404).json({ error: 'No photos found' });
|
||||
}
|
||||
|
||||
// Count unique categories (excluding null)
|
||||
const uniqueCategories = new Set(photos.filter(p => p.category_id).map(p => p.category_id)).size;
|
||||
const hasMultipleCategories = uniqueCategories > 1;
|
||||
|
||||
res.setHeader('Content-Type', 'application/zip');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${req.event.slug}.zip"`);
|
||||
|
||||
@@ -245,13 +261,29 @@ router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => {
|
||||
for (const photo of photos) {
|
||||
const filePath = path.join(getStoragePath(), 'events/active', photo.path);
|
||||
|
||||
// Determine the file name in the archive
|
||||
let archiveName;
|
||||
if (hasMultipleCategories) {
|
||||
if (photo.category_name) {
|
||||
// Use category name as folder (sanitize for filesystem)
|
||||
const folderName = photo.category_name.replace(/[^a-zA-Z0-9-_ ]/g, '').trim();
|
||||
archiveName = path.join(folderName, photo.filename);
|
||||
} else {
|
||||
// Put uncategorized photos in 'Uncategorized' folder
|
||||
archiveName = path.join('Uncategorized', photo.filename);
|
||||
}
|
||||
} else {
|
||||
// No folders, just the filename
|
||||
archiveName = photo.filename;
|
||||
}
|
||||
|
||||
if (watermarkSettings && watermarkSettings.enabled) {
|
||||
// Apply watermark
|
||||
const watermarkedBuffer = await watermarkService.applyWatermark(filePath, watermarkSettings);
|
||||
archive.append(watermarkedBuffer, { name: photo.path });
|
||||
archive.append(watermarkedBuffer, { name: archiveName });
|
||||
} else {
|
||||
// Add original file
|
||||
archive.file(filePath, { name: photo.path });
|
||||
archive.file(filePath, { name: archiveName });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user