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:
2025-07-10 22:19:15 +02:00
parent 6438374258
commit 5328b4f73a
52 changed files with 3237 additions and 683 deletions
+3 -2
View File
@@ -27,6 +27,7 @@ interface UpdateEventData {
is_active?: boolean;
allow_user_uploads?: boolean;
upload_category_id?: number | null;
hero_photo_id?: number | null;
}
interface EventsListResponse {
@@ -70,13 +71,13 @@ export const eventsService = {
// Update event (admin)
async updateEvent(id: number, data: UpdateEventData): Promise<Event> {
const response = await api.put<Event>(`/api/events/${id}`, data);
const response = await api.put<Event>(`/api/admin/events/${id}`, data);
return response.data;
},
// Delete/deactivate event (admin)
async deleteEvent(id: number): Promise<void> {
await api.delete(`/api/events/${id}`);
await api.delete(`/api/admin/events/${id}`);
},
// Force archive event (admin)
+122 -11
View File
@@ -1,4 +1,5 @@
import { api } from '../config/api';
import i18n from '../i18n/config';
export interface Notification {
id: number;
@@ -45,29 +46,113 @@ export const notificationsService = {
// Format notification message
formatNotificationMessage(notification: Notification): string {
const t = i18n.t;
switch (notification.type) {
case 'event_created':
return `New event "${notification.eventName}" was created`;
return t('admin.notificationMessages.eventCreated', { eventName: notification.eventName });
case 'event_archived':
return `Event "${notification.eventName}" was archived`;
return t('admin.notificationMessages.eventArchived', { eventName: notification.eventName });
case 'event_updated':
return t('admin.notificationMessages.eventUpdated', {
eventName: notification.eventName || notification.metadata.eventName
});
case 'event_deleted':
return t('admin.notificationMessages.eventDeleted', {
eventName: notification.metadata.event_name
});
case 'photos_uploaded':
return `${notification.metadata.count || 0} photos uploaded to "${notification.eventName}"`;
return t('admin.notificationMessages.photosUploaded', {
count: notification.metadata.count || 0,
eventName: notification.eventName
});
case 'photo_deleted':
return t('admin.notificationMessages.photoDeleted', { eventName: notification.eventName });
case 'photos_bulk_deleted':
return t('admin.notificationMessages.photosBulkDeleted', {
count: notification.metadata.count || 0,
eventName: notification.eventName
});
case 'event_expiring':
return `Event "${notification.eventName}" expires in ${notification.metadata.days || 0} days`;
return t('admin.notificationMessages.eventExpiring', {
eventName: notification.eventName,
days: notification.metadata.days || 0
});
case 'event_expired':
return `Event "${notification.eventName}" has expired`;
return t('admin.notificationMessages.eventExpired', { eventName: notification.eventName });
case 'password_changed':
return `Password changed by ${notification.actorName}`;
return t('admin.notificationMessages.passwordChanged', { actorName: notification.actorName });
case 'password_reset':
return t('admin.notificationMessages.passwordReset', {
eventName: notification.metadata.eventName
});
case 'settings_updated':
return `${notification.metadata.type || 'System'} settings updated`;
return t('admin.notificationMessages.settingsUpdated', {
type: notification.metadata.type || 'System'
});
case 'email_template_updated':
return `Email template "${notification.metadata.template}" updated`;
return t('admin.notificationMessages.emailTemplateUpdated', {
template: notification.metadata.template
});
case 'bulk_download':
return `${notification.metadata.count || 0} photos downloaded from "${notification.eventName}"`;
return t('admin.notificationMessages.bulkDownload', {
count: notification.metadata.count || 0,
eventName: notification.eventName
});
case 'storage_warning':
return `Storage usage at ${notification.metadata.percentage || 0}%`;
return t('admin.notificationMessages.storageWarning', {
percentage: notification.metadata.percentage || 0
});
case 'admin_logout':
return t('admin.notificationMessages.adminLogout', { actorName: notification.actorName });
case 'category_created':
return t('admin.notificationMessages.categoryCreated', {
name: notification.metadata.name,
eventName: notification.eventName
});
case 'category_updated':
return t('admin.notificationMessages.categoryUpdated', {
name: notification.metadata.name,
eventName: notification.eventName
});
case 'category_deleted':
return t('admin.notificationMessages.categoryDeleted', {
name: notification.metadata.name,
eventName: notification.eventName
});
case 'cms_page_updated':
return t('admin.notificationMessages.cmsPageUpdated', {
slug: notification.metadata.slug
});
case 'email_config_updated':
return t('admin.notificationMessages.emailConfigUpdated');
case 'favicon_uploaded':
return t('admin.notificationMessages.faviconUploaded');
case 'branding_updated':
return t('admin.notificationMessages.brandingUpdated');
case 'general_settings_updated':
return t('admin.notificationMessages.generalSettingsUpdated');
case 'security_settings_updated':
return t('admin.notificationMessages.securitySettingsUpdated');
case 'theme_updated':
return t('admin.notificationMessages.themeUpdated');
case 'archive_downloaded':
return t('admin.notificationMessages.archiveDownloaded', {
eventName: notification.metadata.event_name
});
case 'archive_deleted':
return t('admin.notificationMessages.archiveDeleted', {
eventName: notification.metadata.event_name
});
case 'archive_restored':
return t('admin.notificationMessages.archiveRestored', {
eventName: notification.metadata.event_name
});
default:
return notification.metadata.message || 'System notification';
// Log unknown notification types for debugging
console.warn('Unknown notification type:', notification.type, notification);
return notification.metadata.message || t('admin.notificationMessages.systemActivity', {
type: notification.type.replace(/_/g, ' ')
});
}
},
@@ -78,22 +163,48 @@ export const notificationsService = {
return { icon: 'Calendar', color: 'text-blue-600' };
case 'event_archived':
return { icon: 'Archive', color: 'text-green-600' };
case 'event_updated':
case 'event_deleted':
return { icon: 'Calendar', color: 'text-gray-600' };
case 'photos_uploaded':
return { icon: 'Image', color: 'text-purple-600' };
case 'photo_deleted':
case 'photos_bulk_deleted':
return { icon: 'Image', color: 'text-red-600' };
case 'event_expiring':
return { icon: 'AlertCircle', color: 'text-amber-600' };
case 'event_expired':
return { icon: 'Clock', color: 'text-red-600' };
case 'password_changed':
case 'password_reset':
return { icon: 'Lock', color: 'text-indigo-600' };
case 'settings_updated':
case 'branding_updated':
case 'general_settings_updated':
case 'security_settings_updated':
case 'theme_updated':
return { icon: 'Settings', color: 'text-gray-600' };
case 'email_template_updated':
case 'email_config_updated':
return { icon: 'Mail', color: 'text-teal-600' };
case 'bulk_download':
return { icon: 'Download', color: 'text-cyan-600' };
case 'storage_warning':
return { icon: 'Database', color: 'text-orange-600' };
case 'admin_logout':
return { icon: 'LogOut', color: 'text-gray-600' };
case 'category_created':
case 'category_updated':
case 'category_deleted':
return { icon: 'Folder', color: 'text-indigo-600' };
case 'cms_page_updated':
return { icon: 'FileText', color: 'text-green-600' };
case 'favicon_uploaded':
return { icon: 'Globe', color: 'text-purple-600' };
case 'archive_downloaded':
case 'archive_deleted':
case 'archive_restored':
return { icon: 'Archive', color: 'text-blue-600' };
default:
return { icon: 'Bell', color: 'text-gray-600' };
}