Restore manual email notification feature
- Add /api/notifications/send/:docketEntryId endpoint for manual email sending - Add green 'Send notification' button (mail icon) to each docket entry in admin dashboard - Implement BCC email functionality for manual notifications - Add loading states and user feedback for notification sending - Allow admins to manually trigger email notifications after uploading documents - Recovered from git reflog commit 1e24aa18
This commit is contained in:
parent
cd835cf31b
commit
cefc600230
3 changed files with 114 additions and 6 deletions
|
|
@ -16,6 +16,7 @@ import docketRoutes from './routes/docket';
|
||||||
import documentRoutes from './routes/documents';
|
import documentRoutes from './routes/documents';
|
||||||
import subscriptionRoutes from './routes/subscriptions';
|
import subscriptionRoutes from './routes/subscriptions';
|
||||||
import healthRoutes from './routes/health';
|
import healthRoutes from './routes/health';
|
||||||
|
import notificationRoutes from './routes/notifications';
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
@ -110,6 +111,7 @@ app.use('/api/auth', authRoutes);
|
||||||
app.use('/api/docket-entries', docketRoutes);
|
app.use('/api/docket-entries', docketRoutes);
|
||||||
app.use('/api/documents', documentRoutes);
|
app.use('/api/documents', documentRoutes);
|
||||||
app.use('/api/subscriptions', subscriptionRoutes);
|
app.use('/api/subscriptions', subscriptionRoutes);
|
||||||
|
app.use('/api/notifications', notificationRoutes);
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
app.use('*', (req, res) => {
|
app.use('*', (req, res) => {
|
||||||
|
|
|
||||||
59
backend/src/routes/notifications.ts
Normal file
59
backend/src/routes/notifications.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Router, Response } from 'express';
|
||||||
|
import { authenticateToken, AuthenticatedRequest } from '../middleware/auth';
|
||||||
|
import { emailService } from '../services/emailService';
|
||||||
|
import { logger } from '../utils/logger';
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
// Manual email notification endpoint
|
||||||
|
router.post('/send/:docketEntryId', authenticateToken, async (req: AuthenticatedRequest, res: Response) => {
|
||||||
|
try {
|
||||||
|
const { docketEntryId } = req.params;
|
||||||
|
|
||||||
|
// Import prisma here to avoid circular dependency
|
||||||
|
const { prisma } = await import('../index');
|
||||||
|
|
||||||
|
// Get the docket entry with details
|
||||||
|
const docketEntry = await prisma.docketEntry.findUnique({
|
||||||
|
where: { id: parseInt(docketEntryId) },
|
||||||
|
include: {
|
||||||
|
documents: {
|
||||||
|
orderBy: { displayOrder: 'asc' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!docketEntry) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Docket entry not found'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the email notification using the BCC functionality
|
||||||
|
await emailService.notifySubscribersOfNewEntry(
|
||||||
|
docketEntry.title,
|
||||||
|
docketEntry.date.toISOString(),
|
||||||
|
docketEntry.summary
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(`Manual email notification sent for docket entry: ${docketEntry.title}`, {
|
||||||
|
docketEntryId: docketEntry.id,
|
||||||
|
adminUser: req.user?.username
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Email notification sent successfully to all subscribers'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to send manual email notification:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Failed to send email notification'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
@ -14,7 +14,8 @@ import {
|
||||||
Trash2,
|
Trash2,
|
||||||
X,
|
X,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
CheckCircle
|
CheckCircle,
|
||||||
|
Mail
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
interface DocketEntry {
|
interface DocketEntry {
|
||||||
|
|
@ -37,6 +38,7 @@ export default function AdminDashboard() {
|
||||||
const [editingEntry, setEditingEntry] = useState<DocketEntry | null>(null);
|
const [editingEntry, setEditingEntry] = useState<DocketEntry | null>(null);
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [deletingEntry, setDeletingEntry] = useState<DocketEntry | null>(null);
|
const [deletingEntry, setDeletingEntry] = useState<DocketEntry | null>(null);
|
||||||
|
const [sendingNotification, setSendingNotification] = useState<number | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Form states for add/edit modals
|
// Form states for add/edit modals
|
||||||
|
|
@ -239,6 +241,34 @@ export default function AdminDashboard() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSendNotification = async (entry: DocketEntry) => {
|
||||||
|
if (sendingNotification === entry.id) return; // Prevent double-clicking
|
||||||
|
|
||||||
|
setSendingNotification(entry.id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('adminToken');
|
||||||
|
const response = await fetch(`/api/notifications/send/${entry.id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
alert(`✅ Email notification sent successfully to all subscribers!\n\n"${entry.title}"`);
|
||||||
|
} else {
|
||||||
|
alert(`❌ Failed to send email notification: ${data.message || 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('❌ Network error. Please try again.');
|
||||||
|
} finally {
|
||||||
|
setSendingNotification(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -489,6 +519,23 @@ export default function AdminDashboard() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => handleSendNotification(entry)}
|
||||||
|
disabled={sendingNotification === entry.id}
|
||||||
|
style={{
|
||||||
|
padding: '0.5rem',
|
||||||
|
backgroundColor: sendingNotification === entry.id ? '#9ca3af' : '#16a34a',
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: '0.375rem',
|
||||||
|
border: 'none',
|
||||||
|
cursor: sendingNotification === entry.id ? 'not-allowed' : 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
title="Send email notification to all subscribers"
|
||||||
|
>
|
||||||
|
<Mail style={{ width: '1rem', height: '1rem' }} />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEditEntry(entry)}
|
onClick={() => handleEditEntry(entry)}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue