Made app more modular.
Fixed some bugs. Added some functionality.
This commit is contained in:
67
UIComponents.tsx
Normal file
67
UIComponents.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Navigate, useLocation } from 'react-router-dom';
|
||||
import { Terminal, Radar, Zap, RefreshCw, Box } from 'lucide-react';
|
||||
import { useCTF } from './CTFContext';
|
||||
|
||||
export const formatDuration = (ms: number) => {
|
||||
if (ms < 0) return "00:00:00";
|
||||
const seconds = Math.floor((ms / 1000) % 60);
|
||||
const minutes = Math.floor((ms / (1000 * 60)) % 60);
|
||||
const hours = Math.floor(ms / (1000 * 60 * 60));
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { currentUser } = useCTF();
|
||||
const location = useLocation();
|
||||
if (!currentUser) return <Navigate to="/login" state={{ from: location }} replace />;
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export const Countdown: React.FC<{ target: number; onEnd?: () => void; label?: string }> = ({ target, onEnd, label }) => {
|
||||
const [timeLeft, setTimeLeft] = useState(target - Date.now());
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
const remaining = target - Date.now();
|
||||
setTimeLeft(remaining);
|
||||
if (remaining <= 0) {
|
||||
clearInterval(timer);
|
||||
onEnd?.();
|
||||
}
|
||||
}, 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, [target, onEnd]);
|
||||
|
||||
if (timeLeft <= 0) return null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
{label && <span className="text-[10px] font-black uppercase text-slate-500 tracking-widest">{label}</span>}
|
||||
<span className="font-black text-xl italic tabular-nums text-[#ff0000] drop-shadow-[0_0_5px_rgba(255,0,0,0.5)]">
|
||||
{formatDuration(timeLeft)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CategoryIcon: React.FC<{ category: string; size?: number; color?: string }> = ({ category, size = 32, color = "currentColor" }) => {
|
||||
switch (category) {
|
||||
case 'WEB': return <Radar size={size} color={color} />;
|
||||
case 'PWN': return <Zap size={size} color={color} />;
|
||||
case 'REV': return <RefreshCw size={size} color={color} />;
|
||||
case 'CRY': return <Box size={size} color={color} />;
|
||||
default: return <Terminal size={size} color={color} />;
|
||||
}
|
||||
};
|
||||
|
||||
export const Button: React.FC<React.ButtonHTMLAttributes<HTMLButtonElement> & { variant?: 'primary' | 'secondary' }> = ({ children, variant = 'primary', className = "", ...props }) => {
|
||||
const styles = variant === 'primary'
|
||||
? "bg-[#ff0000] text-black border-2 border-[#ff0000] hover:bg-black hover:text-[#ff0000] disabled:opacity-50"
|
||||
: "bg-black text-[#bf00ff] border-2 border-[#bf00ff] hover:bg-[#bf00ff] hover:text-black disabled:opacity-50";
|
||||
return (
|
||||
<button className={`px-4 py-2 font-black tracking-tighter transition-all active:scale-95 ${styles} ${className}`} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user