Complete implementation across all 13 phases: - vault-core: types, YAML frontmatter parsing, entity classification, filesystem ops, config, prompt composition, validation, search - vault-watch: filesystem watcher with daemon write filtering, event classification - vault-scheduler: cron engine, process executor, task runner with retry logic and concurrency limiting - vault-api: Axum REST API (15 route modules), WebSocket with broadcast, AI assistant proxy, validation, templates - Dashboard: React + TypeScript + Tailwind v4 with kanban, CodeMirror editor, dynamic view system, AI chat sidebar - Nix flake with dev shell and NixOS module - Graceful shutdown, inotify overflow recovery, tracing instrumentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { listNotifications, dismissNotification } from '../api/client';
|
|
|
|
const LEVEL_STYLES: Record<string, string> = {
|
|
info: 'bg-accent/10 border-accent/30 text-accent',
|
|
warning: 'bg-warning/10 border-warning/30 text-warning',
|
|
error: 'bg-danger/10 border-danger/30 text-danger',
|
|
success: 'bg-success/10 border-success/30 text-success',
|
|
};
|
|
|
|
export function NotificationBanner() {
|
|
const queryClient = useQueryClient();
|
|
const { data: notifications } = useQuery({
|
|
queryKey: ['notifications'],
|
|
queryFn: listNotifications,
|
|
refetchInterval: 30000,
|
|
});
|
|
|
|
const dismiss = useMutation({
|
|
mutationFn: dismissNotification,
|
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['notifications'] }),
|
|
});
|
|
|
|
if (!notifications?.length) return null;
|
|
|
|
return (
|
|
<div className="space-y-2 px-6 pt-4">
|
|
{notifications.map((n) => (
|
|
<div
|
|
key={n.id}
|
|
className={`flex items-start gap-3 rounded-md border px-3 py-2 text-sm ${
|
|
LEVEL_STYLES[n.level || 'info'] || LEVEL_STYLES.info
|
|
}`}
|
|
>
|
|
<div className="flex-1">
|
|
<div className="font-medium">{n.title}</div>
|
|
{n.message && <div className="mt-0.5 text-xs opacity-80">{n.message}</div>}
|
|
</div>
|
|
<button
|
|
onClick={() => dismiss.mutate(n.id)}
|
|
className="shrink-0 text-xs opacity-60 hover:opacity-100"
|
|
>
|
|
dismiss
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|