diff --git a/src/pages/challenges/[...id].jsx b/src/pages/challenges/[...id].jsx index 9f7a086e..2435f758 100644 --- a/src/pages/challenges/[...id].jsx +++ b/src/pages/challenges/[...id].jsx @@ -1,245 +1,236 @@ -import { MarkdownViewer } from "@/components/MarkdownViewer"; -import { StandardNav } from "@/components/StandardNav"; -import request from "@/utils/request"; -import { Dialog } from "@headlessui/react"; -import { DocumentTextIcon } from "@heroicons/react/20/solid"; -import Head from "next/head"; -import Link from "next/link"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import Skeleton from "react-loading-skeleton"; -import 'react-loading-skeleton/dist/skeleton.css'; -import ReactMarkdown from "react-markdown"; -import Menu from '@/components/editor/Menu'; -import { comment } from "postcss"; -import { ToastContainer, toast } from "react-toastify"; -import 'react-toastify/dist/ReactToastify.css'; -import { getCookie } from '@/utils/request'; -import { jwtDecode } from 'jwt-decode'; -import api from '@/utils/terminal-api'; -import Confetti from 'react-confetti'; - - -export default function Challenge() { - const router = useRouter(); - const [selectedWriteup, setSelectedWriteup] = useState(null); +import { MarkdownViewer } from '@/components/MarkdownViewer' +import { StandardNav } from '@/components/StandardNav' +import request, { getCookie } from '@/utils/request' +import { Dialog } from '@headlessui/react' +import { DocumentTextIcon } from '@heroicons/react/20/solid' +import Head from 'next/head' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' +import Skeleton from 'react-loading-skeleton' +import 'react-loading-skeleton/dist/skeleton.css' +import ReactMarkdown from 'react-markdown' +import Menu from '@/components/editor/Menu' +import { comment } from 'postcss' +import { ToastContainer, toast } from 'react-toastify' +import 'react-toastify/dist/ReactToastify.css' +import { jwtDecode } from 'jwt-decode' +import api from '@/utils/terminal-api' +import Confetti from 'react-confetti' + +export default function Challenge () { + const router = useRouter() + const [selectedWriteup, setSelectedWriteup] = useState(null) // I hate this - const [urlChallengeId, urlSelectedTab] = (router ?? {})?.query?.id ?? [undefined, undefined]; - + const [urlChallengeId, urlSelectedTab] = (router ?? {})?.query?.id ?? [undefined, undefined] // Very primitive cache system - const [cache, _setCache] = useState({}); + const [cache, _setCache] = useState({}) const setCache = (name, value) => { - const newCache = { ...cache }; - newCache[name] = value; - _setCache(newCache); + const newCache = { ...cache } + newCache[name] = value + _setCache(newCache) } // Tab system is designed to keep browser state in url, // while mainting persistence of the terminal. const tabs = { - 'description': { text: 'Description', element: DescriptionPage, }, - 'write-up': { text: 'Write Up', element: WriteUpPage, }, - 'hints': { text: 'Hints', element: HintsPage, }, - 'leaderboard': { text: 'Leaderboard', element: LeaderboardPage, }, - 'comments': { text: 'Comments', element: CommentsPage, }, + description: { text: 'Description', element: DescriptionPage }, + 'write-up': { text: 'Write Up', element: WriteUpPage }, + hints: { text: 'Hints', element: HintsPage }, + leaderboard: { text: 'Leaderboard', element: LeaderboardPage }, + comments: { text: 'Comments', element: CommentsPage } } - const selectedTab = tabs[urlSelectedTab] ?? tabs.description; + const selectedTab = tabs[urlSelectedTab] ?? tabs.description useEffect(() => { if (!urlChallengeId) { - return; + return } (async () => { if (cache.challenge) { - return; + return } try { - const getChallengeByIdEndPoint = `${process.env.NEXT_PUBLIC_API_URL}/challenges/${urlChallengeId}`; - const getChallengeResult = await request(getChallengeByIdEndPoint, "GET", null); + const getChallengeByIdEndPoint = `${process.env.NEXT_PUBLIC_API_URL}/challenges/${urlChallengeId}` + const getChallengeResult = await request(getChallengeByIdEndPoint, 'GET', null) if (getChallengeResult.success) { - setCache("challenge", getChallengeResult.body); + setCache('challenge', getChallengeResult.body) } - } catch (error) { throw "Failed to fetch challenge: " + error; } - })(); - }, [urlChallengeId]); + } catch (error) { throw 'Failed to fetch challenge: ' + error } + })() + }, [urlChallengeId]) - const [loadingFlagSubmit, setLoadingFlagSubmit] = useState(false); - const [isPointsModalOpen, setIsPointsModalOpen] = useState(false); - const [awardedPoints, setAwardedPoints] = useState(0); + const [loadingFlagSubmit, setLoadingFlagSubmit] = useState(false) + const [isPointsModalOpen, setIsPointsModalOpen] = useState(false) + const [awardedPoints, setAwardedPoints] = useState(0) const showPointsModal = (points) => { - setAwardedPoints(points); - setIsPointsModalOpen(true); - }; + setAwardedPoints(points) + setIsPointsModalOpen(true) + } const onSubmitFlag = (e) => { - e.preventDefault(); + e.preventDefault() if (loadingFlagSubmit) { - return; + return } - const formElements = e.target.elements; - const flag = formElements.flag.value; + const formElements = e.target.elements + const flag = formElements.flag.value setLoadingFlagSubmit(true); (async () => { try { - const submitChallengeEndpoint = `${process.env.NEXT_PUBLIC_API_URL}/challenges/${urlChallengeId}/submissions`; - const submitChallengeResult = await request(submitChallengeEndpoint, 'POST', { keyword: flag }); + const submitChallengeEndpoint = `${process.env.NEXT_PUBLIC_API_URL}/challenges/${urlChallengeId}/submissions` + const submitChallengeResult = await request(submitChallengeEndpoint, 'POST', { keyword: flag }) console.log(submitChallengeResult) - const { success, incorrect, error, points } = submitChallengeResult ?? {}; + const { success, incorrect, error, points } = submitChallengeResult ?? {} if (error || !submitChallengeResult) { // An error occurred >:( - toast.error(error); - return; + toast.error(error) + return } if (incorrect) { // Incorrect - toast.error(incorrect); - return; + toast.error(incorrect) + return } // Success - showPointsModal(points); // Show points modal + showPointsModal(points) // Show points modal } catch (error) { - console.error(error); - toast.error("An unexpected error occurred."); + console.error(error) + toast.error('An unexpected error occurred.') } finally { - setLoadingFlagSubmit(false); + setLoadingFlagSubmit(false) } - })(); + })() } - // ========================================= STEVES TERMINAL STUFF NO TOUCHING ====================================== - const [password, setPassword] = useState("N/A"); - const [containerId, setContainerId] = useState(""); - const [userName, setUserName] = useState(""); - const [minutesRemaining, setMinutesRemaining] = useState(60); - const [fetchingTerminal, setFetchingTerminal] = useState(true); - const [foundTerminal, setFoundTerminal] = useState(false); - const [terminalUrl, setTerminalUrl] = useState(""); - const [loadingMessage, setLoadingMessage] = useState('Connecting to terminal service...'); + // ========================================= STEVES TERMINAL STUFF NO TOUCHING ====================================== + const [password, setPassword] = useState('N/A') + const [containerId, setContainerId] = useState('') + const [userName, setUserName] = useState('') + const [minutesRemaining, setMinutesRemaining] = useState(60) + const [fetchingTerminal, setFetchingTerminal] = useState(true) + const [foundTerminal, setFoundTerminal] = useState(false) + const [terminalUrl, setTerminalUrl] = useState('') + const [loadingMessage, setLoadingMessage] = useState('Connecting to terminal service...') const createTerminal = async (skipToCheckStatus) => { - const challenge = cache.challenge; - const cookie = getCookie('idToken'); + const challenge = cache.challenge + const cookie = getCookie('idToken') - const data = await api.buildDocketTerminal(challenge.id, cookie); - //console.log(data) + const data = await api.buildDocketTerminal(challenge.id, cookie) + // console.log(data) if (data) { - // do a quick http request to that url to see if it's up - + if (!data.url) { - toast.error("Unable to create the terminal, please try again"); - setFetchingTerminal(false); - return; + toast.error('Unable to create the terminal, please try again') + setFetchingTerminal(false) + return } - - setPassword(data.terminalUserPassword); - setTerminalUrl(data.url); - setUserName(data.terminalUserName); - setContainerId(data.containerId); - setFoundTerminal(true); - setMinutesRemaining(60); - setFetchingTerminal(false); - + setPassword(data.terminalUserPassword) + setTerminalUrl(data.url) + setUserName(data.terminalUserName) + setContainerId(data.containerId) + setFoundTerminal(true) + setMinutesRemaining(60) + setFetchingTerminal(false) } else { - toast.error("Unable to create the terminal, please try again"); - setFetchingTerminal(false); + toast.error('Unable to create the terminal, please try again') + setFetchingTerminal(false) } - - return; - }; + } const checkIfTerminalExists = async () => { - const challenge = cache.challenge; - //console.log(challenge); + const challenge = cache.challenge + // console.log(challenge); - const cookie = getCookie('idToken'); - if (!challenge) return; - const data = await api.checkUserTerminal(cookie, challenge.id, 1); // 1 if using docket 2 if not + const cookie = getCookie('idToken') + if (!challenge) return + const data = await api.checkUserTerminal(cookie, challenge.id, 1) // 1 if using docket 2 if not if (data !== null) { - console.log('Found a terminal for the user'); + console.log('Found a terminal for the user') if (data.challengeID !== challenge.id) { // await createTerminal(false); } else { - console.log('User has a terminal for this challenge'); - setTerminalUrl(data.url); - setMinutesRemaining(data.minutesRemaining); - setPassword(data.password); - setUserName(data.userName); - setFoundTerminal(true); - setFetchingTerminal(false); - + console.log('User has a terminal for this challenge') + setTerminalUrl(data.url) + setMinutesRemaining(data.minutesRemaining) + setPassword(data.password) + setUserName(data.userName) + setFoundTerminal(true) + setFetchingTerminal(false) } } else { - console.log("Didnt find a terminal for the user, creating a new one"); - await createTerminal(false); + console.log('Didnt find a terminal for the user, creating a new one') + await createTerminal(false) } } useEffect(() => { if (cache.challenge) { - checkIfTerminalExists(); + checkIfTerminalExists() } }, [cache]) useEffect(() => { const interval = setInterval(() => { - setMinutesRemaining((prev) => (prev > 0 ? prev - 1 : 0)); - }, 60000); // 60000 ms = 1 minute + setMinutesRemaining((prev) => (prev > 0 ? prev - 1 : 0)) + }, 60000) // 60000 ms = 1 minute - return () => clearInterval(interval); // Cleanup interval on component unmount - }, []); + return () => clearInterval(interval) // Cleanup interval on component unmount + }, []) const copyToClipboard = (text) => { navigator.clipboard.writeText(text).then(() => { - toast.success("Copied to clipboard!"); + toast.success('Copied to clipboard!') }).catch(err => { - toast.error("Failed to copy!"); - }); - }; - - function formatTime(minutes) { - const mins = Math.floor(minutes); - const secs = Math.floor((minutes - mins) * 60); - return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; + toast.error('Failed to copy!') + }) + } + + function formatTime (minutes) { + const mins = Math.floor(minutes) + const secs = Math.floor((minutes - mins) * 60) + return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}` } useEffect(() => { - const { id } = router.query; + const { id } = router.query if (id && id.length === 3 && id[1] === 'writeups') { const writeupID = id[2]; // Fetch the writeup by ID and set it as the selected writeup (async () => { try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/writeups/${writeupID}`, "GET", null); + const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/writeups/${writeupID}`, 'GET', null) if (response.success) { - setSelectedWriteup(response.writeup); + setSelectedWriteup(response.writeup) } else { - console.error('Failed to fetch writeup:', response.message); + console.error('Failed to fetch writeup:', response.message) } } catch (error) { - console.error('Error fetching writeup:', error); + console.error('Error fetching writeup:', error) } - })(); + })() } - }, [router.query]); + }, [router.query]) const handleWriteupSelect = (writeup) => { - setSelectedWriteup(writeup); - router.push(`/challenges/${urlChallengeId}/writeups/${writeup.id}`); - }; + setSelectedWriteup(writeup) + router.push(`/challenges/${urlChallengeId}/writeups/${writeup.id}`) + } return ( <>