feat(events): duplicate-gallery action (#626)
Daniel asked for a way to re-use a good gallery configuration without re-entering every setting. Two of his three suggested workflows are covered by this PR; the third (per-event-type behaviour defaults) is partially shipped already via event_types.theme_preset + theme_config and is left as a follow-up if the duplicate workflow doesn't cover it. Backend — POST /admin/events/:id/duplicate. Validates a new event_name (required) + event_date (optional) + customer_name/email (optional); copies branding (color_theme, css_template_id, header/hero/divider/anchor), behaviour toggles (allow_downloads, watermark_*, allow_user_uploads, require_password, etc.), photo_cap, welcome_message, default_photo_sort, admin_email, and feedback settings + per-event photo categories. Mints a fresh slug + share_token + random-placeholder password_hash (admin sets the real one via the publish dialog shipped in #627). Recomputes expires_at = new_event_date + (source.expires_at - source.event_date) so the duplicate keeps the same active window; defaults to 30 days if either source field was null. is_draft is always true. Deliberately NOT carried over: photos, hero_photo_id, client_access secrets, og_image_share opt-in, customer_phone, sent_at flags, archive state, customer-account assignments. Frontend — new DuplicateEventDialog (matches the PublishGalleryDialog pattern), wired into the Actions card on EventDetailsPage. Visible in both draft and live mode since admins typically duplicate from a published gallery. On success the page navigates to the new draft so the admin can finish customising + publish. I18n: EN + DE entries for the dialog + button label. Backend logs an event_duplicated activity with the source event id/name so the trail is auditable. Frontend service: eventsService.duplicateEvent(eventId, data).
This commit is contained in:
@@ -232,6 +232,22 @@ export const eventsService = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Duplicate an event (#626). Creates a new draft gallery that inherits the
|
||||
// source event's branding + behaviour + feedback + categories. Photos are
|
||||
// NOT carried over. The returned id/slug are the new draft event.
|
||||
async duplicateEvent(
|
||||
eventId: number,
|
||||
data: {
|
||||
event_name: string;
|
||||
event_date?: string;
|
||||
customer_name?: string;
|
||||
customer_email?: string;
|
||||
},
|
||||
): Promise<{ message: string; id: number; slug: string; is_draft: boolean }> {
|
||||
const response = await api.post(`/admin/events/${eventId}/duplicate`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get admin preview token (uses existing admin session token)
|
||||
getPreviewToken(): string | null {
|
||||
const token = sessionStorage.getItem('admin_token') || localStorage.getItem('admin_token');
|
||||
|
||||
Reference in New Issue
Block a user