diff --git a/next.config.js b/next.config.js index 7fd39d2f..a89afb9b 100644 --- a/next.config.js +++ b/next.config.js @@ -6,25 +6,32 @@ const withPWA = require('next-pwa')({ /** @type {import('next').NextConfig} */ const nextConfig = { - webpack: (config, { dev }) => { - if (!dev) { - config.plugins = config.plugins.filter( - (plugin) => plugin.constructor.name !== 'ESLintWebpackPlugin' - ); - } - - return config; - }, reactStrictMode: false, eslint: { ignoreDuringBuilds: true, }, experimental: { scrollRestoration: true, - esmExternals: true, // Add this to ensure esmExternals is true + esmExternals: 'loose', + }, + transpilePackages: ['@uiw/react-md-editor', '@uiw/react-markdown-preview'], + async headers() { + return [ + { + source: '/:path*', + headers: [ + { + key: 'Cross-Origin-Embedder-Policy', + value: 'require-corp', + }, + { + key: 'Cross-Origin-Opener-Policy', + value: 'same-origin', + }, + ], + }, + ]; }, - ignoreDuringBuilds: true, - transpilePackages: ['react-md-editor'] }; module.exports = withPWA(removeImports(nextConfig)); \ No newline at end of file diff --git a/package.json b/package.json index 684e21e6..55576fc9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^1.7.2", "@heroicons/react": "^2.0.13", + "@monaco-editor/react": "^4.6.0", "@mui/material": "^5.15.2", "@mui/x-charts": "^6.18.4", "@react-oauth/google": "^0.12.1", @@ -26,8 +27,10 @@ "@tailwindcss/forms": "^0.5.3", "@tremor/react": "^1.8.1", "@uiw/react-markdown-editor": "^6.1.1", - "@uiw/react-md-editor": "3.6.0", + "@uiw/react-markdown-preview": "^5.1.3", + "@uiw/react-md-editor": "^4.0.5", "@vercel/analytics": "^1.3.1", + "@webcontainer/api": "^1.5.1-internal.5", "asciinema-player": "3.6.3", "autoprefixer": "^10.4.12", "babel-plugin-macros": "^3.1.0", @@ -39,7 +42,7 @@ "easymde": "^2.18.0", "enable": "^3.4.0", "focus-visible": "^5.2.0", - "framer-motion": "^10.2.4", + "framer-motion": "^11.15.0", "heroicons": "^2.0.13", "jwt-decode": "^4.0.0", "lucide-react": "^0.456.0", @@ -53,10 +56,11 @@ "puppeteer": "^22.0.0", "react": "18.2.0", "react-activity-calendar": "^2.2.11", + "react-beautiful-dnd": "^13.1.1", "react-chartjs-2": "^5.2.0", "react-circular-progressbar": "^2.1.0", "react-collapsible": "^2.10.0", - "react-confetti": "^6.1.0", + "react-confetti": "^6.2.2", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "18.2.0", @@ -81,6 +85,7 @@ "react-tooltip": "^5.25.1", "react-top-loading-bar": "^2.3.1", "react-transition-group": "^4.4.5", + "react-use": "^17.6.0", "react-visibility-sensor": "^5.1.1", "reactjs-popup": "^2.0.5", "reactour": "^1.19.4", @@ -92,7 +97,8 @@ "tailwind-merge": "^2.3.0", "tailwindcss": "^3.2.1", "tailwindcss-animate": "^1.0.7", - "xterm": "^5.3.0" + "xterm": "^5.3.0", + "xterm-addon-fit": "^0.8.0" }, "devDependencies": { "eslint": "8.26.0", diff --git a/public/loader.webp b/public/loader.webp new file mode 100644 index 00000000..0dd8747d Binary files /dev/null and b/public/loader.webp differ diff --git a/public/whereami.jpeg b/public/whereami.jpeg new file mode 100644 index 00000000..0b8884b9 Binary files /dev/null and b/public/whereami.jpeg differ diff --git a/src/components/StandardNav.jsx b/src/components/StandardNav.jsx index 3b7f37e5..93ab8f7d 100644 --- a/src/components/StandardNav.jsx +++ b/src/components/StandardNav.jsx @@ -314,6 +314,16 @@ export function StandardNav({ guestAllowed, alignCenter = true }) {
+ + Learn + + New! + Dashboard + + Learn + { - const fetchData = async () => { - const response = await fetch(url); - const data = await response.text(); - setMarkdown(data); - }; - fetchData(); - }, [url]); - - return {markdown}; -} diff --git a/src/components/learn/LearnNav.jsx b/src/components/learn/LearnNav.jsx deleted file mode 100644 index d1b1a5ff..00000000 --- a/src/components/learn/LearnNav.jsx +++ /dev/null @@ -1,114 +0,0 @@ -import { DonutChart } from '@tremor/react'; -import Link from 'next/link'; -import { useState, useEffect } from 'react'; - -import request from '@/utils/request'; - -export function LearnNav({ navElements, lessonNum }) { - const [lessonProgress, setLessonProgress] = useState(null); - - useEffect(() => { - const url = `${process.env.NEXT_PUBLIC_API_URL}/lessons/${lessonNum}/progress`; - request(url, 'GET', null) - .then((data) => { - console.log(data); - setLessonProgress(data); - }) - .catch((err) => { - console.log(err); - }); - }, []); - - const progressArray = []; - const colorArray = []; - if (lessonProgress?.sublessons) { - for (let i = 0; i < lessonProgress?.sublessons.length; i++) { - progressArray.push({ - name: `Section ${i + 1}`, - progress: - lessonProgress.sublessons[i].progresses.length != 0 - ? parseInt(lessonProgress.sublessons[i].progresses[0]?.progress) - : 0, - }); - colorArray.push('blue'); - progressArray.push({ - name: `Section ${i + 1}`, - progress: - lessonProgress.sublessons[i].progresses.length != 0 - ? 100 - - parseInt(lessonProgress.sublessons[i].progresses[0]?.progress) - : 100, - }); - colorArray.push('stone'); - } - } - - return ( - <> -
-
    -
    - -

    - - % -

    -

    Lesson Progress

    - {/***/} -
    -
  • - - - {navElements[0].title} - -
  • -
  • - - - {navElements[1].title} - -
  • -
  • - - - {navElements[2].title} - -
  • -
  • - - - {navElements[3].title} - -
  • -
-
- - ); -} diff --git a/src/components/learn/LearningModule.jsx b/src/components/learn/LearningModule.jsx deleted file mode 100644 index 374ba907..00000000 --- a/src/components/learn/LearningModule.jsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useState, useEffect } from 'react'; -import { ProgressBar } from '@tremor/react'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import request from '@/utils/request'; - -export function LearningModule({ - lessonId, - title, - sections, - sectionHrefs, - imgSrc, - link, -}) { - const [lessonProgress, setLessonProgress] = useState(null); - const router = useRouter(); - - useEffect(() => { - const url = `${process.env.NEXT_PUBLIC_API_URL}/lessons/${lessonId}/progress`; - request(url, 'GET', null) - .then((data) => { - setLessonProgress(data); - }) - .catch((err) => { - console.log(err); - }); - }, []); - - return ( - <> -
- -

- {title} - - {lessonProgress ? lessonProgress.totalProgress : 0}% - -

-
- -
-

- {sections[0]} - { - router.push(sectionHrefs[0]); - }} - className="ml-auto cursor-pointer text-blue-500 hover:text-blue-600" - > - View Content → - -

-

- {sections[1]} - { - router.push(sectionHrefs[1]); - }} - className="ml-auto cursor-pointer text-blue-500 hover:text-blue-600" - > - View Content → - -

-

- {sections[2]} - { - router.push(sectionHrefs[2]); - }} - className="ml-auto cursor-pointer text-blue-500 hover:text-blue-600" - > - Start Task → - -

