Phase 8: Visitor Management - Visitor/VisitorLog/AccessZone models - Check-in/out with badge generation - Zone occupancy tracking - Kiosk and management pages Phase 9: Messaging & Communication - Announcements with priority levels - Acknowledgement tracking - Shift notes for team handoffs - AnnouncementBanner component Phase 10: Compliance & Audit Trail - Immutable AuditLog model - Document versioning and approval workflow - Acknowledgement tracking for SOPs - CSV export for audit logs Phase 11: Accessibility & i18n - WCAG 2.1 AA compliance utilities - react-i18next with EN/ES translations - User preferences context (theme, font size, etc) - High contrast and reduced motion support Phase 12: Hardware Integration - QR code generation for batches/plants/visitors - Printable label system - Visitor badge printing Phase 13: Advanced Features - Environmental monitoring (sensors, readings, alerts) - Financial tracking (transactions, P&L reports) - AI/ML insights (yield predictions, anomaly detection)
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Clock, X } from 'lucide-react';
|
|
|
|
export function RateLimitBanner() {
|
|
const [isRateLimited, setIsRateLimited] = useState(false);
|
|
const [waitTime, setWaitTime] = useState(0);
|
|
const [countdown, setCountdown] = useState(0);
|
|
|
|
useEffect(() => {
|
|
const handleRateLimit = (e: CustomEvent<{ waitTime: number; url: string }>) => {
|
|
setIsRateLimited(true);
|
|
const seconds = Math.ceil(e.detail.waitTime / 1000);
|
|
setWaitTime(seconds);
|
|
setCountdown(seconds);
|
|
};
|
|
|
|
window.addEventListener('api:rate-limited', handleRateLimit as EventListener);
|
|
return () => window.removeEventListener('api:rate-limited', handleRateLimit as EventListener);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (countdown > 0) {
|
|
const timer = setInterval(() => {
|
|
setCountdown(prev => {
|
|
if (prev <= 1) {
|
|
setIsRateLimited(false);
|
|
return 0;
|
|
}
|
|
return prev - 1;
|
|
});
|
|
}, 1000);
|
|
return () => clearInterval(timer);
|
|
}
|
|
}, [countdown]);
|
|
|
|
if (!isRateLimited) return null;
|
|
|
|
return (
|
|
<div className="fixed top-0 left-0 right-0 z-[200] bg-amber-600 text-white px-4 py-3 animate-slide-in shadow-lg">
|
|
<div className="max-w-4xl mx-auto flex items-center justify-between gap-4">
|
|
<div className="flex items-center gap-3">
|
|
<Clock size={20} />
|
|
<div>
|
|
<span className="font-medium">Too many requests.</span>
|
|
<span className="ml-2 opacity-90">
|
|
Please wait {countdown}s before trying again.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={() => setIsRateLimited(false)}
|
|
className="p-1 hover:bg-amber-500 rounded transition-colors"
|
|
>
|
|
<X size={18} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|