浏览代码

add resizers on SandboxPage and other

yankevych0210 1 年之前
父节点
当前提交
4a61afe196
共有 31 个文件被更改,包括 518 次插入136 次删除
  1. 52 0
      package-lock.json
  2. 1 0
      package.json
  3. 2 1
      src/App.js
  4. 7 0
      src/assets/img/cssLogo.svg
  5. 7 0
      src/assets/img/htmlLogo.svg
  6. 7 0
      src/assets/img/jsLogo.svg
  7. 17 15
      src/components/common/Editor/Editor.jsx
  8. 20 14
      src/components/common/Editor/Editor.module.scss
  9. 0 16
      src/components/common/Editor/pen.css
  10. 114 0
      src/components/common/Editor/themes/twilight.css
  11. 47 0
      src/components/common/Editors/Editors.jsx
  12. 12 0
      src/components/common/Editors/Editors.module.scss
  13. 2 0
      src/components/common/HeaderPen/HeaderPen.module.scss
  14. 2 5
      src/components/common/Preview/Preview.jsx
  15. 10 2
      src/components/common/Preview/Preview.module.scss
  16. 16 9
      src/components/common/WorkCard/WorkCard.jsx
  17. 1 0
      src/components/common/WorkCard/WorkCard.module.scss
  18. 28 3
      src/components/common/WorkCard/WorkCardPopup/WorkCardPopup.jsx
  19. 9 1
      src/components/pages/HomePage/HomePage.jsx
  20. 1 1
      src/components/pages/LoginPage/LoginPage.jsx
  21. 3 2
      src/components/pages/PenPage/PenPage.jsx
  22. 31 56
      src/components/pages/PenPage/PenPage.module.scss
  23. 51 0
      src/components/pages/SandboxPage/SandboxPage.jsx
  24. 1 1
      src/components/pages/SignUpPage/SignUpPage.jsx
  25. 1 1
      src/components/pages/YourWorksPage/CreateWorkPopup/CreateWorkPopup.jsx
  26. 1 3
      src/hooks/useInput.js
  27. 14 0
      src/hooks/useLocalStorage.js
  28. 4 0
      src/scss/index.scss
  29. 0 6
      src/store/currentWork/currentWorkSlice.js
  30. 48 0
      src/store/works/actions/updateWorkInfo.js
  31. 9 0
      src/store/works/worksSlice.js

+ 52 - 0
package-lock.json

@@ -25,6 +25,7 @@
         "react-redux": "^8.0.5",
         "react-router-dom": "^6.8.1",
         "react-scripts": "5.0.1",
+        "react-simple-resizer": "^2.1.0",
         "redux": "^4.2.1",
         "redux-logger": "^3.0.6",
         "redux-thunk": "^2.4.2",
@@ -16010,6 +16011,18 @@
         }
       }
     },
+    "node_modules/react-simple-resizer": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/react-simple-resizer/-/react-simple-resizer-2.1.0.tgz",
+      "integrity": "sha512-iiPER+vuKsW5+6+HroNnahc2Cah6UpO4w9SeuGw1pf/6p/F/vAD4+4288yJS5fLY4xJpDNEiOiRBk2kLUui6nw==",
+      "dependencies": {
+        "rxjs": "^6.3.3"
+      },
+      "peerDependencies": {
+        "react": "^16.3.0",
+        "react-dom": "^16.4.2"
+      }
+    },
     "node_modules/read-cache": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -16675,6 +16688,22 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "node_modules/rxjs": {
+      "version": "6.6.7",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "dependencies": {
+        "tslib": "^1.9.0"
+      },
+      "engines": {
+        "npm": ">=2.0.0"
+      }
+    },
+    "node_modules/rxjs/node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+    },
     "node_modules/safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -30877,6 +30906,14 @@
         "workbox-webpack-plugin": "^6.4.1"
       }
     },
+    "react-simple-resizer": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/react-simple-resizer/-/react-simple-resizer-2.1.0.tgz",
+      "integrity": "sha512-iiPER+vuKsW5+6+HroNnahc2Cah6UpO4w9SeuGw1pf/6p/F/vAD4+4288yJS5fLY4xJpDNEiOiRBk2kLUui6nw==",
+      "requires": {
+        "rxjs": "^6.3.3"
+      }
+    },
     "read-cache": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -31371,6 +31408,21 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "rxjs": {
+      "version": "6.6.7",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "requires": {
+        "tslib": "^1.9.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+          "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+        }
+      }
+    },
     "safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",