-

- {sections[3]} - { - router.push(sectionHrefs[3]); - }} - className="ml-auto cursor-pointer text-blue-500 hover:text-blue-600" - > - Start Task → - -

-
-
-
- - ); -} diff --git a/src/components/learn/LessonNavbar.js b/src/components/learn/LessonNavbar.js new file mode 100644 index 00000000..40213009 --- /dev/null +++ b/src/components/learn/LessonNavbar.js @@ -0,0 +1,49 @@ +import { useState } from 'react'; +import { useRouter } from 'next/router'; + +export default function LessonNavbar({ lessonId }) { + const [isImporting, setIsImporting] = useState(false); + const router = useRouter(); + + const handleImport = async () => { + setIsImporting(true); + try { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/lessons/import/${lessonId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) throw new Error('Import failed'); + + alert('Lesson imported successfully!'); + } catch (error) { + console.error('Error importing lesson:', error); + alert('Failed to import lesson'); + } finally { + setIsImporting(false); + } + }; + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/learn/MarkDone.jsx b/src/components/learn/MarkDone.jsx deleted file mode 100644 index 3c95b1f0..00000000 --- a/src/components/learn/MarkDone.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useState, useEffect } from 'react'; -import Link from 'next/link'; -import request from '@/utils/request'; - -export function MarkDone({ sublesson, section, href }) { - const [marked, setMarked] = useState(false); - const [showPopup, setShowPopup] = useState(false); - const [sfx, setSfx] = useState(null); - - useEffect(() => { - setSfx(new Audio('../sounds/success.wav')); - }, []); - - const handleSubmit = () => { - if (sfx) { - sfx.currentTime = 0; // Reset the audio to the beginning - sfx.play(); // Play the audio - } - - // Mark Progress - const url = `${process.env.NEXT_PUBLIC_API_URL}/lessons/sublesson/${sublesson}/progress/${section}`; - request(url, 'PUT', {}) - .then((data) => { - setMarked(true); - setShowPopup(true); - setTimeout(() => setShowPopup(false), 4000); - }) - .catch((err) => { - // Trigger Unauthenticated Popup - }); - }; - - return ( - <> - - - - {showPopup && ( -
- Progress Saved! -
- )} - {marked &&

} - - ); -} diff --git a/src/components/learn/Quiz.jsx b/src/components/learn/Quiz.jsx deleted file mode 100644 index bdd62c9b..00000000 --- a/src/components/learn/Quiz.jsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useState } from 'react'; -import request from '@/utils/request'; - -export function Quiz({ page, sublesson, quizData }) { - const [selectedAnswer, setSelectedAnswer] = useState(null); - const [showPopup, setShowPopup] = useState(false); - const [showError, setErrorPopup] = useState(false); - - const handleAnswerSelection = (event) => { - setSelectedAnswer(event.target.value); - }; - - const handleSubmit = (event) => { - event.preventDefault(); - - const solution = quizData ? quizData[page - 1].solution : ''; - const isCorrect = selectedAnswer === solution; - - if (isCorrect) { - console.log('Submission correct!'); - setShowPopup(true); - setTimeout(() => setShowPopup(false), 4000); - - const url = `${process.env.NEXT_PUBLIC_API_URL}/lessons/sublesson/${sublesson}/progress/${page}`; - request(url, 'PUT', {}) - .catch((err) => { - // Trigger Unauthenticated Popup - }); - } else { - console.log('Submission incorrect!'); - setErrorPopup(true); - setTimeout(() => setErrorPopup(false), 4000); - } - }; - - const { question, answers } = quizData - ? quizData[page - 1] - : { question: '', answers: [''] }; - - return ( -
-

- Question {page}: -

-

- {question} -

-
- {answers.map((answer, index) => ( -
- - -
- ))} - -
- {showPopup && ( -
- Correct! -
- )} - {showError && ( -
- Incorrect! -
- )} -
- ); -} diff --git a/src/components/learn/QuizPage.jsx b/src/components/learn/QuizPage.jsx deleted file mode 100644 index ff399791..00000000 --- a/src/components/learn/QuizPage.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Quiz } from './Quiz'; -import { SectionsNav } from './SectionsNav'; -import { useRouter } from 'next/router'; -import Link from 'next/link'; -import { motion } from 'framer-motion'; - -function QuizPage({ totalQuizPages, sublesson, quizData, nextPage }) { - const router = useRouter(); - const [page, setPage] = useState(1); - - // Update page state when query param changes - useEffect(() => { - const queryPage = parseInt(router.query.quizPage); - if (queryPage && !isNaN(queryPage)) { - setPage(queryPage); - } - }, [router.query.quizPage]); - - const handleNext = () => { - setPage(page + 1); - }; - - const handlePrev = () => { - setPage(page - 1); - }; - - const pagePercentage = parseInt(100 / totalQuizPages); - - return ( -
- - -
- {[...Array(totalQuizPages)].map( - (_, index) => - page === index + 1 && ( - - ) - )} -
-
- {page > 1 && ( - - )} - {page < totalQuizPages && ( - - )} - {page === totalQuizPages && ( - - - - )} -
-
- ); -} - -export default QuizPage; diff --git a/src/components/learn/SectionsNav.jsx b/src/components/learn/SectionsNav.jsx deleted file mode 100644 index c27f0d9a..00000000 --- a/src/components/learn/SectionsNav.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useEffect, useState } from 'react'; -import { ProgressBar } from '@tremor/react'; -import request from '@/utils/request'; - -export function SectionsNav({ currentPage, cpv, colors, sublesson }) { - const [lessonProgress, setLessonProgress] = useState(null); - - useEffect(() => { - const url = `${process.env.NEXT_PUBLIC_API_URL}/lessons/sublesson/${sublesson}/progress`; - request(url, 'GET', null) - .then((data) => { - console.log(data); - setLessonProgress(data); - }) - .catch((err) => { - console.log(err); - }); - }, []); - - console.log(lessonProgress); - if (lessonProgress?.completion) { - for (let i = 0; i < lessonProgress.completion.length; i++) { - if (lessonProgress[i]) { - colors[i] = 'green'; - } - } - // colors[currentPage - 1] = "blue"; - } - - return ( - <> -
-
-

- Section Progress -

- -
-
- - ); -} diff --git a/src/components/learn/editor/StudioEditor.jsx b/src/components/learn/editor/StudioEditor.jsx new file mode 100644 index 00000000..aa11859f --- /dev/null +++ b/src/components/learn/editor/StudioEditor.jsx @@ -0,0 +1,1405 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; +import dynamic from 'next/dynamic'; +import { MarkdownViewer } from '@/components/MarkdownViewer'; +import { motion, AnimatePresence } from 'framer-motion'; +import request from '../../../utils/request'; + +// Dynamic import of MonacoEditor +const MonacoEditor = dynamic(() => import('@monaco-editor/react'), { + ssr: false +}); + +const LANGUAGE_CONFIG = { + python: { + icon: 'fab fa-python', + name: 'Python', + monacoLang: 'python' + }, + javascript: { + icon: 'fab fa-js', + name: 'JavaScript', + monacoLang: 'javascript' + }, + cpp: { + icon: 'fas fa-code', + name: 'C++', + monacoLang: 'cpp' + }, + bash: { + icon: 'fas fa-terminal', + name: 'Bash', + monacoLang: 'shell' + }, + shell: { + icon: 'fas fa-terminal', + name: 'Shell Script', + monacoLang: 'shell' + } +}; + +const TUTORIAL_STEPS = [ + { + id: 1, + target: 'editor-container', + content: 'Welcome to the Studio Editor! Right-click anywhere to start adding components.', + position: 'center' + }, + { + id: 2, + target: 'markdown-component', + content: 'Add Markdown components to write formatted text, documentation, or instructions.', + position: 'bottom' + }, + { + id: 3, + target: 'multiple-choice', + content: 'Create multiple choice questions to test knowledge. Don\'t forget to set the correct answer!', + position: 'bottom' + }, + { + id: 4, + target: 'code-component', + content: 'Add code components to write examples or create coding challenges.', + position: 'bottom' + }, + { + id: 5, + target: 'drag-handle', + content: 'Drag components to reorder them. Build your content in any order you like!', + position: 'right' + } +]; + +const getIconForType = (type) => { + switch (type) { + case 'markdown': + return 'markdown'; + case 'multiple-choice': + return 'list-ul'; + case 'code': + return 'code'; + default: + return 'plus'; + } +}; + +const ErrorModal = ({ isOpen, onClose, error }) => ( +
+
e.stopPropagation()} + > +
+ +

