This commit is contained in:
2026-01-03 14:16:16 -08:00
commit 1f0e678d47
71 changed files with 16127 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
'use client'
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { RefreshCw } from 'lucide-react'
import { cn } from '@/lib/utils'
interface RefreshCountdownProps {
onRefresh: () => void
intervalMs?: number
className?: string
}
export function RefreshCountdown({
onRefresh,
intervalMs = 5 * 60 * 1000, // 5 minutes default
className,
}: RefreshCountdownProps) {
const [secondsRemaining, setSecondsRemaining] = useState(intervalMs / 1000)
const [isRefreshing, setIsRefreshing] = useState(false)
useEffect(() => {
const interval = setInterval(() => {
setSecondsRemaining((prev) => {
if (prev <= 1) {
// Auto refresh
handleRefresh()
return intervalMs / 1000
}
return prev - 1
})
}, 1000)
return () => clearInterval(interval)
}, [intervalMs])
const handleRefresh = async () => {
setIsRefreshing(true)
await onRefresh()
setSecondsRemaining(intervalMs / 1000)
setIsRefreshing(false)
}
const formatTime = (seconds: number): string => {
const mins = Math.floor(seconds / 60)
const secs = Math.floor(seconds % 60)
return `${mins}:${secs.toString().padStart(2, '0')}`
}
const progressPercentage = ((intervalMs / 1000 - secondsRemaining) / (intervalMs / 1000)) * 100
return (
<div className={cn('flex items-center gap-4', className)}>
<div className="flex-1">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium">Next refresh</span>
<span className="text-sm text-muted-foreground">{formatTime(secondsRemaining)}</span>
</div>
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
<div
className="h-full bg-primary transition-all duration-1000 ease-linear"
style={{ width: `${progressPercentage}%` }}
/>
</div>
</div>
<Button
onClick={handleRefresh}
disabled={isRefreshing}
size="icon"
variant="outline"
className="min-h-[44px] min-w-[44px]"
aria-label="Refresh now"
>
<RefreshCw className={cn('h-4 w-4', isRefreshing && 'animate-spin')} />
</Button>
</div>
)
}