diff --git a/.env.development b/.env.development
index ce0b2196..7b4ac54c 100644
--- a/.env.development
+++ b/.env.development
@@ -23,7 +23,7 @@ NEXT_PUBLIC_APP_STORAGE_BUCKET=ctfguide-dev.appspot.com
NEXT_PUBLIC_APP_MESSAGING_SENDER_ID=792987058367
NEXT_PUBLIC_APP_ID=1:792987058367:web:c48935325e46043c3cc60a
NEXT_PUBLIC_APP_MEASUREMENT_ID=G-7ZNKM9VFN2
-NEXT_PUBLIC_TERM_URL=https://dev-terminal-api.ctfguide.com/
+NEXT_PUBLIC_TERM_URL=https://prod-terminal-api.ctfguide.com/
NEXT_PUBLIC_APP_STRIPE_KEY=pk_test_51MAliSF4qMywXyoUvCPG6HDRQaRy4ach2pfDIj4sklm4k22VinqVcygs61F7UWosnZo6Ig1iFykui6rsbR0tRStm00LRZCrXQ8
diff --git a/package.json b/package.json
index b6526b72..b2867c26 100644
--- a/package.json
+++ b/package.json
@@ -65,9 +65,11 @@
"react-markdown": "^9.0.1",
"react-markdown-editor-lite": "^1.3.4",
"react-resizable": "^3.0.5",
+ "react-responsive": "^10.0.0",
"react-router-dom": "^6.9.0",
"react-select-country-list": "^2.2.3",
"react-simplemde-editor": "^5.2.0",
+ "react-swipeable": "^7.0.1",
"react-text-loop": "^2.3.0",
"react-text-transition": "^3.1.0",
"react-toastify": "^10.0.5",
diff --git a/src/components/StandardNav.jsx b/src/components/StandardNav.jsx
index 186916de..f8f87590 100644
--- a/src/components/StandardNav.jsx
+++ b/src/components/StandardNav.jsx
@@ -1,4 +1,4 @@
-import { Fragment, useEffect, useState } from 'react';
+import { Fragment, useEffect, useState, useRef } from 'react';
import { Disclosure, Menu, Popover, Transition } from '@headlessui/react';
import { Dialog } from '@headlessui/react';
@@ -218,6 +218,21 @@ export function StandardNav({ guestAllowed, alignCenter = true }) {
: 'text-gray-300 hover:text-gray-50 border-transparent'
}`;
+ const panelRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (panelRef.current && !panelRef.current.contains(event.target)) {
+ setOpen(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [panelRef]);
+
return (
<>
{isPopoverOpen && (
@@ -242,16 +257,16 @@ export function StandardNav({ guestAllowed, alignCenter = true }) {
{/* Mobile menu button */}
-
+
Open main menu
{open ? (
) : (
)}
@@ -565,72 +580,93 @@ export function StandardNav({ guestAllowed, alignCenter = true }) {
-
-
- {/* Current: "bg-blue-50 border-blue-500 text-blue-700", Default: "border-transparent text-gray-300 hover:text-white" */}
-
- Dashboard
-
-
- Learn
-
-
- Classes
-
-
- Practice
-
-
- Live
-
-
-
-
+
+
+
+ {/* Current: "bg-blue-50 border-blue-500 text-blue-700", Default: "border-transparent text-gray-300 hover:text-white" */}
- Profile
+ Dashboard
- Settings
+ Practice
+
+
+ Leaderboards
-
- Sign out
+ Classes
-
-
+
+
+
+
+ Profile
+
+
+
+ Settings
+
+
+
+
+ Sign out
+
+
+
+
+
+
setShowSearchModal(true)}
+ >
+
+ Search for anything
+
+
+
+
+
>
)}
diff --git a/src/components/dashboard/DashboardHeader.jsx b/src/components/dashboard/DashboardHeader.jsx
index 2bd6e369..e8cdfb46 100644
--- a/src/components/dashboard/DashboardHeader.jsx
+++ b/src/components/dashboard/DashboardHeader.jsx
@@ -90,17 +90,17 @@ export function DashboardHeader() {
backgroundImage:
'url("https://images.unsplash.com/photo-1633259584604-afdc243122ea?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80")',
}}
- className="h-20 w-full object-cover lg:h-20"
+ className="h-20 w-full object-cover lg:h-20 "
alt=""
>
-
-
-
+
+
+
{(pfp && (
@@ -116,8 +116,8 @@ export function DashboardHeader() {
-
-
-
- {username || (
-
- )}
-
-
+
);
-}
+}
\ No newline at end of file
diff --git a/src/pages/challenges/[...id].jsx b/src/pages/challenges/[...id].jsx
index ead03f25..29a94e52 100644
--- a/src/pages/challenges/[...id].jsx
+++ b/src/pages/challenges/[...id].jsx
@@ -26,6 +26,8 @@ import WriteupModal from '@/components/WriteupModal';
export default function Challenge() {
const router = useRouter();
const [selectedWriteup, setSelectedWriteup] = useState(null);
+ const [isTerminalFullscreen, setIsTerminalFullscreen] = useState(false);
+ const [isChallengeFullscreen, setIsChallengeFullscreen] = useState(true);
// I hate this
const [urlChallengeId, urlSelectedTab, urlWriteupId] = (router ?? {})?.query?.id ?? [undefined, undefined, undefined];
@@ -313,27 +315,123 @@ export default function Challenge() {
-
+
-
-
-
- {Object.entries(tabs).map(([url, tab]) =>
)}
+
+ {isChallengeFullscreen && (
+
+
+ {Object.entries(tabs).map(([url, tab]) => (
+
+ ))}
+
+ {selectedWriteup ? (
+
setSelectedWriteup(null)} writeup={selectedWriteup} />
+ ) : (
+
+ )}
+
+
+
+
+ {
+ setIsChallengeFullscreen(false);
+ }}
+ className="bg-neutral-700/50 hover:bg-neutral-700 text-white px-4 py-1 rounded mt-2 md:hidden"
+ >
+ Switch to Terminal
+
- {selectedWriteup ? (
- setSelectedWriteup(null)} writeup={selectedWriteup} />
- ) : (
-
- )}
-
-
-
+ )}
+ {!isChallengeFullscreen && (
+
+
+
+ {foundTerminal && (
+
+
+ Username: {userName}
+ copyToClipboard(userName)} className="ml-2 text-blue-500 hover:text-blue-300">
+
+
+
+
+ Password: {password}
+ copyToClipboard(password)} className="ml-2 text-blue-500 hover:text-blue-300">
+
+
+
+
+ Remaining Time: {formatTime(minutesRemaining)}
+ window.open(terminalUrl, '_blank')} className="cursor-pointer hover:text-yellow-500 ml-2 fas fa-expand text-white">
+ {showMessage && (
+
+ Sometimes browsers block iframes, try opening the terminal in full screen if it the terminal is empty.
+ setShowMessage(false)} className="ml-2 text-red-500 hover:text-red-300">
+ Dismiss
+
+
+ )}
+
+
+ )}
+ {fetchingTerminal ? (
+
+
+
+
{loadingMessage}
+
If you see a black screen, please wait a few seconds and refresh the page.
+
+
+ ) : (
+ isTerminalBooted ? (
+
+ ) : (
+
+
+ Boot Terminal
+
+
+ )
+ )}
+
+
+
{
+ setIsChallengeFullscreen(true);
+ }}
+ className="bg-neutral-700/50 hover:bg-neutral-700 text-white px-4 py-1 rounded mt-2 md:hidden"
+ >
+ Switch to Challenge
+
-
-
+ )}
+
{foundTerminal && (
@@ -362,8 +460,6 @@ export default function Challenge() {
)}
-
-
)}
{fetchingTerminal ? (
@@ -372,7 +468,6 @@ export default function Challenge() {
{loadingMessage}
If you see a black screen, please wait a few seconds and refresh the page.
-
) : (
@@ -472,17 +567,17 @@ function PointsModal({ isOpen, setIsOpen, points }) {
numberOfPieces={800}
/>
-
-
-
-
Nice work!
+
+
+
+
Nice work!
You have been awarded {points} points.
setIsOpen(false)} className="mt-4 bg-blue-600 hover:bg-blue-400 text-white px-4 py-2 rounded">
Close
-
-
BADGES
+
+
BADGES
You did not receive any new badges.
REWARD SUMMARY
+{points} points
@@ -498,7 +593,7 @@ function PointsModal({ isOpen, setIsOpen, points }) {
);
}
-function TabLink({ tabName, selected, url }) {
+function TabLink({ tabName, selected, url, className }) {
const selectedStyle = selected ? 'text-white bg-neutral-600' : 'text-neutral-400';
const icon = {
'Comments': 'fas fa-comments text-green-500',
@@ -510,7 +605,7 @@ function TabLink({ tabName, selected, url }) {
}[tabName] || 'fas fa-file-alt text-blue-500';
return (
-
+
{tabName ?? 'This is a test button'}
@@ -1221,18 +1316,18 @@ function CommentsPage({ cache }) {
className="w-8 h-8 rounded-full"
alt={`${comment.username}'s profile`}
/>
-
+
{comment.username}
{comment.role === 'ADMIN' && (
<>
- Developer
+ developer
>
)}
{comment.role === 'PRO' && (
<>
- Pro
+ pro
>
)}
{new Date(comment.createdAt).toLocaleString()}
diff --git a/src/pages/dashboard.jsx b/src/pages/dashboard.jsx
index c65a434d..1a92080c 100644
--- a/src/pages/dashboard.jsx
+++ b/src/pages/dashboard.jsx
@@ -164,47 +164,13 @@ export default function Dashboard() {
-
-
-
-
-
-
-
-
-
-
-
-
- Mobile Device Warning
-
-
-
- We regret to inform you that CTFGuide is not optimized for use on mobile devices. While you can still access our website on your phone or tablet, we strongly recommend using a desktop or laptop computer for the best experience.
-
-
-
-
-
-
- Okay
-
-
-
-
-
+
-
+
{showOnboarding && (
diff --git a/src/pages/leaderboards.jsx b/src/pages/leaderboards.jsx
index 739e2fb1..c2cd992c 100644
--- a/src/pages/leaderboards.jsx
+++ b/src/pages/leaderboards.jsx
@@ -54,10 +54,51 @@ export default function Leaderboard() {
-
+ {/*simple mobile podium*/}
+
+
+
+ {leaderboardData.slice(0, 3).map((entry, index) => (
+
{
+ window.location.href = '../users/' + entry.user.username;
+ }}
+ key={index}
+ className={`cursor-pointer flex items-center gap-2 p-2 rounded-lg w-full ${
+ index === 0
+ ? 'bg-gradient-to-br from-amber-600 via-yellow-400 via-75% to-amber-600'
+ : index === 1
+ ? 'bg-gradient-to-br from-gray-600 via-gray-400 via-65% to-gray-600'
+ : 'bg-gradient-to-br from-orange-900 via-orange-400 via-65% to-orange-900'
+ }`}
+ >
+
{index + 1}.
+
+
+
{entry.user.username}
+
{entry.totalPoints} points
+
+
+ ))}
+
+
{ leaderboardData[0] && leaderboardData[1] && leaderboardData[2] && leaderboardData.length > 3 ? (
<>
-
+
{
@@ -66,7 +107,7 @@ export default function Leaderboard() {
}}
>
-
+
-
+
-
+
+
+
diff --git a/src/pages/users/[user].jsx b/src/pages/users/[user].jsx
index 60a9c636..e835e7fc 100644
--- a/src/pages/users/[user].jsx
+++ b/src/pages/users/[user].jsx
@@ -35,6 +35,8 @@ import 'react-circular-progressbar/dist/styles.css';
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import CusTooltip from '@/components/profile/BadgeOnhover';
+import { useSwipeable } from 'react-swipeable';
+
// Register the necessary chart component
ChartJS.register(
RadialLinearScale,
@@ -158,6 +160,13 @@ export default function Create() {
const [categoryChallenges, setCategoryChallenges] = useState([]);
const [rank, setRank] = useState(null);
+ const [currentSwipeView, setCurrentSwipeView] = useState('skillChart');
+
+ const handlers = useSwipeable({
+ onSwipedLeft: () => setCurrentSwipeView('difficultyBreakdown'),
+ onSwipedRight: () => setCurrentSwipeView('skillChart'),
+ });
+
function closeUnsavedNotif() {
bannerState(false);
setIsBioExpanded(false);
@@ -879,13 +888,13 @@ export default function Create() {
>
-
-
+
+
-
+
-
-
+
+
{(user && user.username) || (
@@ -951,7 +960,7 @@ export default function Create() {
)}
-
+
{' '}
{(user && user.location) || (
-
-
+
-
+ {/* Main layout for larger screens */}
+
@@ -1217,6 +1227,211 @@ export default function Create() {
/>
+
+
+
+
+ {/* Mobile layout */}
+
+
+
+
+
+
+ {user && (
+
+ ABOUT {user.username}
+
+ )}
+
+ {bioViewCheck() ? renderEditButton() : ''}
+
+ {user && user.bio != null ? (
+
+ {bioViewCheck()
+ ? renderUsersBio()
+ : user && }
+
+ ) : (
+
+ {bioViewCheck()
+ ? renderUsersBio()
+ : user && (
+
+ )}
+
+ )}
+
+
+
+ {currentSwipeView === 'skillChart' ? (
+
+
SKILL CHART
+ {categoryChallenges.length > 0 &&
+ categoryChallenges.some(
+ (category) => category.completed > 0
+ ) ? (
+
+
+
+ ) : (
+
+ It seems that {user && user.username} hasn't solved any
+ challenges yet.
+
+ )}
+
+
+ ) : (
+
+
+ DIFFICULTY BREAKDOWN
+
+ {solvedChallenges > 0 ? (
+
+
+
+
+ {solvedChallenges} Solved
+
+
+
+ {completionData.map((item, index) => (
+
+
+ {item.name}
+
+ {item.amount}
+
+ ))}
+
+
+ ) : (
+
+ Hmm, {user && user.username} hasn't solved any challenges
+ yet.
+
+ )}
+
+
+ )}
+
+
+
+ {displayMode === 'followers' && (
+
+ )}
+ {displayMode === 'following' && (
+
+ )}
+
+ {displayMode === 'default' && (
+ <>
+
+
+
+ {[
+ 'SOLVED CHALLENGES',
+ 'WRITEUPS',
+ 'CREATED CHALLENGES',
+ 'BADGES',
+ ].map((tab) => (
+ setActiveTab(tab)}
+ >
+ {tab}
+
+ ))}
+
+
setActiveTab(e.target.value)}
+ >
+ {[
+ 'LIKED CHALLENGES',
+ 'WRITEUPS',
+ 'CREATED CHALLENGES',
+ 'BADGES',
+ ].map((tab) => (
+
+ {tab}
+
+ ))}
+
+
+
+ {user != undefined && renderContent()}
+
+
+
+
+
+
+ ACTIVITY CALENDAR
+
+
+ {activityData && (
+
+ )}{' '}
+
{' '}
+
{' '}
+
+
+
+ >
+ )}
+
+
+
{/* are you even surprised atp? */}
@@ -1247,6 +1462,9 @@ export default function Create() {
>
) : null}
+
+
+
{banner && (
diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css
index 7c329530..6a596b23 100644
--- a/src/styles/tailwind.css
+++ b/src/styles/tailwind.css
@@ -432,7 +432,14 @@ ul {
position: relative;
transition: transform 0.3s ease-in-out, height 0.3s ease-in-out;
}
-
+@media (max-width: 768px) {
+ .podium {
+ display: none;
+ }
+ .top-three {
+ background-color: var(--color);
+ }
+}
.podium:hover {
transform: translateY(-5px);
height: calc(100% + 200px)
diff --git a/yarn.lock b/yarn.lock
index c5bda690..bda41bf7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2602,6 +2602,11 @@ cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
+css-mediaquery@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0"
+ integrity sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==
+
css-selector-parser@^3.0.0:
version "3.0.5"
resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-3.0.5.tgz#9b636ebccf7c4bcce5c1ac21ae27de9f01180ae9"
@@ -4004,6 +4009,11 @@ https-proxy-agent@^7.0.2:
agent-base "^7.0.2"
debug "4"
+hyphenate-style-name@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
+ integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==
+
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@@ -4478,6 +4488,13 @@ marked@^4.1.0, marked@^4.3.0:
resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3"
integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==
+matchmediaquery@^0.4.2:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.4.2.tgz#22582bd4ae63ad9f54c53001bba80cbed0f7eafa"
+ integrity sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==
+ dependencies:
+ css-mediaquery "^0.1.2"
+
material-ui-popup-state@^5.0.5:
version "5.0.9"
resolved "https://registry.yarnpkg.com/material-ui-popup-state/-/material-ui-popup-state-5.0.9.tgz#c4a1054b3ee8457fe1952fc32ad7604f238eca17"
@@ -5871,7 +5888,7 @@ progress@2.0.3:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
-prop-types@15.x, prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@15.x, prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -6191,6 +6208,16 @@ react-resize-detector@^8.0.4:
dependencies:
lodash "^4.17.21"
+react-responsive@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-10.0.0.tgz#657c7a90823cd565f43aa5918bd8eb0cd2c91c91"
+ integrity sha512-N6/UiRLGQyGUqrarhBZmrSmHi2FXSD++N5VbSKsBBvWfG0ZV7asvUBluSv5lSzdMyEVjzZ6Y8DL4OHABiztDOg==
+ dependencies:
+ hyphenate-style-name "^1.0.0"
+ matchmediaquery "^0.4.2"
+ prop-types "^15.6.1"
+ shallow-equal "^3.1.0"
+
react-router-dom@^6.9.0:
version "6.14.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.2.tgz#88f520118b91aa60233bd08dbd3fdcaea3a68488"
@@ -6226,6 +6253,11 @@ react-smooth@^2.0.2:
fast-equals "^5.0.0"
react-transition-group "2.9.0"
+react-swipeable@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-7.0.1.tgz#cd299f5986c5e4a7ee979839658c228f660e1e0c"
+ integrity sha512-RKB17JdQzvECfnVj9yDZsiYn3vH0eyva/ZbrCZXZR0qp66PBRhtg4F9yJcJTWYT5Adadi+x4NoG53BxKHwIYLQ==
+
react-text-loop@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/react-text-loop/-/react-text-loop-2.3.0.tgz#c4035b1f3fb8216b34ed93f3160025fc7b1af3b7"
@@ -6751,6 +6783,11 @@ server-only@^0.0.1:
resolved "https://registry.yarnpkg.com/server-only/-/server-only-0.0.1.tgz#0f366bb6afb618c37c9255a314535dc412cd1c9e"
integrity sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==
+shallow-equal@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-3.1.0.tgz#e7a54bac629c7f248eff6c2f5b63122ba4320bec"
+ integrity sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==
+
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"