login-seiten neu designt, homepage neu designt. "so funktioniert's" ergänzt und Demo-Seite hinzugefügt. Paketansicht in mobile verbessert.

This commit is contained in:
Codex Agent
2025-11-03 11:47:19 +01:00
parent 073b51e2d5
commit 20eda6b4f8
23 changed files with 2481 additions and 587 deletions

View File

@@ -30,6 +30,7 @@ import {
} from '../constants';
import { buildLimitWarnings } from '../lib/limitWarnings';
import { InviteLayoutCustomizerPanel, QrLayoutCustomization } from './components/InviteLayoutCustomizerPanel';
import { buildDownloadFilename, normalizeEventDateSegment } from './components/invite-layout/fileNames';
import { DesignerCanvas } from './components/invite-layout/DesignerCanvas';
import {
CANVAS_HEIGHT,
@@ -252,6 +253,7 @@ export default function EventInvitesPage(): React.ReactElement {
const event = state.event;
const eventName = event ? renderEventName(event.name) : t('toolkit.titleFallback', 'Event');
const eventDate = event?.event_date ?? null;
const selectedInvite = React.useMemo(
() => state.invites.find((invite) => invite.id === selectedInviteId) ?? null,
@@ -587,7 +589,13 @@ export default function EventInvitesPage(): React.ReactElement {
const objectUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = objectUrl;
link.download = `${selectedInvite.token || 'invite'}-qr.png`;
const eventDateSegment = normalizeEventDateSegment(eventDate);
const downloadName = buildDownloadFilename(
['QR Code fuer', eventName, eventDateSegment],
'png',
'qr-code',
);
link.download = downloadName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
@@ -596,7 +604,7 @@ export default function EventInvitesPage(): React.ReactElement {
console.error('[Invites] QR download failed', error);
setExportError(t('invites.export.qr.error', 'QR-Code konnte nicht gespeichert werden.'));
}
}, [selectedInvite, t]);
}, [selectedInvite, eventName, eventDate, t]);
const handleExportDownload = React.useCallback(
async (format: string) => {
@@ -609,6 +617,13 @@ export default function EventInvitesPage(): React.ReactElement {
setExportDownloadBusy(busyKey);
setExportError(null);
const eventDateSegment = normalizeEventDateSegment(eventDate);
const filename = buildDownloadFilename(
['Einladungslayout', eventName, exportLayout.name ?? null, eventDateSegment],
normalizedFormat,
'einladungslayout',
);
const exportOptions = {
elements: exportElements,
accentColor: exportPreview.accentColor,
@@ -623,19 +638,17 @@ export default function EventInvitesPage(): React.ReactElement {
selectedId: null,
} as const;
const filenameStem = `${selectedInvite.token || 'invite'}-${exportLayout.id}`;
try {
if (normalizedFormat === 'png') {
const dataUrl = await generatePngDataUrl(exportOptions);
await triggerDownloadFromDataUrl(dataUrl, `${filenameStem}.png`);
await triggerDownloadFromDataUrl(dataUrl, filename);
} else if (normalizedFormat === 'pdf') {
const pdfBytes = await generatePdfBytes(
exportOptions,
'a4',
'portrait',
);
triggerDownloadFromBlob(new Blob([pdfBytes as any], { type: 'application/pdf' }), `${filenameStem}.pdf`);
triggerDownloadFromBlob(new Blob([pdfBytes.buffer as ArrayBuffer], { type: 'application/pdf' }), filename);
} else {
setExportError(t('invites.customizer.errors.downloadFailed', 'Download fehlgeschlagen. Bitte versuche es erneut.'));
}
@@ -646,7 +659,7 @@ export default function EventInvitesPage(): React.ReactElement {
setExportDownloadBusy(null);
}
},
[selectedInvite, exportLayout, exportPreview, exportElements, exportQr, exportLogo, t]
[selectedInvite, exportLayout, exportPreview, exportElements, exportQr, exportLogo, eventName, eventDate, t]
);
const handleExportPrint = React.useCallback(
@@ -809,6 +822,7 @@ export default function EventInvitesPage(): React.ReactElement {
<InviteLayoutCustomizerPanel
invite={selectedInvite ?? null}
eventName={eventName}
eventDate={eventDate}
saving={customizerSaving}
resetting={customizerResetting}
onSave={handleSaveCustomization}