feat: add notification deletion functionality in API and EditorLayout

This commit is contained in:
SaelixCode
2026-02-27 12:47:16 -05:00
parent f4700ebfc9
commit 6bbd9b6484
3 changed files with 75 additions and 12 deletions
+19
View File
@@ -751,6 +751,25 @@ app.post('/api/notifications/read', async (req: Request, res: Response) => {
}
});
app.delete('/api/notifications/:id', async (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id as string, 10);
DatabaseService.getInstance().deleteNotification(id);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: 'Failed to delete notification' });
}
});
app.delete('/api/notifications', async (req: Request, res: Response) => {
try {
DatabaseService.getInstance().deleteAllNotifications();
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: 'Failed to clear notifications' });
}
});
app.post('/api/notifications/test', async (req: Request, res: Response) => {
try {
const { type, url } = req.body;
+10
View File
@@ -216,4 +216,14 @@ export class DatabaseService {
const stmt = this.db.prepare('UPDATE notification_history SET is_read = 1');
stmt.run();
}
public deleteNotification(id: number): void {
const stmt = this.db.prepare('DELETE FROM notification_history WHERE id = ?');
stmt.run(id);
}
public deleteAllNotifications(): void {
const stmt = this.db.prepare('DELETE FROM notification_history');
stmt.run();
}
}
+46 -12
View File
@@ -160,6 +160,20 @@ export default function EditorLayout() {
} catch (e) { }
};
const deleteNotification = async (id: number) => {
try {
await apiFetch(`/notifications/${id}`, { method: 'DELETE' });
fetchNotifications();
} catch (e) { }
};
const clearAllNotifications = async () => {
try {
await apiFetch('/notifications', { method: 'DELETE' });
fetchNotifications();
} catch (e) { }
};
useEffect(() => {
const wsMap: Record<string, WebSocket> = {};
(containers || []).forEach(container => {
@@ -725,11 +739,19 @@ export default function EditorLayout() {
<PopoverContent className="w-80 p-0" align="end">
<div className="flex items-center justify-between p-4 border-b">
<h4 className="font-semibold">Notifications</h4>
{notifications.filter(n => !n.is_read).length > 0 && (
<Button variant="ghost" size="sm" onClick={markAllRead} className="h-auto p-0 text-xs">
Mark all as read
</Button>
)}
<div className="flex gap-2">
{notifications.filter(n => !n.is_read).length > 0 && (
<Button variant="ghost" size="sm" onClick={markAllRead} className="h-auto p-0 text-xs">
Mark all as read
</Button>
)}
{notifications.length > 0 && (
<Button variant="ghost" size="sm" onClick={clearAllNotifications} className="h-auto p-0 text-xs text-muted-foreground hover:text-destructive transition-colors">
<Trash2 className="w-3 h-3 mr-1" />
Clear all
</Button>
)}
</div>
</div>
<ScrollArea className="h-80">
{notifications.length === 0 ? (
@@ -737,8 +759,8 @@ export default function EditorLayout() {
) : (
<div className="flex flex-col">
{notifications.map((notif: any) => (
<div key={notif.id} className={`p-4 border-b text-sm ${notif.is_read ? 'opacity-70' : 'bg-muted/50'}`}>
<div className="flex items-center gap-2 mb-1">
<div key={notif.id} className={`p-4 border-b text-sm ${notif.is_read ? 'opacity-70' : 'bg-muted/50'} relative group`}>
<div className="flex items-center gap-2 mb-1 pr-6">
<Badge variant={notif.level === 'error' ? 'destructive' : notif.level === 'warning' ? 'secondary' : 'default'} className="text-[10px] uppercase">
{notif.level}
</Badge>
@@ -746,7 +768,20 @@ export default function EditorLayout() {
{new Date(notif.timestamp).toLocaleString()}
</span>
</div>
<p className="font-medium">{notif.message}</p>
<p className="font-medium pr-6">{notif.message}</p>
{/* Delete individual notification button */}
<Button
variant="ghost"
size="icon"
className="absolute top-2 right-2 h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={(e) => {
e.stopPropagation();
deleteNotification(notif.id);
}}
>
<X className="w-3 h-3" />
</Button>
</div>
))}
</div>
@@ -896,12 +931,11 @@ export default function EditorLayout() {
<TooltipTrigger asChild>
<Button
size="sm"
variant="outline"
className="rounded-lg h-8 px-2 mr-1"
variant="ghost"
className="rounded-lg h-8 w-8"
onClick={() => window.open(`http://${window.location.hostname}:${mainPort}`, '_blank')}
>
<ExternalLink className="w-3 h-3 mr-1" />
{mainPort}
<ExternalLink className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Open App ({mainPort})</TooltipContent>