From 7cb1341a28d5866febc42b17c063c360001ade04 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Thu, 16 Jul 2026 21:55:38 +0200 Subject: [PATCH] feat(mail): rich text editor for signatures + placeholder variables - Replace textarea in SignatureManager with TipTap RichTextEditor - Add placeholder variable buttons: {{user.name}}, {{user.first_name}}, {{user.last_name}}, {{user.email}}, {{user.role}}, {{tenant.name}}, {{date}} - Add replaceSignatureVariables() utility in mail API - ComposeModal now replaces variables with actual user/tenant data when inserting signature - Variables resolved from auth store (user name, email, role, tenant name) --- frontend/src/api/mail.ts | 25 ++++++++++++ frontend/src/components/mail/ComposeModal.tsx | 14 +++++-- .../src/components/mail/SignatureManager.tsx | 39 +++++++++++++++---- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/frontend/src/api/mail.ts b/frontend/src/api/mail.ts index 6449cb2..fc45788 100644 --- a/frontend/src/api/mail.ts +++ b/frontend/src/api/mail.ts @@ -417,6 +417,31 @@ export function decodeMimeHeader(value: string | undefined | null): string { } } +/** + * Replace placeholder variables in signature HTML with actual user/tenant data. + * Variables: {{user.name}}, {{user.first_name}}, {{user.last_name}}, + * {{user.email}}, {{user.role}}, {{tenant.name}}, {{date}} + */ +export function replaceSignatureVariables( + html: string, + user: { name?: string; email?: string; role?: string; first_name?: string; last_name?: string }, + tenant?: { name?: string }, +): string { + const now = new Date(); + const dateStr = now.toLocaleDateString([], { year: 'numeric', month: '2-digit', day: '2-digit' }); + const firstName = user.first_name || (user.name ? user.name.split(' ')[0] : ''); + const lastName = user.last_name || (user.name ? user.name.split(' ').slice(1).join(' ') : ''); + + return html + .replace(/\{\{user\.name\}\}/gi, user.name || '') + .replace(/\{\{user\.first_name\}\}/gi, firstName) + .replace(/\{\{user\.last_name\}\}/gi, lastName) + .replace(/\{\{user\.email\}\}/gi, user.email || '') + .replace(/\{\{user\.role\}\}/gi, user.role || '') + .replace(/\{\{tenant\.name\}\}/gi, tenant?.name || '') + .replace(/\{\{date\}\}/gi, dateStr); +} + // ─── Folders ──────────────────────────────────────────────────────────────── export function fetchFolders(accountId: string): Promise { diff --git a/frontend/src/components/mail/ComposeModal.tsx b/frontend/src/components/mail/ComposeModal.tsx index 11dceaa..326c276 100644 --- a/frontend/src/components/mail/ComposeModal.tsx +++ b/frontend/src/components/mail/ComposeModal.tsx @@ -12,7 +12,8 @@ import { Select } from '@/components/ui/Select'; import { TemplatePicker } from './TemplatePicker'; import { RichTextEditor } from './RichTextEditor'; import type { Mail, MailSignature, SendMailPayload, ReplyPayload, ForwardPayload, MailDraftPayload } from '@/api/mail'; -import { uploadAttachment, type UploadedAttachment } from '@/api/mail'; +import { uploadAttachment, type UploadedAttachment, replaceSignatureVariables } from '@/api/mail'; +import { useAuthStore } from '@/store/authStore'; export type ComposeMode = 'new' | 'reply' | 'forward' | 'draft'; @@ -42,6 +43,8 @@ export function ComposeModal({ onClose, }: ComposeModalProps) { const { t } = useTranslation(); + const authUser = useAuthStore((s) => s.user); + const authTenant = useAuthStore((s) => s.currentTenant); const [to, setTo] = useState(''); const [cc, setCc] = useState(''); const [bcc, setBcc] = useState(''); @@ -87,9 +90,14 @@ export function ComposeModal({ setSelectedSignatureId(signatureId); const sig = signatures.find((s) => s.id === signatureId); if (sig) { - setBody((prev) => `${prev}

---
${sig.body_html}`); + const processedHtml = replaceSignatureVariables( + sig.body_html, + { name: authUser?.name, email: authUser?.email, role: authUser?.role, first_name: authUser?.first_name, last_name: authUser?.last_name }, + { name: authTenant?.name }, + ); + setBody((prev) => `${prev}

---
${processedHtml}`); } - }, [signatures]); + }, [signatures, authUser, authTenant]); const handleTemplateSelect = useCallback((templateBody: string, templateSubject: string) => { setBody(templateBody); diff --git a/frontend/src/components/mail/SignatureManager.tsx b/frontend/src/components/mail/SignatureManager.tsx index 211e9c1..b8dd060 100644 --- a/frontend/src/components/mail/SignatureManager.tsx +++ b/frontend/src/components/mail/SignatureManager.tsx @@ -1,5 +1,6 @@ /** - * Signature manager — CRUD for mail signatures. + * Signature manager — CRUD for mail signatures with rich text editor. + * Supports placeholder variables for user/tenant data. */ import React, { useState, useEffect, useCallback } from 'react'; @@ -10,6 +11,7 @@ import { Card } from '@/components/ui/Card'; import { EmptyState } from '@/components/ui/EmptyState'; import { useToast } from '@/components/ui/Toast'; import { ConfirmDialog } from '@/components/ui/ConfirmDialog'; +import { RichTextEditor } from './RichTextEditor'; import { fetchSignatures, createSignature, @@ -18,6 +20,17 @@ import { type MailSignature, } from '@/api/mail'; +/** Available placeholder variables for signatures. */ +const SIGNATURE_VARIABLES = [ + { token: '{{user.name}}', label: 'Vollständiger Name', description: 'Max Mustermann' }, + { token: '{{user.first_name}}', label: 'Vorname', description: 'Max' }, + { token: '{{user.last_name}}', label: 'Nachname', description: 'Mustermann' }, + { token: '{{user.email}}', label: 'E-Mail', description: 'max@firma.de' }, + { token: '{{user.role}}', label: 'Rolle', description: 'Admin' }, + { token: '{{tenant.name}}', label: 'Firma/Organisation', description: 'HMS Licht & Ton' }, + { token: '{{date}}', label: 'Aktuelles Datum', description: '16.07.2026' }, +]; + export function SignatureManager() { const { t } = useTranslation(); const toast = useToast(); @@ -134,12 +147,24 @@ export function SignatureManager() { />
-