diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js index a1cc3f10..4b30546d 100644 --- a/backend/src/routes/adminEvents.js +++ b/backend/src/routes/adminEvents.js @@ -1189,6 +1189,22 @@ router.post('/:id/publish', adminAuth, requirePermission('events.edit'), require status: 'pending', created_at: new Date() }); + } else { + // No inline email, but the gallery may be assigned to registered customer + // account(s). Notify them via the account "your galleries" email + // (customer_gallery_assigned, in the customer's own language) instead of + // the gallery_created mail, which needs an inline recipient. Best-effort. + try { + const customerAccountsService = require('../services/customerAccountsService'); + const assigned = await customerAccountsService.getAssignmentsForEvent(parseInt(id, 10)); + for (const c of assigned.filter((a) => a.is_active !== false && a.is_active !== 0 && a.email)) { + await customerAccountsService + .notifyCustomerOfNewAssignments(c.id, [parseInt(id, 10)]) + .catch((err) => logger.warn('Publish: customer gallery notice failed', { customerId: c.id, error: err.message })); + } + } catch (err) { + logger.warn('Publish: assigned-customer notification skipped', { eventId: id, error: err.message }); + } } // WhatsApp gallery_ready on publish-from-draft (#640D). The PublishGallery diff --git a/backend/src/services/customerAccountsService.js b/backend/src/services/customerAccountsService.js index 8b8061d1..2dd76b38 100644 --- a/backend/src/services/customerAccountsService.js +++ b/backend/src/services/customerAccountsService.js @@ -1456,6 +1456,7 @@ module.exports = { setAssignmentsForEvent, setAssignmentsForCustomer, getAssignmentsForEvent, + notifyCustomerOfNewAssignments, listEventsForCustomer, customerHasAccessToEvent, getPendingInvitations, diff --git a/frontend/src/components/admin/PublishGalleryDialog.tsx b/frontend/src/components/admin/PublishGalleryDialog.tsx index d0421602..5aa09c29 100644 --- a/frontend/src/components/admin/PublishGalleryDialog.tsx +++ b/frontend/src/components/admin/PublishGalleryDialog.tsx @@ -7,6 +7,8 @@ interface PublishGalleryDialogProps { eventName: string; requirePassword: boolean; customerEmail?: string | null; + /** Assigned customer accounts — notified via the account "your galleries" email when there's no inline email. */ + assignedCustomerCount?: number; isPublishing: boolean; onConfirm: (password?: string) => void; onClose: () => void; @@ -28,11 +30,15 @@ export const PublishGalleryDialog: React.FC = ({ eventName, requirePassword, customerEmail, + assignedCustomerCount = 0, isPublishing, onConfirm, onClose, }) => { const { t } = useTranslation(); + // Someone gets notified if there's an inline email OR an assigned account + // (the latter via the account "your galleries" email). + const willNotify = !!customerEmail || assignedCustomerCount > 0; const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [error, setError] = useState(undefined); @@ -72,11 +78,18 @@ export const PublishGalleryDialog: React.FC = ({ defaultValue: 'Publishing "{{eventName}}" makes the gallery accessible and sends the notification email to {{customerEmail}}.', }) - : t('events.publishDialog.descriptionNoEmail', { - eventName, - defaultValue: - 'Publishing "{{eventName}}" makes the gallery accessible. No customer email is set, so no notification will be sent.', - })} + : assignedCustomerCount > 0 + ? t('events.publishDialog.descriptionAssignedAccount', { + eventName, + count: assignedCustomerCount, + defaultValue: + 'Publishing "{{eventName}}" makes the gallery accessible. The assigned customer account(s) will be notified by email (in their language) that it is available.', + }) + : t('events.publishDialog.descriptionNoEmail', { + eventName, + defaultValue: + 'Publishing "{{eventName}}" makes the gallery accessible. No customer email is set, so no notification will be sent.', + })}

{requirePassword && customerEmail && ( @@ -124,10 +137,10 @@ export const PublishGalleryDialog: React.FC = ({ onClick={handleSubmit} disabled={isPublishing} isLoading={isPublishing} - leftIcon={} + leftIcon={willNotify ? : undefined} className="flex-1" > - {t('events.publishAndNotify')} + {willNotify ? t('events.publishAndNotify') : t('events.publishDialog.justPublish', 'Publish')} diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 127a834c..ee26b006 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -1125,6 +1125,8 @@ "title": "Galerie veröffentlichen", "descriptionWithEmail": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht und die Benachrichtigungs-E-Mail an {{customerEmail}} gesendet.", "descriptionNoEmail": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht. Es ist keine Kunden-E-Mail hinterlegt – es wird keine Benachrichtigung gesendet.", + "descriptionAssignedAccount": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht. Die zugewiesenen Kundenkonten werden per E-Mail (in ihrer Sprache) benachrichtigt, dass sie verfügbar ist.", + "justPublish": "Veröffentlichen", "passwordLabel": "Galerie-Passwort", "passwordPlaceholder": "Galerie-Passwort eingeben", "passwordHelp": "Gib das bei der Erstellung gesetzte Passwort erneut ein (oder wähle ein neues). Die E-Mail enthält genau diesen Text; das Backend hasht es erneut, sodass die Galerie-Anmeldung weiterhin funktioniert.", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 081363f1..d15f0fe2 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -670,6 +670,8 @@ "title": "Publish gallery", "descriptionWithEmail": "Publishing \"{{eventName}}\" makes the gallery accessible and sends the notification email to {{customerEmail}}.", "descriptionNoEmail": "Publishing \"{{eventName}}\" makes the gallery accessible. No customer email is set, so no notification will be sent.", + "descriptionAssignedAccount": "Publishing \"{{eventName}}\" makes the gallery accessible. The assigned customer account(s) will be notified by email (in their language) that it is available.", + "justPublish": "Publish", "passwordLabel": "Gallery password", "passwordPlaceholder": "Enter the gallery password", "passwordHelp": "Re-type the password set at creation (or pick a new one). The email includes this exact text; the backend re-hashes it so the gallery login still works.", diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx index 079a8b73..2d7b1097 100644 --- a/frontend/src/pages/admin/EventDetailsPage.tsx +++ b/frontend/src/pages/admin/EventDetailsPage.tsx @@ -2661,6 +2661,7 @@ export const EventDetailsPage: React.FC = () => { eventName={event.event_name} requirePassword={isGalleryPublic(event) ? false : true} customerEmail={event.customer_email} + assignedCustomerCount={((event as { customer_accounts?: Array<{ id: number }> }).customer_accounts || []).length} isPublishing={publishMutation.isPending} onConfirm={(password) => publishMutation.mutate(password)} onClose={() => {