Add Mailgun admin tools and contact API
This commit is contained in:
437
app/admin/mailgun/page.tsx
Normal file
437
app/admin/mailgun/page.tsx
Normal file
@@ -0,0 +1,437 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user