feat: draft mode, admin branding, and workflow improvements

Draft Mode:
- Events are created as drafts by default — no email sent until published
- Add "Publish & Notify Client" button with confirmation dialog
- Draft banner with yellow styling on event details page
- Draft filter tab in events list
- Gallery middleware blocks public access to draft events
- Migration 076 adds is_draft column to events table

Admin Draft Preview:
- Admins can preview draft galleries via JWT preview token (?preview=)
- "View Gallery" link on drafts auto-appends preview token

Admin & Login Page Branding:
- Admin header uses configured company logo/name from branding settings
- Login page shows configured logo instead of hardcoded PicPeak
- Respects logo_display_mode (logo_only, text_only, logo_and_text)

OG Tag Branding:
- DynamicFavicon component updates OG meta tags and page title from
  branding settings

Editable Client Email:
- Customer email is now editable after event creation in edit mode

Branding Inheritance:
- New events inherit hero logo settings (visibility, size, position)
  from global branding configuration

Share Link Full Domain URL:
- New getFrontendBaseUrl() utility with DB fallback to general_site_url
- Used in email processor and share link service
This commit is contained in:
Paul Nothaft
2026-04-08 11:42:38 +02:00
parent 125cd0d003
commit 40332a71db
19 changed files with 456 additions and 62 deletions
+13 -1
View File
@@ -74,7 +74,7 @@ export const eventsService = {
async getEvents(
page: number = 1,
limit: number = 20,
status?: 'active' | 'inactive' | 'archived'
status?: 'active' | 'inactive' | 'archived' | 'draft'
): Promise<EventsListResponse> {
const params = new URLSearchParams({
page: page.toString(),
@@ -173,6 +173,18 @@ export const eventsService = {
return response.data;
},
// Publish a draft event
async publishEvent(eventId: number): Promise<{ message: string; is_draft: boolean }> {
const response = await api.post(`/admin/events/${eventId}/publish`);
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');
return token;
},
// Rename event
async renameEvent(eventId: number, newEventName: string, resendEmail: boolean = false): Promise<{
success: boolean;