import { useState, useCallback, useEffect, useRef } from 'react'; import type { DashboardData } from './types'; import { SMTP_PRESETS } from './data/smtp-presets'; import { Label } from './components/ui/label'; import { Switch } from './components/ui/switch'; import { RefreshCw, Send, Zap, Plus, CheckCircle2, Settings2, Clock as ClockIcon, Globe, Mail, ArrowRight, ArrowLeft, ShieldCheck, Tag, Hash, User, Lock, UserCircle, X, BookOpen, Terminal } from 'lucide-react'; import { SearchableSelect } from './components/ui/searchable-select'; import { SettingsLayout, SettingsHeader, ModernCardHeader, SettingCard, AdminButton, SecondaryButton, GhostButton, SettingInput, MethodButton, StatusMessage, SaveWithStatus, SaveFooter, DeleteButton } from './components/ui/settings-ui'; import { useSettingsSave } from './hooks/useSettingsSave'; import { Sheet, SheetContent } from './components/ui/sheet'; interface SMTPSettings { provider: string; host?: string; port?: number; encryption?: string; user?: string; pass?: string; from_email?: string; from_name?: string; auth?: number; api_key?: string; domain?: string; region?: string; access_key?: string; secret_key?: string; } interface SMTPAccount { id: number; account_name: string; provider: string; is_active: boolean; validated: boolean; config: SMTPSettings; } export default function SMTPSender({ data: dashboardData }: { data: DashboardData }) { const t = useCallback((key: string, fallback: string = ''): string => { const val = (dashboardData.smtpSenderData?.i18n as Record)?.[key] || (dashboardData.i18n as Record)?.[key]; return typeof val === 'string' ? val : fallback; }, [dashboardData.smtpSenderData?.i18n, dashboardData.i18n]); const PROVIDERS = [ { id: 'wp', name: 'WordPress', iconPath: `${dashboardData?.global?.pluginUrl}assets/icons/wordpress.svg`, desc: t('wpProviderDesc', 'Default PHPMailer') }, { id: 'smtp', name: 'Custom SMTP', iconPath: `${dashboardData?.global?.pluginUrl}assets/img/senders/gmail.svg`, desc: t('smtpProviderDesc', 'Any Mail Server') }, ]; const [accounts, setAccounts] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingId, setEditingId] = useState(null); const editingIdRef = useRef(null); const [currentStep, setCurrentStep] = useState(1); const [accountName, setAccountName] = useState(''); const [isUserEditedName, setIsUserEditedName] = useState(false); const [settings, setSettings] = useState({ provider: 'smtp', host: '', port: 587, encryption: 'tls', user: '', pass: '', from_email: '', from_name: '', auth: 1, api_key: '', domain: '', region: 'us', access_key: '', secret_key: '' }); const [statusMessage, setStatusMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null); const [testLog, setTestLog] = useState(null); const [isTestingConnect, setIsTestingConnect] = useState(false); const [isTestingSend, setIsTestingSend] = useState(false); const [testEmail, setTestEmail] = useState(''); const [presetSearch, setPresetSearch] = useState(''); // Keep the mutable ref in sync with the state so the async save handler always reads the latest ID. useEffect(() => { editingIdRef.current = editingId; }, [editingId]); const { saveStatus, hasUnsavedChanges, isSaving, handleSave, markDirty } = useSettingsSave({ onSave: async (silent = true) => { try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/smtp`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ account_name: accountName, provider: settings.provider, config: settings, id: editingIdRef.current }) }); const result = await response.json(); if (result.success) { // If creating new, update the ref immediately so any queued auto-save targets the new row. if (!editingIdRef.current && (result.id || result.data?.id)) { const newId = result.id || result.data.id; editingIdRef.current = newId; setEditingId(newId); } fetchAccounts(); // Only close dialog on explicit (manual) save, not on auto-save if (!silent) { setIsDialogOpen(false); } window.dispatchEvent(new CustomEvent('wawp-refresh-data', { detail: { section: 'smtp-sender' } })); return true; } setStatusMessage({ type: 'error', text: result.message || t('saveFailed', 'Save failed. Please check your settings and try again.') }); return false; } catch { setStatusMessage({ type: 'error', text: t('errorOccurred', 'An error occurred.') }); return false; } } }); // Before unload warning useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (hasUnsavedChanges && isDialogOpen) { e.preventDefault(); e.returnValue = ''; } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [hasUnsavedChanges, isDialogOpen]); const fetchAccounts = useCallback(async () => { setIsLoading(true); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/smtp?_=${Date.now()}`, { headers: { 'X-WP-Nonce': dashboardData.global.wpRestNonce } }); const result = await response.json(); if (result.success) { setAccounts(result.data || []); } } catch (error) { console.error('Failed to fetch SMTP accounts:', error); } setIsLoading(false); }, [dashboardData.global.apiRestUrl, dashboardData.global.wpRestNonce]); const [prevData, setPrevData] = useState(dashboardData); if (dashboardData !== prevData) { setPrevData(dashboardData); fetchAccounts(); } useEffect(() => { const timer = setTimeout(() => { fetchAccounts(); }, 0); return () => clearTimeout(timer); }, [fetchAccounts]); const handleInputChange = (field: K, value: SMTPSettings[K]) => { setSettings(prev => { const next = { ...prev, [field]: value }; // Auto-fill account name if user hasn't edited it manually if (field === 'provider' && !isUserEditedName) { const providerName = PROVIDERS.find(p => p.id === value)?.name || ''; setAccountName(`${providerName} Sender`); } return next; }); markDirty(); }; const openAddDialog = () => { setEditingId(null); editingIdRef.current = null; setAccountName('Custom SMTP Sender'); setIsUserEditedName(false); setCurrentStep(1); setSettings({ provider: 'smtp', host: '', port: 587, encryption: 'tls', user: '', pass: '', from_email: '', from_name: '', auth: 1, api_key: '', domain: '', region: 'us', access_key: '', secret_key: '' }); setStatusMessage(null); setTestLog(null); setIsDialogOpen(true); }; const openEditDialog = (account: SMTPAccount) => { setEditingId(account.id); editingIdRef.current = account.id; setAccountName(account.account_name); setIsUserEditedName(true); setCurrentStep(1); setSettings({ ...account.config, provider: account.provider || 'smtp', pass: '' }); setStatusMessage(null); setTestLog(null); setIsDialogOpen(true); }; const deleteAccount = async (id: number) => { if (!confirm(t('deleteConfirm', 'Are you sure you want to delete this account?'))) return; try { const resp = await fetch(`${dashboardData.global.apiRestUrl}/accounts/smtp/${id}`, { method: 'DELETE', headers: { 'X-WP-Nonce': dashboardData.global.wpRestNonce } }); const result = await resp.json(); if (result.success) { fetchAccounts(); window.dispatchEvent(new CustomEvent('wawp-refresh-data', { detail: { section: 'smtp-sender' } })); } } catch (error) { console.error('Failed to delete account:', error); } }; const toggleAccount = async (id: number, active: boolean) => { try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/smtp/${id}/toggle`, { method: 'POST', headers: { 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ active }) }); const result = await response.json(); if (result.success) { fetchAccounts(); window.dispatchEvent(new CustomEvent('wawp-refresh-data', { detail: { section: 'smtp-sender' } })); } } catch (error) { console.error('Failed to toggle account:', error); } }; const testConnection = async () => { setIsTestingConnect(true); setStatusMessage(null); setTestLog(null); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/smtp/validate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ provider: settings.provider, config: { ...settings, id: editingIdRef.current } }) }); const result = await response.json(); if (result.success) { setStatusMessage({ type: 'success', text: result.message || result.data?.message || t('connectionSuccessful', 'Connection successful!') }); setTestLog(result.log || null); if (editingIdRef.current) fetchAccounts(); } else { const errorText = result.message || (typeof result.data === 'string' ? result.data : result.data?.message) || t('connectionFailed', 'Connection failed.'); setStatusMessage({ type: 'error', text: errorText }); setTestLog(result.log || null); } } catch { setStatusMessage({ type: 'error', text: t('errorDuringTest', 'An error occurred during test.') }); } setIsTestingConnect(false); }; const sendTestEmail = async () => { if (!testEmail) { setStatusMessage({ type: 'error', text: t('enterTestEmail', 'Please enter a test email address.') }); return; } setIsTestingSend(true); setStatusMessage(null); setTestLog(null); try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/smtp/test`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ to: testEmail, id: editingIdRef.current, config: settings }) }); const result = await response.json(); if (result.success) { setStatusMessage({ type: 'success', text: result.message || result.data || t('testEmailSent', 'Test email sent successfully.') }); } else { const errorText = typeof result.data === 'string' ? result.data : (result.data?.message || result.message || t('failedSendTestEmail', 'Failed to send test email.')); setStatusMessage({ type: 'error', text: errorText }); } } catch { setStatusMessage({ type: 'error', text: t('errorOccurred', 'An error occurred.') }); } setIsTestingSend(false); }; const nextStep = () => setCurrentStep(prev => prev + 1); const prevStep = () => setCurrentStep(prev => prev - 1); return (
{/* Header */}
{t('addAccount', 'Add Account')} window.open(`https://help.wawp.net/${dashboardData?.rtl ? 'ar' : 'en'}/articles/how-to-setup-smtp-email`, '_blank')} title={dashboardData?.rtl ? 'دليل إعداد بريد SMTP' : 'SMTP Setup Guide'} >
{/* Accounts Table */} {t('smtpAccountsDesc', 'Manage configurations and validation status')}} />
{isLoading ? ( null ) : accounts.length === 0 ? ( ) : accounts.map((account) => ( ))}
{t('defaultSender', 'Default Sender')} {t('accountName', 'Account Name')} {t('provider', 'Provider')} {t('serverStatus', 'Server Status')} {t('actions', 'Actions')}
{t('noAccountsConfigured', 'No accounts configured yet.')}
{account.is_active ? ( {t('selected', 'Selected')} ) : ( toggleAccount(account.id, true)} className="h-7 px-3 text-[10px] font-bold uppercase tracking-widest text-slate-400 hover:text-[#004449] hover:bg-slate-100" > {t('setDefault', 'Set Default')} )}
{account.account_name} {account.provider === 'smtp' && ( {account.config?.host}:{account.config?.port} )}
{(() => { let name = ''; let iconSrc = ''; if (account.provider === 'wp') { const p = PROVIDERS.find(pr => pr.id === 'wp'); name = p?.name || 'WordPress'; iconSrc = p?.iconPath || ''; } else { const matchedPreset = SMTP_PRESETS.find( preset => preset.host === account.config?.host && Number(preset.port) === Number(account.config?.port) ); if (matchedPreset) { name = matchedPreset.name; iconSrc = `${dashboardData?.global?.pluginUrl}${matchedPreset.iconFile}`; } else { const p = PROVIDERS.find(pr => pr.id === 'smtp'); name = p?.name || 'Custom SMTP'; iconSrc = p?.iconPath || ''; } } return ( <>
{iconSrc ? ( {name} { (e.target as HTMLImageElement).style.display = 'none'; }} /> ) : ( )}
{name} ); })()}
{account.validated ? ( {t('active', 'Active')} ) : ( {t('pendingSetup', 'Pending Setup')} )}
openEditDialog(account)} className="h-8 px-3 text-[10px] font-bold uppercase tracking-widest text-blue-600 border-blue-100 hover:bg-blue-50 rounded-[4px]" icon={Settings2}> {t('editAndTest', 'Edit & Test')} deleteAccount(account.id)} size="sm" />
{/* Dialog: 3-Step Wizard */}
Step {currentStep} of 3 — {currentStep === 1 ? t('chooseDeliveryMethod', 'Choose Delivery Method') : currentStep === 2 ? t('configureCredentials', 'Configure Credentials') : t('diagnosticCheck', 'Diagnostic Check')}
} iconPath={`${dashboardData?.global?.pluginUrl}assets/img/senders/gmail.svg`} rightAction={ setIsDialogOpen(false)} size="icon-sm" className="text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded-full" icon={X} > {null} } /> {/* Progress Bar Container */}
{statusMessage && (
{statusMessage.type === 'success' ? : } {typeof statusMessage.text === 'string' ? statusMessage.text : JSON.stringify(statusMessage.text)}
)} {testLog && (
{t('serverLog', 'Server Log')}
{testLog}
)} {/* STEP 1: CHOOSE SENDER */} {currentStep === 1 && (
{t('providerSelection', 'Provider Selection')}
{PROVIDERS.map((p) => ( handleInputChange('provider', p.id)} label={p.name} description={p.desc} iconPath={p.iconPath} activeStyles="bg-white border-[#004449] ring-4 ring-[#004449]/5" /> ))}
{t('management', 'Management')}
{ setAccountName(val); setIsUserEditedName(true); }} placeholder={t('accountFriendlyNamePlaceholder', 'e.g. Sales Team SMTP')} icon={Tag} />

