diff --git a/backend/src/index.ts b/backend/src/index.ts index 29183f37..39ae4fb3 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -16,6 +16,7 @@ import docketRoutes from './routes/docket'; import documentRoutes from './routes/documents'; import subscriptionRoutes from './routes/subscriptions'; import healthRoutes from './routes/health'; +import notificationRoutes from './routes/notifications'; // Load environment variables dotenv.config(); @@ -110,6 +111,7 @@ app.use('/api/auth', authRoutes); app.use('/api/docket-entries', docketRoutes); app.use('/api/documents', documentRoutes); app.use('/api/subscriptions', subscriptionRoutes); +app.use('/api/notifications', notificationRoutes); // 404 handler app.use('*', (req, res) => { diff --git a/backend/src/routes/notifications.ts b/backend/src/routes/notifications.ts new file mode 100644 index 00000000..c5bfd693 --- /dev/null +++ b/backend/src/routes/notifications.ts @@ -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; diff --git a/frontend/src/app/admin/dashboard/page.tsx b/frontend/src/app/admin/dashboard/page.tsx index 6c418937..d2dc725d 100644 --- a/frontend/src/app/admin/dashboard/page.tsx +++ b/frontend/src/app/admin/dashboard/page.tsx @@ -2,11 +2,11 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { - FileText, - Plus, - Calendar, - Users, +import { + FileText, + Plus, + Calendar, + Users, Upload, LogOut, Eye, @@ -14,7 +14,8 @@ import { Trash2, X, AlertCircle, - CheckCircle + CheckCircle, + Mail } from 'lucide-react'; interface DocketEntry { @@ -37,6 +38,7 @@ export default function AdminDashboard() { const [editingEntry, setEditingEntry] = useState(null); const [showDeleteModal, setShowDeleteModal] = useState(false); const [deletingEntry, setDeletingEntry] = useState(null); + const [sendingNotification, setSendingNotification] = useState(null); const router = useRouter(); // 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) { return ( @@ -489,6 +519,23 @@ export default function AdminDashboard() {
+