import { useState, useCallback, useEffect } 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, Trash2, CheckCircle2, Settings2, ChevronRight, Clock as ClockIcon, Globe, Mail, ArrowRight, ArrowLeft, ShieldCheck, Tag, Hash, User, Lock, UserCircle, X } from 'lucide-react'; import { SearchableSelect } from './components/ui/searchable-select'; import { SettingsLayout, SettingsHeader, ModernCardHeader, SettingCard, AdminButton, SecondaryButton, GhostButton, SettingInput, MethodButton, StatusMessage, SaveWithStatus } 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 PROVIDERS = [ { id: 'wp', name: 'WordPress', iconPath: `${dashboardData?.global?.pluginUrl}assets/icons/wordpress.svg`, desc: 'Default PHPMailer' }, { id: 'smtp', name: 'Custom SMTP', iconPath: `${dashboardData?.global?.pluginUrl}assets/img/senders/gmail.svg`, desc: 'Any Mail Server' }, ]; const [accounts, setAccounts] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingId, setEditingId] = useState(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 [isTestingConnect, setIsTestingConnect] = useState(false); const [isTestingSend, setIsTestingSend] = useState(false); const [testEmail, setTestEmail] = useState(''); const [presetSearch, setPresetSearch] = useState(''); const { saveStatus, hasUnsavedChanges, isSaving, handleSave, markDirty } = useSettingsSave({ onSave: async () => { try { const response = await fetch(`${dashboardData.global.apiRestUrl}/accounts/smtp${dashboardData.global.apiRestUrl.includes('?') ? '&' : '?'}_=${Date.now()}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': dashboardData.global.wpRestNonce }, body: JSON.stringify({ account_name: accountName, config: settings, id: editingId }) }); const result = await response.json(); if (result.success) { window.dispatchEvent(new CustomEvent('wawp-refresh-data', { detail: { section: 'smtp-sender' } })); return true; } return false; } catch { 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${dashboardData.global.apiRestUrl.includes('?') ? '&' : '?'}_=${Date.now()}`, { method: 'GET', 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); 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); setIsDialogOpen(true); }; const openEditDialog = (account: SMTPAccount) => { setEditingId(account.id); setAccountName(account.account_name); setIsUserEditedName(true); setCurrentStep(1); setSettings({ ...account.config, provider: account.provider || 'smtp', pass: '' }); setStatusMessage(null); setIsDialogOpen(true); }; const deleteAccount = async (id: number) => { if (!confirm('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); 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 }) }); const result = await response.json(); if (result.success) { setStatusMessage({ type: 'success', text: result.data?.message || 'Connection successful!' }); if (editingId) fetchAccounts(); // Refresh to show validated status } else { const errorText = typeof result.data === 'string' ? result.data : (result.data?.message || result.message || 'Connection failed.'); setStatusMessage({ type: 'error', text: errorText }); } } catch { setStatusMessage({ type: 'error', text: 'An error occurred during test.' }); } setIsTestingConnect(false); }; const sendTestEmail = async () => { if (!testEmail) { setStatusMessage({ type: 'error', text: 'Please enter a test email address.' }); return; } setIsTestingSend(true); setStatusMessage(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: editingId }) }); const result = await response.json(); if (result.success) { setStatusMessage({ type: 'success', text: result.data || 'Test email sent successfully.' }); } else { const errorText = typeof result.data === 'string' ? result.data : (result.data?.message || result.message || 'Failed to send test email.'); setStatusMessage({ type: 'error', text: errorText }); } } catch { setStatusMessage({ type: 'error', text: 'An error occurred.' }); } setIsTestingSend(false); }; const nextStep = () => setCurrentStep(prev => prev + 1); const prevStep = () => setCurrentStep(prev => prev - 1); return (
{/* Header */} Add Account {/* Accounts Table */} Manage configurations and validation status} />
{isLoading ? ( null ) : accounts.length === 0 ? ( ) : accounts.map((account) => ( ))}
Default Sender Account Name Provider Server Status Actions
No accounts configured yet.
{account.is_active ? ( 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" > Set Default )}
{account.account_name} {account.provider === 'smtp' && ( {account.config?.host}:{account.config?.port} )}
{(() => { const p = PROVIDERS.find(p => p.id === account.provider); return p?.iconPath ? : ; })()}
{PROVIDERS.find(p => p.id === account.provider)?.name || account.provider}
{account.validated ? ( Active ) : ( 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}> Edit & Test deleteAccount(account.id)} size="icon-sm" className="text-rose-500 hover:text-rose-600 hover:bg-rose-50 rounded-[4px]" icon={Trash2}> {null}
{/* Dialog: 3-Step Wizard */}
Step {currentStep} of 3 — {currentStep === 1 ? 'Choose Delivery Method' : currentStep === 2 ? 'Configure Credentials' : '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)}
)} {/* STEP 1: CHOOSE SENDER */} {currentStep === 1 && (
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" /> ))}
Management
{ setAccountName(val); setIsUserEditedName(true); }} placeholder="e.g. Sales Team SMTP" icon={Tag} />

This name is only for your reference in the dashboard.

)} {/* STEP 2: CONFIGURATION */} {currentStep === 2 && (
Connection Credentials for {PROVIDERS.find(p => p.id === settings.provider)?.name}
{/* ─── Provider Preset Picker (Custom SMTP only) ─── */} {settings.provider === 'smtp' && (() => { const CATS: Record = { popular: 'Popular', transactional: 'Transactional', hosting: 'Hosting', enterprise: 'Enterprise', regional: '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 (
Quick-fill from a known provider
{SMTP_PRESETS.length} Providers
setPresetSearch(e.target.value)} className="w-full h-9 px-3 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 && (

No providers match your search.

)}
{activePreset?.hint && (

💡 {activePreset.hint}

)}
); })()} {settings.provider === 'wp' ? (

System Integration Ready

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-11 bg-white border-slate-200 text-slate-700 font-bold text-[13px] hover:border-[#004449]/30 transition-all rounded-[5px]" />
Login Required handleInputChange('auth', c ? 1 : 0)} />
{!!settings.auth && (
handleInputChange('user', val)} placeholder="user@example.com" icon={User} /> handleInputChange('pass', val)} placeholder="••••••••" icon={Lock} />
)}
)}
{/* Common Identity for Sender */}
Public Identity
handleInputChange('from_email', val)} placeholder="site@wawp.net" icon={Mail} /> handleInputChange('from_name', val)} placeholder="Wawp Support" icon={UserCircle} />
)} {/* STEP 3: TEST */} {currentStep === 3 && (
Validate Endpoint Sync & Send Direct Mail} />
{/* Connection Check */}

Server Integration

Verify that the plugin can successfully connect to your SMTP server using the provided credentials.

Run Server Check
{/* Email Check */}
{/* Vertical divider on desktop */}

Direct Mail Test

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={Send} className="flex-1" />
{null}

Configuration Note

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.

)}
setIsDialogOpen(false)} className="flex-1 rounded-lg font-bold text-slate-500 hover:bg-white active:scale-95 transition-all text-xs uppercase tracking-widest border border-transparent hover:border-slate-200"> Close {currentStep > 1 && ( Back )} {currentStep < 3 ? ( Continue Setup ) : ( handleSave(false)} t={{ saveChanges: editingId ? 'Update Settings' : 'Initialize Account' }} /> )}
); }