From 88929a21a6cd146474070e00d16eac29b41c7a96 Mon Sep 17 00:00:00 2001 From: AQIB-NAWAB Date: Thu, 13 Mar 2025 20:15:35 +0500 Subject: [PATCH 1/6] add loader to the validate button --- app/components/CodeEditor/CodeEditor.tsx | 72 ++++++++++++------------ 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/app/components/CodeEditor/CodeEditor.tsx b/app/components/CodeEditor/CodeEditor.tsx index 5118db2..9c9a807 100644 --- a/app/components/CodeEditor/CodeEditor.tsx +++ b/app/components/CodeEditor/CodeEditor.tsx @@ -7,8 +7,6 @@ import Editor from "@monaco-editor/react"; import { Flex, useColorMode } from "@chakra-ui/react"; import { useEffect, useState, useRef } from "react"; import MyBtn from "../MyBtn"; -import { CodeFile, OutputResult } from "@/lib/types"; -import { OutputReducerAction } from "@/lib/reducers"; import { tryFormattingCode, validateCode } from "@/lib/client-functions"; import FiChevronRight from "@/app/styles/icons/HiChevronRightGreen"; import { useRouter } from "next/navigation"; @@ -24,22 +22,14 @@ export default function CodeEditor({ stepIndex, chapterIndex, outputResult, -}: { - codeString: string; - setCodeString: (codeString: string) => void; - codeFile: CodeFile; - dispatchOutput: React.Dispatch; - nextStepPath: string | undefined; - stepIndex: number; - chapterIndex: number; - outputResult: OutputResult; }) { const { colorMode } = useColorMode(); - const [monaco, setMonaco] = useState(null); + const [monaco, setMonaco] = useState(null); + const [isValidating, setIsValidating] = useState(false); const router = useRouter(); const editorStore = useEditorStore(); const userSolutionStore = useUserSolutionStore(); - const editorRef = useRef(null); + const editorRef = useRef(null); useEffect(() => { if (monaco) { @@ -54,26 +44,30 @@ export default function CodeEditor({ monaco.editor.setTheme(colorMode === "light" ? "light" : "my-theme"); } }, [monaco, colorMode]); + useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { + const handleKeyDown = (event) => { if (event.key == "Enter" && event.shiftKey) { sendGAEvent("event", "buttonClicked", { value: "Validate (through shortcut)", }); event.preventDefault(); - tryFormattingCode(editorRef, setCodeString); - validateCode( - codeString, - codeFile, - dispatchOutput, - stepIndex, - chapterIndex, - ); + setIsValidating(true); + setTimeout(() => { + tryFormattingCode(editorRef, setCodeString); + validateCode( + codeString, + codeFile, + dispatchOutput, + stepIndex, + chapterIndex, + ); + setIsValidating(false); + }, 500); } }; document.addEventListener("keydown", handleKeyDown); - return () => { document.removeEventListener("keydown", handleKeyDown); }; @@ -111,11 +105,10 @@ export default function CodeEditor({ defaultValue={codeString} theme={colorMode === "light" ? "light" : "my-theme"} value={codeString} - height={"100%"} + height="100%" onChange={(codeString) => setCodeString(codeString ?? "")} options={{ minimap: { enabled: false }, - fontSize: 14, formatOnPaste: true, formatOnType: true, @@ -129,24 +122,29 @@ export default function CodeEditor({ />
- + { - tryFormattingCode(editorRef, setCodeString); - validateCode( - codeString, - codeFile, - dispatchOutput, - stepIndex, - chapterIndex, - ); + onClick={() => { + setIsValidating(true); + setTimeout(() => { + tryFormattingCode(editorRef, setCodeString); + validateCode( + codeString, + codeFile, + dispatchOutput, + stepIndex, + chapterIndex, + ); + setIsValidating(false); + }, 500); }} + isDisabled={isValidating} variant={ outputResult.validityStatus === "valid" ? "success" : "default" } tooltip="Shift + Enter" > - Validate + {isValidating ? "Validating..." : "Validate"} Reset From 94774049365d914823b8cde0d2fcece70cb1c180 Mon Sep 17 00:00:00 2001 From: AQIB-NAWAB Date: Thu, 13 Mar 2025 21:11:43 +0500 Subject: [PATCH 2/6] add the types again and create handleValidateFunction --- app/components/CodeEditor/CodeEditor.tsx | 60 ++++++++++++------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/app/components/CodeEditor/CodeEditor.tsx b/app/components/CodeEditor/CodeEditor.tsx index 9c9a807..1ebfe5f 100644 --- a/app/components/CodeEditor/CodeEditor.tsx +++ b/app/components/CodeEditor/CodeEditor.tsx @@ -12,6 +12,8 @@ import FiChevronRight from "@/app/styles/icons/HiChevronRightGreen"; import { useRouter } from "next/navigation"; import { useUserSolutionStore, useEditorStore } from "@/lib/stores"; import { sendGAEvent } from "@next/third-parties/google"; +import { CodeFile, OutputResult } from "@/lib/types"; +import { OutputReducerAction } from "@/lib/reducers"; export default function CodeEditor({ codeString, @@ -22,14 +24,23 @@ export default function CodeEditor({ stepIndex, chapterIndex, outputResult, +}: { + codeString: string; + setCodeString: (codeString: string) => void; + codeFile: CodeFile; + dispatchOutput: React.Dispatch; + nextStepPath: string | undefined; + stepIndex: number; + chapterIndex: number; + outputResult: OutputResult; }) { const { colorMode } = useColorMode(); - const [monaco, setMonaco] = useState(null); + const [monaco, setMonaco] = useState(null); const [isValidating, setIsValidating] = useState(false); const router = useRouter(); const editorStore = useEditorStore(); const userSolutionStore = useUserSolutionStore(); - const editorRef = useRef(null); + const editorRef = useRef(null); useEffect(() => { if (monaco) { @@ -52,18 +63,7 @@ export default function CodeEditor({ value: "Validate (through shortcut)", }); event.preventDefault(); - setIsValidating(true); - setTimeout(() => { - tryFormattingCode(editorRef, setCodeString); - validateCode( - codeString, - codeFile, - dispatchOutput, - stepIndex, - chapterIndex, - ); - setIsValidating(false); - }, 500); + handleValidate(); } }; @@ -97,6 +97,21 @@ export default function CodeEditor({ } }, [userSolutionStore]); + const handleValidate = () => { + setIsValidating(true); + setTimeout(() => { + tryFormattingCode(editorRef, setCodeString); + validateCode( + codeString, + codeFile, + dispatchOutput, + stepIndex, + chapterIndex, + ); + setIsValidating(false); + }, 500); + }; + return ( <>
@@ -124,24 +139,11 @@ export default function CodeEditor({
{ - setIsValidating(true); - setTimeout(() => { - tryFormattingCode(editorRef, setCodeString); - validateCode( - codeString, - codeFile, - dispatchOutput, - stepIndex, - chapterIndex, - ); - setIsValidating(false); - }, 500); - }} - isDisabled={isValidating} + onClick={() => handleValidate()} variant={ outputResult.validityStatus === "valid" ? "success" : "default" } + isDisabled={isValidating} tooltip="Shift + Enter" > {isValidating ? "Validating..." : "Validate"} From c60c7961690379648a400e9e596eeeaa67e516e8 Mon Sep 17 00:00:00 2001 From: AQIB-NAWAB Date: Fri, 14 Mar 2025 21:04:57 +0500 Subject: [PATCH 3/6] add the get certificate button --- app/components/CodeEditor/CodeEditor.tsx | 231 +++++++++++++++-------- 1 file changed, 150 insertions(+), 81 deletions(-) diff --git a/app/components/CodeEditor/CodeEditor.tsx b/app/components/CodeEditor/CodeEditor.tsx index 1ebfe5f..85f3c4f 100644 --- a/app/components/CodeEditor/CodeEditor.tsx +++ b/app/components/CodeEditor/CodeEditor.tsx @@ -3,7 +3,7 @@ import styles from "./CodeEditor.module.css"; import ctx from "classnames"; import { GeistMono } from "geist/font/mono"; -import Editor from "@monaco-editor/react"; +import Editor, { Monaco } from "@monaco-editor/react"; import { Flex, useColorMode } from "@chakra-ui/react"; import { useEffect, useState, useRef } from "react"; import MyBtn from "../MyBtn"; @@ -14,34 +14,10 @@ import { useUserSolutionStore, useEditorStore } from "@/lib/stores"; import { sendGAEvent } from "@next/third-parties/google"; import { CodeFile, OutputResult } from "@/lib/types"; import { OutputReducerAction } from "@/lib/reducers"; +import CertificateButton from "../CertificateButton/CertificateButton"; -export default function CodeEditor({ - codeString, - setCodeString, - codeFile, - dispatchOutput, - nextStepPath, - stepIndex, - chapterIndex, - outputResult, -}: { - codeString: string; - setCodeString: (codeString: string) => void; - codeFile: CodeFile; - dispatchOutput: React.Dispatch; - nextStepPath: string | undefined; - stepIndex: number; - chapterIndex: number; - outputResult: OutputResult; -}) { - const { colorMode } = useColorMode(); - const [monaco, setMonaco] = useState(null); - const [isValidating, setIsValidating] = useState(false); - const router = useRouter(); - const editorStore = useEditorStore(); - const userSolutionStore = useUserSolutionStore(); - const editorRef = useRef(null); - +// Custom hook for editor theme setup +const useEditorTheme = (monaco: Monaco, colorMode: "dark" | "light") => { useEffect(() => { if (monaco) { monaco.editor.defineTheme("my-theme", { @@ -55,10 +31,16 @@ export default function CodeEditor({ monaco.editor.setTheme(colorMode === "light" ? "light" : "my-theme"); } }, [monaco, colorMode]); +}; +// Custom hook for keyboard shortcuts +const useValidationShortcut = ( + handleValidate: () => void, + codeString: string, +) => { useEffect(() => { - const handleKeyDown = (event) => { - if (event.key == "Enter" && event.shiftKey) { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter" && event.shiftKey) { sendGAEvent("event", "buttonClicked", { value: "Validate (through shortcut)", }); @@ -71,8 +53,20 @@ export default function CodeEditor({ return () => { document.removeEventListener("keydown", handleKeyDown); }; - }, [codeString]); + }, [handleValidate, codeString]); +}; +// Custom hook for code persistence +const useCodePersistence = ( + chapterIndex: number, + stepIndex: number, + codeString: string, + setCodeString: (value: string) => void, + codeFile: CodeFile, +) => { + const userSolutionStore = useUserSolutionStore(); + + // Load saved code useEffect(() => { const savedCode = userSolutionStore.getSavedUserSolutionByLesson( chapterIndex, @@ -83,6 +77,7 @@ export default function CodeEditor({ } }, [chapterIndex, stepIndex]); + // Save code changes useEffect(() => { userSolutionStore.saveUserSolutionForLesson( chapterIndex, @@ -91,11 +86,104 @@ export default function CodeEditor({ ); }, [codeString, chapterIndex, stepIndex]); + // Initialize code if no saved solutions useEffect(() => { - if (Object.keys(userSolutionStore.userSolutionsByLesson).length == 0) { + if (Object.keys(userSolutionStore.userSolutionsByLesson).length === 0) { setCodeString(JSON.stringify(codeFile.code, null, 2)); } }, [userSolutionStore]); +}; + +// EditorControls component for the buttons section +const EditorControls = ({ + handleValidate, + isValidating, + resetCode, + nextStepPath, + outputResult, +}: { + handleValidate: () => void; + isValidating: boolean; + resetCode: () => void; + nextStepPath: string | undefined; + outputResult: OutputResult; +}) => { + const router = useRouter(); + + return ( +
+ + + {isValidating ? "Validating ..." : "Validate"} + + + + Reset + + + {nextStepPath ? ( + <> + { + if (nextStepPath) router.push("/" + nextStepPath); + }} + variant={ + outputResult.validityStatus === "valid" ? "default" : "success" + } + isDisabled={!!isValidating} + size={outputResult.validityStatus === "valid" ? "sm" : "xs"} + > + Next + + + + ) : ( + + )} +
+ ); +}; + +export default function CodeEditor({ + codeString, + setCodeString, + codeFile, + dispatchOutput, + nextStepPath, + stepIndex, + chapterIndex, + outputResult, +}: { + codeString: string; + setCodeString: (codeString: string) => void; + codeFile: CodeFile; + dispatchOutput: React.Dispatch; + nextStepPath: string | undefined; + stepIndex: number; + chapterIndex: number; + outputResult: OutputResult; +}) { + const { colorMode } = useColorMode(); + const [monaco, setMonaco] = useState(null); + const [isValidating, setIsValidating] = useState(false); + const editorStore = useEditorStore(); + const editorRef = useRef(null); + + // Apply custom hooks + useEditorTheme(monaco, colorMode); const handleValidate = () => { setIsValidating(true); @@ -112,6 +200,28 @@ export default function CodeEditor({ }, 500); }; + useValidationShortcut(handleValidate, codeString); + useCodePersistence( + chapterIndex, + stepIndex, + codeString, + setCodeString, + codeFile, + ); + + const resetCode = () => { + setCodeString(JSON.stringify(codeFile.code, null, 2)); + dispatchOutput({ type: "RESET" }); + }; + + const handleEditorMount = (editor: any, monaco: Monaco) => { + setMonaco(monaco); + + editorRef.current = editor; + editorStore.setEditor(editor); + editorStore.setMonaco(monaco); + }; + return ( <>
@@ -128,57 +238,16 @@ export default function CodeEditor({ formatOnPaste: true, formatOnType: true, }} - onMount={(editor, monaco) => { - setMonaco(monaco); - editorRef.current = editor; - editorStore.setEditor(editor); - editorStore.setMonaco(monaco); - }} + onMount={handleEditorMount} />
-
- - handleValidate()} - variant={ - outputResult.validityStatus === "valid" ? "success" : "default" - } - isDisabled={isValidating} - tooltip="Shift + Enter" - > - {isValidating ? "Validating..." : "Validate"} - - - { - setCodeString(JSON.stringify(codeFile.code, null, 2)); - dispatchOutput({ type: "RESET" }); - }} - variant="error" - > - Reset - - - { - if (nextStepPath) router.push("/" + nextStepPath); - }} - variant={ - outputResult.validityStatus === "valid" ? "default" : "success" - } - isDisabled={!nextStepPath} - size={outputResult.validityStatus === "valid" ? "sm" : "xs"} - > - Next - - -
+ ); } From 53a35a8c49899e7256c6653b2e141aa180a4be57 Mon Sep 17 00:00:00 2001 From: AQIB-NAWAB Date: Fri, 14 Mar 2025 21:13:02 +0500 Subject: [PATCH 4/6] resolve the mergre conficts --- app/components/CodeEditor/CodeEditor.tsx | 49 ++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/app/components/CodeEditor/CodeEditor.tsx b/app/components/CodeEditor/CodeEditor.tsx index 85f3c4f..e447874 100644 --- a/app/components/CodeEditor/CodeEditor.tsx +++ b/app/components/CodeEditor/CodeEditor.tsx @@ -128,31 +128,32 @@ const EditorControls = ({ Reset
- {nextStepPath ? ( - <> + { + nextStepPath?( + <> { - if (nextStepPath) router.push("/" + nextStepPath); - }} - variant={ - outputResult.validityStatus === "valid" ? "default" : "success" - } - isDisabled={!!isValidating} - size={outputResult.validityStatus === "valid" ? "sm" : "xs"} - > - Next - - - - ) : ( - - )} + onClick={() => { + if (nextStepPath) router.push("/" + nextStepPath); + }} + variant={ + outputResult.validityStatus === "valid" ? "default" : "success" + } + isDisabled={!nextStepPath} + size={outputResult.validityStatus === "valid" ? "sm" : "xs"} + > + Next + + + + ): + + }
); }; From 3673524906764eca4404edd30b060224a7afb891 Mon Sep 17 00:00:00 2001 From: AQIB-NAWAB Date: Fri, 14 Mar 2025 21:13:53 +0500 Subject: [PATCH 5/6] refactor the code --- app/components/CodeEditor/CodeEditor.tsx | 46 +++++++++++------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/app/components/CodeEditor/CodeEditor.tsx b/app/components/CodeEditor/CodeEditor.tsx index e447874..371ea5c 100644 --- a/app/components/CodeEditor/CodeEditor.tsx +++ b/app/components/CodeEditor/CodeEditor.tsx @@ -128,32 +128,28 @@ const EditorControls = ({ Reset - { - nextStepPath?( - <> + {nextStepPath ? ( + <> { - if (nextStepPath) router.push("/" + nextStepPath); - }} - variant={ - outputResult.validityStatus === "valid" ? "default" : "success" - } - isDisabled={!nextStepPath} - size={outputResult.validityStatus === "valid" ? "sm" : "xs"} - > - Next - - - - ): - - } + onClick={() => router.push("/" + nextStepPath)} + variant={ + outputResult.validityStatus === "valid" ? "default" : "success" + } + size={outputResult.validityStatus === "valid" ? "sm" : "xs"} + > + Next + + + + ) : ( + + )}
); }; From d1b03b15e2615dd401b12de2f5bfa8336a7e954d Mon Sep 17 00:00:00 2001 From: AQIB-NAWAB Date: Sun, 16 Mar 2025 10:30:54 +0500 Subject: [PATCH 6/6] add aditional check to validate that current lesson is last lesson --- app/components/CodeEditor/CodeEditor.tsx | 47 ++++++++++++++---------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/app/components/CodeEditor/CodeEditor.tsx b/app/components/CodeEditor/CodeEditor.tsx index 371ea5c..2623760 100644 --- a/app/components/CodeEditor/CodeEditor.tsx +++ b/app/components/CodeEditor/CodeEditor.tsx @@ -7,7 +7,11 @@ import Editor, { Monaco } from "@monaco-editor/react"; import { Flex, useColorMode } from "@chakra-ui/react"; import { useEffect, useState, useRef } from "react"; import MyBtn from "../MyBtn"; -import { tryFormattingCode, validateCode } from "@/lib/client-functions"; +import { + isTheTourCompleted, + tryFormattingCode, + validateCode, +} from "@/lib/client-functions"; import FiChevronRight from "@/app/styles/icons/HiChevronRightGreen"; import { useRouter } from "next/navigation"; import { useUserSolutionStore, useEditorStore } from "@/lib/stores"; @@ -109,6 +113,11 @@ const EditorControls = ({ outputResult: OutputResult; }) => { const router = useRouter(); + const [isTheTourCompletedState, setIsTheTourCompletedState] = + useState(isTheTourCompleted()); + useEffect(() => { + setIsTheTourCompletedState(isTheTourCompleted()); + }, [isTheTourCompleted()]); return (
@@ -128,27 +137,27 @@ const EditorControls = ({ Reset - {nextStepPath ? ( + {!nextStepPath || isTheTourCompletedState ? ( <> - router.push("/" + nextStepPath)} - variant={ - outputResult.validityStatus === "valid" ? "default" : "success" - } - size={outputResult.validityStatus === "valid" ? "sm" : "xs"} - > - Next - - + ) : ( - + router.push("/" + nextStepPath)} + variant={ + outputResult.validityStatus === "valid" ? "default" : "success" + } + size={outputResult.validityStatus === "valid" ? "sm" : "xs"} + > + Next + + )}
);