Error Saving Lesson

+
+

+ {error || 'An unexpected error occurred while saving. Please try again.'} +

+
+ +
+
+
+); + +// Add this Toast component near the top of the file, after other component imports +const Toast = ({ message, type = 'success', onClose }) => { + // Add useEffect for auto-dismiss + useEffect(() => { + const timer = setTimeout(() => { + onClose(); + }, 2000); + + // Cleanup timer + return () => clearTimeout(timer); + }, [onClose]); + + return ( + + + {message} + + + ); +}; + +const StudioEditor = ({ initialLesson = null, onLessonCreated }) => { + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(null); + const [toast, setToast] = useState(null); + const [pages, setPages] = useState(() => { + if (initialLesson && initialLesson.content) { + try { + // Parse the content directly from initialLesson.content + const parsedPages = JSON.parse(initialLesson.content); + console.log('Parsed pages from content:', parsedPages); + return parsedPages; + } catch (error) { + console.error('Error parsing lesson content:', error); + return [{ id: Date.now(), title: 'Page 1', components: [] }]; + } + } + return [{ id: Date.now(), title: 'Page 1', components: [] }]; + }); + + // Remove the window.alert that was showing the content + useEffect(() => { + console.log('StudioEditor initialLesson:', initialLesson); + console.log('StudioEditor initial pages:', pages); + }, [initialLesson]); + + // Log whenever pages change + useEffect(() => { + console.log('Current pages state:', pages); + }, [pages]); + + // Initialize pages state + const [currentPageId, setCurrentPageId] = useState(() => pages[0]?.id || null); + + // Find current page and its components + const currentPage = pages.find(p => p.id === currentPageId) || pages[0]; + const currentComponents = currentPage?.components || []; + + const [showPageModal, setShowPageModal] = useState(false); + const [newPageTitle, setNewPageTitle] = useState(''); + + const [menuVisible, setMenuVisible] = useState(false); + const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); + const [showTutorial, setShowTutorial] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [hasSeenTutorial, setHasSeenTutorial] = useState(false); + const [showJsonModal, setShowJsonModal] = useState(false); + const [showLoadJsonModal, setShowLoadJsonModal] = useState(false); + const [jsonInput, setJsonInput] = useState(''); + const [jsonError, setJsonError] = useState(''); + const [showImportProjectModal, setShowImportProjectModal] = useState(false); + const [projectJsonInput, setProjectJsonInput] = useState(''); + const [importError, setImportError] = useState(''); + const [isLoading, setIsLoading] = useState(true); + const [saveError, setSaveError] = useState(null); + + // Log the initial data + useEffect(() => { + // window.alert(JSON.stringify(initialLesson.content)); + console.log('StudioEditor initialLesson:', initialLesson); + console.log('StudioEditor initial pages:', pages); + }, [initialLesson]); + + const handleStartFresh = () => { + setIsLoading(false); + }; + + const handleInitialImport = () => { + try { + const parsedJson = JSON.parse(projectJsonInput); + if (!parsedJson.pages || !Array.isArray(parsedJson.pages)) { + throw new Error('Invalid project format: missing pages array'); + } + + setPages(parsedJson.pages); + setCurrentPageId(parsedJson.pages[0]?.id || null); + setIsLoading(false); + setProjectJsonInput(''); + setImportError(''); + } catch (error) { + setImportError(error.message); + } + }; + + useEffect(() => { + // Check if user has seen tutorial before + const tutorialSeen = localStorage.getItem('studioEditorTutorialSeen'); + if (!tutorialSeen) { + setShowTutorial(true); + localStorage.setItem('studioEditorTutorialSeen', 'true'); + } + }, []); + + const nextTutorialStep = () => { + if (currentStep < TUTORIAL_STEPS.length - 1) { + setCurrentStep(currentStep + 1); + } else { + setShowTutorial(false); + setHasSeenTutorial(true); + } + }; + + const skipTutorial = () => { + setShowTutorial(false); + setHasSeenTutorial(true); + }; + + // Handle right-click to show component menu + const handleContextMenu = (e) => { + e.preventDefault(); + setMenuPosition({ x: e.clientX, y: e.clientY }); + setMenuVisible(true); + }; + + // Handle click outside to close menu + useEffect(() => { + const handleClickOutside = () => setMenuVisible(false); + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }, []); + + // Handle adding new components + const handleAddComponent = (type) => { + const newComponent = { + id: Date.now(), + type, + content: '', + config: getDefaultConfig(type) + }; + + setPages(prevPages => prevPages.map(page => { + if (page.id === currentPageId) { + return { + ...page, + components: [...page.components, newComponent] + }; + } + return page; + })); + setMenuVisible(false); + }; + + // Get default configuration based on component type + const getDefaultConfig = (type) => { + switch (type) { + case 'markdown': + return { preview: false }; + case 'multiple-choice': + return { options: [], correctAnswer: null }; + case 'code': + return { language: 'python', testCases: [] }; + default: + return {}; + } + }; + + // Handle updating component content + const handleUpdateComponent = (id, updates) => { + setPages(prevPages => prevPages.map(page => { + if (page.id === currentPageId) { + return { + ...page, + components: page.components.map(comp => + comp.id === id ? { ...comp, ...updates } : comp + ) + }; + } + return page; + })); + }; + + const insertText = (id, text) => { + const component = currentComponents.find(comp => comp.id === id); + if (!component) return; + + const textarea = document.querySelector(`textarea[data-id="${id}"]`); + if (!textarea) return; + + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const currentContent = component.content || ''; + const newContent = currentContent.substring(0, start) + text + currentContent.substring(end); + + handleUpdateComponent(id, { content: newContent }); + }; + + // Handle deleting components + const handleDeleteComponent = (id) => { + setPages(prevPages => prevPages.map(page => { + return { + ...page, + components: page.components.filter(comp => comp.id !== id) + }; + })); + }; + + // Handle drag and drop reordering + const onDragEnd = (result) => { + if (!result.destination) return; + + const items = Array.from(currentComponents); + const [reorderedItem] = items.splice(result.source.index, 1); + items.splice(result.destination.index, 0, reorderedItem); + + setPages(prevPages => prevPages.map(page => { + if (page.id === currentPageId) { + return { + ...page, + components: items + }; + } + return page; + })); + }; + + // Render different component types + const renderComponent = (component) => { + switch (component.type) { + case 'markdown': + return ( + +
+
+ +

