feat: Add SMTP settings and collapsible email configuration UI

- Add SMTP fields to TicketEmailSettings model (host, port, TLS/SSL, credentials, from email/name)
- Update serializers with SMTP fields and is_smtp_configured flag
- Add TicketEmailTestSmtpView for testing SMTP connections
- Update frontend API types and hooks for SMTP settings
- Add collapsible IMAP and SMTP configuration sections with "Configured" badges
- Fix TypeScript errors in mockData.ts (missing required fields, type mismatches)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-11-29 18:28:29 -05:00
parent 0c7d76e264
commit cfc1b36ada
94 changed files with 13419 additions and 1121 deletions

View File

@@ -25,6 +25,12 @@ import {
Lock,
Users,
ExternalLink,
Mail,
Clock,
Server,
Play,
ChevronDown,
ChevronUp,
} from 'lucide-react';
import {
usePlatformSettings,
@@ -42,14 +48,23 @@ import {
usePlatformOAuthSettings,
useUpdatePlatformOAuthSettings,
} from '../../hooks/usePlatformOAuth';
import {
useTicketEmailSettings,
useUpdateTicketEmailSettings,
useTestImapConnection,
useTestSmtpConnection,
useFetchEmailsNow,
} from '../../hooks/useTicketEmailSettings';
import { Send } from 'lucide-react';
type TabType = 'stripe' | 'tiers' | 'oauth';
type TabType = 'general' | 'stripe' | 'tiers' | 'oauth';
const PlatformSettings: React.FC = () => {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState<TabType>('stripe');
const [activeTab, setActiveTab] = useState<TabType>('general');
const tabs: { id: TabType; label: string; icon: React.ElementType }[] = [
{ id: 'general', label: t('platform.settings.general', 'General'), icon: Settings },
{ id: 'stripe', label: 'Stripe', icon: CreditCard },
{ id: 'tiers', label: t('platform.settings.tiersPricing'), icon: Layers },
{ id: 'oauth', label: t('platform.settings.oauthProviders'), icon: Users },
@@ -94,6 +109,7 @@ const PlatformSettings: React.FC = () => {
</div>
{/* Tab Content */}
{activeTab === 'general' && <GeneralSettingsTab />}
{activeTab === 'stripe' && <StripeSettingsTab />}
{activeTab === 'tiers' && <TiersSettingsTab />}
{activeTab === 'oauth' && <OAuthSettingsTab />}
@@ -101,6 +117,692 @@ const PlatformSettings: React.FC = () => {
);
};
const GeneralSettingsTab: React.FC = () => {
const { t } = useTranslation();
const { data: emailSettings, isLoading, error } = useTicketEmailSettings();
const updateMutation = useUpdateTicketEmailSettings();
const testImapMutation = useTestImapConnection();
const testSmtpMutation = useTestSmtpConnection();
const fetchNowMutation = useFetchEmailsNow();
const [formData, setFormData] = useState({
// IMAP settings
imap_host: '',
imap_port: 993,
imap_use_ssl: true,
imap_username: '',
imap_password: '',
imap_folder: 'INBOX',
// SMTP settings
smtp_host: '',
smtp_port: 587,
smtp_use_tls: true,
smtp_use_ssl: false,
smtp_username: '',
smtp_password: '',
smtp_from_email: '',
smtp_from_name: '',
// General settings
support_email_address: '',
support_email_domain: '',
is_enabled: false,
delete_after_processing: true,
check_interval_seconds: 60,
});
const [showImapPassword, setShowImapPassword] = useState(false);
const [showSmtpPassword, setShowSmtpPassword] = useState(false);
const [isImapExpanded, setIsImapExpanded] = useState(false);
const [isSmtpExpanded, setIsSmtpExpanded] = useState(false);
// Update form when settings load
React.useEffect(() => {
if (emailSettings) {
setFormData({
// IMAP settings
imap_host: emailSettings.imap_host || '',
imap_port: emailSettings.imap_port || 993,
imap_use_ssl: emailSettings.imap_use_ssl ?? true,
imap_username: emailSettings.imap_username || '',
imap_password: '', // Don't prefill password
imap_folder: emailSettings.imap_folder || 'INBOX',
// SMTP settings
smtp_host: emailSettings.smtp_host || '',
smtp_port: emailSettings.smtp_port || 587,
smtp_use_tls: emailSettings.smtp_use_tls ?? true,
smtp_use_ssl: emailSettings.smtp_use_ssl ?? false,
smtp_username: emailSettings.smtp_username || '',
smtp_password: '', // Don't prefill password
smtp_from_email: emailSettings.smtp_from_email || '',
smtp_from_name: emailSettings.smtp_from_name || '',
// General settings
support_email_address: emailSettings.support_email_address || '',
support_email_domain: emailSettings.support_email_domain || '',
is_enabled: emailSettings.is_enabled ?? false,
delete_after_processing: emailSettings.delete_after_processing ?? true,
check_interval_seconds: emailSettings.check_interval_seconds || 60,
});
}
}, [emailSettings]);
const handleSave = async () => {
// Only send passwords if they were changed
const dataToSend = { ...formData };
if (!dataToSend.imap_password) {
delete (dataToSend as any).imap_password;
}
if (!dataToSend.smtp_password) {
delete (dataToSend as any).smtp_password;
}
await updateMutation.mutateAsync(dataToSend);
};
const handleTestImap = async () => {
await testImapMutation.mutateAsync();
};
const handleTestSmtp = async () => {
await testSmtpMutation.mutateAsync();
};
const handleFetchNow = async () => {
await fetchNowMutation.mutateAsync();
};
if (isLoading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="w-8 h-8 animate-spin text-blue-600" />
</div>
);
}
if (error) {
return (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
<div className="flex items-center gap-2 text-red-700 dark:text-red-400">
<AlertCircle className="w-5 h-5" />
<span>Failed to load email settings</span>
</div>
</div>
);
}
return (
<div className="space-y-6">
{/* Email Processing Status */}
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<Mail className="w-5 h-5" />
{t('platform.settings.emailProcessing', 'Support Email Processing')}
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
{emailSettings?.is_enabled ? (
<CheckCircle className="w-5 h-5 text-green-500" />
) : (
<AlertCircle className="w-5 h-5 text-yellow-500" />
)}
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">Status</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{emailSettings?.is_enabled ? 'Enabled' : 'Disabled'}
</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
{emailSettings?.is_imap_configured ? (
<CheckCircle className="w-5 h-5 text-green-500" />
) : (
<AlertCircle className="w-5 h-5 text-yellow-500" />
)}
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">IMAP (Inbound)</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{emailSettings?.is_imap_configured ? 'Configured' : 'Not configured'}
</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
{emailSettings?.is_smtp_configured ? (
<CheckCircle className="w-5 h-5 text-green-500" />
) : (
<AlertCircle className="w-5 h-5 text-yellow-500" />
)}
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">SMTP (Outbound)</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{emailSettings?.is_smtp_configured ? 'Configured' : 'Not configured'}
</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
<Clock className="w-5 h-5 text-blue-500" />
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">Last Check</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{emailSettings?.last_check_at
? new Date(emailSettings.last_check_at).toLocaleString()
: 'Never'}
</p>
</div>
</div>
</div>
{emailSettings?.last_error && (
<div className="mb-4 p-3 bg-red-50 dark:bg-red-900/20 rounded-lg">
<p className="text-sm text-red-700 dark:text-red-400">
<span className="font-medium">Last Error:</span> {emailSettings.last_error}
</p>
</div>
)}
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
<span>Emails processed: <strong>{emailSettings?.emails_processed_count || 0}</strong></span>
<span>Check interval: <strong>{emailSettings?.check_interval_seconds || 60}s</strong></span>
</div>
</div>
{/* IMAP Configuration */}
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
<button
type="button"
onClick={() => setIsImapExpanded(!isImapExpanded)}
className="w-full p-6 flex items-center justify-between text-left"
>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<Server className="w-5 h-5" />
{t('platform.settings.imapConfig', 'IMAP Server Configuration (Inbound)')}
{emailSettings?.is_imap_configured && (
<span className="ml-2 px-2 py-0.5 text-xs font-medium bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 rounded">
Configured
</span>
)}
</h2>
{isImapExpanded ? (
<ChevronUp className="w-5 h-5 text-gray-500" />
) : (
<ChevronDown className="w-5 h-5 text-gray-500" />
)}
</button>
{isImapExpanded && (
<div className="px-6 pb-6 space-y-4">
{/* Enable/Disable Toggle */}
<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
<div>
<p className="font-medium text-gray-900 dark:text-white">Enable Email Processing</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
Automatically fetch and process incoming support emails
</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={formData.is_enabled}
onChange={(e) => setFormData((prev) => ({ ...prev, is_enabled: e.target.checked }))}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-600 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-500 peer-checked:bg-blue-600"></div>
</label>
</div>
{/* Server Settings */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
IMAP Host
</label>
<input
type="text"
value={formData.imap_host}
onChange={(e) => setFormData((prev) => ({ ...prev, imap_host: e.target.value }))}
placeholder="mail.talova.net"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Port
</label>
<input
type="number"
value={formData.imap_port}
onChange={(e) => setFormData((prev) => ({ ...prev, imap_port: parseInt(e.target.value) || 993 }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="flex items-end pb-2">
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.imap_use_ssl}
onChange={(e) => setFormData((prev) => ({ ...prev, imap_use_ssl: e.target.checked }))}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="text-sm text-gray-700 dark:text-gray-300">Use SSL</span>
</label>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Username
</label>
<input
type="text"
value={formData.imap_username}
onChange={(e) => setFormData((prev) => ({ ...prev, imap_username: e.target.value }))}
placeholder="support@yourdomain.com"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Password
</label>
<div className="relative">
<input
type={showImapPassword ? 'text' : 'password'}
value={formData.imap_password}
onChange={(e) => setFormData((prev) => ({ ...prev, imap_password: e.target.value }))}
placeholder={emailSettings?.imap_password_masked || 'Enter password'}
className="w-full px-3 py-2 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
<button
type="button"
onClick={() => setShowImapPassword(!showImapPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
{showImapPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Folder
</label>
<input
type="text"
value={formData.imap_folder}
onChange={(e) => setFormData((prev) => ({ ...prev, imap_folder: e.target.value }))}
placeholder="INBOX"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Support Email Domain
</label>
<input
type="text"
value={formData.support_email_domain}
onChange={(e) => setFormData((prev) => ({ ...prev, support_email_domain: e.target.value }))}
placeholder="mail.talova.net"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Domain for reply-to addresses (e.g., support+ticket-123@domain)
</p>
</div>
</div>
{/* Test IMAP Button */}
<div className="flex gap-3">
<button
onClick={handleTestImap}
disabled={testImapMutation.isPending || !formData.imap_host}
className="inline-flex items-center gap-2 px-4 py-2 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 font-medium rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50"
>
{testImapMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Testing IMAP...
</>
) : (
<>
<RefreshCw className="w-4 h-4" />
Test IMAP Connection
</>
)}
</button>
</div>
{testImapMutation.isSuccess && (
<div className={`p-3 rounded-lg ${
testImapMutation.data?.success
? 'bg-green-50 dark:bg-green-900/20'
: 'bg-red-50 dark:bg-red-900/20'
}`}>
<p className={`text-sm flex items-center gap-2 ${
testImapMutation.data?.success
? 'text-green-700 dark:text-green-400'
: 'text-red-700 dark:text-red-400'
}`}>
{testImapMutation.data?.success ? (
<CheckCircle className="w-4 h-4" />
) : (
<AlertCircle className="w-4 h-4" />
)}
{testImapMutation.data?.message}
</p>
</div>
)}
</div>
)}
</div>
{/* SMTP Configuration */}
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
<button
type="button"
onClick={() => setIsSmtpExpanded(!isSmtpExpanded)}
className="w-full p-6 flex items-center justify-between text-left"
>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<Send className="w-5 h-5" />
{t('platform.settings.smtpConfig', 'SMTP Server Configuration (Outbound)')}
{emailSettings?.is_smtp_configured && (
<span className="ml-2 px-2 py-0.5 text-xs font-medium bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 rounded">
Configured
</span>
)}
</h2>
{isSmtpExpanded ? (
<ChevronUp className="w-5 h-5 text-gray-500" />
) : (
<ChevronDown className="w-5 h-5 text-gray-500" />
)}
</button>
{isSmtpExpanded && (
<div className="px-6 pb-6 space-y-4">
{/* Server Settings */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
SMTP Host
</label>
<input
type="text"
value={formData.smtp_host}
onChange={(e) => setFormData((prev) => ({ ...prev, smtp_host: e.target.value }))}
placeholder="smtp.example.com"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="grid grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Port
</label>
<input
type="number"
value={formData.smtp_port}
onChange={(e) => setFormData((prev) => ({ ...prev, smtp_port: parseInt(e.target.value) || 587 }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="flex items-end pb-2">
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.smtp_use_tls}
onChange={(e) => setFormData((prev) => ({ ...prev, smtp_use_tls: e.target.checked, smtp_use_ssl: e.target.checked ? false : prev.smtp_use_ssl }))}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="text-sm text-gray-700 dark:text-gray-300">TLS</span>
</label>
</div>
<div className="flex items-end pb-2">
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.smtp_use_ssl}
onChange={(e) => setFormData((prev) => ({ ...prev, smtp_use_ssl: e.target.checked, smtp_use_tls: e.target.checked ? false : prev.smtp_use_tls }))}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="text-sm text-gray-700 dark:text-gray-300">SSL</span>
</label>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Username
</label>
<input
type="text"
value={formData.smtp_username}
onChange={(e) => setFormData((prev) => ({ ...prev, smtp_username: e.target.value }))}
placeholder="user@example.com"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Password
</label>
<div className="relative">
<input
type={showSmtpPassword ? 'text' : 'password'}
value={formData.smtp_password}
onChange={(e) => setFormData((prev) => ({ ...prev, smtp_password: e.target.value }))}
placeholder={emailSettings?.smtp_password_masked || 'Enter password'}
className="w-full px-3 py-2 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
<button
type="button"
onClick={() => setShowSmtpPassword(!showSmtpPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
{showSmtpPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
From Email
</label>
<input
type="email"
value={formData.smtp_from_email}
onChange={(e) => setFormData((prev) => ({ ...prev, smtp_from_email: e.target.value }))}
placeholder="support@yourdomain.com"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
From Name
</label>
<input
type="text"
value={formData.smtp_from_name}
onChange={(e) => setFormData((prev) => ({ ...prev, smtp_from_name: e.target.value }))}
placeholder="SmoothSchedule Support"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
{/* Test SMTP Button */}
<div className="flex gap-3">
<button
onClick={handleTestSmtp}
disabled={testSmtpMutation.isPending || !formData.smtp_host}
className="inline-flex items-center gap-2 px-4 py-2 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 font-medium rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50"
>
{testSmtpMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Testing SMTP...
</>
) : (
<>
<RefreshCw className="w-4 h-4" />
Test SMTP Connection
</>
)}
</button>
</div>
{testSmtpMutation.isSuccess && (
<div className={`p-3 rounded-lg ${
testSmtpMutation.data?.success
? 'bg-green-50 dark:bg-green-900/20'
: 'bg-red-50 dark:bg-red-900/20'
}`}>
<p className={`text-sm flex items-center gap-2 ${
testSmtpMutation.data?.success
? 'text-green-700 dark:text-green-400'
: 'text-red-700 dark:text-red-400'
}`}>
{testSmtpMutation.data?.success ? (
<CheckCircle className="w-4 h-4" />
) : (
<AlertCircle className="w-4 h-4" />
)}
{testSmtpMutation.data?.message}
</p>
</div>
)}
</div>
)}
</div>
{/* Processing Settings */}
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-6">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4 flex items-center gap-2">
<Settings className="w-5 h-5" />
{t('platform.settings.processingSettings', 'Processing Settings')}
</h2>
<div className="space-y-4">
<h3 className="font-medium text-gray-900 dark:text-white mb-4">Email Fetching</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Check Interval (seconds)
</label>
<input
type="number"
min="10"
max="3600"
value={formData.check_interval_seconds}
onChange={(e) => setFormData((prev) => ({ ...prev, check_interval_seconds: parseInt(e.target.value) || 60 }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
How often to check for new emails (10-3600 seconds)
</p>
</div>
<div className="flex items-end pb-2">
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={formData.delete_after_processing}
onChange={(e) => setFormData((prev) => ({ ...prev, delete_after_processing: e.target.checked }))}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="text-sm text-gray-700 dark:text-gray-300">
Delete emails after processing
</span>
</label>
</div>
</div>
{/* Actions */}
<div className="flex flex-wrap gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
<button
onClick={handleSave}
disabled={updateMutation.isPending}
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{updateMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Saving...
</>
) : (
'Save Settings'
)}
</button>
<button
onClick={handleFetchNow}
disabled={fetchNowMutation.isPending || !emailSettings?.is_imap_configured}
className="inline-flex items-center gap-2 px-4 py-2 text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 font-medium rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50"
>
{fetchNowMutation.isPending ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Fetching...
</>
) : (
<>
<Play className="w-4 h-4" />
Fetch Now
</>
)}
</button>
</div>
{/* Status Messages */}
{updateMutation.isSuccess && (
<div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
<p className="text-sm text-green-700 dark:text-green-400 flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
Settings saved successfully
</p>
</div>
)}
{updateMutation.isError && (
<div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg">
<p className="text-sm text-red-700 dark:text-red-400">
Failed to save settings. Please try again.
</p>
</div>
)}
{fetchNowMutation.isSuccess && (
<div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
<p className="text-sm text-green-700 dark:text-green-400 flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
{fetchNowMutation.data?.message}
</p>
</div>
)}
</div>
</div>
</div>
);
};
const StripeSettingsTab: React.FC = () => {
const { data: settings, isLoading, error } = usePlatformSettings();
const updateKeysMutation = useUpdateStripeKeys();