diff --git a/src/app/(dashboard)/dashboard/(task)/layout.tsx b/src/app/(dashboard)/dashboard/(task)/layout.tsx
new file mode 100644
index 0000000..f8aa855
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/(task)/layout.tsx
@@ -0,0 +1,19 @@
+import { Metadata } from "next";
+import React from "react";
+import Header from "@/components/task/header";
+
+export const metadata: Metadata = {
+ title: "Task | Skilled Ai",
+ description: "Skilled Ai",
+ };
+
+const Layout = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+
+ {children}
+
+ );
+};
+
+export default Layout;
diff --git a/src/app/(dashboard)/dashboard/(task)/task/[...taskId]/page.tsx b/src/app/(dashboard)/dashboard/(task)/task/[...taskId]/page.tsx
new file mode 100644
index 0000000..064425d
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/(task)/task/[...taskId]/page.tsx
@@ -0,0 +1,170 @@
+import React from "react";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import {
+ ResizableHandle,
+ ResizablePanel,
+ ResizablePanelGroup,
+} from "@/components/ui/resizable";
+import { unified } from "unified";
+import remarkParse from "remark-parse";
+import remarkRehype from "remark-rehype";
+import rehypeStringify from "rehype-stringify";
+import rehypePrettyCode from "rehype-pretty-code";
+import markdownStyles from "@/styles/markdown-styles.module.css";
+import { tutorialData } from "@/constants/tutorial-data";
+import { notFound } from "next/navigation";
+import { buttonVariants } from "@/components/ui/button";
+import Link from "next/link";
+import { cn } from "@/lib/utils";
+import EditorWindow from "@/components/task/editor-window";
+import { LANGUAGE_VERSIONS } from "@/constants/language-options";
+
+async function main(tutorialCode: string) {
+ const file = await unified()
+ .use(remarkParse)
+ .use(remarkRehype)
+ .use(rehypePrettyCode, {})
+ .use(rehypeStringify)
+ .process(tutorialCode);
+
+ return file;
+}
+
+const languages = Object.keys(LANGUAGE_VERSIONS);
+
+const Page = async ({ params }: { params: { taskId: string[] } }) => {
+ if (!params.taskId[0] || !languages.includes(params.taskId[0])) {
+ notFound();
+ }
+
+ const langTutorial = tutorialData.find(
+ (t) => t.language === params.taskId[0],
+ )!;
+
+ const tutorial = langTutorial.tutorial.find(
+ (t) => t.id === (Number(params.taskId[1]) || 1),
+ );
+
+ const tutorialIndex = langTutorial.tutorial.findIndex(
+ (t) => t.id === (Number(params.taskId[1]) || 1),
+ );
+
+ if (!tutorial || tutorialIndex === -1) {
+ notFound();
+ }
+
+ const code = await main(tutorial?.content || "");
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {langTutorial?.tutorial[tutorialIndex - 1] && (
+
+ Prev
+
+ )}
+
+
+
+ {langTutorial?.tutorial[tutorialIndex + 1] && (
+
+ Next
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {langTutorial?.tutorial[tutorialIndex - 1] && (
+
+ Prev
+
+ )}
+
+
+
+ {langTutorial?.tutorial[tutorialIndex + 1] && (
+
+ Next
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Page;
diff --git a/src/components/task/code-editor.tsx b/src/components/task/code-editor.tsx
new file mode 100644
index 0000000..12a2d20
--- /dev/null
+++ b/src/components/task/code-editor.tsx
@@ -0,0 +1,135 @@
+import React, { useCallback } from "react";
+import Editor from "@monaco-editor/react";
+
+const activateMonacoJSXHighlighter = async (monacoEditor: any, monaco: any) => {
+ // monaco-jsx-highlighter depends on these in addition to Monaco and an instance of a Monaco Editor.
+ const { default: traverse } = await import("@babel/traverse");
+ const { parse } = await import("@babel/parser");
+ // >>> The star of the show =P >>>
+ const {
+ default: MonacoJSXHighlighter,
+ JSXTypes,
+ makeBabelParse, //By @HaimCandiTech
+ } = await import(
+ // @ts-ignore
+ "monaco-jsx-highlighter" // Note: there is a polyfilled version alongside the regular version.
+ ); // For example, starting with 2.0.2, 2.0.2-polyfilled is also available.
+
+ const parseJSX = makeBabelParse(parse, true); // param0:Babel's parse, param1: default config for JSX syntax (false), TSX (true).
+ // Instantiate the highlighter
+ const monacoJSXHighlighter = new MonacoJSXHighlighter(
+ monaco, // references Range and other APIs
+ parseJSX, // obtains an AST, internally passes to parse options: {...options, sourceType: "module",plugins: ["jsx"],errorRecovery: true}
+ traverse, // helps collecting the JSX expressions within the AST
+ monacoEditor, // highlights the content of that editor via decorations
+ );
+ // Start the JSX highlighting and get the dispose function
+ let disposeJSXHighlighting =
+ monacoJSXHighlighter.highlightOnDidChangeModelContent();
+ // Enhance monaco's editor.action.commentLine with JSX commenting and get its disposer
+ let disposeJSXCommenting = monacoJSXHighlighter.addJSXCommentCommand();
+ // <<< You are all set. >>>
+
+ // Optional: customize the color font in JSX texts (style class JSXElement.JSXText.tastyPizza from ./index.css)
+ JSXTypes.JSXText.options.inlineClassName = "JSXElement.JSXText.tastyPizza";
+ // more details here: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IModelDecorationOptions.html
+ // console.log(
+ // "Customize each JSX expression type's options, they must match monaco.editor.IModelDecorationOptions:",
+ // JSXTypes,
+ // );
+
+ // This example's shorthands for toggling actions
+ const toggleJSXHighlighting = () => {
+ if (disposeJSXHighlighting) {
+ disposeJSXHighlighting();
+ disposeJSXHighlighting = null;
+ return false;
+ }
+
+ disposeJSXHighlighting =
+ monacoJSXHighlighter.highlightOnDidChangeModelContent();
+ return true;
+ };
+
+ const toggleJSXCommenting = () => {
+ if (disposeJSXCommenting) {
+ disposeJSXCommenting();
+ disposeJSXCommenting = null;
+ return false;
+ }
+
+ disposeJSXCommenting = monacoJSXHighlighter.addJSXCommentCommand();
+ return true;
+ };
+
+ const isToggleJSXHighlightingOn = () => !!disposeJSXHighlighting;
+ const isToggleJSXCommentingOn = () => !!disposeJSXCommenting;
+
+ return {
+ monacoJSXHighlighter,
+ toggleJSXHighlighting,
+ toggleJSXCommenting,
+ isToggleJSXHighlightingOn,
+ isToggleJSXCommentingOn,
+ };
+};
+
+type CodeEditorProps = {
+ // code: string;
+ defaultCode?: string;
+ onChange: (value: string | undefined) => void;
+ theme: string;
+ language: string;
+ codeValue: string;
+};
+export function CodeEditor({
+ defaultCode,
+ onChange,
+ theme,
+ language,
+ codeValue,
+}: CodeEditorProps) {
+ // const [value, setValue] = useState("");
+
+ const handleEditorDidMount = useCallback((monacoEditor: any, monaco: any) => {
+ // monacoEditor.updateOptions({ wordWrap: "on" });
+ activateMonacoJSXHighlighter(monacoEditor, monaco)
+ .then((monacoJSXHighlighterRefCurrent) => {
+ // monacoJSXHighlighterRef.current = monacoJSXHighlighterRefCurrent;
+ // setIsEditorReady(!!monacoEditor);
+ // setIsJSXHighlightingOn(
+ // monacoJSXHighlighterRefCurrent.isToggleJSXHighlightingOn()
+ // );
+ // setIsJSXCommentingOn(
+ // monacoJSXHighlighterRefCurrent.isToggleJSXCommentingOn()
+ // );
+ })
+ .catch((e) => {
+ // console.log(e)
+ });
+ }, []);
+
+ // const handleEditorChange = (value: string | undefined) => {
+ // setValue(value || "");
+ // onChange(value || "");
+ // };
+
+ return (
+
+ );
+}
diff --git a/src/components/task/compiler.ts b/src/components/task/compiler.ts
new file mode 100644
index 0000000..3813bfb
--- /dev/null
+++ b/src/components/task/compiler.ts
@@ -0,0 +1,27 @@
+//@ts-ignore
+import { transform } from "@babel/standalone";
+
+const compile = (input: string) =>
+ transform(input, {
+ filename: "random.tsx",
+ presets: ["react", "es2017"]
+ })?.code;
+
+export const compileCode = (code: string) => {
+ // try {
+
+
+ const compiled = compile(
+ code.replace(
+ "import * as React from 'react'; //don't change this line\n",
+ ""
+ )
+ )?.replace('"use strict";\n', "");
+
+ //@ts-ignore
+ return new Function("React", "", `return ${compiled};`);
+// } catch (error) {
+// console.log(error)
+// // return new Function("React", "", `return ;`);
+// }
+};
\ No newline at end of file
diff --git a/src/components/task/editor-window.tsx b/src/components/task/editor-window.tsx
new file mode 100644
index 0000000..6b413a5
--- /dev/null
+++ b/src/components/task/editor-window.tsx
@@ -0,0 +1,150 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import { compileCode } from "./compiler";
+import { v4 as uuid } from "uuid";
+import { CodeEditor } from "./code-editor";
+import { ErrorBoundary } from "react-error-boundary";
+import PreviewErrorFallback from "./preview-error-fallback";
+import {
+ ResizableHandle,
+ ResizablePanel,
+ ResizablePanelGroup,
+} from "@/components/ui/resizable";
+import { ThemeDropdown } from "./theme-dropdown";
+import LanguagesDropdown from "./languages-dropdown";
+import Output from "./output";
+import { tutorialData } from "@/constants/tutorial-data";
+import { notFound, useRouter } from "next/navigation";
+import { defineTheme } from "@/utils/define-theme";
+import Preview from "./preview";
+import { MyComponents } from "./my-components";
+
+
+
+export default function EditorWindow({
+ language: lang,
+ step,
+}: {
+ language: string;
+ step: number;
+}) {
+ const [componentName, setComponentName] = useState("");
+ const [theme, setTheme] = useState("cobalt");
+ // const [language, setLanguage] = useState(languageOptions[0]);
+ // const [language, setLanguage] = useState("react");
+ // const [defaultCode, setDefaultCode] = useState("");
+ const [codeValue, setCodeValue] = useState("");
+ const router = useRouter();
+
+ const handleCodeChange = (code: string | undefined) => {
+ try {
+ setCodeValue(code || "");
+ const func = compileCode(code || "");
+ const id = uuid();
+ MyComponents[id] = func(React);
+ setComponentName(id);
+ } catch (err) {
+ console.log(err);
+ // setComponentName("error");
+ }
+ };
+
+ useEffect(() => {
+ defineTheme("oceanic-next").then((_) => setTheme("oceanic-next"));
+ }, []);
+
+ function handleThemeChange(th: any) {
+ const theme = th;
+ // console.log("theme...", theme);
+
+ if (["light", "vs-dark"].includes(theme)) {
+ setTheme(theme);
+ } else {
+ defineTheme(theme).then((_) => setTheme(theme));
+ }
+ }
+
+ const handleLanguageChange = (lang: string) => {
+ router.push(`/dashboard/task/${lang}/1`);
+ // try {
+ // if (lang === "react") {
+ // const func = compileCode(CODE_SNIPPETS[lang]);
+ // const id = uuid();
+ // MyComponents[id] = func(React);
+ // setComponentName(id);
+ // }
+
+ // setLanguage(lang);
+ // // @ts-ignore
+ // setCodeValue(CODE_SNIPPETS[lang]);
+ // // @ts-ignore
+ // // setDefaultCode(CODE_SNIPPETS[language]);
+ // } catch (error) {
+ // // console.log(error);
+ // }
+ };
+
+ const tutorial = tutorialData
+ .find((t) => t.language === lang)
+ ?.tutorial.find((t) => t.id === step);
+
+ if (!tutorial) {
+ notFound();
+ }
+ // console.log(tutorial);
+
+ return (
+
+
+
+
+
+
+
+
+ {lang === "react" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
+
+
diff --git a/src/components/task/header.tsx b/src/components/task/header.tsx
new file mode 100644
index 0000000..e77f3a9
--- /dev/null
+++ b/src/components/task/header.tsx
@@ -0,0 +1,24 @@
+import ThemeToggle from "@/components/layout/ThemeToggle/theme-toggle";
+import Link from "next/link";
+import React from "react";
+
+const Header = () => {
+ return (
+
+ );
+};
+
+export default Header;
diff --git a/src/components/task/languages-dropdown.tsx b/src/components/task/languages-dropdown.tsx
new file mode 100644
index 0000000..9b28b3b
--- /dev/null
+++ b/src/components/task/languages-dropdown.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { LANGUAGE_VERSIONS } from "@/constants/language-options";
+// import { ScrollArea } from "@/components/ui/scroll-area";
+// import Link from "next/link";
+
+const languages = Object.entries(LANGUAGE_VERSIONS);
+
+const LanguagesDropdown = ({ handleLanguageChange, language }: any) => {
+ return (
+
+ );
+};
+
+export default LanguagesDropdown;
diff --git a/src/components/task/my-components.tsx b/src/components/task/my-components.tsx
new file mode 100644
index 0000000..2dc1234
--- /dev/null
+++ b/src/components/task/my-components.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { compileCode } from "./compiler";
+import { CODE_SNIPPETS } from "@/constants/code-snippets";
+
+export const MyComponents: { [key: string]: any } = {
+ default: (() => {
+ const func = compileCode(CODE_SNIPPETS["react"]);
+ return func(React);
+ })(),
+ error: function () {
+ return Error
;
+ },
+ };
\ No newline at end of file
diff --git a/src/components/task/output.tsx b/src/components/task/output.tsx
new file mode 100644
index 0000000..bbc75ac
--- /dev/null
+++ b/src/components/task/output.tsx
@@ -0,0 +1,53 @@
+import { Button } from "@/components/ui/button";
+import { useState } from "react";
+import { cn } from "@/lib/utils";
+import { useToast } from "@/components/ui/use-toast";
+import { executeCode } from "@/lib/api";
+
+const Output = ({ codeValue, language }: any) => {
+ const { toast } = useToast();
+ const [output, setOutput] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isError, setIsError] = useState(false);
+
+ const runCode = async () => {
+ if (!codeValue) return;
+ try {
+ setIsLoading(true);
+ const { run: result } = await executeCode(language, codeValue);
+ setOutput(result.output.split("\n"));
+ result.stderr ? setIsError(true) : setIsError(false);
+ } catch (error) {
+ // console.log(error);
+ toast({
+ variant: "destructive",
+ title: "An error occurred.",
+ description: (error as Error).message || "Unable to run code",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+ {/*
Output
*/}
+
+
+ {output
+ ? output.map((line: any, i: number) =>
{line}
)
+ : 'Click "Run Code" to see the output here'}
+
+
+ );
+};
+export default Output;
diff --git a/src/components/task/preview-error-fallback.tsx b/src/components/task/preview-error-fallback.tsx
new file mode 100644
index 0000000..b0c0164
--- /dev/null
+++ b/src/components/task/preview-error-fallback.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import { Button } from "@/components/ui/button";
+import React from "react";
+// import { useErrorBoundary } from "react-error-boundary";
+
+const PreviewErrorFallback = ({ error, resetErrorBoundary }: any) => {
+ // const { resetBoundary } = useErrorBoundary();
+
+ // console.log(error);
+ return (
+
+
+
Something went wrong!
+
{error.message}
+
+
+
+ );
+};
+
+export default PreviewErrorFallback;
diff --git a/src/components/task/preview.tsx b/src/components/task/preview.tsx
new file mode 100644
index 0000000..579813b
--- /dev/null
+++ b/src/components/task/preview.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { compileCode } from "./compiler";
+import { MyComponents } from "./my-components";
+
+export default function Preview({
+ componentName,
+ tutorialCode,
+}: {
+ componentName: string;
+ tutorialCode: string;
+}) {
+ // const Component =
+ // componentName && MyComponents[componentName] ? (
+ // MyComponents[componentName]
+ // ) : (
+ // <>>
+ // );
+ const Component =
+ componentName !== ""
+ ? MyComponents[componentName]
+ : compileCode(tutorialCode)(React);
+ return ;
+}
diff --git a/src/components/task/theme-dropdown.tsx b/src/components/task/theme-dropdown.tsx
new file mode 100644
index 0000000..1f72eae
--- /dev/null
+++ b/src/components/task/theme-dropdown.tsx
@@ -0,0 +1,44 @@
+import * as React from "react";
+
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+// @ts-ignore
+import monacoThemes from "monaco-themes/themes/themelist";
+import { ScrollArea } from "@/components/ui/scroll-area";
+
+export function ThemeDropdown({ handleThemeChange, theme }: any) {
+ const themes = Object.entries(monacoThemes)
+ // .filter(([themeId, themeName]) => themeId !== "cobalt2")
+ .map(([themeId, themeName]) => ({
+ label: themeName,
+ value: themeId,
+ key: themeId,
+ }));
+
+ return (
+
+ );
+}