Markdown Editor

+
+
+ {['bold', 'italic', 'heading', 'link', 'code'].map((tool) => ( + insertText(component.id, getToolMarkdown(tool))} + className="hover:bg-white/10 p-1.5 rounded transition-all group" + > + + + ))} +
+
+
e.stopPropagation()}> +
+
+ */} - - {/*

External Testing Client

*/} - {/* */} - {/*
*/} - -
-
-
- ); -} diff --git a/src/pages/learn/ch1/preview.jsx b/src/pages/learn/ch1/preview.jsx deleted file mode 100644 index 5c3c118f..00000000 --- a/src/pages/learn/ch1/preview.jsx +++ /dev/null @@ -1,145 +0,0 @@ -import Head from 'next/head'; - -import { Footer } from '@/components/Footer'; - -import { StandardNav } from '@/components/StandardNav'; -import { LearnNav } from '@/components/learn/LearnNav'; -import { MarkDone } from '@/components/learn/MarkDone'; -import { motion } from 'framer-motion'; -import { useState, useEffect, Fragment } from 'react'; -import { Disclosure, Menu, Transition, Dialog } from '@headlessui/react'; - - -import { useRouter } from 'next/router'; - - - -export default function Dashboard() { - const [open, setOpen] = useState(false); - const router = useRouter(); - - const [markdown, setMarkdown] = useState(''); - - return ( - <> - - Learn - CTFGuide - - - - - - - - -
- - console.log("hello") -
-
-
-

- Linux Basics -

-
-
-
- {/* Sidebar */} - - - {/* Main content area */} -
- {/* Load in markdown from a github url */} - -
- - What is Linux - -
- - - - - - - -
-
- -
-
-

A brief introduction...

- Linux is a free and{' '} - setOpen(true)}> - open source - {' '} - operating system. It is developed by Linus Torvalds, a programmer - and the creator of the Linux operating system. Development of - Linux originially started in 1991 and has quickly grown to be one - of the most popular operating systems in the world.

-

The syntax of Linux commands are very similar to that of - Unix. You'll find that in the tech world, that the Linux operating - system is the most popular choice for servers. Although, Windows - and MacOS can still be used as servers. A plethora of - cybersecurity tools are generally designed for Linux. There are - many Linux Distributions that you can use, the most popular one - for cybersecurity is Kali Linux. It's advised that if you're new - to Linux you use a distribution like Ubuntu. -
-
- -

Picture of Linus Torwalds, the creator of Linux

-
-
- -

- Understanding the Linux architecture. -

- Linux architecture is based on a modular approach. The kernel, - which is the core of the operating system, manages the resources - of the computer and provides services to applications. The shell - is a command line interpreter that allows users to interact with - the kernel. The hardware consists of the physical components of - the computer, such as the processor, memory, and storage devices. - The utilities are programs that provide functions that are not - directly related to the operation of the computer, such as text - editors and file managers. - - -
-
-
-
- -