Added solves log

This commit is contained in:
m0rph3us1987
2026-03-07 11:29:33 +01:00
parent b6a7e4f41d
commit 425921d688
2 changed files with 80 additions and 1 deletions

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { HashRouter, Routes, Route, Link, Navigate } from 'react-router-dom';
import { Terminal, Flag, Trophy, Newspaper, Shield, Settings, LogOut, X } from 'lucide-react';
import { Terminal, Flag, Trophy, Newspaper, Shield, Settings, LogOut, X, History } from 'lucide-react';
import { CTFProvider, useCTF } from './CTFContext';
import { ProtectedRoute, Button, Countdown } from './UIComponents';
import { Home } from './Home';
@@ -10,6 +10,7 @@ import { Blog } from './Blog';
import { Scoreboard, ScoreMatrix } from './Scoreboard';
import { Admin } from './Admin';
import { Login, Register } from './Auth';
import { Log } from './Log';
const ProfileSettingsModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const { updateProfile } = useCTF();
@@ -73,6 +74,7 @@ const LayoutShell: React.FC = () => {
<Link to="/challenges" className="hover:text-[#bf00ff] flex items-center gap-2 transition-colors"><Flag size={14}/> Challenges</Link>
<Link to="/blog" className="hover:text-[#bf00ff] flex items-center gap-2 transition-colors"><Newspaper size={14}/> Blog</Link>
<Link to="/scoreboard" className="hover:text-[#bf00ff] flex items-center gap-2 transition-colors"><Trophy size={14}/> Scoreboard</Link>
{currentUser ? <Link to="/log" className="hover:text-[#bf00ff] flex items-center gap-2 transition-colors"><History size={14}/> Log</Link> : null}
{currentUser?.isAdmin ? <Link to="/admin" className="text-[#ff0000] hover:text-white transition-colors flex items-center gap-2"><Shield size={14}/> Admin</Link> : null}
</div>
{isEventLive && (
@@ -98,6 +100,7 @@ const LayoutShell: React.FC = () => {
<Route path="/challenges" element={<ProtectedRoute><ChallengeList /></ProtectedRoute>} />
<Route path="/blog" element={<Blog />} />
<Route path="/scoreboard" element={<Scoreboard />} />
<Route path="/log" element={<ProtectedRoute><Log /></ProtectedRoute>} />
<Route path="/matrix" element={<ScoreMatrix />} />
<Route path="/admin" element={<ProtectedRoute>{currentUser?.isAdmin ? <Admin /> : <Navigate to="/" />}</ProtectedRoute>} />
<Route path="*" element={<Navigate to="/" />} />

76
Log.tsx Normal file
View File

@@ -0,0 +1,76 @@
import React, { useMemo } from 'react';
import { Medal, CheckCircle2, History } from 'lucide-react';
import { useCTF } from './CTFContext';
import { calculateChallengeValue, getFirstBloodBonusFactor } from './services/scoring';
export const Log: React.FC = () => {
const { state } = useCTF();
const sortedSolves = useMemo(() => {
return [...state.solves].sort((a, b) => b.timestamp - a.timestamp);
}, [state.solves]);
return (
<div className="w-full px-6 py-12 max-w-5xl mx-auto">
<div className="flex items-center gap-4 mb-8">
<History size={32} className="text-[#bf00ff]" />
<h2 className="text-4xl font-black italic text-white uppercase tracking-tighter">Event Log</h2>
</div>
<div className="flex flex-col gap-2">
{sortedSolves.length === 0 ? (
<div className="p-8 hxp-border border-[#333] text-center text-slate-500 font-bold uppercase tracking-widest bg-white/5">
No activities recorded yet.
</div>
) : (
sortedSolves.map((solve, idx) => {
const team = state.teams.find(t => t.id === solve.teamId);
const challenge = state.challenges.find(c => c.id === solve.challengeId);
if (!team || !challenge) return null;
const challengeSolves = state.solves
.filter(s => s.challengeId === solve.challengeId)
.sort((a, b) => a.timestamp - b.timestamp);
const rank = challengeSolves.findIndex(s => s.teamId === solve.teamId);
const bonusFactor = getFirstBloodBonusFactor(rank);
const basePoints = calculateChallengeValue(
challenge.initialPoints,
challenge.minimumPoints || 0,
challenge.decaySolves || 1,
challengeSolves.length
);
const bonus = Math.floor(challenge.initialPoints * bonusFactor);
const pointsGained = basePoints + bonus;
const getRankIcon = (rank: number) => {
if (rank === 0) return <Medal size={18} className="text-[#ffaa00]" />; // Gold
if (rank === 1) return <Medal size={18} className="text-[#c0c0c0]" />; // Silver
if (rank === 2) return <Medal size={18} className="text-[#cd7f32]" />; // Bronze
return <CheckCircle2 size={18} className="text-[#00ff00]" />; // Checkmark
};
const difficultyColor =
challenge.difficulty === 'Low' ? 'text-[#00ff00]' :
challenge.difficulty === 'Medium' ? 'text-[#ffaa00]' :
'text-[#ff0000]';
const dateStr = new Date(solve.timestamp).toLocaleString();
return (
<div key={`${solve.teamId}-${solve.challengeId}-${idx}`} className="p-4 hxp-border border-[#333] bg-white/5 flex items-center gap-4 hover:border-[#bf00ff] transition-colors">
<div className="flex-shrink-0">
{getRankIcon(rank)}
</div>
<div className="text-sm font-mono text-slate-300">
<span className="text-slate-500 mr-2">[{dateStr}]</span>
<span className="font-bold text-white uppercase italic">{team.name}</span> solved <span className="text-[#bf00ff] uppercase">{challenge.category}</span> - <span className={`font-bold ${difficultyColor}`}>{challenge.title}</span> and gained <span className="text-white font-bold hxp-border-purple px-1 py-0.5 bg-[#bf00ff]/10">{pointsGained} points</span>.
</div>
</div>
);
})
)}
</div>
</div>
);
};