Skip to content

Commit d5c2162

Browse files
committed
added preserve validation function
1 parent 78df994 commit d5c2162

File tree

3 files changed

+208
-20
lines changed

3 files changed

+208
-20
lines changed

app/components/CodeEditor/CodeEditor.tsx

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import ctx from "classnames";
55
import { GeistMono } from "geist/font/mono";
66
import Editor, { Monaco } from "@monaco-editor/react";
77
import { Flex, useColorMode } from "@chakra-ui/react";
8-
import { useEffect, useState, useRef } from "react";
8+
import { useEffect, useState, useRef, useCallback } from "react";
99
import MyBtn from "../MyBtn";
10-
import { tryFormattingCode, validateCode } from "@/lib/client-functions";
10+
import {
11+
tryFormattingCode,
12+
validateCode,
13+
restorePreviousValidation,
14+
hasValidationResult,
15+
} from "@/lib/client-functions";
1116
import FiChevronRight from "@/app/styles/icons/HiChevronRightGreen";
1217
import { useRouter } from "next/navigation";
1318
import { useUserSolutionStore, useEditorStore } from "@/lib/stores";
@@ -94,6 +99,39 @@ const useCodePersistence = (
9499
}, [userSolutionStore]);
95100
};
96101

102+
// Custom hook for validation restoration
103+
const useValidationRestore = (
104+
chapterIndex: number,
105+
stepIndex: number,
106+
dispatchOutput: React.Dispatch<OutputReducerAction>,
107+
setCodeString: (value: string) => void,
108+
) => {
109+
const [isRestored, setIsRestored] = useState(false);
110+
111+
useEffect(() => {
112+
// Restore previous validation on component mount or when lesson changes
113+
if (!isRestored && hasValidationResult(chapterIndex, stepIndex)) {
114+
try {
115+
const { restored } = restorePreviousValidation(
116+
chapterIndex,
117+
stepIndex,
118+
dispatchOutput,
119+
setCodeString
120+
);
121+
if (restored) {
122+
setIsRestored(true);
123+
console.log('✅ Previous validation restored for lesson:', chapterIndex, stepIndex);
124+
}
125+
} catch (error) {
126+
console.error('Failed to restore validation:', error);
127+
}
128+
}
129+
}, [chapterIndex, stepIndex, isRestored, dispatchOutput, setCodeString]);
130+
131+
return { isRestored };
132+
};
133+
134+
97135
// EditorControls component for the buttons section
98136
const EditorControls = ({
99137
handleValidate,
@@ -179,7 +217,7 @@ export default function CodeEditor({
179217
// Apply custom hooks
180218
useEditorTheme(monaco, colorMode);
181219

182-
const handleValidate = () => {
220+
const handleValidate = useCallback(() => {
183221
setIsValidating(true);
184222
setTimeout(() => {
185223
tryFormattingCode(editorRef, setCodeString);
@@ -192,7 +230,7 @@ export default function CodeEditor({
192230
);
193231
setIsValidating(false);
194232
}, 500);
195-
};
233+
}, [codeString, codeFile, dispatchOutput, stepIndex, chapterIndex]);
196234

197235
useValidationShortcut(handleValidate, codeString);
198236
useCodePersistence(
@@ -203,21 +241,43 @@ export default function CodeEditor({
203241
codeFile,
204242
);
205243

244+
const { isRestored } = useValidationRestore(
245+
chapterIndex,
246+
stepIndex,
247+
dispatchOutput,
248+
setCodeString,
249+
);
250+
206251
const resetCode = () => {
207252
setCodeString(JSON.stringify(codeFile.code, null, 2));
208253
dispatchOutput({ type: "RESET" });
209254
};
210255

211-
const handleEditorMount = (editor: any, monaco: Monaco) => {
212-
setMonaco(monaco);
256+
const handleEditorMount = (editor: monaco.editor.IStandaloneCodeEditor, monacoInstance: Monaco) => {
257+
setMonaco(monacoInstance);
213258

214259
editorRef.current = editor;
215260
editorStore.setEditor(editor);
216-
editorStore.setMonaco(monaco);
261+
editorStore.setMonaco(monacoInstance);
217262
};
218263

219264
return (
220265
<>
266+
{isRestored && (
267+
<div
268+
style={{
269+
padding: "8px 12px",
270+
backgroundColor: "#e8f5e8",
271+
borderLeft: "3px solid #4caf50",
272+
marginBottom: "8px",
273+
fontSize: "14px",
274+
color: "#2e7d32",
275+
}}
276+
>
277+
✅ Previous submission restored
278+
</div>
279+
)}
280+
221281
<div className={ctx(styles.codeEditor, GeistMono.className)}>
222282
<Editor
223283
language="json"

lib/client-functions.ts

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import { CodeFile, TestCaseResult } from "./types";
88
import { hyperjumpCheckAnnotations, hyperjumpValidate } from "./validators";
99
import { sendGAEvent } from "@next/third-parties/google";
1010
import { contentManager } from "./contentManager";
11+
import {
12+
saveValidationResult,
13+
getValidationResult,
14+
hasValidationResult,
15+
} from "./progressSaving";
1116

1217
export async function validateCode(
1318
codeString: string,
@@ -53,17 +58,29 @@ export async function validateCode(
5358
});
5459
}
5560
}
61+
5662
if (codeFile.expectedAnnotations) {
5763
await hyperjumpCheckAnnotations(schemaCode, codeFile.expectedAnnotations);
5864
}
5965

66+
const sortedResults = testCaseResults.sort((a, b) => {
67+
if (a.passed === b.passed) {
68+
return 0;
69+
}
70+
return a.passed ? 1 : -1;
71+
});
72+
73+
// Save validation result to localStorage BEFORE dispatching
74+
saveValidationResult(
75+
chapterIndex,
76+
stepIndex,
77+
codeString,
78+
sortedResults,
79+
totalTestCases,
80+
validationStatus,
81+
);
82+
6083
if (validationStatus === "valid") {
61-
const sortedResults = testCaseResults.sort((a, b) => {
62-
if (a.passed === b.passed) {
63-
return 0; // If both are the same, their order doesn't change
64-
}
65-
return a.passed ? 1 : -1; // If a.passed is true, put a after b; if false, put a before b
66-
});
6784
dispatchOutput({
6885
type: "valid",
6986
payload: { testCaseResults: sortedResults, totalTestCases },
@@ -72,13 +89,7 @@ export async function validateCode(
7289
sendGAEvent("event", "validation", {
7390
validation_result: "passed",
7491
});
75-
} else {
76-
const sortedResults = testCaseResults.sort((a, b) => {
77-
if (a.passed === b.passed) {
78-
return 0; // If both are the same, their order doesn't change
79-
}
80-
return a.passed ? 1 : -1; // If a.passed is true, put a after b; if false, put a before b
81-
});
92+
} else {
8293
dispatchOutput({
8394
type: "invalid",
8495
payload: { testCaseResults: sortedResults, totalTestCases },
@@ -88,6 +99,9 @@ export async function validateCode(
8899
});
89100
}
90101
} catch (e) {
102+
// Save error state as well
103+
saveValidationResult(chapterIndex, stepIndex, codeString, [], 0, "invalid");
104+
91105
if ((e as Error).message === "Invalid Schema") {
92106
dispatchOutput({
93107
type: "invalidSchema",
@@ -193,3 +207,45 @@ export async function tryFormattingCode(
193207
return;
194208
}
195209
}
210+
211+
export function restorePreviousValidation(
212+
chapterIndex: number,
213+
stepIndex: number,
214+
dispatchOutput: React.Dispatch<OutputReducerAction>,
215+
setCodeString?: (code: string) => void
216+
): { restored: boolean; code?: string } {
217+
if (typeof window === "undefined") return { restored: false };
218+
219+
const validationResult = getValidationResult(chapterIndex, stepIndex);
220+
221+
if (validationResult) {
222+
// Restore code if setter provided
223+
if (setCodeString) {
224+
setCodeString(validationResult.code);
225+
}
226+
227+
// Restore validation results
228+
if (validationResult.validationStatus === "valid") {
229+
dispatchOutput({
230+
type: "valid",
231+
payload: {
232+
testCaseResults: validationResult.testCaseResults,
233+
totalTestCases: validationResult.totalTestCases
234+
},
235+
});
236+
} else if (validationResult.validationStatus === "invalid") {
237+
dispatchOutput({
238+
type: "invalid",
239+
payload: {
240+
testCaseResults: validationResult.testCaseResults,
241+
totalTestCases: validationResult.totalTestCases
242+
},
243+
});
244+
}
245+
246+
return { restored: true, code: validationResult.code };
247+
}
248+
249+
return { restored: false };
250+
}
251+
export { hasValidationResult } from "./progressSaving";

lib/progressSaving.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,75 @@
1+
interface ValidationResult {
2+
code: string;
3+
testCaseResults: any[];
4+
totalTestCases: number;
5+
validationStatus: "valid" | "invalid" | "neutral";
6+
timestamp: number;
7+
chapterIndex: number;
8+
stepIndex: number;
9+
}
10+
11+
export function saveValidationResult(
12+
chapterIndex: number,
13+
stepIndex: number,
14+
code: string,
15+
testCaseResults: any[],
16+
totalTestCases: number,
17+
validationStatus: "valid" | "invalid" | "neutral"
18+
): boolean {
19+
if (typeof window === "undefined") return false;
20+
21+
const key = `validation-${chapterIndex}-${stepIndex}`;
22+
const validationData: ValidationResult = {
23+
code,
24+
testCaseResults,
25+
totalTestCases,
26+
validationStatus,
27+
timestamp: Date.now(),
28+
chapterIndex,
29+
stepIndex
30+
};
31+
32+
try {
33+
localStorage.setItem(key, JSON.stringify(validationData));
34+
return true;
35+
} catch (error) {
36+
console.warn('Failed to save validation result:', error);
37+
return false;
38+
}
39+
}
40+
41+
export function getValidationResult(chapterIndex: number, stepIndex: number): ValidationResult | null {
42+
if (typeof window === "undefined") return null;
43+
44+
const key = `validation-${chapterIndex}-${stepIndex}`;
45+
const stored = localStorage.getItem(key);
46+
47+
if (stored) {
48+
try {
49+
return JSON.parse(stored);
50+
} catch (error) {
51+
console.warn('Failed to parse validation result:', error);
52+
return null;
53+
}
54+
}
55+
return null;
56+
}
57+
58+
export function hasValidationResult(chapterIndex: number, stepIndex: number): boolean {
59+
if (typeof window === "undefined") return false;
60+
61+
const key = `validation-${chapterIndex}-${stepIndex}`;
62+
return localStorage.getItem(key) !== null;
63+
}
64+
65+
export function clearValidationResult(chapterIndex: number, stepIndex: number): boolean {
66+
if (typeof window === "undefined") return false;
67+
68+
const key = `validation-${chapterIndex}-${stepIndex}`;
69+
localStorage.removeItem(key);
70+
return true;
71+
}
72+
173
export function setCheckpoint(path: string) {
274
if (typeof window === "undefined") return false;
375
const checkpoint = path;

0 commit comments

Comments
 (0)