Jelajahi Sumber

project in process

Vadym Hlushko 3 tahun lalu
induk
melakukan
4f3a7056d9

+ 5 - 3
projectreact/public/index.html

@@ -19,7 +19,7 @@
       user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
     -->
     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
-    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
+    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
     <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
 
     
@@ -36,8 +36,10 @@
   </head>
   <body>
     
-    <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.0/js/bootstrap.min.js"></script>
-  <script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
+   
+  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
+<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
     <div id="root"></div>
     <!--
       This HTML file is a template.

+ 3 - 0
projectreact/src/App.css

@@ -1,5 +1,8 @@
+
+
 .App {
   font: 400 14px 'Open Sans', 'Arial', 'Helvetica Neue', 'Helvetica', sans-serif;
+  /* overflow-x:hidden;  для горизонтального */
 }
 
 .editor {

+ 12 - 34
projectreact/src/App.js

@@ -1,45 +1,23 @@
 import React from 'react';
 import './App.css';
-import createHistory from "history/createBrowserHistory";
 import {Provider, connect}   from 'react-redux';
-import thunk from 'redux-thunk';
-import {createStore, combineReducers, applyMiddleware} from 'redux';
+import ConnectedRoutes from './components/routes';
 import store from './reducers';
-import RegForm from './pages/reg';
-import { LogForm } from './pages/login';
-import Home from './pages/homePage';
-import Cabinet from './pages/cabinet';
-import Projects from './pages/projects';
-import ConUpload from './pages/upload';
-import { imgFind } from './actions';
-import SelectLang from './components/select';
-import {
-  BrowserRouter as Router,
-  Switch,
-  Route, 
-  Link
-} from "react-router-dom";
-
+import CTester from './components/tester';
 
 
 
 function App() {
-  return (
-    <div className="App">
-      <div className = 'contentDiv'>
-      <Provider store = {store}>
-      <Router history = {createHistory()}>
-      <Route exact path='/' component={Home} />
-      <Route exact path='/registration' component={RegForm}/>
-      <Route exact path='/login' component={LogForm}/>
-      <Route exact path='/cabinet' component={Cabinet}/>
-      <Route exact path='/projects' component={Projects}/>
-      <Route exact path='/upload' component={ConUpload}/>
-      </Router>
-      </Provider>
-      </div>
-    </div>
-  );
+return (
+  
+  <>
+  
+  <Provider store = {store}>
+  <CTester/>
+ <ConnectedRoutes/>
+ </Provider>
+ </>
+)
 }
 
 export default App;

+ 219 - 127
projectreact/src/actions/index.js

@@ -1,157 +1,249 @@
-import store from "../reducers"
-const getGQL = url => 
-    (query, variables={}) => fetch(url, {
-        method: 'POST',
-        headers: {
-            //Accept: "application/json",
-            "Content-Type": "application/json",
-            ...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken } : {})
-        },
-        body: JSON.stringify({query, variables})
-    }).then(res => res.json())
-
-    let gql = getGQL("/graphql")
-
-export const actionPending = name =>            ({type: "PROMISE" ,status:"PENDING", name})
-export const actionResolved = (name,payload) => ({type: "PROMISE" ,status:"RESOLVED", name,payload})
-export const actionRejected = (name,error) =>   ({type: "PROMISE" ,status:"REJECTED", name,error})
-
-export const actionPromise = (name, promise) => 
-  async dispatch => {
-      dispatch(actionPending(name))
-      try {
-          let payload = await promise
-          dispatch(actionResolved(name , payload))
-          return payload
-      }
-      catch(error){
-          dispatch(actionRejected(name , error))
-      }
+const getGQL =
+  (url) =>
+  (query, variables = {}) =>
+    fetch(url, {
+      method: "POST",
+      headers: {
+        //Accept: "application/json",
+        "Content-Type": "application/json",
+        ...(localStorage.authToken
+          ? { Authorization: "Bearer " + localStorage.authToken }
+          : {}),
+      },
+      body: JSON.stringify({ query, variables }),
+    }).then((res) => res.json());
+
+let gql = getGQL("/graphql");
+
+export const actionPending = (name) => ({
+  type: "PROMISE",
+  status: "PENDING",
+  name,
+});
+export const actionResolved = (name, payload) => ({
+  type: "PROMISE",
+  status: "RESOLVED",
+  name,
+  payload,
+});
+export const actionRejected = (name, error) => ({
+  type: "PROMISE",
+  status: "REJECTED",
+  name,
+  error,
+});
+
+export const actionPromise = (name, promise) => async (dispatch) => {
+  dispatch(actionPending(name));
+  try {
+    let payload = await promise;
+    dispatch(actionResolved(name, payload));
+    return payload;
+  } catch (error) {
+    dispatch(actionRejected(name, error));
   }
+};
 
-  let log = async(login , password) => {
-    let query = ` query log($l:String!,$p:String!) {
+let log = async (login, password) => {
+  let query = ` query log($l:String!,$p:String!) {
       login(login:$l,password:$p)
-    }`
-let qVariables = {
-    "l": login,
-    "p": password 
-}
-let result = await gql(query,qVariables)
-return result
-}
-
-let reg = async(login,password) => {
+    }`;
+  let qVariables = {
+    l: login,
+    p: password,
+  };
+  let result = await gql(query, qVariables);
+  return result;
+};
+
+let reg = async (login, password) => {
   let query = `mutation reg($l:String! , $p:String!) {
       createUser(login:$l,password:$p){
   _id login
 }
-}`
-    let qVariables = {
-      "l":  login,
-      "p": password
-    }
-    let result = await gql(query,qVariables)
-    return result
+}`;
+  let qVariables = {
+    l: login,
+    p: password,
+  };
+  let result = await gql(query, qVariables);
+  return result;
+};
+
+export const actionAuthLogin = (token) => ({ type: "LOGIN", token });
+export const actionAuthLogout = () => ({ type: "LOGOUT" });
+export const actionFullLogin = (login, password) => async (dispatch) => {
+  let result = await dispatch(actionPromise("login", log(login, password)));
+  if (result?.data?.login !== null) {
+    dispatch(actionAuthLogin(result.data.login));
+  } else {
+    alert("такого пользователя не существует");
   }
+  await dispatch(actionFindUser());
+};
 
+export const actionRegister = (login, password) => async (dispatch) => {
+  return await dispatch(actionPromise("register", reg(login, password)));
+};
 
-export const actionAuthLogin = token => ({type:'LOGIN', token})
-export const actionAuthLogout = () => ({type:'LOGOUT'})
-export const actionFullLogin = (login , password) => async dispatch => {
-  let result = await dispatch(actionPromise("login",log(login,password)))
-  console.log(result)
-  if (result?.data?.login !== null){
-  dispatch(actionAuthLogin(result.data.login))
-  }
-  else {
-      alert ('такого пользователя не существует')
-  }
-}
+export const actionFullRegister = (login, password) => async (dispatch) => {
+  let result = await dispatch(actionRegister(login, password));
 
-export const actionRegister = (login,password) => async dispatch => {
-        return await dispatch (actionPromise('register' , reg(login,password)))
-     } 
-
-
-export const actionFullRegister = (login,password) => async dispatch => {
-  let result =  await dispatch (actionRegister(login,password))
-  
   if (result?.data?.createUser !== null) {
-      await dispatch (actionFullLogin(login,password))
-  }
-  else { 
-      alert("Такой пользователь уже есть")
-
+    await dispatch(actionFullLogin(login, password));
+  } else {
+    alert("Такой пользователь уже есть");
   }
-}
+};
 
-export const imgFind = async() => {
- return await gql(`query fhbg{
+export const imgFind = async () => {
+  return await gql(`query fhbg{
     ImageFind(query:"[{}]"){
       url owner{
         nick
       }
     }
-  }`)
-}
-
-const userFind = async(id) => {
-    let query = `query userOne {
-      UserFindOne(query:"[{\"_id\":\"6151892f68554b3e33cd8134\"}]"){
-        _id avatar{
-          url
-        }
-      }
-    }`
-
-      let qVariables = {id}
-
-      let res = await gql(query,qVariables)
-      console.log(res)
-      return res
-}
-
-let ava = async (idUser , id) => {
-  let query = `mutation setAvatar{
-    UserUpsert(user:{_id: "6151892f68554b3e33cd8134", avatar: {_id: "61518b0b68554b3e33cd813c"}}){
-        _id, avatar{
-            _id
+  }`);
+};
+
+const userFind = (_id) => {
+  return gql(
+    `query userOne($query:String) {
+                                     UserFindOne(query:$query){
+                                       _id avatar{
+                                         url
+                                       }
+                                     }
+}`,
+    { query: JSON.stringify([{ _id }]) }
+  );
+};
+
+const snippetByOwner = async (id) => {
+  let query = `query snippetFind($query:String){
+      SnippetFind(query:$query){
+                        owner{
+                          _id 
+                        }
+            title description _id files {
+             type text name
+           }
         }
-    }
-}`
-let qVariables = {idUser , id}
-
-let res = await gql(query, qVariables)
-return res
-}
+}`;
+  let variables = {
+    query: JSON.stringify([{___owner: id } , {sort:[{_id: -1}]}])
+  };
 
-export const actionImgFind = () => async dispatch => {
-  return await dispatch(actionPromise('img'),imgFind())
+  let res = gql(query, variables);
+  return res;
 }
 
+const snippetById = async(id) => {
+  let query = `query snippetFind($query:String){
+    SnippetFind(query:$query){
+                      owner{
+                        _id 
+                      }
+          title description _id files {
+           type text name
+         }
+      }
+}`;
+let variables = {
+  query: JSON.stringify([{_id: id }])
+};
 
-export const actionFindUser = () => async dispatch => {
-  return await dispatch (actionPromise('findUser'), userFind(store.getState().auth.payload.sub.id) )
+let res = gql(query, variables);
+return res;
 }
 
 
-export const actionSetAvatar = (idUser , id) => async dispatch => {
-  return await dispatch (actionPromise('setAvatar') , ava(idUser , id))
-}
 
-export const actionUploadFile = (files) => async dispatch => { 
-  let fd = new FormData()
-  fd.append('photo' , files)
-  return await dispatch (actionPromise('upload',  fetch('/upload', {
-  method: "POST",
-  headers: localStorage.authToken ? {Authorization: 'Bearer ' + localStorage.authToken} : {},
-  body: fd
-}).then(res => res.json())))
-}
+let ava = async (idUser, id) => {
+  let query = `mutation setAvatar($idUser:String , $idAvatar:ID){ 
+    UserUpsert(user:{_id: $idUser, avatar: {_id: $idAvatar}}){
+        _id, avatar{
+            url
+        }
+    }
+}`;
+  let qVariables = { idUser: idUser, idAvatar: id };
 
-export const actionFullAvatar = (idUser , id) => async dispatch => {
- let result = await dispatch (actionUploadFile())
- await dispatch (actionSetAvatar(idUser,id))
-}
+  let res = await gql(query, qVariables);
+  return res;
+};
 
+let snippetAdd = async (title, description, files , idSnippet) => {
+  let query = `mutation newSnippet($snippet:SnippetInput) {
+    SnippetUpsert(snippet:$snippet){
+      _id
+    }
+  }`;
+
+  let qVariables = { snippet: { title, description, files } ,_id: idSnippet  };
+
+  let res = await gql(query, qVariables);
+  return res;
+};
+
+export const actionImgFind = () => async (dispatch) => {
+  return await dispatch(actionPromise("img", imgFind()));
+};
+
+export const actionFindUser = () => async (dispatch, getState) => {
+  return await dispatch(
+    actionPromise("findUser", userFind(getState().auth.payload.sub.id))
+  );
+};
+
+export const actionSetAvatar = (id) => async (dispatch, getState) => {
+  return await dispatch(
+    actionPromise("setAvatar", ava(getState().auth.payload.sub.id, id))
+  );
+};
+
+export const actionUploadFile = (files) => async (dispatch) => {
+  let fd = new FormData();
+  fd.append("photo", files);
+  return await dispatch(
+    actionPromise(
+      "upload",
+      fetch("/upload", {
+        method: "POST",
+        headers: localStorage.authToken
+          ? { Authorization: "Bearer " + localStorage.authToken }
+          : {},
+        body: fd,
+      }).then((res) => res.json())
+    )
+  );
+};
+
+export const actionFullAvatar = (file) => async (dispatch) => {
+  let result = await dispatch(actionUploadFile(file));
+  await dispatch(actionSetAvatar(result._id));
+  await dispatch(actionFindUser());
+};
+
+export const actionSnippetAdd =
+  (title, description, files , idSnippet) => async (dispatch) => {
+    return await dispatch(
+      actionPromise("addSnippet",
+      snippetAdd(title, description, files , idSnippet))
+    );
+  };
+
+  export const actionSnippetFindByOwner =
+  (id) => async (dispatch) => {
+    return await dispatch(
+      actionPromise("findSnippet",
+      snippetByOwner(id))
+    );
+  };
+  export const actionSnippetById =
+  (id) => async (dispatch) => {
+    return await dispatch(
+      actionPromise("findSnippetById",
+      snippetById(id))
+    );
+  };

+ 14 - 0
projectreact/src/components/ava.js

@@ -0,0 +1,14 @@
+import React from "react";
+import { connect } from "react-redux";
+import icon from '../icon.png'
+
+
+function AvaLogo({px , mrgn , link }) {
+    // Import result is the URL of your image
+    return <img src= {link ? ('http://localhost:3000/' + link) : icon} className = 'ava'  style = {{width: px , marginRight:mrgn , borderRadius:"50%"}} />;
+  }
+
+  const ConnectedAvaLogo = connect(state => ({link:state?.promise?.findUser?.payload?.data?.UserFindOne?.avatar?.url}))(AvaLogo)
+  //'http://localhost:3000/images/5d6600e2254cff050d32c6967bcf3104'
+
+  export default ConnectedAvaLogo

+ 0 - 53
projectreact/src/components/avatar.js

@@ -1,53 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import Avatar from 'react-avatar-edit'
-
-class App extends React.Component {
-
-  constructor(props) {
-    super(props)
-    const src = './example/einshtein.jpg'
-    this.state = {
-      preview: null,
-      src
-    }
-    this.onCrop = this.onCrop.bind(this)
-    this.onClose = this.onClose.bind(this)
-    this.onBeforeFileLoad = this.onBeforeFileLoad.bind(this)
-  }
-  
-  onClose() {
-    this.setState({preview: null})
-  }
-  
-  onCrop(preview) {
-    this.setState({preview})
-  }
-
-  onBeforeFileLoad(elem) {
-    if(elem.target.files[0].size > 71680){
-      alert("File is too big!");
-      elem.target.value = "";
-    };
-  }
-  
- 
-}
-
-const Avatar = () => {
-    return (
-      <div>
-        <Avatar
-          width={390}
-          height={295}
-          onCrop={this.onCrop}
-          onClose={this.onClose}
-          onBeforeFileLoad={this.onBeforeFileLoad}
-          src={this.state.src}
-        />
-        <img src={this.state.preview} alt="Preview" />
-      </div>
-    )
-    }
-
-    export default Avatar

+ 0 - 0
projectreact/src/components/button.js


+ 0 - 17
projectreact/src/components/coursor.js

@@ -1,17 +0,0 @@
-import React from "react";
-import AnimatedCursor from "react-animated-cursor"
-
-const Coursor = () =>  {
-  return (
-    <AnimatedCursor
-      innerSize={8}
-      outerSize={8}
-      color='193, 11, 111'
-      outerAlpha={0.2}
-      innerScale={0.7}
-      outerScale={5}
-    />
-  );
-}
-
-export default Coursor

+ 33 - 14
projectreact/src/components/editor.js

@@ -21,7 +21,11 @@ import "ace-builds/src-noconflict/theme-monokai";
 import "ace-builds/src-noconflict/ext-language_tools";
 import { edit } from "ace-builds";
 
-const Editor = ({ data = { type: "html", text: "", name: "" }, onChange  , onDelete}) => {
+const Editor = ({
+  data = { type: "html", text: "", name: "" },
+  onChange,
+  onDelete,
+}) => {
   return (
     <div
       style={{
@@ -37,25 +41,40 @@ const Editor = ({ data = { type: "html", text: "", name: "" }, onChange  , onDel
         value={data.type}
       />{" "}
       <br />
-      <input
-      placeholder='Your name of file'
-        style={{ marginBottom: "10px", marginLeft: "5px" }}
-        type="text"
-        value={data.name}
-        onChange={(e) =>
+      <div className="input-group ml-4">
+        <div className="input-group-prepend">
+          <span className="input-group-text" id="basic-addon1">
+            Name of file
+          </span>
+        </div>
+        <input
+          type="text"
+          className="form-control w-25"
+          placeholder="Name of file"
+          aria-label="Name of file"
+          aria-describedby="basic-addon1"
+          value={data.name}
+          onChange={(e) =>
           onChange({ type: data.type, text: data.text, name: e.target.value })
-        }
-      />
-      <button className = 'btn btn-success btn-xs ml-1'>Apply</button> 
-      <button className = 'btn btn-warning btn-xs ml-1'>Edit</button>
-      <button type="button" className="close mr-4" aria-label="Close" onClick = {onDelete}>
-          <span className = 'text-danger' aria-hidden="true">&times;</span>
+                    }
+            
+        />
+      </div>
+      <button
+        type="button"
+        className="close mr-4"
+        aria-label="Close"
+        onClick={onDelete}
+      >
+        <span className="text-danger" aria-hidden="true">
+          &times;
+        </span>
       </button>
       <AceEditor
         className="editor"
         value={data.text}
         onChange={(text) =>
-          onChange({ type:data.type, text, name:data.name })
+          onChange({ type: data.type, text, name: data.name })
         }
         placeholder="Your Code"
         mode={data.type}

+ 4 - 2
projectreact/src/components/header.js

@@ -2,11 +2,13 @@ import React from "react"
 import ConnectedNick from "./nickHeader"
 import { connect } from "react-redux"
 import Avatar from "react-avatar-edit"
-import ImgLogo from "../images/logo"
+import ImgLogo from "./logo"
+
 
 const Header = () => {
     return (
-<nav className="navbar navbar-expand-lg navbar-light bg-white m-0">
+<nav className="navbar navbar-expand-lg shadow-lg navbar-light bg-white mb-5">
+    
     <ImgLogo px = {'70px'} />
      <ConnectedNick/>
      {/* <Avatar/> */}

projectreact/src/images/logo.js → projectreact/src/components/logo.js


+ 1 - 1
projectreact/src/components/nick.js

@@ -11,5 +11,5 @@ const Nick =  ({nick}) => {
       )
     }
     
-    const ConnectNickName = connect(state => ({nick:state?.auth?.payload?.sub?.login }))(Nick)
+    const ConnectNickName = connect(state => ({nick:state?.auth?.payload?.sub?.login || 0 }))(Nick)
     export default ConnectNickName

+ 7 - 5
projectreact/src/components/nickHeader.js

@@ -1,18 +1,20 @@
 import { connect } from "react-redux"
-import AvaLogo from "../images/ava"
+import ConnectedAvaLogo from "./ava"
+import { actionAuthLogout } from "../actions"
 
-const NickName =  ({nick}) => {
+const NickName =  ({nick , onLogOut}) => {
     return (
       <>
-      <div style ={{position:'relative', left:'80%'}}>
+      <div style ={{position:'relative', left:'75%'}}>
       <span style ={{color:'black'}}>Your nickname </span>
       <a href = '/cabinet' style = {{textDecoration:'none' , color:"#5F9EA0"}}>{nick} &nbsp;&nbsp;
-      <AvaLogo px = '50px'/>
+      <ConnectedAvaLogo px = {'50px'} mrgn = {'10px'} />
       </a>
+      <button className = 'btn btn-secondary btn-sm' onClick = {() => (onLogOut())}>Log out</button>
       </div>
       </>
       )
     }
     
-    const ConnectedNick = connect(state => ({nick:state?.auth?.payload?.sub?.login }))(NickName)
+    const ConnectedNick = connect(state => ({nick:state?.auth?.payload?.sub?.login}),{onLogOut:actionAuthLogout})(NickName)
     export default ConnectedNick

+ 1 - 1
projectreact/src/images/profile.js

@@ -1,7 +1,7 @@
 import React from "react";
 import wall from '../profileWall.jpeg'
 
-function ImgProfile({px}) {
+function ImgProfile({px }) {
     return <a href="/"> <img src={wall} alt="wall" style = {{width: px}} /> </a>
   }
   

+ 44 - 0
projectreact/src/components/routes.js

@@ -0,0 +1,44 @@
+import React from 'react';
+import createHistory from "history/createBrowserHistory";
+import {Provider, connect}   from 'react-redux';
+import store from '../reducers';
+import RegForm from '../pages/reg';
+import { LogForm } from '../pages/login';
+import Home from '../pages/homePage';
+import ConnectCabinet from '../pages/cabinet';
+import CProjects from '../pages/projects';
+import ConUpload from '../pages/upload';
+import ConnectedProject from '../pages/project';
+import {
+  BrowserRouter as Router,
+  Switch,
+  Route, 
+  Link
+} from "react-router-dom";
+
+const Routes = ({isAuth}) => {
+    return (
+        <div className="App">
+          <div className = 'contentDiv'>
+         
+            {/* <CTester/> */}
+          <Router history = {createHistory()}>
+              {isAuth && <Switch>
+            <Route exact path='/' component={Home} />
+          <Route path='/cabinet' component={ConnectCabinet}/>
+          <Route path='/projects' component={CProjects}/>
+          <Route path='/project/:id' component={ConnectedProject}/>
+          <Route path='/upload' component={ConUpload}/>
+          </Switch>}
+          {!isAuth && <Switch>
+          <Route path='/login' component={LogForm}/>
+          <Route path='/registration' component={RegForm}/>
+              </Switch>}
+          </Router>
+          </div>
+        </div>
+      );
+}
+
+const ConnectedRoutes = connect(state => ({isAuth: state.auth.token}))(Routes)
+export default ConnectedRoutes

+ 1 - 1
projectreact/src/components/select.js

@@ -5,7 +5,7 @@ const SelectLang = ({list = languages , onChange , value}) => {
    
     return (
      <>
-      <select style = {{marginBottom:'10px' , marginLeft:'5px'}} value = {value} onChange ={(e) => onChange(e.target.value)}  className = 'select'>
+      <select style = {{marginBottom:'10px' , marginLeft:'15px'}} value = {value} onChange ={(e) => onChange(e.target.value)}  className = 'select'>
       {Object.entries(list).map(([value , text]) => <option value={value} key={value}>{text}</option>)}
       </select>
       </>

+ 82 - 25
projectreact/src/components/snippet.js

@@ -1,31 +1,88 @@
 import { useState } from "react";
 import Editor from "./editor";
+import { actionSnippetAdd } from "../actions";
+import { connect } from "react-redux";
 
-
-const Snippet = () =>{
-    const [files, setFiles] = useState([{type:'html',name:`index.html`} , {type:'css' , name:'index.css' } , {type:'javascript' , name:'index.js'}]);
-    const [name, setName] = useState("");
-    return (
+const SnippetHome = ({onSave}) => {
+  const [files, setFiles] = useState([
+    { type: "html" },
+    { type: "css"},
+    { type: "javascript" },
+  ]);
+  const [name, setName] = useState("");
+  const [title, setTitle] = useState("");
+  const [description, setDescription] = useState("");
+  return (
+    <div>
+      {files.map((data, index) => (
+        <>
+          <Editor
+            onDelete={() => setFiles(files.filter((item) => item != data))}
+            data={data}
+            onChange={({ type, name, text }) =>
+              setFiles([
+                ...files.slice(0, index),
+                { type, name, text },
+                ...files.slice(index + 1),
+              ])
+            }
+          />
+        </>
+      ))}
+      <br />
+      <div
+        style={{
+          alignItems: "center",
+          textAlign: "center",
+          marginBottom: "10px",
+        }}
+      >
         <div>
-    {files.map((data , index) => 
-    <>
-    <Editor onDelete = {() => setFiles(files.filter(item => item != data))} data = {data} onChange = {({type , name , text}) =>
-    setFiles([...files.slice(0,index),
-    {type , name , text},
-    ...files.slice(index+1)])  } /> 
-    </>
-    ) 
-    
-     }<br/>
-    <div style = {{alignItems:'center' , textAlign:'center' , marginBottom:'10px'}}>
-    <button  className = 'btn btn-primary' style = {{marginLeft:'100px'}} onClick = {() => setFiles([...files,{type:'html'}])} key ={files} >Add editor</button>
-    <button  className = 'btn btn-success float-right mr-5' >Save project</button>
-    
-    </div>
-    
+          <button
+            className="btn btn-primary"
+            onClick={() => setFiles([...files, { type: "html" }])}
+            key={files}
+          >
+            Add editor
+          </button>
+        </div>
+        <div className="input-group mb-3 mt-5 ml-auto mr-auto w-25">
+          <div className="input-group-prepend">
+            <span className="input-group-text" id="basic-addon1">
+              Name of your project
+            </span>
+          </div>
+          <input
+            value={title}
+            onChange={(e) => setTitle(e.target.value)}
+            type="text"
+            className="form-control"
+            placeholder="Name of project"
+            aria-label="Name of project"
+            aria-describedby="basic-addon1"
+          />
+        </div>
+        <div className="input-group ml-auto mr-auto w-50">
+          <div className="input-group-prepend">
+            <span className="input-group-text ">Description</span>
+          </div>
+          <textarea
+            value={description}
+            onChange={(e) => setDescription(e.target.value)}
+            className="form-control "
+            aria-label="With textarea"
+            placeholder="Your description"
+          ></textarea>
+        </div>
+        <button className="btn btn-success float-right mr-5 mb-5" onClick = {() => onSave(title , description , files)}>
+          Save project
+        </button>
+      </div>
     </div>
-    
-    )
-}
+  );
+};
+
+const ConnectedSnippetHome = connect(null,{ onSave: actionSnippetAdd })(SnippetHome);
+export default ConnectedSnippetHome
+
 
-export default Snippet 

+ 11 - 0
projectreact/src/components/tester.js

@@ -0,0 +1,11 @@
+import React from "react";
+import { connect } from "react-redux";
+
+const Tester = (props) => 
+<pre>
+    {JSON.stringify(props , null , 4)}
+</pre>
+
+const CTester = connect(state => (state))(Tester)
+
+export default CTester

TEMPAT SAMPAH
projectreact/src/icon.png


+ 0 - 14
projectreact/src/images/ava.js

@@ -1,14 +0,0 @@
-import React from "react";
-import avatar from '../user.png'
-import { imgFind } from "../actions";
-import {createStore, combineReducers,applyMiddleware } from 'redux';
-import { reducers } from "../reducers";
-import thunk from "redux-thunk";
-const store = createStore(combineReducers(reducers), applyMiddleware(thunk))
-
-function AvaLogo({px}) {
-    // Import result is the URL of your image
-    return <img src='http://localhost:3000/images/5d6600e2254cff050d32c6967bcf3104' className = 'ava' alt="avatar" style = {{width: px}} />;
-  }
-  
-  export default AvaLogo;

TEMPAT SAMPAH
projectreact/src/images/logo.png


+ 20 - 17
projectreact/src/pages/cabinet.js

@@ -1,28 +1,29 @@
 import React from "react";
 import { connect } from "react-redux";
 import ConnectNickName from "../components/nick";
-import AvaLogo from "../images/ava";
-import ImgProfile from "../images/profile";
+
+import ImgProfile from "../components/profile";
 import ConUpload from "./upload";
-import { imgFind } from "../actions";
-import { actionFindUser } from "../actions";
+import { actionAuthLogout } from "../actions";
 
-const Cabinet = () => {
+const Cabinet = ({onLogOut}) => {
     return (
     <>
-    <div class="container d-flex justify-content-center align-items-center mt-5">
-    <div class="card">
-        <div class="upper"> <ImgProfile px ={'300px'}/> </div>
-        <div class="user text-center">
-            <div class="profile mt-3" > <ConUpload width = {'100px'}/> </div>
+    <div className="container d-flex justify-content-center align-items-center mt-5 mb-5">
+    <div className="card">
+        <div className="upper"> <ImgProfile px ={'300px'} /> </div>
+        <div className="user text-center">
+            <div className="profile mt-3" > <ConUpload width = {'100px'}/> </div>
         </div>
-        <div class="mt-5 text-center">
-            <h4 class="mb-0"><ConnectNickName/></h4> 
-            <a href="/projects" className = 'text-decoration-none'><h6 >Your projects</h6></a>
-            {/* <span class="text-muted d-block mb-2">Los Angles</span>  */}
-            <div class="d-flex justify-content-center align-items-center mt-4 px-4">
-            <a href="/"><button class="btn btn-primary btn-sm mb-3 " onClick = {console.log('aaa')} >Main Page</button></a>
+        <div className="mt-5 text-center">
+            <h4 className="mb-0"><ConnectNickName/></h4> 
+            <a href="/projects"><button type="button" className="btn btn-outline-success">My Projects</button></a>
+            <div className="d-flex justify-content-center align-items-center mt-4 px-4">
+            <a href="/">
+                <button className="btn btn-primary btn-sm mb-3 "  >Main Page</button>
+                </a> 
             </div>
+                <button className = 'btn btn-secondary btn-sm mb-3' onClick = {() => (onLogOut())}>Log out</button>
         </div>
     </div>
 </div>
@@ -30,4 +31,6 @@ const Cabinet = () => {
     )
 }
 
-export default Cabinet
+const ConnectCabinet = connect(null ,{onLogOut:actionAuthLogout})(Cabinet)
+
+export default ConnectCabinet

+ 2 - 23
projectreact/src/pages/homePage.js

@@ -1,33 +1,12 @@
-
 import React from "react";
-import { render } from "react-dom";
-
 import Header from "../components/header";
-import { useState } from "react";
-import Snippet from "../components/snippet";
-import Button from "../components/button";
-
-
-
-
-// function onChangeHtml(newValue) {
-//     console.log('html' + ' ' + newValue);
-//   }
-// function onChangeCss(newValue) {
-//     console.log('css' + ' ' + newValue);
-//   }
-
-
-
-
-
-
+import ConnectedSnippetHome from "../components/snippet";
 
 const Home = () =>{
 return (
   <>
   <Header/>
-  <Snippet/>
+  <ConnectedSnippetHome/>
   </>
     )
 }

+ 92 - 61
projectreact/src/pages/login.js

@@ -1,69 +1,100 @@
 import { connect } from "react-redux";
-import { useState} from "react";
+import { useState } from "react";
 import { actionFullLogin } from "../actions";
 import { Redirect } from "react-router-dom";
 
-const Log = ({ onLog }) => {
-    const [login, setLogin] = useState("");
-    const [password, setPassword] = useState("");
-    const [click , setClick] = useState(false)
-        //надо тип инпуту
-    //надо проверку на пустоту инпутов и запрет кнопки (disabled)
-    //надо при кнопке отправить в onLogin login и пароль. onLogin - это функция-колбэк
-    return click ? <Redirect to = "/"/> :
-    (
-      <>
-        <div className="container">    
-        <div id="loginbox" style={{"margin-top":"50px"}} className="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">                    
-            <div className="panel panel-info" >
-                    <div className="panel-heading">
-                        <div className="panel-title">Sign In</div>
-                        <div style={{"float":"right", "font-size": "80%", "position": "relative", "top":"-10px"}}><a href="#">Forgot password?</a></div>
-                    </div>     
+const Log = ({ onLog , LogedIn}) => {
+  const [login, setLogin] = useState("");
+  const [password, setPassword] = useState("");
+  return LogedIn ? <Redirect to="/" />
+   : (
+    <>
+      <div className="container">
+        <div
+          id="loginbox"
+          style={{ "margin-top": "50px" }}
+          className="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2"
+        >
+          <div className="panel panel-info">
+            <div className="panel-heading">
+              <div className="panel-title">Sign In</div>
+            </div>
 
-                    <div style={{"padding-top":"30px"}} className="panel-body" >
+            <div style={{ "padding-top": "30px" }} className="panel-body">
+              <div
+                style={{ display: "none" }}
+                id="login-alert"
+                className="alert alert-danger col-sm-12"
+              ></div>
 
-                        <div style={{"display":"none"}} id="login-alert" className="alert alert-danger col-sm-12"></div>
-                            
-                        <form id="loginform" className="form-horizontal" role="form">
-                                    
-                            <div style={{"margin-bottom": "25px"}} className="input-group">
-                                        <span className="input-group-addon"><i className="glyphicon glyphicon-user"></i></span>
-                                        <input id="login-username" type="text" className="form-control" name="username" value={login} onChange={(e) => setLogin(e.target.value)} placeholder="Login"/>                                        
-                                    </div>
-                                
-                            <div style={{"margin-bottom": "25px"}} className="input-group">
-                                        <span className="input-group-addon"><i className="glyphicon glyphicon-lock"></i></span>
-                                        <input id="login-password" type="password" className="form-control" name="password" placeholder="Password" value = {password} onChange={(e) => setPassword(e.target.value)}/>
-                                    </div>
-                                <div style={{'margin-top':"10px"}} className="form-group">
-                                    <div className="col-sm-12 controls">
-                                      <a id="btn-login" href="#" className="btn btn-success" onClick={() => (onLog(login, password),setClick(true))}>Login  </a>
+              <form id="loginform" className="form-horizontal" role="form">
+                <div
+                  style={{ "margin-bottom": "25px" }}
+                  className="input-group"
+                >
+                  <span className="input-group-addon">
+                    <i className="glyphicon glyphicon-user"></i>
+                  </span>
+                  <input
+                    id="login-username"
+                    type="text"
+                    className="form-control"
+                    name="username"
+                    value={login}
+                    onChange={(e) => setLogin(e.target.value)}
+                    placeholder="Login"
+                  />
+                </div>
 
-                                    </div>
-                                </div>
-
-
-                                <div className="form-group">
-                                    <div className="col-md-12 control">
-                                        <div style={{"border-top": "1px solid#888", "padding-top":"15px", "font-size":"85%"}}>
-                                        <a href="/registration">
-                                            Sign Up Here
-                                        </a>
-                                        </div>
-                                    </div>
-                                </div>    
-                            </form>     
-
-
-
-                        </div>                     
-                    </div>  
+                <div
+                  style={{ "margin-bottom": "25px" }}
+                  className="input-group"
+                >
+                  <span className="input-group-addon">
+                    <i className="glyphicon glyphicon-lock"></i>
+                  </span>
+                  <input
+                    id="login-password"
+                    type="password"
+                    className="form-control"
+                    name="password"
+                    placeholder="Password"
+                    value={password}
+                    onChange={(e) => setPassword(e.target.value)}
+                  />
+                </div>
+                <div style={{ "margin-top": "10px" }} className="form-group">
+                  <div className="col-sm-12 controls">
+                    <a
+                      id="btn-login"
+                      href="#"
+                      className="btn btn-success"
+                      onClick={() => (onLog(login, password))}
+                    >
+                      Login{" "}
+                    </a>
+                  </div>
+                </div>
+                <div className="form-group">
+                  <div className="col-md-12 control">
+                    <div
+                      style={{
+                        "border-top": "1px solid#888",
+                        "padding-top": "15px",
+                        "font-size": "85%",
+                      }}
+                    >
+                      <a href="/registration">Sign Up Here</a>
+                    </div>
+                  </div>
+                </div>
+              </form>
+            </div>
+          </div>
         </div>
-    </div>
-        
-      </>
-    );
-  };
-  
-  export const LogForm = connect(null, {onLog: actionFullLogin})(Log)
+      </div>
+    </>
+  );
+};
+
+export const LogForm = connect(state => ({LogedIn:state?.auth?.token}), { onLog: actionFullLogin })(Log);

+ 124 - 0
projectreact/src/pages/project.js

@@ -0,0 +1,124 @@
+import { useEffect } from "react";
+import { connect } from "react-redux";
+import { actionSnippetById } from "../actions";
+import { useState } from "react";
+import Editor from "../components/editor";
+import { actionSnippetAdd } from "../actions";
+import { Link } from "react-router-dom";
+
+const ProjectSnippet = ({
+  onSave,
+  getSnippet,
+  match: {
+    params: { id },
+  },
+  titleText,
+  descriptionText,
+  filesArr,
+  nameText,
+}) => {
+  useEffect(() => {
+    getSnippet(id);
+  }, []);
+  const [files, setFiles] = useState([]);
+  const [name, setName] = useState(nameText);
+  const [title, setTitle] = useState('');
+  const [description, setDescription] = useState('');
+  useEffect(() => {
+      setFiles(filesArr) 
+      setTitle(titleText)
+      setDescription(descriptionText)
+  },[filesArr , titleText , descriptionText])
+  return (
+    <div>
+      {files?.map((data, index) => (
+        <>
+          <Editor
+            onDelete={() => setFiles(files.filter((item) => item != data))}
+            data={data}
+            onChange={({ type, name, text }) =>
+              setFiles([
+                ...files.slice(0, index),
+                { type, name, text },
+                ...files.slice(index + 1),
+              ])
+            }
+          />
+        </>
+      ))}
+      <br />
+      <div
+        style={{
+          alignItems: "center",
+          textAlign: "center",
+          marginBottom: "10px",
+        }}
+      >
+        <div>
+          <button
+            className="btn btn-primary"
+            onClick={() => setFiles([...files, { type: "html" }])}
+            key={files}
+          >
+            Add editor
+          </button>
+          <div>
+          <Link to ="/projects">
+              <button className = 'btn btn-outline-info border-info'>All projects</button>
+          </Link>
+          </div>
+        </div>
+        <div className="input-group mb-3 mt-5 ml-auto mr-auto w-25">
+          <div className="input-group-prepend">
+            <span className="input-group-text" id="basic-addon1">
+              Name of your project
+            </span>
+          </div>
+          <input
+            value={title}
+            onChange={(e) => setTitle(e.target.value)}
+            type="text"
+            className="form-control"
+            placeholder="Name of project"
+            aria-label="Name of project"
+            aria-describedby="basic-addon1"
+          />
+        </div>
+        <div className="input-group ml-auto mr-auto w-50">
+          <div className="input-group-prepend">
+            <span className="input-group-text ">Description</span>
+          </div>
+          <textarea
+            value={description}
+            onChange={(e) => setDescription(e.target.value)}
+            className="form-control "
+            aria-label="With textarea"
+            placeholder="Your description"
+          ></textarea>
+        </div>
+        <button
+          className="btn btn-success float-right mr-5 mb-5"
+          onClick={() => onSave(title, description, files)}
+        >
+          Save project
+        </button>
+      </div>
+    </div>
+  );
+};
+
+const ConnectedProject = connect(
+  (state) => ({
+    title:
+      state?.promise?.findSnippetById?.payload?.data?.SnippetFind?.[0]?.title,
+    descriptionText:
+      state?.promise?.findSnippetById?.payload?.data?.SnippetFind?.[0]
+        ?.description,
+    nameText:
+      state?.promise?.findSnippetById?.payload?.data?.SnippetFind?.[0]?.name,
+    filesArr:
+      state?.promise?.findSnippetById?.payload?.data?.SnippetFind?.[0]?.files,
+  }),
+  { getSnippet: actionSnippetById ,onSave: actionSnippetAdd  }
+)(ProjectSnippet);
+export default ConnectedProject;

+ 40 - 7
projectreact/src/pages/projects.js

@@ -1,9 +1,42 @@
-import React from "react";
+import { connect } from "react-redux";
+import { Link } from "react-router-dom";
 
-const Projects = () => {
-    return (
-    <h1>Your projects</h1>
-    )
-}
+const Projects = ({ snippets }) => {
+  return  snippets  ? (
+    <div>
+        <Link to ="/"><button className = 'float-left btn-secondary d-inline-block mt-2 ml-2'>Back to Main Page</button></Link> <br/> <br/>
+      {snippets?.map((key, index) => (
+        <div style ={{textAlign:'center' , alignItems:'center'}}>
+          <div className="card w-50 ml-auto mr-auto mt-3 mb-5"  >
+            <div className="card-body" style ={{textAlign:'center'}}>
+                <h3 className="card-title mb-4 text-info">{snippets?.[index]?.title || "Project without name"}</h3>   
+              <p className="card-text">
+                  <span className = 'text-muted'>Description</span>&nbsp; 
+              {snippets?.[index]?.description || ""} 
+              </p>
+              <Link to = {'/project/' + snippets?.[index]?._id}>
+              <button className="btn btn-primary mt-3"  >
+                Open project
+                </button>  
+                </Link>
+            </div>
+          </div>
+        </div>
+      ))}
+    </div>
+  ) : (
+    <div>
+    <Link to ="/"><button className = 'float-left btn-secondary d-inline-block mt-2 ml-2'>Back to Main Page</button></Link> <br/> <br/>
+    <div className="d-flex justify-content-center">
+    <div className="spinner-border mt-3" style={{width: "10rem" ,  height: "10rem"}} role="status">
+      <span className="sr-only">Loading...</span>
+    </div>
+  </div>
+  </div>
+  );
+};
 
-export default Projects
+const CProjects = connect(state => ({
+  snippets: state?.promise?.findSnippet?.payload?.data?.SnippetFind
+}))(Projects);
+export default CProjects;

+ 5 - 7
projectreact/src/pages/reg.js

@@ -3,22 +3,20 @@ import { useState} from "react";
 import { actionFullRegister } from "../actions";
 import { Redirect } from "react-router-dom";
 
-const Reg = ({ onReg }) => {
+const Reg = ({ onReg,LogedIn }) => {
   const [login, setLogin] = useState("");
   const [password, setPassword] = useState("");
-  const [click , setClick] = useState(false)
       //надо тип инпуту
   //надо проверку на пустоту инпутов и запрет кнопки (disabled)
   //надо при кнопке отправить в onLogin login и пароль. onLogin - это функция-колбэк
-  return click ? <Redirect to = "/"/> :
+  return LogedIn? <Redirect to = "/"/> :
   (
     <>
       <div className="container">    
       <div id="loginbox" style={{"margin-top":"50px"}} className="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">                    
           <div className="panel panel-info" >
                   <div className="panel-heading">
-                      <div className="panel-title">Sign In</div>
-                      <div style={{"float":"right", "font-size": "80%", "position": "relative", "top":"-10px"}}><a href="#">Forgot password?</a></div>
+                      <div className="panel-title">Registration</div>
                   </div>     
 
                   <div style={{"padding-top":"30px"}} className="panel-body" >
@@ -38,7 +36,7 @@ const Reg = ({ onReg }) => {
                                   </div>
                               <div style={{'margin-top':"10px"}} className="form-group">
                                   <div className="col-sm-12 controls">
-                                    <a id="btn-login" href="#" className="btn btn-success" onClick={() => (onReg(login, password),setClick(true))}>Sign up  </a>
+                                    <a id="btn-login" href="#" className="btn btn-success" onClick={() => (onReg(login, password))}>Sign up  </a>
 
                                   </div>
                               </div>
@@ -66,7 +64,7 @@ const Reg = ({ onReg }) => {
     );
   };
   
-  const RegForm = connect(null, {onReg: actionFullRegister})(Reg)
+  const RegForm = connect(state => ({LogedIn:state?.auth?.token}), {onReg: actionFullRegister})(Reg)
 
   
 export default RegForm

+ 7 - 25
projectreact/src/pages/upload.js

@@ -1,42 +1,24 @@
-import React, { useRef } from 'react';
+import React from 'react';
 import {useDropzone} from 'react-dropzone';
-import Coursor from '../components/coursor';
-import { actionUploadFile } from '../actions';
+import { actionFullAvatar } from '../actions';
 import { connect } from 'react-redux';
-import AvaLogo from '../images/ava';
+import ConnectedAvaLogo from '../components/ava';
 
 
-// const Upload = () => {
-//     const formRef = useRef(null)
-//     return (
-//     <>
-// <form action="/upload" method="post" enctype="multipart/form-data" ref={formRef}>
-//   <input type="file" name="photo" id='photo'onChange = {async () => {
-//     fetch('/upload', {
-//         method: "POST",
-//         headers: localStorage.authToken ? {Authorization: 'Bearer ' + localStorage.authToken} : {},
-//         body: new FormData(formRef.current)
-//     })
-// }
-//   }/>
-// </form>
-// </>
-//     )
-// }
 
-const Upload = ({onUpload , width , up}) => {
+const Upload = ({onUpload , width , radius}) => {
   const {acceptedFiles, getRootProps, getInputProps} = useDropzone();
   let files = acceptedFiles.map (file => onUpload(file))
   return (
       <div {...getRootProps({className: 'dropzone'})} style = {{display:'inline'}}>
         <input {...getInputProps()} />
-        <AvaLogo px = {width}/>
-        <h1>{up}</h1>
+        <ConnectedAvaLogo px = {width} border = {radius}/>
+        
       </div>
    
   );
 }
 
-const ConUpload = connect(state => ({up:state?.promise?.upload?.[1]._id })  , {onUpload:actionUploadFile})(Upload)
+const ConUpload = connect(null , {onUpload:actionFullAvatar})(Upload)
 
 export default ConUpload;

+ 7 - 3
projectreact/src/reducers/index.js

@@ -1,5 +1,7 @@
 import thunk  from 'redux-thunk'
 import {createStore, combineReducers,applyMiddleware } from 'redux';
+import { actionFindUser , actionSnippetFindByOwner} from '../actions';
+
 
 function promiseReducer(state , {type, name ,status , payload, error}) {
   if (!state){
@@ -8,7 +10,7 @@ function promiseReducer(state , {type, name ,status , payload, error}) {
   if (type === 'PROMISE') {
       return {
           ...state,
-          [name]: [status,payload , error]
+          [name]: {status,payload , error}
       }
   }
   return state
@@ -24,7 +26,6 @@ function authReducer(state, action){ //....
         // добавить в action token из localStorage, и проимитировать LOGIN 
     }
     if (action.type === 'LOGIN'){
-        console.log('ЛОГИН')
         localStorage.authToken = action.token
         function jwt_decode (token) {
             var start64Url = token.split('.')[1]
@@ -33,7 +34,6 @@ function authReducer(state, action){ //....
         return {token: action.token, payload: jwt_decode(action.token)}
     }
     if (action.type === 'LOGOUT'){
-        console.log('ЛОГАУТ')
         localStorage.removeItem("authToken")
         //вернуть пустой объект
         return {}
@@ -47,5 +47,9 @@ export const reducers = {
 
 export const store = createStore(combineReducers(reducers), applyMiddleware(thunk))
 const unsubscribe = store.subscribe(() => console.log('result here',store.getState()))
+if (localStorage.authToken) {
+    store.dispatch(actionFindUser())
+    store.dispatch(actionSnippetFindByOwner(store.getState().auth.payload.sub.id))
+}
 
 export default store