event admin verfeinert und UI reduziert.
This commit is contained in:
@@ -1623,30 +1623,157 @@ export function InviteLayoutCustomizerPanel({
|
||||
<SelectContent className="max-h-60">
|
||||
{availableLayouts.map((layout) => (
|
||||
<SelectItem key={layout.id} value={layout.id}>
|
||||
<div className="flex w-full flex-col gap-1 text-left">
|
||||
<span className="text-sm font-medium text-foreground">{layout.name || t('invites.customizer.layoutFallback', 'Layout')}</span>
|
||||
{layout.subtitle ? <span className="text-xs text-muted-foreground">{layout.subtitle}</span> : null}
|
||||
{layout.formats?.length ? (
|
||||
<span className="text-[10px] font-medium uppercase tracking-wide text-amber-700">
|
||||
{layout.formats.map((format) => String(format).toUpperCase()).join(' · ')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{layout.name || t('invites.customizer.layoutFallback', 'Layout')}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{activeLayout ? (
|
||||
<div className="rounded-xl border border-[var(--tenant-border-strong)] bg-[var(--tenant-surface-muted)] p-4 text-sm text-[var(--tenant-foreground-soft)] transition-colors">
|
||||
<p className="font-medium text-foreground">{activeLayout.name}</p>
|
||||
{activeLayout.subtitle ? <p className="mt-1 text-xs text-muted-foreground">{activeLayout.subtitle}</p> : null}
|
||||
{activeLayout.description ? <p className="mt-2 leading-relaxed text-muted-foreground">{activeLayout.description}</p> : null}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
|
||||
{renderResponsiveSection(
|
||||
'content',
|
||||
t('invites.customizer.sections.content', 'Texte & Branding'),
|
||||
t('invites.customizer.sections.contentHint', 'Passe Texte, Anleitungsschritte und Farben deiner Einladung an.'),
|
||||
<Tabs defaultValue="text" className="space-y-4">
|
||||
<TabsList className="grid w-full grid-cols-3 gap-1 text-xs sm:text-sm">
|
||||
<TabsTrigger value="text">{t('invites.customizer.sections.text', 'Texte')}</TabsTrigger>
|
||||
<TabsTrigger value="branding">{t('invites.customizer.sections.branding', 'Farbgebung')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="text" className="space-y-4">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-headline">{t('invites.customizer.fields.headline', 'Überschrift')}</Label>
|
||||
<Input
|
||||
id="invite-headline"
|
||||
value={form.headline ?? ''}
|
||||
onChange={(event) => updateForm('headline', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-subtitle">{t('invites.customizer.fields.subtitle', 'Unterzeile')}</Label>
|
||||
<Input
|
||||
id="invite-subtitle"
|
||||
value={form.subtitle ?? ''}
|
||||
onChange={(event) => updateForm('subtitle', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-description">{t('invites.customizer.fields.description', 'Beschreibung')}</Label>
|
||||
<Textarea
|
||||
id="invite-description"
|
||||
value={form.description ?? ''}
|
||||
onChange={(event) => updateForm('description', event.target.value)}
|
||||
className="min-h-[96px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-badge">{t('invites.customizer.fields.badge', 'Badge-Label')}</Label>
|
||||
<Input
|
||||
id="invite-badge"
|
||||
value={form.badge_label ?? ''}
|
||||
onChange={(event) => updateForm('badge_label', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-cta">{t('invites.customizer.fields.cta', 'Call-to-Action')}</Label>
|
||||
<Input
|
||||
id="invite-cta"
|
||||
value={form.cta_label ?? ''}
|
||||
onChange={(event) => updateForm('cta_label', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-link-heading">{t('invites.customizer.fields.linkHeading', 'Link-Überschrift')}</Label>
|
||||
<Input
|
||||
id="invite-link-heading"
|
||||
value={form.link_heading ?? ''}
|
||||
onChange={(event) => updateForm('link_heading', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-link-label">{t('invites.customizer.fields.linkLabel', 'Link/Begleittext')}</Label>
|
||||
<Input
|
||||
id="invite-link-label"
|
||||
value={form.link_label ?? ''}
|
||||
onChange={(event) => updateForm('link_label', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="branding" className="space-y-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-accent">{t('invites.customizer.fields.accentColor', 'Akzentfarbe')}</Label>
|
||||
<Input
|
||||
id="invite-accent"
|
||||
type="color"
|
||||
value={form.accent_color ?? '#6366F1'}
|
||||
onChange={(event) => updateForm('accent_color', event.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-text-color">{t('invites.customizer.fields.textColor', 'Textfarbe')}</Label>
|
||||
<Input
|
||||
id="invite-text-color"
|
||||
type="color"
|
||||
value={form.text_color ?? '#111827'}
|
||||
onChange={(event) => updateForm('text_color', event.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-background-color">{t('invites.customizer.fields.backgroundColor', 'Hintergrund')}</Label>
|
||||
<Input
|
||||
id="invite-background-color"
|
||||
type="color"
|
||||
value={form.background_color ?? '#FFFFFF'}
|
||||
onChange={(event) => updateForm('background_color', event.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-badge-color">{t('invites.customizer.fields.badgeColor', 'Badge')}</Label>
|
||||
<Input
|
||||
id="invite-badge-color"
|
||||
type="color"
|
||||
value={form.badge_color ?? '#2563EB'}
|
||||
onChange={(event) => updateForm('badge_color', event.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{t('invites.customizer.fields.logo', 'Logo')}</Label>
|
||||
{form.logo_data_url ? (
|
||||
<div className="flex items-center gap-4 rounded-lg border border-[var(--tenant-border-strong)] bg-[var(--tenant-surface-muted)] p-3 text-[var(--tenant-foreground-soft)]">
|
||||
<img src={form.logo_data_url} alt="Logo" className="h-12 w-12 rounded border border-[var(--tenant-border-strong)] object-contain" />
|
||||
<Button type="button" variant="ghost" onClick={handleLogoRemove} className="text-destructive hover:text-destructive/80">
|
||||
{t('invites.customizer.actions.removeLogo', 'Logo entfernen')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label className="flex cursor-pointer items-center gap-3 rounded-lg border border-dashed border-[var(--tenant-border-strong)] bg-[var(--tenant-surface-muted)] px-4 py-3 text-sm text-muted-foreground hover:border-primary">
|
||||
<UploadCloud className="h-4 w-4" />
|
||||
<span>{t('invites.customizer.actions.uploadLogo', 'Logo hochladen (max. 1 MB)')}</span>
|
||||
<input type="file" accept="image/*" className="hidden" onChange={handleLogoUpload} />
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
{renderResponsiveSection(
|
||||
'elements',
|
||||
t('invites.customizer.elements.title', 'Elemente & Positionierung'),
|
||||
@@ -1725,182 +1852,6 @@ export function InviteLayoutCustomizerPanel({
|
||||
</>
|
||||
)}
|
||||
|
||||
{renderResponsiveSection(
|
||||
'content',
|
||||
t('invites.customizer.sections.content', 'Texte & Branding'),
|
||||
t('invites.customizer.sections.contentHint', 'Passe Texte, Anleitungsschritte und Farben deiner Einladung an.'),
|
||||
<Tabs defaultValue="text" className="space-y-4">
|
||||
<TabsList className="grid w-full grid-cols-3 gap-1 text-xs sm:text-sm">
|
||||
<TabsTrigger value="text">{t('invites.customizer.sections.text', 'Texte')}</TabsTrigger>
|
||||
<TabsTrigger value="instructions">{t('invites.customizer.sections.instructions', 'Schritt-für-Schritt')}</TabsTrigger>
|
||||
<TabsTrigger value="branding">{t('invites.customizer.sections.branding', 'Farbgebung')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="text" className="space-y-4">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-headline">{t('invites.customizer.fields.headline', 'Überschrift')}</Label>
|
||||
<Input
|
||||
id="invite-headline"
|
||||
value={form.headline ?? ''}
|
||||
onChange={(event) => updateForm('headline', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-subtitle">{t('invites.customizer.fields.subtitle', 'Unterzeile')}</Label>
|
||||
<Input
|
||||
id="invite-subtitle"
|
||||
value={form.subtitle ?? ''}
|
||||
onChange={(event) => updateForm('subtitle', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-description">{t('invites.customizer.fields.description', 'Beschreibung')}</Label>
|
||||
<Textarea
|
||||
id="invite-description"
|
||||
value={form.description ?? ''}
|
||||
onChange={(event) => updateForm('description', event.target.value)}
|
||||
className="min-h-[96px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-badge">{t('invites.customizer.fields.badge', 'Badge-Label')}</Label>
|
||||
<Input
|
||||
id="invite-badge"
|
||||
value={form.badge_label ?? ''}
|
||||
onChange={(event) => updateForm('badge_label', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-cta">{t('invites.customizer.fields.cta', 'Call-to-Action')}</Label>
|
||||
<Input
|
||||
id="invite-cta"
|
||||
value={form.cta_label ?? ''}
|
||||
onChange={(event) => updateForm('cta_label', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-link-heading">{t('invites.customizer.fields.linkHeading', 'Link-Überschrift')}</Label>
|
||||
<Input
|
||||
id="invite-link-heading"
|
||||
value={form.link_heading ?? ''}
|
||||
onChange={(event) => updateForm('link_heading', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-link-label">{t('invites.customizer.fields.linkLabel', 'Link/Begleittext')}</Label>
|
||||
<Input
|
||||
id="invite-link-label"
|
||||
value={form.link_label ?? ''}
|
||||
onChange={(event) => updateForm('link_label', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="instructions" className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-instruction-heading">{t('invites.customizer.fields.instructionsHeading', 'Abschnittsüberschrift')}</Label>
|
||||
<Input
|
||||
id="invite-instruction-heading"
|
||||
value={form.instructions_heading ?? ''}
|
||||
onChange={(event) => updateForm('instructions_heading', event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{instructions.map((entry, index) => (
|
||||
<div key={`instruction-${index}`} className="flex gap-2">
|
||||
<Input
|
||||
value={entry}
|
||||
onChange={(event) => handleInstructionChange(index, event.target.value)}
|
||||
placeholder={t('invites.customizer.fields.instructionPlaceholder', 'Beschreibung des Schritts')}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className="text-muted-foreground hover:text-destructive"
|
||||
onClick={() => handleRemoveInstruction(index)}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={handleAddInstruction} disabled={instructions.length >= MAX_INSTRUCTIONS}>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
{t('invites.customizer.actions.addInstruction', 'Punkt hinzufügen')}
|
||||
</Button>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="branding" className="space-y-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-accent">{t('invites.customizer.fields.accentColor', 'Akzentfarbe')}</Label>
|
||||
<Input
|
||||
id="invite-accent"
|
||||
type="color"
|
||||
value={form.accent_color ?? '#6366F1'}
|
||||
onChange={(event) => updateForm('accent_color', event.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-text-color">{t('invites.customizer.fields.textColor', 'Textfarbe')}</Label>
|
||||
<Input
|
||||
id="invite-text-color"
|
||||
type="color"
|
||||
value={form.text_color ?? '#111827'}
|
||||
onChange={(event) => updateForm('text_color', event.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-background-color">{t('invites.customizer.fields.backgroundColor', 'Hintergrund')}</Label>
|
||||
<Input
|
||||
id="invite-background-color"
|
||||
type="color"
|
||||
value={form.background_color ?? '#FFFFFF'}
|
||||
onChange={(event) => updateForm('background_color', event.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-badge-color">{t('invites.customizer.fields.badgeColor', 'Badge')}</Label>
|
||||
<Input
|
||||
id="invite-badge-color"
|
||||
type="color"
|
||||
value={form.badge_color ?? '#2563EB'}
|
||||
onChange={(event) => updateForm('badge_color', event.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{t('invites.customizer.fields.logo', 'Logo')}</Label>
|
||||
{form.logo_data_url ? (
|
||||
<div className="flex items-center gap-4 rounded-lg border border-[var(--tenant-border-strong)] bg-[var(--tenant-surface-muted)] p-3 text-[var(--tenant-foreground-soft)]">
|
||||
<img src={form.logo_data_url} alt="Logo" className="h-12 w-12 rounded border border-[var(--tenant-border-strong)] object-contain" />
|
||||
<Button type="button" variant="ghost" onClick={handleLogoRemove} className="text-destructive hover:text-destructive/80">
|
||||
{t('invites.customizer.actions.removeLogo', 'Logo entfernen')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<label className="flex cursor-pointer items-center gap-3 rounded-lg border border-dashed border-[var(--tenant-border-strong)] bg-[var(--tenant-surface-muted)] px-4 py-3 text-sm text-muted-foreground hover:border-primary">
|
||||
<UploadCloud className="h-4 w-4" />
|
||||
<span>{t('invites.customizer.actions.uploadLogo', 'Logo hochladen (max. 1 MB)')}</span>
|
||||
<input type="file" accept="image/*" className="hidden" onChange={handleLogoUpload} />
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
<div className={cn('mt-6 flex flex-col gap-2 sm:flex-row sm:justify-end lg:hidden', showFloatingActions ? 'hidden' : 'flex')}>
|
||||
{renderActionButtons('inline')}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user