Made app more modular.

Fixed some bugs.
Added some functionality.
This commit is contained in:
m0rph3us1987
2026-01-21 18:59:14 +01:00
parent 5802b80d61
commit 40f496c3f2
18 changed files with 1709 additions and 1535 deletions

117
CTFContext.tsx Normal file
View File

@@ -0,0 +1,117 @@
import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';
import { Challenge, Team, Solve, CTFState, BlogPost } from './types';
import { api } from './services/api';
interface CTFContextType {
state: CTFState;
currentUser: Team | null;
login: (name: string, pass: string) => Promise<boolean>;
register: (name: string, pass: string) => Promise<void>;
logout: () => void;
submitFlag: (challengeId: string, flag: string) => Promise<boolean>;
toggleCtf: () => Promise<void>;
resetScores: () => Promise<void>;
upsertChallenge: (data: FormData, id?: string) => Promise<void>;
deleteChallenge: (id: string) => Promise<void>;
deleteAllChallenges: () => Promise<void>;
exportChallenges: () => Promise<{ challenges: any[] }>;
importChallenges: (file: File) => Promise<void>;
backupDatabase: () => Promise<any>;
restoreDatabase: (file: File) => Promise<void>;
updateTeam: (id: string, data: any) => Promise<void>;
updateProfile: (password?: string) => Promise<void>;
deleteTeam: (id: string) => Promise<void>;
createBlogPost: (data: { title: string, content: string }) => Promise<void>;
updateBlogPost: (id: string, data: { title: string, content: string }) => Promise<void>;
deleteBlogPost: (id: string) => Promise<void>;
updateConfig: (formData: FormData) => Promise<void>;
refreshState: () => Promise<void>;
loading: boolean;
loadError: string | null;
}
const CTFContext = createContext<CTFContextType | null>(null);
export const useCTF = () => {
const context = useContext(CTFContext);
if (!context) throw new Error('useCTF must be used within provider');
return context;
};
export const CTFProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, setState] = useState<CTFState>({ isStarted: false, startTime: null, teams: [], challenges: [], solves: [], blogs: [], config: {} });
const [currentUser, setCurrentUser] = useState<Team | null>(null);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState<string | null>(null);
// Fix: Removed return value to match refreshState: () => Promise<void> interface
const refreshState = useCallback(async () => {
try {
const newState = await api.getState();
setState(newState);
} catch (err: any) {
console.error("State refresh failed:", err);
throw err;
}
}, []);
useEffect(() => {
const init = async () => {
const session = localStorage.getItem('hip6_session');
if (session) { try { const { team } = JSON.parse(session); setCurrentUser(team); } catch (e) {} }
const safetyTimeout = setTimeout(() => {
setLoading(false);
setLoadError("CONNECTION_TIMED_OUT: RETRYING...");
}, 6000);
try {
await refreshState();
setLoadError(null);
} catch (err: any) {
setLoadError("COMMUNICATION_FAULT: RECONNECTING...");
} finally {
clearTimeout(safetyTimeout);
setLoading(false);
}
};
init();
const interval = setInterval(() => {
refreshState().catch(() => {});
}, 30000);
return () => clearInterval(interval);
}, [refreshState]);
const login = async (n: string, p: string) => { try { const { team, token } = await api.login(n, p); localStorage.setItem('hip6_session', JSON.stringify({ team, token })); setCurrentUser(team); await refreshState(); return true; } catch (e) { return false; } };
const register = async (n: string, p: string) => { const { team, token } = await api.register(n, p); localStorage.setItem('hip6_session', JSON.stringify({ team, token })); setCurrentUser(team); await refreshState(); };
const logout = () => { localStorage.removeItem('hip6_session'); setCurrentUser(null); };
const submitFlag = async (cid: string, f: string) => { const res = await api.submitFlag(cid, f); await refreshState(); return res.success; };
const toggleCtf = async () => { await api.toggleCtf(); await refreshState(); };
const resetScores = async () => { if (window.confirm("Reset all scores to 0?")) { await api.resetScores(); await refreshState(); }};
const upsertChallenge = async (d: FormData, id?: string) => { await api.upsertChallenge(d, id); await refreshState(); };
const deleteChallenge = async (id: string) => { if (window.confirm("DELETE_CHALLENGE?")) { await api.deleteChallenge(id); await refreshState(); } };
const deleteAllChallenges = async () => { if (window.confirm("Delete all challenges?")) {await api.deleteAllChallenges(); await refreshState(); }};
const exportChallenges = async () => await api.exportChallenges();
const importChallenges = async (f: File) => { await api.importChallenges(f); await refreshState(); };
const backupDatabase = async () => await api.backupDatabase();
const restoreDatabase = async (f: File) => await api.restoreDatabase(f);
const updateTeam = async (id: string, d: any) => { await api.updateTeam(id, d); await refreshState(); };
const updateProfile = async (p?: string) => { await api.updateProfile({ password: p }); await refreshState(); };
const deleteTeam = async (id: string) => { if (window.confirm("EXPEL_OPERATOR?")) { await api.deleteTeam(id); await refreshState(); } };
const createBlogPost = async (d: any) => { await api.createBlogPost(d); await refreshState(); };
const updateBlogPost = async (id: string, d: any) => { await api.updateBlogPost(id, d); await refreshState(); };
const deleteBlogPost = async (id: string) => { await api.deleteBlogPost(id); await refreshState(); };
const updateConfig = async (d: FormData) => { await api.updateConfig(d); await refreshState(); };
return (
<CTFContext.Provider value={{
state, currentUser, login, register, logout, submitFlag, toggleCtf, resetScores,
upsertChallenge, deleteChallenge, deleteAllChallenges, exportChallenges,
importChallenges, backupDatabase, restoreDatabase, updateTeam, updateProfile,
deleteTeam, createBlogPost, updateBlogPost, deleteBlogPost, updateConfig,
refreshState, loading, loadError
}}>
{children}
</CTFContext.Provider>
);
};