feat(KB-039): complete Step 2 — Usage Indicator Component
This commit is contained in:
@@ -8,12 +8,21 @@ interface UsageIndicatorProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get color class for usage percentage
|
||||||
|
* - >90%: high (red/error color)
|
||||||
|
* - >70%: medium (yellow/triage color)
|
||||||
|
* - <=70%: low (green/success color)
|
||||||
|
*/
|
||||||
function getUsageColorClass(percentUsed: number): string {
|
function getUsageColorClass(percentUsed: number): string {
|
||||||
if (percentUsed > 90) return "usage-progress-fill--high";
|
if (percentUsed > 90) return "usage-progress-fill--high";
|
||||||
if (percentUsed > 70) return "usage-progress-fill--medium";
|
if (percentUsed > 70) return "usage-progress-fill--medium";
|
||||||
return "usage-progress-fill--low";
|
return "usage-progress-fill--low";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single usage window row with progress bar
|
||||||
|
*/
|
||||||
function UsageWindowRow({ window }: { window: UsageWindow }) {
|
function UsageWindowRow({ window }: { window: UsageWindow }) {
|
||||||
const colorClass = getUsageColorClass(window.percentUsed);
|
const colorClass = getUsageColorClass(window.percentUsed);
|
||||||
|
|
||||||
@@ -44,6 +53,9 @@ function UsageWindowRow({ window }: { window: UsageWindow }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider card showing status and usage windows
|
||||||
|
*/
|
||||||
function ProviderCard({ provider }: { provider: ProviderUsage }) {
|
function ProviderCard({ provider }: { provider: ProviderUsage }) {
|
||||||
const getStatusBadge = () => {
|
const getStatusBadge = () => {
|
||||||
switch (provider.status) {
|
switch (provider.status) {
|
||||||
@@ -107,6 +119,9 @@ function ProviderCard({ provider }: { provider: ProviderUsage }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loading skeleton for usage providers
|
||||||
|
*/
|
||||||
function UsageSkeleton() {
|
function UsageSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="usage-skeleton">
|
<div className="usage-skeleton">
|
||||||
@@ -125,20 +140,29 @@ function UsageSkeleton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usage Indicator Modal
|
||||||
|
*
|
||||||
|
* Displays AI provider subscription usage across multiple providers.
|
||||||
|
* Shows hourly and weekly usage windows with percentage bars,
|
||||||
|
* reset timers, and pace indicators.
|
||||||
|
*/
|
||||||
export function UsageIndicator({ isOpen, onClose }: UsageIndicatorProps) {
|
export function UsageIndicator({ isOpen, onClose }: UsageIndicatorProps) {
|
||||||
const { providers, loading, error, lastUpdated, refresh } = useUsageData({
|
const { providers, loading, error, lastUpdated, refresh } = useUsageData({
|
||||||
autoRefresh: isOpen,
|
autoRefresh: isOpen, // Only poll when modal is open
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Handle manual refresh
|
||||||
const handleRefresh = useCallback(async () => {
|
const handleRefresh = useCallback(async () => {
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
await refresh();
|
await refresh();
|
||||||
setIsRefreshing(false);
|
setIsRefreshing(false);
|
||||||
}, [refresh]);
|
}, [refresh]);
|
||||||
|
|
||||||
|
// Close on Escape key
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
|
|
||||||
@@ -152,6 +176,7 @@ export function UsageIndicator({ isOpen, onClose }: UsageIndicatorProps) {
|
|||||||
return () => document.removeEventListener("keydown", handleKey);
|
return () => document.removeEventListener("keydown", handleKey);
|
||||||
}, [isOpen, onClose]);
|
}, [isOpen, onClose]);
|
||||||
|
|
||||||
|
// Close on overlay click
|
||||||
const handleOverlayClick = useCallback(
|
const handleOverlayClick = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
|
|||||||
Reference in New Issue
Block a user