openreader/src/components/rate-limit-banner.tsx
Richard R 4689f3676f refactor(auth): consolidate authentication contexts and enhance rate limit UI integration
Streamline auth client factory and remove redundant session manager
Introduce unified auto rate limit context for seamless status updates
Implement problem details in TTS responses for improved error clarity
Add rate limit pause controls and banners across document viewers
Refine PDF scaling logic with height-aware resize handling
Update privacy popup triggers and first-visit modal behavior

BREAKING CHANGE: Daily character limits adjusted (anonymous: 250K → 50K, authenticated: 1M → 500K)
2026-01-25 11:12:19 -07:00

82 lines
2.4 KiB
TypeScript

'use client';
import { useAutoRateLimit, formatCharCount } from '@/contexts/AutoRateLimitContext';
import Link from 'next/link';
interface RateLimitBannerProps {
className?: string;
}
export function RateLimitBanner({ className = '' }: RateLimitBannerProps) {
const { status, isAtLimit, timeUntilReset, authEnabled } = useAutoRateLimit();
// Don't show banner if auth is not enabled or if not at limit
if (!authEnabled || !status?.authEnabled || !isAtLimit) {
return null;
}
const isAnonymous = status.userType === 'anonymous';
return (
<div className={`bg-amber-500/10 border border-amber-500/30 rounded-lg px-3 py-2 ${className}`}>
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2">
<div className="text-xs sm:text-sm">
<span className="font-medium text-amber-700 dark:text-amber-400">
Daily TTS limit reached.
</span>
<span className="text-amber-600 dark:text-amber-500 ml-1.5">
{`Used ${formatCharCount(status.currentCount)} / ${formatCharCount(status.limit)} characters.`}
{' Resets in '}{timeUntilReset}.
</span>
</div>
{isAnonymous && (
<Link
href="/signup"
className="inline-flex items-center px-2 py-1 text-xs font-medium rounded-md
bg-accent text-background hover:bg-secondary-accent
transform transition-transform duration-200 hover:scale-[1.04]"
>
Sign up for a higher limit
</Link>
)}
</div>
</div>
);
}
/**
* Compact version for inline display
*/
export function RateLimitIndicator({ className = '' }: RateLimitBannerProps) {
const { status, isAtLimit, authEnabled } = useAutoRateLimit();
// Don't show if auth is not enabled
if (!authEnabled || !status?.authEnabled) {
return null;
}
const percentage = status.limit > 0
? Math.min(100, (status.currentCount / status.limit) * 100)
: 0;
const isWarning = percentage >= 80;
if (isAtLimit) {
return (
<span className={`text-xs font-medium text-amber-600 dark:text-amber-400 ${className}`}>
Limit reached
</span>
);
}
if (isWarning) {
return (
<span className={`text-xs text-muted ${className}`}>
{formatCharCount(status.remainingChars)} chars left
</span>
);
}
return null;
}