437 lines
13 KiB
TypeScript
437 lines
13 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import {
|
|
Box,
|
|
Paper,
|
|
Typography,
|
|
TextField,
|
|
Button,
|
|
FormControl,
|
|
InputLabel,
|
|
Select,
|
|
MenuItem,
|
|
Switch,
|
|
FormControlLabel,
|
|
Alert,
|
|
Divider,
|
|
Card,
|
|
CardContent,
|
|
InputAdornment,
|
|
IconButton,
|
|
Chip
|
|
} from '@mui/material'
|
|
import {
|
|
Save as SaveIcon,
|
|
PlayArrow as TestIcon,
|
|
Visibility,
|
|
VisibilityOff,
|
|
Email as EmailIcon,
|
|
Settings as SettingsIcon,
|
|
CheckCircle,
|
|
Error as ErrorIcon
|
|
} from '@mui/icons-material'
|
|
|
|
interface MailgunSettings {
|
|
id?: string
|
|
apiKey?: string
|
|
domain: string
|
|
region: string
|
|
fromEmail: string
|
|
fromName: string
|
|
replyToEmail?: string
|
|
isEnabled: boolean
|
|
testMode: boolean
|
|
webhookUrl?: string
|
|
createdAt?: string
|
|
updatedAt?: string
|
|
}
|
|
|
|
export default function MailgunSettingsPage() {
|
|
const [settings, setSettings] = useState<MailgunSettings>({
|
|
domain: '',
|
|
region: 'US',
|
|
fromEmail: '',
|
|
fromName: '',
|
|
isEnabled: false,
|
|
testMode: true
|
|
})
|
|
const [loading, setLoading] = useState(false)
|
|
const [saving, setSaving] = useState(false)
|
|
const [testing, setTesting] = useState(false)
|
|
const [showApiKey, setShowApiKey] = useState(false)
|
|
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
|
|
const [testEmail, setTestEmail] = useState('')
|
|
const [connectionStatus, setConnectionStatus] = useState<'unknown' | 'connected' | 'error'>('unknown')
|
|
|
|
useEffect(() => {
|
|
loadSettings()
|
|
}, [])
|
|
|
|
const loadSettings = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const response = await fetch('/api/admin/mailgun', {
|
|
credentials: 'include'
|
|
})
|
|
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
if (data.success && data.data) {
|
|
setSettings(data.data)
|
|
if (data.data.isEnabled) {
|
|
testConnection()
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading settings:', error)
|
|
setMessage({ type: 'error', text: 'Failed to load settings' })
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleSave = async () => {
|
|
setSaving(true)
|
|
setMessage(null)
|
|
|
|
try {
|
|
const method = settings.id ? 'PUT' : 'POST'
|
|
const response = await fetch('/api/admin/mailgun', {
|
|
method,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
credentials: 'include',
|
|
body: JSON.stringify(settings)
|
|
})
|
|
|
|
const data = await response.json()
|
|
|
|
if (response.ok && data.success) {
|
|
setSettings(data.data)
|
|
setMessage({ type: 'success', text: 'Settings saved successfully' })
|
|
if (data.data.isEnabled) {
|
|
testConnection()
|
|
}
|
|
} else {
|
|
setMessage({ type: 'error', text: data.error || 'Failed to save settings' })
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving settings:', error)
|
|
setMessage({ type: 'error', text: 'Failed to save settings' })
|
|
} finally {
|
|
setSaving(false)
|
|
}
|
|
}
|
|
|
|
const testConnection = async () => {
|
|
setTesting(true)
|
|
try {
|
|
const response = await fetch('/api/admin/mailgun/test', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
credentials: 'include',
|
|
body: JSON.stringify({ testType: 'connection' })
|
|
})
|
|
|
|
const data = await response.json()
|
|
|
|
if (data.success) {
|
|
setConnectionStatus('connected')
|
|
setMessage({ type: 'success', text: 'Connection test successful' })
|
|
} else {
|
|
setConnectionStatus('error')
|
|
setMessage({ type: 'error', text: `Connection failed: ${data.error}` })
|
|
}
|
|
} catch (error) {
|
|
setConnectionStatus('error')
|
|
setMessage({ type: 'error', text: 'Connection test failed' })
|
|
} finally {
|
|
setTesting(false)
|
|
}
|
|
}
|
|
|
|
const sendTestEmail = async () => {
|
|
if (!testEmail) {
|
|
setMessage({ type: 'error', text: 'Please enter a test email address' })
|
|
return
|
|
}
|
|
|
|
setTesting(true)
|
|
try {
|
|
const response = await fetch('/api/admin/mailgun/test', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
credentials: 'include',
|
|
body: JSON.stringify({ testType: 'email', email: testEmail })
|
|
})
|
|
|
|
const data = await response.json()
|
|
|
|
if (data.success) {
|
|
setMessage({ type: 'success', text: `Test email sent successfully! ${data.messageId ? `Message ID: ${data.messageId}` : ''}` })
|
|
} else {
|
|
setMessage({ type: 'error', text: `Failed to send test email: ${data.error}` })
|
|
}
|
|
} catch (error) {
|
|
setMessage({ type: 'error', text: 'Failed to send test email' })
|
|
} finally {
|
|
setTesting(false)
|
|
}
|
|
}
|
|
|
|
const handleChange = (field: keyof MailgunSettings, value: any) => {
|
|
setSettings(prev => ({ ...prev, [field]: value }))
|
|
}
|
|
|
|
return (
|
|
<Box sx={{ maxWidth: 1200, mx: 'auto', p: 3 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
|
|
<EmailIcon sx={{ mr: 2, color: 'primary.main' }} />
|
|
<Typography variant="h4" component="h1">
|
|
Mailgun Settings
|
|
</Typography>
|
|
</Box>
|
|
|
|
{message && (
|
|
<Alert
|
|
severity={message.type}
|
|
sx={{ mb: 3 }}
|
|
onClose={() => setMessage(null)}
|
|
>
|
|
{message.text}
|
|
</Alert>
|
|
)}
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
{/* Connection Status */}
|
|
<Card>
|
|
<CardContent>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
<SettingsIcon sx={{ mr: 2 }} />
|
|
<Typography variant="h6">Connection Status</Typography>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
{connectionStatus === 'connected' && (
|
|
<Chip
|
|
label="Connected"
|
|
color="success"
|
|
icon={<CheckCircle />}
|
|
size="small"
|
|
/>
|
|
)}
|
|
{connectionStatus === 'error' && (
|
|
<Chip
|
|
label="Connection Error"
|
|
color="error"
|
|
icon={<ErrorIcon />}
|
|
size="small"
|
|
/>
|
|
)}
|
|
{connectionStatus === 'unknown' && (
|
|
<Chip
|
|
label="Unknown"
|
|
color="default"
|
|
size="small"
|
|
/>
|
|
)}
|
|
<Button
|
|
variant="outlined"
|
|
size="small"
|
|
onClick={testConnection}
|
|
disabled={testing || !settings.isEnabled}
|
|
startIcon={<TestIcon />}
|
|
>
|
|
Test Connection
|
|
</Button>
|
|
</Box>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Box sx={{ display: 'flex', gap: 3, flexDirection: { xs: 'column', md: 'row' } }}>
|
|
{/* Main Settings */}
|
|
<Paper sx={{ p: 3, flex: { xs: '1 1 100%', md: '2 1 0' } }}>
|
|
<Typography variant="h6" sx={{ mb: 3 }}>
|
|
Mailgun Configuration
|
|
</Typography>
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
<TextField
|
|
label="API Key"
|
|
type={showApiKey ? 'text' : 'password'}
|
|
value={settings.apiKey || ''}
|
|
onChange={(e) => handleChange('apiKey', e.target.value)}
|
|
placeholder="key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
fullWidth
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
onClick={() => setShowApiKey(!showApiKey)}
|
|
edge="end"
|
|
>
|
|
{showApiKey ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
helperText="Your Mailgun API key"
|
|
/>
|
|
|
|
<TextField
|
|
label="Domain"
|
|
value={settings.domain}
|
|
onChange={(e) => handleChange('domain', e.target.value)}
|
|
placeholder="mg.yourdomain.com"
|
|
fullWidth
|
|
helperText="Your Mailgun domain"
|
|
/>
|
|
|
|
<FormControl fullWidth>
|
|
<InputLabel>Region</InputLabel>
|
|
<Select
|
|
value={settings.region}
|
|
onChange={(e) => handleChange('region', e.target.value)}
|
|
label="Region"
|
|
>
|
|
<MenuItem value="US">US</MenuItem>
|
|
<MenuItem value="EU">EU</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
|
|
<Divider />
|
|
|
|
<Typography variant="h6">Email Settings</Typography>
|
|
|
|
<TextField
|
|
label="From Email"
|
|
type="email"
|
|
value={settings.fromEmail}
|
|
onChange={(e) => handleChange('fromEmail', e.target.value)}
|
|
placeholder="noreply@yourdomain.com"
|
|
fullWidth
|
|
helperText="Default sender email address"
|
|
/>
|
|
|
|
<TextField
|
|
label="From Name"
|
|
value={settings.fromName}
|
|
onChange={(e) => handleChange('fromName', e.target.value)}
|
|
placeholder="Biblical Guide"
|
|
fullWidth
|
|
helperText="Default sender name"
|
|
/>
|
|
|
|
<TextField
|
|
label="Reply-To Email (Optional)"
|
|
type="email"
|
|
value={settings.replyToEmail || ''}
|
|
onChange={(e) => handleChange('replyToEmail', e.target.value)}
|
|
placeholder="support@yourdomain.com"
|
|
fullWidth
|
|
helperText="Default reply-to address"
|
|
/>
|
|
|
|
<TextField
|
|
label="Webhook URL (Optional)"
|
|
type="url"
|
|
value={settings.webhookUrl || ''}
|
|
onChange={(e) => handleChange('webhookUrl', e.target.value)}
|
|
placeholder="https://yourdomain.com/api/webhooks/mailgun"
|
|
fullWidth
|
|
helperText="URL for Mailgun webhooks (tracking, bounces, etc.)"
|
|
/>
|
|
|
|
<Divider />
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
<FormControlLabel
|
|
control={
|
|
<Switch
|
|
checked={settings.isEnabled}
|
|
onChange={(e) => handleChange('isEnabled', e.target.checked)}
|
|
/>
|
|
}
|
|
label="Enable Mailgun"
|
|
/>
|
|
|
|
<FormControlLabel
|
|
control={
|
|
<Switch
|
|
checked={settings.testMode}
|
|
onChange={(e) => handleChange('testMode', e.target.checked)}
|
|
/>
|
|
}
|
|
label="Test Mode (emails won't actually be sent)"
|
|
/>
|
|
</Box>
|
|
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleSave}
|
|
disabled={saving || loading}
|
|
startIcon={<SaveIcon />}
|
|
size="large"
|
|
sx={{ alignSelf: 'flex-start' }}
|
|
>
|
|
{saving ? 'Saving...' : 'Save Settings'}
|
|
</Button>
|
|
</Box>
|
|
</Paper>
|
|
|
|
{/* Test Email */}
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, flex: { xs: '1 1 100%', md: '1 1 0' } }}>
|
|
<Paper sx={{ p: 3 }}>
|
|
<Typography variant="h6" sx={{ mb: 3 }}>
|
|
Test Email
|
|
</Typography>
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
<TextField
|
|
label="Test Email Address"
|
|
type="email"
|
|
value={testEmail}
|
|
onChange={(e) => setTestEmail(e.target.value)}
|
|
placeholder="test@example.com"
|
|
fullWidth
|
|
/>
|
|
|
|
<Button
|
|
variant="outlined"
|
|
onClick={sendTestEmail}
|
|
disabled={testing || !settings.isEnabled || !testEmail}
|
|
startIcon={<EmailIcon />}
|
|
fullWidth
|
|
>
|
|
{testing ? 'Sending...' : 'Send Test Email'}
|
|
</Button>
|
|
</Box>
|
|
|
|
{settings.testMode && (
|
|
<Alert severity="info" sx={{ mt: 2 }}>
|
|
Test mode is enabled. Emails will be logged but not actually sent.
|
|
</Alert>
|
|
)}
|
|
</Paper>
|
|
|
|
{settings.updatedAt && (
|
|
<Paper sx={{ p: 2 }}>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Last updated: {new Date(settings.updatedAt).toLocaleString()}
|
|
</Typography>
|
|
</Paper>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
} |