|
1 | 1 |
|
2 | 2 | import React, { useState, useEffect } from 'react'; |
3 | 3 | import { |
4 | | - ShieldCheck, Menu, ChevronLeft, BookOpen, MessageSquare, Settings, Activity, Zap, ChevronDown, ChevronRight, Flag, Award, Terminal as TerminalIcon, Layers, Lock, Sun, Moon, CheckCircle, Code, ArrowLeft, Send, Clipboard, X, HelpCircle, Book, Flame, MapPin, ToggleRight, ToggleLeft, FastForward, Key |
| 4 | + ShieldCheck, Menu, ChevronLeft, BookOpen, MessageSquare, Settings, Activity, Zap, ChevronDown, ChevronRight, Flag, Award, Terminal as TerminalIcon, Layers, Lock, Sun, Moon, CheckCircle, Code, ArrowLeft, Send, Clipboard, X, HelpCircle, Book, Flame, MapPin, ToggleRight, ToggleLeft, FastForward, Key, AlertTriangle, Check |
5 | 5 | } from 'lucide-react'; |
6 | 6 | import ReactMarkdown from 'react-markdown'; |
7 | 7 | import Terminal from './components/Terminal'; |
@@ -329,6 +329,16 @@ const App: React.FC = () => { |
329 | 329 | setShowApiKeyModal(false); |
330 | 330 | }; |
331 | 331 |
|
| 332 | + const handleCertificateAccess = () => { |
| 333 | + if (userProfile?.isCertified) { |
| 334 | + setAppState(AppState.CERTIFICATE); |
| 335 | + } else { |
| 336 | + alert("Access Denied: You must pass the Final Certification Exam first."); |
| 337 | + } |
| 338 | + setMobileMenuOpen(false); |
| 339 | + setActiveMobileTab('learn'); |
| 340 | + }; |
| 341 | + |
332 | 342 | const getDifficultyColor = (diff: string) => { |
333 | 343 | switch(diff) { |
334 | 344 | case 'Beginner': return 'bg-gray-800 text-gray-400 border-gray-700'; |
@@ -361,9 +371,9 @@ const App: React.FC = () => { |
361 | 371 | <ShieldCheck size={16} className={userProfile?.isCertified ? "text-green-500" : "text-gray-400"} /> |
362 | 372 | Certification |
363 | 373 | </button> |
364 | | - <button onClick={() => {setAppState(AppState.CERTIFICATE); setMobileMenuOpen(false); setActiveMobileTab('learn');}} className={`w-full flex items-center gap-3 px-3 py-2 rounded-md text-xs font-medium transition-colors ${appState === AppState.CERTIFICATE ? 'bg-brand/10 text-brand' : 'text-gray-400 hover:bg-[#111]'}`}> |
| 374 | + <button onClick={handleCertificateAccess} className={`w-full flex items-center gap-3 px-3 py-2 rounded-md text-xs font-medium transition-colors ${appState === AppState.CERTIFICATE ? 'bg-brand/10 text-brand' : 'text-gray-400 hover:bg-[#111]'}`}> |
365 | 375 | <Award size={16} className={userProfile?.isCertified ? "text-brand" : "text-gray-400"} /> |
366 | | - My Credential |
| 376 | + My Credential {userProfile?.isCertified ? "" : "(Locked)"} |
367 | 377 | </button> |
368 | 378 | <button onClick={() => setShowApiKeyModal(true)} className="w-full flex items-center gap-3 px-3 py-2 rounded-md text-xs font-medium text-gray-400 hover:bg-[#111] transition-colors"> |
369 | 379 | <Key size={16} /> API Key |
@@ -444,11 +454,11 @@ const App: React.FC = () => { |
444 | 454 | {isDarkMode ? <Sun size={18} /> : <Moon size={18} />} |
445 | 455 | </button> |
446 | 456 |
|
447 | | - <div className="flex items-center gap-2 px-3 py-1 bg-gray-100 dark:bg-[#111] rounded border border-gray-200 dark:border-[#27272a]"> |
| 457 | + <div className="flex items-center gap-2 px-3 py-1 bg-gray-100 dark:bg-[#111] rounded border border-gray-200 dark:border-gray-700"> |
448 | 458 | <Flame size={12} className="text-orange-500 fill-orange-500" /> |
449 | 459 | <span className="text-xs font-bold text-gray-700 dark:text-gray-300">{userProfile?.streak || 1}</span> |
450 | 460 | </div> |
451 | | - <div className="flex items-center gap-2 px-3 py-1 bg-gray-100 dark:bg-[#111] rounded border border-gray-200 dark:border-[#27272a]"> |
| 461 | + <div className="flex items-center gap-2 px-3 py-1 bg-gray-100 dark:bg-[#111] rounded border border-gray-200 dark:border-gray-700"> |
452 | 462 | <Zap size={12} className="text-brand fill-brand" /> |
453 | 463 | <span className="text-xs font-bold text-gray-700 dark:text-gray-300">{userProfile?.xp || 0} XP</span> |
454 | 464 | </div> |
@@ -514,7 +524,15 @@ const App: React.FC = () => { |
514 | 524 | </div> |
515 | 525 |
|
516 | 526 | <div className="p-4 border border-gray-200 dark:border-[#27272a] rounded-lg bg-gray-50 dark:bg-[#050505]"> |
517 | | - <div className="flex gap-2"><input type="text" value={flagInput} onChange={(e) => setFlagInput(e.target.value)} placeholder="CIPHER-CTF{...}" className="flex-1 bg-white dark:bg-[#0a0a0a] border border-gray-300 dark:border-[#27272a] rounded px-3 py-2 text-gray-900 dark:text-white font-mono text-sm focus:border-brand outline-none" /><button onClick={handleFlagSubmit} className="bg-gray-900 dark:bg-white text-white dark:text-black font-bold px-4 py-2 rounded text-sm hover:opacity-90">Submit</button></div> |
| 527 | + <div className="flex gap-2 items-center"> |
| 528 | + <input type="text" value={flagInput} onChange={(e) => setFlagInput(e.target.value)} placeholder="CIPHER-CTF{...}" className={`flex-1 bg-white dark:bg-[#0a0a0a] border rounded px-3 py-2 text-gray-900 dark:text-white font-mono text-sm outline-none transition-all ${flagStatus === 'error' ? 'border-red-500 ring-1 ring-red-500 animate-pulse' : 'border-gray-300 dark:border-[#27272a] focus:border-brand'}`} /> |
| 529 | + <button onClick={handleFlagSubmit} className="bg-gray-900 dark:bg-white text-white dark:text-black font-bold px-4 py-2 rounded text-sm hover:opacity-90 flex items-center gap-2"> |
| 530 | + {flagStatus === 'success' ? <Check size={16} className="text-green-500" /> : flagStatus === 'error' ? <AlertTriangle size={16} className="text-red-500" /> : <Send size={16} />} |
| 531 | + {flagStatus === 'success' ? "CAPTURED" : "Submit"} |
| 532 | + </button> |
| 533 | + </div> |
| 534 | + {flagStatus === 'success' && <p className="text-green-500 text-xs mt-2 font-bold animate-bounce">Access Granted! +{activeCTF.xpReward} XP</p>} |
| 535 | + {flagStatus === 'error' && <p className="text-red-500 text-xs mt-2 font-bold">Incorrect Flag. Try again.</p>} |
518 | 536 | </div> |
519 | 537 | </div> |
520 | 538 | </div> |
@@ -597,8 +615,15 @@ const App: React.FC = () => { |
597 | 615 | <div className="prose prose-sm max-w-none mb-6 break-words text-gray-700 dark:text-gray-400 leading-relaxed dark:prose-invert"> |
598 | 616 | <ReactMarkdown components={MarkdownComponents}>{activeCTF.description}</ReactMarkdown> |
599 | 617 | </div> |
600 | | - <div className="mb-6"> |
601 | | - <div className="flex gap-2"><input type="text" value={flagInput} onChange={(e) => setFlagInput(e.target.value)} placeholder="CIPHER-CTF{...}" className="flex-1 border rounded px-3 py-2 text-sm bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-white border-gray-300 dark:border-[#27272a] font-mono outline-none focus:border-brand" /><button onClick={handleFlagSubmit} className="bg-gray-900 dark:bg-white text-white dark:text-black px-4 rounded font-bold text-sm"><Send size={16}/></button></div> |
| 618 | + <div className="mb-6 p-4 rounded-lg bg-gray-50 dark:bg-[#050505] border border-gray-200 dark:border-[#27272a]"> |
| 619 | + <div className="flex gap-2 items-center"> |
| 620 | + <input type="text" value={flagInput} onChange={(e) => setFlagInput(e.target.value)} placeholder="CIPHER-CTF{...}" className={`flex-1 border rounded px-3 py-2 text-sm bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-white font-mono outline-none transition-all ${flagStatus === 'error' ? 'border-red-500 ring-1 ring-red-500' : 'border-gray-300 dark:border-[#27272a] focus:border-brand'}`} /> |
| 621 | + <button onClick={handleFlagSubmit} className="bg-gray-900 dark:bg-white text-white dark:text-black px-4 rounded font-bold text-sm h-[38px] flex items-center justify-center min-w-[50px]"> |
| 622 | + {flagStatus === 'success' ? <Check size={16} className="text-green-500" /> : <Send size={16} />} |
| 623 | + </button> |
| 624 | + </div> |
| 625 | + {flagStatus === 'success' && <p className="text-green-500 text-xs mt-2 font-bold text-center">Correct! +{activeCTF.xpReward} XP</p>} |
| 626 | + {flagStatus === 'error' && <p className="text-red-500 text-xs mt-2 font-bold text-center">Incorrect Flag</p>} |
602 | 627 | </div> |
603 | 628 | </div> |
604 | 629 | )} |
|
0 commit comments