import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { useSentinelStore } from '../store/sentinelStore'; import styles from './SentinelAlertBanner.module.css'; /** * SentinelAlertBanner * Non-blocking notification ribbon that slides from the top edge * when a CCTV event fires (showroom visitor detected). * * Choreography from UX Master Plan §4.2: * T+0ms — WebSocket event fires * T+200ms — Ribbon slides in: translateY(-100%) → translateY(0) * spring(stiffness:300, damping:28) * Never blocks the current view. * "Open Sentinel" → navigate to /showroom * Auto-dismiss after 30s if broker does not interact. */ export function SentinelAlertBanner() { const navigate = useNavigate(); const { pendingAlert, clearPendingAlert, } = useSentinelStore(); // Auto-dismiss after 30 seconds useEffect(() => { if (!pendingAlert) return; const timer = setTimeout(clearPendingAlert, 30_000); return () => clearTimeout(timer); }, [pendingAlert, clearPendingAlert]); const handleOpenShowroom = () => { clearPendingAlert(); navigate('/showroom'); }; return ( {pendingAlert && (
{/* Live indicator */} {/* Alert text */}
{pendingAlert.matchedName ? `${pendingAlert.matchedName} detected in showroom` : 'Visitor detected in Showroom Zone A' } {pendingAlert.confidence ? `${pendingAlert.matchedName ? 'Match' : 'Face'} · ${pendingAlert.confidence}% confidence` : 'Unknown contact' }
{/* Actions */}
)}
); }