167 lines
6.4 KiB
TypeScript
167 lines
6.4 KiB
TypeScript
|
|
import { cn } from '@/lib/utils';
|
||
|
|
import { ChevronLeft, ChevronRight, Plus } from 'lucide-react';
|
||
|
|
import { useState } from 'react';
|
||
|
|
|
||
|
|
interface Milestone {
|
||
|
|
target: number;
|
||
|
|
label: string;
|
||
|
|
color: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface MilestoneProgressBarProps {
|
||
|
|
currentShares: number;
|
||
|
|
className?: string;
|
||
|
|
onStatsToggle?: () => void;
|
||
|
|
showStats?: boolean;
|
||
|
|
isVisible?: boolean;
|
||
|
|
onAddPurchase?: () => void;
|
||
|
|
onHover?: (isHovered: boolean) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const milestones: Milestone[] = [
|
||
|
|
{ target: 1500, label: '1.5K', color: 'bg-blue-500' },
|
||
|
|
{ target: 3000, label: '3K', color: 'bg-green-500' },
|
||
|
|
{ target: 4500, label: '4.5K', color: 'bg-yellow-500' },
|
||
|
|
{ target: 6000, label: '6K', color: 'bg-red-500' },
|
||
|
|
];
|
||
|
|
|
||
|
|
export default function MilestoneProgressBar({
|
||
|
|
currentShares,
|
||
|
|
className,
|
||
|
|
onStatsToggle,
|
||
|
|
showStats = false,
|
||
|
|
isVisible = false,
|
||
|
|
onAddPurchase,
|
||
|
|
onHover
|
||
|
|
}: MilestoneProgressBarProps) {
|
||
|
|
const [currentMilestoneIndex, setCurrentMilestoneIndex] = useState(0);
|
||
|
|
|
||
|
|
const currentMilestone = milestones[currentMilestoneIndex];
|
||
|
|
const progress = Math.min((currentShares / currentMilestone.target) * 100, 100);
|
||
|
|
const isCompleted = currentShares >= currentMilestone.target;
|
||
|
|
|
||
|
|
const nextMilestone = () => {
|
||
|
|
setCurrentMilestoneIndex((prev) =>
|
||
|
|
prev < milestones.length - 1 ? prev + 1 : 0
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
const prevMilestone = () => {
|
||
|
|
setCurrentMilestoneIndex((prev) =>
|
||
|
|
prev > 0 ? prev - 1 : milestones.length - 1
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleBarClick = () => {
|
||
|
|
if (onStatsToggle) {
|
||
|
|
onStatsToggle();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
"absolute bottom-0 left-0 right-0",
|
||
|
|
"bg-black/90 backdrop-blur-sm",
|
||
|
|
"border-t border-red-500/30",
|
||
|
|
"transition-all duration-300 transform",
|
||
|
|
isVisible ? "translate-y-0 opacity-100" : "translate-y-full opacity-0",
|
||
|
|
className
|
||
|
|
)}
|
||
|
|
onMouseEnter={() => onHover?.(true)}
|
||
|
|
onMouseLeave={() => onHover?.(false)}
|
||
|
|
>
|
||
|
|
<div className="relative">
|
||
|
|
{/* Progress Bar */}
|
||
|
|
<div
|
||
|
|
className="h-2 bg-gray-800 cursor-pointer relative overflow-hidden"
|
||
|
|
onClick={handleBarClick}
|
||
|
|
>
|
||
|
|
{/* Background pulse for completed milestones */}
|
||
|
|
{isCompleted && (
|
||
|
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-pulse" />
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Progress fill */}
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
"h-full transition-all duration-1000 ease-out",
|
||
|
|
currentMilestone.color,
|
||
|
|
"shadow-lg",
|
||
|
|
isCompleted ? "animate-pulse" : ""
|
||
|
|
)}
|
||
|
|
style={{ width: `${progress}%` }}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* Glow effect */}
|
||
|
|
<div
|
||
|
|
className={cn(
|
||
|
|
"absolute top-0 h-full transition-all duration-1000 ease-out",
|
||
|
|
"bg-gradient-to-r from-transparent to-white/30",
|
||
|
|
"blur-sm"
|
||
|
|
)}
|
||
|
|
style={{ width: `${Math.min(progress + 10, 100)}%` }}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Milestone Info */}
|
||
|
|
<div className="flex items-center justify-between px-4 py-2">
|
||
|
|
{/* Left: Previous milestone button */}
|
||
|
|
<button
|
||
|
|
onClick={prevMilestone}
|
||
|
|
className="text-red-400 hover:text-red-300 transition-colors p-1"
|
||
|
|
aria-label="Previous milestone"
|
||
|
|
>
|
||
|
|
<ChevronLeft className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
|
||
|
|
{/* Center: Milestone info */}
|
||
|
|
<div className="flex items-center space-x-4 text-center">
|
||
|
|
<div className="text-red-400 text-sm font-mono">
|
||
|
|
{currentShares.toFixed(2)} / {currentMilestone.target}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="text-red-500 font-bold text-sm">
|
||
|
|
{currentMilestone.label}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="text-red-400 text-sm">
|
||
|
|
{isCompleted ? 'COMPLETED' : `${(100 - progress).toFixed(1)}% TO GO`}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Right: Add Purchase, Next milestone button and stats toggle */}
|
||
|
|
<div className="flex items-center space-x-2">
|
||
|
|
{/* Add Purchase Button */}
|
||
|
|
{onAddPurchase && (
|
||
|
|
<button
|
||
|
|
onClick={onAddPurchase}
|
||
|
|
className="flex items-center space-x-1 px-2 py-1 rounded bg-red-600/20 border border-red-500/50 text-red-400 hover:bg-red-600/40 hover:text-red-300 transition-colors text-xs"
|
||
|
|
aria-label="Add purchase"
|
||
|
|
>
|
||
|
|
<Plus className="w-3 h-3" />
|
||
|
|
<span className="font-mono">ADD</span>
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<button
|
||
|
|
onClick={nextMilestone}
|
||
|
|
className="text-red-400 hover:text-red-300 transition-colors p-1"
|
||
|
|
aria-label="Next milestone"
|
||
|
|
>
|
||
|
|
<ChevronRight className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
|
||
|
|
{/* Stats indicator */}
|
||
|
|
<div className={cn(
|
||
|
|
"text-xs text-red-400/60 cursor-pointer transition-colors",
|
||
|
|
showStats && "text-red-400"
|
||
|
|
)}>
|
||
|
|
{showStats ? '▲' : '▼'}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|