Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/patch notes #197

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/components/PatchNote.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { MarkdownViewer } from '@/components/MarkdownViewer';
import React from 'react';
import { useState } from 'react';

export function PatchNote({description, date, title, version}) {
const [dropDown, setDropDown] = useState(false);
const [readMoreBtn, setReadMoreBtn] = useState(false);


return (
<>
<div
className="cursor-pointer mb-5 mt-3 rounded-lg bg-gradient-to-r from-blue-900 to-gray-950 " >
<div className="mb-4 pt-2 pb-2 px-4" onClick={() => setDropDown(!(dropDown) )
}>
<div className='flex justify-center items-center'>
<div>
<h1 className="text-xl font-semibold text-blue-600 ">
{version}
<h2 className="text-lg text-white ">{title}</h2>
</h1>
</div>
<div className='ml-auto'>
<h2 className="pl-3 text-right text-lg text-white">{date}</h2>
</div>
</div>
</div>

{!dropDown ? <div className={`px-8 py-5 rounded-b-lg bg-neutral-800 transition-all duration-200 ease-in-out opacity-100`}>
<div contentEditable={false} style={{ overflow: 'auto' }} >
<MarkdownViewer className={`text-white font-medium transition-all duration-200 ease-in-out opacity-100`} content={readMoreBtn ? description.substring(0, 125): description}/>
{description.length>190 && (
<button onClick={() => setReadMoreBtn(!readMoreBtn)} className='text-slate-600 ml-2' > {readMoreBtn ? '...Read More': 'Read Less'}</button>
)
}
</div>
</div>: <div className={`px-7 py-0.5 rounded-b-lg transition-all duration-300 ease-in-out opacity-0`}/>
}



</div>
</>
);
}
132 changes: 129 additions & 3 deletions src/pages/moderation.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import {
ArrowRightIcon,
QuestionMarkCircleIcon,
Expand All @@ -13,20 +13,40 @@ import request from '@/utils/request';
import { MyTable } from '@/components/Table';
import { TableHead, TableRow, TableHeader, TableCell, TableBody, Table } from "@/components/ui/table"
import ViewChallenge from '@/components/moderation/ViewChallenge';
import { MarkdownViewer } from '@/components/MarkdownViewer';



export default function Competitions() {
const [selectedChallenges, setSelectedChallenges] = useState([]);
const [selectedId, setSelectedId] = useState(null);
const [pendingChallenges, setPendingChallenges] = useState([]);
const [challengeIsOpen, setChallengeIsOpen] = useState(false);

const [contentPreview, setContentPreview] = useState('');
const textRef = useRef(null);

const [reports, setReports] = useState([]);


const [bonusPoints, setBonusPoints] = useState(0);


const insertText = (text) => {
const textarea = textRef.current;
const startPos = textarea.selectionStart;
const endPos = textarea.selectionEnd;
const newValue =
textarea.value.substring(0, startPos) +
text +
textarea.value.substring(endPos, textarea.value.length);
setContentPreview(newValue);
textarea.focus();
textarea.selectionEnd = startPos + text.length;
};
const magicSnippet = () => {
const id = Math.random().toString(36).substring(7);
insertText(`[Click to run: ${id}](https://ctfguide.com/magic/)`);
};

// get reports
useEffect(() => {
const fetchReports = async () => {
Expand Down Expand Up @@ -157,6 +177,31 @@ export default function Competitions() {
}
};

const handleUploadPatchnote = async () => {
const title = document.getElementById('titleInput').value;
const version = document.getElementById('versionInput').value;
const content = document.getElementById('content').value;
const author = "CTFGuide";

if (!title || !version || !content) {
alert("Please fill in all fields.");
return;
}

try{
const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/admin/createPatchNote`, "POST", {title, version, content});
if(response.success){
alert("Patchnote uploaded successfully!");
}else {
alert("Failed to upload the patchnote.");
}

}catch(err){
console.log(err);
alert("An error occurred while uploading the patchnote.");
}
};

return (
<>
<Head>
Expand Down Expand Up @@ -261,9 +306,90 @@ export default function Competitions() {
<textarea value={selectedChallenges.join(", ") || "N/A"} className='hidden text-white bg-neutral-800 border-none w-full'>
</textarea>

<div className='border border-neutral-700 px-4 py-4 mt-3'>

<div className='grid grid-cols-2 gap-x-4'>
<div className='pr-2'>
<h1 className='text-white text-xl mb-2'>
Create a Patchnote
</h1>

<input type="text" placeholder='Title' id="titleInput" className='mb-2 text-white bg-neutral-800 border-none w-full'></input>
<input type="text" placeholder='Version' id="versionInput" className='mb-2 text-white bg-neutral-800 border-none w-full'></input>

<div
className="mb-2 flex w-full justify-between "
>
<div className="flex space-x-2 rounded-md bg-neutral-900">
<button
onClick={() => insertText('**Enter bold here**')}
className="toolbar-button ml-2 mr-1 pr-2 text-white"
>
<i className="fas fa-bold text-sm"></i>
</button>

<button
onClick={() => insertText('*Enter italic here*')}
className="toolbar-button mr-1 px-2 text-white"
>
<i className="fas fa-italic text-sm"></i>
</button>

<button
onClick={() => insertText('# Enter Heading here')}
className="toolbar-button mr-1 px-2 text-white"
>
<i className="fas fa-heading text-sm"></i>
</button>

<button
onClick={() => insertText('[Name](url)')}
className="toolbar-button mr-1 px-2 text-white"
>
<i className="fas fa-link text-sm"></i>
</button>

<button
onClick={() => insertText('```Enter Code here```')}
className="toolbar-button mr-1 px-2 text-white"
>
<i className="fas fa-code text-sm"></i>
</button>

<button
onClick={() => magicSnippet()}
className="toolbar-button mr-1 px-2 text-white"
>
<i className="fas fa-terminal text-sm"></i>
</button>
</div>

</div>

<textarea value={contentPreview} ref={textRef} className='mb-2 text-white bg-neutral-800 border-none w-full h-36'
id="content"
placeholder="Enter your description here..."
onChange={(event) => {
setContentPreview(event.target.value);
}}
></textarea>

<button className='px-2 py-1 bg-blue-600 text-white' onClick={handleUploadPatchnote}>Upload Patchnote</button>
</div>

<div>
<h1 className="text-xl font-medium text-white mb-2">
Patchnote Content Preview
</h1>
<div className='bg-neutral-800 rounded-lg py-2 px-2' style={{height:'272px', overflowY: 'auto' }}>
<MarkdownViewer className="text-white"content={contentPreview}/>
</div>
</div>
</div>

</div>
</div>

</main>
<div className='flex w-full h-full grow basis-0'></div>
<ViewChallenge open={challengeIsOpen} setOpen={setChallengeIsOpen} selected={selectedId} />
Expand Down
70 changes: 70 additions & 0 deletions src/pages/patch-notes.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Head from 'next/head';
import { Footer } from '@/components/Footer';
import { StandardNav } from '@/components/StandardNav';
import { useEffect, useState } from 'react';
import {PatchNote} from '@/components/PatchNote';
import request from '@/utils/request';

export default function PatchNotes() {
const[patchNotes, setPatchNotes] = useState([{
description: "Description of the patch note",
date: "8/4/2024",
title: "CTFGuide Release",
version: "Version 1.0.0"
},{
description: "# ALLAHxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx `sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss`",
date: "8/4/2024",
title: "CTFGuide Release",
version: "Version 1.0.0"
}]);

async function getPatchNotes() {
//fetch patch notes
const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/patchNotes`, 'GET', null);
if(!response.error) {
setPatchNotes(response);
}
}

useEffect(() => {
getPatchNotes();
}, []);

console.log('patchNotes:', patchNotes);

return (
<>
<Head>
<title>Patch Notes - CTFGuide</title>
<meta
name="description"
content="Cybersecurity made easy for everyone"
/>
<style>
@import
url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
</style>
</Head>

<StandardNav/>
<div className=' mt-8 mx-auto max-w-7xl '>
<h1 className='text-2xl align-middle text-left font-bold text-white'>
Patch Notes
</h1>
<h2 className='text-xl text-white text-left align-middle font-bold'>
A changelog of updates to the <span className='text-blue-600'>CTFGuide</span> platform
</h2>
{patchNotes &&
patchNotes.map((patchNote) => {
return (
<PatchNote description={patchNote.description} date={patchNote.date} title={patchNote.title} version={patchNote.version}/>
);
})
}
<PatchNote description ="we did a whole lot!" date="7/31/24" title="CTFGuide Release" version="Version 1.0.0"/>
</div>
<div className='flex w-full h-full grow basis-0'></div>
<Footer />
</>
);
}