+ 1 - 0
package.json

@@ -21,6 +21,7 @@
     "react-redux": "^8.0.5",
     "react-router-dom": "^6.8.1",
     "react-scripts": "5.0.1",
+    "react-simple-resizer": "^2.1.0",
     "redux": "^4.2.1",
     "redux-logger": "^3.0.6",
     "redux-thunk": "^2.4.2",

+ 2 - 1
src/App.js

@@ -5,12 +5,13 @@ import { HomePage } from "./components/pages/HomePage/HomePage";
 import { PenPage } from "./components/pages/PenPage/PenPage";
 import { SignUpPage } from "./components/pages/SignUpPage/SignUpPage";
 import { YourWorks } from "./components/pages/YourWorksPage/YourWorksPage";
+import { SandboxPage } from "./components/pages/SandboxPage/SandboxPage";
 
 function App() {
   return (
     <Routes>
       <Route path="/" element={<HomePage />} />
-      <Route path="/pen" element={<PenPage />} />
+      <Route path="/sandbox" element={<SandboxPage />} />
       <Route path="/login" element={<LoginPage />} />
       <Route path="/signup" element={<SignUpPage />} />
       <Route path="/your-works" element={<YourWorks />} />

+ 7 - 0
src/assets/img/cssLogo.svg

@@ -0,0 +1,7 @@
+<svg viewBox="0 0 15 15" class="file-type-icon" id="icon-file-css">
+  <rect fill="#0EBEFF" width="15" height="15" rx="4"></rect>
+  <path
+    d="M8 8.366l1.845 1.065a.507.507 0 0 0 .686-.181.507.507 0 0 0-.186-.685L8.5 7.5l1.845-1.065a.507.507 0 0 0 .186-.685.507.507 0 0 0-.686-.181L8 6.634v-2.13A.507.507 0 0 0 7.5 4c-.268 0-.5.225-.5.503v2.131L5.155 5.569a.507.507 0 0 0-.686.181.507.507 0 0 0 .186.685L6.5 7.5 4.655 8.565a.507.507 0 0 0-.186.685c.134.232.445.32.686.181L7 8.366v2.13c0 .271.224.504.5.504.268 0 .5-.225.5-.503V8.366z"
+    fill="#282828"
+  ></path>
+</svg>

+ 7 - 0
src/assets/img/htmlLogo.svg

@@ -0,0 +1,7 @@
+<svg viewBox="0 0 15 15" class="file-type-icon" id="icon-file-html">
+  <rect fill="#FF3C41" width="15" height="15" rx="4"></rect>
+  <path
+    d="M10.97 2.29a.563.563 0 0 0-.495-.29.572.572 0 0 0-.488.277l-5.905 9.86a.565.565 0 0 0-.007.574c.102.18.287.289.495.289a.572.572 0 0 0 .488-.277l5.905-9.86a.565.565 0 0 0 .007-.574"
+    fill="#28282B"
+  ></path>
+</svg>

+ 7 - 0
src/assets/img/jsLogo.svg

@@ -0,0 +1,7 @@
+<svg viewBox="0 0 15 15" class="file-type-icon" id="icon-file-js">
+  <rect fill="#FCD000" width="15" height="15" rx="4"></rect>
+  <path
+    d="M6.554 3.705c0 .267-.19.496-.452.543-1.2.217-2.12 1.61-2.12 3.275 0 1.665.92 3.057 2.12 3.274a.554.554 0 0 1-.205 1.087c-1.733-.322-3.022-2.175-3.022-4.361 0-2.187 1.289-4.04 3.022-4.362a.554.554 0 0 1 .657.544zm1.892 0c0-.347.316-.607.657-.544 1.733.322 3.022 2.175 3.022 4.362 0 2.186-1.289 4.04-3.022 4.361a.554.554 0 0 1-.205-1.087c1.2-.217 2.12-1.61 2.12-3.274 0-1.665-.92-3.058-2.12-3.275a.551.551 0 0 1-.452-.543z"
+    fill="#282828"
+  ></path>
+</svg>

+ 17 - 15
src/components/common/Editor/Editor.jsx

@@ -1,37 +1,40 @@
 import React, { useState } from "react";
 import "codemirror/lib/codemirror.css";
-import "codemirror/theme/material.css";
 import "codemirror/mode/xml/xml";
 import "codemirror/mode/javascript/javascript";
 import "codemirror/mode/css/css";
 import { Controlled as ControlledEditor } from "react-codemirror2";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faCompressAlt, faExpandAlt } from "@fortawesome/free-solid-svg-icons";
-import "./pen.css";
 import { useDispatch } from "react-redux";
 import style from "./Editor.module.scss";
-
+import "./themes/twilight.css";
+import { ReactComponent as HtmlLogo } from "../../../assets/img/htmlLogo.svg";
+import { ReactComponent as CssLogo } from "../../../assets/img/cssLogo.svg";
+import { ReactComponent as JsLogo } from "../../../assets/img/jsLogo.svg";
 export default function Editor(props) {
   const { language, displayName, value, onChange } = props;
-  const [open, setOpen] = useState(true);
   const dispatch = useDispatch();
 
+  const logos = {
+    xml: <HtmlLogo />,
+    css: <CssLogo />,
+    javascript: <JsLogo />,
+  };
+
   function handleChange(editor, data, value) {
     dispatch(onChange(value));
   }
 
   return (
-    <div className={open ? style.editorOpen : style.collapsed}>
+    <div className={style.editor}>
       <div className={style.header}>
-        {displayName}
-        <button
-          type="button"
-          className="expand-collapse-btn"
-          onClick={() => setOpen((prevOpen) => !prevOpen)}
-        >
-          <FontAwesomeIcon icon={open ? faCompressAlt : faExpandAlt} />
-        </button>
+        <h2>
+          {logos[language]}
+          {displayName}
+        </h2>
       </div>
+
       <ControlledEditor
         onBeforeChange={handleChange}
         value={value}
@@ -40,10 +43,9 @@ export default function Editor(props) {
           lineWrapping: true,
           lint: true,
           mode: language,
-          theme: "material",
+          theme: "twilight",
           lineNumbers: true,
           autoCorrect: true,
-          autocapitalize: true,
         }}
       />
     </div>

+ 20 - 14
src/components/common/Editor/Editor.module.scss

@@ -1,29 +1,35 @@
-.editorOpen {
-  flex-grow: 1;
-  flex-basis: 0;
+.editor {
   display: flex;
   flex-direction: column;
-  margin: 0 7px;
   background-color: black;
-  border-left: 1px solid #2f2f2f;
-  border-right: 1px solid #2f2f2f;
+  height: 100%;
+  width: auto;
 
   .header {
     display: flex;
-    justify-content: space-between;
+    align-items: center;
     background-color: black;
     color: white;
-    padding: 0.5rem 0.5rem 0.5rem 1rem;
-    // background-color: red;
+
+    h2 {
+      display: flex;
+      align-items: center;
+      font-size: 1.1rem;
+      color: #a8acba;
+      background-color: #1d1e22;
+      padding: 9px 12px;
+      border-top: 3px solid #34363d;
+    }
+
+    svg {
+      width: 17px;
+      margin-right: 8px;
+    }
   }
 
   .codeMirrorWrapper {
+    width: 100%;
     flex-grow: 1;
     overflow: hidden;
   }
 }
-
-.collapsed {
-  @extend .editorOpen;
-  flex-grow: 0;
-}

+ 0 - 16
src/components/common/Editor/pen.css

@@ -1,16 +0,0 @@
-.editor-container.collapsed .CodeMirror-scroll {
-  position: absolute;
-  overflow: hidden !important;
-}
-
-.expand-collapse-btn {
-  margin-left: 0.5rem;
-  background: none;
-  border: none;
-  color: white;
-  cursor: pointer;
-}
-
-.CodeMirror {
-  height: 100% !important;
-}

+ 114 - 0
src/components/common/Editor/themes/twilight.css

@@ -0,0 +1,114 @@
+.cm-s-twilight.CodeMirror {
+  background: #1d1e22;
+  color: #f7f7f7;
+} /**/
+.cm-s-twilight div.CodeMirror-selected {
+  background: #323232;
+} /**/
+.cm-s-twilight .CodeMirror-line::selection,
+.cm-s-twilight .CodeMirror-line > span::selection,
+.cm-s-twilight .CodeMirror-line > span > span::selection {
+  background: rgba(50, 50, 50, 0.99);
+}
+.cm-s-twilight .CodeMirror-line::-moz-selection,
+.cm-s-twilight .CodeMirror-line > span::-moz-selection,
+.cm-s-twilight .CodeMirror-line > span > span::-moz-selection {
+  background: rgba(50, 50, 50, 0.99);
+}
+
+.CodeMirror-gutters .CodeMirror-linenumbers {
+  background-color: #1d1e22;
+}
+
+.cm-s-twilight .CodeMirror-gutters {
+  background: #222;
+  border-right: 1px solid #aaa;
+}
+.cm-s-twilight .CodeMirror-guttermarker {
+  color: white;
+}
+.cm-s-twilight .CodeMirror-guttermarker-subtle {
+  color: #aaa;
+}
+.cm-s-twilight .CodeMirror-linenumber {
+  color: #aaa;
+}
+.cm-s-twilight .CodeMirror-cursor {
+  border-left: 1px solid white;
+}
+
+.cm-s-twilight .cm-keyword {
+  color: #f9ee98;
+} /**/
+.cm-s-twilight .cm-atom {
+  color: #fc0;
+}
+.cm-s-twilight .cm-number {
+  color: #ca7841;
+} /**/
+.cm-s-twilight .cm-def {
+  color: #8da6ce;
+}
+.cm-s-twilight span.cm-variable-2,
+.cm-s-twilight span.cm-tag {
+  color: #607392;
+} /**/
+.cm-s-twilight span.cm-variable-3,
+.cm-s-twilight span.cm-def,
+.cm-s-twilight span.cm-type {
+  color: #607392;
+} /**/
+.cm-s-twilight .cm-operator {
+  color: #cda869;
+} /**/
+.cm-s-twilight .cm-comment {
+  color: #777;
+  font-style: italic;
+  font-weight: normal;
+} /**/
+.cm-s-twilight .cm-string {
+  color: #8f9d6a;
+  font-style: italic;
+} /**/
+.cm-s-twilight .cm-string-2 {
+  color: #bd6b18;
+} /*?*/
+.cm-s-twilight .cm-meta {
+  background-color: #141414;
+  color: #f7f7f7;
+} /*?*/
+.cm-s-twilight .cm-builtin {
+  color: #cda869;
+} /*?*/
+.cm-s-twilight .cm-tag {
+  color: #997643;
+} /**/
+.cm-s-twilight .cm-attribute {
+  color: #d6bb6d;
+} /*?*/
+.cm-s-twilight .cm-header {
+  color: #ff6400;
+}
+.cm-s-twilight .cm-hr {
+  color: #aeaeae;
+}
+.cm-s-twilight .cm-link {
+  color: #ad9361;
+  font-style: italic;
+  text-decoration: none;
+} /**/
+.cm-s-twilight .cm-error {
+  border-bottom: 1px solid red;
+}
+
+.cm-s-twilight .CodeMirror-activeline-background {
+  background: #27282e;
+}
+.cm-s-twilight .CodeMirror-matchingbracket {
+  outline: 1px solid grey;
+  color: white !important;
+}
+
+.cm-s-twilight .CodeMirror-gutters {
+  border-right: 0px;
+}

+ 47 - 0
src/components/common/Editors/Editors.jsx

@@ -0,0 +1,47 @@
+import React from "react";
+import { Bar, Container, Section } from "react-simple-resizer";
+import Editor from "../Editor/Editor";
+import style from "./Editors.module.scss";
+
+export const Editors = (props) => {
+  const { html, setHtmlValue, css, setCssValue, js, setJsValue } = props;
+
+  return (
+    <Container width={"100vw"} height={"100%"} className={style.editors}>
+      <Bar className={style.bar} />
+      <Section
+        children={
+          <Editor
+            language="xml"
+            displayName={`HTML`}
+            value={html}
+            onChange={setHtmlValue}
+          />
+        }
+      />
+
+      <Bar className={style.bar} />
+      <Section
+        children={
+          <Editor
+            language="css"
+            displayName="CSS"
+            value={css}
+            onChange={setCssValue}
+          />
+        }
+      />
+      <Bar className={style.bar} />
+      <Section
+        children={
+          <Editor
+            language="javascript"
+            displayName="JS"
+            value={js}
+            onChange={setJsValue}
+          />
+        }
+      />
+    </Container>
+  );
+};

+ 12 - 0
src/components/common/Editors/Editors.module.scss

@@ -0,0 +1,12 @@
+.editors {
+  height: 100%;
+  display: flex;
+  background-color: black;
+
+  .bar {
+    background: black;
+    border-left: 1px solid #2f2f2f;
+    border-right: 1px solid #2f2f2f;
+    width: 15px;
+  }
+}

+ 2 - 0
src/components/common/HeaderPen/HeaderPen.module.scss

@@ -5,6 +5,8 @@
   @include flex($justify: space-between, $align: center);
   height: 65px;
 
+  margin-bottom: 1px;
+
   &::before {
     content: "";
     display: block;

+ 2 - 5
src/components/common/Preview/Preview.jsx

@@ -7,7 +7,7 @@ export const Preview = ({ html, css, js }) => {
   useEffect(() => {
     const timeout = setTimeout(() => {
       setSrcDoc(`
-            <html>
+            <html >
               <body>${html}</body>
               <style>${css}</style>
               <script>${js}</script>
@@ -23,10 +23,7 @@ export const Preview = ({ html, css, js }) => {
       <iframe
         srcDoc={srcDoc}
         title="output"
-        sandbox="allow-scripts"
-        frameBorder="none"
-        width="100%"
-        height="100%"
+        sandbox="allow-forms allow-modals allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation allow-downloads allow-presentation"
       />
     </div>
   );

+ 10 - 2
src/components/common/Preview/Preview.module.scss

@@ -1,4 +1,12 @@
 .pane {
-  height: 50vh;
-  display: flex;
+  width: 100%;
+  height: auto;
+
+  iframe {
+    display: block;
+    width: 100%;
+    height: 100%;
+    border: none;
+    transform-origin: 0 0;
+  }
 }

+ 16 - 9
src/components/common/WorkCard/WorkCard.jsx

@@ -2,25 +2,32 @@ import style from "./WorkCard.module.scss";
 import { NavLink } from "react-router-dom";
 import { usePopup } from "../../../hooks/usePopup";
 import { WorkCardPopup } from "./WorkCardPopup/WorkCardPopup";
-import { useDispatch } from "react-redux";
-import { setCurrentWork } from "../../../store/works/worksSlice";
+import { Preview } from "../Preview/Preview";
 
 export const WorkCard = ({ work }) => {
-  const dispatch = useDispatch();
   const { isPopupVisible, toggle, ref: menuRef } = usePopup();
+  const [html, css, js] = work.files;
 
   const openMenu = (e) => {
     e.preventDefault();
     toggle();
   };
+
   if (work.title === null) return null;
   return (
-    <NavLink
-      onClick={() => dispatch(setCurrentWork(work))}
-      className={style.card}
-      to={`/your-works/${work._id}`}
-    >
-      <div className={style.preview}></div>
+    <NavLink className={style.card} to={`/your-works/${work._id}`}>
+      <div style={{ pointerEvents: "none" }} className={style.preview}>
+        <Preview
+          html={html.text}
+          css={`
+            html {
+              zoom: 0.3;
+            }
+            ${css.text}
+          `}
+          js={js.text}
+        />
+      </div>
 
       <div className={style.info}>
         <span className={style.title}>{work.title}</span>

+ 1 - 0
src/components/common/WorkCard/WorkCard.module.scss

@@ -18,6 +18,7 @@
     width: 100%;
     height: 65%;
     background: white;
+    overflow: hidden;
   }
 
   .info {

+ 28 - 3
src/components/common/WorkCard/WorkCardPopup/WorkCardPopup.jsx

@@ -1,19 +1,44 @@
 import { useDispatch } from "react-redux";
 import { deleteWork } from "../../../../store/works/actions/deleteWork";
+import { updateWorkInfo } from "../../../../store/works/actions/updateWorkInfo";
 import style from "./WorkCardPopup.module.scss";
 
 export const WorkCardPopup = ({ menuRef, isVisible, work }) => {
   const dispatch = useDispatch();
 
-  // TODO: rename && changeDesc
   const renameWork = (e) => {
     e.preventDefault();
-    console.log("renameWork");
+
+    const newTitle = prompt(`rename work: ${work.title}`, work.title);
+    if (newTitle) {
+      const updatedWorkInfo = {
+        id: work._id,
+        title: newTitle,
+        description: work.description,
+        files: work.files,
+      };
+
+      dispatch(updateWorkInfo(updatedWorkInfo));
+    }
   };
 
   const changeDescription = (e) => {
     e.preventDefault();
-    console.log("changeDescription");
+
+    const newDescription = prompt(
+      `change description: ${work.title}`,
+      work.description
+    );
+    if (newDescription) {
+      const updatedWorkInfo = {
+        id: work._id,
+        title: work.title,
+        description: newDescription,
+        files: work.files,
+      };
+
+      dispatch(updateWorkInfo(updatedWorkInfo));
+    }
   };
 
   const handleDelete = (e) => {

+ 9 - 1
src/components/pages/HomePage/HomePage.jsx

@@ -1,5 +1,6 @@
 import { Link } from "react-router-dom";
 import { Header } from "../../common/Header/Header";
+import { Footer } from "../../common/Footer/Footer";
 import style from "./HomePage.module.scss";
 
 import { ReactComponent as HomeLine } from "../../../assets/img/homeline.svg";
@@ -9,8 +10,11 @@ import { ReactComponent as HomeLineTwo } from "../../../assets/img/linehometwo.s
 import { ReactComponent as Icon1 } from "../../../assets/img/icon1.svg";
 import { ReactComponent as Icon2 } from "../../../assets/img/icon2.svg";
 import { ReactComponent as Icon3 } from "../../../assets/img/icon3.svg";
+import { useSelector } from "react-redux";
 
 export const HomePage = () => {
+  const { isLogged } = useSelector((state) => state.auth);
+
   return (
     <div className={style.homePage}>
       <Header />
@@ -26,7 +30,10 @@ export const HomePage = () => {
             build test cases to learn and debug, and find inspiration.
           </p>
 
-          <Link className={style.link} to="/pen">
+          <Link
+            className={style.link}
+            to={isLogged ? "/your-works" : "/sandbox"}
+          >
             Start Codding
           </Link>
         </div>
@@ -82,6 +89,7 @@ export const HomePage = () => {
           <button>Explore Trending</button>
         </div>
       </div>
+      <Footer />
     </div>
   );
 };

+ 1 - 1
src/components/pages/LoginPage/LoginPage.jsx

@@ -5,7 +5,7 @@ import { useEffect } from "react";
 import { useDispatch, useSelector } from "react-redux";
 import { login } from "../../../store/auth/actions/loginAction";
 import { Input } from "../../common/Input/Input";
-import useInput from "../../../hooks/useInput";
+import { useInput } from "../../../hooks/useInput";
 import { LoadingPage } from "../LoadingPage/LoadingPage";
 
 export const LoginPage = () => {

+ 3 - 2
src/components/pages/PenPage/PenPage.jsx

@@ -1,6 +1,5 @@
 import React, { useEffect } from "react";
 import Editor from "../../common/Editor/Editor";
-import "../../common/Editor/pen.css";
 import { Preview } from "../../common/Preview/Preview";
 import { useDispatch, useSelector } from "react-redux";
 import { fetchCurrentWork } from "../../../store/currentWork/actions/fetchCurrentWork";
@@ -50,7 +49,7 @@ export const PenPage = () => {
       <div className={style.editors}>
         <Editor
           language="xml"
-          displayName="HTML"
+          displayName={`HTML`}
           value={html.text}
           onChange={setHtml}
         />
@@ -68,6 +67,8 @@ export const PenPage = () => {
         />
       </div>
 
+      <div className={style.line}></div>
+
       <Preview html={html.text} css={css.text} js={js.text} />
     </>
   );

+ 31 - 56
src/components/pages/PenPage/PenPage.module.scss

@@ -1,61 +1,36 @@
 @import "../../../scss/index.scss";
 
-.editors {
-  height: 50vh;
-  display: flex;
-  background-color: black;
-}
-
-// .code {
-//   display: flex;
-//   flex-wrap: wrap;
-//   justify-content: space-between;
-//   height: calc(100vh - 60px);
-// }
-
-// .code__html,
-// .code__css,
-// .code__js {
-//   width: calc(33.33% - 10px);
-//   height: 100%;
-//   padding: 10px;
-//   font-size: 16px;
-//   line-height: 1.5;
-//   border: 1px solid #ccc;
-//   box-sizing: border-box;
-//   resize: none;
-//   &:focus {
-//     outline: none;
-//   }
-// }
+.container {
+  width: 100vw;
+  height: 100vh;
 
-// .code_run {
-//   height: 50px;
-//   margin-top: 10px;
-//   padding: 5px 10px;
-//   font-size: 16px;
-//   border: none;
-//   background-color: black;
-//   color: #fff;
-//   cursor: pointer;
-//   &:hover {
-//     background-color: #49494a;
-//   }
-// }
+  // .editors {
+  //   height: 50%;
+  //   display: flex;
+  //   background-color: black;
+  //   width: 100%;
 
-// .console {
-//   padding: 10px;
-//   background-color: #f8f8f8;
-//   overflow-y: scroll;
-// }
-
-// .console__title {
-//   margin: 0 0 10px;
-//   font-size: 18px;
-// }
+  //   .bar {
+  //     background: black;
+  //     border-left: 1px solid #2f2f2f;
+  //     border-right: 1px solid #2f2f2f;
+  //     width: 15px;
+  //   }
+  // }
+  .barVertical {
+    background: black;
+    width: 100%;
+    height: 15px;
+    border-top: 1px solid #2f2f2f;
+    border-bottom: 1px solid #2f2f2f;
+  }
+}
 
-// .console__log {
-//   white-space: pre-wrap;
-//   font-size: 16px;
-//   line-height: 1.5;
-// }
+.line {
+  display: block;
+  width: 100%;
+  height: 14px;
+  background-color: black;
+  border-top: 1px solid #2f2f2f;
+  border-bottom: 1px solid #2f2f2f;
+}

+ 51 - 0
src/components/pages/SandboxPage/SandboxPage.jsx

@@ -0,0 +1,51 @@
+import Editor from "../../common/Editor/Editor";
+import { Preview } from "../../common/Preview/Preview";
+import { HeaderPen } from "../../common/HeaderPen/HeaderPen";
+import style from "../../pages/PenPage/PenPage.module.scss";
+import { useLocalStorage } from "../../../hooks/useLocalStorage";
+import { useNavigate } from "react-router-dom";
+import { Container, Section, Bar } from "react-simple-resizer";
+import { Editors } from "../../common/Editors/Editors";
+import { useCallback, useState } from "react";
+
+export const SandboxPage = () => {
+  const [html, setHtmlValue] = useLocalStorage(`html`, "");
+  const [css, setCssValue] = useLocalStorage(`css`, "");
+  const [js, setJsValue] = useLocalStorage(`js`, "");
+  const navigate = useNavigate();
+
+  const handleSave = () => {
+    if (
+      // eslint-disable-next-line no-restricted-globals
+      confirm(`You’ll have to Log In or Sign Up  to save your Pen.
+    Don’t worry! All your work will be saved to your account.`)
+    ) {
+      navigate("/login");
+    }
+  };
+
+  return (
+    <Container vertical={true} className={style.container}>
+      <HeaderPen
+        workTitle={"Untitled"}
+        onSave={handleSave}
+        workOwner={"Captain anonymous"}
+      />
+      <Section
+        children={
+          <Editors
+            html={html}
+            setHtmlValue={setHtmlValue}
+            css={css}
+            setCssValue={setCssValue}
+            js={js}
+            setJsValue={setJsValue}
+          />
+        }
+      />
+
+      <Bar className={style.barVertical} />
+      <Section children={<Preview html={html} css={css} js={js} />} />
+    </Container>
+  );
+};

+ 1 - 1
src/components/pages/SignUpPage/SignUpPage.jsx

@@ -4,7 +4,7 @@ import { NavLink, useNavigate } from "react-router-dom";
 import { useEffect } from "react";
 import { useDispatch, useSelector } from "react-redux";
 import { registration } from "../../../store/auth/actions/registrationAction";
-import useInput from "../../../hooks/useInput";
+import { useInput } from "../../../hooks/useInput";
 import { Input } from "../../common/Input/Input";
 import { Validate } from "../../../utils/Validate";
 import { LoadingPage } from "../LoadingPage/LoadingPage";

+ 1 - 1
src/components/pages/YourWorksPage/CreateWorkPopup/CreateWorkPopup.jsx

@@ -1,5 +1,5 @@
 import { useDispatch, useSelector } from "react-redux";
-import useInput from "../../../../hooks/useInput";
+import { useInput } from "../../../../hooks/useInput";
 import { createWork } from "../../../../store/works/actions/createWork";
 import { Input } from "../../../common/Input/Input";
 import style from "./CreateWorkPopup.module.scss";

+ 1 - 3
src/hooks/useInput.js

@@ -1,6 +1,6 @@
 import { useState } from "react";
 
-const useInput = (initialValue, validator) => {
+export const useInput = (initialValue, validator) => {
   const [value, setValue] = useState(initialValue);
   const [error, setError] = useState(null);
 
@@ -21,5 +21,3 @@ const useInput = (initialValue, validator) => {
     onChange,
   };
 };
-
-export default useInput;

+ 14 - 0
src/hooks/useLocalStorage.js

@@ -0,0 +1,14 @@
+import { useState, useEffect } from "react";
+
+export const useLocalStorage = (key, initialValue) => {
+  const [value, setValue] = useState(() => {
+    const storedValue = localStorage.getItem(key);
+    return storedValue ? JSON.parse(storedValue) : initialValue;
+  });
+
+  useEffect(() => {
+    localStorage.setItem(key, JSON.stringify(value));
+  }, [key, value]);
+
+  return [value, setValue];
+};

+ 4 - 0
src/scss/index.scss

@@ -28,3 +28,7 @@ body {
   width: 100vw;
   height: 100vh;
 }
+
+.CodeMirror {
+  height: 100% !important;
+}

+ 0 - 6
src/store/currentWork/currentWorkSlice.js

@@ -27,12 +27,6 @@ export const currentWorkSlice = createSlice({
     setJs(state, action) {
       state.files[2].text = action.payload;
     },
-
-    setCurrentWork(state, action) {
-      state.currentWork = state.works.find(
-        (work) => (work.id = action.payload)
-      );
-    },
   },
   extraReducers: (builder) => {
     // fetchWorks

+ 48 - 0
src/store/works/actions/updateWorkInfo.js

@@ -0,0 +1,48 @@
+import { createAsyncThunk } from "@reduxjs/toolkit";
+import { getGql } from "../../../services/api";
+import { fetchWorks } from "./fetchWorks";
+
+export const updateWorkInfo = createAsyncThunk(
+  "work/updateInfo",
+
+  async ({ id, title, description, files }, { rejectWithValue, dispatch }) => {
+    const gql = getGql();
+    try {
+      const response = await gql.request(
+        `
+            mutation addWork($snippet: SnippetInput!) {
+              SnippetUpsert(snippet: $snippet) {
+                _id
+                title
+                description
+                files {
+                  _id
+                  text
+                  type
+                }
+                owner{ _id }
+              }
+            }
+          `,
+        {
+          snippet: {
+            _id: id,
+            title: title,
+            description: description,
+            files,
+          },
+        }
+      );
+
+      if (response.SnippetUpsert) {
+        dispatch(fetchWorks(response.SnippetUpsert.owner._id));
+        return response.SnippetUpsert;
+      } else {
+        return rejectWithValue("Failed to change work info, please try again");
+      }
+    } catch (error) {
+      console.log(error);
+      return rejectWithValue("Failed to change work info, please try again");
+    }
+  }
+);

+ 9 - 0
src/store/works/worksSlice.js

@@ -2,6 +2,7 @@ import { createSlice } from "@reduxjs/toolkit";
 import { fetchWorks } from "./actions/fetchWorks";
 import { createWork } from "./actions/createWork";
 import { deleteWork } from "./actions/deleteWork";
+import { updateWorkInfo } from "./actions/updateWorkInfo";
 
 const initialState = {
   works: [],
@@ -44,6 +45,14 @@ export const worksSlice = createSlice({
     builder.addCase(deleteWork.rejected, (state, action) => {
       state.error = action.payload;
     });
+
+    // // updateWorkInfo (title | description)
+    // builder.addCase(updateWorkInfo.fulfilled, (state, action) => {
+    //   state.work
+    // });
+    // builder.addCase(updateWorkInfo.rejected, (state, action) => {
+    //   state.error = action.payload;
+    // });
   },
 });