/** * Platform Email Address Manager Component * Manages email addresses hosted on mail.talova.net via SSH */ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Mail, Plus, Trash2, Edit, CheckCircle, XCircle, Loader2, Star, TestTube, RefreshCw, Server, AlertTriangle, X, Download, Unlink, } from 'lucide-react'; import { usePlatformEmailAddresses, useDeletePlatformEmailAddress, useRemoveLocalPlatformEmailAddress, useTestImapConnection, useTestSmtpConnection, useSyncPlatformEmailAddress, useSetAsDefault, useTestMailServerConnection, useCreatePlatformEmailAddress, useUpdatePlatformEmailAddress, useAssignableUsers, useImportFromMailServer, PlatformEmailAddressListItem, } from '../hooks/usePlatformEmailAddresses'; import toast from 'react-hot-toast'; // Color options for email addresses const COLOR_OPTIONS = [ '#3b82f6', // blue '#10b981', // green '#f59e0b', // amber '#ef4444', // red '#8b5cf6', // violet '#ec4899', // pink '#06b6d4', // cyan '#f97316', // orange ]; interface EmailAddressFormData { display_name: string; sender_name: string; assigned_user_id: number | null; local_part: string; domain: string; color: string; password: string; is_active: boolean; is_default: boolean; } interface ConfirmModalState { isOpen: boolean; title: string; message: string; confirmText: string; confirmStyle: 'danger' | 'warning'; onConfirm: () => void; } const PlatformEmailAddressManager: React.FC = () => { const { t } = useTranslation(); const [isModalOpen, setIsModalOpen] = useState(false); const [editingAddress, setEditingAddress] = useState(null); const [confirmModal, setConfirmModal] = useState({ isOpen: false, title: '', message: '', confirmText: 'Confirm', confirmStyle: 'danger', onConfirm: () => {}, }); const [formData, setFormData] = useState({ display_name: '', sender_name: '', assigned_user_id: null, local_part: '', domain: 'smoothschedule.com', color: '#3b82f6', password: '', is_active: true, is_default: false, }); const [formErrors, setFormErrors] = useState>({}); const { data: emailAddresses = [], isLoading } = usePlatformEmailAddresses(); const { data: usersData } = useAssignableUsers(); const deleteAddress = useDeletePlatformEmailAddress(); const removeLocal = useRemoveLocalPlatformEmailAddress(); const testImap = useTestImapConnection(); const testSmtp = useTestSmtpConnection(); const syncAddress = useSyncPlatformEmailAddress(); const setDefault = useSetAsDefault(); const testMailServer = useTestMailServerConnection(); const createAddress = useCreatePlatformEmailAddress(); const updateAddress = useUpdatePlatformEmailAddress(); const importFromServer = useImportFromMailServer(); const handleAdd = () => { setEditingAddress(null); setFormData({ display_name: '', sender_name: '', assigned_user_id: null, local_part: '', domain: 'smoothschedule.com', color: '#3b82f6', password: '', is_active: true, is_default: false, }); setFormErrors({}); setIsModalOpen(true); }; const handleEdit = (address: PlatformEmailAddressListItem) => { setEditingAddress(address); setFormData({ display_name: address.display_name, sender_name: address.sender_name || '', assigned_user_id: address.assigned_user?.id || null, local_part: address.local_part, domain: address.domain, color: address.color, password: '', is_active: address.is_active, is_default: address.is_default, }); setFormErrors({}); setIsModalOpen(true); }; const handleCloseModal = () => { setIsModalOpen(false); setEditingAddress(null); setFormErrors({}); }; const validateForm = (): boolean => { const errors: Record = {}; if (!formData.display_name.trim()) { errors.display_name = 'Display name is required'; } if (!editingAddress && !formData.local_part.trim()) { errors.local_part = 'Email local part is required'; } else if (!editingAddress && !/^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/i.test(formData.local_part)) { errors.local_part = 'Invalid email format'; } if (!editingAddress && !formData.password) { errors.password = 'Password is required'; } else if (formData.password && formData.password.length < 8) { errors.password = 'Password must be at least 8 characters'; } setFormErrors(errors); return Object.keys(errors).length === 0; }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { return; } try { if (editingAddress) { // Update existing address const updateData: any = { display_name: formData.display_name, sender_name: formData.sender_name, assigned_user_id: formData.assigned_user_id, color: formData.color, is_active: formData.is_active, is_default: formData.is_default, }; if (formData.password) { updateData.password = formData.password; } await updateAddress.mutateAsync({ id: editingAddress.id, data: updateData, }); toast.success('Email address updated successfully'); } else { // Create new address await createAddress.mutateAsync({ display_name: formData.display_name, sender_name: formData.sender_name, assigned_user_id: formData.assigned_user_id, local_part: formData.local_part.toLowerCase(), domain: formData.domain, color: formData.color, password: formData.password, is_active: formData.is_active, is_default: formData.is_default, }); toast.success('Email address created and synced to mail server'); } handleCloseModal(); } catch (error: any) { const errorMessage = error.response?.data?.mail_server || error.response?.data?.local_part || error.response?.data?.detail || 'Failed to save email address'; toast.error(Array.isArray(errorMessage) ? errorMessage[0] : errorMessage); } }; const handleDelete = (id: number, displayName: string) => { setConfirmModal({ isOpen: true, title: 'Delete Email Address', message: `Are you sure you want to delete "${displayName}"? This will permanently remove the account from both the database and the mail server. This action cannot be undone.`, confirmText: 'Delete', confirmStyle: 'danger', onConfirm: async () => { try { await deleteAddress.mutateAsync(id); toast.success(`${displayName} deleted successfully`); } catch (error) { toast.error('Failed to delete email address'); } setConfirmModal(prev => ({ ...prev, isOpen: false })); }, }); }; const handleRemoveLocal = (id: number, displayName: string) => { setConfirmModal({ isOpen: true, title: 'Remove from Database', message: `Remove "${displayName}" from the database? The email account will remain active on the mail server and can be re-imported later.`, confirmText: 'Remove', confirmStyle: 'warning', onConfirm: async () => { try { const result = await removeLocal.mutateAsync(id); toast.success(result.message); } catch (error) { toast.error('Failed to remove email address'); } setConfirmModal(prev => ({ ...prev, isOpen: false })); }, }); }; const closeConfirmModal = () => { setConfirmModal(prev => ({ ...prev, isOpen: false })); }; const handleTestImap = async (id: number, displayName: string) => { toast.loading(`Testing IMAP connection for ${displayName}...`, { id: `imap-${id}` }); try { const result = await testImap.mutateAsync(id); if (result.success) { toast.success(result.message, { id: `imap-${id}` }); } else { toast.error(result.message, { id: `imap-${id}` }); } } catch (error: any) { toast.error(error.response?.data?.message || 'IMAP test failed', { id: `imap-${id}` }); } }; const handleTestSmtp = async (id: number, displayName: string) => { toast.loading(`Testing SMTP connection for ${displayName}...`, { id: `smtp-${id}` }); try { const result = await testSmtp.mutateAsync(id); if (result.success) { toast.success(result.message, { id: `smtp-${id}` }); } else { toast.error(result.message, { id: `smtp-${id}` }); } } catch (error: any) { toast.error(error.response?.data?.message || 'SMTP test failed', { id: `smtp-${id}` }); } }; const handleSync = async (id: number, displayName: string) => { toast.loading(`Syncing ${displayName} to mail server...`, { id: `sync-${id}` }); try { const result = await syncAddress.mutateAsync(id); if (result.success) { toast.success(result.message, { id: `sync-${id}` }); } else { toast.error(result.message, { id: `sync-${id}` }); } } catch (error: any) { toast.error(error.response?.data?.message || 'Sync failed', { id: `sync-${id}` }); } }; const handleSetDefault = async (id: number, displayName: string) => { try { const result = await setDefault.mutateAsync(id); if (result.success) { toast.success(result.message); } } catch (error) { toast.error('Failed to set as default'); } }; const handleTestMailServer = async () => { toast.loading('Testing connection to mail server...', { id: 'mail-server-test' }); try { const result = await testMailServer.mutateAsync(); if (result.success) { toast.success(result.message, { id: 'mail-server-test' }); } else { toast.error(result.message, { id: 'mail-server-test' }); } } catch (error: any) { toast.error(error.response?.data?.message || 'Connection test failed', { id: 'mail-server-test' }); } }; const handleImportFromServer = async () => { toast.loading('Importing email addresses from mail server...', { id: 'import-emails' }); try { const result = await importFromServer.mutateAsync(); if (result.success) { if (result.imported_count > 0) { toast.success(result.message, { id: 'import-emails' }); } else { toast.success('No new email addresses to import', { id: 'import-emails' }); } } else { toast.error('Import failed', { id: 'import-emails' }); } } catch (error: any) { toast.error(error.response?.data?.message || 'Import failed', { id: 'import-emails' }); } }; if (isLoading) { return (
); } return (
{/* Header */}

Email addresses are managed directly on the mail server (mail.talova.net)

{/* Email Addresses List */} {emailAddresses.length === 0 ? (

No email addresses configured

Add your first platform email address to start receiving support tickets

) : (
{emailAddresses.map((address) => (

{address.display_name}

{address.is_default && ( Default )} {address.is_active ? ( Active ) : ( Inactive )} {address.mail_server_synced ? ( Synced ) : ( Not Synced )}

{address.email_address}

Processed: {address.emails_processed_count} emails {address.last_check_at && ( Last checked: {new Date(address.last_check_at).toLocaleString()} )}
{!address.is_default && ( )}
))}
)} {/* Modal */} {isModalOpen && (

{editingAddress ? 'Edit Email Address' : 'Add Email Address'}

{/* Display Name */}
setFormData({ ...formData, display_name: e.target.value })} placeholder="e.g., Support, Billing, Sales" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> {formErrors.display_name && (

{formErrors.display_name}

)}
{/* Sender Name */}
setFormData({ ...formData, sender_name: e.target.value })} placeholder="e.g., SmoothSchedule Support Team" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent" />

Name shown in the From field of outgoing emails. If blank, uses Display Name.

{/* Assigned User */}

If assigned, the user's name will be used as the sender name in outgoing emails.

{/* Email Address (only show for new addresses) */} {!editingAddress && (
setFormData({ ...formData, local_part: e.target.value.toLowerCase() })} placeholder="support" className="flex-1 min-w-0 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> @smoothschedule.com
{formErrors.local_part && (

{formErrors.local_part}

)}
)} {/* Password */}
setFormData({ ...formData, password: e.target.value })} placeholder={editingAddress ? 'Leave blank to keep current' : 'Enter password'} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> {formErrors.password && (

{formErrors.password}

)}

Minimum 8 characters. This password will be synced to the mail server.

{/* Color */}
{COLOR_OPTIONS.map((color) => (
{/* Active & Default */}
{/* Actions */}
)} {/* Confirmation Modal */} {confirmModal.isOpen && (

{confirmModal.confirmStyle === 'danger' ? ( ) : ( )} {confirmModal.title}

{confirmModal.message}

)}
); }; export default PlatformEmailAddressManager;