diff --git a/package.json b/package.json index f36fd89..07e8673 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "next": "^14.0.2", "next-pwa": "^5.6.0", "next-remove-imports": "^1.0.12", + "node-fetch": "^2.6.7", "postcss-focus-visible": "^6.0.4", "puppeteer": "^22.0.0", "react": "18.2.0", @@ -80,15 +81,14 @@ "react-transition-group": "^4.4.5", "react-visibility-sensor": "^5.1.1", "reactjs-popup": "^2.0.5", - "recharts": "^2.5.0", + "recharts": "^2.12.7", "simplemde": "^1.11.2", "socket.io-client": "^4.7.4", "stripe": "^13.9.0", "tailwind-merge": "^2.3.0", "tailwindcss": "^3.2.1", "tailwindcss-animate": "^1.0.7", - "xterm": "^5.3.0", - "node-fetch": "^2.6.7" + "xterm": "^5.3.0" }, "devDependencies": { "eslint": "8.26.0", diff --git a/public/insightDemo.png b/public/insightDemo.png new file mode 100644 index 0000000..f084d97 Binary files /dev/null and b/public/insightDemo.png differ diff --git a/src/pages/challenges/[...id].jsx b/src/pages/challenges/[...id].jsx index 669cbbf..ca01180 100644 --- a/src/pages/challenges/[...id].jsx +++ b/src/pages/challenges/[...id].jsx @@ -21,6 +21,7 @@ import Confetti from 'react-confetti'; import { Context } from '@/context'; import { useRef } from 'react'; import WriteupModal from '@/components/WriteupModal'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'; export default function Challenge() { @@ -711,7 +712,56 @@ function HintsPage({ cache }) { function DescriptionPage({ cache, fileIDName, fileIDLink }) { + + const { challenge } = cache; + + const [solvesData, setSolvesData] = useState([]); + const [creatorMode, setCreatorMode] = useState(false); // Add state for creator mode + + const [insights, setInsights] = useState({ + solves: 0, + attempts: 0, + solvesLast30Days: 0, + attemptsLast30Days: 0, + }); + + useEffect(() => { + const fetchSolvesData = async () => { + try { + const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/${challenge.id}/insights/solvesLast10Days`, 'GET', null); + setSolvesData(response); + console.log(response); + } catch (error) { + console.error('Failed to fetch solves data: ', error); + } + }; + + const fetchCreatorMode = async () => { + try { + const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/account`, 'GET', null); + setCreatorMode(response.creatorMode); + console.log(response.creatorMode); + } catch (error) { + console.error('Failed to fetch creator mode: ', error); + } + }; + + const fetchInsights = async () => { + try { + const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/${challenge.id}/insights`, 'GET', null); + setInsights(response); + console.log(response); + } catch (error) { + console.error('Failed to fetch solves data: ', error); + } + }; + + fetchSolvesData(); + fetchInsights(); + fetchCreatorMode(); + }, [challenge]); + const [challengeData, setChallengeData] = useState(null); const [authorPfp, setAuthorPfp] = useState(null); const { username } = useContext(Context); @@ -821,8 +871,43 @@ function DescriptionPage({ cache, fileIDName, fileIDLink }) { ) : (

You may need to boot the terminal to see the associated files.

)} - - +
+ {creatorMode && ( + <> +
+
+
+

Creator Insights

+
+
+
+

Solves

+

{insights.solves}

+
+
+

Attempts

+

{insights.attempts}

+
+
+

Solves (30d)

+

{insights.solvesLast30Days}

+
+
+

Attempts (30d)

+

{insights.attemptsLast30Days}

+
+
+
+ + + + + + +
+
+ + )}
@@ -1603,4 +1688,3 @@ function DeleteCommentModal({ isOpen, onClose, onConfirm }) { ); } - diff --git a/src/pages/create.jsx b/src/pages/create.jsx index 337eac0..d103c52 100644 --- a/src/pages/create.jsx +++ b/src/pages/create.jsx @@ -20,6 +20,8 @@ import Box from '@mui/material/Box'; import { useRouter } from 'next/router'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +import { Dialog, Transition } from '@headlessui/react'; +import { Fragment } from 'react'; export default function Create() { const router = useRouter(); @@ -254,6 +256,52 @@ export default function Create() { fetchNotifications(); }, []); + const [showCreatorMode, setShowCreatorMode] = useState(null); + + const [isEnableModalOpen, setIsEnableModalOpen] = useState(false); + const [isDisableModalOpen, setIsDisableModalOpen] = useState(false); + + const handleEnableCreatorMode = async () => { + try { + await request(`${process.env.NEXT_PUBLIC_API_URL}/account/creatorMode`, 'POST', { creatorMode: true }); + setIsEnableModalOpen(false); + // Additional logic to enable creator mode + setCreatorMode(true); + setShowCreatorMode(true); + toast.success('Creator mode enabled successfully'); + } catch (error) { + console.error('Error enabling creator mode:', error); + toast.error('Failed to enable creator mode'); + } + }; + + const handleDisableCreatorMode = async () => { + try { + await request(`${process.env.NEXT_PUBLIC_API_URL}/account/creatorMode`, 'POST', { creatorMode: false }); + setIsDisableModalOpen(false); + setCreatorMode(false); + setShowCreatorMode(true); + toast.success('Creator mode disabled successfully'); + } catch (error) { + console.error('Error disabling creator mode:', error); + } + }; + + const [creatorMode, setCreatorMode] = useState(false); + + useEffect(() => { + const fetchCreatorMode = async () => { + try { + const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/account`, "GET", null); + setCreatorMode(response.creatorMode); + } catch (error) { + console.error('Error fetching creator mode:', error); + } + }; + + fetchCreatorMode(); + }, []); + return ( <> @@ -489,7 +537,7 @@ export default function Create() { )} -
+

@@ -567,8 +615,8 @@ export default function Create() {

-
-
+
+

@@ -593,7 +641,7 @@ export default function Create() { Module Name - + Last Updated @@ -643,6 +691,135 @@ export default function Create() {
+ + + + + + setIsEnableModalOpen(false)}> + +
+ + +
+
+ + + + Enable Creator Mode BETA + +
+ +

+ Creator mode will allow you to see insights for every challenge on CTFGuide. You're welcome to leverage this data when creating challenges. +



+ + Are you sure you want to enable creator mode? +

+
+ +
+ + +
+
+
+
+
+
+
+ + + setIsDisableModalOpen(false)}> + +
+ + +
+
+ + + + Disable Creator Mode + +
+

+ Are you sure you want to disable creator mode? +

+
+ +
+ + +
+
+
+
+
+
+
+

Challenge Notifications

@@ -673,6 +850,9 @@ export default function Create() { )}
+ + +

@@ -711,7 +891,7 @@ export default function Create() {



-
+


If you disagree with the changes, please join our Discord and voice your opinion.

diff --git a/yarn.lock b/yarn.lock index 062215c..e9b8e35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4680,7 +4680,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-equals@^5.0.0: +fast-equals@^5.0.0, fast-equals@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d" integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== @@ -7159,7 +7159,7 @@ next@^14.0.2: "@next/swc-win32-ia32-msvc" "14.0.4" "@next/swc-win32-x64-msvc" "14.0.4" -node-fetch@^2.6.12: +node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -8042,6 +8042,15 @@ react-smooth@^2.0.2: fast-equals "^5.0.0" react-transition-group "2.9.0" +react-smooth@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a" + integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w== + dependencies: + fast-equals "^5.0.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + react-swipeable@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-7.0.1.tgz#cd299f5986c5e4a7ee979839658c228f660e1e0c" @@ -8143,7 +8152,21 @@ recharts-scale@^0.4.4: dependencies: decimal.js-light "^2.4.1" -recharts@^2.3.2, recharts@^2.5.0: +recharts@^2.12.7: + version "2.12.7" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.7.tgz#c7f42f473a257ff88b43d88a92530930b5f9e773" + integrity sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ== + dependencies: + clsx "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.21" + react-is "^16.10.2" + react-smooth "^4.0.0" + recharts-scale "^0.4.4" + tiny-invariant "^1.3.1" + victory-vendor "^36.6.8" + +recharts@^2.3.2: version "2.7.2" resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.7.2.tgz#6d813681910ad33a4bbf3bdd06c6f64f20b39319" integrity sha512-HMKRBkGoOXHW+7JcRa6+MukPSifNtJlqbc+JreGVNA407VLE/vOP+8n3YYjprDVVIF9E2ZgwWnL3D7K/LUFzBg== @@ -9230,6 +9253,11 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tiny-invariant@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tippy.js@^6.3.1: version "6.3.7" resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"