ooo containing html

This commit is contained in:
2026-04-27 16:35:22 -05:00
parent b03c257de1
commit 31b3fd8c9f
4 changed files with 113 additions and 20 deletions

View File

@@ -13,6 +13,14 @@ const TABS = [
{ id: 'block', label: 'Blocklist', icon: FiSlash },
];
const emptyRule = (email) => ({
email_address: email,
ooo_active: false,
ooo_message: '',
ooo_content_type: 'text',
forwards: [],
});
const MailboxSettingsModal = ({ open, email, initialTab = 'fwd', onClose, onToast }) => {
const [activeTab, setActiveTab] = useState(initialTab);
const [rule, setRule] = useState(null);
@@ -29,15 +37,13 @@ const MailboxSettingsModal = ({ open, email, initialTab = 'fwd', onClose, onToas
setLoading(true);
try {
const [r, b] = await Promise.all([
mailboxesAPI.getRules(email).catch(() => ({
email_address: email, ooo_active: false, ooo_message: '', forwards: [],
})),
mailboxesAPI.getRules(email).catch(() => emptyRule(email)),
mailboxesAPI.getBlocklist(email).catch(() => ({
email_address: email, blocked_patterns: [],
})),
]);
if (cancelled) return;
setRule(r || { email_address: email, ooo_active: false, ooo_message: '', forwards: [] });
setRule(r ? { ...emptyRule(email), ...r } : emptyRule(email));
setBlocklist(b || { email_address: email, blocked_patterns: [] });
} catch (err) {
if (!cancelled) onToast?.(`Failed to load settings: ${err.message}`, 'error');
@@ -50,10 +56,12 @@ const MailboxSettingsModal = ({ open, email, initialTab = 'fwd', onClose, onToas
// Merge updates with existing rule and persist.
const saveRule = async (updates) => {
const base = rule || emptyRule(email);
const merged = {
ooo_active: rule?.ooo_active ?? false,
ooo_message: rule?.ooo_message ?? '',
forwards: rule?.forwards ?? [],
ooo_active: base.ooo_active ?? false,
ooo_message: base.ooo_message ?? '',
ooo_content_type: base.ooo_content_type ?? 'text',
forwards: base.forwards ?? [],
...updates,
};
try {
@@ -127,4 +135,4 @@ const MailboxSettingsModal = ({ open, email, initialTab = 'fwd', onClose, onToas
);
};
export default MailboxSettingsModal;
export default MailboxSettingsModal;

View File

@@ -1,15 +1,22 @@
import React, { useState } from 'react';
import { FiCalendar } from 'react-icons/fi';
import { FiCalendar, FiFileText } from 'react-icons/fi';
const OutOfOffice = ({ rule, onSave }) => {
const [isActive, setIsActive] = useState(rule?.ooo_active || false);
const [message, setMessage] = useState(rule?.ooo_message || '');
const [contentType, setContentType] = useState(
rule?.ooo_content_type === 'html' ? 'html' : 'text'
);
const [isSaving, setIsSaving] = useState(false);
const handleSave = async () => {
setIsSaving(true);
try {
await onSave({ ooo_active: isActive, ooo_message: message });
await onSave({
ooo_active: isActive,
ooo_message: message,
ooo_content_type: contentType,
});
} finally {
setIsSaving(false);
}
@@ -17,6 +24,7 @@ const OutOfOffice = ({ rule, onSave }) => {
return (
<div className="space-y-6">
{/* Toggle Active/Inactive */}
<div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg border border-gray-200">
<div className="flex items-center gap-3">
<FiCalendar className="w-5 h-5 text-gray-600" />
@@ -43,6 +51,40 @@ const OutOfOffice = ({ rule, onSave }) => {
</button>
</div>
{/* Content Type Selector */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Message Format
</label>
<div className="flex gap-2">
<button
type="button"
onClick={() => setContentType('text')}
className={`flex-1 px-4 py-2 rounded-lg border-2 transition-all ${
contentType === 'text'
? 'border-primary-600 bg-primary-50 text-primary-700 font-semibold'
: 'border-gray-300 bg-white text-gray-700 hover:border-gray-400'
}`}
>
<FiFileText className="inline w-4 h-4 mr-2" />
Plain Text
</button>
<button
type="button"
onClick={() => setContentType('html')}
className={`flex-1 px-4 py-2 rounded-lg border-2 transition-all ${
contentType === 'html'
? 'border-primary-600 bg-primary-50 text-primary-700 font-semibold'
: 'border-gray-300 bg-white text-gray-700 hover:border-gray-400'
}`}
>
<span className="font-mono text-sm mr-2">&lt;/&gt;</span>
HTML
</button>
</div>
</div>
{/* Message Editor */}
<div>
<label htmlFor="ooo-message" className="block text-sm font-semibold text-gray-700 mb-2">
Auto-Reply Message
@@ -52,22 +94,36 @@ const OutOfOffice = ({ rule, onSave }) => {
value={message}
onChange={(e) => setMessage(e.target.value)}
rows={8}
placeholder="I am currently out of office until [date].&#10;&#10;Best regards,&#10;Your Name"
placeholder={
contentType === 'html'
? '<p>I am currently out of office until [date].</p>\n<p>Best regards,<br>Your Name</p>'
: 'I am currently out of office until [date].\n\nBest regards,\nYour Name'
}
className="input-field font-mono text-sm resize-none"
disabled={!isActive}
/>
<p className="mt-2 text-xs text-gray-500">
Plain text message that gets returned automatically to senders.
{contentType === 'html'
? 'You can use HTML tags for formatting. The reply will be sent as text/html.'
: 'Plain text message that gets returned automatically to senders.'}
</p>
</div>
{/* Message Preview */}
{isActive && message && (
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Message Preview
</label>
<div className="p-4 bg-gray-50 border border-gray-200 rounded-lg">
<pre className="text-sm text-gray-800 whitespace-pre-wrap font-sans">{message}</pre>
{contentType === 'html' ? (
<div
className="prose prose-sm max-w-none text-sm text-gray-800"
dangerouslySetInnerHTML={{ __html: message }}
/>
) : (
<pre className="text-sm text-gray-800 whitespace-pre-wrap font-sans">{message}</pre>
)}
</div>
</div>
)}
@@ -85,4 +141,4 @@ const OutOfOffice = ({ rule, onSave }) => {
);
};
export default OutOfOffice;
export default OutOfOffice;