feat(gallery): publish notifies assigned customer accounts via the account email
Publishing a gallery with no inline customer_email but assigned customer account(s) previously sent nothing (the dialog said "no notification"). Now the publish route falls back to the existing customer_gallery_assigned "your galleries" email (sent per assigned active account in their preferred language) so registered customers learn the gallery is available. Inline-email path (gallery_created) is unchanged. The publish dialog now reflects this: with an inline email it notifies that address; with only assigned accounts it says the account(s) will be notified; with neither, the button is just "Publish" (no false notify promise). Exports notifyCustomerOfNewAssignments; EN/DE strings added.
This commit is contained in:
@@ -1189,6 +1189,22 @@ router.post('/:id/publish', adminAuth, requirePermission('events.edit'), require
|
|||||||
status: 'pending',
|
status: 'pending',
|
||||||
created_at: new Date()
|
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
|
// WhatsApp gallery_ready on publish-from-draft (#640D). The PublishGallery
|
||||||
|
|||||||
@@ -1456,6 +1456,7 @@ module.exports = {
|
|||||||
setAssignmentsForEvent,
|
setAssignmentsForEvent,
|
||||||
setAssignmentsForCustomer,
|
setAssignmentsForCustomer,
|
||||||
getAssignmentsForEvent,
|
getAssignmentsForEvent,
|
||||||
|
notifyCustomerOfNewAssignments,
|
||||||
listEventsForCustomer,
|
listEventsForCustomer,
|
||||||
customerHasAccessToEvent,
|
customerHasAccessToEvent,
|
||||||
getPendingInvitations,
|
getPendingInvitations,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ interface PublishGalleryDialogProps {
|
|||||||
eventName: string;
|
eventName: string;
|
||||||
requirePassword: boolean;
|
requirePassword: boolean;
|
||||||
customerEmail?: string | null;
|
customerEmail?: string | null;
|
||||||
|
/** Assigned customer accounts — notified via the account "your galleries" email when there's no inline email. */
|
||||||
|
assignedCustomerCount?: number;
|
||||||
isPublishing: boolean;
|
isPublishing: boolean;
|
||||||
onConfirm: (password?: string) => void;
|
onConfirm: (password?: string) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -28,11 +30,15 @@ export const PublishGalleryDialog: React.FC<PublishGalleryDialogProps> = ({
|
|||||||
eventName,
|
eventName,
|
||||||
requirePassword,
|
requirePassword,
|
||||||
customerEmail,
|
customerEmail,
|
||||||
|
assignedCustomerCount = 0,
|
||||||
isPublishing,
|
isPublishing,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
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 [password, setPassword] = useState('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [error, setError] = useState<string | undefined>(undefined);
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
@@ -72,11 +78,18 @@ export const PublishGalleryDialog: React.FC<PublishGalleryDialogProps> = ({
|
|||||||
defaultValue:
|
defaultValue:
|
||||||
'Publishing "{{eventName}}" makes the gallery accessible and sends the notification email to {{customerEmail}}.',
|
'Publishing "{{eventName}}" makes the gallery accessible and sends the notification email to {{customerEmail}}.',
|
||||||
})
|
})
|
||||||
: t('events.publishDialog.descriptionNoEmail', {
|
: assignedCustomerCount > 0
|
||||||
eventName,
|
? t('events.publishDialog.descriptionAssignedAccount', {
|
||||||
defaultValue:
|
eventName,
|
||||||
'Publishing "{{eventName}}" makes the gallery accessible. No customer email is set, so no notification will be sent.',
|
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.',
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{requirePassword && customerEmail && (
|
{requirePassword && customerEmail && (
|
||||||
@@ -124,10 +137,10 @@ export const PublishGalleryDialog: React.FC<PublishGalleryDialogProps> = ({
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isPublishing}
|
disabled={isPublishing}
|
||||||
isLoading={isPublishing}
|
isLoading={isPublishing}
|
||||||
leftIcon={<Send className="w-4 h-4" />}
|
leftIcon={willNotify ? <Send className="w-4 h-4" /> : undefined}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
{t('events.publishAndNotify')}
|
{willNotify ? t('events.publishAndNotify') : t('events.publishDialog.justPublish', 'Publish')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1125,6 +1125,8 @@
|
|||||||
"title": "Galerie veröffentlichen",
|
"title": "Galerie veröffentlichen",
|
||||||
"descriptionWithEmail": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht und die Benachrichtigungs-E-Mail an {{customerEmail}} gesendet.",
|
"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.",
|
"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",
|
"passwordLabel": "Galerie-Passwort",
|
||||||
"passwordPlaceholder": "Galerie-Passwort eingeben",
|
"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.",
|
"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.",
|
||||||
|
|||||||
@@ -670,6 +670,8 @@
|
|||||||
"title": "Publish gallery",
|
"title": "Publish gallery",
|
||||||
"descriptionWithEmail": "Publishing \"{{eventName}}\" makes the gallery accessible and sends the notification email to {{customerEmail}}.",
|
"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.",
|
"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",
|
"passwordLabel": "Gallery password",
|
||||||
"passwordPlaceholder": "Enter the 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.",
|
"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.",
|
||||||
|
|||||||
@@ -2661,6 +2661,7 @@ export const EventDetailsPage: React.FC = () => {
|
|||||||
eventName={event.event_name}
|
eventName={event.event_name}
|
||||||
requirePassword={isGalleryPublic(event) ? false : true}
|
requirePassword={isGalleryPublic(event) ? false : true}
|
||||||
customerEmail={event.customer_email}
|
customerEmail={event.customer_email}
|
||||||
|
assignedCustomerCount={((event as { customer_accounts?: Array<{ id: number }> }).customer_accounts || []).length}
|
||||||
isPublishing={publishMutation.isPending}
|
isPublishing={publishMutation.isPending}
|
||||||
onConfirm={(password) => publishMutation.mutate(password)}
|
onConfirm={(password) => publishMutation.mutate(password)}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user