From 6748cadfea30301c3f11fe4d64807c287d8028d8 Mon Sep 17 00:00:00 2001 From: Saloni Tarone Date: Sat, 4 Oct 2025 17:31:05 +0530 Subject: [PATCH 1/2] add a Tuterial Frontend Signed-off-by: Saloni Tarone --- frontend/package-lock.json | 309 ++++++++++++++++-- frontend/package.json | 1 + frontend/src/App.js | 10 + frontend/src/components/Layout/Navbar.jsx | 1 + .../components/Tutorial/TutorialLayout.jsx | 112 +++++++ .../components/Tutorial/TutorialProgress.jsx | 56 ++++ .../src/components/Tutorial/TutorialQuiz.jsx | 79 +++++ .../src/components/Tutorial/TutorialStep.jsx | 46 +++ frontend/src/pages/TutorialPage.jsx | 60 ++++ frontend/src/pages/TutorialsPage.jsx | 92 ++++++ frontend/src/services/tutorialService.js | 113 +++++++ 11 files changed, 849 insertions(+), 30 deletions(-) create mode 100644 frontend/src/components/Tutorial/TutorialLayout.jsx create mode 100644 frontend/src/components/Tutorial/TutorialProgress.jsx create mode 100644 frontend/src/components/Tutorial/TutorialQuiz.jsx create mode 100644 frontend/src/components/Tutorial/TutorialStep.jsx create mode 100644 frontend/src/pages/TutorialPage.jsx create mode 100644 frontend/src/pages/TutorialsPage.jsx create mode 100644 frontend/src/services/tutorialService.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 662ddaf..4d57b46 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -30,6 +30,7 @@ "react-icons": "^5.5.0", "react-router-dom": "^6.8.1", "react-scripts": "5.0.1", + "react-syntax-highlighter": "^15.5.0", "tailwindcss": "^3.2.7", "use-sound": "^4.0.1" }, @@ -98,7 +99,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -748,7 +748,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1632,7 +1631,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", @@ -4882,6 +4880,15 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -5046,7 +5053,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5142,6 +5148,12 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -5171,7 +5183,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -5225,7 +5236,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -5595,7 +5605,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5694,7 +5703,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6660,7 +6668,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001735", "electron-to-chromium": "^1.5.204", @@ -6860,12 +6867,41 @@ "node": ">=10" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chart.js": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", "license": "MIT", - "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -7120,6 +7156,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -7711,8 +7757,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/d3": { "version": "7.9.0", @@ -8031,7 +8076,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -8979,7 +9023,6 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -9803,6 +9846,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -10200,6 +10256,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -10707,6 +10771,33 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -10716,6 +10807,21 @@ "he": "bin/he" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -11174,6 +11280,30 @@ "node": ">= 10" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -11335,6 +11465,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -11422,6 +11562,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -11862,7 +12012,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -14914,6 +15063,20 @@ "tslib": "^2.0.3" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -15209,8 +15372,7 @@ "version": "0.36.1", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.36.1.tgz", "integrity": "sha512-/CaclMHKQ3A6rnzBzOADfwdSJ25BFoFT0Emxsc4zYVyav5SkK9iA6lEtIeuN/oRYbwPgviJT+t3l+sjFa28jYg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ms": { "version": "2.1.3", @@ -15718,6 +15880,24 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -15971,7 +16151,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17159,7 +17338,6 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17311,6 +17489,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -17356,6 +17543,19 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -17516,7 +17716,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -17677,7 +17876,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -17729,7 +17927,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -17839,6 +18036,23 @@ } } }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.6", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz", + "integrity": "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.30.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -17933,6 +18147,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -18238,7 +18476,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -18490,7 +18727,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -18961,6 +19197,16 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "license": "MIT" }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -19744,7 +19990,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -20125,7 +20370,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -20234,7 +20478,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20561,7 +20804,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -20633,7 +20875,6 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "license": "MIT", - "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -21046,7 +21287,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -21368,6 +21608,15 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index becc672..4bab14b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "react-icons": "^5.5.0", "react-router-dom": "^6.8.1", "react-scripts": "5.0.1", + "react-syntax-highlighter": "^15.5.0", "tailwindcss": "^3.2.7", "use-sound": "^4.0.1" }, diff --git a/frontend/src/App.js b/frontend/src/App.js index bd0f0cc..70cfbc8 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -10,6 +10,8 @@ import HomePage from './pages/Home'; import AboutPage from './pages/AboutPage'; import DocumentationPage from './pages/DocumentationPage'; import ContributorsPage from './pages/ContributorsPage'; +import TutorialPage from './pages/TutorialPage'; +import TutorialsPage from './pages/TutorialsPage'; import './App.css'; function App() { @@ -79,6 +81,14 @@ function App() { path="/contributors" element={} /> + } + /> + } + /> diff --git a/frontend/src/components/Layout/Navbar.jsx b/frontend/src/components/Layout/Navbar.jsx index d03a6eb..63ee553 100644 --- a/frontend/src/components/Layout/Navbar.jsx +++ b/frontend/src/components/Layout/Navbar.jsx @@ -38,6 +38,7 @@ const Navbar = ({ darkMode, setDarkMode }) => { { path: '/graph', label: 'Graph', icon: Network }, { path: '/string', label: 'String', icon: Type }, { path: '/dp', label: 'DP', icon: Layers }, + { path: '/tutorials', label: 'Tutorials', icon: BookOpen }, { path: '/about', label: 'About', icon: Info }, { path: '/docs', label: 'Docs', icon: BookOpen } ]; diff --git a/frontend/src/components/Tutorial/TutorialLayout.jsx b/frontend/src/components/Tutorial/TutorialLayout.jsx new file mode 100644 index 0000000..9754729 --- /dev/null +++ b/frontend/src/components/Tutorial/TutorialLayout.jsx @@ -0,0 +1,112 @@ +import React, { useState, useEffect } from 'react'; +import TutorialStep from './TutorialStep'; +import TutorialProgress from './TutorialProgress'; +import TutorialQuiz from './TutorialQuiz'; + +const TutorialLayout = ({ tutorialId }) => { + const [currentTutorial, setCurrentTutorial] = useState(null); + const [currentStep, setCurrentStep] = useState(0); + const [progress, setProgress] = useState({}); + + useEffect(() => { + // Load tutorial data + const loadTutorial = async () => { + // TODO: Fetch tutorial data from backend + // For now using mock data + const mockTutorial = { + id: "sorting-tutorial", + title: "Sorting Algorithms Mastery", + steps: [ + { + id: "intro", + type: "explanation", + content: "Sorting is fundamental...", + interactive: true, + quiz: { + question: "What is the time complexity of bubble sort?", + options: ["O(n)", "O(n²)", "O(n log n)", "O(1)"], + correct: 1 + } + } + ], + prerequisites: ["basic-arrays"], + estimatedTime: "30 minutes" + }; + setCurrentTutorial(mockTutorial); + }; + + loadTutorial(); + loadProgress(); + }, [tutorialId]); + + const loadProgress = () => { + const savedProgress = localStorage.getItem('tutorialProgress'); + if (savedProgress) { + setProgress(JSON.parse(savedProgress)); + } + }; + + const saveProgress = (updatedProgress) => { + localStorage.setItem('tutorialProgress', JSON.stringify(updatedProgress)); + setProgress(updatedProgress); + }; + + const handleStepComplete = () => { + const updatedProgress = { + ...progress, + [tutorialId]: { + ...progress[tutorialId], + completedSteps: [...(progress[tutorialId]?.completedSteps || []), currentStep] + } + }; + saveProgress(updatedProgress); + setCurrentStep(currentStep + 1); + }; + + if (!currentTutorial) { + return
Loading tutorial...
; + } + + const currentStepData = currentTutorial.steps[currentStep]; + + return ( +
+
+
+

+ {currentTutorial.title} +

+
+ Estimated time: {currentTutorial.estimatedTime} +
+
+ +
+
+ {currentStepData.type === "explanation" && ( + + )} + {currentStepData.quiz && ( + + )} +
+
+ +
+
+
+
+ ); +}; + +export default TutorialLayout; diff --git a/frontend/src/components/Tutorial/TutorialProgress.jsx b/frontend/src/components/Tutorial/TutorialProgress.jsx new file mode 100644 index 0000000..a921003 --- /dev/null +++ b/frontend/src/components/Tutorial/TutorialProgress.jsx @@ -0,0 +1,56 @@ +import React from 'react'; + +const TutorialProgress = ({ tutorial, currentStep, progress }) => { + const calculateProgress = () => { + if (!progress?.completedSteps) return 0; + return (progress.completedSteps.length / tutorial.steps.length) * 100; + }; + + const progressPercentage = calculateProgress(); + + return ( +
+

Your Progress

+
+
+
+
+

+ {Math.round(progressPercentage)}% Complete +

+
+
+ {tutorial.steps.map((step, index) => ( +
+
+ {progress?.completedSteps?.includes(index) ? '✓' : index + 1} +
+ + {step.title || `Step ${index + 1}`} + +
+ ))} +
+
+ ); +}; + +export default TutorialProgress; diff --git a/frontend/src/components/Tutorial/TutorialQuiz.jsx b/frontend/src/components/Tutorial/TutorialQuiz.jsx new file mode 100644 index 0000000..201ab98 --- /dev/null +++ b/frontend/src/components/Tutorial/TutorialQuiz.jsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; + +const TutorialQuiz = ({ quiz, onComplete }) => { + const [selectedAnswer, setSelectedAnswer] = useState(null); + const [showFeedback, setShowFeedback] = useState(false); + const [isCorrect, setIsCorrect] = useState(false); + + const handleAnswerSelect = (index) => { + setSelectedAnswer(index); + }; + + const handleSubmit = () => { + if (selectedAnswer === null) return; + + const correct = selectedAnswer === quiz.correct; + setIsCorrect(correct); + setShowFeedback(true); + + if (correct) { + setTimeout(() => { + onComplete(); + }, 1500); + } + }; + + return ( +
+

Quiz Time!

+
+

{quiz.question}

+
+
+ {quiz.options.map((option, index) => ( + + ))} +
+ {!showFeedback && ( + + )} + {showFeedback && ( +
+ {isCorrect ? 'Correct! Well done!' : 'Try again! Review the material and give it another shot.'} +
+ )} +
+ ); +}; + +export default TutorialQuiz; diff --git a/frontend/src/components/Tutorial/TutorialStep.jsx b/frontend/src/components/Tutorial/TutorialStep.jsx new file mode 100644 index 0000000..2e197ef --- /dev/null +++ b/frontend/src/components/Tutorial/TutorialStep.jsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { dracula } from 'react-syntax-highlighter/dist/esm/styles/prism'; + +const TutorialStep = ({ step, onComplete }) => { + const [isCompleted, setIsCompleted] = useState(false); + + const handleComplete = () => { + setIsCompleted(true); + onComplete(); + }; + + const renderContent = () => { + if (step.codeExample) { + return ( +
+ + {step.codeExample} + +
+ ); + } + return
{step.content}
; + }; + + return ( +
+

+ {step.title} +

+ {renderContent()} + {step.interactive && !isCompleted && ( +
+ +
+ )} +
+ ); +}; + +export default TutorialStep; diff --git a/frontend/src/pages/TutorialPage.jsx b/frontend/src/pages/TutorialPage.jsx new file mode 100644 index 0000000..0f2a264 --- /dev/null +++ b/frontend/src/pages/TutorialPage.jsx @@ -0,0 +1,60 @@ +import React, { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import TutorialLayout from '../components/Tutorial/TutorialLayout'; +import { getTutorial } from '../services/tutorialService'; + +const TutorialPage = () => { + const { tutorialId } = useParams(); + const navigate = useNavigate(); + const [tutorial, setTutorial] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + const loadTutorial = async () => { + try { + const tutorialData = await getTutorial(tutorialId); + if (!tutorialData) { + setError('Tutorial not found'); + return; + } + setTutorial(tutorialData); + } catch (err) { + setError('Failed to load tutorial'); + } + }; + + loadTutorial(); + }, [tutorialId]); + + if (error) { + return ( +
+
+

{error}

+ +
+
+ ); + } + + if (!tutorial) { + return ( +
+
+
+
+
+
+
+ ); + } + + return ; +}; + +export default TutorialPage; diff --git a/frontend/src/pages/TutorialsPage.jsx b/frontend/src/pages/TutorialsPage.jsx new file mode 100644 index 0000000..4ae4de0 --- /dev/null +++ b/frontend/src/pages/TutorialsPage.jsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +const TutorialsPage = ({ darkMode }) => { + const navigate = useNavigate(); + + const tutorials = [ + { + id: 'sorting', + title: 'Sorting Algorithms', + description: 'Learn about different sorting algorithms and their implementations', + difficulty: 'Beginner', + duration: '30 minutes', + icon: '🔄' + }, + { + id: 'graph', + title: 'Graph Algorithms', + description: 'Master graph traversal and pathfinding algorithms', + difficulty: 'Intermediate', + duration: '45 minutes', + icon: '🕸️' + }, + { + id: 'string', + title: 'String Algorithms', + description: 'Explore pattern matching and string manipulation', + difficulty: 'Intermediate', + duration: '40 minutes', + icon: '📝' + }, + { + id: 'dp', + title: 'Dynamic Programming', + description: 'Learn optimization through dynamic programming', + difficulty: 'Advanced', + duration: '60 minutes', + icon: '🧮' + } + ]; + + const difficultyColors = { + Beginner: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + Intermediate: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + Advanced: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' + }; + + return ( +
+
+

+ Interactive Tutorials +

+

+ Choose a tutorial to start learning algorithms through interactive visualizations +

+
+ +
+ {tutorials.map((tutorial) => ( +
navigate(`/tutorial/${tutorial.id}`)} + > +
+
+ {tutorial.icon} +
+

+ {tutorial.title} +

+ + {tutorial.difficulty} + +
+
+

+ {tutorial.description} +

+
+ ⏱️ {tutorial.duration} +
+
+
+ ))} +
+
+ ); +}; + +export default TutorialsPage; diff --git a/frontend/src/services/tutorialService.js b/frontend/src/services/tutorialService.js new file mode 100644 index 0000000..d6e3511 --- /dev/null +++ b/frontend/src/services/tutorialService.js @@ -0,0 +1,113 @@ +// Tutorial service to manage tutorial data and progress +const tutorialData = { + 'sorting': { + id: 'sorting', + title: 'Sorting Algorithms Mastery', + difficulty: 'Beginner', + prerequisites: ['basic-arrays'], + estimatedTime: '30 minutes', + steps: [ + { + id: 'intro', + title: 'Introduction to Sorting', + type: 'explanation', + content: 'Sorting algorithms are fundamental building blocks in computer science...', + interactive: true + }, + { + id: 'bubble-sort', + title: 'Bubble Sort', + type: 'explanation', + content: 'Bubble sort is a simple sorting algorithm...', + codeExample: `function bubbleSort(arr) { + const n = arr.length; + for (let i = 0; i < n - 1; i++) { + for (let j = 0; j < n - i - 1; j++) { + if (arr[j] > arr[j + 1]) { + // Swap elements + [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; + } + } + } + return arr; +}`, + interactive: true, + quiz: { + question: 'What is the time complexity of bubble sort?', + options: ['O(n)', 'O(n²)', 'O(n log n)', 'O(1)'], + correct: 1 + } + } + ] + }, + 'graph': { + id: 'graph', + title: 'Graph Algorithms', + difficulty: 'Intermediate', + prerequisites: ['basic-arrays', 'sorting'], + estimatedTime: '45 minutes', + steps: [ + { + id: 'intro', + title: 'Introduction to Graphs', + type: 'explanation', + content: 'Graphs are versatile data structures...', + interactive: true + } + ] + } +}; + +export const getTutorial = async (id) => { + // Simulate API call + return new Promise((resolve) => { + setTimeout(() => { + resolve(tutorialData[id]); + }, 500); + }); +}; + +export const getTutorialProgress = (tutorialId) => { + const progress = localStorage.getItem('tutorialProgress'); + if (progress) { + const allProgress = JSON.parse(progress); + return allProgress[tutorialId]; + } + return null; +}; + +export const saveTutorialProgress = (tutorialId, progress) => { + const existingProgress = localStorage.getItem('tutorialProgress'); + const allProgress = existingProgress ? JSON.parse(existingProgress) : {}; + + allProgress[tutorialId] = progress; + localStorage.setItem('tutorialProgress', JSON.stringify(allProgress)); +}; + +export const generateCertificate = (tutorialId) => { + const progress = getTutorialProgress(tutorialId); + if (!progress || !tutorialData[tutorialId]) { + return null; + } + + const tutorial = tutorialData[tutorialId]; + const completionPercentage = (progress.completedSteps.length / tutorial.steps.length) * 100; + + if (completionPercentage < 100) { + return null; + } + + return { + tutorialTitle: tutorial.title, + completionDate: new Date().toISOString(), + difficulty: tutorial.difficulty, + studentName: 'Student Name' // TODO: Add user management + }; +}; + +export default { + getTutorial, + getTutorialProgress, + saveTutorialProgress, + generateCertificate +}; From 33de05ce35a436fb74ff0788b580bb12cc13f77b Mon Sep 17 00:00:00 2001 From: Saloni Tarone Date: Sat, 4 Oct 2025 17:36:53 +0530 Subject: [PATCH 2/2] Update Also backend Signed-off-by: Saloni Tarone --- backend/main.py | 29 +++++- backend/models/tutorial_models.py | 31 ++++++ backend/services/tutorial_service.py | 95 +++++++++++++++++++ .../components/Tutorial/TutorialLayout.jsx | 29 ++---- frontend/src/pages/TutorialsPage.jsx | 52 ++++------ frontend/src/services/api.js | 37 ++++++++ 6 files changed, 216 insertions(+), 57 deletions(-) create mode 100644 backend/models/tutorial_models.py create mode 100644 backend/services/tutorial_service.py diff --git a/backend/main.py b/backend/main.py index de381ed..c62f4ac 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,11 +1,13 @@ import os from pathlib import Path -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from pydantic import BaseModel, ConfigDict from typing import List, Optional, Dict, Any +from services.tutorial_service import TutorialService +from models.tutorial_models import Tutorial import uvicorn app = FastAPI( @@ -14,6 +16,31 @@ version="1.0.0" ) +# Tutorial routes +@app.get("/api/tutorials", response_model=List[Tutorial]) +async def get_tutorials( + difficulty: Optional[str] = Query(None, description="Filter tutorials by difficulty level"), + category: Optional[str] = Query(None, description="Filter tutorials by category") +): + """ + Get all tutorials with optional filtering by difficulty or category. + """ + if difficulty: + return TutorialService.get_tutorials_by_difficulty(difficulty) + elif category: + return TutorialService.get_tutorials_by_category(category) + return TutorialService.get_all_tutorials() + +@app.get("/api/tutorials/{tutorial_id}", response_model=Tutorial) +async def get_tutorial(tutorial_id: str): + """ + Get a specific tutorial by ID. + """ + tutorial = TutorialService.get_tutorial_by_id(tutorial_id) + if not tutorial: + raise HTTPException(status_code=404, detail="Tutorial not found") + return tutorial + # Environment detection ENVIRONMENT = os.getenv("ENVIRONMENT", "development") IS_PRODUCTION = ENVIRONMENT == "production" diff --git a/backend/models/tutorial_models.py b/backend/models/tutorial_models.py new file mode 100644 index 0000000..402a6a7 --- /dev/null +++ b/backend/models/tutorial_models.py @@ -0,0 +1,31 @@ +from typing import List, Optional +from pydantic import BaseModel + +class QuizOption(BaseModel): + text: str + isCorrect: bool + +class Quiz(BaseModel): + question: str + options: List[QuizOption] + explanation: Optional[str] = None + +class TutorialStep(BaseModel): + id: str + type: str # 'explanation', 'quiz', 'interactive' + title: str + content: str + quiz: Optional[Quiz] = None + codeExample: Optional[str] = None + interactive: bool = False + +class Tutorial(BaseModel): + id: str + title: str + description: str + difficulty: str # 'Beginner', 'Intermediate', 'Advanced' + estimatedTime: str + prerequisites: List[str] + steps: List[TutorialStep] + category: str # 'sorting', 'graph', 'string', 'dp' + icon: str diff --git a/backend/services/tutorial_service.py b/backend/services/tutorial_service.py new file mode 100644 index 0000000..ac3fc3a --- /dev/null +++ b/backend/services/tutorial_service.py @@ -0,0 +1,95 @@ +from typing import Dict, List, Optional +from models.tutorial_models import Tutorial, TutorialStep, Quiz, QuizOption + +# Mock database of tutorials +TUTORIALS_DB: Dict[str, Tutorial] = { + "sorting": Tutorial( + id="sorting", + title="Sorting Algorithms Mastery", + description="Learn about different sorting algorithms and their implementations", + difficulty="Beginner", + estimatedTime="30 minutes", + prerequisites=["basic-arrays"], + category="sorting", + icon="🔄", + steps=[ + TutorialStep( + id="intro", + type="explanation", + title="Introduction to Sorting", + content="Sorting is a fundamental operation in computer science...", + interactive=True + ), + TutorialStep( + id="bubble-sort", + type="explanation", + title="Bubble Sort Algorithm", + content="Bubble Sort is the simplest sorting algorithm...", + codeExample="""def bubble_sort(arr): + n = len(arr) + for i in range(n): + for j in range(0, n-i-1): + if arr[j] > arr[j+1]: + arr[j], arr[j+1] = arr[j+1], arr[j] + return arr""", + interactive=True, + quiz=Quiz( + question="What is the time complexity of Bubble Sort?", + options=[ + QuizOption(text="O(n)", isCorrect=False), + QuizOption(text="O(n²)", isCorrect=True), + QuizOption(text="O(n log n)", isCorrect=False), + QuizOption(text="O(1)", isCorrect=False) + ], + explanation="Bubble Sort uses nested loops, leading to O(n²) time complexity" + ) + ) + ] + ), + "graph": Tutorial( + id="graph", + title="Graph Algorithms", + description="Master graph traversal and pathfinding algorithms", + difficulty="Intermediate", + estimatedTime="45 minutes", + prerequisites=["basic-arrays", "sorting"], + category="graph", + icon="🕸️", + steps=[ + TutorialStep( + id="intro", + type="explanation", + title="Introduction to Graphs", + content="Graphs are versatile data structures...", + interactive=True + ) + ] + ) +} + +class TutorialService: + @staticmethod + def get_all_tutorials() -> List[Tutorial]: + """Get all available tutorials.""" + return list(TUTORIALS_DB.values()) + + @staticmethod + def get_tutorial_by_id(tutorial_id: str) -> Optional[Tutorial]: + """Get a specific tutorial by ID.""" + return TUTORIALS_DB.get(tutorial_id) + + @staticmethod + def get_tutorials_by_difficulty(difficulty: str) -> List[Tutorial]: + """Get tutorials filtered by difficulty level.""" + return [ + tutorial for tutorial in TUTORIALS_DB.values() + if tutorial.difficulty.lower() == difficulty.lower() + ] + + @staticmethod + def get_tutorials_by_category(category: str) -> List[Tutorial]: + """Get tutorials filtered by category.""" + return [ + tutorial for tutorial in TUTORIALS_DB.values() + if tutorial.category.lower() == category.lower() + ] diff --git a/frontend/src/components/Tutorial/TutorialLayout.jsx b/frontend/src/components/Tutorial/TutorialLayout.jsx index 9754729..aef439f 100644 --- a/frontend/src/components/Tutorial/TutorialLayout.jsx +++ b/frontend/src/components/Tutorial/TutorialLayout.jsx @@ -11,28 +11,13 @@ const TutorialLayout = ({ tutorialId }) => { useEffect(() => { // Load tutorial data const loadTutorial = async () => { - // TODO: Fetch tutorial data from backend - // For now using mock data - const mockTutorial = { - id: "sorting-tutorial", - title: "Sorting Algorithms Mastery", - steps: [ - { - id: "intro", - type: "explanation", - content: "Sorting is fundamental...", - interactive: true, - quiz: { - question: "What is the time complexity of bubble sort?", - options: ["O(n)", "O(n²)", "O(n log n)", "O(1)"], - correct: 1 - } - } - ], - prerequisites: ["basic-arrays"], - estimatedTime: "30 minutes" - }; - setCurrentTutorial(mockTutorial); + try { + const tutorialData = await getTutorialById(tutorialId); + setCurrentTutorial(tutorialData); + } catch (error) { + console.error('Error loading tutorial:', error); + // Handle error appropriately + } }; loadTutorial(); diff --git a/frontend/src/pages/TutorialsPage.jsx b/frontend/src/pages/TutorialsPage.jsx index 4ae4de0..de43b54 100644 --- a/frontend/src/pages/TutorialsPage.jsx +++ b/frontend/src/pages/TutorialsPage.jsx @@ -3,41 +3,25 @@ import { useNavigate } from 'react-router-dom'; const TutorialsPage = ({ darkMode }) => { const navigate = useNavigate(); + const [tutorials, setTutorials] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - const tutorials = [ - { - id: 'sorting', - title: 'Sorting Algorithms', - description: 'Learn about different sorting algorithms and their implementations', - difficulty: 'Beginner', - duration: '30 minutes', - icon: '🔄' - }, - { - id: 'graph', - title: 'Graph Algorithms', - description: 'Master graph traversal and pathfinding algorithms', - difficulty: 'Intermediate', - duration: '45 minutes', - icon: '🕸️' - }, - { - id: 'string', - title: 'String Algorithms', - description: 'Explore pattern matching and string manipulation', - difficulty: 'Intermediate', - duration: '40 minutes', - icon: '📝' - }, - { - id: 'dp', - title: 'Dynamic Programming', - description: 'Learn optimization through dynamic programming', - difficulty: 'Advanced', - duration: '60 minutes', - icon: '🧮' - } - ]; + useEffect(() => { + const fetchTutorials = async () => { + try { + const data = await getTutorials(); + setTutorials(data); + } catch (err) { + setError('Failed to load tutorials'); + console.error('Error fetching tutorials:', err); + } finally { + setLoading(false); + } + }; + + fetchTutorials(); + }, []); const difficultyColors = { Beginner: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index f541d43..dcfc6bb 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -322,6 +322,43 @@ function generateFallbackSteps(algorithm, graphData) { return { steps }; } +// Tutorial API endpoints +export const getTutorials = async (filters = {}) => { + const { difficulty, category } = filters; + const params = new URLSearchParams(); + if (difficulty) params.append('difficulty', difficulty); + if (category) params.append('category', category); + + try { + const response = await axios.get(`${getApiBaseUrl()}/api/tutorials?${params.toString()}`); + return response.data; + } catch (error) { + console.error('Error fetching tutorials:', error); + throw error; + } +}; + +export const getTutorialById = async (tutorialId) => { + try { + const response = await axios.get(`${getApiBaseUrl()}/api/tutorials/${tutorialId}`); + return response.data; + } catch (error) { + console.error(`Error fetching tutorial ${tutorialId}:`, error); + throw error; + } +}; + +// Update the tutorial service to use the API +export const updateTutorialProgress = async (tutorialId, progress) => { + // Store progress locally for now + // In a real application, this would be an API call + const existingProgress = localStorage.getItem('tutorialProgress'); + const allProgress = existingProgress ? JSON.parse(existingProgress) : {}; + allProgress[tutorialId] = progress; + localStorage.setItem('tutorialProgress', JSON.stringify(allProgress)); + return progress; +}; + console.log(`🔗 API Base URL: ${API_BASE_URL}`); console.log(`🏗️ Environment: ${process.env.NODE_ENV}`);