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

@@ -54,6 +54,7 @@ import {
triggerDownloadFromBlob,
triggerDownloadFromDataUrl,
} from './invite-layout/export-utils';
import { buildDownloadFilename, normalizeEventDateSegment } from './invite-layout/fileNames';
export type { QrLayoutCustomization } from './invite-layout/schema';
@@ -182,6 +183,7 @@ function serializeElements(elements: LayoutElement[], context: LayoutSerializati
type InviteLayoutCustomizerPanelProps = {
invite: EventQrInvite | null;
eventName: string;
eventDate: string | null;
saving: boolean;
resetting: boolean;
onSave: (customization: QrLayoutCustomization) => Promise<void>;
@@ -199,6 +201,7 @@ const ZOOM_STEP = 0.05;
export function InviteLayoutCustomizerPanel({
invite,
eventName,
eventDate,
saving,
resetting,
onSave,
@@ -1391,7 +1394,12 @@ export function InviteLayoutCustomizerPanel({
}
const normalizedFormat = format.toLowerCase();
const filenameStem = `${invite.token || 'invite'}-${normalizedFormat}`;
const eventDateSegment = normalizeEventDateSegment(eventDate);
const filename = buildDownloadFilename(
['Einladungslayout', eventName, activeLayout?.name ?? null, eventDateSegment],
normalizedFormat,
'einladungslayout',
);
setDownloadBusy(normalizedFormat);
setError(null);
@@ -1412,14 +1420,14 @@ export function InviteLayoutCustomizerPanel({
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.buffer as ArrayBuffer], { type: 'application/pdf' }), `${filenameStem}.pdf`);
triggerDownloadFromBlob(new Blob([pdfBytes.buffer as ArrayBuffer], { type: 'application/pdf' }), filename);
} else {
throw new Error(`Unsupported format: ${normalizedFormat}`);
}
@@ -1509,34 +1517,8 @@ export function InviteLayoutCustomizerPanel({
return (
<div className="space-y-4">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div>
<h2 className="text-xl font-semibold text-foreground">{t('invites.customizer.heading', 'Layout anpassen')}</h2>
<p className="text-sm text-muted-foreground">{t('invites.customizer.copy', 'Bearbeite Texte, Farben und Positionen direkt neben der Live-Vorschau. Änderungen werden sofort sichtbar.')}</p>
</div>
<div className="flex flex-wrap gap-2">
<Button variant="outline" onClick={handleResetClick} disabled={resetting || saving}>
{resetting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <RotateCcw className="mr-2 h-4 w-4" />}
{t('invites.customizer.actions.reset', 'Zurücksetzen')}
</Button>
<Button
type="button"
onClick={() => {
if (formRef.current) {
if (typeof formRef.current.requestSubmit === 'function') {
formRef.current.requestSubmit();
} else {
formRef.current.submit();
}
}
}}
className="bg-gradient-to-r from-amber-500 via-orange-500 to-rose-500 text-white shadow-lg shadow-rose-500/20"
disabled={saving || resetting}
>
{saving ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
{t('invites.customizer.actions.save', 'Layout speichern')}
</Button>
</div>
<div className="hidden flex-wrap items-center justify-end gap-2 lg:flex">
{renderActionButtons('inline')}
</div>
{error ? (
@@ -1845,7 +1827,7 @@ export function InviteLayoutCustomizerPanel({
</Tabs>
</section>
<div className={cn('mt-6 flex flex-col gap-2 sm:flex-row sm:justify-end', showFloatingActions ? 'hidden' : 'flex')}>
<div className={cn('mt-6 flex flex-col gap-2 sm:flex-row sm:justify-end lg:hidden', showFloatingActions ? 'hidden' : 'flex')}>
{renderActionButtons('inline')}
</div>
<div ref={actionsSentinelRef} className="h-1 w-full" />