anton123 2 年之前
父节点
当前提交
a5bc4cca72

文件差异内容过多而无法显示
+ 508 - 17176
package-lock.json


+ 11 - 0
package.json

@@ -3,12 +3,23 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@dnd-kit/core": "^6.0.7",
+    "@dnd-kit/sortable": "^7.0.2",
+    "@material-ui/core": "^4.12.4",
+    "@material-ui/icons": "^4.11.3",
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^13.4.0",
     "@testing-library/user-event": "^13.5.0",
+    "array-move": "^4.0.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-dropzone": "^14.2.3",
+    "react-redux": "^8.0.5",
+    "react-router-dom": "^5.3.0",
     "react-scripts": "5.0.1",
+    "redux": "^4.2.0",
+    "redux-thunk": "^2.4.2",
+    "sass": "^1.57.1",
     "web-vitals": "^2.1.4"
   },
   "scripts": {

+ 2 - 0
public/index.html

@@ -15,6 +15,8 @@
       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://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
+    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
     <!--
       Notice the use of %PUBLIC_URL% in the tags above.
       It will be replaced with the URL of the `public` folder during the build.

+ 0 - 38
src/App.css

@@ -1,38 +0,0 @@
-.App {
-  text-align: center;
-}
-
-.App-logo {
-  height: 40vmin;
-  pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
-  .App-logo {
-    animation: App-logo-spin infinite 20s linear;
-  }
-}
-
-.App-header {
-  background-color: #282c34;
-  min-height: 100vh;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  font-size: calc(10px + 2vmin);
-  color: white;
-}
-
-.App-link {
-  color: #61dafb;
-}
-
-@keyframes App-logo-spin {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
-}

+ 413 - 19
src/App.js

@@ -1,25 +1,419 @@
-import logo from './logo.svg';
-import './App.css';
+import {createStore, combineReducers, applyMiddleware} from 'redux';
+import thunk from 'redux-thunk';
+import {Provider, connect} from 'react-redux';
+import {Router, Route} from 'react-router-dom';
+import React from 'react';
+import './App.scss';
+import SignIn from './components/signIn';
+import SignUp from './components/signup';
+import MyProfile from './components/myprofile';
+import CreateAd from './components/add';
+import Good from './components/good';
+import Cards from './components/goods'
+import CardsUser from './components/productsUser';
+import Header from './components/header'
+import Message from './components/message'
+const history = require("history").createBrowserHistory()
+
+const getGQL = url =>
+	(query, variables= {}) =>
+		fetch(url, {
+			method: 'POST',
+			headers: checkToken(),
+			body:JSON.stringify({query, variables})
+		}).then(res => res.json())
+			.then(data => {
+				try {
+					if(!data.data && data.errors) {
+						throw new SyntaxError(`SyntaxError - ${JSON.stringify(Object.values(data.errors)[0])}`);
+					}
+					return Object.values(data.data)[0];
+				} catch (e) {
+					console.error(e);
+				}
+			});
+
+const checkToken = () => {
+  const headers = {
+    "Content-Type": "application/json",
+    "Accept": "application/json",
+  }
+  if(localStorage.getItem('authToken')) {
+    return {
+      ...headers,
+      "Authorization": `Bearer ${localStorage.getItem('authToken')}`
+    }
+  } else {
+    return headers;
+  }
+}
+
+
+const url = 'http://marketplace.node.ed.asmer.org.ua/'
+const gql = getGQL(url + 'graphql')
+
+const actionPromise = (namePromise,promise) =>
+    async dispatch => { 
+        dispatch(actionPending(namePromise)) //сигнализируем redux, что промис начался
+        try{
+            const payload = await promise //ожидаем промиса
+            dispatch(actionFulfilled(namePromise,payload)) //сигнализируем redux, что промис успешно выполнен
+            return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
+        }
+        catch (error){
+            dispatch(actionRejected(namePromise,error)) //в случае ошибки - сигнализируем redux, что промис несложился
+        }
+    }
+
+const actionPending   = (namePromise)      => ({type: 'PROMISE', status: 'PENDING',namePromise})
+const actionFulfilled = (namePromise,payload) => ({type: 'PROMISE', status: 'FULFILLED',namePromise, payload})
+const actionRejected  = (namePromise,error)  => ({type: 'PROMISE', status: 'REJECTED', namePromise, error})
+
+function promiseReducer(state={}, {type, status, payload, error,namePromise}){
+  if (type === 'PROMISE'){
+      return {
+      ...state,
+        [namePromise] : {status, payload, error}
+      }
+    }
+    return state
+}
+
+
+const actionUpload = (files) =>
+	async (dispatch) => {
+		const formData = new FormData();
+		formData.append('photo', files);
+		await dispatch(actionUploadFile(formData));
+	}
+
+
+  const actionUploadFile = formData =>
+	actionPromise('upload',
+		fetch(`${url}upload`, {
+			method: "POST",
+			headers: localStorage.authToken ? {Authorization: 'Bearer ' + localStorage.authToken} : {},
+			body: formData
+		}).then(res => res.json())
+	);
+
+
+const actionlist = () => 
+actionPromise('list', gql(`query list{
+  AdFind(query:"[{}]") {
+    price 
+    _id 
+    owner {
+      _id 
+      login 
+      nick 
+      avatar {
+        _id 
+        url
+        }
+    }
+    title 
+    description 
+    images {
+      _id 
+      url
+    }
+  }
+}`))
+
+
+
+const actionOnelist = (_id) => 
+actionPromise('onelist', gql(`query onelist($q: String!) {
+    AdFindOne(query:$q) {
+      price _id owner {
+                  _id login nick avatar {
+                    _id url
+                  }
+      }
+      title description images {
+        _id url
+      }
+      comments {
+                  _id owner {
+                    _id login nick avatar {
+                      _id url
+                    }
+                  }
+                  createdAt
+                  text
+                  answerTo {
+                    _id
+                  }
+      }
+    }
+  }`,{q: JSON.stringify([{_id}])}
+)) 
+
+
+const actionLogin = (login, password) =>
+actionPromise('login', gql(`query Login($login: String!, $password: String!) {
+     login(login: $login, password: $password)
+   }`, {login, password}));
+
+
+const actionRegister = (login, password) => 
+actionPromise('reg', gql(`mutation Reg($login: String!, $password: String!) {
+     createUser(login: $login, password: $password) {
+       _id
+     }
+   }`, 
+{"login" : login,
+"password": password}
+))
+
+const actionUpdateMe = (user) =>
+    actionPromise('profile', gql(`mutation upsertProfile($user:UserInput) {
+                                        UserUpsert(user:$user) {
+                                            _id login nick
+                                        }
+                                    }`, {user}))
+
+
+
+const actioCreateAd = (ad) =>
+actionPromise('createAd', gql(`mutation userAd($ad:AdInput) {
+  AdUpsert(ad: $ad) {
+      _id title images{
+          _id url
+      }
+  }
+}`, {ad}))
+
+
+const actionOneUser = (_id) =>
+actionPromise('user', gql(`query userOne($qq: String!){
+  UserFindOne(query:$qq){
+    _id
+    login
+   phones
+   addresses
+   nick 
+   incomings{
+    _id text createdAt  owner{
+      _id login
+    }
+   }
+   avatar{
+      url
+    }
+  }
+}`, {qq: JSON.stringify([{_id}])}))
+
+
+
+
+
+const actionAdComment = (comment) =>
+actionPromise('adComment', gql(`mutation adComment($comment:CommentInput) {
+  CommentUpsert(comment: $comment) {
+    _id
+    text owner {
+      _id login nick 
+    }
+  }
+}`, {comment}))
+
+
+const actionAdMessage = (message) =>
+actionPromise('admessage', gql(`mutation admessage($message:MessageInput) {
+  MessageUpsert(message: $message) {
+    _id
+    text
+    createdAt
+    to{
+      _id login
+    }
+    owner{
+      _id
+      login
+      nick
+    }
+  }
+}`, {message}))
+
+
+const actionAllProductsUser = (_id) =>
+actionPromise("productsUser", gql(`query productsUser($userId: String) {
+                AdFind(query: $userId) {
+                  price _id owner {
+                                      _id login nick avatar {
+                                        _id url
+                                      }
+                                    }
+                                    title description images {
+                                      _id url
+                                    }
+                }
+              }`, {userId: JSON.stringify([{___owner: _id}])}));
+
+
+const actionMyUserMessage = (_id) => 
+actionPromise('allMeMessage', gql(`query allMeMessage($userId: String){
+  MessageFind(query: $userId){
+    _id
+  text
+  createdAt
+  to{
+    _id login
+  }
+  owner{
+    _id
+    login
+    nick
+  }
+  }
+}`, {userId: JSON.stringify([{___owner: _id}])}));
+
+
+const actionForMeMessage = (_id) =>
+actionPromise('allForMeMessage', gql(`query forMeMessage($qq: String!){
+  UserFindOne(query:$qq){
+   incomings{
+    _id text createdAt  owner{
+      _id login
+    }
+   }
+  }
+}`, {qq: JSON.stringify([{_id}])}))
+
+
+
+const store = createStore(combineReducers({auth: authReducer, promise:localStoredReducer(promiseReducer, 'promise')}), applyMiddleware(thunk))
+store.subscribe(() => console.log(store.getState()))
+
+
+function jwtDecode(token){
+	try {
+		return JSON.parse(atob(token.split('.')[1]))
+	}
+	catch(e){
+	}
+}
+
+function localStoredReducer(reducer, localStorageKey){
+	function wrapper(state, action){
+		if (state === undefined){
+			try {
+				return JSON.parse(localStorage[localStorageKey]) 
+			}
+			catch(e){ } 
+		}
+		const newState = reducer(state, action)
+		localStorage.setItem(localStorageKey, JSON.stringify(newState)) 
+		return newState
+	}
+	return wrapper
+}
+
+
+function authReducer(state={}, {type, token}){
+	if (type === 'AUTH_LOGIN'){ 
+		const payload = jwtDecode(token)
+		try{
+            if (payload){
+                return {
+                    token,
+                    payload
+                }
+		    }
+        }
+        catch (e) {}
+	}
+	if (type === 'AUTH_LOGOUT'){ 
+		return {}
+	}
+	return state
+}
+
+const actionAuthLogout = () =>
+	() => {
+		store.dispatch({type: 'AUTH_LOGOUT'});
+		localStorage.removeItem('authToken');
+    localStorage.clear()
+	}
+
+const actionAuthLogin = (token) =>
+	() => {
+		const oldState = store.getState()
+		store.dispatch({type: 'AUTH_LOGIN', token})
+		const newState = store.getState()
+		if (oldState !== newState)
+			localStorage.setItem('authToken', token)
+	}
+
+
+
+const origUrl = "http://marketplace.node.ed.asmer.org.ua/"
+const defaultImg = "https://www.lionstroy.ru/published/publicdata/U70619SHOP/attachments/SC/products_pictures/nophoto.png"
+
+const mainList = connect(state => ({
+  cats: state.promise?.list?.payload, 
+  stat: state.promise?.list?.status
+}),
+{loadData: actionlist})(Cards)
+
+const goodUser = connect(state => ({
+  good: state.promise?.onelist?.payload||[],
+  myUser: state.promise?.user?.payload||[]}),{
+    loadData: actionOnelist, 
+    data:actionOneUser} )(Good)
+
+const Profile = connect(state => ({
+  myUser: state.promise?.user?.payload||[]}),
+  {loadData: actionOneUser} )(MyProfile)
+
+const Messages = connect(state => ({
+  myMessage: state.promise?.allMeMessage?.payload, 
+  forMeMessage: state.promise?.allForMeMessage?.payload?.incomings||[],
+  myNewMessage: state.promise?.admessage?.payload||[]}),{
+    loadData: actionMyUserMessage, 
+    dataMyUser:actionForMeMessage,
+    sendMessage:actionAdMessage} )(Message)
+
+const AllProductsUser = connect(state => ({
+  productsUser: state.promise?.productsUser?.payload||[], 
+  stat: state.promise?.list?.status||[]}),
+  {loadData: actionAllProductsUser} )(CardsUser)
+
+const CreateAdConnect = connect(state => ({
+  newImg: state.promise?.upload?.payload||[]}),
+  {action: actionUpload} )(CreateAd)
+
+
+
+store.dispatch(actionAuthLogin(localStorage.authToken))
+!store.getState().auth.token&&history.push("/sign_in")
+
+
 
 function App() {
   return (
-    <div className="App">
-      <header className="App-header">
-        <img src={logo} className="App-logo" alt="logo" />
-        <p>
-          Edit <code>src/App.js</code> and save to reload.
-        </p>
-        <a
-          className="App-link"
-          href="https://reactjs.org"
-          target="_blank"
-          rel="noopener noreferrer"
-        >
-          Learn React
-        </a>
-      </header>
-    </div>
+  <Router history = {history}>
+    <Provider store={store}>
+    <Route path="/sign_in" component={SignIn} exact/>
+    <Route path="/sign_up" component={SignUp} exact/>
+    <Route path="/my_profile" component={Profile} exact/>
+    <Route path="/create_ad" component={CreateAdConnect} exact/>
+    <Header/>
+      <main className="mainHome" style={{ backgroundColor: '#EEEFF1' }}>
+        <main  className="mainCard">
+        <Route path="/message" component={Messages} exact/>
+        <Route path="/all_products_user" component={AllProductsUser} exact/>
+        <Route path="/" component={mainList} exact/>
+        <Route path="/good/:_id" component={goodUser} exact/>
+        </main>
+      </main>
+    </Provider>
+  </Router>
   );
 }
 
-export default App;
+export {actionLogin,actionPromise, store, origUrl,defaultImg,actionUpload, actionOneUser,actionlist, actionAuthLogout,actionAdMessage, actionOnelist,actionAdComment, actioCreateAd, actionAuthLogin, actionRegister, mainList, history, actionUpdateMe, jwtDecode} 
+
+export default App;

+ 96 - 0
src/App.scss

@@ -0,0 +1,96 @@
+.App {
+  text-align: center;
+}
+
+.App-logo {
+  height: 40vmin;
+  pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  .App-logo {
+    animation: App-logo-spin infinite 20s linear;
+  }
+}
+
+.App-header {
+  background-color: #282c34;
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  font-size: calc(10px + 2vmin);
+  color: white;
+}
+
+.App-link {
+  color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+.mainCard{
+  margin-left: 10%;
+  margin-right: 10%;
+}
+
+.mainHome{
+  background-color: #EEEFF1;
+  padding: 26px;
+}
+
+
+.preloader{
+  width: 100%;
+  height: 100%;
+}
+
+.headerButton{
+  display: flex;
+}
+
+.myButt{
+  width: 100%;
+}
+
+.commentCard{
+  margin: 3%;
+  width: 50%;
+}
+
+.commentCardSend{
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  margin: 3%;
+  padding: 10px;
+  width: 50%;
+}
+
+.messageCardSend{
+  display: flex;
+  padding: 10px;
+  align-items: center;
+}
+
+.commentAvatar{
+  display: flex;
+  align-items: center;
+}
+
+.avatarName{
+  margin-left: 8px;
+}
+
+.comment{
+  padding-left: 4%;
+  padding-bottom: 4%;
+}

二进制
src/avatar.png


+ 63 - 0
src/components/add/Add.scss

@@ -0,0 +1,63 @@
+.mainAdd {
+    background-color: #EEEFF1;
+    display: flex;
+    flex-direction: column;
+}
+.bannerAdd {
+    margin: 85px 20px 0px 20px;
+}
+.conteiner__images__pc {
+    margin-top: 20px;
+    margin-left: 78px;
+}
+.flex__boxx {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+.conteiner__images__pc {
+    margin-top: 20px;
+    margin-left: 78px;
+}
+.bannerr {
+    width: 50%;
+    display: flex;
+    flex-direction: column;
+    margin: 3%;
+}
+.banner-link-wrap {
+    min-width: 225px;
+    height: 55px;  
+}
+.buttonPhoto{
+    margin: 20px;
+}
+
+.img{
+    border-radius: 8px;
+    box-shadow: 0.4em 0.4em 5px rgba(122,122,122,0.5);
+}
+
+.inputText{
+    margin-bottom: 3%;
+}
+
+.dropzone {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 20px;
+    border-width: 2px;
+    border-radius: 2px;
+    border-color: #eeeeee;
+    border-style: dashed;
+    background-color: #fafafa;
+    color: #bdbdbd;
+    outline: none;
+    transition: border .24s ease-in-out;
+}
+
+.imd__flex__boxx{
+    display: flex;
+}

+ 258 - 0
src/components/add/index.js

@@ -0,0 +1,258 @@
+import React from 'react';
+import Button from '@material-ui/core/Button';
+import TextField from '@material-ui/core/TextField';
+import { makeStyles } from '@material-ui/core/styles';
+import './Add.scss';
+import {useState, useEffect} from "react";
+import {useDispatch, useSelector} from "react-redux";
+import {actioCreateAd,origUrl} from '../../App.js';
+import AddCircleIcon from '@material-ui/icons/AddCircle';
+import {useDropzone} from 'react-dropzone';
+import {
+  DndContext,
+  KeyboardSensor,
+  PointerSensor,
+  useSensor,
+  useSensors, useDroppable
+} from "@dnd-kit/core";
+import { sortableKeyboardCoordinates, rectSortingStrategy, SortableContext, useSortable, horizontalListSortingStrategy  } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import {arrayMoveImmutable} from 'array-move';
+
+
+const SortableItem = (props) => {
+  const {
+    attributes,
+    listeners,
+    setNodeRef,
+    transform,
+    transition
+  } = useSortable({ id: props.id });
+
+  const itemStyle = {
+    transform: CSS.Transform.toString(transform),
+    transition,
+    cursor: "grab",
+  };
+    
+    const Render = props.render
+
+  return (
+    <div style={itemStyle} ref={setNodeRef} {...attributes} {...listeners}>
+      <Render {...{[props.itemProp]:props.item}}/>
+    </div>
+  );
+};
+
+const Droppable = ({ id, items, itemProp, keyField, render }) => {
+  const { setNodeRef } = useDroppable({ id });
+
+  return (
+    <SortableContext id={id} items={items} strategy={rectSortingStrategy}>
+        {items.map((item) => (
+          <SortableItem render={render} key={item[keyField]} id={item} 
+                        itemProp={itemProp} item={item}/>
+        ))}
+    </SortableContext>
+  );
+};
+
+function Dnd({items:startItems,render, itemProp, keyField, onChange, horizontal}) {
+  const [items, setItems] = useState(
+      startItems
+  );
+  useEffect(() => setItems(startItems), [startItems])
+
+  useEffect(() => {
+      if (typeof onChange === 'function'){
+          onChange(items)
+      }
+  },[items])
+
+  const sensors = useSensors(
+      useSensor(PointerSensor,  { activationConstraint: { distance: 5 } }),
+      useSensor(KeyboardSensor, {
+          coordinateGetter: sortableKeyboardCoordinates
+      })
+  );
+
+  const handleDragEnd = ({ active, over }) => {
+      const activeIndex = active.data.current.sortable.index;
+      const overIndex = over.data.current?.sortable.index || 0;
+
+      setItems((items) => {
+          return arrayMoveImmutable( items, activeIndex, overIndex)
+      });
+  }
+
+  const containerStyle = { display: horizontal ? "flex" : '' };
+
+return (
+  <DndContext
+    sensors={sensors}
+    onDragEnd={handleDragEnd}
+  >
+    <div style={containerStyle}>
+        <Droppable id="aaa" 
+                   items={items} 
+                   itemProp={itemProp} 
+                   keyField={keyField} 
+                   render={render}/>
+    </div>
+  </DndContext>
+);
+}
+
+
+const ImgBox = ({imgArr})=>{
+
+  const useStyles = makeStyles((theme) => ({
+    img:{
+      borderRadius: "8px",
+      width: '100%'
+    }
+  }));
+  const classes = useStyles();
+
+  return(<>
+        <img className={classes.img} src={origUrl+imgArr.url}/>
+        </>
+  )
+}
+
+ function CreateAd({action, newImg}) {
+  const dispatch = useDispatch();
+  const {acceptedFiles, getRootProps, getInputProps} = useDropzone();
+  const [imgArr, setImgArr] = useState([]);
+  const [sortImgArr, setSortImgArr] = useState([]);
+  const [createAd, setCreateAd] = useState({ title:'', description:'', price:0});
+  const [sortOldImgArr, setOldSortImgArr] = useState([]);
+
+  useEffect(() => {
+    acceptedFiles.map(file =>action(file));
+  }, [acceptedFiles]);
+
+useEffect(() => {
+  if(JSON.stringify(newImg) !== JSON.stringify(sortOldImgArr)){
+    if(imgArr.length>0){
+    setImgArr([...imgArr,...[newImg]])
+    setOldSortImgArr(newImg)
+    }else{
+      setImgArr([newImg])
+      setOldSortImgArr(newImg)
+    }
+  }
+}, [newImg]);
+
+
+useEffect(() => {
+  setImgArr([])
+},[])
+
+
+
+
+const onclickCreate = ()=>{
+  if(sortImgArr.length>0){
+    let arrIdImg = sortImgArr.map((el)=> el={_id:el._id})
+    dispatch(actioCreateAd({images:arrIdImg,...createAd, tags: [''], address: owner.address}))
+  }else{
+    dispatch(actioCreateAd({...createAd, tags: [''], address: owner.address}))
+  }
+}
+
+  
+const useStyles = makeStyles((theme) => ({
+  root: {
+    height: '50vh',
+  },
+  paper: {
+    margin: theme.spacing(8, 4),
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+  },
+  input:{
+    marginBottom: '3%'
+  },
+  form: {
+    width: '100%', // Fix IE 11 issue.
+    marginTop: theme.spacing(1),
+  },
+  submit: {
+    margin: theme.spacing(3, 0, 2),
+  },
+}));
+const owner = useSelector(state => state.promise?.user?.payload)
+  const classes = useStyles();
+
+  return (
+    <main className="mainAdd">
+        <div className="bannerAdd" >
+          <div className="conteiner">
+            <div className="flex__boxx">
+              <div className="imd__flex__boxx">
+                <Dnd items={imgArr.filter((el)=>el._id)} render={ImgBox} itemProp="imgArr" keyField="_id"
+                    onChange={newArray=>setSortImgArr(newArray)} //ставлю место консоль лога это: setSortImgArr(newArray)
+                    />
+              </div>
+            <section className="container">
+              <div {...getRootProps({className: 'dropzone'})}>
+                <input {...getInputProps()} />
+                <p>Drag 'n' drop some files here, or click to select files</p>
+              </div>
+              <aside>
+                <h4>Files</h4>
+              </aside>
+            </section>
+          
+                
+              <div className="bannerr">
+              <TextField 
+              type='number'
+              value={createAd.price}
+              onChange={(e) => setCreateAd({...createAd, price: +(e.target.value)})}
+                className={classes.input} 
+                required id="standard-required" 
+                label="Price $" 
+                />
+              <TextField
+              value={createAd.title}
+              onChange={(e) => setCreateAd({...createAd, title: e.target.value})} 
+                className={classes.input} 
+                required id="standard-required" 
+                label="Title"  
+                />
+              <TextField 
+              value={createAd.description}
+              onChange={(e) => setCreateAd({...createAd, description: e.target.value})}
+                id="outlined-multiline-static"
+                label="Description"
+                multiline
+                rows={4}
+                variant="outlined"
+              />
+              <Button
+              onClick={onclickCreate}
+              type="submit"
+              fullWidth
+              variant="contained"
+              color="primary"
+              className={classes.submit}
+              startIcon={<AddCircleIcon/>}
+            >
+              create ad
+            </Button>
+              </div>
+                
+               
+            </div>
+          </div>
+        </div>
+    </main>
+  );
+}
+
+
+
+export default CreateAd

+ 145 - 0
src/components/good/index.js

@@ -0,0 +1,145 @@
+import {useState, useEffect} from 'react';
+import {useSelector, useDispatch} from 'react-redux';
+import { useParams} from 'react-router-dom';
+import {Typography,  Button} from '@material-ui/core';
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import Avatar from '@material-ui/core/Avatar';
+import Card from '@material-ui/core/Card';
+import CardActionArea from '@material-ui/core/CardActionArea';
+import CardContent from '@material-ui/core/CardContent';
+import CardMedia from '@material-ui/core/CardMedia';
+import '../../App.scss';
+import TextField from '@material-ui/core/TextField';
+import {actionAdComment,origUrl,actionAdMessage} from '../../App.js';
+
+
+
+const defaultImg = "https://www.lionstroy.ru/published/publicdata/U70619SHOP/attachments/SC/products_pictures/nophoto.png"
+const url = 'http://marketplace.node.ed.asmer.org.ua/'
+
+const Good = ({loadData,data, good, myUser })=>{
+    const {_id} = useParams()
+    const dispatch = useDispatch();
+    const id = useSelector(state => state.auth?.payload?.sub?.id)
+    const [comment, setComment] = useState('');
+    const [message, setMessage] = useState('');
+      useEffect(() => {
+          loadData(_id)
+      },[_id])
+      useEffect(() => {
+        data(id)
+      },[id])
+    const [indexArr, setindexArr] = useState(0)
+  
+    
+    const useStylesImgGood = makeStyles({
+      root: {
+        maxWidth: "80%",
+        marginTop: 50,
+        margin: "auto"
+      },
+      comment:{
+        width: "100%",
+        marginBottom: '4px'
+      },
+      media: {
+        height: 430,
+      },
+    })
+    const onclickSend =()=>{
+      dispatch(actionAdComment({text:comment, ad:{_id}}))
+      setComment('')
+    }
+    const onclickMessage=()=>{
+      dispatch(actionAdMessage({text:message, to:{_id:good.owner._id}}))
+      setMessage('')
+    }
+    const Commets = ()=>{
+      return(<>
+          {good.comments.map(el=>
+          <div className='commentCard'>
+            <Card >
+              <CardContent className='commentAvatar'>
+                <Avatar  alt="Remy Sharp" src={el.owner.avatar?url+el.owner.avatar.url:''} />
+                <p className="avatarName">
+                  {el.owner.login}
+                </p>
+              </CardContent>
+              <div className='comment'>
+                {el.text&&el.text}
+              </div>
+            </Card>
+          </div>)}</>
+      )
+    }
+
+    
+    
+    return(<>
+        <Card className={useStylesImgGood().root}>
+          <CardActionArea>
+          <CardMedia
+            className={useStylesImgGood().media}
+            onClick={()=>indexArr=== good.images.length-1?setindexArr(0) :setindexArr(indexArr+1)}
+            image={good.images&&good.images[indexArr]?origUrl+good.images[indexArr].url:defaultImg}
+            title="Contemplative Reptile"/>
+            </CardActionArea>
+            <CardContent>
+              <Typography gutterBottom variant="h5" component="h2">
+              {good.title?good.title:""}
+              </Typography>
+              <Typography variant="body2" color="textSecondary" component="p">
+              {good.description?good.description:""}
+              </Typography>
+              <Typography gutterBottom variant="h6" component="h3">
+              {"Цена: "+(good.price?good.price:0)+" грн"}
+              </Typography>
+              <div className='messageCardSend'>
+              <CardContent className='commentAvatar'>
+                <Avatar  alt="Remy Sharp"  />
+                <p className="avatarName">
+                  {good.owner?good.owner.login:""}
+                </p>
+              </CardContent>
+            <TextField 
+              value={message}
+              onChange={(e) => setMessage(e.target.value)} 
+              className={useStylesImgGood().comment}
+              id="outlined-multiline-static"
+              label="Message"
+              multiline
+              variant="outlined"/>
+              <Button size="small" color="primary" onClick={onclickMessage}>
+            SEND
+          </Button>
+          
+        </div>
+            </CardContent>
+          
+        </Card>
+        <Card className='commentCardSend'>
+              <CardContent className='commentAvatar'>
+                <Avatar  alt="Remy Sharp" src={myUser.avatar?url+myUser.avatar.url:''} />
+                <p className="avatarName">
+                  {myUser.login?myUser.login:''}
+                </p>
+              </CardContent>
+            <TextField 
+              value={comment}
+              onChange={(e) => setComment(e.target.value)} 
+              className={useStylesImgGood().comment}
+              id="outlined-multiline-static"
+              label="Comment"
+              multiline
+              variant="outlined"/>
+              <Button size="small" color="primary" onClick={onclickSend}>
+            SEND
+          </Button>
+          
+        </Card>
+        {good.comments&&<Commets/>}
+        </>
+  )}
+
+  export default Good

+ 104 - 0
src/components/goods/index.js

@@ -0,0 +1,104 @@
+import {useState, useEffect} from 'react';
+import {Typography,  Button} from '@material-ui/core';
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import Card from '@material-ui/core/Card';
+import CardContent from '@material-ui/core/CardContent';
+import CardMedia from '@material-ui/core/CardMedia';
+import '../../App.scss';
+import {origUrl} from '../../App.js';
+import SearchIcon from '@material-ui/icons/Search';
+import Grid from '@material-ui/core/Grid';
+import CardActionArea from '@material-ui/core/CardActionArea';
+import CardActions from '@material-ui/core/CardActions';
+import Paper from '@material-ui/core/Paper';
+import InputBase from '@material-ui/core/InputBase';
+import {IconButton} from '@material-ui/core';
+import {history} from '../../App.js';
+
+
+const defaultImg = "https://www.lionstroy.ru/published/publicdata/U70619SHOP/attachments/SC/products_pictures/nophoto.png"
+const url = 'http://marketplace.node.ed.asmer.org.ua/'
+const GoodCard = ({good}) => {
+  const [indexArr, setindexArr] = useState(0)
+  const useStylesImg = makeStyles({
+    root: {
+      maxWidth: 345,
+    },
+    media: {
+      height: 280,
+    },
+  })
+  const onClickGood = ()=>{
+    history.push("/good/"+good._id)
+  }
+  return (
+  <Grid item xs={6} md={3} >
+    <Card className={useStylesImg().root}>
+    <CardActionArea>
+    <CardMedia
+      className={useStylesImg().media}
+      onClick={()=>indexArr=== good.images.length-1?setindexArr(0) :setindexArr(indexArr+1)}
+      image={good.images&&good.images[indexArr]?origUrl+good.images[indexArr].url:defaultImg}
+      title="Contemplative Reptile"/>
+      <CardContent>
+          <Typography gutterBottom variant="h5" component="h2">
+          {good.title?good.title:""}
+          </Typography>
+          <Typography gutterBottom variant="h6" component="h3">
+          {"Цена: "+(good.price?good.price:0)+" грн"}
+          </Typography>
+        </CardContent>
+    </CardActionArea>
+    <CardActions>
+        <Button size="small" color="primary" onClick={onClickGood}>
+          Подробнее
+        </Button>
+      </CardActions>
+    </Card>
+  </Grid>
+)}
+
+const Cards = ({loadData, cats=[], stat}) => {
+  useEffect(() => {
+    loadData()
+  },[])
+  const useStyles = makeStyles((theme) => ({
+    root: {
+      padding: '2px 4px',
+      display: 'flex',
+      alignItems: 'center',
+      marginTop: '55px',
+      marginBottom: '20px',
+      width: '70%',
+      marginLeft: '16%'
+    },
+    input: {
+      marginLeft: theme.spacing(1),
+      flex: 1,
+    },
+    iconButton: {
+      padding: 10,
+    },
+  }));
+  const classes = useStyles()
+  if(stat==='PENDING'){
+    return(
+      <img className='preloader' src='https://i.pinimg.com/originals/c8/a1/76/c8a1765ffc8243f3a7b176c0ca84c3c1.gif' />
+    )
+  }
+  return(<>
+    <Paper component="form" className={classes.root}>
+    <IconButton className={classes.iconButton} aria-label="search">
+        <SearchIcon />
+      </IconButton>
+      <InputBase
+        className={classes.input}
+        placeholder="Search"/>
+    </Paper>
+  <Grid container spacing={4} >
+  {cats.reverse().map(el=><GoodCard key={el._id} good={el}/>)}
+  </Grid></>)
+}
+
+  export default Cards

+ 148 - 0
src/components/header/index.js

@@ -0,0 +1,148 @@
+import {useSelector} from 'react-redux';
+import React from 'react';
+import '../../App.scss';
+import {history, actionlist, actionAuthLogout, store} from '../../App.js';
+import MenuItem from '@material-ui/core/MenuItem';
+import Menu from '@material-ui/core/Menu';
+import {AppBar, Container, Toolbar, IconButton, Typography, Box, Button} from '@material-ui/core';
+import { makeStyles } from '@material-ui/core/styles';
+import Avatar from '@material-ui/core/Avatar';
+import HomeIcon from '@material-ui/icons/Home';
+import MeetingRoomIcon from '@material-ui/icons/MeetingRoom';
+import AccountBoxIcon from '@material-ui/icons/AccountBox';
+import AddCircleIcon from '@material-ui/icons/AddCircle';
+import BallotIcon from '@material-ui/icons/Ballot';
+import EmailIcon from '@material-ui/icons/Email';
+
+
+
+const Header = ()=> {
+  const logined = useSelector(state => state?.auth?.token);
+  const useStyles = makeStyles((theme)=>({
+    menuButton:{
+      position: "absolute",
+      right: 100,
+    },
+    Buttons:{
+      position: "absolute",
+      left: 50,
+      marginTop: "-20px"
+    },
+    title:{
+      flexGrow:1,
+      cursor: "pointer"
+    },
+    root: {
+      display: 'flex',
+      '& > *': {
+        margin: theme.spacing(1),
+      },
+    },
+    small: {
+      width: theme.spacing(3),
+      height: theme.spacing(3),
+    },
+    large: {
+      width: theme.spacing(7),
+      height: theme.spacing(7),
+      position: "absolute",
+      right: 50,
+
+    },
+    menu:{
+      marginLeft: "6%"
+    }
+    
+  }))
+  const classes = useStyles()
+  const onclickHome = ()=>{
+    handleClose()
+    if(store.getState().auth.token){
+      history.push("/")
+      store.dispatch(actionlist())
+    }else{
+      history.push("/sign_in")
+    }
+  }
+  const onclickLogout = ()=>{
+    store.dispatch(actionAuthLogout())
+    handleClose()
+    history.push("/sign_in")
+  }
+  const onclickMyProfile = ()=>{
+    handleClose()
+    history.push("/my_profile")
+  }
+  const onclickAllProductsUser = ()=>{
+    handleClose()
+    history.push("/all_products_user")
+  }
+  const onclickCreateAd = ()=>{
+    handleClose()
+    history.push("/create_ad")
+  }
+  const onclickMessage = ()=>{
+    handleClose()
+    history.push("/message")
+  }
+  const [anchorEl, setAnchorEl] = React.useState(null);
+
+  const handleClick = (event) => {
+    setAnchorEl(event.currentTarget);
+  };
+
+  const handleClose = () => {
+    setAnchorEl(null);
+  };
+  const Buttons = ()=>{
+    if (logined){
+      return(<>
+      
+      <Avatar alt="Remy Sharp" src="https://d18jg6w55vcmy1.cloudfront.net/media/resume_images/38beb2471ba34320baae996ce289a7ed.png" className={classes.large} />
+      
+      
+      <div>
+        <Button className={classes.Buttons} variant="outlined" color="inherit" aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
+          Open Menu
+        </Button>
+        <Menu
+        className={classes.menu}
+          id="simple-menu"
+          anchorEl={anchorEl}
+          keepMounted
+          open={Boolean(anchorEl)}
+          onClose={handleClose}
+        >
+          <MenuItem onClick={onclickHome}><HomeIcon/> Home</MenuItem>
+          <MenuItem onClick={onclickMessage}><EmailIcon/> Message</MenuItem>
+          <MenuItem onClick={onclickMyProfile}><AccountBoxIcon/> Profile</MenuItem>
+          <MenuItem onClick={onclickCreateAd}><AddCircleIcon/> Create Ad</MenuItem>
+          <MenuItem onClick={onclickAllProductsUser}><BallotIcon/> My Products</MenuItem>
+          <MenuItem onClick={onclickLogout}><MeetingRoomIcon/> Logout</MenuItem>
+        </Menu>
+      </div>
+    </>)
+    }else{
+      return<>
+        <Box mr={3}>
+        <Button  color="inherit" variant="outlined" onClick={()=>history.push("/sign_in")}>Sign in</Button>
+        </Box>
+        <Button color="secondary" variant="contained" onClick={()=>history.push("/sign_up")}>Sign Up</Button></>
+    }
+  }
+  return (
+  <AppBar position="fixed" className='myButt'>
+          <Container fixed>
+          <Toolbar>
+          <Buttons />
+            <IconButton edge='start'
+            color='inherit'  className={classes.menuButton} onClick={onclickHome}>
+              <Typography variant='h6' className={classes.title}>OLX</Typography>
+            </IconButton>
+          </Toolbar>
+          </Container>
+        </AppBar>
+        )
+}
+
+  export default Header

+ 249 - 0
src/components/message/index.js

@@ -0,0 +1,249 @@
+import React from 'react';
+import Button from '@material-ui/core/Button';
+import TextField from '@material-ui/core/TextField';
+import SaveIcon from '@material-ui/icons/Save';
+import { makeStyles } from '@material-ui/core/styles';
+import AddAPhotoIcon from '@material-ui/icons/AddAPhoto';
+import './message.scss';
+import {useState, useEffect} from "react";
+import Avatar from '@material-ui/core/Avatar';
+import {useDispatch, useSelector, connect} from "react-redux";
+import {useParams} from 'react-router-dom';
+import Card from '@material-ui/core/Card';
+import CardActionArea from '@material-ui/core/CardActionArea';
+import CardContent from '@material-ui/core/CardContent';
+import CardMedia from '@material-ui/core/CardMedia';
+import {actionAdMessage} from '../../App.js';
+
+
+const useStyles = makeStyles((theme) => ({
+  messageList: {
+    width: "70%",
+    maxWidth: "70%",
+    backgroundColor: '#ccd7e3',
+    display: 'flex',
+    flexDirection: 'column-reverse',
+  },
+  userList: {
+    width: "30%",
+    marginRight: "5px",
+    minWidth: "max-content",
+    backgroundColor: '#ccd7e3'
+  },
+  MessageInput:{
+    margin: '10px'
+  },
+  user:{
+    display: 'flex',
+    height: '60px',
+    backgroundColor: '#88a0b9',
+    margin: '10px',
+    alignItems: 'center',
+    padding: '5px'
+  },
+  avatar:{
+    marginRight: '5px'
+  },
+  messageCard:{
+    maxWidth: '80%',
+    margin: '10px',
+    width: 'max-content',
+    padding: '4px'
+  },
+  myUserColor:{
+    backgroundColor: "#848bd8",
+    maxWidth: '80%',
+    margin: '10px',
+    marginRight: '-6px',
+    marginLeft: '-5px',
+    width: 'max-content',
+    padding: '4px',
+  },
+  userColor:{
+    backgroundColor: "#50905f",
+    maxWidth: '80%',
+    maxHeight: '21px',
+    margin: '10px',
+    marginRight: '-6px',
+    width: 'max-content',
+    padding: '4px',
+  },
+  UserDiv:{
+    display: 'flex',
+  },
+  myUserDiv:{
+    display: 'flex',
+    flexDirection: 'row-reverse',
+    marginRight: '15px',
+  }
+}));
+
+function MyMessage({user, message}) {
+  const login = useSelector(state => state.auth?.payload?.sub?.login)
+  const classes = useStyles();
+  return(<div className={login===user?classes.myUserDiv:classes.UserDiv}>
+    <Card className={login===user?classes.myUserColor:classes.userColor}>{user} </Card>
+    <Card className={classes.messageCard}>{message} </Card>
+    </div>
+  )
+}
+
+
+const Users = ({myOnclick, user})=>{
+  const classes = useStyles();
+  return(
+      <CardActionArea onClick={()=>myOnclick(user._id)}>
+        <Card className={classes.user}>
+        <Avatar  className={classes.avatar} alt="Remy Sharp" />
+                <p >{user.login} </p>
+        </Card>
+      </CardActionArea>
+  )
+}
+
+  const Message = ({loadData,dataMyUser ,sendMessage, myMessage=[],forMeMessage=[] , myNewMessage=[]})=>{
+    const classes = useStyles();
+    const dispatch = useDispatch();
+    const id = useSelector(state => state.auth?.payload?.sub?.id)
+    const [time, setTime] = useState(true);
+    
+    const [users, setUsers] = useState([]);
+    
+    useEffect(() => {
+      if(forMeMessage.length>0||myMessage.length>0){
+        let allmessages=[]
+        if(forMeMessage.length>0){
+          allmessages = [...forMeMessage] 
+        }
+        if(myMessage.length>0){
+          allmessages = [...myMessage] 
+        }
+        if(forMeMessage.length>0&&myMessage.length>0){
+          allmessages = [...forMeMessage,...myMessage] 
+        }
+        
+        if(allmessages.length>0){
+          
+          let usersList = allmessages.map((el)=>{
+            if(el.to){
+              return{login:el.to.login, _id:el.to._id}
+            }
+            return{login:el.owner.login, _id:el.owner._id}
+          } )
+        usersList = usersList.reduce((a, b) => {
+          if (!a.find(v => v._id == b._id)) {
+            a.push(b);
+          }
+          return a;
+        }, []);
+        setUsers(usersList)
+        }
+        
+      }
+
+    },[forMeMessage,myMessage])
+    
+    const [send, setSend] = useState(false);
+    useEffect(() => {
+      loadData(id)
+    },[send])
+    const [message, setMessage] = useState('');
+
+
+    const [iduser, setIduser]= useState("");
+
+    
+
+    
+    const [messageArr, setMessageArr] = useState([]);
+    const [allmessage, setAllMessage] = useState([]);
+
+    const onclickSend =()=>{
+      if(message){
+        dispatch(actionAdMessage({text:message, to:{_id:iduser}}))
+        setSend(!send)
+        setMessage('')
+      }
+    }
+
+
+
+  const upDate=()=>{
+      let usersMessage = []
+      forMeMessage.map((el)=>{
+        if(el.owner._id===iduser){
+          usersMessage.push(el)
+        } 
+      })
+      
+      myMessage.map((el)=>{
+        if(el.to._id===iduser){
+          usersMessage.push(el)
+        } 
+      })
+  
+      const sort = usersMessage.sort((a,b)=>a.createdAt-b.createdAt)
+      setMessageArr(sort)
+      setAllMessage([...forMeMessage,...myMessage]) 
+    }
+
+    useEffect(() => {
+      setTimeout(()=>{
+        dataMyUser(id)
+        setTime(!time);
+      },3000)
+    },[time])
+
+
+
+
+    const onclickUser =(id)=>{
+     setIduser(id)
+     let usersMessage = []
+     forMeMessage.map((el)=>{
+        if(el.owner._id===id){
+          usersMessage.push(el)
+        } 
+      })
+      
+      myMessage.map((el)=>{
+        if(el.to._id===id){
+          usersMessage.push(el)
+        } 
+      })
+        
+      const sort = usersMessage.sort((a,b)=>a.createdAt-b.createdAt)
+      setMessageArr(sort) 
+    }
+
+   if(allmessage.length<forMeMessage.length+myMessage.length){
+    upDate()
+   }
+    
+    
+  return (
+    <main className='mainMessage'>
+    <Card className={classes.userList}>
+    {users.map((el)=><Users myOnclick={onclickUser} user={el}/>)}
+    </Card>
+    <Card className={classes.messageList}>
+      <div className='divButtonText'>
+      <Button onClick={onclickSend} size="small" color="primary">SEND</Button>
+        <TextField
+          value={message}
+          onChange={(e) => setMessage(e.target.value)} 
+          className={classes.MessageInput} 
+          id="standard-basic" 
+          label="Message" />
+        <div >{messageArr.map((el)=><MyMessage user={el.owner.login} message={el.text}/>)}</div>
+      </div>
+    </Card>
+    </main>
+  );
+  }
+
+  
+
+
+
+export default Message

+ 24 - 0
src/components/message/message.scss

@@ -0,0 +1,24 @@
+.mainMessage{
+    display: flex;
+    margin-top: 65px;
+    height: 500px;
+}
+
+.messageList{
+    width: 70%;
+}
+
+.userList{
+    width: 30%;
+    margin-right: 5px;
+}
+
+.divButtonText{
+    display: flex;
+    flex-direction: column-reverse;
+}
+
+.userColor{
+    color: rgb(0, 0, 0);
+}
+

+ 35 - 0
src/components/myprofile/App.scss

@@ -0,0 +1,35 @@
+.main1 {
+    background-color: #EEEFF1;
+}
+.banner-wrap {
+    padding: 147px 0 146px;
+}
+.conteiner__images__pc {
+    margin-top: 20px;
+    margin-left: 78px;
+}
+.flex__box {
+    display: flex;
+    justify-content: space-evenly;
+}
+.conteiner__images__pc {
+    margin-top: 20px;
+    margin-left: 78px;
+}
+.banner {
+    width: 50%;
+    margin-left: 3%;
+    margin-right: 3%;
+}
+.banner-link-wrap {
+    min-width: 225px;
+    height: 55px;  
+}
+.buttonPhoto{
+    margin: 20px;
+}
+
+.imgx{
+    border-radius: 8px;
+    box-shadow: 0.4em 0.4em 5px rgba(122,122,122,0.5);
+}

+ 142 - 0
src/components/myprofile/index.js

@@ -0,0 +1,142 @@
+import React from 'react';
+import Button from '@material-ui/core/Button';
+import TextField from '@material-ui/core/TextField';
+import SaveIcon from '@material-ui/icons/Save';
+import { makeStyles } from '@material-ui/core/styles';
+import AddAPhotoIcon from '@material-ui/icons/AddAPhoto';
+import './App.scss';
+import {useState, useEffect} from "react";
+import {useDispatch, useSelector, connect} from "react-redux";
+import {actionUpdateMe} from '../../App.js';
+import {useParams} from 'react-router-dom';
+
+
+const useStyles = makeStyles((theme) => ({
+  root: {
+    height: '50vh',
+  },
+  paper: {
+    margin: theme.spacing(8, 4),
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+  },
+  
+  form: {
+    width: '100%', // Fix IE 11 issue.
+    marginTop: theme.spacing(1),
+  },
+  submit: {
+    margin: theme.spacing(3, 0, 2),
+  },
+}));
+
+ function MyProfile({loadData, myUser=[] }) {
+  const id = useSelector(state => state.auth?.payload?.sub?.id)
+  useEffect(() => {
+    loadData(id)
+},[id])
+  const dispatch = useDispatch();
+  const [user, setUser] = useState(myUser)
+  const [profile, setProfile] = useState({ login:'', nick: '', addresses:'', phones:''});
+  useEffect(() => {
+    if(myUser!==[]){
+        setProfile({ login:myUser.login, nick:myUser.nick, addresses:myUser.addresses, phones:myUser.phones})
+    }
+},[myUser])
+
+  const classes = useStyles();
+  return (
+    <main className="main1">
+        <div className="banner-wrap" >
+          <div className="conteiner">
+            <div className="flex__box">
+              <div className="conteiner__images__pc">
+                <img className="imgx" src="https://d18jg6w55vcmy1.cloudfront.net/media/resume_images/38beb2471ba34320baae996ce289a7ed.png" alt=""/>
+                <div className="buttonPhoto">
+                <Button
+              type="submit"
+              fullWidth
+              variant="outlined"
+              color='primary'
+              startIcon={<AddAPhotoIcon />}>
+              Сhange photo
+            </Button>
+                </div>
+              </div>
+              <div className="banner">
+                <TextField
+              variant="outlined"
+              margin="normal"
+              required
+              fullWidth
+              id="Login"
+              label="Login"
+              name="Login"
+              autoComplete="Login"
+              autoFocus
+              value={profile.login}
+              onChange={(e) => setProfile({...profile, login: e.target.value})}
+            />
+            <TextField
+              variant="outlined"
+              margin="normal"
+              required
+              fullWidth
+              id="Name"
+              label="Name"
+              name="Name"
+              autoComplete="Name"
+              autoFocus
+              value={profile.nick}
+              onChange={(e) => setProfile({...profile, nick: e.target.value})}
+            />
+            <TextField
+              variant="outlined"
+              margin="normal"
+              required
+              fullWidth
+              id="Number phone"
+              label="Number phone"
+              name="Number phone"
+              autoComplete="Number phone"
+              autoFocus
+              value={profile.phones}
+              onChange={(e) => setProfile({...profile, phones: e.target.value})}
+            />
+            <TextField
+              variant="outlined"
+              margin="normal"
+              required
+              fullWidth
+              id="City"
+              label="City"
+              name="City"
+              autoComplete="City"
+              autoFocus
+              value={profile.addresses}
+              onChange={(e) => setProfile({...profile, addresses: e.target.value})}
+            />
+            <Button
+              onClick={() => {dispatch(actionUpdateMe({...profile, _id: id}))}}
+              type="submit"
+              fullWidth
+              variant="contained"
+              color="primary"
+              className={classes.submit}
+              startIcon={<SaveIcon />}
+            >
+              Сhange profile
+            </Button>
+                
+               </div>
+            </div>
+          </div>
+        </div>
+    </main>
+  );
+}
+
+
+
+export default MyProfile

+ 173 - 0
src/components/productsUser/index.js

@@ -0,0 +1,173 @@
+import {useState, useEffect, useRef} from 'react';
+import {Provider, useDispatch, useSelector} from 'react-redux';
+import { makeStyles } from '@material-ui/core/styles';
+import Grid from '@material-ui/core/Grid';
+import Card from '@material-ui/core/Card';
+import TextField from '@material-ui/core/TextField';
+import CardActions from '@material-ui/core/CardActions';
+import CardContent from '@material-ui/core/CardContent';
+import CardMedia from '@material-ui/core/CardMedia';
+import { IconButton, Typography, Button} from '@material-ui/core';
+import AddAPhotoIcon from '@material-ui/icons/AddAPhoto';
+import DeleteIcon from '@material-ui/icons/Delete';
+import CreateIcon from '@material-ui/icons/Create';
+import Paper from '@material-ui/core/Paper';
+import SearchIcon from '@material-ui/icons/Search';
+import InputBase from '@material-ui/core/InputBase';
+import VisibilityIcon from '@material-ui/icons/Visibility';
+import {origUrl, defaultImg, actioCreateAd, history} from '../../App.js';
+
+
+
+const GoodCard = ({good}) => {
+    const dispatch = useDispatch();
+    const owner = useSelector(state => state.promise?.user?.payload)
+    const [indexArr, setindexArr] = useState(0)
+    const [createAd, setCreateAd] = useState({ title:good.title?good.title:"", description:good.description?good.description:"", price:good.price?good.price:0});
+    const onClickGoods = ()=>{
+        dispatch(actioCreateAd({...createAd, _id:good._id, tags: [''], address: owner.address}))
+    }
+    const useStylesImg = makeStyles({
+        button:{
+            margin: "3%"
+        },
+        input:{
+            margin: '3px'
+        },
+        inputDescription:{
+            margin: '3px',
+            width: '90%'
+        },
+        mediaButton:{
+            display: "flex",
+            flexDirection: "column",
+            justifyContent: "space-around",
+            alignItems: "stretch"
+        },
+        contentCard:{
+            display: "flex",
+            flexDirection: "column",
+            width: "40%",
+        },
+        content:{
+            width: "60%",
+            display: "flex",
+            flexWrap: "wrap",
+            alignContent: "space-around",
+            justifyContent: "space-evenly",
+        },
+        root: {
+            display: "flex",
+            margin: "2%",
+            marginTop: "15px",
+        },
+        media: {
+            width: "100%",
+            height: 280,
+        },
+    })
+    return (
+    
+      <Card className={useStylesImg().root}>
+        <CardContent className={useStylesImg().contentCard}>
+            <CardMedia
+                className={useStylesImg().media}
+                onClick={()=>indexArr=== good.images.length-1?setindexArr(0) :setindexArr(indexArr+1)}
+                image={good.images&&good.images[indexArr]?origUrl+good.images[indexArr].url:defaultImg}
+                title="Contemplative Reptile"/>
+            <Button
+                className={useStylesImg().button}
+                type="submit"
+                fullWidth
+                variant="outlined"
+                color='primary'
+                startIcon={<AddAPhotoIcon />}>
+                Сhange photo
+            </Button>
+        </CardContent>
+        <CardContent className={useStylesImg().content}>
+        <TextField
+              value={createAd.title}
+              onChange={(e) => setCreateAd({...createAd, title: e.target.value})} 
+                className={useStylesImg().input} 
+                required id="standard-required" 
+                label="Title"  
+                />
+            <TextField 
+              type='number'
+              value={createAd.price}
+              onChange={(e) => setCreateAd({...createAd, price: +(e.target.value)})}
+                className={useStylesImg().input} 
+                required id="standard-required" 
+                label="Price $" 
+                />
+                <TextField 
+              value={createAd.description}
+              onChange={(e) => setCreateAd({...createAd, description: e.target.value})}
+                id="outlined-multiline-static"
+                label="Description"
+                className={useStylesImg().inputDescription} 
+                multiline
+                rows={4}
+                variant="outlined"
+              />
+          </CardContent>
+          <CardActions className={useStylesImg().mediaButton}>
+          <Button onClick={onClickGoods} color="inherit" variant="outlined" ><CreateIcon/> Сhange </Button>
+          <Button onClick={()=> history.push("/good/"+good._id)} color="inherit" variant="outlined" ><VisibilityIcon/> Open </Button>
+          <Button color="secondary" variant="contained"><DeleteIcon/> Delete </Button>
+        </CardActions>
+      
+      </Card>
+    
+  )}
+  
+  const CardsUser = ({loadData, productsUser=[], stat}) => {
+    const id = useSelector(state => state.auth?.payload?.sub?.id)
+    useEffect(() => {
+      loadData(id)
+    },[id])
+    const useStyles = makeStyles((theme) => ({
+      root: {
+        padding: '2px 4px',
+        display: 'flex',
+        alignItems: 'center',
+        marginTop: '55px',
+        marginBottom: '20px',
+        width: '70%',
+        marginLeft: '16%'
+      },
+      rootGrid:{
+        display: 'flex',
+        flexDirection: 'column',
+      },
+      input: {
+        marginLeft: theme.spacing(1),
+        flex: 1,
+      },
+      iconButton: {
+        padding: 10,
+      },
+    }));
+    const classes = useStyles()
+    if(stat==='PENDING'){
+      return(
+        <img className='preloader' src='https://i.pinimg.com/originals/c8/a1/76/c8a1765ffc8243f3a7b176c0ca84c3c1.gif' />
+      )
+    }
+    return(<>
+      <Paper component="form" className={classes.root}>
+      <IconButton className={classes.iconButton} aria-label="search">
+          <SearchIcon />
+        </IconButton>
+        <InputBase
+          className={classes.input}
+          placeholder="Search"/>
+      </Paper>
+    <Grid className={classes.rootGrid} container spacing={4} >
+    {productsUser.map(el=><GoodCard key={el._id} good={el} />)}
+    </Grid></>)
+  }
+
+
+  export default CardsUser

+ 134 - 0
src/components/signIn/index.js

@@ -0,0 +1,134 @@
+import React from 'react';
+import Avatar from '@material-ui/core/Avatar';
+import  {Button} from '@material-ui/core';
+import CssBaseline from '@material-ui/core/CssBaseline';
+import TextField from '@material-ui/core/TextField';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Checkbox from '@material-ui/core/Checkbox';
+import Link from '@material-ui/core/Link';
+import Grid from '@material-ui/core/Grid';
+import Box from '@material-ui/core/Box';
+import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
+import Typography from '@material-ui/core/Typography';
+import { makeStyles } from '@material-ui/core/styles';
+import Container from '@material-ui/core/Container';
+import {useState, useEffect, useRef} from 'react';
+import {actionLogin, store, actionAuthLogin, history} from '../../App.js';
+
+function Copyright() {
+  return (
+    <Typography variant="body2" color="textSecondary" align="center">
+      {'Anton project © '}
+      <Link color="inherit" href="https://mui.com/">
+        OLX
+      </Link>{' '}
+      {new Date().getFullYear()}
+      {'.'}
+    </Typography>
+  );
+}
+
+const useStyles = makeStyles((theme) => ({
+  paper: {
+    marginTop: theme.spacing(12),
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+  },
+  avatar: {
+    margin: theme.spacing(1),
+    backgroundColor: theme.palette.secondary.main,
+  },
+  form: {
+    width: '100%', // Fix IE 11 issue.
+    marginTop: theme.spacing(1),
+  },
+  submit: {
+    margin: theme.spacing(3, 0, 2),
+  },
+}));
+
+
+
+
+export default function SignIn() {
+  const classes = useStyles();
+  const [text, setText] = useState({login: '', pass: ''})
+  console.log(text)
+    async function buttonOnckick(){
+        const token = await store.dispatch(actionLogin(text.login, text.pass))
+        if (token){
+            store.dispatch(actionAuthLogin(token));
+            history.push("/")
+        }
+    }
+  return (
+        <Container component="main" maxWidth="xs" >
+        <CssBaseline />
+        <div className={classes.paper}>
+            <Avatar className={classes.avatar}>
+            <LockOutlinedIcon />
+            </Avatar>
+            <Typography component="h1" variant="h5">
+            Sign in
+            </Typography>
+            <form className={classes.form} noValidate>
+            <TextField
+                value={text.login}
+                onChange={e => setText({...text, login: e.target.value})}
+                variant="outlined"
+                margin="normal"
+                required
+                fullWidth
+                id="email"
+                label="Email Address"
+                name="email"
+                autoComplete="email"
+                autoFocus
+            />
+            <TextField
+                value={text.pass}
+                onChange={e => setText({...text, pass: e.target.value})}
+                variant="outlined"
+                margin="normal"
+                required
+                fullWidth
+                name="password"
+                label="Password"
+                type="password"
+                id="password"
+                autoComplete="current-password"
+            />
+            <FormControlLabel
+                control={<Checkbox value="remember" color="primary" />}
+                label="Remember me"
+            />
+            <Button
+                onClick={buttonOnckick}
+                fullWidth
+                variant="contained"
+                color="primary"
+                className={classes.submit}
+            >
+                Sign In
+            </Button>
+            <Grid container>
+                <Grid item xs>
+                <Link href="#" variant="body2">
+                    Forgot password?
+                </Link>
+                </Grid>
+                <Grid item>
+                <Link href="#" variant="body2">
+                    {"Don't have an account? Sign Up"}
+                </Link>
+                </Grid>
+            </Grid>
+            </form>
+        </div>
+        <Box mt={8}>
+            <Copyright />
+        </Box>
+        </Container>
+  );
+}

+ 186 - 0
src/components/signup/index.js

@@ -0,0 +1,186 @@
+import React from 'react';
+import Avatar from '@material-ui/core/Avatar';
+import Button from '@material-ui/core/Button';
+import CssBaseline from '@material-ui/core/CssBaseline';
+import TextField from '@material-ui/core/TextField';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Checkbox from '@material-ui/core/Checkbox';
+import Link from '@material-ui/core/Link';
+import Grid from '@material-ui/core/Grid';
+import Box from '@material-ui/core/Box';
+import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
+import Typography from '@material-ui/core/Typography';
+import { makeStyles } from '@material-ui/core/styles';
+import Container from '@material-ui/core/Container';
+import {useState, useEffect, useRef} from 'react';
+import {actionLogin, store, actionAuthLogin, actionRegister, history, actionUpdateMe, jwtDecode} from '../../App.js';
+import {useSelector, useDispatch} from "react-redux";
+
+function Copyright() {
+  return (
+    <Typography variant="body2" color="textSecondary" align="center">
+      {'Anton project © '}
+      <Link color="inherit" href="https://mui.com/">
+      OLX
+      </Link>{' '}
+      {new Date().getFullYear()}
+      {'.'}
+    </Typography>
+  );
+}
+
+const useStyles = makeStyles((theme) => ({
+  paper: {
+    marginTop: theme.spacing(13),
+    display: 'flex',
+    flexDirection: 'column',
+    alignItems: 'center',
+  },
+  avatar: {
+    margin: theme.spacing(1),
+    backgroundColor: theme.palette.secondary.main,
+  },
+  typography: {
+    marginLeft: theme.spacing(15),
+  },
+  form: {
+    width: '100%', // Fix IE 11 issue.
+    marginTop: theme.spacing(3),
+  },
+  submit: {
+    margin: theme.spacing(3, 0, 2),
+  },
+}));
+
+export default function SignUp() {
+  const classes = useStyles();
+  const dispatch = useDispatch();
+  const [text, setText] = useState({login: '', pass: '', pass2: '', lastName:'', firstName:''})
+  
+    async function buttonOnckick(){
+      const user = await dispatch(actionRegister(text.login, text.pass))
+      if(user) {
+        const token = await dispatch(actionLogin(text.login, text.pass))
+        if (token){
+
+            await store.dispatch(actionAuthLogin(token));
+            const nick = text.lastName+" "+text.firstName
+            let profile={ login:text.login, nick: nick, addresses:'', phones:''}
+            const id=jwtDecode(token).sub?.id
+            dispatch(actionUpdateMe({nick, _id: id}));
+            history.push("/")
+        }
+      }
+    }
+  return (
+    <Container component="main" maxWidth="xs">
+      <CssBaseline />
+      <div className={classes.paper}>
+        <Avatar className={classes.avatar}>
+          <LockOutlinedIcon />
+        </Avatar>
+        <Typography component="h1" variant="h5">
+          Sign up
+        </Typography>
+        <form className={classes.form} noValidate>
+          <Grid container spacing={2}>
+          <Grid item xs={12} sm={6}>
+              <TextField
+              value={text.firstName}
+              onChange={e => setText({...text, firstName: e.target.value})}
+                autoComplete="fname"
+                name="firstName"
+                variant="outlined"
+                required
+                fullWidth
+                id="firstName"
+                label="First Name"
+                autoFocus
+              />
+            </Grid>
+            <Grid item xs={12} sm={6}>
+              <TextField
+              value={text.lastName}
+              onChange={e => setText({...text, lastName: e.target.value})}
+                variant="outlined"
+                required
+                fullWidth
+                id="lastName"
+                label="Last Name"
+                name="lastName"
+                autoComplete="lname"
+              />
+            </Grid>
+            <Grid item xs={12}>
+              <TextField
+                value={text.login}
+                onChange={e => setText({...text, login: e.target.value})}
+                variant="outlined"
+                required
+                fullWidth
+                id="email"
+                label="Email Address"
+                name="email"
+                autoComplete="email"
+              />
+            </Grid>
+            <Grid item xs={12}>
+              <TextField
+                value={text.pass}
+                onChange={e => setText({...text, pass: e.target.value})}
+                variant="outlined"
+                required
+                fullWidth
+                name="password"
+                label="Password"
+                type="password"
+                id="password"
+                autoComplete="current-password"
+              />
+            </Grid>
+            <Grid item xs={12}>
+              <TextField
+                value={text.pass2}
+                onChange={e => setText({...text, pass2: e.target.value})}
+                variant="outlined"
+                required
+                fullWidth
+                name="password"
+                label="Password"
+                type="password"
+                id="password"
+                autoComplete="current-password"
+              />
+            </Grid>
+            <Typography variant="body2" color="secondary" className={classes.typography}>{text.pass!==text.pass2&&'Passwords do not match'}</Typography>
+            <Grid item xs={12}>
+              <FormControlLabel
+                control={<Checkbox value="allowExtraEmails" color="primary" />}
+                label="I want to receive inspiration, marketing promotions and updates via email."
+              />
+            </Grid>
+          </Grid>
+          <Button
+            onClick={text.pass===text.pass2&&buttonOnckick}
+            fullWidth
+            variant="contained"
+            color="primary"
+            className={classes.submit}
+          >
+            Sign Up
+          </Button>
+          <Grid container justifyContent="flex-end">
+            <Grid item>
+              <Link href="#" variant="body2">
+                Already have an account? Sign in
+              </Link>
+            </Grid>
+          </Grid>
+        </form>
+      </div>
+      <Box mt={5}>
+        <Copyright />
+      </Box>
+    </Container>
+  );
+}

+ 2 - 2
src/index.js

@@ -6,9 +6,9 @@ import reportWebVitals from './reportWebVitals';
 
 const root = ReactDOM.createRoot(document.getElementById('root'));
 root.render(
-  <React.StrictMode>
+  //<React.StrictMode>
     <App />
-  </React.StrictMode>
+  //</React.StrictMode>
 );
 
 // If you want to start measuring performance in your app, pass a function