From 1910a58955d7d266345f900bc6a9509b9001e4cd Mon Sep 17 00:00:00 2001 From: Me Priyank Date: Sat, 29 Nov 2025 15:45:00 +0530 Subject: [PATCH 1/3] Fix: #138 - Implement mobile responsiveness for the tour page UI. --- app/mobile/page.module.css | 4 +-- app/mobile/page.tsx | 46 ++++++++++++++--------------- app/providers.tsx | 60 ++++++++++++++++++++++++++++++++------ app/styles/page.module.css | 9 ++++-- 4 files changed, 83 insertions(+), 36 deletions(-) diff --git a/app/mobile/page.module.css b/app/mobile/page.module.css index c77e46f..82dc628 100644 --- a/app/mobile/page.module.css +++ b/app/mobile/page.module.css @@ -1,4 +1,4 @@ -.main { +/* .main { display: flex; flex-direction: column; justify-content: center; @@ -9,4 +9,4 @@ .message { font-weight: 500; -} +} */ diff --git a/app/mobile/page.tsx b/app/mobile/page.tsx index 63a848d..0418d3a 100644 --- a/app/mobile/page.tsx +++ b/app/mobile/page.tsx @@ -1,25 +1,25 @@ -"use client"; +// "use client"; -import React, { useEffect } from "react"; -import styles from "./page.module.css"; -import classnames from "classnames"; -import { outfitFont } from "../styles/fonts"; +// import React, { useEffect } from "react"; +// import styles from "./page.module.css"; +// import classnames from "classnames"; +// import { outfitFont } from "../styles/fonts"; -export default function Mobile() { - useEffect(() => { - // if not on mobile, redirect to the main page - if (window.innerWidth > 768) { - window.location.href = "/"; - } - }, []); - return ( -
-
- We are sorry, -
- Tour of JSON Schema is not optimized for mobile devices. Please use a - desktop computer for the best experience. -
-
- ); -} +// export default function Mobile() { +// useEffect(() => { +// // if not on mobile, redirect to the main page +// if (window.innerWidth > 768) { +// window.location.href = "/"; +// } +// }, []); +// return ( +//
+//
+// We are sorry, +//
+// Tour of JSON Schema is not optimized for mobile devices. Please use a +// desktop computer for the best experience. +//
+//
+// ); +// } diff --git a/app/providers.tsx b/app/providers.tsx index 77c11b6..64e369a 100644 --- a/app/providers.tsx +++ b/app/providers.tsx @@ -1,14 +1,56 @@ "use client"; import { ChakraProvider } from "@chakra-ui/react"; import { theme } from "./styles/theme"; -import { useEffect } from "react"; +import styles from "./styles/page.module.css"; +import classnames from "classnames"; + +import { useEffect, useState } from "react"; +import { outfitFont } from "./styles/fonts"; + +const MOBILE_BREAKPOINT = 768; export function Providers({ children }: { children: React.ReactNode }) { - // useEffect(() => { - // if (window.innerWidth < 768 && !window.location.href.includes("/mobile")) { - // window.location.href = "/mobile"; - // } - // }, []); - - return {children}; -} + // 1. Initialize state for mobile status + const [isMobile, setIsMobile] = useState(false); + const [hasChecked, setHasChecked] = useState(false); + + useEffect(() => { + // 2. Function to check the screen width + const checkMobileStatus = () => { + setIsMobile(window.innerWidth <= MOBILE_BREAKPOINT); + setHasChecked(true); + }; + + checkMobileStatus(); + + + window.addEventListener('resize', checkMobileStatus); + + return () => { + window.removeEventListener('resize', checkMobileStatus); + }; + }, []); + + if (!hasChecked) { + return null; + } + + if (!isMobile) { + return ( + + {children} + + ); + } + + return ( +
+
+ We are sorry, +
+ Tour of JSON Schema is not optimized for mobile devices. Please use a + desktop computer for the best experience. +
+
+ ); +} \ No newline at end of file diff --git a/app/styles/page.module.css b/app/styles/page.module.css index 7b3b959..acff2fc 100644 --- a/app/styles/page.module.css +++ b/app/styles/page.module.css @@ -1,9 +1,10 @@ .main { display: flex; flex-direction: column; - height: inherit; - + justify-content: center; align-items: center; + height: 100%; + padding-inline: 12px; } .wrapper { @@ -86,3 +87,7 @@ .footerText { font-weight: 700; } + +.message { + font-weight: 500; +} From 86f428e4368f46175c00451bb7a48bc37a19be5f Mon Sep 17 00:00:00 2001 From: Me Priyank Date: Sun, 30 Nov 2025 16:59:58 +0530 Subject: [PATCH 2/3] Feature, UX Improvement, and Bugfix(fix #123) --- .../CodeEditor/CodeEditor.module.css | 34 ++++++ app/components/CodeEditor/CodeEditor.tsx | 101 +++++++++++++----- .../EditorNOutput/EditorNOutput.tsx | 35 +++++- app/components/Output/Output.tsx | 47 ++++---- 4 files changed, 163 insertions(+), 54 deletions(-) diff --git a/app/components/CodeEditor/CodeEditor.module.css b/app/components/CodeEditor/CodeEditor.module.css index e71d01a..c4880bf 100644 --- a/app/components/CodeEditor/CodeEditor.module.css +++ b/app/components/CodeEditor/CodeEditor.module.css @@ -4,6 +4,7 @@ overflow-y: auto; padding-bottom: 32px; } + .buttonsWrapper { position: absolute; bottom: 8px; @@ -15,3 +16,36 @@ padding-left: 16px; padding-right: 32px; } + + +.tabBar { + display: flex; + background-color: var(--code-editor-background); + border-bottom: 1px solid var(--border-color); + padding: 0 10px; +} + +.tabButton { + background: none; + border: none; + padding: 8px 15px; + cursor: pointer; + font-size: 14px; + color: var(--text-muted); + border-bottom: 3px solid transparent; + transition: all 0.2s; + margin-bottom: -1px; +} + +.tabButton:hover { + color: var(--text-color); +} + +.activeTab { + color: var(--primary-color) !important; + border-bottom-color: var(--primary-color); +} + +.codeEditor { + flex: 1; +} \ No newline at end of file diff --git a/app/components/CodeEditor/CodeEditor.tsx b/app/components/CodeEditor/CodeEditor.tsx index 32870c3..8ea7657 100644 --- a/app/components/CodeEditor/CodeEditor.tsx +++ b/app/components/CodeEditor/CodeEditor.tsx @@ -16,7 +16,6 @@ import { CodeFile, OutputResult } from "@/lib/types"; import { OutputReducerAction } from "@/lib/reducers"; import CertificateButton from "../CertificateButton/CertificateButton"; -// Custom hook for editor theme setup const useEditorTheme = (monaco: Monaco, colorMode: "dark" | "light") => { useEffect(() => { if (monaco) { @@ -33,7 +32,6 @@ const useEditorTheme = (monaco: Monaco, colorMode: "dark" | "light") => { }, [monaco, colorMode]); }; -// Custom hook for keyboard shortcuts const useValidationShortcut = ( handleValidate: () => void, codeString: string, @@ -56,7 +54,6 @@ const useValidationShortcut = ( }, [handleValidate, codeString]); }; -// Custom hook for code persistence const useCodePersistence = ( chapterIndex: number, stepIndex: number, @@ -66,7 +63,6 @@ const useCodePersistence = ( ) => { const userSolutionStore = useUserSolutionStore(); - // Load saved code useEffect(() => { const savedCode = userSolutionStore.getSavedUserSolutionByLesson( chapterIndex, @@ -77,24 +73,16 @@ const useCodePersistence = ( } }, [chapterIndex, stepIndex]); - // Save code changes - useEffect(() => { - userSolutionStore.saveUserSolutionForLesson( - chapterIndex, - stepIndex, - codeString, - ); - }, [codeString, chapterIndex, stepIndex]); - // Initialize code if no saved solutions useEffect(() => { if (Object.keys(userSolutionStore.userSolutionsByLesson).length === 0) { setCodeString(JSON.stringify(codeFile.code, null, 2)); } }, [userSolutionStore]); + + return userSolutionStore; }; -// EditorControls component for the buttons section const EditorControls = ({ handleValidate, isValidating, @@ -160,6 +148,9 @@ export default function CodeEditor({ stepIndex, chapterIndex, outputResult, + solutionRequested, + resetSolution, + hasValidated, }: { codeString: string; setCodeString: (codeString: string) => void; @@ -169,6 +160,9 @@ export default function CodeEditor({ stepIndex: number; chapterIndex: number; outputResult: OutputResult; + solutionRequested: boolean; + resetSolution: () => void; + hasValidated: boolean; }) { const { colorMode } = useColorMode(); const [monaco, setMonaco] = useState(null); @@ -176,9 +170,18 @@ export default function CodeEditor({ const editorStore = useEditorStore(); const editorRef = useRef(null); - // Apply custom hooks + const [activeView, setActiveView] = useState<'code' | 'solution'>('code'); + useEditorTheme(monaco, colorMode); + const userSolutionStore = useCodePersistence( + chapterIndex, + stepIndex, + codeString, + setCodeString, + codeFile, + ); + const handleValidate = () => { setIsValidating(true); setTimeout(() => { @@ -195,42 +198,84 @@ export default function CodeEditor({ }; useValidationShortcut(handleValidate, codeString); - useCodePersistence( - chapterIndex, - stepIndex, - codeString, - setCodeString, - codeFile, - ); const resetCode = () => { - setCodeString(JSON.stringify(codeFile.code, null, 2)); + const initialCode = JSON.stringify(codeFile.code, null, 2); + setCodeString(initialCode); dispatchOutput({ type: "RESET" }); + + resetSolution(); + setActiveView('code'); + + userSolutionStore.saveUserSolutionForLesson(chapterIndex, stepIndex, initialCode); }; const handleEditorMount = (editor: any, monaco: Monaco) => { setMonaco(monaco); - editorRef.current = editor; editorStore.setEditor(editor); editorStore.setMonaco(monaco); }; + const handleCodeChange = (newCode: string | undefined) => { + if (activeView === 'code' && newCode !== undefined) { + setCodeString(newCode); + userSolutionStore.saveUserSolutionForLesson( + chapterIndex, + stepIndex, + newCode + ); + } + }; + + useEffect(() => { + if (solutionRequested && activeView !== 'solution') { + setActiveView('solution'); + } + if (!solutionRequested && activeView === 'solution') { + setActiveView('code'); + } + }, [solutionRequested]); + + const isSolutionView = activeView === 'solution'; + const editorContent = isSolutionView + ? JSON.stringify(codeFile.solution, null, 2) + : codeString; + return ( <> +
+ + + {solutionRequested && ( + + )} +
+
setCodeString(codeString ?? "")} + onChange={handleCodeChange} options={{ minimap: { enabled: false }, fontSize: 14, formatOnPaste: true, formatOnType: true, + readOnly: isSolutionView, }} onMount={handleEditorMount} /> @@ -244,4 +289,4 @@ export default function CodeEditor({ /> ); -} +} \ No newline at end of file diff --git a/app/components/EditorNOutput/EditorNOutput.tsx b/app/components/EditorNOutput/EditorNOutput.tsx index 05957f7..573ee45 100644 --- a/app/components/EditorNOutput/EditorNOutput.tsx +++ b/app/components/EditorNOutput/EditorNOutput.tsx @@ -23,8 +23,21 @@ export default function EditorNOutput({ JSON.stringify(codeFile.code, null, 2), ); + // Tracks if the user has requested to view the solution + const [solutionRequested, setSolutionRequested] = useState(false); + + // NEW STATE: Tracks if the user has validated at least once + const [hasValidated, setHasValidated] = useState(false); + + // Function to show solution const showSolution = () => { - setCodeString(JSON.stringify(codeFile.solution, null, 2)); + setSolutionRequested(true); + }; + + // Function to reset the solution visibility state + const resetSolution = () => { + setSolutionRequested(false); + setHasValidated(false); }; const [output, dispatchOutput] = useReducer(outputReducer, { @@ -32,7 +45,7 @@ export default function EditorNOutput({ errors: "", testCaseResults: [], }); - const [topWidth, setTopWidth] = useState(400); // Initial width of the left div + const [topWidth, setTopWidth] = useState(400); const dividerRef = useRef(null); const containerRef = useRef(null); @@ -70,6 +83,12 @@ export default function EditorNOutput({ } }, []); + useEffect(() => { + if (output.validityStatus !== "neutral") { + setHasValidated(true); + } + }, [output.validityStatus]); + return (
- +
); -} +} \ No newline at end of file diff --git a/app/components/Output/Output.tsx b/app/components/Output/Output.tsx index 5a5d395..57f425e 100644 --- a/app/components/Output/Output.tsx +++ b/app/components/Output/Output.tsx @@ -81,9 +81,13 @@ const SchemaError = ({ schemaPath }: { schemaPath: string }) => { function Output({ outputResult, showSolution, + solutionRequested, + hasValidated, }: { outputResult: OutputResult; showSolution: () => void; + solutionRequested: boolean; + hasValidated: boolean; }) { let outputBodyContent; @@ -92,7 +96,7 @@ function Output({ {" "} Please click the{" "} - {}}> + { }}> validate {" "} button or use to view the @@ -149,29 +153,28 @@ function Output({
{outputBodyContent} - {outputResult.validityStatus !== "neutral" && - outputResult.validityStatus !== "valid" && ( -
- Stuck?{" "} - -
- )} + {hasValidated && !solutionRequested && ( +
+ Stuck?{" "} + +
+ )}
); } -export default Output; +export default Output; \ No newline at end of file From 8ef89603b504de7bdb8af198f2247a88de151da8 Mon Sep 17 00:00:00 2001 From: Me Priyank Date: Sun, 30 Nov 2025 18:44:15 +0530 Subject: [PATCH 3/3] Revert "Fix: #138 - Implement mobile responsiveness for the tour page UI." This reverts commit 1910a58955d7d266345f900bc6a9509b9001e4cd. --- app/mobile/page.module.css | 4 +-- app/mobile/page.tsx | 46 ++++++++++++++--------------- app/providers.tsx | 60 ++++++-------------------------------- app/styles/page.module.css | 9 ++---- 4 files changed, 36 insertions(+), 83 deletions(-) diff --git a/app/mobile/page.module.css b/app/mobile/page.module.css index 82dc628..c77e46f 100644 --- a/app/mobile/page.module.css +++ b/app/mobile/page.module.css @@ -1,4 +1,4 @@ -/* .main { +.main { display: flex; flex-direction: column; justify-content: center; @@ -9,4 +9,4 @@ .message { font-weight: 500; -} */ +} diff --git a/app/mobile/page.tsx b/app/mobile/page.tsx index 0418d3a..63a848d 100644 --- a/app/mobile/page.tsx +++ b/app/mobile/page.tsx @@ -1,25 +1,25 @@ -// "use client"; +"use client"; -// import React, { useEffect } from "react"; -// import styles from "./page.module.css"; -// import classnames from "classnames"; -// import { outfitFont } from "../styles/fonts"; +import React, { useEffect } from "react"; +import styles from "./page.module.css"; +import classnames from "classnames"; +import { outfitFont } from "../styles/fonts"; -// export default function Mobile() { -// useEffect(() => { -// // if not on mobile, redirect to the main page -// if (window.innerWidth > 768) { -// window.location.href = "/"; -// } -// }, []); -// return ( -//
-//
-// We are sorry, -//
-// Tour of JSON Schema is not optimized for mobile devices. Please use a -// desktop computer for the best experience. -//
-//
-// ); -// } +export default function Mobile() { + useEffect(() => { + // if not on mobile, redirect to the main page + if (window.innerWidth > 768) { + window.location.href = "/"; + } + }, []); + return ( +
+
+ We are sorry, +
+ Tour of JSON Schema is not optimized for mobile devices. Please use a + desktop computer for the best experience. +
+
+ ); +} diff --git a/app/providers.tsx b/app/providers.tsx index 64e369a..77c11b6 100644 --- a/app/providers.tsx +++ b/app/providers.tsx @@ -1,56 +1,14 @@ "use client"; import { ChakraProvider } from "@chakra-ui/react"; import { theme } from "./styles/theme"; -import styles from "./styles/page.module.css"; -import classnames from "classnames"; - -import { useEffect, useState } from "react"; -import { outfitFont } from "./styles/fonts"; - -const MOBILE_BREAKPOINT = 768; +import { useEffect } from "react"; export function Providers({ children }: { children: React.ReactNode }) { - // 1. Initialize state for mobile status - const [isMobile, setIsMobile] = useState(false); - const [hasChecked, setHasChecked] = useState(false); - - useEffect(() => { - // 2. Function to check the screen width - const checkMobileStatus = () => { - setIsMobile(window.innerWidth <= MOBILE_BREAKPOINT); - setHasChecked(true); - }; - - checkMobileStatus(); - - - window.addEventListener('resize', checkMobileStatus); - - return () => { - window.removeEventListener('resize', checkMobileStatus); - }; - }, []); - - if (!hasChecked) { - return null; - } - - if (!isMobile) { - return ( - - {children} - - ); - } - - return ( -
-
- We are sorry, -
- Tour of JSON Schema is not optimized for mobile devices. Please use a - desktop computer for the best experience. -
-
- ); -} \ No newline at end of file + // useEffect(() => { + // if (window.innerWidth < 768 && !window.location.href.includes("/mobile")) { + // window.location.href = "/mobile"; + // } + // }, []); + + return {children}; +} diff --git a/app/styles/page.module.css b/app/styles/page.module.css index acff2fc..7b3b959 100644 --- a/app/styles/page.module.css +++ b/app/styles/page.module.css @@ -1,10 +1,9 @@ .main { display: flex; flex-direction: column; - justify-content: center; + height: inherit; + align-items: center; - height: 100%; - padding-inline: 12px; } .wrapper { @@ -87,7 +86,3 @@ .footerText { font-weight: 700; } - -.message { - font-weight: 500; -}