{t('accountFriendlyNameDesc', 'This name is only for your reference in the dashboard.')}

)} {/* STEP 2: CONFIGURATION */} {currentStep === 2 && (
{/* ─── Provider Preset Picker (Custom SMTP only) ─── */} {settings.provider === 'smtp' && (() => { const CATS: Record = { popular: t('catPopular', 'Popular'), transactional: t('catTransactional', 'Transactional'), hosting: t('catHosting', 'Hosting'), enterprise: t('catEnterprise', 'Enterprise'), regional: t('catRegional', 'Regional / ISP') }; const q = presetSearch; const filtered = SMTP_PRESETS.filter(p => !q || p.name.toLowerCase().includes(q.toLowerCase()) || p.host.toLowerCase().includes(q.toLowerCase())); const activePreset = SMTP_PRESETS.find(p => settings.host === p.host && Number(settings.port) === p.port); return ( {SMTP_PRESETS.length} {t('providersCountLabel', 'Providers')}} />
setPresetSearch(e.target.value)} className="w-full !h-8 px-3.5 rounded-[5px] border border-slate-200 bg-white text-xs font-medium text-slate-700 placeholder:text-slate-300 focus:ring-1 focus:ring-[#004449]/30 focus:border-[#004449]/30 outline-none transition-all" />
{Object.entries(CATS).map(([catKey, catLabel]) => { const items = filtered.filter(p => p.category === catKey); if (items.length === 0) return null; return (
{catLabel}
{items.map((preset) => { const isActive = settings.host === preset.host && Number(settings.port) === preset.port; return ( ); })}
); })} {filtered.length === 0 && (

{t('noProvidersMatch', 'No providers match your search.')}

)}
{activePreset?.hint && (

💡 {activePreset.hint}

)}
); })()} {/* ─── Connection Credentials ─── */} p.id === settings.provider)?.name}`} description={{t('configureCredentials', 'Configure Credentials')}} />
{settings.provider === 'wp' ? (

{t('systemIntegrationReady', 'System Integration Ready')}

{t('systemIntegrationReadyDesc', "Wawp will use your server's default mailer. Ensure your hosting provider supports PHPMailer or has sendmail configured.")}

) : (
handleInputChange('host', val)} placeholder="smtp.gmail.com" icon={Globe} /> handleInputChange('port', Number(val))} placeholder="587" icon={Hash} />
handleInputChange('encryption', v)} className="h-8 bg-white border-slate-200 text-slate-700 font-bold text-[12px] hover:border-[#004449]/30 transition-all rounded-[5px]" />
{t('loginRequired', 'Login Required')} handleInputChange('auth', c ? 1 : 0)} />
{!!settings.auth && (
handleInputChange('user', val)} placeholder="user@example.com" icon={User} /> handleInputChange('pass', val)} placeholder="••••••••" icon={Lock} />
)}
)}
{/* Status bar inside card footer */}
{/* ─── Public Identity (From) ─── */} {t('fromEmail', 'From Email')} & {t('fromName', 'From Name')}} />
handleInputChange('from_email', val)} placeholder="site@wawp.net" icon={Mail} /> handleInputChange('from_name', val)} placeholder="Wawp Support" icon={UserCircle} />
)} {/* STEP 3: TEST */} {currentStep === 3 && (
{/* Two action cards side by side */}
{/* Connection Check Card */} {t('verificationAndTestingDesc', 'Validate Endpoint Sync')}} />

{t('serverIntegrationDesc', 'Verify that the plugin can successfully connect to your SMTP server using the provided credentials.')}

{t('runServerCheck', 'Run Server Check')}
{/* Direct Mail Test Card */} {t('testEmailAddress', 'Test Email Address')}} />

{t('directMailTestDesc', 'Send a physical test email to ensure messages are routed and delivered correctly without ending up in spam.')}

setTestEmail(val)} placeholder="to@example.com" icon={Mail} className="flex-1" />
{/* Note bar */}

{t('configurationNote', 'Configuration Note')}

{t('configurationNoteDesc', 'It is highly recommended to perform a successful test before finalizing. Accounts with a failed test will remain in "Pending" status and might not route emails correctly.')}

)}
{currentStep > 1 && ( {t('back', 'Back')} )} {currentStep < 3 && ( {t('continueSetup', 'Continue Setup')} )} handleSave(false)} />
handleSave(false)} docsUrl={`https://help.wawp.net/${dashboardData?.rtl ? 'ar' : 'en'}/articles/how-to-setup-smtp-email`} docsTitle={dashboardData?.rtl ? "كيفية إعداد بريد SMTP" : "How to Setup SMTP Email"} /> ); }