-
Notifications
You must be signed in to change notification settings - Fork 104
Fix/practice challenge vocabulary #1690
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,66 +1,140 @@ | ||
| "use client"; | ||
|
|
||
| import Breadcrumbs from '../../components/layout/Breadcrumbs' | ||
| import Breadcrumbs from "../../components/layout/Breadcrumbs"; | ||
| import WorkspaceLayout from "../../components/layout/WorkspaceLayout"; | ||
| import { useSearchParams } from "next/navigation"; | ||
| import { Suspense, useEffect, useState } from "react"; | ||
| import ChallengeMode from "../../components/challenge/ChallengeMode"; | ||
| import DailyQuiz from "../../components/challenge/DailyQuiz"; | ||
| import QuestionBankQuiz from "../../components/challenge/QuestionBankQuiz" | ||
| import CustomChallengeBuilder from "../../components/challenge/CustomChallengeBuilder" | ||
| import { deserializeCustomChallengeSet, type CustomChallengeSet } from '@/lib/challenge/customChallengeSerializer'; | ||
| import QuestionBankQuiz from "../../components/challenge/QuestionBankQuiz"; | ||
| import CustomChallengeBuilder from "../../components/challenge/CustomChallengeBuilder"; | ||
| import { | ||
| deserializeCustomChallengeSet, type CustomChallengeSet } from "@/lib/challenge/customChallengeSerializer"; | ||
| import { QUESTION_BANK } from "@/lib/challenge/questionBank"; | ||
|
|
||
| type ChallengeTab = "daily" | "bank" | "decryption"; | ||
|
|
||
| function ChallengeContent() { | ||
| const searchParams = useSearchParams(); | ||
| const urlCipher = searchParams.get('cipher'); | ||
| const [activeTab, setActiveTab] = useState<'daily' | 'bank' | 'decryption'>(searchParams.get('custom') ? 'decryption' : 'daily'); | ||
| const urlCipher = searchParams.get("cipher"); | ||
| const customChallengeParam = searchParams.get("custom"); | ||
| const [activeTab, setActiveTab] = useState<ChallengeTab>( | ||
| customChallengeParam ? "decryption" : "daily",); | ||
| const [showOnboarding, setShowOnboarding] = useState(true); | ||
| const [customChallenge, setCustomChallenge] = useState<CustomChallengeSet | null>(null); | ||
| const [customError, setCustomError] = useState(''); | ||
| const [customError, setCustomError] = useState(""); | ||
|
|
||
| useEffect(() => { | ||
| const encoded = searchParams.get('custom'); | ||
| if (!encoded) return; | ||
| void deserializeCustomChallengeSet(encoded) | ||
| .then(setCustomChallenge) | ||
| .catch((error) => setCustomError(error instanceof Error ? error.message : 'Invalid custom challenge link.')); | ||
| }, [searchParams]); | ||
| if (!customChallengeParam) { | ||
| setCustomChallenge(null); | ||
| setCustomError(""); | ||
| return; | ||
| } | ||
|
|
||
| let cancelled = false; | ||
|
|
||
| void deserializeCustomChallengeSet(customChallengeParam) | ||
| .then((challenge) => { | ||
|
Comment on lines
+36
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: sed -n '1,145p' lib/challenge/customChallengeSerializer.tsRepository: csxark/CryptoViz Length of output: 6303 Denial of Service (CWE-409) Reachability: External · Exploitability: Moderate Cap decompression output before buffering it.
🤖 Prompt for AI Agents |
||
| if (cancelled) return; | ||
|
|
||
| setCustomChallenge(challenge); | ||
| setCustomError(""); | ||
| setActiveTab("decryption"); | ||
| }) | ||
| .catch((error: unknown) => { | ||
| if (cancelled) return; | ||
|
|
||
| setCustomChallenge(null); | ||
| setCustomError( | ||
| error instanceof Error | ||
| ? error.message | ||
| : "Invalid custom challenge link.", | ||
| ); | ||
| }); | ||
|
|
||
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| }, [customChallengeParam]); | ||
|
Comment on lines
27
to
+58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Do not render the previous challenge while a new link loads. When 🧰 Tools🪛 ESLint[error] 29-29: Error: Calling setState synchronously within an effect can trigger cascading renders Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect). /home/jailuser/git/app/challenge/page.tsx:29:7
(react-hooks/set-state-in-effect) 🤖 Prompt for AI Agents |
||
|
|
||
| const questionCount = QUESTION_BANK.length; | ||
|
|
||
| const handleCustomChallengeCreated = (serialized: string) => { | ||
| const url = new URL(window.location.href); | ||
|
|
||
| url.searchParams.set("custom", serialized); | ||
|
|
||
| window.history.replaceState({}, "", url.toString()); | ||
|
|
||
| void deserializeCustomChallengeSet(serialized) | ||
| .then((challenge) => { | ||
| setCustomChallenge(challenge); | ||
| setCustomError(""); | ||
| setActiveTab("decryption"); | ||
| }) | ||
| .catch((error: unknown) => { | ||
| setCustomChallenge(null); | ||
| setCustomError( | ||
| error instanceof Error | ||
| ? error.message | ||
| : "Invalid custom challenge link.", | ||
| ); | ||
| }); | ||
|
Comment on lines
+62
to
+82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Make custom-challenge loading latest-wins.
🤖 Prompt for AI Agents |
||
| }; | ||
|
|
||
| return ( | ||
| <WorkspaceLayout activeCipherId={urlCipher || undefined}> | ||
| <main className="relative mx-auto max-w-6xl px-4 sm:px-6 lg:px-8 py-12"> | ||
| <Breadcrumbs items={[{ label: "Practice" }, { label: "Guided Challenge & Question Bank" }]} /> | ||
|
|
||
| <main className="relative mx-auto max-w-6xl px-4 py-12 sm:px-6 lg:px-8"> | ||
| <Breadcrumbs | ||
| items={[ | ||
| { label: "Practice" }, | ||
| { label: "Practice Challenges" }, | ||
| ]} | ||
| /> | ||
|
|
||
| {/* Background accent */} | ||
| <div className="pointer-events-none absolute inset-0 -z-20 bg-[linear-gradient(to_right,#80808008_1px,transparent_1px),linear-gradient(to_bottom,#80808008_1px,transparent_1px)] bg-[size:24px_24px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)] dark:bg-[linear-gradient(to_right,#ffffff05_1px,transparent_1px),linear-gradient(to_bottom,#ffffff05_1px,transparent_1px)]" /> | ||
|
|
||
| {/* Hero & Guided Onboarding Header */} | ||
| <div className="text-center flex flex-col items-center mb-8"> | ||
| {/* Practice Challenge Header */} | ||
| <div className="mb-8 flex flex-col items-center text-center"> | ||
| <div className="mb-3 inline-flex items-center gap-2 rounded-full border border-teal-500/30 bg-teal-50/80 px-3.5 py-1 text-xs font-bold uppercase tracking-widest text-teal-800 dark:border-teal-500/30 dark:bg-teal-500/20 dark:text-teal-300"> | ||
| ★ Recommended Learning Path | ||
| ★ Adaptive Practice Challenges | ||
| </div> | ||
|
|
||
| <h1 className="flex items-center justify-center gap-3 text-3xl font-extrabold tracking-tight text-zinc-900 dark:text-white sm:text-4xl"> | ||
| Guided Practice & Challenge Hub | ||
| Practice Challenge Hub | ||
| </h1> | ||
|
|
||
| <p className="mx-auto mt-3 max-w-2xl text-sm leading-relaxed text-zinc-600 dark:text-zinc-300"> | ||
| Master cryptography through structured daily micro-quizzes, comprehensive topic question banks, timed interactive cipher decryption, or custom shareable challenges. | ||
| Build cryptography skills through daily practice, a comprehensive | ||
| question bank, timed decryption challenges, and custom | ||
| shareable challenges. | ||
| </p> | ||
| </div> | ||
|
|
||
| {/* Onboarding Guide Box */} | ||
| {/* Onboarding Guide */} | ||
| {showOnboarding && ( | ||
| <div className="mb-8 rounded-2xl border border-teal-500/30 bg-teal-50/40 p-6 backdrop-blur-sm dark:border-teal-500/20 dark:bg-teal-950/30"> | ||
| <div className="flex items-start justify-between"> | ||
| <div className="flex items-start justify-between gap-4"> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-teal-500/20 text-teal-600 dark:text-teal-400 font-bold text-lg"> | ||
| <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-teal-500/20 text-lg font-bold text-teal-600 dark:text-teal-400"> | ||
| 1 | ||
| </div> | ||
|
|
||
| <div> | ||
| <h2 className="text-base font-bold text-zinc-900 dark:text-white">How Guided Practice Works</h2> | ||
| <p className="text-xs text-zinc-600 dark:text-zinc-400">Follow our 3-step practice flow to build steady cryptography skills.</p> | ||
| <h2 className="text-base font-bold text-zinc-900 dark:text-white"> | ||
| How Practice Challenges Work | ||
| </h2> | ||
|
|
||
| <p className="text-xs text-zinc-600 dark:text-zinc-400"> | ||
| Follow our three-step practice flow to build steady | ||
| cryptography skills. | ||
| </p> | ||
| </div> | ||
| </div> | ||
|
|
||
| <button | ||
| type="button" | ||
| onClick={() => setShowOnboarding(false)} | ||
| className="text-xs font-semibold text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-white" | ||
| > | ||
|
|
@@ -69,92 +143,158 @@ function ChallengeContent() { | |
| </div> | ||
|
|
||
| <div className="mt-6 grid gap-4 md:grid-cols-3"> | ||
| <div className={`rounded-xl border p-4 transition-all ${activeTab === 'daily' ? 'border-teal-500 bg-white shadow-md dark:bg-zinc-900' : 'border-zinc-200 bg-white/50 dark:border-zinc-800 dark:bg-zinc-900/40'}`}> | ||
| <span className="text-xs font-bold text-teal-600 dark:text-teal-400">Step 1 • Daily Recommended</span> | ||
| <h3 className="mt-1 text-sm font-bold text-zinc-900 dark:text-white">Daily Micro-Quiz</h3> | ||
| <p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">5 fast daily questions to maintain streak and gain base XP.</p> | ||
| {/* Daily Challenge */} | ||
| <div | ||
| className={`rounded-xl border p-4 transition-all ${ | ||
| activeTab === "daily" | ||
| ? "border-teal-500 bg-white shadow-md dark:bg-zinc-900" | ||
| : "border-zinc-200 bg-white/50 dark:border-zinc-800 dark:bg-zinc-900/40" | ||
| }`} | ||
| > | ||
| <span className="text-xs font-bold text-teal-600 dark:text-teal-400"> | ||
| Step 1 • Daily Recommended | ||
| </span> | ||
|
|
||
| <h3 className="mt-1 text-sm font-bold text-zinc-900 dark:text-white"> | ||
| Daily Practice Challenge | ||
| </h3> | ||
|
|
||
| <p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400"> | ||
| Five focused questions to maintain your practice streak and | ||
| build foundational cryptography skills. | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className={`rounded-xl border p-4 transition-all ${activeTab === 'bank' ? 'border-teal-500 bg-white shadow-md dark:bg-zinc-900' : 'border-zinc-200 bg-white/50 dark:border-zinc-800 dark:bg-zinc-900/40'}`}> | ||
| <span className="text-xs font-bold text-teal-600 dark:text-teal-400">Step 2 • Guided Mastery</span> | ||
| <h3 className="mt-1 text-sm font-bold text-zinc-900 dark:text-white">Topic Question Bank</h3> | ||
| <p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Explore 300+ categorized questions across symmetric, asymmetric, and hash primitives.</p> | ||
| {/* Question Bank */} | ||
| <div | ||
| className={`rounded-xl border p-4 transition-all ${ | ||
| activeTab === "bank" | ||
| ? "border-teal-500 bg-white shadow-md dark:bg-zinc-900" | ||
| : "border-zinc-200 bg-white/50 dark:border-zinc-800 dark:bg-zinc-900/40" | ||
| }`} | ||
| > | ||
| <span className="text-xs font-bold text-teal-600 dark:text-teal-400"> | ||
| Step 2 • Guided Mastery | ||
| </span> | ||
|
|
||
| <h3 className="mt-1 text-sm font-bold text-zinc-900 dark:text-white"> | ||
| Cryptographic Question Bank | ||
| </h3> | ||
|
|
||
| <p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400"> | ||
| Explore {questionCount} curated multiple-choice questions | ||
| across classical, symmetric, asymmetric, hash, and attack | ||
| categories. | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className={`rounded-xl border p-4 transition-all ${activeTab === 'decryption' ? 'border-teal-500 bg-white shadow-md dark:bg-zinc-900' : 'border-zinc-200 bg-white/50 dark:border-zinc-800 dark:bg-zinc-900/40'}`}> | ||
| <span className="text-xs font-bold text-teal-600 dark:text-teal-400">Step 3 • Advanced Lab</span> | ||
| <h3 className="mt-1 text-sm font-bold text-zinc-900 dark:text-white">Interactive Decryption</h3> | ||
| <p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Test live decryption against the clock with dynamic hint reveals.</p> | ||
| {/* Timed Decryption */} | ||
| <div | ||
| className={`rounded-xl border p-4 transition-all ${ | ||
| activeTab === "decryption" | ||
| ? "border-teal-500 bg-white shadow-md dark:bg-zinc-900" | ||
| : "border-zinc-200 bg-white/50 dark:border-zinc-800 dark:bg-zinc-900/40" | ||
| }`} | ||
| > | ||
| <span className="text-xs font-bold text-teal-600 dark:text-teal-400"> | ||
| Step 3 • Advanced Lab | ||
| </span> | ||
|
|
||
| <h3 className="mt-1 text-sm font-bold text-zinc-900 dark:text-white"> | ||
| Decryption Time Attack | ||
| </h3> | ||
|
|
||
| <p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400"> | ||
| Test your decryption skills against the clock with dynamic | ||
| hints and challenge scenarios. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Guided Navigation Tabs */} | ||
| <div className="mb-8 flex flex-wrap items-center justify-center gap-3 border-b border-zinc-200 pb-4 dark:border-zinc-800"> | ||
| {/* Practice Challenge Navigation */} | ||
| <div | ||
| className="mb-8 flex flex-wrap items-center justify-center gap-3 border-b border-zinc-200 pb-4 dark:border-zinc-800" | ||
| role="tablist" | ||
| aria-label="Practice challenge types" | ||
| > | ||
| <button | ||
| onClick={() => setActiveTab('daily')} | ||
| type="button" | ||
| role="tab" | ||
| aria-selected={activeTab === "daily"} | ||
| onClick={() => setActiveTab("daily")} | ||
| className={`flex items-center gap-2 rounded-xl px-5 py-2.5 text-sm font-bold transition-all ${ | ||
| activeTab === 'daily' | ||
| ? 'bg-teal-500 text-white shadow-lg shadow-teal-500/25' | ||
| : 'bg-zinc-100 text-zinc-600 hover:bg-zinc-200 dark:bg-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800' | ||
| activeTab === "daily" | ||
| ? "bg-teal-500 text-white shadow-lg shadow-teal-500/25" | ||
| : "bg-zinc-100 text-zinc-600 hover:bg-zinc-200 dark:bg-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800" | ||
| }`} | ||
| > | ||
| 🎯 Recommended: Daily Quiz | ||
| 🎯 Daily Challenge | ||
| </button> | ||
|
|
||
| <button | ||
| onClick={() => setActiveTab('bank')} | ||
| type="button" | ||
| role="tab" | ||
| aria-selected={activeTab === "bank"} | ||
| onClick={() => setActiveTab("bank")} | ||
| className={`flex items-center gap-2 rounded-xl px-5 py-2.5 text-sm font-bold transition-all ${ | ||
| activeTab === 'bank' | ||
| ? 'bg-teal-500 text-white shadow-lg shadow-teal-500/25' | ||
| : 'bg-zinc-100 text-zinc-600 hover:bg-zinc-200 dark:bg-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800' | ||
| activeTab === "bank" | ||
| ? "bg-teal-500 text-white shadow-lg shadow-teal-500/25" | ||
| : "bg-zinc-100 text-zinc-600 hover:bg-zinc-200 dark:bg-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800" | ||
| }`} | ||
| > | ||
| 📚 Question Bank (300+ Qs) | ||
| 📚 Comprehensive Question Bank | ||
| </button> | ||
|
|
||
| <button | ||
| onClick={() => setActiveTab('decryption')} | ||
| type="button" | ||
| role="tab" | ||
| aria-selected={activeTab === "decryption"} | ||
| onClick={() => setActiveTab("decryption")} | ||
| className={`flex items-center gap-2 rounded-xl px-5 py-2.5 text-sm font-bold transition-all ${ | ||
| activeTab === 'decryption' | ||
| ? 'bg-teal-500 text-white shadow-lg shadow-teal-500/25' | ||
| : 'bg-zinc-100 text-zinc-600 hover:bg-zinc-200 dark:bg-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800' | ||
| activeTab === "decryption" | ||
| ? "bg-teal-500 text-white shadow-lg shadow-teal-500/25" | ||
| : "bg-zinc-100 text-zinc-600 hover:bg-zinc-200 dark:bg-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800" | ||
| }`} | ||
| > | ||
| ⚡ Advanced: Timed Decryption | ||
| ⚡ Timed Decryption Challenge | ||
| </button> | ||
| </div> | ||
|
Comment on lines
+216
to
263
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Propagate the new terminology and count into the rendered quizzes. The updated tab labels do not update the child copy. 🤖 Prompt for AI Agents |
||
|
|
||
| {/* Custom Challenge Error */} | ||
| {customError && ( | ||
| <div role="alert" className="mb-6 rounded-xl border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900 dark:bg-red-950/30 dark:text-red-300"> | ||
| <div | ||
| role="alert" | ||
| className="mb-6 rounded-xl border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900 dark:bg-red-950/30 dark:text-red-300" | ||
| > | ||
| Could not open this custom challenge: {customError} | ||
| </div> | ||
| )} | ||
|
|
||
| {activeTab === 'decryption' && !customChallenge && ( | ||
| {/* Custom Challenge Builder */} | ||
| {activeTab === "decryption" && !customChallenge && ( | ||
| <div className="mb-6"> | ||
| <CustomChallengeBuilder onCreated={(serialized) => { | ||
| const url = new URL(window.location.href); | ||
| url.searchParams.set('custom', serialized); | ||
| window.history.replaceState({}, '', url.toString()); | ||
| void deserializeCustomChallengeSet(serialized).then(setCustomChallenge); | ||
| }} /> | ||
| <CustomChallengeBuilder | ||
| onCreated={handleCustomChallengeCreated} | ||
| /> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Active Flow Content */} | ||
| {activeTab === 'daily' && ( | ||
| {/* Active Practice Challenge */} | ||
| {activeTab === "daily" && ( | ||
| <div> | ||
| <DailyQuiz /> | ||
| </div> | ||
| )} | ||
|
|
||
| {activeTab === 'bank' && ( | ||
| {activeTab === "bank" && ( | ||
| <div> | ||
| <QuestionBankQuiz /> | ||
| </div> | ||
| )} | ||
|
|
||
| {activeTab === 'decryption' && ( | ||
| {activeTab === "decryption" && ( | ||
| <div> | ||
| <ChallengeMode customChallenge={customChallenge} /> | ||
| </div> | ||
|
|
@@ -164,11 +304,14 @@ function ChallengeContent() { | |
| ); | ||
| } | ||
|
|
||
|
|
||
| export default function ChallengePage() { | ||
| return ( | ||
| <Suspense fallback={<div className="p-8">Loading challenge workspace...</div>}> | ||
| <Suspense | ||
| fallback={ | ||
| <div className="p-8">Loading practice challenge workspace...</div> | ||
| } | ||
| > | ||
| <ChallengeContent /> | ||
| </Suspense> | ||
| ) | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Resolve the
react-hooks/set-state-in-effecterror.The configured ESLint rule reports the synchronous
setCustomChallenge(null)andsetCustomError("")calls in this effect. Refactor the reset path so the effect handles asynchronous loading while preserving URL-change reset behavior.🧰 Tools
🪛 ESLint
[error] 29-29: Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/jailuser/git/app/challenge/page.tsx:29:7
27 | useEffect(() => {
28 | if (!customChallengeParam) {
(react-hooks/set-state-in-effect)
🤖 Prompt for AI Agents
Source: Linters/SAST tools