adk-gateway 1.0.0

Multi-channel AI gateway for adk-rust agents — Telegram, Slack, WhatsApp, Discord, Matrix + control panel
import { useEffect, useRef } from 'react';

export interface Notification {
  id: string;
  type: 'success' | 'error' | 'warning';
  message: string;
  persistent: boolean;
}

interface NotificationStackProps {
  notifications: Notification[];
  onDismiss: (id: string) => void;
}

const typeStyles: Record<Notification['type'], string> = {
  success: 'bg-green-50 border-green-300 text-green-800',
  error: 'bg-red-50 border-red-300 text-red-800',
  warning: 'bg-yellow-50 border-yellow-300 text-yellow-800',
};

const dismissButtonStyles: Record<Notification['type'], string> = {
  success: 'text-green-600 hover:text-green-800',
  error: 'text-red-600 hover:text-red-800',
  warning: 'text-yellow-600 hover:text-yellow-800',
};

export default function NotificationStack({ notifications, onDismiss }: NotificationStackProps) {
  const timersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());

  useEffect(() => {
    const currentTimers = timersRef.current;

    notifications.forEach((notification) => {
      if (notification.type === 'success' && !notification.persistent && !currentTimers.has(notification.id)) {
        const timer = setTimeout(() => {
          onDismiss(notification.id);
          currentTimers.delete(notification.id);
        }, 3000);
        currentTimers.set(notification.id, timer);
      }
    });

    // Clean up timers for notifications that no longer exist
    currentTimers.forEach((timer, id) => {
      if (!notifications.find((n) => n.id === id)) {
        clearTimeout(timer);
        currentTimers.delete(id);
      }
    });

    return () => {
      currentTimers.forEach((timer) => clearTimeout(timer));
    };
  }, [notifications, onDismiss]);

  if (notifications.length === 0) {
    return null;
  }

  return (
    <div className="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm w-full">
      {notifications.map((notification) => (
        <div
          key={notification.id}
          role="alert"
          className={`border rounded-lg p-3 shadow-md flex items-start gap-2 ${typeStyles[notification.type]}`}
        >
          <p className="text-sm flex-1">{notification.message}</p>
          <button
            type="button"
            onClick={() => onDismiss(notification.id)}
            className={`text-sm font-medium shrink-0 ${dismissButtonStyles[notification.type]}`}
            aria-label="Dismiss notification"
          >
            ✕
          </button>
        </div>
      ))}
    </div>
  );
}