diff --git a/src/components/profile/ChallengeCard.jsx b/src/components/profile/ChallengeCard.jsx index 3e84723b..644a87ff 100644 --- a/src/components/profile/ChallengeCard.jsx +++ b/src/components/profile/ChallengeCard.jsx @@ -1,102 +1,102 @@ -import React, { useEffect, useState } from 'react'; -import { CardDecorator } from '../design/CardDecorator'; -import Link from 'next/link'; -import Skeleton from 'react-loading-skeleton'; -import { EyeIcon, HeartIcon, PuzzlePieceIcon, ThumbUpIcon, ThumbDownIcon } from '@heroicons/react/20/solid'; -import request from '@/utils/request'; +import React, { useEffect, useState } from 'react' +import { CardDecorator } from '../design/CardDecorator' +import Link from 'next/link' +import Skeleton from 'react-loading-skeleton' +import { EyeIcon, HeartIcon, PuzzlePieceIcon, ThumbUpIcon, ThumbDownIcon } from '@heroicons/react/20/solid' +import request from '@/utils/request' -/** - * @param {import('react').HTMLAttributes & { challenge: {id: string, title: string, category: string, difficulty: string, createdAt: string, creator: string, views: number, likes: number} }} props +/** + * @param {import('react').HTMLAttributes & { challenge: {id: string, title: string, category: string, difficulty: string, createdAt: string, creator: string, views: number, likes: number} }} props * */ const ChallengeCard = (_props) => { - const { challenge, ...props } = _props; - const [creatorPfp, setCreatorPfp] = useState(''); - const baseUrl = process.env.NEXT_PUBLIC_FRONTEND_URL; + const { challenge, ...props } = _props + const [creatorPfp, setCreatorPfp] = useState('') + const baseUrl = process.env.NEXT_PUBLIC_FRONTEND_URL // generate code to hit /users endpoint for each challenge creator - const [creator, setCreator] = useState(null); + const [creator, setCreator] = useState(null) useEffect(() => { - async function fetchCreatorData(username) { - const endPoint = `${process.env.NEXT_PUBLIC_API_URL}/users/${username}`; - const result = await request(endPoint, "GET", null); - setCreator(result); + async function fetchCreatorData (username) { + const endPoint = `${process.env.NEXT_PUBLIC_API_URL}/users/${username}` + const result = await request(endPoint, 'GET', null) + setCreator(result) if (result.profileImage) { - setCreatorPfp(result.profileImage); + setCreatorPfp(result.profileImage) } else { - setCreatorPfp(`https://robohash.org/${username}.png?set=set1&size=150x150`); + setCreatorPfp(`https://robohash.org/${username}.png?set=set1&size=150x150`) } } if (challenge && challenge.creator) { - fetchCreatorData(challenge.creator); + fetchCreatorData(challenge.creator) } - }, [challenge]); + }, [challenge]) const dateFormatted = (date) => new Date(date) .toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', - year: 'numeric', - }); + year: 'numeric' + }) const colorBG = { - 'BEGINNER': 'group-hover:bg-blue-500', - 'EASY': 'group-hover:bg-green-500', - 'MEDIUM': 'group-hover:bg-orange-500', - 'HARD': 'group-hover:bg-red-500', - 'INSANE': 'group-hover:bg-purple-500', - }; + BEGINNER: 'group-hover:bg-blue-500', + EASY: 'group-hover:bg-green-500', + MEDIUM: 'group-hover:bg-orange-500', + HARD: 'group-hover:bg-red-500', + INSANE: 'group-hover:bg-purple-500' + } const colorText = { - 'BEGINNER': 'bg-blue-500 text-blue-50', - 'EASY': 'bg-green-500 text-green-50', - 'MEDIUM': 'bg-orange-500 text-orange-50', - 'HARD': 'bg-red-500 text-red-50', - 'INSANE': 'bg-purple-500 text-purple-50', - }; + BEGINNER: 'bg-blue-500 text-blue-50', + EASY: 'bg-green-500 text-green-50', + MEDIUM: 'bg-orange-500 text-orange-50', + HARD: 'bg-red-500 text-red-50', + INSANE: 'bg-purple-500 text-purple-50' + } const colorBorder = { - 'BEGINNER': 'border-blue-500', - 'EASY': 'border-green-500', - 'MEDIUM': 'border-orange-500', - 'HARD': 'border-red-500', - 'INSANE': 'border-purple-500', - }; + BEGINNER: 'border-blue-500', + EASY: 'border-green-500', + MEDIUM: 'border-orange-500', + HARD: 'border-red-500', + INSANE: 'border-purple-500' + } return ( challenge && ( - -
-

{challenge.title}

+ +
+

{challenge.title}

-
- {challenge.upvotes !== undefined ? challenge.upvotes : } - {challenge.downvotes !== undefined ? challenge.downvotes : } +
+ {challenge.upvotes !== undefined ? challenge.upvotes : } + {challenge.downvotes !== undefined ? challenge.downvotes : }
-
+
Profile Picture @{challenge.creator} {creator && creator.role === 'ADMIN' && ( - + )} {creator && creator.role === 'PRO' && ( - - - )} + + + )}
-
- - {challenge.category} +
+ + {challenge.category} {challenge.difficulty?.toLowerCase() || } -
- - {challenge.views !== undefined ? challenge.views : } - -
+
+ + {challenge.views !== undefined ? challenge.views : } + +
) || ( @@ -107,7 +107,7 @@ const ChallengeCard = (_props) => {
) - ); -}; + ) +} -export default ChallengeCard; \ No newline at end of file +export default ChallengeCard diff --git a/src/components/profile/Following.jsx b/src/components/profile/Following.jsx index c1d3b63e..79b78faf 100644 --- a/src/components/profile/Following.jsx +++ b/src/components/profile/Following.jsx @@ -1,63 +1,67 @@ -import React from 'react'; -import { CardDecorator } from '../design/CardDecorator'; -import Link from 'next/link'; -import Skeleton from 'react-loading-skeleton'; -import { Tooltip } from 'react-tooltip'; -import FriendCard from '../social/FriendCard'; +import React from 'react' +import { CardDecorator } from '../design/CardDecorator' +import Link from 'next/link' +import Skeleton from 'react-loading-skeleton' +import { Tooltip } from 'react-tooltip' +import FriendCard from '../social/FriendCard' const Following = ({ followings, pageData, userData }) => { - const { user, ownUser } = userData; - const { setDisplayMode, page, totalPages, prevPage, nextPage } = pageData; + const { user, ownUser } = userData + const { setDisplayMode, page, totalPages, prevPage, nextPage } = pageData return ( <> -
-
+
+
setDisplayMode('default')} - > + /> -

+

{user && user.username}'s Following

- {followings.length>0 ? ( -
-
-
- {followings.length > 0 ? ( - followings.map((following, index) => ( - - )) - ) : ( -

- You have no friends yet. -

- )} + {followings.length > 0 + ? ( +
+
+
+ {followings.length > 0 + ? ( + followings.map((following, index) => ( + + )) + ) + : ( +

+ You have no friends yet. +

+ )} +
-
- ) : ( -

{user && user.username} is not following anyone yet.

- )} + ) + : ( +

{user && user.username} is not following anyone yet.

+ )} {totalPages > 0 && ( -
+
- + Page {page + 1} of {totalPages}
)}
- - ); -}; + ) +} -export default Following; +export default Following diff --git a/src/pages/challenges/[...id].jsx b/src/pages/challenges/[...id].jsx index 2ac286ec..f7e54d43 100644 --- a/src/pages/challenges/[...id].jsx +++ b/src/pages/challenges/[...id].jsx @@ -1,288 +1,278 @@ -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, useContext } 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'; -import { Context } from '@/context'; -import { useRef } from 'react'; - - -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, useContext, useRef } 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' +import { Context } from '@/context' + +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, }, - 'hints': { text: 'Hints', element: HintsPage, }, + description: { text: 'Description', element: DescriptionPage }, + hints: { text: 'Hints', element: HintsPage }, - 'comments': { text: 'Comments', element: CommentsPage, }, + comments: { text: 'Comments', element: CommentsPage }, - 'write-up': { text: 'Write Up', element: WriteUpPage, }, - 'leaderboard': { text: 'Leaderboard', element: LeaderboardPage, }, + 'write-up': { text: 'Write Up', element: WriteUpPage }, + leaderboard: { text: 'Leaderboard', element: LeaderboardPage } } - 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}`) + } - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [commentToDelete, setCommentToDelete] = useState(null); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) + const [commentToDelete, setCommentToDelete] = useState(null) const openDeleteModal = (commentId) => { - setCommentToDelete(commentId); - setIsDeleteModalOpen(true); - }; + setCommentToDelete(commentId) + setIsDeleteModalOpen(true) + } const closeDeleteModal = () => { - setIsDeleteModalOpen(false); - setCommentToDelete(null); - }; + setIsDeleteModalOpen(false) + setCommentToDelete(null) + } const deleteComment = async (commentId) => { try { - const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/challenges/comments/${commentId}`, "DELETE", {}); + const response = await request(`${process.env.NEXT_PUBLIC_API_URL}/challenges/comments/${commentId}`, 'DELETE', {}) if (response.success) { - setComments(comments.filter(comment => comment.id !== commentId)); - toast.success("Comment deleted successfully."); + setComments(comments.filter(comment => comment.id !== commentId)) + toast.success('Comment deleted successfully.') } else { - toast.error("Failed to delete comment."); + toast.error('Failed to delete comment.') } } catch (error) { - console.error("Error deleting comment:", error); - toast.error("An unexpected error occurred."); + console.error('Error deleting comment:', error) + toast.error('An unexpected error occurred.') } finally { - closeDeleteModal(); + closeDeleteModal() } - }; + } - const [expandedComments, setExpandedComments] = useState({}); + const [expandedComments, setExpandedComments] = useState({}) const toggleReplies = (commentId) => { setExpandedComments((prev) => ({ ...prev, - [commentId]: !prev[commentId], - })); - }; + [commentId]: !prev[commentId] + })) + } return ( <> Challenge - CTFGuide - - -
-
-
-
-
-
-
- - {(user && ( - item.name), + datasets: [ + { + data: completionData.map(item => item.amount), + backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'], + hoverBackgroundColor: ['#2563eb', '#059669', '#d97706', '#dc2626', '#7c3aed'], + borderWidth: 0, + borderRadius: 0, + cutout: '70%' // This makes it a donut chart + } + ] + } + + const pieOptions = { + plugins: { + legend: { + display: false // Hide the legend + }, + tooltip: { + callbacks: { + label: function (context) { + const label = context.label || '' + const value = context.raw || 0 + return `${label}: ${value} challenges` + } + } + } + } + } + + const updatedCompletionData = completionData.map(item => ({ + ...item + })) + + const totalChallenges = completionData.reduce((acc, item) => acc + item.amount, 0) + const solvedChallenges = totalCompletedChallenges + + const segments = completionData.map(item => ({ + value: (item.amount / totalChallenges) * 100, + color: item.color.split('-')[1] === 'blue' + ? '#3b82f6' + : item.color.split('-')[1] === 'green' + ? '#10b981' + : item.color.split('-')[1] === 'orange' + ? '#f59e0b' + : item.color.split('-')[1] === 'red' + ? '#ef4444' + : item.color.split('-')[1] === 'purple' ? '#8b5cf6' : '#fff' + })) + + return ( + <> + + {user && user.username}'s Profile - CTFGuide + + + +
+
+
+
+
+
+ -
-
-
-
-
-

- {(user && user.username) || ( - - )} - {user && rank && ( - - #{rank} - - )} - - - {user && user.role === 'ADMIN' && ( - - developer - - )} - - {user && user.role === 'PRO' && ( - - pro - - )} - {!ownUser && followedUser && ( - - - - )} - {!ownUser && !followedUser && ( - - - - )} -

-

- {' '} - {(user && user.location) || ( - - )} -

-
-
-

- {user && ( - <> - setDisplayMode('followers')} - > - {' '} - Followers - {' '} - {followerNum || '0'} - setDisplayMode('following')} - > - {' '} - Following - {' '} - {followingNum || '0'} - - )} -

-
-
-
-
-
+ alt='' + /> + )) || ( + + )} + +
+
+
+
+
+
+

+ {(user && user.username) || ( + + )} + {user && rank && ( + + #{rank} + + )} + + {user && user.role === 'ADMIN' && ( + + developer + + )} + + {user && user.role === 'PRO' && ( + + pro + + )} + {!ownUser && followedUser && ( + + + + )} + {!ownUser && !followedUser && ( + + + + )} +

+

+ {' '} + {(user && user.location) || ( + + )} +

+
+
+

+ {user && ( + <> + setDisplayMode('followers')} + > + {' '} + Followers + {' '} + {followerNum || '0'} + setDisplayMode('following')} + > + {' '} + Following + {' '} + {followingNum || '0'} + + )} +

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

SKILL CHART

+ {(categoryChallenges.length > 0 && categoryChallenges.some(category => category.completed > 0)) + ? ( + + ) + : ( +

+ It seems that {user && user.username} hasn't solved any challenges yet. +

+ )} +
+
-
-
-
- - -
-
-

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

-
-
- {( +
+
+

+ DIFFICULTY BREAKDOWN +

+ {solvedChallenges > 0 + ? ( +
+
+ +

{solvedChallenges} Solved

+
+
+ {( completionData.map((item, index) => ( -
- - {item.name} - - {item.amount} -
+
+ + {item.name} + + {item.amount} +
)) )} -
-
- ) : ( -

Hmm, {user && user.username} hasn't solved any challenges yet.

- )} -
-
-
- - -
-

NERD STATS

- {user && ( -
-

- Creation Date :{' '} - {new Date(user.createdAt).toLocaleDateString('en-US', { - month: 'long', - day: 'numeric', - year: 'numeric', - minute: 'numeric', - hour: 'numeric', - })} -

-

Total Challenges: {user.totalChallenges || '0'}

-

Total Writeups: {user.totalWriteups || '0'}

-

Total Badges: {user.totalBadges || '0'}

-
- )} -
+
-
- {displayMode === 'followers' && ( - - )} - {displayMode === 'following' && ( - + ) + : ( +

Hmm, {user && user.username} hasn't solved any challenges yet.

+ )} +
+
+
+ +
+

NERD STATS

+ {user && ( +
+

+ Creation Date :{' '} + {new Date(user.createdAt).toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + minute: 'numeric', + hour: 'numeric' + })} +

+

Total Challenges: {user.totalChallenges || '0'}

+

Total Writeups: {user.totalWriteups || '0'}

+

Total Badges: {user.totalBadges || '0'}

+
+ )} +
+
+
+ {displayMode === 'followers' && ( + + )} + {displayMode === 'following' && ( + + )} + + {displayMode === 'default' && ( + <> +
+
+
+
+ {user && ( +

ABOUT {user.username}

)} +
+ {bioViewCheck() ? renderEditButton() : ''} - {displayMode === 'default' && ( - <> -
-
-
-
- {user && ( -

ABOUT {user.username}

- )} -
- {bioViewCheck() ? renderEditButton():'' } - - -
- {user && user.bio != null ? ( -

- {bioViewCheck() ? renderUsersBio() : user && } -

- - ): ( -

- {bioViewCheck() ? renderUsersBio() : user && } -

- )} - -
+ {user && user.bio != null + ? ( +

+ {bioViewCheck() ? renderUsersBio() : user && } +

+ + ) + : ( +

+ {bioViewCheck() ? renderUsersBio() : user && } +

+ )} -
-
-
-
    - {[ - 'SOLVED CHALLENGES', - 'WRITEUPS', - 'CREATED CHALLENGES', - - ].map((tab) => ( -
  • +
+ +
+
+
+
    + {[ + 'SOLVED CHALLENGES', + 'WRITEUPS', + 'CREATED CHALLENGES' + + ].map((tab) => ( +
  • setActiveTab(tab)} - > - {tab} -
  • - ))} -
- -
-
- {user != undefined && renderContent()} -
- -
-
-
-

- ACTIVITY CALENDAR -

-
- { activityData && - - - } - {' '} -
{' '} -
{' '} -
-
-
-
- - )} - - + onClick={() => setActiveTab(tab)} + > + {tab} + + ))} + + +
+
+ {user != undefined && renderContent()}
-
- -
- - {/* are you even surprised atp? */} - {displayMode === 'followers' || displayMode === 'following' ? ( - <> -

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

- - ) : null} - -
- - {banner && ( - + )} - - ); -} \ No newline at end of file + + +
+
+ +
+ + {/* are you even surprised atp? */} + {displayMode === 'followers' || displayMode === 'following' + ? ( + <> +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + ) + : null} + +