Mandantenfähigkeit: Domain + Kunden-E-Mail-Label dynamisch pro Provider

Alle hardcoded Referenzen auf 'stressfrei-wechseln.de' und 'Stressfrei-Wechseln'
durch dynamische Werte aus der EmailProviderConfig ersetzt. Notwendig für
Multi-Mandanten-Betrieb, wenn das CRM an Dritte vermietet wird.

Schema:
- Neues Feld EmailProviderConfig.customerEmailLabel (String?)
- Wenn leer, wird Label aus Domain abgeleitet ('stressfrei-wechseln.de' → 'Stressfrei-Wechseln')

Backend:
- Neuer Endpoint GET /api/email-providers/public-settings liefert { domain, customerEmailLabel }
- Neue Service-Funktionen: getProviderPublicSettings(), deriveLabelFromDomain()
- create/updateProviderConfig erweitert um customerEmailLabel

Frontend:
- Neuer Hook useProviderSettings() mit Auto-Caching
- Neues Eingabefeld 'Bezeichnung für Kunden-E-Mails' im Provider-Modal
- Dynamische Domain-Suffix im Adress-Hinzufügen-Dialog (@<domain>)
- Tab-Label 'Stressfrei-Wechseln' im Kunden-Detail → dynamisch
- 'Stressfrei-Wechseln Adresse' in ContractForm → dynamisch
- '(Stressfrei-Wechseln)' Badge in ContractDetail → dynamisch
- 'Stressfrei-Wechseln E-Mail' im Generate-Modal → dynamisch
- Leere-Zustand-Meldungen in Tab und E-Mail-Client → dynamisch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 15:43:19 +02:00
parent cf4370c905
commit cdde7b4ab7
13 changed files with 156 additions and 19 deletions
@@ -18,6 +18,7 @@ import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
import BirthdayManagementModal from '../../components/BirthdayManagementModal';
import { formatDate } from '../../utils/dateFormat';
import { getContractTypeInfo } from '../../utils/contractInfo';
import { useProviderSettings } from '../../hooks/useProviderSettings';
import type { Address, BankCard, IdentityDocument, Meter, Customer, CustomerRepresentative, CustomerSummary, CustomerConsent, ConsentType, ConsentStatus, RepresentativeAuthorization } from '../../types';
export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?: number } = {}) {
@@ -31,6 +32,7 @@ export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?
const customerId = portalCustomerId || parseInt(id!);
const defaultTab = searchParams.get('tab') || 'addresses';
const [activeTab, setActiveTab] = useState(defaultTab);
const { customerEmailLabel } = useProviderSettings();
// Tab-Wechsel in URL synchronisieren (für Browser-History)
const handleTabChange = (tabId: string) => {
@@ -148,7 +150,7 @@ export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?
},
...(!isCustomerPortal ? [{
id: 'stressfrei',
label: 'Stressfrei-Wechseln',
label: customerEmailLabel,
content: (
<StressfreiEmailsTab
customerId={customerId}
@@ -2928,9 +2930,7 @@ function MeterReadingModal({
);
}
// ==================== STRESSFREI-WECHSELN E-MAIL TAB ====================
const STRESSFREI_DOMAIN = '@stressfrei-wechseln.de';
// ==================== KUNDEN-E-MAIL TAB ====================
function StressfreiEmailsTab({
customerId,
@@ -2950,6 +2950,7 @@ function StressfreiEmailsTab({
onEdit: (email: StressfreiEmail) => void;
}) {
const queryClient = useQueryClient();
const { customerEmailLabel } = useProviderSettings();
const updateMutation = useMutation({
mutationFn: ({ id, data }: { id: number; data: Partial<StressfreiEmail> }) =>
@@ -3067,7 +3068,7 @@ function StressfreiEmailsTab({
))}
</div>
) : (
<p className="text-gray-500">Keine Stressfrei-Wechseln Adressen vorhanden.</p>
<p className="text-gray-500">Keine {customerEmailLabel} Adressen vorhanden.</p>
)}
</div>
);
@@ -3240,6 +3241,10 @@ function StressfreiEmailModal({
const [provisionError, setProvisionError] = useState<string | null>(null);
const [providerStatus, setProviderStatus] = useState<'idle' | 'checking' | 'exists' | 'not_exists' | 'error'>('idle');
const [isProvisioning, setIsProvisioning] = useState(false);
// Domain dynamisch vom Provider (mit Fallback)
const { domain: providerDomain } = useProviderSettings();
const domainSuffix = `@${providerDomain || 'stressfrei-wechseln.de'}`;
const [isEnablingMailbox, setIsEnablingMailbox] = useState(false);
const [mailboxEnabled, setMailboxEnabled] = useState(false);
const [showCredentials, setShowCredentials] = useState(false);
@@ -3446,7 +3451,7 @@ function StressfreiEmailModal({
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setProvisionError(null);
const fullEmail = localPart + STRESSFREI_DOMAIN;
const fullEmail = localPart + domainSuffix;
if (isEditing) {
updateMutation.mutate({
@@ -3482,11 +3487,11 @@ function StressfreiEmailModal({
className="block w-full px-3 py-2 border border-gray-300 rounded-l-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
<span className="inline-flex items-center px-3 py-2 border border-l-0 border-gray-300 bg-gray-100 text-gray-600 rounded-r-lg text-sm">
{STRESSFREI_DOMAIN}
{domainSuffix}
</span>
</div>
<p className="text-xs text-gray-500 mt-1">
Vollständige Adresse: <span className="font-mono">{localPart || '...'}{STRESSFREI_DOMAIN}</span>
Vollständige Adresse: <span className="font-mono">{localPart || '...'}{domainSuffix}</span>
</p>
</div>