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 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) => {
|
||||
|
|
|
|||
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,
|
||||
X,
|
||||
AlertCircle,
|
||||
CheckCircle
|
||||
CheckCircle,
|
||||
Mail
|
||||
} from 'lucide-react';
|
||||
|
||||
interface DocketEntry {
|
||||
|
|
@ -37,6 +38,7 @@ export default function AdminDashboard() {
|
|||
const [editingEntry, setEditingEntry] = useState<DocketEntry | null>(null);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [deletingEntry, setDeletingEntry] = useState<DocketEntry | null>(null);
|
||||
const [sendingNotification, setSendingNotification] = useState<number | null>(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() {
|
|||
</div>
|
||||
</div>
|
||||
<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
|
||||
onClick={() => handleEditEntry(entry)}
|
||||
style={{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue