feat: more stat components
This commit is contained in:
128
web/src/components/lootdrop-card.tsx
Normal file
128
web/src/components/lootdrop-card.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
|
||||
import { Progress } from "./ui/progress";
|
||||
import { Gift, Clock, Sparkles, Zap, Timer } from "lucide-react";
|
||||
import { cn } from "../lib/utils";
|
||||
import { Skeleton } from "./ui/skeleton";
|
||||
|
||||
export interface LootdropData {
|
||||
rewardAmount: number;
|
||||
currency: string;
|
||||
createdAt: string;
|
||||
expiresAt: string | null;
|
||||
}
|
||||
|
||||
export interface LootdropState {
|
||||
monitoredChannels: number;
|
||||
hottestChannel: {
|
||||
id: string;
|
||||
messages: number;
|
||||
progress: number;
|
||||
cooldown: boolean;
|
||||
} | null;
|
||||
config: {
|
||||
requiredMessages: number;
|
||||
dropChance: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface LootdropCardProps {
|
||||
drop?: LootdropData | null;
|
||||
state?: LootdropState;
|
||||
isLoading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function LootdropCard({ drop, state, isLoading, className }: LootdropCardProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className={cn("glass-card border-none bg-card/40", className)}>
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium">Lootdrop Status</CardTitle>
|
||||
<Gift className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<Skeleton className="h-8 w-[120px]" />
|
||||
<Skeleton className="h-4 w-[80px]" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const isActive = !!drop;
|
||||
const progress = state?.hottestChannel?.progress || 0;
|
||||
const isCooldown = state?.hottestChannel?.cooldown || false;
|
||||
|
||||
return (
|
||||
<Card className={cn(
|
||||
"glass-card border-none transition-all duration-500 overflow-hidden relative",
|
||||
isActive ? "bg-primary/5 border-primary/20 hover-glow ring-1 ring-primary/20" : "bg-card/40",
|
||||
className
|
||||
)}>
|
||||
{/* Ambient Background Effect */}
|
||||
{isActive && (
|
||||
<div className="absolute -right-4 -top-4 w-24 h-24 bg-primary/20 blur-3xl rounded-full pointer-events-none animate-pulse" />
|
||||
)}
|
||||
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2 relative z-10">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
{isActive ? "Active Lootdrop" : "Lootdrop Potential"}
|
||||
{isActive && (
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
|
||||
</span>
|
||||
)}
|
||||
</CardTitle>
|
||||
<Gift className={cn("h-4 w-4 transition-colors", isActive ? "text-primary " : "text-muted-foreground")} />
|
||||
</CardHeader>
|
||||
<CardContent className="relative z-10">
|
||||
{isActive ? (
|
||||
<div className="space-y-3 animate-in fade-in slide-up">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-2xl font-bold text-primary flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-yellow-500 fill-yellow-500 animate-pulse" />
|
||||
{drop.rewardAmount.toLocaleString()} {drop.currency}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Clock className="w-3 h-3" />
|
||||
<span>Dropped {new Date(drop.createdAt).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{isCooldown ? (
|
||||
<div className="flex flex-col items-center justify-center py-2 text-muted-foreground space-y-1">
|
||||
<Timer className="w-6 h-6 text-yellow-500 opacity-80" />
|
||||
<p className="text-sm font-medium text-yellow-500/80">Cooling Down...</p>
|
||||
<p className="text-xs opacity-50">Channels are recovering.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Zap className={cn("w-3 h-3", progress > 80 ? "text-yellow-500" : "text-muted-foreground")} />
|
||||
<span>Next Drop Chance</span>
|
||||
</div>
|
||||
<span className="font-mono">{Math.round(progress)}%</span>
|
||||
</div>
|
||||
<Progress value={progress} className="h-1.5" indicatorClassName={cn(progress > 80 ? "bg-yellow-500" : "bg-primary")} />
|
||||
{state?.hottestChannel ? (
|
||||
<p className="text-[10px] text-muted-foreground text-right opacity-70">
|
||||
{state.hottestChannel.messages} / {state.config.requiredMessages} msgs
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-[10px] text-muted-foreground text-center opacity-50 pt-1">
|
||||
No recent activity
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user