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:
parent
cfcdf088df
commit
1290cdad10
|
|
@ -358,6 +358,10 @@ model EmailProviderConfig {
|
|||
systemEmailAddress String? // z.B. "info@stressfrei-wechseln.de"
|
||||
systemEmailPasswordEncrypted String? // Passwort (verschlüsselt)
|
||||
|
||||
// Label für Kunden-E-Mail-Adressen in der UI (z.B. "Stressfrei-Wechseln")
|
||||
// Wenn leer, wird automatisch aus der Domain abgeleitet (z.B. "stressfrei-wechseln.de" → "Stressfrei-Wechseln")
|
||||
customerEmailLabel String?
|
||||
|
||||
isActive Boolean @default(true)
|
||||
isDefault Boolean @default(false) // Standard-Provider
|
||||
createdAt DateTime @default(now())
|
||||
|
|
|
|||
|
|
@ -337,3 +337,20 @@ export async function getProviderDomain(req: Request, res: Response): Promise<vo
|
|||
} as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffentliche Provider-Einstellungen für die Frontend-UI:
|
||||
* Domain + Label für Kunden-E-Mail-Adressen.
|
||||
* Auch für Nicht-Admin-Mitarbeiter verfügbar, da nur UI-Labels.
|
||||
*/
|
||||
export async function getPublicSettings(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const settings = await emailProviderService.getProviderPublicSettings();
|
||||
res.json({ success: true, data: settings } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Fehler beim Laden der Einstellungen',
|
||||
} as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ router.delete('/configs/:id', authenticate, requirePermission('settings:update')
|
|||
router.post('/test-connection', authenticate, requirePermission('settings:update'), emailProviderController.testConnection);
|
||||
router.post('/test-mail-access', authenticate, requirePermission('settings:update'), emailProviderController.testMailAccess);
|
||||
router.get('/domain', authenticate, emailProviderController.getProviderDomain);
|
||||
router.get('/public-settings', authenticate, emailProviderController.getPublicSettings);
|
||||
router.get('/check/:localPart', authenticate, requirePermission('customers:read'), emailProviderController.checkEmailExists);
|
||||
router.post('/provision', authenticate, requirePermission('customers:update'), emailProviderController.provisionEmail);
|
||||
router.delete('/deprovision/:localPart', authenticate, requirePermission('customers:update'), emailProviderController.deprovisionEmail);
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ export interface CreateProviderConfigData {
|
|||
// System-E-Mail
|
||||
systemEmailAddress?: string;
|
||||
systemEmailPassword?: string;
|
||||
// UI-Label für Kunden-E-Mail-Adressen (z.B. "Stressfrei-Wechseln", "Meine-Firma")
|
||||
customerEmailLabel?: string;
|
||||
isActive?: boolean;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
|
@ -107,6 +109,7 @@ export async function createProviderConfig(data: CreateProviderConfigData) {
|
|||
allowSelfSignedCerts: data.allowSelfSignedCerts ?? false,
|
||||
systemEmailAddress: data.systemEmailAddress || null,
|
||||
systemEmailPasswordEncrypted,
|
||||
customerEmailLabel: data.customerEmailLabel || null,
|
||||
isActive: data.isActive ?? true,
|
||||
isDefault: data.isDefault ?? false,
|
||||
},
|
||||
|
|
@ -139,6 +142,7 @@ export async function updateProviderConfig(
|
|||
if (data.smtpEncryption !== undefined) updateData.smtpEncryption = data.smtpEncryption;
|
||||
if (data.allowSelfSignedCerts !== undefined) updateData.allowSelfSignedCerts = data.allowSelfSignedCerts;
|
||||
if (data.systemEmailAddress !== undefined) updateData.systemEmailAddress = data.systemEmailAddress || null;
|
||||
if (data.customerEmailLabel !== undefined) updateData.customerEmailLabel = data.customerEmailLabel?.trim() || null;
|
||||
if (data.isActive !== undefined) updateData.isActive = data.isActive;
|
||||
if (data.isDefault !== undefined) updateData.isDefault = data.isDefault;
|
||||
|
||||
|
|
@ -471,6 +475,39 @@ export async function getProviderDomain(): Promise<string | null> {
|
|||
return config?.domain || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label aus der Domain ableiten, z.B. "stressfrei-wechseln.de" → "Stressfrei-Wechseln".
|
||||
* Nimmt den Hauptteil bis zum ersten Punkt, trennt an "-" und kapitalisiert jeden Teil.
|
||||
*/
|
||||
export function deriveLabelFromDomain(domain: string | null | undefined): string {
|
||||
if (!domain) return 'Kunden-E-Mail';
|
||||
const mainPart = domain.split('.')[0] || domain;
|
||||
return mainPart
|
||||
.split('-')
|
||||
.map((s) => (s.length === 0 ? '' : s.charAt(0).toUpperCase() + s.slice(1)))
|
||||
.join('-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffentliche Provider-Einstellungen (Domain + Label) für UI.
|
||||
* Kein auth-geschütztes Geheimnis, nur damit die Frontend-Labels stimmen.
|
||||
*/
|
||||
export async function getProviderPublicSettings(): Promise<{
|
||||
domain: string | null;
|
||||
customerEmailLabel: string;
|
||||
customerEmailLabelIsCustom: boolean;
|
||||
}> {
|
||||
const config = await getActiveProviderConfig();
|
||||
const domain = config?.domain ?? null;
|
||||
const customLabel = config?.customerEmailLabel?.trim();
|
||||
|
||||
return {
|
||||
domain,
|
||||
customerEmailLabel: customLabel && customLabel.length > 0 ? customLabel : deriveLabelFromDomain(domain),
|
||||
customerEmailLabelIsCustom: !!(customLabel && customLabel.length > 0),
|
||||
};
|
||||
}
|
||||
|
||||
// Provider-Instanz aus übergebener Config erstellen (für Tests mit ungespeicherten Daten)
|
||||
function createProviderFromFormData(data: {
|
||||
type: 'PLESK' | 'CPANEL' | 'DIRECTADMIN';
|
||||
|
|
|
|||
|
|
@ -14,6 +14,15 @@
|
|||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **Mandantenfähigkeit: Domain + Kunden-E-Mail-Label dynamisch pro Provider**
|
||||
- Neues Feld `customerEmailLabel` am EmailProviderConfig (z.B. "Stressfrei-Wechseln", "Meine-Firma")
|
||||
- Wenn leer, wird das Label automatisch aus der Domain abgeleitet ("stressfrei-wechseln.de" → "Stressfrei-Wechseln")
|
||||
- Neuer Frontend-Hook `useProviderSettings()` liefert Domain + Label
|
||||
- Alle hardcoded "Stressfrei-Wechseln" und `@stressfrei-wechseln.de` Strings durch dynamische Werte ersetzt
|
||||
(CustomerDetail, ContractForm, ContractDetail, EmailClientTab, Settings)
|
||||
- Modal-Eingabefeld "Bezeichnung für Kunden-E-Mails" in Provider-Einstellungen
|
||||
- Notwendig für Multi-Mandanten-Betrieb wenn das CRM an Dritte vermietet wird
|
||||
|
||||
- [x] **Factory-Defaults: Export + Import von Stammdaten-Katalogen**
|
||||
- Enthält: Anbieter, Tarife, Kündigungsfristen, Laufzeiten, Vertragskategorien, PDF-Auftragsvorlagen (+ PDF-Dateien)
|
||||
- Enthält NICHT: Kundendaten, Verträge, Dokumente, Emails, Einstellungen (dafür gibt es den Datenbank-Backup)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|||
import toast from 'react-hot-toast';
|
||||
import { cachedEmailApi, stressfreiEmailApi, CachedEmail } from '../../services/api';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useProviderSettings } from '../../hooks/useProviderSettings';
|
||||
import Button from '../ui/Button';
|
||||
import EmailList from './EmailList';
|
||||
import EmailDetail from './EmailDetail';
|
||||
|
|
@ -18,6 +19,7 @@ interface EmailClientTabProps {
|
|||
}
|
||||
|
||||
export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
||||
const { customerEmailLabel } = useProviderSettings();
|
||||
const [selectedAccountId, setSelectedAccountId] = useState<number | null>(null);
|
||||
const [selectedFolder, setSelectedFolder] = useState<EmailFolder>('INBOX');
|
||||
const [selectedEmail, setSelectedEmail] = useState<CachedEmail | null>(null);
|
||||
|
|
@ -150,7 +152,7 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) {
|
|||
Keine E-Mail-Konten vorhanden
|
||||
</h3>
|
||||
<p className="text-sm text-center max-w-md">
|
||||
Erstellen Sie eine Stressfrei-Wechseln E-Mail-Adresse mit aktivierter Mailbox,
|
||||
Erstellen Sie eine {customerEmailLabel} E-Mail-Adresse mit aktivierter Mailbox,
|
||||
um E-Mails hier empfangen und versenden zu können.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { emailProviderApi } from '../services/api';
|
||||
|
||||
export interface ProviderSettings {
|
||||
domain: string | null;
|
||||
customerEmailLabel: string; // z.B. "Stressfrei-Wechseln" (aus Config oder aus Domain abgeleitet)
|
||||
customerEmailLabelIsCustom: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt die öffentlichen Provider-Einstellungen (Domain + Label für Kunden-E-Mail-Adressen).
|
||||
* Mit Default-Fallback bei Ladefehler – UI-Labels werden dann generisch angezeigt.
|
||||
*/
|
||||
export function useProviderSettings(): ProviderSettings {
|
||||
const { data } = useQuery({
|
||||
queryKey: ['email-provider-public-settings'],
|
||||
queryFn: () => emailProviderApi.getPublicSettings(),
|
||||
staleTime: 5 * 60_000,
|
||||
});
|
||||
|
||||
return {
|
||||
domain: data?.data?.domain ?? null,
|
||||
customerEmailLabel: data?.data?.customerEmailLabel || 'Kunden-E-Mail',
|
||||
customerEmailLabelIsCustom: data?.data?.customerEmailLabelIsCustom ?? false,
|
||||
};
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ export default function Settings() {
|
|||
Email-Provisionierung
|
||||
<ChevronRight className="w-4 h-4 opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 mt-1">Konfigurieren Sie die automatische E-Mail-Erstellung für Stressfrei-Wechseln Adressen.</p>
|
||||
<p className="text-sm text-gray-500 mt-1">Konfigurieren Sie die automatische E-Mail-Erstellung für Kunden-E-Mail-Adressen.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { Edit, Trash2, Copy, Eye, EyeOff, ArrowLeft, ArrowRight, Download, Exter
|
|||
import { calculateConsumption, calculateCosts, calculateMultiMeterConsumption } from '../../utils/energyCalculations';
|
||||
import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
|
||||
import { formatDate } from '../../utils/dateFormat';
|
||||
import { useProviderSettings } from '../../hooks/useProviderSettings';
|
||||
import type { ContractType, ContractStatus, SimCard, MeterReading, ContractTask, ContractTaskSubtask, ContractMeter, ContractDocument } from '../../types';
|
||||
|
||||
const typeLabels: Record<ContractType, string> = {
|
||||
|
|
@ -1444,6 +1445,7 @@ export default function ContractDetail() {
|
|||
const currentPath = `/contracts/${id}`;
|
||||
const { hasPermission, isCustomer, isCustomerPortal } = useAuth();
|
||||
const contractId = parseInt(id!);
|
||||
const { customerEmailLabel } = useProviderSettings();
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [decryptedPassword, setDecryptedPassword] = useState<string | null>(null);
|
||||
|
|
@ -2310,7 +2312,7 @@ export default function ContractDetail() {
|
|||
<dt className="text-sm text-gray-500">
|
||||
Benutzername
|
||||
{c.stressfreiEmail && (
|
||||
<span className="ml-2 text-xs text-blue-600">(Stressfrei-Wechseln)</span>
|
||||
<span className="ml-2 text-xs text-blue-600">({customerEmailLabel})</span>
|
||||
)}
|
||||
</dt>
|
||||
<dd className="font-mono flex items-center gap-1">
|
||||
|
|
@ -3363,6 +3365,7 @@ function GenerateInputModal({ templateId, templateName, contractId, onClose }: {
|
|||
const [stressfreiEmailId, setStressfreiEmailId] = useState('');
|
||||
const [manualValues, setManualValues] = useState<Record<string, string>>({});
|
||||
const [generating] = useState(false);
|
||||
const { customerEmailLabel } = useProviderSettings();
|
||||
|
||||
const { data: inputsData, isLoading } = useQuery({
|
||||
queryKey: ['pdf-inputs', templateId, contractId],
|
||||
|
|
@ -3390,7 +3393,7 @@ function GenerateInputModal({ templateId, templateName, contractId, onClose }: {
|
|||
<div className="space-y-4">
|
||||
{inputs?.needsStressfreiEmail && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Stressfrei-Wechseln E-Mail</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{customerEmailLabel} E-Mail</label>
|
||||
<select
|
||||
value={stressfreiEmailId}
|
||||
onChange={(e) => setStressfreiEmailId(e.target.value)}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import Input from '../../components/ui/Input';
|
|||
import Select from '../../components/ui/Select';
|
||||
import type { ContractType } from '../../types';
|
||||
import { formatDate } from '../../utils/dateFormat';
|
||||
import { useProviderSettings } from '../../hooks/useProviderSettings';
|
||||
import { Plus, Trash2, Eye, EyeOff, Info, X, ArrowLeft } from 'lucide-react';
|
||||
|
||||
// Contract types are now loaded dynamically from the database
|
||||
|
|
@ -67,6 +68,7 @@ export default function ContractForm() {
|
|||
const queryClient = useQueryClient();
|
||||
const isEdit = !!id;
|
||||
const back = popHistory(location.state, isEdit ? `/contracts/${id}` : '/contracts');
|
||||
const { customerEmailLabel } = useProviderSettings();
|
||||
|
||||
const preselectedCustomerId = searchParams.get('customerId');
|
||||
|
||||
|
|
@ -914,7 +916,7 @@ export default function ContractForm() {
|
|||
}}
|
||||
className="text-blue-600"
|
||||
/>
|
||||
<span className="text-sm">Stressfrei-Wechseln Adresse</span>
|
||||
<span className="text-sm">{customerEmailLabel} Adresse</span>
|
||||
</label>
|
||||
{usernameType === 'stressfrei' && (
|
||||
<Select
|
||||
|
|
@ -929,7 +931,7 @@ export default function ContractForm() {
|
|||
)}
|
||||
{usernameType === 'stressfrei' && stressfreiEmails.length === 0 && (
|
||||
<p className="text-xs text-amber-600">
|
||||
Keine Stressfrei-Wechseln Adressen für diesen Kunden vorhanden. Bitte zuerst beim Kunden anlegen.
|
||||
Keine {customerEmailLabel} Adressen für diesen Kunden vorhanden. Bitte zuerst beim Kunden anlegen.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ interface ProviderFormData {
|
|||
// System-E-Mail
|
||||
systemEmailAddress: string;
|
||||
systemEmailPassword: string;
|
||||
// UI-Label für Kunden-E-Mail-Adressen
|
||||
customerEmailLabel: string;
|
||||
isActive: boolean;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
|
@ -57,6 +59,7 @@ const emptyForm: ProviderFormData = {
|
|||
allowSelfSignedCerts: false,
|
||||
systemEmailAddress: '',
|
||||
systemEmailPassword: '',
|
||||
customerEmailLabel: '',
|
||||
isActive: true,
|
||||
isDefault: false,
|
||||
};
|
||||
|
|
@ -143,6 +146,7 @@ export default function EmailProviders() {
|
|||
allowSelfSignedCerts: config.allowSelfSignedCerts ?? false,
|
||||
systemEmailAddress: config.systemEmailAddress || '',
|
||||
systemEmailPassword: '', // Passwort wird nicht geladen
|
||||
customerEmailLabel: config.customerEmailLabel || '',
|
||||
isActive: config.isActive,
|
||||
isDefault: config.isDefault,
|
||||
});
|
||||
|
|
@ -290,6 +294,7 @@ export default function EmailProviders() {
|
|||
smtpEncryption: formData.smtpEncryption,
|
||||
allowSelfSignedCerts: formData.allowSelfSignedCerts,
|
||||
systemEmailAddress: formData.systemEmailAddress,
|
||||
customerEmailLabel: formData.customerEmailLabel?.trim() || null,
|
||||
isActive: formData.isActive,
|
||||
isDefault: formData.isDefault,
|
||||
};
|
||||
|
|
@ -336,9 +341,9 @@ export default function EmailProviders() {
|
|||
|
||||
<Card className="mb-6">
|
||||
<p className="text-gray-600 mb-4">
|
||||
Hier konfigurieren Sie die automatische Erstellung von Stressfrei-Wechseln E-Mail-Adressen.
|
||||
Wenn beim Anlegen einer Stressfrei-Adresse die Option "Bei Provider anlegen" aktiviert ist,
|
||||
wird die E-Mail-Weiterleitung automatisch erstellt.
|
||||
Hier konfigurieren Sie die automatische Erstellung von Kunden-E-Mail-Adressen auf Ihrer
|
||||
eigenen Domain. Wenn beim Anlegen einer Adresse die Option "Bei Provider anlegen"
|
||||
aktiviert ist, wird die E-Mail-Weiterleitung automatisch erstellt.
|
||||
</p>
|
||||
<Button onClick={openCreateModal}>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
|
|
@ -550,6 +555,16 @@ export default function EmailProviders() {
|
|||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Bezeichnung für Kunden-E-Mails (UI-Label)"
|
||||
value={formData.customerEmailLabel}
|
||||
onChange={(e) => setFormData({ ...formData, customerEmailLabel: e.target.value })}
|
||||
placeholder={`wird aus Domain abgeleitet, z.B. "${formData.domain ? formData.domain.split('.')[0].split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('-') : 'Stressfrei-Wechseln'}"`}
|
||||
/>
|
||||
<p className="text-xs text-gray-500 -mt-2">
|
||||
Wird überall dort angezeigt, wo es bisher "Stressfrei-Wechseln" hieß (z.B. Tab-Name, Adress-Listen). Wenn leer, wird der Name aus der Domain abgeleitet.
|
||||
</p>
|
||||
|
||||
<Input
|
||||
label="Standard-Weiterleitungsadresse"
|
||||
value={formData.defaultForwardEmail}
|
||||
|
|
@ -758,7 +773,10 @@ export default function EmailProviders() {
|
|||
<div>
|
||||
<strong>IMAP</strong> fehlgeschlagen
|
||||
{mailAccessResult.imap.server && (
|
||||
<span className="text-xs opacity-75"> ({mailAccessResult.imap.server}:{mailAccessResult.imap.port})</span>
|
||||
<span className="text-xs opacity-75">
|
||||
{' '}({mailAccessResult.imap.server}:{mailAccessResult.imap.port}
|
||||
{mailAccessResult.imap.encryption ? `, ${mailAccessResult.imap.encryption}` : ''})
|
||||
</span>
|
||||
)}
|
||||
{mailAccessResult.imap.error && (
|
||||
<div className="mt-1 text-xs">{mailAccessResult.imap.error}</div>
|
||||
|
|
@ -783,7 +801,10 @@ export default function EmailProviders() {
|
|||
<div>
|
||||
<strong>SMTP</strong> fehlgeschlagen
|
||||
{mailAccessResult.smtp.server && (
|
||||
<span className="text-xs opacity-75"> ({mailAccessResult.smtp.server}:{mailAccessResult.smtp.port})</span>
|
||||
<span className="text-xs opacity-75">
|
||||
{' '}({mailAccessResult.smtp.server}:{mailAccessResult.smtp.port}
|
||||
{mailAccessResult.smtp.encryption ? `, ${mailAccessResult.smtp.encryption}` : ''})
|
||||
</span>
|
||||
)}
|
||||
{mailAccessResult.smtp.error && (
|
||||
<div className="mt-1 text-xs">{mailAccessResult.smtp.error}</div>
|
||||
|
|
|
|||
|
|
@ -1200,6 +1200,8 @@ export interface EmailProviderConfig {
|
|||
// System-E-Mail für automatisierten Versand
|
||||
systemEmailAddress?: string;
|
||||
systemEmailPasswordEncrypted?: string;
|
||||
// UI-Label für Kunden-E-Mail-Adressen (z.B. "Stressfrei-Wechseln")
|
||||
customerEmailLabel?: string | null;
|
||||
isActive: boolean;
|
||||
isDefault: boolean;
|
||||
createdAt: string;
|
||||
|
|
@ -1275,6 +1277,14 @@ export const emailProviderApi = {
|
|||
const res = await api.get<ApiResponse<{ domain: string | null }>>('/email-providers/domain');
|
||||
return res.data;
|
||||
},
|
||||
getPublicSettings: async () => {
|
||||
const res = await api.get<ApiResponse<{
|
||||
domain: string | null;
|
||||
customerEmailLabel: string;
|
||||
customerEmailLabelIsCustom: boolean;
|
||||
}>>('/email-providers/public-settings');
|
||||
return res.data;
|
||||
},
|
||||
checkEmailExists: async (localPart: string) => {
|
||||
const res = await api.get<ApiResponse<{ exists: boolean; email?: string }>>(`/email-providers/check/${localPart}`);
|
||||
return res.data;
|
||||
|
|
|
|||
Loading…
Reference in New Issue