Skip to content

Commit f94b1f0

Browse files
authored
Merge pull request #211 from ctfguide-tech/dev
feat: creator insights platform
2 parents dd4fc4a + 9125618 commit f94b1f0

File tree

5 files changed

+306
-14
lines changed

5 files changed

+306
-14
lines changed

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"next": "^14.0.2",
4848
"next-pwa": "^5.6.0",
4949
"next-remove-imports": "^1.0.12",
50+
"node-fetch": "^2.6.7",
5051
"postcss-focus-visible": "^6.0.4",
5152
"puppeteer": "^22.0.0",
5253
"react": "18.2.0",
@@ -80,15 +81,14 @@
8081
"react-transition-group": "^4.4.5",
8182
"react-visibility-sensor": "^5.1.1",
8283
"reactjs-popup": "^2.0.5",
83-
"recharts": "^2.5.0",
84+
"recharts": "^2.12.7",
8485
"simplemde": "^1.11.2",
8586
"socket.io-client": "^4.7.4",
8687
"stripe": "^13.9.0",
8788
"tailwind-merge": "^2.3.0",
8889
"tailwindcss": "^3.2.1",
8990
"tailwindcss-animate": "^1.0.7",
90-
"xterm": "^5.3.0",
91-
"node-fetch": "^2.6.7"
91+
"xterm": "^5.3.0"
9292
},
9393
"devDependencies": {
9494
"eslint": "8.26.0",

public/insightDemo.png

77.6 KB
Loading

src/pages/challenges/[...id].jsx

+87-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Confetti from 'react-confetti';
2121
import { Context } from '@/context';
2222
import { useRef } from 'react';
2323
import WriteupModal from '@/components/WriteupModal';
24+
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts';
2425

2526

2627
export default function Challenge() {
@@ -711,7 +712,56 @@ function HintsPage({ cache }) {
711712

712713

713714
function DescriptionPage({ cache, fileIDName, fileIDLink }) {
715+
716+
714717
const { challenge } = cache;
718+
719+
const [solvesData, setSolvesData] = useState([]);
720+
const [creatorMode, setCreatorMode] = useState(false); // Add state for creator mode
721+
722+
const [insights, setInsights] = useState({
723+
solves: 0,
724+
attempts: 0,
725+
solvesLast30Days: 0,
726+
attemptsLast30Days: 0,
727+
});
728+
729+
useEffect(() => {
730+
const fetchSolvesData = async () => {
731+
try {
732+
const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/${challenge.id}/insights/solvesLast10Days`, 'GET', null);
733+
setSolvesData(response);
734+
console.log(response);
735+
} catch (error) {
736+
console.error('Failed to fetch solves data: ', error);
737+
}
738+
};
739+
740+
const fetchCreatorMode = async () => {
741+
try {
742+
const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/account`, 'GET', null);
743+
setCreatorMode(response.creatorMode);
744+
console.log(response.creatorMode);
745+
} catch (error) {
746+
console.error('Failed to fetch creator mode: ', error);
747+
}
748+
};
749+
750+
const fetchInsights = async () => {
751+
try {
752+
const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/${challenge.id}/insights`, 'GET', null);
753+
setInsights(response);
754+
console.log(response);
755+
} catch (error) {
756+
console.error('Failed to fetch solves data: ', error);
757+
}
758+
};
759+
760+
fetchSolvesData();
761+
fetchInsights();
762+
fetchCreatorMode();
763+
}, [challenge]);
764+
715765
const [challengeData, setChallengeData] = useState(null);
716766
const [authorPfp, setAuthorPfp] = useState(null);
717767
const { username } = useContext(Context);
@@ -821,8 +871,43 @@ function DescriptionPage({ cache, fileIDName, fileIDLink }) {
821871
) : (
822872
<p>You may need to boot the terminal to see the associated files.</p>
823873
)}
824-
825-
874+
<hr className="border-neutral-700 mt-4"></hr>
875+
{creatorMode && (
876+
<>
877+
<hr className="border-neutral-700 mt-4"></hr>
878+
<div className="w-full mt-10 bg-neutral-700/50 hover:bg-neutral-700/90 duration-100 rounded-sm text-white mx-auto items-center text-blue-500">
879+
<div className="bg-neutral-800/40 px-4 py-1 pb-3">
880+
<h1 className="text-lg mt-2"><i className="fas fa-lightbulb mr-2"></i>Creator Insights</h1>
881+
</div>
882+
<div className="grid grid-cols-4 w-full mt-2 px-5 py-2">
883+
<div>
884+
<h1 className="text-md">Solves</h1>
885+
<p className="text-xl">{insights.solves}</p>
886+
</div>
887+
<div>
888+
<h1 className="text-md">Attempts</h1>
889+
<p className="text-xl">{insights.attempts}</p>
890+
</div>
891+
<div>
892+
<h1 className="text-md">Solves <span className="text-neutral-500 text-sm">(30d)</span></h1>
893+
<p className="text-xl text-white">{insights.solvesLast30Days}</p>
894+
</div>
895+
<div>
896+
<h1 className="text-md">Attempts <span className="text-neutral-500 text-sm">(30d)</span></h1>
897+
<p className="text-xl text-white">{insights.attemptsLast30Days}</p>
898+
</div>
899+
</div>
900+
<br />
901+
<LineChart className="mb-3" width={600} height={300} data={solvesData}>
902+
<XAxis dataKey="date" />
903+
<YAxis />
904+
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
905+
<Line type="monotone" dataKey="solves" stroke="#8884d8" />
906+
</LineChart>
907+
<br />
908+
</div>
909+
</>
910+
)}
826911
</div >
827912
<div className="shrink-0 bg-neutral-800 h-10 w-full"></div>
828913
</>
@@ -1603,4 +1688,3 @@ function DeleteCommentModal({ isOpen, onClose, onConfirm }) {
16031688
</Dialog>
16041689
);
16051690
}
1606-

src/pages/create.jsx

+185-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import Box from '@mui/material/Box';
2020
import { useRouter } from 'next/router';
2121
import { ToastContainer, toast } from 'react-toastify';
2222
import 'react-toastify/dist/ReactToastify.css';
23+
import { Dialog, Transition } from '@headlessui/react';
24+
import { Fragment } from 'react';
2325

2426
export default function Create() {
2527
const router = useRouter();
@@ -254,6 +256,52 @@ export default function Create() {
254256
fetchNotifications();
255257
}, []);
256258

259+
const [showCreatorMode, setShowCreatorMode] = useState(null);
260+
261+
const [isEnableModalOpen, setIsEnableModalOpen] = useState(false);
262+
const [isDisableModalOpen, setIsDisableModalOpen] = useState(false);
263+
264+
const handleEnableCreatorMode = async () => {
265+
try {
266+
await request(`${process.env.NEXT_PUBLIC_API_URL}/account/creatorMode`, 'POST', { creatorMode: true });
267+
setIsEnableModalOpen(false);
268+
// Additional logic to enable creator mode
269+
setCreatorMode(true);
270+
setShowCreatorMode(true);
271+
toast.success('Creator mode enabled successfully');
272+
} catch (error) {
273+
console.error('Error enabling creator mode:', error);
274+
toast.error('Failed to enable creator mode');
275+
}
276+
};
277+
278+
const handleDisableCreatorMode = async () => {
279+
try {
280+
await request(`${process.env.NEXT_PUBLIC_API_URL}/account/creatorMode`, 'POST', { creatorMode: false });
281+
setIsDisableModalOpen(false);
282+
setCreatorMode(false);
283+
setShowCreatorMode(true);
284+
toast.success('Creator mode disabled successfully');
285+
} catch (error) {
286+
console.error('Error disabling creator mode:', error);
287+
}
288+
};
289+
290+
const [creatorMode, setCreatorMode] = useState(false);
291+
292+
useEffect(() => {
293+
const fetchCreatorMode = async () => {
294+
try {
295+
const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/account`, "GET", null);
296+
setCreatorMode(response.creatorMode);
297+
} catch (error) {
298+
console.error('Error fetching creator mode:', error);
299+
}
300+
};
301+
302+
fetchCreatorMode();
303+
}, []);
304+
257305
return (
258306
<>
259307
<Head>
@@ -489,7 +537,7 @@ export default function Create() {
489537
)}
490538
</div>
491539

492-
<hr className='mt-4 border-neutral-700'></hr>
540+
<hr className='mt-4 border-neutral-800'></hr>
493541
<div className=" mt-4 pb-4 ">
494542
<div className="flex items-center">
495543
<h1 className="flex-1 text-2xl font-medium text-white">
@@ -567,8 +615,8 @@ export default function Create() {
567615
</div>
568616

569617

570-
<hr className='mt-4 border-neutral-700'></hr>
571-
<div className=" mt-4 pb-4 ">
618+
<hr className='mt-4 border-neutral-700 hidden'></hr>
619+
<div className=" mt-4 pb-4 hidden ">
572620
<div className="flex items-center">
573621
<h1 className="flex-1 text-2xl font-medium text-white">
574622
<div className="flex">
@@ -593,7 +641,7 @@ export default function Create() {
593641
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text--white">
594642
Module Name
595643
</th>
596-
644+
597645

598646
<th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text--white">
599647
Last Updated
@@ -643,6 +691,135 @@ export default function Create() {
643691
</div>
644692

645693
<div className=" pr-4 sm:pr-6 lg:flex-shrink-0 lg:border-neutral-700 lg:pr-8 xl:pr-0 ">
694+
695+
<button
696+
className={`mb-4 float-right ${creatorMode ? 'bg-blue-700 hover:bg-blue-700/90' : 'bg-blue-700 hover:bg-blue-700/90'} text-sm shadow-sm px-2 py-1 text-white rounded-sm mr-3`}
697+
onClick={() => creatorMode ? setIsDisableModalOpen(true) : setIsEnableModalOpen(true)}
698+
>
699+
{creatorMode ? 'Disable Creator Mode' : 'Enable Creator Mode'}
700+
</button>
701+
702+
703+
<Transition appear show={isEnableModalOpen} as={Fragment}>
704+
<Dialog as="div" className="relative z-10" onClose={() => setIsEnableModalOpen(false)}>
705+
<Transition.Child
706+
as={Fragment}
707+
enter="ease-out duration-300"
708+
enterFrom="opacity-0 scale-95"
709+
enterTo="opacity-100 scale-100"
710+
leave="ease-in duration-200"
711+
leaveFrom="opacity-100 scale-100"
712+
leaveTo="opacity-0 scale-95"
713+
>
714+
<div className="fixed inset-0 bg-black bg-opacity-25" />
715+
</Transition.Child>
716+
717+
<div className="fixed inset-0 overflow-y-auto">
718+
<div className="flex min-h-full items-center justify-center p-4 text-center">
719+
<Transition.Child
720+
as={Fragment}
721+
enter="ease-out duration-300"
722+
enterFrom="opacity-0 scale-95"
723+
enterTo="opacity-100 scale-100"
724+
leave="ease-in duration-200"
725+
leaveFrom="opacity-100 scale-100"
726+
leaveTo="opacity-0 scale-95"
727+
>
728+
<Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-2xl bg-neutral-800 p-6 text-left align-middle shadow-xl transition-all">
729+
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-white">
730+
Enable Creator Mode <span className='text-sm text-gray-300'> BETA</span>
731+
</Dialog.Title>
732+
<div className="mt-2">
733+
<img src="/insightDemo.png" className='mt-4 mb-4 w-3/4 mx-auto rounded-lg shadow-lg border-none shadow-blur shadow-blue-900/50'></img>
734+
<p className="text-md text-gray-300 mt-3">
735+
Creator mode will allow you to see insights for every challenge on CTFGuide. You're welcome to leverage this data when creating challenges.
736+
<br></br> <br></br>
737+
738+
Are you sure you want to enable creator mode?
739+
</p>
740+
</div>
741+
742+
<div className="mt-4">
743+
<button
744+
type="button"
745+
className="inline-flex justify-center rounded-md border border-transparent bg-blue-900 px-4 py-2 text-sm font-medium text-white hover:bg-blue-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
746+
onClick={handleEnableCreatorMode}
747+
>
748+
Yes, Enable
749+
</button>
750+
<button
751+
type="button"
752+
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-red-900 px-4 py-2 text-sm font-medium text-white hover:bg-red-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
753+
onClick={() => setIsEnableModalOpen(false)}
754+
>
755+
Cancel
756+
</button>
757+
</div>
758+
</Dialog.Panel>
759+
</Transition.Child>
760+
</div>
761+
</div>
762+
</Dialog>
763+
</Transition>
764+
765+
<Transition appear show={isDisableModalOpen} as={Fragment}>
766+
<Dialog as="div" className="relative z-10" onClose={() => setIsDisableModalOpen(false)}>
767+
<Transition.Child
768+
as={Fragment}
769+
enter="ease-out duration-300"
770+
enterFrom="opacity-0 scale-95"
771+
enterTo="opacity-100 scale-100"
772+
leave="ease-in duration-200"
773+
leaveFrom="opacity-100 scale-100"
774+
leaveTo="opacity-0 scale-95"
775+
>
776+
<div className="fixed inset-0 bg-black bg-opacity-25" />
777+
</Transition.Child>
778+
779+
<div className="fixed inset-0 overflow-y-auto">
780+
<div className="flex min-h-full items-center justify-center p-4 text-center">
781+
<Transition.Child
782+
as={Fragment}
783+
enter="ease-out duration-300"
784+
enterFrom="opacity-0 scale-95"
785+
enterTo="opacity-100 scale-100"
786+
leave="ease-in duration-200"
787+
leaveFrom="opacity-100 scale-100"
788+
leaveTo="opacity-0 scale-95"
789+
>
790+
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-neutral-800 p-6 text-left align-middle shadow-xl transition-all">
791+
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-white">
792+
Disable Creator Mode
793+
</Dialog.Title>
794+
<div className="mt-2">
795+
<p className="text-sm text-gray-300">
796+
Are you sure you want to disable creator mode?
797+
</p>
798+
</div>
799+
800+
<div className="mt-4">
801+
<button
802+
type="button"
803+
className="inline-flex justify-center rounded-md border border-transparent bg-blue-900 px-4 py-2 text-sm font-medium text-white hover:bg-blue-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
804+
onClick={handleDisableCreatorMode}
805+
>
806+
Yes, Disable
807+
</button>
808+
<button
809+
type="button"
810+
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-red-900 px-4 py-2 text-sm font-medium text-white hover:bg-red-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
811+
onClick={() => setIsDisableModalOpen(false)}
812+
>
813+
Cancel
814+
</button>
815+
</div>
816+
</Dialog.Panel>
817+
</Transition.Child>
818+
</div>
819+
</div>
820+
</Dialog>
821+
</Transition>
822+
646823
<div className="pl-6 lg:w-80">
647824
<div className="pt-6 pb-2">
648825
<h2 className="text-2xl text-white ">Challenge Notifications</h2>
@@ -673,6 +850,9 @@ export default function Create() {
673850
)}
674851
</ul>
675852
</div>
853+
854+
855+
676856
</div>
677857
</div>
678858
</div>
@@ -711,7 +891,7 @@ export default function Create() {
711891

712892
<br></br>
713893
<br></br>
714-
<hr className='border-neutral-700'></hr>
894+
<hr className='border-neutral-800'></hr>
715895
<br></br>
716896
If you disagree with the changes, please join our <a href='https://discord.gg/bH6gu3HCFF' className='text-blue-500 hover:text-blue-600'><i className='fab fa-discord '></i> Discord</a> and voice your opinion.
717897
<br></br>

0 commit comments

Comments
 (0)