Skip to content

Commit 73422f4

Browse files
Add Loader to validate button (#144)
* add loader to the validate button * add the types again and create handleValidateFunction * add the type for event * resolved the branch issue * refactor CodeEditor component by extracting custom hooks and creating EditorControls for better readability and maintainability * fix: correct spacing in validation button text --------- Co-authored-by: JeelRajodiya <jeelrajodiyajeel@gmail.com>
1 parent 7098eec commit 73422f4

File tree

1 file changed

+162
-100
lines changed

1 file changed

+162
-100
lines changed

app/components/CodeEditor/CodeEditor.tsx

Lines changed: 162 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,20 @@
33
import styles from "./CodeEditor.module.css";
44
import ctx from "classnames";
55
import { GeistMono } from "geist/font/mono";
6-
import Editor from "@monaco-editor/react";
6+
import Editor, { Monaco } from "@monaco-editor/react";
77
import { Flex, useColorMode } from "@chakra-ui/react";
88
import { useEffect, useState, useRef } from "react";
99
import MyBtn from "../MyBtn";
10-
import { CodeFile, OutputResult } from "@/lib/types";
11-
import { OutputReducerAction } from "@/lib/reducers";
1210
import { tryFormattingCode, validateCode } from "@/lib/client-functions";
1311
import FiChevronRight from "@/app/styles/icons/HiChevronRightGreen";
1412
import { useRouter } from "next/navigation";
1513
import { useUserSolutionStore, useEditorStore } from "@/lib/stores";
1614
import { sendGAEvent } from "@next/third-parties/google";
15+
import { CodeFile, OutputResult } from "@/lib/types";
16+
import { OutputReducerAction } from "@/lib/reducers";
1717

18-
export default function CodeEditor({
19-
codeString,
20-
setCodeString,
21-
codeFile,
22-
dispatchOutput,
23-
nextStepPath,
24-
stepIndex,
25-
chapterIndex,
26-
outputResult,
27-
}: {
28-
codeString: string;
29-
setCodeString: (codeString: string) => void;
30-
codeFile: CodeFile;
31-
dispatchOutput: React.Dispatch<OutputReducerAction>;
32-
nextStepPath: string | undefined;
33-
stepIndex: number;
34-
chapterIndex: number;
35-
outputResult: OutputResult;
36-
}) {
37-
const { colorMode } = useColorMode();
38-
const [monaco, setMonaco] = useState<any>(null);
39-
const router = useRouter();
40-
const editorStore = useEditorStore();
41-
const userSolutionStore = useUserSolutionStore();
42-
const editorRef = useRef<any>(null);
43-
18+
// Custom hook for editor theme setup
19+
const useEditorTheme = (monaco: Monaco, colorMode: "dark" | "light") => {
4420
useEffect(() => {
4521
if (monaco) {
4622
monaco.editor.defineTheme("my-theme", {
@@ -54,31 +30,42 @@ export default function CodeEditor({
5430
monaco.editor.setTheme(colorMode === "light" ? "light" : "my-theme");
5531
}
5632
}, [monaco, colorMode]);
33+
};
34+
35+
// Custom hook for keyboard shortcuts
36+
const useValidationShortcut = (
37+
handleValidate: () => void,
38+
codeString: string,
39+
) => {
5740
useEffect(() => {
5841
const handleKeyDown = (event: KeyboardEvent) => {
59-
if (event.key == "Enter" && event.shiftKey) {
42+
if (event.key === "Enter" && event.shiftKey) {
6043
sendGAEvent("event", "buttonClicked", {
6144
value: "Validate (through shortcut)",
6245
});
6346
event.preventDefault();
64-
tryFormattingCode(editorRef, setCodeString);
65-
validateCode(
66-
codeString,
67-
codeFile,
68-
dispatchOutput,
69-
stepIndex,
70-
chapterIndex,
71-
);
47+
handleValidate();
7248
}
7349
};
7450

7551
document.addEventListener("keydown", handleKeyDown);
76-
7752
return () => {
7853
document.removeEventListener("keydown", handleKeyDown);
7954
};
80-
}, [codeString]);
55+
}, [handleValidate, codeString]);
56+
};
57+
58+
// Custom hook for code persistence
59+
const useCodePersistence = (
60+
chapterIndex: number,
61+
stepIndex: number,
62+
codeString: string,
63+
setCodeString: (value: string) => void,
64+
codeFile: CodeFile,
65+
) => {
66+
const userSolutionStore = useUserSolutionStore();
8167

68+
// Load saved code
8269
useEffect(() => {
8370
const savedCode = userSolutionStore.getSavedUserSolutionByLesson(
8471
chapterIndex,
@@ -89,6 +76,7 @@ export default function CodeEditor({
8976
}
9077
}, [chapterIndex, stepIndex]);
9178

79+
// Save code changes
9280
useEffect(() => {
9381
userSolutionStore.saveUserSolutionForLesson(
9482
chapterIndex,
@@ -97,11 +85,135 @@ export default function CodeEditor({
9785
);
9886
}, [codeString, chapterIndex, stepIndex]);
9987

88+
// Initialize code if no saved solutions
10089
useEffect(() => {
101-
if (Object.keys(userSolutionStore.userSolutionsByLesson).length == 0) {
90+
if (Object.keys(userSolutionStore.userSolutionsByLesson).length === 0) {
10291
setCodeString(JSON.stringify(codeFile.code, null, 2));
10392
}
10493
}, [userSolutionStore]);
94+
};
95+
96+
// EditorControls component for the buttons section
97+
const EditorControls = ({
98+
handleValidate,
99+
isValidating,
100+
resetCode,
101+
nextStepPath,
102+
outputResult,
103+
}: {
104+
handleValidate: () => void;
105+
isValidating: boolean;
106+
resetCode: () => void;
107+
nextStepPath: string | undefined;
108+
outputResult: OutputResult;
109+
}) => {
110+
const router = useRouter();
111+
112+
return (
113+
<div className={styles.buttonsWrapper}>
114+
<Flex dir="row" gap="8px" alignItems="end">
115+
<MyBtn
116+
onClick={handleValidate}
117+
variant={
118+
outputResult.validityStatus === "valid" ? "success" : "default"
119+
}
120+
isDisabled={isValidating}
121+
tooltip="Shift + Enter"
122+
>
123+
{isValidating ? "Validating ..." : "Validate"}
124+
</MyBtn>
125+
126+
<MyBtn onClick={resetCode} variant="error">
127+
Reset
128+
</MyBtn>
129+
</Flex>
130+
<MyBtn
131+
onClick={() => {
132+
if (nextStepPath) router.push("/" + nextStepPath);
133+
}}
134+
variant={
135+
outputResult.validityStatus === "valid" ? "default" : "success"
136+
}
137+
isDisabled={!nextStepPath}
138+
size={outputResult.validityStatus === "valid" ? "sm" : "xs"}
139+
>
140+
Next <span style={{ marginLeft: "4px" }}></span>
141+
<FiChevronRight
142+
color={
143+
outputResult.validityStatus === "valid"
144+
? "white"
145+
: "hsl(var(--success))"
146+
}
147+
/>
148+
</MyBtn>
149+
</div>
150+
);
151+
};
152+
153+
export default function CodeEditor({
154+
codeString,
155+
setCodeString,
156+
codeFile,
157+
dispatchOutput,
158+
nextStepPath,
159+
stepIndex,
160+
chapterIndex,
161+
outputResult,
162+
}: {
163+
codeString: string;
164+
setCodeString: (codeString: string) => void;
165+
codeFile: CodeFile;
166+
dispatchOutput: React.Dispatch<OutputReducerAction>;
167+
nextStepPath: string | undefined;
168+
stepIndex: number;
169+
chapterIndex: number;
170+
outputResult: OutputResult;
171+
}) {
172+
const { colorMode } = useColorMode();
173+
const [monaco, setMonaco] = useState<any>(null);
174+
const [isValidating, setIsValidating] = useState(false);
175+
const editorStore = useEditorStore();
176+
const editorRef = useRef<any>(null);
177+
178+
// Apply custom hooks
179+
useEditorTheme(monaco, colorMode);
180+
181+
const handleValidate = () => {
182+
setIsValidating(true);
183+
setTimeout(() => {
184+
tryFormattingCode(editorRef, setCodeString);
185+
validateCode(
186+
codeString,
187+
codeFile,
188+
dispatchOutput,
189+
stepIndex,
190+
chapterIndex,
191+
);
192+
setIsValidating(false);
193+
}, 500);
194+
};
195+
196+
useValidationShortcut(handleValidate, codeString);
197+
useCodePersistence(
198+
chapterIndex,
199+
stepIndex,
200+
codeString,
201+
setCodeString,
202+
codeFile,
203+
);
204+
205+
const resetCode = () => {
206+
setCodeString(JSON.stringify(codeFile.code, null, 2));
207+
dispatchOutput({ type: "RESET" });
208+
};
209+
210+
const handleEditorMount = (editor: any, monaco: Monaco) => {
211+
setMonaco(monaco);
212+
213+
editorRef.current = editor;
214+
editorStore.setEditor(editor);
215+
editorStore.setMonaco(monaco);
216+
};
105217

106218
return (
107219
<>
@@ -111,74 +223,24 @@ export default function CodeEditor({
111223
defaultValue={codeString}
112224
theme={colorMode === "light" ? "light" : "my-theme"}
113225
value={codeString}
114-
height={"100%"}
226+
height="100%"
115227
onChange={(codeString) => setCodeString(codeString ?? "")}
116228
options={{
117229
minimap: { enabled: false },
118-
119230
fontSize: 14,
120231
formatOnPaste: true,
121232
formatOnType: true,
122233
}}
123-
onMount={(editor, monaco) => {
124-
setMonaco(monaco);
125-
editorRef.current = editor;
126-
editorStore.setEditor(editor);
127-
editorStore.setMonaco(monaco);
128-
}}
234+
onMount={handleEditorMount}
129235
/>
130236
</div>
131-
<div className={styles.buttonsWrapper}>
132-
<Flex dir="row" gap={"8px"} alignItems={"end"}>
133-
<MyBtn
134-
onClick={async () => {
135-
tryFormattingCode(editorRef, setCodeString);
136-
validateCode(
137-
codeString,
138-
codeFile,
139-
dispatchOutput,
140-
stepIndex,
141-
chapterIndex,
142-
);
143-
}}
144-
variant={
145-
outputResult.validityStatus === "valid" ? "success" : "default"
146-
}
147-
tooltip="Shift + Enter"
148-
>
149-
Validate
150-
</MyBtn>
151-
152-
<MyBtn
153-
onClick={() => {
154-
setCodeString(JSON.stringify(codeFile.code, null, 2));
155-
dispatchOutput({ type: "RESET" });
156-
}}
157-
variant={"error"}
158-
>
159-
Reset
160-
</MyBtn>
161-
</Flex>
162-
<MyBtn
163-
onClick={() => {
164-
if (nextStepPath) router.push("/" + nextStepPath);
165-
}}
166-
variant={
167-
outputResult.validityStatus === "valid" ? "default" : "success"
168-
}
169-
isDisabled={!nextStepPath}
170-
size={outputResult.validityStatus === "valid" ? "sm" : "xs"}
171-
>
172-
Next <span style={{ marginLeft: "4px" }}></span>
173-
<FiChevronRight
174-
color={
175-
outputResult.validityStatus === "valid"
176-
? "white"
177-
: "hsl(var(--success))"
178-
}
179-
/>
180-
</MyBtn>
181-
</div>
237+
<EditorControls
238+
handleValidate={handleValidate}
239+
isValidating={isValidating}
240+
resetCode={resetCode}
241+
nextStepPath={nextStepPath}
242+
outputResult={outputResult}
243+
/>
182244
</>
183245
);
184246
}

0 commit comments

Comments
 (0)