import React, { useState } from 'react' import { X, Settings, Zap, Bell, Mail, Plus, Pencil, Trash2, ArrowLeft, SlidersHorizontal, ArrowRight, ExternalLink, Server, KeyRound, Send } from 'lucide-react' import { useFormStore } from '../store/useFormStore' import EmailAlerts from './EmailAlerts' import ConditionalLogic from './ConditionalLogic' import { SettingsCard, Switch, fieldInputCls, fieldLabelCls } from './settings/SettingsUI' import RichTextEditor from './settings/RichTextEditor' import { TagTextInput } from './settings/MergeTagPicker' import QuizOutcomeEditor from './settings/QuizOutcomeEditor' import { logicValue, logicPatch } from './settings/logicKeys' import { confirmationKind, confirmationSummary, KIND_BADGE, validateConfirmation, withQuizDefaults } from './settings/confirmations' import { isValidEmail } from './settings/mergeTags' import { apiRequest, hasApi } from '../utils/api' import { applyFilters, Slot, useRegistryVersion } from '../extensions/registry' const errorCls = 'text-xs text-red-600 mt-1' // SMTP provider presets (the reference builder EmailSettingsTab). Picking one fills host, port and encryption. const SMTP_PROVIDERS = { google: { name: 'Google (Gmail / Workspace)', host: 'smtp.gmail.com', port: 587, encryption: 'tls', help: 'Use an App Password for Gmail accounts with 2-step verification.' }, outlook: { name: 'Outlook (Microsoft 365)', host: 'smtp.office365.com', port: 587, encryption: 'tls', help: 'Use your Microsoft 365 email and password.' }, sendgrid: { name: 'SendGrid', host: 'smtp.sendgrid.net', port: 587, encryption: 'tls', help: 'Username is "apikey"; the password is your SendGrid API key.' }, mailgun: { name: 'Mailgun', host: 'smtp.mailgun.org', port: 587, encryption: 'tls', help: 'Use the SMTP credentials from your Mailgun domain settings.' }, sendinblue: { name: 'Brevo (Sendinblue)', host: 'smtp-relay.brevo.com', port: 587, encryption: 'tls', help: 'Use the SMTP key from your Brevo account.' }, 'amazon-ses': { name: 'Amazon SES', host: 'email-smtp.us-east-1.amazonaws.com', port: 587, encryption: 'tls', help: 'Use your SES SMTP credentials; change the host to your AWS region.' }, zoho: { name: 'Zoho Mail', host: 'smtp.zoho.com', port: 587, encryption: 'tls', help: 'Use your Zoho email and an app-specific password.' }, 'elastic-email': { name: 'Elastic Email', host: 'smtp.elasticemail.com', port: 2525, encryption: 'tls', help: 'Use your Elastic Email SMTP credentials.' }, other: { name: 'Other (custom SMTP)', host: '', port: 587, encryption: 'tls', help: 'Enter your SMTP server details.' }, } // the reference builder SettingsDrawer internal tab bar (ids are shared with App.jsx / FloatingIcons). Extensions add tabs with the // `settings.tabs` filter: { id, label, icon, title, Component } (Component receives { onSave, onClose, setActiveTab }). export const SETTINGS_TABS = [ { id: 'Form Settings', label: 'General Setup', icon: Settings, title: 'General Setup' }, { id: 'Submit Actions', label: 'Submit Actions', icon: Zap, title: 'Submit Actions' }, { id: 'Notifications', label: 'Notifications', icon: Bell, title: 'Notifications' }, { id: 'Email Settings', label: 'Sender Identity', icon: Mail, title: 'Sender Identity' }, ] export default function SettingsDrawer({ isVisible, onClose, activeTab, setActiveTab, formName, setFormName, onSave, }) { useRegistryVersion() const settings = useFormStore((state) => state.settings) const setSettings = useFormStore((state) => state.setSettings) const fields = useFormStore((state) => state.fields) const handleChange = (key, value) => { setSettings({ [key]: value }) } const confirmations = useFormStore((state) => state.confirmations) const addConfirmation = useFormStore((state) => state.addConfirmation) const updateConfirmation = useFormStore((state) => state.updateConfirmation) const deleteConfirmation = useFormStore((state) => state.deleteConfirmation) const [editingConfirmationId, setEditingConfirmationId] = useState(null) const [editingConfirmation, setEditingConfirmation] = useState(null) React.useEffect(() => { // Migration logic if (settings.submitActionType && (!confirmations || confirmations.length === 0 || (confirmations.length === 1 && confirmations[0].id === 'default' && settings.submitSuccessMessage && settings.submitSuccessMessage !== 'Thank you! Your submission has been received.'))) { const migrated = { id: 'migrated', name: 'Default Confirmation', type: settings.submitActionType === 'redirect' ? 'redirect' : 'message', message: settings.submitSuccessMessage || 'Thank you!', redirectUrl: settings.redirectUrl || '', active: true }; // Update if default exists, otherwise add if (confirmations && confirmations.length > 0) { updateConfirmation(confirmations[0].id, migrated); } else { addConfirmation(migrated); } // Clear old setting so we don't migrate again setSettings({ submitActionType: null }); } }, [settings.submitActionType, confirmations, setSettings, addConfirmation, updateConfirmation]); const [adminBarHeight, setAdminBarHeight] = useState(0) React.useEffect(() => { const bar = document.getElementById('wpadminbar') if (bar) { setAdminBarHeight(bar.offsetHeight) } const handleResize = () => { if (bar) setAdminBarHeight(bar.offsetHeight) } window.addEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize) }, []) // Close on Escape (the reference builder parity) React.useEffect(() => { if (!isVisible) return undefined const onKey = (e) => { if (e.key === 'Escape') onClose && onClose() } document.addEventListener('keydown', onKey) return () => document.removeEventListener('keydown', onKey) }, [isVisible, onClose]) const settingsTabs = applyFilters('settings.tabs', [...SETTINGS_TABS]) || SETTINGS_TABS const currentTab = settingsTabs.find((t) => t.id === activeTab) || settingsTabs[0] const HeaderIcon = currentTab.icon const [confirmationErrors, setConfirmationErrors] = useState({}) const [pendingDelete, setPendingDelete] = useState(null) const startNewConfirmation = (type) => { setConfirmationErrors({}) setEditingConfirmationId('new') if (type === 'redirect') setEditingConfirmation({ name: 'Post-Submit Redirect', type: 'redirect', message: '', redirectUrl: 'https://', active: true }) else if (type === 'quiz_recommendation') setEditingConfirmation(withQuizDefaults({ name: 'Quiz Result', type: 'quiz_recommendation', message: '', redirectUrl: '', active: true })) else setEditingConfirmation({ name: 'New Confirmation', type: 'message', message: 'Thank you!', redirectUrl: '', active: true }) } // Once errors are shown they follow the edits (a fixed field loses its message). React.useEffect(() => { if (editingConfirmation && Object.keys(confirmationErrors).length) setConfirmationErrors(validateConfirmation(editingConfirmation)) // eslint-disable-next-line react-hooks/exhaustive-deps }, [editingConfirmation]) const closeConfirmationEditor = () => { setEditingConfirmationId(null) setEditingConfirmation(null) setConfirmationErrors({}) } const saveConfirmation = () => { const errors = validateConfirmation(editingConfirmation || {}) setConfirmationErrors(errors) if (Object.keys(errors).length) return if (editingConfirmationId === 'new') { addConfirmation({ ...editingConfirmation, active: editingConfirmation.active !== false }) } else { updateConfirmation(editingConfirmationId, editingConfirmation) } closeConfirmationEditor() } // Sender Identity: test email with the settings on screen (admin-only REST endpoint, uses wp_mail + SMTP). const [testEmailTo, setTestEmailTo] = useState(() => (typeof wpvfbSettings !== 'undefined' && wpvfbSettings.adminEmail) || '') const [testEmailState, setTestEmailState] = useState({ status: 'idle', message: '' }) const sendTestEmail = async () => { if (!isValidEmail(testEmailTo)) { setTestEmailState({ status: 'error', message: 'Enter a valid email address.' }) return } if (!hasApi()) { setTestEmailState({ status: 'error', message: 'The WordPress REST API is not available.' }) return } setTestEmailState({ status: 'sending', message: '' }) try { const keys = ['smtpEnabled', 'smtpProvider', 'smtpHost', 'smtpPort', 'smtpEncryption', 'smtpUsername', 'smtpPassword', 'smtpFromEmail', 'smtpFromName'] const res = await apiRequest('/test-email', { method: 'POST', body: { to: testEmailTo.trim(), settings: Object.fromEntries(keys.map((k) => [k, settings[k]])) } }) setTestEmailState({ status: 'sent', message: `Test email sent to ${testEmailTo.trim()}${res && res.smtp ? ' through your SMTP server' : ''}.` }) } catch (err) { setTestEmailState({ status: 'error', message: err.message || 'The test email could not be sent.' }) } } return ( <> {/* Overlay Backdrop */}
{/* Drawer Container */}Configure general properties such as the form name, description and who can submit the form.
The link appears when "Anyone can register" is on in WordPress Settings → General.
> )}Configure what happens after submission in the Submit Actions tab.
Control what happens after a user submits the form. You can show an on-page success message, redirect them to another URL, or show a quiz result based on their score.
{editingConfirmationId ? ({confirmationErrors.name}
}Tags are replaced with the submitted values (URL-encoded), e.g. ?email={'{field_email}'}.
{confirmationErrors.redirectUrl}
}{confirmationErrors.message}
}When off, the success message shows above the form and the form stays visible.
{confirmationSummary(conf)}
No submit actions configured
Create an action to define what happens after your users submit this form.
Configure how form notification emails are sent. Use a custom SMTP server (e.g. Gmail, SendGrid) or WordPress's default mail delivery.
Route email alerts through custom credentials instead of the default PHP mailer.
{SMTP_PROVIDERS[settings.smtpProvider].help}
)}Used by notifications that don't set their own From address.
Enter a valid email address.
)}Sends a short message with the settings above (no need to save first).
{testEmailState.message}
} {testEmailState.status === 'error' &&{testEmailState.message}
}