ca-grow-ops-manager/frontend/src/components/ui/RateLimitBanner.tsx
fullsizemalt 32fd739ccf
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
Test / backend-test (push) Failing after 0s
Test / frontend-test (push) Failing after 0s
feat: Complete Phases 8-13 implementation
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)
2025-12-11 00:26:25 -08:00

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>
);
}