ostapenkonataliia 1 ano atrás
pai
commit
5cb8b04b37
100 arquivos alterados com 54385 adições e 925 exclusões
  1. 102 17
      .idea/workspace.xml
  2. 0 90
      HW_react_1/my-app/src/App.js
  3. BIN
      MODULE/cart.png
  4. 0 32
      MODULE/index.html
  5. 0 667
      MODULE/js.js
  6. BIN
      MODULE/logo.png
  7. 0 119
      MODULE/style.css
  8. BIN
      MODULE/thumbnail_add6f9d1c30d_1x.webp
  9. 0 0
      final-project/.gitignore
  10. 5 0
      final-project/.idea/.gitignore
  11. 12 0
      final-project/.idea/final-project.iml
  12. 6 0
      final-project/.idea/inspectionProfiles/Project_Default.xml
  13. 8 0
      final-project/.idea/modules.xml
  14. 6 0
      final-project/.idea/vcs.xml
  15. 0 0
      final-project/README.md
  16. BIN
      final-project/final-project.zip
  17. 30383 0
      final-project/package-lock.json
  18. 45 0
      final-project/package.json
  19. 0 0
      final-project/public/favicon.ico
  20. 0 0
      final-project/public/index.html
  21. 0 0
      final-project/public/logo192.png
  22. 0 0
      final-project/public/logo512.png
  23. 0 0
      final-project/public/manifest.json
  24. 0 0
      final-project/public/robots.txt
  25. 22 0
      final-project/src/App.css
  26. 41 0
      final-project/src/App.js
  27. 0 0
      final-project/src/App.test.js
  28. 112 0
      final-project/src/action/action.js
  29. 11 0
      final-project/src/components/Dialogs.js
  30. 15 0
      final-project/src/components/Header.js
  31. 0 0
      final-project/src/components/Likes.js
  32. 26 0
      final-project/src/components/LoginForm.js
  33. 23 0
      final-project/src/components/NavBar.js
  34. 46 0
      final-project/src/components/Posts.js
  35. 43 0
      final-project/src/components/PostsDraw.js
  36. 12 0
      final-project/src/components/Profile.js
  37. 20 0
      final-project/src/components/ProfileInfo.js
  38. 0 0
      final-project/src/components/RegisterForm.js
  39. 0 0
      final-project/src/components/Style/Dialog.module.css
  40. 14 0
      final-project/src/components/Style/Header.module.css
  41. 7 0
      final-project/src/components/Style/MyPosts.module.css
  42. 11 0
      final-project/src/components/Style/NavBar.modules.css
  43. 0 0
      final-project/src/components/Style/Profile.module.css
  44. 0 0
      final-project/src/components/UserDraw.js
  45. 15 0
      final-project/src/index.css
  46. 19 0
      final-project/src/index.js
  47. 0 0
      final-project/src/logo.svg
  48. 76 0
      final-project/src/reducers/reducers.js
  49. 0 0
      final-project/src/reportWebVitals.js
  50. 0 0
      final-project/src/setupTests.js
  51. 1 0
      hipstagram/.gitignore
  52. 5 0
      hipstagram/.idea/.gitignore
  53. 12 0
      hipstagram/.idea/INSTAGRAM.iml
  54. 6 0
      hipstagram/.idea/inspectionProfiles/Project_Default.xml
  55. 8 0
      hipstagram/.idea/modules.xml
  56. 70 0
      hipstagram/README.md
  57. 18252 0
      hipstagram/package-lock.json
  58. 53 0
      hipstagram/package.json
  59. BIN
      hipstagram/public/favicon.ico
  60. 1 0
      hipstagram/public/heart-like.svg
  61. 43 0
      hipstagram/public/index.html
  62. 15 0
      hipstagram/public/like-svgrepo-com.svg
  63. BIN
      hipstagram/public/logo192.png
  64. BIN
      hipstagram/public/logo512.png
  65. 25 0
      hipstagram/public/manifest.json
  66. 3 0
      hipstagram/public/robots.txt
  67. 435 0
      hipstagram/src/App.css
  68. 16 0
      hipstagram/src/App.js
  69. 8 0
      hipstagram/src/App.test.js
  70. 504 0
      hipstagram/src/actions/index.js
  71. 46 0
      hipstagram/src/actions/like.js
  72. 121 0
      hipstagram/src/actions/likeInComment.js
  73. 39 0
      hipstagram/src/actions/postEdite.js
  74. 119 0
      hipstagram/src/actions/user.js
  75. 34 0
      hipstagram/src/components/Comments/comment.module.js
  76. 471 0
      hipstagram/src/components/Comments/index.js
  77. 77 0
      hipstagram/src/components/Header/header.module.js
  78. 51 0
      hipstagram/src/components/Header/index.js
  79. 47 0
      hipstagram/src/components/LoginForm/index.js
  80. 83 0
      hipstagram/src/components/LoginForm/login-page.module.js
  81. 145 0
      hipstagram/src/components/Post/index.js
  82. 7 0
      hipstagram/src/components/Post/post.module.js
  83. 434 0
      hipstagram/src/components/PostDraw/index.js
  84. 140 0
      hipstagram/src/components/PostDraw/post-draw.module.js
  85. 136 0
      hipstagram/src/components/Profile/PostDetails.css
  86. 55 0
      hipstagram/src/components/Profile/editePost.js
  87. 50 0
      hipstagram/src/components/Profile/editeProfile.js
  88. 626 0
      hipstagram/src/components/Profile/index.js
  89. 289 0
      hipstagram/src/components/Profile/postDEtails.js
  90. 59 0
      hipstagram/src/components/Profile/profile.module.js
  91. 91 0
      hipstagram/src/components/RegistrForm/index.js
  92. 44 0
      hipstagram/src/components/Route.js
  93. 156 0
      hipstagram/src/components/Search/index.js
  94. 29 0
      hipstagram/src/components/Search/search.module.js
  95. 225 0
      hipstagram/src/components/UploadForm/index.js
  96. 105 0
      hipstagram/src/components/UploadForm/upload-form.module.js
  97. 60 0
      hipstagram/src/components/UploadPhoto/index.js
  98. 70 0
      hipstagram/src/components/UserDraw/index.js
  99. 39 0
      hipstagram/src/components/UserDraw/user-draw.module.js
  100. 0 0
      hipstagram/src/images/envelope.png

+ 102 - 17
.idea/workspace.xml

@@ -2,8 +2,70 @@
 <project version="4">
   <component name="ChangeListManager">
     <list default="true" id="c45bf7d2-992f-400a-8194-6f236ee5f805" name="Changes" comment="">
+      <change afterPath="$PROJECT_DIR$/final-project/src/App.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/App.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/Dialogs/Dialog.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/Dialogs/Dialogs.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/Header/Header.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/Header/Header.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/NavBar/NavBar.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/NavBar/NavBar.modules.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/Profile/MyPost/Profile.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/Profile/Profile.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/Profile/Profile.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/final-project/src/components/Profile/ProfileInfo/ProfileInfo.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Dialogs/DialogItem/DialogItem.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Dialogs/DialogItem/DialogItem.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Dialogs/Dialogs.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Dialogs/Dialogs.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Dialogs/DialogsContainer.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Dialogs/Message/Message.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Friends/Friends.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Friends/Friends.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Header/Header.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Header/Header.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Music/Music.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/NavBar/NavBar.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/NavBar/Navbar.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/News/News.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/MyPosts/MyPosts.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/MyPosts/MyPosts.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/MyPosts/MyPostsContainer.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/MyPosts/Post/Post.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/MyPosts/Post/Post.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/Profile.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/Profile.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/ProfileInfo/ProfileInfo.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Profile/ProfileInfo/ProfileInfo.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Setting/Setting.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Users/Users.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Users/Users.module.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/components/Users/UsersContainer.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/redux/DialogsReducer.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/redux/ProfileReducer.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/redux/UsersReducer.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/redux/reduxStore.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/my-app/src/redux/store.js" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/App.js" beforeDir="false" afterPath="$PROJECT_DIR$/HW_react_1/my-app/src/App.js" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/.gitignore" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/README.md" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/package-lock.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/package.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/public/favicon.ico" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/public/index.html" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/public/logo192.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/public/logo512.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/public/manifest.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/public/robots.txt" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/App.css" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/App.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/App.test.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/index.css" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/index.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/logo.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/reportWebVitals.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/HW_react_1/my-app/src/setupTests.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/rgb.js" beforeDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -13,9 +75,9 @@
   <component name="FileTemplateManagerImpl">
     <option name="RECENT_TEMPLATES">
       <list>
-        <option value="CSS File" />
-        <option value="JavaScript File" />
         <option value="HTML File" />
+        <option value="JavaScript File" />
+        <option value="CSS File" />
       </list>
     </option>
   </component>
@@ -31,22 +93,34 @@
     <option name="hideEmptyMiddlePackages" value="true" />
     <option name="showLibraryContents" value="true" />
   </component>
-  <component name="PropertiesComponent">{
-  &quot;keyToString&quot;: {
-    &quot;DefaultHtmlFileTemplate&quot;: &quot;HTML File&quot;,
-    &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
-    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
-    &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
-    &quot;last_opened_file_path&quot;: &quot;C:/A-Level/Modul/index.html&quot;,
-    &quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
-    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
-    &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
-    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
+  <component name="PropertiesComponent"><![CDATA[{
+  "keyToString": {
+    "DefaultHtmlFileTemplate": "HTML File",
+    "RunOnceActivity.OpenProjectViewOnStart": "true",
+    "RunOnceActivity.ShowReadmeOnStart": "true",
+    "WebServerToolWindowFactoryState": "false",
+    "last_opened_file_path": "C:/HipstaGram-master (2)",
+    "list.type.of.created.stylesheet": "CSS",
+    "nodejs_package_manager_path": "npm",
+    "settings.editor.selected.configurable": "preferences.pluginManager",
+    "ts.external.directory.path": "C:\\Program Files\\JetBrains\\WebStorm 2022.2\\plugins\\JavaScriptLanguage\\jsLanguageServicesImpl\\external",
+    "vue.rearranger.settings.migration": "true"
   }
-}</component>
+}]]></component>
   <component name="RecentsManager">
+    <key name="CopyFile.RECENT_KEYS">
+      <recent name="C:\A-Level\JS\final-project\src\components\Profile\ProfileInfo" />
+      <recent name="C:\A-Level\JS\final-project\src\components\Profile\MyPosts" />
+      <recent name="C:\A-Level\JS\final-project\src" />
+      <recent name="C:\A-Level\JS\my-app\src\components\Users" />
+      <recent name="C:\A-Level\JS\my-app\src\redux" />
+    </key>
     <key name="MoveFile.RECENT_KEYS">
-      <recent name="C:\A-Level\JS" />
+      <recent name="C:\A-Level\JS\final-project\src\component\Profile" />
+      <recent name="C:\A-Level\JS\my-app\src\components\Friens" />
+      <recent name="C:\A-Level\JS\my-app\src\components\Profile" />
+      <recent name="C:\A-Level\JS\my-app\src\components\NavBar" />
+      <recent name="C:\A-Level\JS\my-app\src\components\Header" />
     </key>
   </component>
   <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
@@ -113,7 +187,18 @@
       <workItem from="1674856401303" duration="1332000" />
       <workItem from="1674903743476" duration="36957000" />
       <workItem from="1675282416423" duration="1017000" />
-      <workItem from="1675342216018" duration="11779000" />
+      <workItem from="1675342216018" duration="13019000" />
+      <workItem from="1675590048953" duration="10038000" />
+      <workItem from="1675786107956" duration="65000" />
+      <workItem from="1675869407667" duration="8536000" />
+      <workItem from="1675980572395" duration="605000" />
+      <workItem from="1676018356316" duration="16277000" />
+      <workItem from="1676059927673" duration="70488000" />
+      <workItem from="1676534503116" duration="38119000" />
+      <workItem from="1676719260503" duration="67915000" />
+      <workItem from="1676987868108" duration="7277000" />
+      <workItem from="1677015437421" duration="14053000" />
+      <workItem from="1677099793698" duration="24000" />
     </task>
     <servers />
   </component>

+ 0 - 90
HW_react_1/my-app/src/App.js

@@ -1,90 +0,0 @@
-import logo from './logo.svg';
-import './App.css';
-import {useState} from 'react';
-
-const MyDiv = () => {
-    return (
-        <div>
-            Привет-привет!
-            <input />
-        </div>
-        )
-}
-
-const BlockOfTextWithHeader = ({children,title='No Title'}) =>
-<>
-    <h2>{title}</h2>
-    <p>
-        {children}
-    </p>
-</>
-
-// const arr = ["Див 1", "Див 2"]
-// const data = [
-//     {children: '1111',title: 'адын-адын'},
-//     {children: '2222',title: 'два-два'},
-//     {children: '3333',title: 'тры-тры'},
-// ]
-
-// const Counter = () => {
-//     const [count, setCount] = useState(10)
-//     console.log(count)
-//     return (<button onClick={() => setCount(count +1)}>
-//                     {count}
-//             </button>)
-// }
-//
-// const TextLength = ({text}) => <h1>{text.length}</h1>
-//
-// const Input = () => {
-//     const [text, setText] = useState('testtest')
-//     return (
-//         <div>
-//             <input value={text}
-//                 onChange={e => setText(e.target.value.toUpperCase())}/>
-//             <TextLength text={text} />
-//         </div>
-//     )
-// }
-
-const LoginForm = ({onLogin}) => {
-    //тут надо два состояния - для логина и для пароля;
-    //кнопка логина должна быть disabled если одно из полей пустое
-    //по клику на кнопку запустить onLogin и передать туда текущее состояние login и password
-    const [login, setLogin] = useState('')
-    const [password, setPassword] = useState('')
-
-    const lenghtCheck = !(login.length >= 4 && password.length >= 4)
-    return (
-        <div>
-            <input
-                value = {login}
-                onChange={e => setLogin(e.target.value)}
-                type="text"
-                placeholder='Login'/>
-            <input
-                value = {password}
-                onChange={e => setPassword(e.target.value)}
-                type="password"
-                placeholder='Password'
-            />
-            <button
-                onClick={() => onLogin({login, password})}
-                disabled = {lenghtCheck}
-                type="submit"
-            > Login... </button>
-        </div>
-    )
-}
-
-function App() {
-    return (
-        <div className="App">
-            <LoginForm onLogin={({login, password}) => console.log('ЛОГИН И ПАРОЛЬ', login, password)}/>
-        </div>
-    );
-}
-
-
-
-export default App;

BIN
MODULE/cart.png


+ 0 - 32
MODULE/index.html

@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-  <meta charset="UTF-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>Shop</title>
-  <link rel="stylesheet" href="style.css">
-
-</head>
-<body>
-
-<header>
-  <img src="thumbnail_add6f9d1c30d_1x.webp" id="logo">
-  <div id="container">
-    <div id="loginForm">
-      <button id="login">Войти</button>
-      <button id="registration">Зарегистрироваться</button>
-    </div>
-  </div>
-  <div id='cartIcon'><b></b></div>
-</header>
-
-<div id='mainContainer'>
-  <aside id='aside'></aside>
-  <main id='main'></main>
-</div>
-
-<script src="js.js"></script>
-
-</body>
-</html>

+ 0 - 667
MODULE/js.js

@@ -1,667 +0,0 @@
-function createStore(reducer){
-    let state       = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
-    let cbs         = []                     //массив подписчиков
-
-    const getState  = () => state            //функция, возвращающая переменную из замыкания
-    const subscribe = cb => (cbs.push(cb),   //запоминаем подписчиков в массиве
-        () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
-
-    const dispatch  = action => {
-        if (typeof action === 'function'){ //если action - не объект, а функция
-            return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
-        }
-        const newState = reducer(state, action) //пробуем запустить редьюсер
-        if (newState !== state){ //проверяем, смог ли редьюсер обработать action
-            state = newState //если смог, то обновляем state
-            for (let cb of cbs)  cb(state) //и запускаем подписчиков
-        }
-    }
-
-    return {
-        getState, //добавление функции getState в результирующий объект
-        dispatch,
-        subscribe //добавление subscribe в объект
-    }
-}
-
-const jwtDecode = (token) => {
-    try {
-        let payload = JSON.parse(atob(token.split('.')[1]))
-        console.log(payload)
-        return payload
-    } catch (e) {
-        return undefined
-    }
-}
-
-//---------------------------------------getGql---------------------------------------------
-const getGql = url =>
-    (query, variables) => fetch(url, {
-        method: 'POST',
-        headers: {
-            "Content-Type": "application/json",
-            ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
-        },
-        body: JSON.stringify({ query, variables })
-    }).then(res => res.json())
-        .then(data => {
-            if (data.data) {
-                return Object.values(data.data)[0]
-            }
-            else throw new Error(JSON.stringify(data.errors))
-        })
-
-
-const url = 'http://shop-roles.node.ed.asmer.org.ua/'
-const gql = getGql(url + 'graphql')
-
-
-//------------------------------------------------PromiseReducer---------------------------------
-function promiseReducer(state={}, {type, status, payload, error, name}){
-    if (type === 'PROMISE'){
-        return {
-            ...state,
-            [name] : {status, payload, error}
-        }
-    }
-    return state
-}
-
-const actionPending   = (name) => ({type: 'PROMISE', status: 'PENDING', name})
-const actionFulfilled = (name, payload) => ({type: 'PROMISE', status: 'FULFILLED', name, payload})
-const actionRejected  = (name, error)   => ({type: 'PROMISE', status: 'REJECTED',  name, error})
-
-
-//-----------------------------------------------------actionPromise-------------------------------------------
-const actionPromise = (name, promise) =>
-    async dispatch => {
-        dispatch(actionPending(name)) //сигнализируем redux, что промис начался
-        try{
-            const payload = await promise //ожидаем промиса
-            dispatch(actionFulfilled(name, payload)) //сигнализируем redux, что промис успешно выполнен
-            return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
-        }
-        catch (error){
-            dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
-        }
-    }
-
-
-//----------------------------------------------------authReducer---------------------------------------
-function authReducer(state={}, {type, token}) {
-    if (type === 'AUTH_LOGOUT'){
-        window.localStorage.removeItem('authToken');
-        return {}
-    }
-    if(type === "AUTH_LOGIN"){
-        try{
-            window.localStorage.setItem('authToken',token);
-            return {
-                token: token,
-                payload: jwtDecode(token)
-            }
-        }catch (e) {
-        }
-    }
-    return state
-}
-
-const actionAuthLogin  = token => ({type: 'AUTH_LOGIN', token})
-const actionAuthLogout = ()    => ({type: 'AUTH_LOGOUT'})
-
-
-//--------------------------------------------------cartReducer------------------------------------------
-function cartReducer  (state = {}, {type, good, count=1}) {
-
-    if (type === 'CART_ADD') {
-        return {
-            ...state,
-            [good._id]: {
-                good,
-                count: +count}
-        }
-    }
-
-    if (type === 'CART_SUB') {
-        if (state([good._id].count - count) <= 0) {
-            delete state[good._id]
-        } else {
-            return {
-                ...state,
-                [good._id]: {
-                    good,
-                    count: state[good._id].count - count}
-            }
-        }
-    }
-
-    if (type === 'CART_DEL') {
-        delete state[good._id]
-        return {...state}
-
-    }
-
-    if (type === 'CART_SET') {
-        return {
-            ...state,
-            [good._id]: {
-                good,
-                count}
-        }
-    }
-
-    if (type === 'CART_CLEAR') {
-        state = {}
-    }
-    return state
-}
-
-const actionCartAdd = (good, count=1) => ({type: 'CART_ADD', count, good})
-const actionCartSub = (good, count=1) => ({type: 'CART_SUB', count, good})
-const actionCartDel = (good) => ({type: 'CART_DEL', good})
-const actionCartSet = (good, count=1) => ({type: 'CART_SET', count, good})
-const actionCartClear = () => ({type: 'CART_CLEAR'})
-
-
-//---------------------------------------------localStoredReducer---------------------------------
-function localStoredReducer(originalReducer, localStorageKey) {
-    function wrapper(state, action) {
-        if (!state) {
-            try {
-                return JSON.parse(localStorage[localStorageKey])
-            }
-            catch { }
-        }
-        let res = originalReducer(state, action)
-        localStorage[localStorageKey] = JSON.stringify(res)
-        return res;
-    }
-    return wrapper
-}
-
-// -------------------------------------------CombineReducers---------------------------------------
-function combineReducers(reducers){
-    function totalReducer(state={}, action){
-        const newTotalState = {}
-        for (const [reducerName, reducer] of Object.entries(reducers)){
-            const newSubState = reducer(state[reducerName], action)
-            if (newSubState !== state[reducerName]){
-                newTotalState[reducerName] = newSubState
-            }
-        }
-        if (Object.keys(newTotalState).length){
-            return {...state, ...newTotalState}
-        }
-        return state
-    }
-    return totalReducer
-}
-
-
-const totalReducer = combineReducers({
-    promise: promiseReducer,
-    auth: localStoredReducer(authReducer,'auth'),
-    cart: localStoredReducer(cartReducer,'cart')
-})
-
-const store = createStore(totalReducer)
-store.subscribe(() => console.log(store.getState()))
-
-
-//Запрос на список корневых категорий
-const actionRootCats = () =>
-    actionPromise('rootCats', gql(`query rootCats2{
-    CategoryFind(query: "[{\\"parent\\": null}]"){
-            _id 
-            name
-        }   
-    }`))
-
-store.dispatch(actionRootCats())
-
-//Запрос для получения одной категории с товарами и картинками
-const oneCatWithGoods = (_id) =>
-    actionPromise('oneCatWithGoods', gql(`query oneCatWithGoods ($q:String) {
-      CategoryFindOne (query: $q){
-          _id 
-          name 
-          parent{
-            _id 
-            name} 
-          subCategories {
-          _id 
-          name
-        },
-        goods {
-          _id 
-          name 
-          price 
-          description
-          images {
-            url
-          }
-        }
-      }}`,
-        {q: JSON.stringify([{_id}])}
-    ))
-
-
-//Запрос на получение товара с описанием и картинками
-const goodWithDescAndImg = (_id) =>
-    actionPromise('goodWithDescAndImg', gql(`query goodWithDescAndImg ($q:String) {
-      GoodFindOne (query: $q){
-          _id 
-          name
-          price
-          description 
-          images {
-            url
-          }
-    }}`,
-        {q: JSON.stringify([{_id}])}
-    ))
-
-
-// Запрос на регистрацию
-const registration = (login, password) =>
-    actionPromise ('registration', gql(`mutation registration ($login:String, $password: String) {
-    UserUpsert (user: {login: $login, password: $password}) {
-      _id createdAt
-    }
-  }`,
-        {"login" : login, "password": password}
-    ))
-
-
-// Запрос на логин
-const loginUser = (login, password) =>
-    actionPromise(
-        'login',
-        gql(
-            `query log($login: String, $password: String) {
-      login(login: $login, password: $password)
-      }`,
-            {login, password}
-        )
-    )
-
-
-// Запрос истории заказов
-const historyOfOrders = () =>
-    actionPromise('historyOfOrders', gql(`query historyOfOrders ($q: String) {
-      OrderFind(query: $q) {
-        _id
-        total
-        createdAt
-        orderGoods {
-          good {
-            name
-          }
-          price
-          count
-          total
-        }
-        total
-      }
-    }`,
-        {q: JSON.stringify([{}])}
-    ))
-
-store.dispatch(actionRootCats())
-
-
-// Запрос оформления заказа
-const NewOrder = (orderGoods) =>
-    actionPromise('NewOrder', gql(`mutation NewOrder($order: OrderInput) {
-        OrderUpsert(order: $order) {
-          _id
-          orderGoods {
-            _id
-            price
-            count
-            total
-            good {
-              name
-              _id
-              price
-              images {
-                url
-              }
-            }
-          }
-        }
-      }`,
-        {order: {orderGoods}}
-    ))
-
-
-//-----------------------------------Отрисовка категорий-------------------------------------
-store.subscribe(() => {
-    const {status, payload, error} = store.getState().promise.rootCats
-    if (status === 'FULFILLED'){
-        aside.innerHTML = ''
-        for (const {_id, name} of payload){
-            aside.innerHTML += `<a href= "#/category/${_id}">${name}</a>`
-        }
-    }
-})
-
-
-//--------------------------------------отрисовка товаров в категории-----------------------
-store.subscribe(() => {
-    const {status, payload, error} = store.getState().promise?.oneCatWithGoods || {}
-    const [,route] = location.hash.split('/')
-    if(route !== 'category') {
-        return
-    }
-    if (status === 'FULFILLED'){
-        main.innerHTML = ''
-
-        const {name, goods, subCategories} = payload
-        main.innerHTML = `<h1>${name}</h1>`
-
-        if (subCategories !== null) {
-            for (const {_id, name} of subCategories) {
-                main.innerHTML += `<a href= "#/category/${_id}">${name}</a>`
-                console.log(name)
-            }
-        }
-
-        for (const {_id, name, price, images} of goods){
-
-            for (const img of images) {
-                main.innerHTML += `<img src= "${url+ img.url}"> </br>`
-            }
-            main.innerHTML += `<a href= "#/good/${_id}">${name} </br> ${price} грн</a>`
-        }
-    }}
-)
-
-
-//-------------------------------------Отрисовка товара------------------------------------------
-store.subscribe(() => {
-        const {status, payload, error} = store.getState().promise?.goodWithDescAndImg || { }
-        const [,route] = location.hash.split('/')
-        if(route !== 'good') {
-            return
-        }
-
-        if (status === 'FULFILLED'){
-            main.innerHTML = ''
-            const {name, description, images, price} = payload
-
-            main.innerHTML = `<h1>${name}</h1>`
-            for (const img of images) {
-                main.innerHTML += `<img src= "${url+ img.url}">`
-            }
-
-            main.innerHTML += `<p>${description}</p>
-            <p>${price} грн. </p>
-            <button id="buy"> В корзину </button>`
-
-            const buyButton = document.getElementById('buy')
-            cartIcon.innerHTML = ''
-            buyButton.onclick = function () {
-                store.dispatch(actionCartAdd({_id: name, price: price, img: images}))
-            }
-        }
-    }
-)
-
-//----------------------------------Отрисовка цифры в корзине-------------------------------
-store.subscribe(() => {
-    const {cart} = store.getState()
-    let summ = 0
-    for(const {count} of Object.values(cart)) {
-        summ += +count
-    }
-    cartIcon.innerHTML = `<b>${summ}</b>`
-})
-
-
-//-----------------------------------------Логин----------------------------------------
-const loginButton = document.getElementById('login')
-loginForm.append(loginButton)
-loginButton.onclick = () => location.href = `#/login`
-
-const actionFullLogin = (login, password) =>
-    async (dispatch) => {
-        const token = await dispatch(loginUser(login, password))
-
-        if(typeof token === "string"){
-            dispatch(actionAuthLogin(token))
-            main.innerHTML = `<h1>Вы вошли на сайт</h1>`
-
-        } else {
-            main.innerHTML =
-                `<p>Вы ввели неправильные логин или пароль. Повторите попытку </p>
-                <button id="buttonRepeat">Повторить попытку</button>`
-
-            const loginRepeat = document.getElementById('buttonRepeat')
-            loginRepeat.onclick = () => {
-                location.reload()
-                location.href = `#/login`
-            }
-        }
-    }
-
-//-----------------------------------------Авторизация-------------------------------------
-store.subscribe(() => {
-    if(!store.getState().auth) return;
-    const {payload} = store.getState().auth;
-    if(payload){
-        loginForm.innerHTML =
-        `<button id="history"> История заказов </button>
-        <button id="logOut"> Выйти с сайта </button>`
-
-        loginButton.hidden = true
-        registration.hidden = true
-
-        const historyButton = document.getElementById('history')
-        historyButton.onclick = function () {
-            location.href = `#/history`
-        }
-
-        const logOutButton = document.getElementById('logOut')
-        logOutButton.onclick = function () {
-            store.dispatch(actionAuthLogout())
-            main.innerHTML = ` `
-            loginForm.innerHTML = ` `
-            loginButton.hidden = false
-            registration.hidden = false
-        }
-    }
-})
-
-//------------------------------------Регистрация--------------------------------------------
-const registrationButton = document.getElementById('registration')
-loginForm.append(registrationButton)
-registrationButton.onclick = () => location.href = `#/register`
-
-const actionFullRegister = (login, password) =>
-    async (dispatch) => {
-        let userReg = await dispatch(registration(login, password))
-
-        if(userReg){
-            dispatch(actionFullLogin(login,password))
-        } else {
-            main.innerHTML = `Регистрация не удалась. Повторите попытку ещё раз.
-            <button id="buttonRepeatReg">Повторить попытку</button>`
-
-            const buttonRepeatReg = document.getElementById('buttonRepeatReg')
-            buttonRepeatReg.onclick = () => {
-                location.reload()
-                location.href = `#/register`
-            }
-        }
-    }
-
-//-------------------------------------------Заказ-------------------------------------
-const newOrder = () => async (dispatch, getState) => {
-    let { cart } = getState();
-    const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({ good: { _id }, count }));
-
-    let result = await dispatch(NewOrder(orderGoods))
-    if (result?._id) {
-        dispatch(actionCartClear())
-    }
-}
-
-
-//--------------------------------------Корзина------------------------------------------
-store.subscribe ( () => {
-    let cartIcon = document.getElementById('cartIcon')
-    cartIcon.onclick = function myCart() {
-        location.href = `#/cartIcon`
-        console.log(store.getState().cart)
-
-        let storeCart = store.getState().cart
-        main.innerHTML = `<h1>Корзина</h1>`
-
-
-        for (let i=0; i<(Object.keys(storeCart).length); i++){
-
-            let div = document.createElement('div')
-            div.id = i
-            main.append(div)
-            let order = document.getElementById(i)
-
-            let name = Object.keys(storeCart)[i]
-            order.innerHTML += `<p>${store.getState().cart[name].good._id}</p>`
-
-            for (const img of store.getState().cart[name].good.img) {
-                order.innerHTML += `<p><img src= "${url+ img.url}"></p>`
-            }
-
-            order.innerHTML +=
-                `<p>${store.getState().cart[name].count} шт</p>
-                <p>Итого: ${store.getState().cart[name].count * store.getState().cart[name].good.price}  </p>`
-
-            let input = document.createElement('input')
-            input.type = 'number'
-            input.value = store.getState().cart[name].count
-            order.append(input)
-
-            let divForBtn = document.createElement('div')
-            order.append(divForBtn)
-            let button = document.createElement('button')
-            button.id = 'delCartBtn'
-            button.innerText = 'Удалить товар'
-            divForBtn.append(button)
-
-            input.oninput = function () {
-                if (input.value <= 0){
-                    store.dispatch(actionCartDel({_id: name}))
-                    myCart()
-                }
-                console.log(input.value, name)
-
-                store.dispatch(actionCartSet({_id: name, price: store.getState().cart[name].good.price, img: store.getState().cart[name].good.img}, input.value))
-                myCart()
-            }
-
-            button.onclick = function () {
-                store.dispatch(actionCartDel({_id: name}))
-                myCart()
-            }
-        }
-
-        let btnCreateOrder = document.createElement('button')
-        btnCreateOrder.id = 'createOrder'
-        btnCreateOrder.innerText = 'Оформить заказ'
-        main.append(btnCreateOrder)
-
-        const idCreateOrderBtn = document.getElementById('createOrder')
-        if (Object.keys(store.getState().auth).length === 0) {
-            idCreateOrderBtn.disabled = true
-        }
-
-        if (Object.keys(store.getState().auth).length !== 0) {
-            idCreateOrderBtn.disabled = false
-
-            idCreateOrderBtn.onclick = function () {
-                store.dispatch(newOrder())
-                store.dispatch(actionCartClear())
-                myCart()
-            }
-        }
-    }
-})
-
-
-//--------------------------------------------История заказов--------------------------------------
-store.subscribe ( () => {
-    const {status, payload, error} = store.getState().promise?.historyOfOrders || { }
-    const [,route] = location.hash.split('/')
-    if(route !== 'history') {
-        return
-    }
-
-    if (status === 'FULFILLED'){
-        main.innerHTML = `<h1> История заказов </h1>`
-        const {_id, total} = payload
-        console.log(payload)
-
-        for(const order of payload){
-            const {_id, total} = order
-            main.innerHTML +=
-                `<div style="width: 300px; border:  solid skyblue;">
-            <p>Номер заказа: ${_id}</p>
-                <p>Всего: ${total} денег </p>
-            </div>  
-            `
-        }
-    }
-})
-
-window.onhashchange = () => {
-    const [,route, _id] = location.hash.split('/')
-
-    const routes = {
-        category() {
-            store.dispatch(oneCatWithGoods(_id))
-        },
-
-        good(){
-            store.dispatch(goodWithDescAndImg(_id))
-        },
-
-        login(){
-            main.innerHTML =
-                `<h2 id="inputTitle">Вход на сайт:</h2>
-        <input id="loginInput" type="text" name="login" placeholder="Введите логин">
-        <input id="passwordInput" type="password" name="password" placeholder="Введите пароль">
-        <button id="sign_in">Войти</button>`
-
-            const sign_inBtn = document.getElementById('sign_in')
-            sign_inBtn.onclick = function () {
-                store.dispatch(actionFullLogin(loginInput.value, passwordInput.value))
-            }
-        },
-
-        register(){
-            main.innerHTML =
-                `<h2>Регистрация:</h2>
-        <input id="loginReg" type="text" name="login" placeholder="Введите логин">
-        <input id="passwordReg" type="password" name="password" placeholder="Введите пароль">
-        <button id="reg">Зарегистрироваться</button>`
-            const regBtn = document.getElementById('reg')
-
-            regBtn.onclick = function () {
-                store.dispatch(actionFullRegister(loginReg.value, passwordReg.value))
-            }
-        },
-
-        cart(){},
-
-        history(){
-            store.dispatch(historyOfOrders())
-        }
-    }
-
-    if (route in routes){ //если route есть в routes
-        routes[route]() //то запустить функцию, которая там лежит
-    }
-}
-
-window.onhashchange()

BIN
MODULE/logo.png


+ 0 - 119
MODULE/style.css

@@ -1,119 +0,0 @@
-body {
-    padding: 0px;
-    margin: 0px;
-}
-
-#mainContainer {
-    display: flex;
-    background-color: #f1eee7;
-}
-
-main {
-    padding: 20px;
-}
-
-#aside {
-    width: 20%;
-}
-
-#aside > a{
-    display: block;
-    text-decoration: none;
-    color: rgb(88, 88, 88);
-    padding: 7px 15px;
-    border: 1px solid lightgray;
-}
-
-#aside > a:hover {
-    background-color: #e0a4b6;
-}
-
-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    min-height: 120px;
-    background-color: #e0a4b6;
-}
-
-#logo {
-    padding-left: 30px;
-    width: 170px;
-    height: 75px;
-}
-
-main a {
-    display: block;
-    text-decoration: none;
-    color: rgb(88, 88, 88);
-
-}
-
-#cartIcon {
-    min-width: 60px;
-    min-height: 50px;
-    background-image: url(cart.png);
-    background-size: 70px;
-    margin-right: 20px;
-    border-radius: 4px;
-
-}
-
-#cartIcon b {
-    border-radius: 5px;
-    background-color: #f6d1f1;
-    padding: 6px;
-    font-size: 11px;
-}
-
-img {
-    max-width: 150px;
-    max-height: 150px;
-}
-
-#greeting {
-    border: 1px solid #fff;
-    height: 26px;
-    font-size: 14px;
-}
-
-#login,
-#registration,
-#logOut,
-#history{
-    margin: 10px;
-    padding: 5px 15px;
-    font-size: 14px;
-    box-shadow: 0 5px 6px rgb(65 132 144 / 10%), 0 2px 4px rgb(0 0 0 / 8%);
-    background-color: #efd6d6;
-
-}
-
-#loginInput,
-#passwordInput,
-#sign_in,
-#buy,
-#delCartBtn,
-#loginReg,
-#passwordReg,
-#reg{
-    box-sizing: border-box;
-    text-decoration: none;
-    font-size: 14px;
-    padding: 5px;
-    margin: 15px;
-    border: 1px solid dimgrey;
-}
-
-#ordersLink {
-    border: 1px solid #fff;
-    height: 26px;
-    font-size: 14px;
-}
-
-#createOrder {
-    font-size: 18px;
-    padding: 10px 15px;
-}
-
-

BIN
MODULE/thumbnail_add6f9d1c30d_1x.webp


HW_react_1/my-app/.gitignore → final-project/.gitignore


+ 5 - 0
final-project/.idea/.gitignore

@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 12 - 0
final-project/.idea/final-project.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
final-project/.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+  </profile>
+</component>

+ 8 - 0
final-project/.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/final-project.iml" filepath="$PROJECT_DIR$/.idea/final-project.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
final-project/.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
+  </component>
+</project>

HW_react_1/my-app/README.md → final-project/README.md


BIN
final-project/final-project.zip


Diferenças do arquivo suprimidas por serem muito extensas
+ 30383 - 0
final-project/package-lock.json


+ 45 - 0
final-project/package.json

@@ -0,0 +1,45 @@
+{
+  "name": "final-project",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@emotion/react": "^11.10.6",
+    "@emotion/styled": "^11.10.6",
+    "@mui/material": "^5.11.11",
+    "@testing-library/jest-dom": "^5.16.5",
+    "@testing-library/react": "^13.4.0",
+    "@testing-library/user-event": "^13.5.0",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-redux": "^8.0.5",
+    "react-router-dom": "^6.5.0",
+    "react-scripts": "5.0.1",
+    "redux": "^4.2.1",
+    "redux-thunk": "^2.4.2",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test",
+    "eject": "react-scripts eject"
+  },
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  }
+}

HW_react_1/my-app/public/favicon.ico → final-project/public/favicon.ico


HW_react_1/my-app/public/index.html → final-project/public/index.html


HW_react_1/my-app/public/logo192.png → final-project/public/logo192.png


HW_react_1/my-app/public/logo512.png → final-project/public/logo512.png


HW_react_1/my-app/public/manifest.json → final-project/public/manifest.json


HW_react_1/my-app/public/robots.txt → final-project/public/robots.txt


+ 22 - 0
final-project/src/App.css

@@ -0,0 +1,22 @@
+.app-wrapper {
+    display: grid;
+
+    grid-template-areas:
+  "h h"
+  "n c";
+    grid-template-rows: 100px 1fr;
+    grid-template-columns: 2fr 10fr;
+
+    width: 100%;
+    margin: 0 auto;
+}
+
+.banner {
+    width: 200px;
+    height: 200px;
+}
+
+.app-wrapper-content {
+    grid-area: c;
+    background-color: #dadee0;
+}

+ 41 - 0
final-project/src/App.js

@@ -0,0 +1,41 @@
+import './App.css';
+import Header from "./components/Header";
+import NavBar from "./components/NavBar";
+import Profile from "./components/Profile";
+import {BrowserRouter, Routes, Route} from "react-router-dom";
+import Dialogs from "./components/Dialogs";
+import {ConnectPosts, Post} from "./components/Posts";
+import Login, {ConnectLoginForm} from "./components/LoginForm";
+import {Provider, connect, useDispatch, useSelector} from "react-redux";
+import store from "./reducers/reducers";
+import {ConnectPostDraw} from "./components/PostsDraw";
+import {AppBar, Container} from "@mui/material";
+
+
+const App = (props) => {
+    return (
+        <Provider store={store}>
+            <BrowserRouter>
+                <div className='app-wrapper'>
+                    <AppBar>
+                        <Header />
+                    </AppBar>
+
+                    <NavBar />
+
+                        <div className='app-wrapper-content'>
+                            <Routes>
+                                <Route path="/profile" element={<Profile/>}/>
+                                <Route path="/dialogs" element={<Dialogs/>}/>
+                                <Route path="/posts" element={<ConnectPosts />}/>
+                            </Routes>
+                        </div>
+
+                </div>
+            </BrowserRouter>
+        </Provider>
+    )
+}
+
+export default App;
+

HW_react_1/my-app/src/App.test.js → final-project/src/App.test.js


+ 112 - 0
final-project/src/action/action.js

@@ -0,0 +1,112 @@
+import {actionPromise} from "../reducers/reducers";
+import {connect} from "react-redux";
+
+//---------------------------------------getGql---------------------------------------------
+const getGql = url =>
+    (query, variables) => fetch(url, {
+        method: 'POST',
+        headers: {
+            "Content-Type": "application/json",
+            ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } : {})
+        },
+        body: JSON.stringify({ query, variables })
+    }).then(res => res.json())
+        .then(data => {
+            if (data.data) {
+                return Object.values(data.data)[0]
+            }
+            else throw new Error(JSON.stringify(data.errors))
+        })
+
+const gql = getGql('http://hipstagram.node.ed.asmer.org.ua/graphql')
+
+//---------------------------------------Action---------------------------------------------
+export const actionGetAboutMe = (id) =>
+    actionPromise('aboutMe', gql(`query userOned($myID:String!){
+                        UserFindOne(query: $myID){
+                            _id  login nick
+                            avatar { _id url }
+                            following{ _id}
+                        }
+                }`, { myID: JSON.stringify([{ _id: id }]) }))
+
+export const actionUserById = (id) => {
+    return(
+        actionPromise('UserById', gql(`query UserById($_id:String) {
+        UserFindOne(query: $_id){
+          _id, nick, login, avatar {url}
+          followers {_id, nick, login},
+          following {_id, nick, login}
+          } 
+        }`,{"_id": JSON.stringify([{ "_id": id }])})
+        ))}
+
+
+export const actionGetPosts = (skip) =>{
+    return(
+        actionPromise('allPosts', gql(`query allposts($query: String!){
+          PostFind(query: $query){
+            _id, text, title,
+            owner{_id, nick, login, avatar 
+             {url}
+            }, 
+            images{url},
+            comments{text},
+            createdAt
+        }
+      }`, {query: JSON.stringify([{},
+                {sort:[{_id: -1}],
+                    skip:[skip || 0],
+                    limit:[500],}
+            ])})))
+}
+
+export const actionGetAllUsers = (value) =>
+    actionPromise('findUsersAll', gql(`query findUsersAll($query:String!) {
+                                UserFind(query: $query) {
+                                    _id login nick 
+                                    avatar { _id url } 
+                                }
+    }`, {
+        query: JSON.stringify([{
+            $or: [{ nick: `/${value}/` }, { login: `/${value}/` }]},
+            {sort: [{ login: 1 }]},
+        ])
+    }))
+
+// export const actionUploadPhoto = (file, userId) => async dispatch =>{
+//     // let result = await dispatch(actionUploadFile(file))
+//     console.log(userId, result._id)
+//     dispatch(actionInsertPhoto(result._id, userId))
+// }
+
+
+export const actionPostById = (id) => {
+    return(
+        actionPromise('PostById', gql(`query PostById($_id:String) {
+      PostFind(query: $_id){
+        _id createdAt title text,
+        images {url},
+        owner {login, nick, avatar {url},
+        followers {_id, nick, login},
+    		following {_id, nick, login}}
+      }
+	} `, {"_id": JSON.stringify([{ "___owner": id }])}))
+    )}
+
+export const actionOnLikePost = (_id) =>
+    actionPromise('onLikePost', gql(`mutation LikePost($like:LikeInput){
+        LikeUpsert(like:$like){
+            _id
+        }
+    }`, { like: { post: { _id } } }))
+
+export const actionDelLikePost = (_id) =>
+    actionPromise('delLikePost', gql(`mutation LikeRemove($like:LikeInput){
+            LikeDelete(like:$like){
+                _id
+            }
+        }`, { like: { _id } }))
+
+
+

+ 11 - 0
final-project/src/components/Dialogs.js

@@ -0,0 +1,11 @@
+import style from './Style/Dialog.module.css'
+
+const Dialogs = (props) => {
+  return (
+      <div>
+          Dialogs
+      </div>
+  )
+}
+
+export default Dialogs

+ 15 - 0
final-project/src/components/Header.js

@@ -0,0 +1,15 @@
+import style from './Style/Header.module.css'
+
+import './Style/Header.module.css'
+import {NavLink} from "react-router-dom";
+
+const Header = (props) => {
+    return (
+        <header className={style.header}>
+            <div><h1> HIPSTAGRAM </h1></div>
+            <NavLink to="/login"> Login </NavLink>
+        </header>
+    )
+}
+
+export default Header

+ 0 - 0
final-project/src/components/Likes.js


+ 26 - 0
final-project/src/components/LoginForm.js

@@ -0,0 +1,26 @@
+// import React, {useState} from 'react';
+// // import { Form, Button } from 'react-bootstrap';
+// import { connect } from 'react-redux';
+// import '../App.css';
+// import {actionFullLogin} from "../reducers/reducers";
+//
+//
+// const LoginForm = ({onLogin}) => {
+//     const [login, setLogin] = useState('')
+//     const [password, setPassword] = useState('')
+//
+//     return (
+//         <>
+//             <div className="login-page">
+//                 <div className="login-form">
+//                     <input class = "login" type="login" placeholder="Enter login" value = {login} onChange = {e => setLogin(e.target.value)}/>
+//                     <input class = "password" type="password" placeholder="Password" value = {password} onChange = {e => setPassword(e.target.value)}/>
+//                     <button class = "btn" disabled = {!login || !password} onClick = {() => onLogin(login, password)}>Submit</button>
+//                     <p>У вас ещё нет аккаунта?<a href="/reg">Зарегистрироваться</a></p>
+//                 </div>
+//             </div>
+//         </>
+//     )
+// }
+//
+// export const ConnectLoginForm = connect(null, {onLogin:actionFullLogin}) (LoginForm)

+ 23 - 0
final-project/src/components/NavBar.js

@@ -0,0 +1,23 @@
+import {NavLink} from "react-router-dom";
+import style from './Style/NavBar.modules.css'
+
+const NavBar = (props) => {
+    return (
+            <nav className={style.nav}>
+                <div className={style.item}>
+                    <NavLink to="/profile">My profile</NavLink>
+                </div>
+                <div className={style.item}>
+                    <NavLink to="/posts">Posts</NavLink>
+                </div>
+                <div className={style.item}>
+                    <NavLink to="/dialogs">Dialogs</NavLink>
+                </div>
+                <div className={style.item}>
+                    <NavLink to="/users">Friends</NavLink>
+                </div>
+            </nav>
+    )
+}
+
+export default NavBar

+ 46 - 0
final-project/src/components/Posts.js

@@ -0,0 +1,46 @@
+import {useEffect, useRef, useState} from "react";
+import store from "../reducers/reducers";
+import {ConnectPostDraw} from "./PostsDraw";
+import {connect} from "react-redux";
+import {actionGetPosts} from "../action/action";
+
+const Post = ({posts = [], getPosts}) => {
+
+    useEffect(()=>{
+        getPosts();
+    }, [])
+
+    useEffect(()=>{
+        setNewPosts(newPosts.concat(posts))
+    }, [posts])
+
+    let ref = useRef(null)
+    let [skip, setSkip] = useState(100)
+    let [newPosts, setNewPosts] = useState([])
+
+    let scrollHandler = () => {
+        if (ref.current.scrollTop + ref.current.clientHeight === ref.current.scrollHeight) {
+            getPosts(skip)
+            setSkip(skip+100)
+        }
+    }
+    useEffect(()=>{
+        getPosts();
+    }, [])
+
+    useEffect(()=>{
+        setNewPosts(newPosts.concat(posts))
+    }, [posts])
+
+    // console.log(store.getState())
+
+    return (
+        <main>
+            <div className="wrapper" ref={ref} onScroll={()=>scrollHandler()}>
+                {newPosts.map(post=><ConnectPostDraw post = {post}/>)}
+            </div>
+        </main>
+    )
+}
+export const ConnectPosts = connect(state=>({posts: state?.promise?.allPosts?.payload?.data?.PostFind}), {getPosts: actionGetPosts})(Post)
+

+ 43 - 0
final-project/src/components/PostsDraw.js

@@ -0,0 +1,43 @@
+import { connect } from 'react-redux'
+
+const PostDraw = ({post}) => {
+    
+    let comments = post?.comments
+    let timestamp = post?.createdAt
+    let date = new Date(+timestamp).toString().slice(0, 24);
+    if (post?.images?.[0]?.url) {
+
+        return (
+            <div className="post">
+                <div className="info">
+                    <div className="user">
+                        <div className="profile-pic"><a href={`user/${post?.owner?._id}`}><img src={`http://hipstagram.asmer.fs.a-level.com.ua/${post?.owner?.avatar?.url}`} alt="profile-avatar"/></a></div>
+                        <a href={`user/${post?.owner?._id}`}><p className="username">{post?.owner?.login}</p></a>
+                    </div>
+                    <img src="https://cdn-icons.flaticon.com/png/512/3018/premium/3018442.png?token=exp=1635258753~hmac=93c765c52b9641296e5a03f536552014" className="options" alt="options"/>
+                </div>
+                <img src={`http://hipstagram.asmer.fs.a-level.com.ua/${post?.images?.[0]?.url}`} className="post-image" alt="post"/>
+                <div className="post-content">
+                    <div className="reaction-wrapper">
+                        <img src="https://cdn-icons-png.flaticon.com/512/1077/1077035.png" className="icon" alt="like-icon"/>
+                        <img src="https://cdn-icons-png.flaticon.com/512/2462/2462719.png" className="icon" alt="comment-icon"/>
+                    </div>
+                    <p className="description">{post?.text}</p>
+                    {comments?.map((comment) => <p className="comment">{comment?.text}</p>)}
+                    <p className="post-time">{date}</p>
+                </div>
+                <div className="comment-wrapper">
+                    <img src="https://cdn-icons-png.flaticon.com/512/747/747402.png" className="icon" alt="smile-icon"/>
+                    <input type="text" className="comment-box" placeholder="Add a comment"/>
+                    <button className="comment-btn">post</button>
+                </div>
+            </div>
+        )
+    }
+    else return null
+
+}
+
+export const ConnectPostDraw = connect(null, {posts: PostDraw})(PostDraw)
+
+

+ 12 - 0
final-project/src/components/Profile.js

@@ -0,0 +1,12 @@
+// import MyPost from "./Profile/MyPost/MyPosts";
+import ProfileInfo from './ProfileInfo';
+
+const Profile = (props) => {
+    return (
+            <div>
+                <ProfileInfo />
+            </div>
+    )
+}
+
+export default Profile

+ 20 - 0
final-project/src/components/ProfileInfo.js

@@ -0,0 +1,20 @@
+const ProfileInfo = (props) => {
+    return (
+            <div className='content'>
+                <div>
+                    <img className='banner'
+                          src='https://previews.123rf.com/images/djvstock/djvstock1610/djvstock161003154/64716282-vrouw-vrouwelijke-avatar-ge%C3%AFsoleerde-vectorillustratieontwerp.jpg'/>
+                </div>
+                <div>
+                    Nataliia Ostapenko
+                </div>
+
+                <textarea></textarea>
+                <div><button>Add post</button></div>
+
+
+            </div>
+    )
+}
+
+export default ProfileInfo

+ 0 - 0
final-project/src/components/RegisterForm.js


+ 0 - 0
final-project/src/components/Style/Dialog.module.css


+ 14 - 0
final-project/src/components/Style/Header.module.css

@@ -0,0 +1,14 @@
+.header {
+    grid-area: h;
+    background-color: #bb788c;
+    display: flex;
+
+}
+
+h1 {
+    margin: 20px;
+}
+
+.login {
+
+}

+ 7 - 0
final-project/src/components/Style/MyPosts.module.css

@@ -0,0 +1,7 @@
+.postImg {
+    width: 200px;
+}
+
+.like {
+    width: 20px;
+}

+ 11 - 0
final-project/src/components/Style/NavBar.modules.css

@@ -0,0 +1,11 @@
+
+nav {
+    grid-area: n;
+    background-color: rgba(231, 231, 111, 0.97);
+    padding: 20px;
+}
+
+.item a {
+    color: white;
+    text-decoration: none;
+}

+ 0 - 0
final-project/src/components/Style/Profile.module.css


+ 0 - 0
final-project/src/components/UserDraw.js


+ 15 - 0
final-project/src/index.css

@@ -0,0 +1,15 @@
+body {
+  margin: 0;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+    monospace;
+}
+
+

+ 19 - 0
final-project/src/index.js

@@ -0,0 +1,19 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+import reportWebVitals from './reportWebVitals';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+
+        <React.StrictMode>
+                <App />
+        </React.StrictMode>
+
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();

HW_react_1/my-app/src/logo.svg → final-project/src/logo.svg


+ 76 - 0
final-project/src/reducers/reducers.js

@@ -0,0 +1,76 @@
+import {createStore, applyMiddleware, combineReducers} from 'redux';
+import thunk from 'redux-thunk';
+
+
+//----------------------------PromiseReducer---------------------------
+export function promiseReducer(state={}, {type,name,status,payload,error}){
+
+    if(type === 'PROMISE'){
+        return{
+            ...state,
+            [name]:{status,payload,error}
+        }
+    }
+    return state
+}
+
+const actionPending   = (name) => ({type: 'PROMISE', status: 'PENDING', name})
+const actionFulfilled = (name, payload) => ({type: 'PROMISE', status: 'FULFILLED', name, payload})
+const actionRejected  = (name, error)   => ({type: 'PROMISE', status: 'REJECTED',  name, error})
+
+export const actionPromise = (name, promise) =>
+    async dispatch => {
+        dispatch(actionPending(name)) //сигнализируем redux, что промис начался
+        try{
+            const payload = await promise //ожидаем промиса
+            dispatch(actionFulfilled(name, payload)) //сигнализируем redux, что промис успешно выполнен
+            return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
+        }
+        catch (error){
+            dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
+        }
+    }
+
+
+//--------------------------------jwtDecode---------------------------------
+function jwtDecode(token){
+    try{
+        let payload = JSON.parse(atob(token.split('.')[1]));
+        return payload;
+    } catch(e){
+
+    }
+}
+
+//--------------------------------authReducer-------------------------------------
+export function authReducer(state,{type, token}){
+    if(state === undefined){
+        if(localStorage.authToken){
+            type = 'AUTH_LOGIN';
+            token = localStorage.authToken
+        }
+    }
+
+    if(type === 'AUTH_LOGIN'){
+        let payload = jwtDecode(token);
+        if(payload){
+            localStorage.authToken = token;
+            return {token,payload}
+        }
+    }
+
+    if(type === 'AUTH_LOGOUT'){
+        localStorage.authToken = '';
+        return {};
+    }
+    return state || {};
+}
+
+const actionAuthLogin  = token => ({type: 'AUTH_LOGIN', token})
+const actionAuthLogout = ()    => ({type: 'AUTH_LOGOUT'})
+
+const store = createStore(promiseReducer, applyMiddleware(thunk))
+store.subscribe(() => console.log(store.getState()))
+
+export default store;
+

HW_react_1/my-app/src/reportWebVitals.js → final-project/src/reportWebVitals.js


HW_react_1/my-app/src/setupTests.js → final-project/src/setupTests.js


+ 1 - 0
hipstagram/.gitignore

@@ -0,0 +1 @@
+node_modules

+ 5 - 0
hipstagram/.idea/.gitignore

@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 12 - 0
hipstagram/.idea/INSTAGRAM.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
hipstagram/.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+  </profile>
+</component>

+ 8 - 0
hipstagram/.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/INSTAGRAM.iml" filepath="$PROJECT_DIR$/.idea/INSTAGRAM.iml" />
+    </modules>
+  </component>
+</project>

+ 70 - 0
hipstagram/README.md

@@ -0,0 +1,70 @@
+# Getting Started with Create React App
+
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm start`
+
+Runs the app in the development mode.\
+Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
+
+The page will reload when you make changes.\
+You may also see any lint errors in the console.
+
+### `npm test`
+
+Launches the test runner in the interactive watch mode.\
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.\
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+The build is minified and the filenames include the hashes.\
+Your app is ready to be deployed!
+
+See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+
+### `npm run eject`
+
+**Note: this is a one-way operation. Once you `eject`, you can't go back!**
+
+If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+
+Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
+
+You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
+
+## Learn More
+
+You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+
+To learn React, check out the [React documentation](https://reactjs.org/).
+
+### Code Splitting
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
+
+### Analyzing the Bundle Size
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
+
+### Making a Progressive Web App
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
+
+### Advanced Configuration
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
+
+### Deployment
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
+
+### `npm run build` fails to minify
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

Diferenças do arquivo suprimidas por serem muito extensas
+ 18252 - 0
hipstagram/package-lock.json


+ 53 - 0
hipstagram/package.json

@@ -0,0 +1,53 @@
+{
+  "name": "hipstagram4",
+  "version": "0.1.0",
+  "private": true,
+  "proxy": "http://hipstagram.node.ed.asmer.org.ua",
+  "dependencies": {
+    "@emotion/react": "^11.10.6",
+    "@emotion/styled": "^11.10.6",
+    "@mui/material": "^5.11.13",
+    "@mui/styled-engine-sc": "^5.11.11",
+    "@testing-library/jest-dom": "^5.16.5",
+    "@testing-library/react": "^13.4.0",
+    "@testing-library/user-event": "^13.5.0",
+    "base-64": "^1.0.0",
+    "react": "^18.2.0",
+    "react-beautiful-dnd": "^13.1.1",
+    "react-bootstrap": "^2.7.2",
+    "react-dom": "^18.2.0",
+    "react-dropzone": "^14.2.3",
+    "react-redux": "^8.0.5",
+    "react-router": "^6.8.2",
+    "react-router-dom": "^5.3.4",
+    "react-scripts": "5.0.1",
+    "redux": "^4.2.1",
+    "redux-thunk": "^2.4.2",
+    "styled-components": "^5.3.9",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test",
+    "eject": "react-scripts eject"
+  },
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  }
+}

BIN
hipstagram/public/favicon.ico


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
hipstagram/public/heart-like.svg


+ 43 - 0
hipstagram/public/index.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+    <!--
+      manifest.json provides metadata used when your web app is installed on a
+      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <!--
+      Notice the use of %PUBLIC_URL% in the tags above.
+      It will be replaced with the URL of the `public` folder during the build.
+      Only files inside the `public` folder can be referenced from the HTML.
+
+      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      work correctly both with client-side routing and a non-root public URL.
+      Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+    <title>React App</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>

+ 15 - 0
hipstagram/public/like-svgrepo-com.svg

@@ -0,0 +1,15 @@
+<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+	 viewBox="0 0 460.958 460.958" xml:space="preserve">
+<g>
+	<g>
+		<path d="M337.843,23.957c-45.74,0-86.155,25.047-107.364,62.788c-21.209-37.741-61.623-62.788-107.364-62.788
+			C55.229,23.957,0,79.186,0,147.072c0,54.355,37.736,119.46,112.16,193.506c54.115,53.84,107.363,92.031,109.603,93.631
+			c2.607,1.861,5.662,2.792,8.716,2.792s6.109-0.93,8.715-2.792c2.241-1.6,55.489-39.791,109.604-93.631
+			c74.424-74.046,112.16-139.151,112.16-193.506C460.958,79.186,405.729,23.957,337.843,23.957z M327.919,319.032
+			c-39.843,39.681-80.171,71.279-97.44,84.307c-17.269-13.029-57.597-44.626-97.44-84.307C65.63,251.899,30,192.436,30,147.072
+			c0-51.344,41.771-93.115,93.115-93.115c47.279,0,87.03,35.369,92.464,82.271c0.876,7.565,7.284,13.273,14.9,13.273
+			c7.616,0,14.023-5.708,14.9-13.273c5.435-46.902,45.185-82.271,92.464-82.271c51.344,0,93.115,41.771,93.115,93.115
+			C430.958,192.436,395.328,251.899,327.919,319.032z"/>
+	</g>
+</g>
+</svg>

BIN
hipstagram/public/logo192.png


BIN
hipstagram/public/logo512.png


+ 25 - 0
hipstagram/public/manifest.json

@@ -0,0 +1,25 @@
+{
+  "short_name": "React App",
+  "name": "Create React App Sample",
+  "icons": [
+    {
+      "src": "favicon.ico",
+      "sizes": "64x64 32x32 24x24 16x16",
+      "type": "image/x-icon"
+    },
+    {
+      "src": "logo192.png",
+      "type": "image/png",
+      "sizes": "192x192"
+    },
+    {
+      "src": "logo512.png",
+      "type": "image/png",
+      "sizes": "512x512"
+    }
+  ],
+  "start_url": ".",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}

+ 3 - 0
hipstagram/public/robots.txt

@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:

+ 435 - 0
hipstagram/src/App.css

@@ -0,0 +1,435 @@
+@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700;900&display=swap");
+
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+*:focus {
+  outline: none;
+}
+
+body {
+  width: 100%;
+  background: #fafafa;
+  position: relative;
+  font-family: "roboto", sans-serif;
+  overflow-x: hidden;
+}
+
+.user-profile .profile-info p {
+  margin: 20px;
+}
+
+.post-image {
+  position: relative;
+
+  width: 100%;
+  height: fit-content;
+  min-height: 400px;
+  position: relative;
+  object-fit: cover;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  padding: 12px;
+  align-items: center;
+  border-radius: 12px;
+  justify-content: space-between;
+  background-color: #f3f3f3;
+}
+
+.post_tool_block{
+  position: absolute;
+  top: 2px;
+  right: 2px;
+  display: flex;
+  gap: 15px;
+}
+.flw_btn{
+  cursor: pointer;
+  background-color:#077Bff ;
+  color: white;
+  border: none;
+  width: 100%;
+  border-radius: 12px;
+  padding: 10px;
+
+}
+.edite_post{
+  cursor: pointer;
+  background-color:#efefef;
+  color: black;
+  border: none;
+  border-radius: 12px;
+  padding: 10px;
+}
+
+.delete_post{
+  cursor: pointer;
+  background-color: red;
+  color: white;
+  border: none;
+  border-radius: 12px;
+  padding: 10px;
+}
+
+.likes {
+  font-weight: bold;
+}
+
+.description span {
+  font-weight: bold;
+  margin-right: 10px;
+}
+
+.comment span {
+  font-weight: bold;
+  margin-right: 10px;
+}
+
+.comment-wrapper {
+  width: 100%;
+  height: 50px;
+  border-radius: 1px solid #dfdfdf;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.comment-wrapper .icon {
+  height: 22px;
+}
+
+.comment-box {
+  width: 80%;
+  height: 100%;
+  border: none;
+  outline: none;
+  font-size: 14px;
+}
+
+.comment-btn,
+.action-btn {
+  width: 70px;
+  height: 100%;
+  background: none;
+  border: none;
+  outline: none;
+  text-transform: capitalize;
+  font-size: 16px;
+  color: #00a2ff;
+  opacity: 0.5;
+}
+
+.reaction-wrapper .icon {
+  height: 25px;
+  margin: 0;
+  margin-right: 20px;
+}
+
+.reaction-wrapper .icon.save {
+  margin-left: auto;
+}
+
+.post-input {
+  margin: 100px auto;
+  width: 50%;
+  display: flex;
+  flex-direction: column;
+}
+
+.dropzone {
+  display: flex;
+  text-align: center;
+  width: 350px;
+  height: 300px;
+  border: 1px solid rgba(0, 149, 246, 1);
+  margin: 20px auto;
+  justify-content: center;
+  flex-direction: column;
+  font-size: 16px;
+  line-height: 20px;
+}
+.edite_profile{
+  cursor: pointer;
+  background-color:#efefefef ;
+  color: black;
+  border: none;
+  border-radius: 12px;
+  padding: 10px;
+}
+
+.dropzone img {
+  width: 90px;
+  margin: 20px auto;
+}
+
+.fYfqrd{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.comment_likes_block{
+  display: flex;
+  gap: 12px ;
+  max-width: 20px;
+}
+
+.modal{
+  position: fixed;
+  width: 100%;
+  height: 100vh;
+  left: 0;
+  z-index: 999;
+  top: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(0, 0, 0, 0.47);
+}
+
+.modal_block{
+  background: white;
+  width: 500px;
+  border-radius: 20px;
+  position: relative;
+  padding: 20px;
+}
+
+.followers_modal_block h2{
+  text-align: center;
+  margin-bottom: 10px;
+}
+.close_followers_modal{
+  position: absolute;
+  right: 20px;
+  top: 10px;
+  cursor: pointer;
+  background-color: transparent;
+  border: none;
+  font-size: 31px;
+}
+
+
+.list_followers{
+  height: 400px;
+  overflow-y: scroll;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  padding: 12px;
+}
+
+.list_followers .user{
+  width: 400px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 10px;
+  cursor: pointer;
+}
+
+.list_followers .user img{
+  max-width: 40px;
+  border-radius: 100%;
+  cursor: pointer;
+}
+
+.list_followers .user button{
+  background-color: #2a7edf;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 400;
+  border: none;
+  padding: 6px 12px;
+  cursor: pointer;
+  color: white;
+}
+
+
+.edt_btn{
+  font-family: IBM Plex Sans, sans-serif;
+font-weight: bold;
+font-size: 0.875rem;
+background-color: #007FFF;
+padding: 12px 24px;
+border-radius: 12px;
+color: white;
+transition: all 150ms ease;
+cursor: pointer;
+border: none;
+}
+
+.cncl_btn{
+  font-family: IBM Plex Sans, sans-serif;
+  font-weight: bold;
+  font-size: 0.875rem;
+  background-color: #efefefef;
+  padding: 12px 24px;
+  border-radius: 12px;
+  color: black;
+  transition: all 150ms ease;
+  cursor: pointer;
+  border: none;
+}
+
+
+.unflw_btn {
+  cursor: pointer;
+  background-color:#efefefef ;
+  border: none;
+  border-radius: 12px;
+  padding: 10px;
+  color: black;
+  width: 100%;
+}
+
+
+.lds-roller {
+  display: flex;
+  position: relative;
+  width: 80px;
+  height: 80px;
+  margin: 400px auto;
+}
+.lds-roller div {
+  animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
+  transform-origin: 40px 40px;
+}
+.lds-roller div:after {
+  content: " ";
+  display: block;
+  position: absolute;
+  width: 7px;
+  height: 7px;
+  border-radius: 50%;
+  background: #000000;
+  margin: -4px 0 0 -4px;
+}
+.lds-roller div:nth-child(1) {
+  animation-delay: -0.036s;
+}
+.lds-roller div:nth-child(1):after {
+  top: 63px;
+  left: 63px;
+}
+.lds-roller div:nth-child(2) {
+  animation-delay: -0.072s;
+}
+.lds-roller div:nth-child(2):after {
+  top: 68px;
+  left: 56px;
+}
+.lds-roller div:nth-child(3) {
+  animation-delay: -0.108s;
+}
+.lds-roller div:nth-child(3):after {
+  top: 71px;
+  left: 48px;
+}
+.lds-roller div:nth-child(4) {
+  animation-delay: -0.144s;
+}
+.lds-roller div:nth-child(4):after {
+  top: 72px;
+  left: 40px;
+}
+.lds-roller div:nth-child(5) {
+  animation-delay: -0.18s;
+}
+.lds-roller div:nth-child(5):after {
+  top: 71px;
+  left: 32px;
+}
+.lds-roller div:nth-child(6) {
+  animation-delay: -0.216s;
+}
+.lds-roller div:nth-child(6):after {
+  top: 68px;
+  left: 24px;
+}
+.lds-roller div:nth-child(7) {
+  animation-delay: -0.252s;
+}
+.lds-roller div:nth-child(7):after {
+  top: 63px;
+  left: 17px;
+}
+.lds-roller div:nth-child(8) {
+  animation-delay: -0.288s;
+}
+.lds-roller div:nth-child(8):after {
+  top: 56px;
+  left: 12px;
+}
+@keyframes lds-roller {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+
+.SerachBoxPopup{
+  background-color: white;
+  padding: 12px;
+  position: absolute;
+  border-radius: 8px;
+  box-shadow: #000000;
+  min-height: 100px;
+  width: 100%;
+  top: 40px;
+  z-index: 99999999;
+}
+
+
+.serachBG{
+  background-color: #0000006b;
+  position: fixed;
+  left: 0;
+  width: 100%;
+  height: 100vh;
+  top: 0;
+}
+
+.serach_link_user{
+  text-decoration: none;
+  color: black;
+  margin-bottom: 10px;
+}
+
+.answerTo_block{
+  margin-bottom: 20px;
+  margin-left: 40px;
+  color: rgb(194, 190, 190);
+   font-size: 12px;
+   align-items: center;
+   gap: 12px;
+   display: flex;
+}
+
+.close_btn{
+  position: absolute;
+  right: 12px;
+  top: 12px;
+  cursor: pointer;
+  background-color: transparent;
+  border: none;
+  font-size: 30px;
+}
+
+.post_details{
+  width: 100%;
+  padding: 50px;
+  display: flex;
+  gap: 30px;
+}
+
+.pd_img{
+  max-height: 400px;
+}

+ 16 - 0
hipstagram/src/App.js

@@ -0,0 +1,16 @@
+import React, { useEffect } from "react";
+import { Provider } from "react-redux";
+import { ConnectedRouts } from "./components/Route";
+import store from "./reducers";
+
+function App() {
+  return (
+    <>
+      <Provider store={store}>
+        <ConnectedRouts />
+      </Provider>
+    </>
+  );
+}
+
+export default App;

+ 8 - 0
hipstagram/src/App.test.js

@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+  render(<App />);
+  const linkElement = screen.getByText(/learn react/i);
+  expect(linkElement).toBeInTheDocument();
+});

+ 504 - 0
hipstagram/src/actions/index.js

@@ -0,0 +1,504 @@
+const getGQL = (url) => (query, variables) =>
+  fetch(url, {
+    method: "POST",
+    headers: {
+      "content-type": "application/json",
+      ...(localStorage.authToken
+        ? { Authorization: "Bearer " + localStorage.authToken }
+        : {}),
+    },
+    body: JSON.stringify({ query, variables }),
+  }).then((res) => res.json());
+
+export const gql = getGQL("/graphql");
+
+const reg = async (login, password) => {
+  let query = `mutation reg($login: String!, $password: String!) {
+    createUser(login: $login, password: $password)
+		{
+      _id
+      login
+
+    }
+}`;
+  let variables = {
+    login: login,
+    password: password,
+  };
+  let result = await gql(query, variables);
+  return result;
+};
+
+const log = async (login, password) => {
+  let query = `query login($login:String!, $password:String!) {
+      login(login: $login, password: $password)
+    }`;
+  let variables = {
+    login: login,
+    password: password,
+  };
+  let result = await gql(query, variables);
+  console.log(result);
+  return result;
+};
+
+export const upload = async (file) => {
+  let fd = new FormData();
+  fd.append("photo", file);
+  return fetch("/upload", {
+    method: "POST",
+    headers: localStorage.authToken
+      ? { Authorization: "Bearer " + localStorage.authToken }
+      : {},
+    body: fd,
+  }).then((res) => res.json());
+};
+
+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));
+  }
+};
+
+export const actionShowFollowers = (id) => {
+  return actionPromise(
+    "getFollowers",
+    gql(
+      `query getFollowers($_id: String) {
+        UserFindOne(query: $_id) {
+          followers{
+           _id
+           login
+           avatar {
+               url
+           },
+          }
+        }
+      }`,
+      {
+        _id: JSON.stringify([{ _id: id }])
+      }
+    )
+  )
+}
+export const actionShowFollowing = (id) => {
+  return actionPromise(
+    "getFollowing",
+    gql(
+      `query getFollowing($_id: String) {
+         UserFindOne(query: $_id) {
+           following{
+            _id
+            login
+            avatar {
+                url
+            },
+           }
+         }
+       }`,
+      {
+        _id: JSON.stringify([{ _id: id }])
+      }
+    )
+  )
+}
+
+
+export const getOnePost = (_id) =>
+  actionPromise(
+    "getOnePost",
+    gql(
+      `query OneFind($post:String){
+       PostFindOne(query:$post){
+        _id title text images{_id url}
+        owner{_id login avatar{_id url}}
+        createdAt
+        comments{
+          _id, createdAt, text  owner{_id login avatar{_id url}}
+          answers{
+            _id, createdAt, text owner{_id login  avatar{_id url}}
+            
+          }
+        owner{_id login avatar{_id url}}}
+        likes{
+          _id
+          owner{				
+              _id login avatar {_id url}
+            }
+        }
+      }
+    }`,
+      {
+        post: JSON.stringify([{ _id }]),
+      }
+    )
+  );
+
+
+export const actionUserById = (id) => {
+  return actionPromise(
+    "UserById",
+    gql(
+      `query UserById($_id:String) {
+        UserFindOne(query: $_id){
+          _id, nick, login, avatar {url}
+          followers {_id, nick, login, avatar {url}},
+          following {_id, nick, login,avatar {url}}
+          } 
+        }`,
+      { _id: JSON.stringify([{ _id: id }]) }
+    )
+  );
+};
+
+export const actionGetPosts = (skip) => {
+  // console.log('news')
+  return actionPromise(
+    "allPosts",
+    gql(
+      `query allposts($query: String!){
+          PostFind(query: $query){
+            _id, text, title,
+            owner{_id, nick, login, avatar 
+              {
+                url
+              }
+            }, 
+            images{url},
+            comments{
+              answerTo{
+                text
+                owner{
+                  login
+                  avatar{
+                    url
+                  }
+                }
+              }
+                          answers{
+                text
+                owner{
+                  login
+                  avatar{
+                    url
+                  }
+                }
+              }
+              likesCount
+
+              likes{
+                _id
+                owner{
+                  _id
+                  login
+                  avatar{
+                    url
+                  }
+                }
+              }
+              _id
+              text,
+              likesCount
+              owner {
+                _id
+                login,
+                avatar {
+                  url
+                }
+                
+              }
+            },
+            likes {
+              _id
+              owner {
+                _id
+                nick
+                login
+              }
+            }
+            createdAt
+        }
+      }`,
+      {
+        query: JSON.stringify([
+          {},
+          { sort: [{ _id: -1 }], skip: [skip || 0], limit: [500] },
+        ]),
+      }
+    )
+  );
+};
+
+
+
+
+export const actionGetUsers = (skip) => {
+  console.log("users");
+  return actionPromise(
+    "allUsers",
+    gql(
+      `query allUsers($query: String!){
+    UserFind(query: $query){
+      _id, login, nick, avatar 
+       {_id, url}
+     }
+  }`,
+      {
+        query: JSON.stringify([
+          {},
+          { sort: [{ login: -1 }], skip: [skip || 0], limit: [500000] },
+        ]),
+      }
+    )
+  );
+};
+
+export const actionInsertPosts = (title, text, _id) => {
+  console.log("post");
+  return actionPromise(
+    "InsertPosts",
+    gql(
+      `mutation newPost($post:PostInput){
+    PostUpsert(post:$post){
+      _id 
+    }
+  }`,
+      { post: { title: title, text: text, images: { _id: _id } } }
+    )
+  );
+};
+
+export const actionSendComment = (postId, text) =>
+  actionPromise(
+    gql(
+      `mutation AddComment($comment:CommentInput){
+          CommentUpsert(comment:$comment)
+          {
+            _id
+            text
+            createdAt
+            likesCount
+            likes {
+              _id
+            }
+          }
+        }`,
+      {
+        comment: {
+          post: {
+            _id: postId,
+          },
+          text,
+
+        },
+      }
+    )
+  );
+
+export const actionAnswerComment = (commentId, text) =>
+  actionPromise(
+    gql(
+      `mutation AddCommentAnswe($comment:CommentInput){
+          CommentUpsert(comment:$comment)
+          {
+            _id
+            text 
+            owner{
+              avatar{
+                url
+              }
+            }
+           
+          }
+        }`,
+      {
+        comment: {
+          _id: commentId,
+
+          answerTo: {
+            text: text,
+
+          },
+
+        },
+      }
+    )
+  );
+
+
+
+
+const actionPending = (name) => ({ type: "PROMISE", status: "PENDING", name });
+const actionResolved = (name, payload) => ({
+  type: "PROMISE",
+  status: "RESOLVED",
+  name,
+  payload,
+});
+const actionRejected = (name, error) => ({
+  type: "PROMISE",
+  status: "REJECTED",
+  name,
+  error,
+});
+const actionAuthLogin = (token) => ({ type: "LOGIN", token });
+export const actionAuthLogout = () => ({ type: "LOGOUT" });
+export const actionLogin = (login, password) =>
+  actionPromise("login", log(login, password));
+const actionRegister = (login, password) =>
+  actionPromise("register", reg(login, password));
+export const actionFullLogin = (login, password) => async (dispatch) => {
+  let token = await dispatch(actionLogin(login, password));
+  console.log(token);
+  if (token?.data?.login) {
+    console.log(token.data.login);
+    dispatch(actionAuthLogin(token.data.login));
+  } else {
+    console.log("Authorization failed");
+    // dispatch(actionLoginNetworkError())
+  }
+};
+export const actionFullRegister = (login, password) => async (dispatch) => {
+  let token = await dispatch(actionRegister(login, password));
+  console.log(token);
+  if (token?.data?.createUser !== null) {
+    dispatch(actionFullLogin(login, password));
+  } else {
+    alert("User name is exist");
+  }
+};
+export const actionUploadFile = (file) => actionPromise("upload", upload(file));
+
+export const actionUploadPost = (text, title, file) => async (dispatch) => {
+  let result = await dispatch(actionUploadFile(file));
+  let postId = await dispatch(actionInsertPosts(text, title, result._id));
+  dispatch(actionGetPosts());
+  console.log(postId);
+};
+
+export const actionInsertPhoto = (userId, photoId) => {
+  console.log("post");
+  return actionPromise(
+    "InsertPosts",
+    gql(
+      `mutation setUserAvatar($avatar: ImageInput){
+    ImageUpsert(image: $avatar){
+        _id, userAvatar {_id}
+    }
+  }`,
+      { avatar: { _id: userId, userAvatar: { _id: photoId } } }
+    )
+  );
+};
+
+export const actionUploadPhoto = (file, userId) => async (dispatch) => {
+  let result = await dispatch(actionUploadFile(file));
+  console.log(userId, result._id);
+  dispatch(actionInsertPhoto(result._id, userId));
+};
+
+export const actionPostById = (id) => {
+  return actionPromise(
+    "PostById",
+    gql(
+      `query PostById($_id:String) {
+      PostFind(query: $_id){
+        _id createdAt title text,
+        images {url},
+        likes{
+          _id
+        }
+        comments{
+          text
+          owner{
+            avatar{
+              url
+            }
+          }
+          answerTo{
+            text
+            owner{
+              avatar{
+                url
+              }
+            }
+          }
+        }
+        owner {login, nick, avatar {url},
+        followers {_id, nick, login},
+    		following {_id, nick, login}}
+      }
+	} `,
+      { _id: JSON.stringify([{ ___owner: id }]) }
+    )
+  );
+};
+
+export const actionSubscribe = (id, token) => {
+  console.log("subscribe");
+  return actionPromise(
+    "Subscribe",
+    gql(
+      `mutation following($user:UserInput){
+        UserUpsert( user:$user){
+            following {
+              _id
+              nick
+            }
+        }
+      }`,
+      {
+        user: {
+          _id: id,
+          following: [{ _id: token }],
+        },
+      }
+    )
+  );
+};
+
+
+
+export const delete_post = (id) => {
+  console.log("subscribe");
+  return (
+    "delete post ",
+    gql(
+      `mutation deletePost($_id:PostInput){
+        PostDelete( post:$_id){
+          _id
+        }
+      }`,
+      {
+        _id: {
+          _id: id,
+
+        },
+      }
+    )
+  );
+};
+
+
+export const updateProfileNickname = (userId, nickname) =>
+  actionPromise(
+    gql(
+      `mutation updateUserProfile($user:UserInput){
+        UserUpsert(user:$user){
+          login
+        }
+      }`,
+      {
+        user: {
+          _id: userId,
+          login: nickname
+
+        }
+      }
+    )
+  )

+ 46 - 0
hipstagram/src/actions/like.js

@@ -0,0 +1,46 @@
+import { gql, actionPromise } from "./index";
+
+
+
+export const gqlAddLike = (postId) =>
+  actionPromise(
+    "addLike",
+    gql(
+      `mutation AddLike($like:LikeInput){
+          LikeUpsert(like:$like)
+          {
+            _id
+          }
+        }`,
+      {
+        like: {
+          post: {
+            _id: postId,
+          },
+        },
+      }
+    )
+  );
+
+
+
+export const gqlDeleteLike = (likeId, postId) =>
+  actionPromise(
+    "deletePromise",
+    gql(
+      `mutation DeleteLike($like:LikeInput){
+          LikeDelete(like: $like)
+          {
+            _id
+          }
+        }`,
+      {
+        like: {
+          _id: likeId,
+          post: {
+            _id: postId,
+          },
+        },
+      }
+    )
+  );

+ 121 - 0
hipstagram/src/actions/likeInComment.js

@@ -0,0 +1,121 @@
+import { gql, actionPromise } from "./index";
+
+export const gqlGetCommentLikesCount = (commentId) =>
+  actionPromise(
+    "getCommentLikesCount",
+    gql(
+      `query getCommentLikesCount($_id: String) {
+        CommentFindOne(query: $_id) {
+          likesCount
+        }
+      }`,
+      {
+        _id: JSON.stringify([{ _id: commentId }])
+      }
+    )
+  );
+
+  export const gqlAddLikeComment = (commentId) =>
+  actionPromise(
+    "addLikeComment",
+    gql(
+      `mutation AddLikeComment($like: LikeInput) {
+          LikeUpsert(like: $like) {
+            _id
+
+          }
+        }`,
+      {
+        like: {
+          comment: {
+            _id: commentId,
+          },
+        },
+      }
+    )
+  );
+
+  
+  export const gqlDeleteLikeComment = (likeId, commentId) =>
+  actionPromise(
+    "deletePromise",
+    gql(
+      `mutation DeleteLikeComment($like:LikeInput){
+          LikeDelete(like: $like)
+          {
+            _id
+          }
+        }`,
+      {
+        like: {
+          _id: likeId,
+          comment: {
+            _id: commentId,
+          },
+        },
+      }
+    )
+  );
+
+
+
+
+
+
+
+
+
+
+  export const gqlAddCsdmmentLike = (commentId, like) =>
+  actionPromise(
+    "addCommentLike",
+    gql(
+      `mutation AddCommentLike($like: LikeInput){
+          LikeUpsert(like: $like) {
+            _id
+          }
+          CommentUpdate(_id: "${commentId}", like: $like) {
+            _id
+            text
+            createdAt
+            likesCount
+            like {
+              _id
+            }
+          }
+        }`,
+      {
+        like: {
+          user: {
+            _id: like.userId,
+          },
+        },
+      }
+    )
+  );
+
+
+
+export const deleteCommentLike = (commentId) =>
+  actionPromise(
+    "deleteCommentLike",
+    gql(
+      `mutation DeleteCommentLike($like: CommentLikeInput!) {
+        CommentLikeDelete(like: $like) {
+          _id
+          likes {
+            user {
+              _id
+            }
+          }
+        }
+      }`,
+      {
+        like: {
+          comment: {
+            _id: commentId,
+          },
+        },
+      }
+    )
+  );

+ 39 - 0
hipstagram/src/actions/postEdite.js

@@ -0,0 +1,39 @@
+import { gql, actionPromise } from "./index";
+
+export const updateProfileNickname = (userId, nickname) =>
+actionPromise(
+  gql(
+    `mutation updateUserProfile($user:UserInput){
+      UserUpsert(user:$user){
+        login
+      }
+    }`,
+    {
+      user:{
+        _id : userId,
+        login: nickname
+      }
+    }
+  )
+)
+
+
+export const updatePost = (postId, title, text) =>{
+    actionPromise(
+        gql(
+            `mutation updatePostTitle($post:PostInput){
+                PostUpsert(post:$post){
+                    title,
+                    text
+                }
+            }`,
+            {
+                post:{
+                    _id:postId,
+                    title: title,
+                    text:text
+                }
+            }
+        )
+    )
+}

+ 119 - 0
hipstagram/src/actions/user.js

@@ -0,0 +1,119 @@
+import { gql, actionPromise } from "./index";
+
+export const gqlAboutUser = (_id) =>
+  gql(
+    `query AboutMe($userId:String){
+            UserFindOne(query:$userId)
+            {
+              _id createdAt login nick avatar{_id url} 
+              likes {_id}
+              followers{_id login nick avatar{_id url}} 
+              following{_id login nick avatar{_id url}}
+            }
+          }`,
+    {
+      userId: JSON.stringify([{ _id }]),
+    }
+  );
+
+  export const gqlFollowUser = (userId, followerId) =>
+  actionPromise(
+    "followUser",
+    gql(
+      `
+        mutation FollowUser($user: UserInput) {
+          UserUpsert(user: $user) {
+            _id
+          }
+        }
+      `,
+      {
+        user: {
+          _id: userId,
+          following: [{ _id: followerId }],
+        },
+      }
+    )
+  );
+
+  export const actionSubscribe = (token,followingIds) => {
+    console.log("subscribe");
+    return actionPromise(
+      "Subscribe",
+      gql(
+        `mutation following($user:UserInput){
+          UserUpsert( user:$user){
+              following {
+                _id
+                nick
+              }
+          }
+        }`,
+        {
+          user: {
+            _id: token,
+            following: followingIds
+          },
+        }
+      )
+    );
+  };
+
+  export const actionUnfollow = (token,id) => {
+    return actionPromise(
+      "unfollow",
+      gql(
+        `mutation unfollow($user:UserInput){
+          UserDelete( user:$user){
+              following {
+                _id
+              }
+          }
+        }`,
+        {
+          user: {
+            _id: token,
+            following: {_id:id}
+          },
+        }
+      )
+    );
+  };
+  
+
+  export const getUserFollowing = (token) => {
+    return (
+      gql(
+        `query getFollowing($_id: String) {
+           UserFindOne(query: $_id) {
+             following{
+              _id
+             }
+           }
+         }`,
+        {
+          _id: JSON.stringify([{ _id: token }])
+        }
+      )
+    )
+  }
+  
+
+
+
+
+
+
+
+export const gqlUnfollowUser = (userId) =>
+  actionPromise(
+    "unfollowUser",
+    gql(
+      `mutation UnfollowUser($_id: String!) {
+          UserUpsert(user: { following: [], _id: $_id }) {
+            _id
+          }
+        }`,
+      { _id: userId }
+    )
+  );

+ 34 - 0
hipstagram/src/components/Comments/comment.module.js

@@ -0,0 +1,34 @@
+import { Box } from "@mui/material";
+import styled from "styled-components";
+
+export const BoxWrapper = styled(Box)`
+  overflow: hidden;
+`;
+
+export const StyledComment = styled(Box)`
+  display: flex;
+  align-items: center;
+  margin: 10px 0;
+  font-size: 14px;
+  line-height: 20px;
+  overflow: hidden;
+  transition: opacity 0.2s ease-out, height 0.2s ease-out;
+`;
+
+export const Avatar = styled((props) => (
+  <Box
+    component="img"
+    {...props}
+    alt="avatar"
+    width="30"
+    height="30"
+    src={props.src}
+    style={{ marginRight: 5, borderRadius: "50%" }}
+  />
+))`
+  width: 22px;
+  height: 22px;
+  cursor: pointer;
+  margin: 0 10px;
+  display: inline-block;
+`;

Diferenças do arquivo suprimidas por serem muito extensas
+ 471 - 0
hipstagram/src/components/Comments/index.js


+ 77 - 0
hipstagram/src/components/Header/header.module.js

@@ -0,0 +1,77 @@
+import { BottomNavigation, Box, TextField } from "@mui/material";
+import styled from "styled-components";
+
+export const StyledBottomNavigation = styled((props) => (
+  <BottomNavigation {...props} />
+))`
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  background: #fff;
+  border-bottom: 1px solid #dfdfdf;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 5px 0;
+`;
+
+export const NavWrapperBox = styled((props) => <Box {...props} />)`
+  width: 70%;
+  max-width: 1000px;
+  height: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+`;
+
+export const BrandImage = styled((props) => (
+  <Box
+    component="img"
+    alt="logo"
+    src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/Instagram_logo.svg/800px-Instagram_logo.svg.png"
+    {...props}
+  />
+))`
+  height: 40px;
+  cursor: pointer;
+  margin-top: 5px;
+`;
+
+export const SearchBox = styled((props) => (
+  <TextField {...props} placeholder="search..." />
+))`
+  background: white;
+  & label.Mui-focused {
+    color: white;
+  }
+  & .MuiInput-underline:after {
+    border-bottom-color: white;
+  }
+  & .MuiOutlinedInput-root {
+    & fieldset {
+      border-color: black;
+    }
+    &.Mui-focused fieldset {
+      border-color: black;
+    }
+    & input {
+      padding: 5px;
+    }
+  }
+`;
+
+export const NavItemsBox = styled(Box)`
+  position: relative;
+  height: 22px;
+`;
+
+export const Icon = styled((props) => (
+  <Box component="img" {...props} alt="icon-profile" src={props.src} />
+))`
+  width: 22px;
+  height: 22px;
+  cursor: pointer;
+  margin: 0 10px;
+  display: inline-block;
+`;

+ 51 - 0
hipstagram/src/components/Header/index.js

@@ -0,0 +1,51 @@
+import React from "react";
+import { connect } from "react-redux";
+import { actionAuthLogout } from "../../actions";
+import { Box, Link } from "@mui/material";
+import {
+  StyledBottomNavigation,
+  NavWrapperBox,
+  BrandImage,
+  Icon,
+  NavItemsBox,
+} from "./header.module";
+import "../../App.css";
+import envelope from "../../images/envelope.png";
+import group from "../../images/group.png";
+import user from "../../images/user.png";
+import SearchBoxCommponent from "../Search";
+
+const NavBar = ({ id, onLogout }) => {
+  return (
+    <StyledBottomNavigation>
+      <NavWrapperBox>
+        <Link href="/">
+          <BrandImage />
+        </Link>
+        <SearchBoxCommponent/>
+        <NavItemsBox>
+          <Link href="/">
+            <Icon src="https://cdn-icons-png.flaticon.com/512/20/20176.png" />
+          </Link>
+          <Link href="/input">
+            <Icon src={envelope} />
+          </Link>
+          <Link href="/users">
+            <Icon src={group} />
+          </Link>
+          <Link href={`/user/${id}`}>
+            <Icon src={user} />
+          </Link>
+          <Link href="/" onClick={() => onLogout()}>
+            <Icon src="https://cdn-icons-png.flaticon.com/512/59/59801.png" />
+          </Link>
+        </NavItemsBox>
+      </NavWrapperBox>
+    </StyledBottomNavigation>
+  );
+};
+
+export const ConnectNavBar = connect(
+  (state) => ({ id: state?.auth.payload?.id }),
+  { onLogout: actionAuthLogout }
+)(NavBar);

+ 47 - 0
hipstagram/src/components/LoginForm/index.js

@@ -0,0 +1,47 @@
+import React, { useState } from "react";
+import { connect } from "react-redux";
+import { actionFullLogin } from "../../actions";
+import { LoginPage, StyledLoginForm } from "./login-page.module";
+import { TextField, Button, Link, Box } from "@mui/material";
+import "../../App.css";
+
+const LoginForm = ({ onLogin }) => {
+  const [login, setLogin] = useState("");
+  const [password, setPassword] = useState("");
+
+  return (
+    <LoginPage>
+      <StyledLoginForm>
+        <TextField
+          sx={{ marginBottom: "5px" }}
+          type="text"
+          placeholder="Enter login"
+          value={login}
+          onChange={(e) => setLogin(e.target.value)}
+        />
+        <TextField
+          sx={{ marginBottom: "5px" }}
+          type="password"
+          placeholder="Password"
+          value={password}
+          onChange={(e) => setPassword(e.target.value)}
+        />
+        <Button
+          sx={{ marginBottom: "5px" }}
+          variant="contained"
+          disabled={!login || !password}
+          onClick={() => onLogin(login, password)}
+        >
+          Login
+        </Button>
+        <Box>
+          У вас ещё нет аккаунта?<Link href="/reg">Зарегистрироваться</Link>
+        </Box>
+      </StyledLoginForm>
+    </LoginPage>
+  );
+};
+
+export const ConnectLoginForm = connect(null, { onLogin: actionFullLogin })(
+  LoginForm
+);

+ 83 - 0
hipstagram/src/components/LoginForm/login-page.module.js

@@ -0,0 +1,83 @@
+import { Box } from "@mui/material";
+import styled from "styled-components";
+
+export const LoginPage = styled((props) => <Box {...props} />)`
+  width: 360px;
+  padding: 8% 0 0;
+  margin: auto;
+`;
+
+export const StyledLoginForm = styled(Box)`
+  position: relative;
+  z-index: 1;
+  background: #ffffff;
+  padding: 45px 45px 35px 45px;
+  text-align: center;
+  box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
+`;
+{
+  /* <div className="login-form">
+  <input
+    class="login"
+    type="login"
+    placeholder="Enter login"
+    value={props.login}
+    onChange={(e) => props.setLogin(e.target.value)}
+  />
+  <input
+    class="password"
+    type="password"
+    placeholder="Password"
+    value={props.password}
+    onChange={(e) => props.setPassword(e.target.value)}
+  />
+  <button
+    class="btn"
+    disabled={!login || !password}
+    onClick={() => props.onLogin(login, password)}
+  >
+    Submit
+  </button>
+  <p>
+    У вас ещё нет аккаунта?<a href="/reg">Зарегистрироваться</a>
+  </p>
+</div>; */
+}
+
+// .login-page input {
+//   font-family: "Roboto", sans-serif;
+//   outline: 0;
+//   background: #f2f2f2;
+//   width: 100%;
+//   border: 0;
+//   margin: 0 0 15px;
+//   padding: 15px;
+//   box-sizing: border-box;
+//   font-size: 14px;
+// }
+// .login-page button {
+//   font-family: "Roboto", sans-serif;
+//   text-transform: uppercase;
+//   outline: 0;
+//   background: #a8bfdd;
+//   width: 100%;
+//   border: 0;
+//   padding: 15px;
+//   color: #ffffff;
+//   font-size: 14px;
+//   cursor: pointer;
+// }
+// .login-page button:hover,
+// .form button:active,
+// .form button:focus {
+//   background: #97abc4;
+// }
+
+// .login-form p {
+//   margin: 20px;
+// }
+
+// .login-form a {
+//   padding-top: 10px;
+//   color: rgba(0, 149, 246, 1);
+// }

+ 145 - 0
hipstagram/src/components/Post/index.js

@@ -0,0 +1,145 @@
+// import React, { useEffect, useState, useRef } from "react";
+// import { connect } from "react-redux";
+// import { Box } from "@mui/material";
+// import { actionGetPosts } from "../../actions";
+// import { ConnectPostDraw } from "../PostDraw";
+// import { gqlAboutUser } from "../../actions/user";
+// import store from "../../reducers";
+// import "../../App.css";
+// import { WrapperBox } from "./post.module";
+//
+// const Post = ({ getPosts }) => {
+//
+//   const [skip, setSkip] = useState(0);
+//   const [currentUser, setCurrentUser] = useState({});
+//   const generalPostsRef = useRef([]);
+//
+//   const scrollHandler = async () => {
+//     // const scrollTop = document.documentElement.scrollTop;
+//     // const scrollHeight = document.documentElement.scrollHeight;
+//     // const clientHeight = document.documentElement.clientHeight;
+//     // if (scrollTop + clientHeight < scrollHeight) {
+//     //   return;
+//     // }
+//
+//     const sliceLength = generalPostsRef.current.slice(skip, skip + 5).length;
+//
+//     if (sliceLength > 0) {
+//       setSkip((prevSkip) => prevSkip + 5);
+//     }
+//   };
+//
+//   useEffect(() => {
+//     (async () => {
+//       const currentUser = (await gqlAboutUser(store.getState().auth.payload.id))
+//         .data.UserFindOne;
+//       setCurrentUser(currentUser);
+//
+//       const generalPosts = (await getPosts()).data.PostFind;
+//       generalPostsRef.current = generalPosts;
+//       document.addEventListener("scroll", scrollHandler);
+//       setSkip((prevSkip) => prevSkip + 5);
+//     })();
+//     return () => document.removeEventListener("scroll", scrollHandler);
+//   }, []);
+//
+//   const filteredPosts =
+//     generalPostsRef.current && generalPostsRef.current.length > 0
+//       ? generalPostsRef.current
+//           .slice(0, skip + 5)
+//           .filter((post) => post?.owner?.avatar?.url && post?.images?.[0]?.url)
+//       : [];
+//
+//   return (
+//     <Box component="main">
+//       <WrapperBox>
+//         {filteredPosts.length > 0 ? (
+//           filteredPosts.map((post) => (
+//             <ConnectPostDraw
+//               key={post._id}
+//               post={post}
+//               currentUser={currentUser}
+//             />
+//           ))
+//         ) : (
+//           <div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>
+//         )}
+//       </WrapperBox>
+//     </Box>
+//   );
+// };
+// export const ConnectPosts = connect(null, { getPosts: actionGetPosts })(Post);
+
+import React, { useEffect, useState, useRef } from "react";
+import { connect } from "react-redux";
+import { Box } from "@mui/material";
+import { actionGetPosts } from "../../actions";
+import { ConnectPostDraw } from "../PostDraw";
+import { gqlAboutUser } from "../../actions/user";
+import store from "../../reducers";
+import "../../App.css";
+import { WrapperBox } from "./post.module";
+
+const Post = ({ getPosts }) => {
+  const [skip, setSkip] = useState(0);
+  const [currentUser, setCurrentUser] = useState({});
+  const generalPostsRef = useRef([]);
+
+  const scrollHandler = async () => {
+    const sliceLength = generalPostsRef.current.slice(skip, skip + 5).length;
+
+    if (sliceLength > 0) {
+      setSkip((prevSkip) => prevSkip + 5);
+    }
+  };
+
+  useEffect(() => {
+    (async () => {
+      const currentUser = (await gqlAboutUser(store.getState().auth.payload.id)).data.UserFindOne;
+      setCurrentUser(currentUser);
+
+      const generalPosts = (await getPosts()).data.PostFind;
+      generalPostsRef.current = generalPosts;
+      document.addEventListener("scroll", scrollHandler);
+      setSkip((prevSkip) => prevSkip + 5);
+    })();
+
+    return () => document.removeEventListener("scroll", scrollHandler);
+  }, []);
+
+  const filteredPosts =
+      generalPostsRef.current && generalPostsRef.current.length > 0
+          ? generalPostsRef.current
+              .slice(0, skip + 5)
+              .filter((post) => post?.owner?.avatar?.url && post?.images?.[0]?.url)
+          : [];
+
+  return (
+      <Box component="main">
+        <WrapperBox>
+          {filteredPosts.length > 0 ? (
+              filteredPosts.map((post) => (
+                  <ConnectPostDraw
+                      key={post._id}
+                      post={post}
+                      currentUser={currentUser}
+                  />
+              ))
+          ) : (
+              <div class="lds-roller">
+                <div></div>
+                <div></div>
+                <div></div>
+                <div></div>
+                <div></div>
+                <div></div>
+                <div></div>
+                <div></div>
+              </div>
+          )}
+        </WrapperBox>
+      </Box>
+  );
+};
+
+export const ConnectPosts = connect(null, { getPosts: actionGetPosts })(Post);

+ 7 - 0
hipstagram/src/components/Post/post.module.js

@@ -0,0 +1,7 @@
+import { Box } from "@mui/material";
+import styled from "styled-components";
+
+export const WrapperBox = styled(Box)`
+  margin: 50px auto;
+  width: 50%;
+`;

+ 434 - 0
hipstagram/src/components/PostDraw/index.js

@@ -0,0 +1,434 @@
+// import { useState, useEffect } from "react";
+// import { connect, useStore, useDispatch } from "react-redux";
+// import { gqlAddLike, gqlDeleteLike } from "../../actions/like";
+// import Comments from "../Comments";
+// import "../../App.css";
+// import Dots from "../../images/more.png";
+// import Heart from "../../images/heart.png";
+// import { actionSendComment, getOnePost } from "../../actions";
+// import {
+//   Post,
+//   Info,
+//   User,
+//   Avatar,
+//   Icon,
+//   PostImage,
+//   PostContent,
+//   ReactionWrapper,
+//   Description,
+//   ReactionIcon,
+//   PostTime,
+//   CommentWrapper,
+//   CommentBox,
+//   IconComment,
+//   StyledButton,
+// } from "./post-draw.module";
+// import { Link, Box } from "@mui/material";
+// import {updatePost} from "../../actions/postEdite";
+//
+// const PostDraw = ({ post, currentUser }) => {
+//
+//   const store = useStore();
+//   const dispatch = useDispatch();
+//   const [comment, setComment] = useState("");
+//   const [comments, setComments] = useState(post.comments ?? []);
+//   const [likesAmount, setLikesAmount] = useState(post.likes?.length);
+//   const [isExistComments, setIsExistComments] = useState(false);
+//   const [isShowExtendedComments, setIsShowExtendedComments] = useState(false);
+//   let timestamp = post?.createdAt;
+//   let date = new Date(+timestamp).toString().slice(0, 24);
+//
+//   useEffect(() => {
+//     if (post.comments?.length > 0) {
+//       setIsExistComments(true);
+//     } else {
+//       setIsExistComments(false);
+//     }
+//   }, []);
+//
+//   const clickCommentHandler = async (event, postId) => {
+//     await actionSendComment(postId, comment)(dispatch);
+//     setComments((prevComments) =>
+//       comment.length > 0
+//         ? prevComments.concat({
+//             _id: Math.random(),
+//             text: comment,
+//             owner: { avatar: { url: currentUser.avatar?.url } },
+//           })
+//         : prevComments
+//     );
+//     setComment("");
+//     setIsExistComments(true);
+//   };
+//
+//
+//   const clickLikeHandler = async (event, postId) => {
+//     const currentPost = (await getOnePost(post._id)(dispatch)).data.PostFindOne;
+//     const isLiked = currentPost.likes.some(
+//         (like) => like.owner._id === store.getState().auth.payload.id
+//     );
+//     if (isLiked) {
+//       event.target.src =
+//           "https://cdn-icons-png.flaticon.com/512/1077/1077035.png";
+//       const likeId = currentPost.likes.find(
+//           (like) => like.owner._id === currentUser._id
+//       );
+//       gqlDeleteLike(likeId._id, postId);
+//     } else {
+//       event.target.src = Heart;
+//       gqlAddLike(postId);
+//     }
+//     setLikesAmount(
+//         isLiked ? currentPost.likes.length - 1 : currentPost.likes.length + 1
+//     );
+//   };
+//
+//   return (
+//     <Post>
+//       <Info>
+//         <User>
+//           <Box>
+//             <Link href={`user/${post?.owner?._id}`}>
+//               <Avatar src={post?.owner?.avatar?.url} />
+//             </Link>
+//           </Box>
+//           <Link href={`user/${post?.owner?._id}`}>
+//             <Box
+//               sx={{
+//                 width: "auto",
+//                 fontWeight: "bold",
+//                 color: "#000",
+//                 fontSize: "14px",
+//                 marginLeft: "10px",
+//               }}
+//             >
+//               {post?.owner?.login}
+//             </Box>
+//           </Link>
+//         </User>
+//         <Icon src={Dots} />
+//       </Info>
+//       <PostImage src={post?.images?.[0]?.url} />
+//       <PostContent>
+//         <ReactionWrapper>
+//           <ReactionIcon
+//             src={
+//               post.likes?.some(
+//                 (like) => like.owner._id === store.getState().auth.payload.id
+//               )
+//                 ? Heart
+//                 : "https://cdn-icons-png.flaticon.com/512/1077/1077035.png"
+//             }
+//             alt="like-icon"
+//             onClick={(event) => clickLikeHandler(event, post._id)}
+//           />
+//           <Box style={{ marginRight: "10px" }}>{likesAmount}</Box>
+//           <ReactionIcon
+//             src="https://cdn-icons-png.flaticon.com/512/2462/2462719.png"
+//             alt="comment-icon"
+//             onClick={() => setIsShowExtendedComments((prevState) => !prevState)}
+//           />
+//         </ReactionWrapper>
+//         <Description>{post?.text}</Description>
+//         {isExistComments && (
+//           <Comments
+//             comments={comments}
+//             currentUser={currentUser}
+//             isShowExtendedComments={isShowExtendedComments}
+//             setIsShowExtendedComments={setIsShowExtendedComments}
+//
+//           />
+//         )}
+//
+//        <PostTime>{date}</PostTime>
+//       </PostContent>
+//       <CommentWrapper className="comment-wrapper">
+//         <IconComment src="https://cdn-icons-png.flaticon.com/512/747/747402.png" />
+//         <CommentBox
+//           placeholder="Add a comment"
+//           value={comment}
+//           onChange={(event) => setComment(event.target.value)}
+//           inputProps={{ style: { backgroundColor: "transparent" } }}
+//         />
+//         <StyledButton onClick={(event) => clickCommentHandler(event, post._id)}>
+//           post
+//         </StyledButton>
+//       </CommentWrapper>
+//     </Post>
+//   );
+// };
+//
+// export const ConnectPostDraw = connect(null, { posts: PostDraw })(PostDraw);
+//
+// import { useState, useEffect } from "react";
+// import { connect, useStore, useDispatch } from "react-redux";
+// import { gqlAddLike, gqlDeleteLike } from "../../actions/like";
+// import Comments from "../Comments";
+// import "../../App.css";
+// import Dots from "../../images/more.png";
+// import Heart from "../../images/heart.png";
+// import { actionSendComment, getOnePost } from "../../actions";
+// import { Post, Info, User, Avatar, Icon, PostImage, PostContent, ReactionWrapper, Description, ReactionIcon, PostTime, CommentWrapper, CommentBox, IconComment, StyledButton } from "./post-draw.module";
+// import { Link, Box } from "@mui/material";
+// import { updatePost } from "../../actions/postEdite";
+//
+// const PostDraw = ({ post, currentUser }) => {
+//   const store = useStore();
+//   const dispatch = useDispatch();
+//   const [comment, setComment] = useState("");
+//   const [comments, setComments] = useState(post.comments ?? []);
+//   const [likesAmount, setLikesAmount] = useState(post.likes?.length);
+//   const [isExistComments, setIsExistComments] = useState(false);
+//   const [isShowExtendedComments, setIsShowExtendedComments] = useState(false);
+//   const timestamp = post?.createdAt;
+//   const date = new Date(+timestamp).toString().slice(0, 24);
+//
+//   useEffect(() => {
+//     setIsExistComments(post.comments?.length > 0);
+//   }, [post.comments]);
+//
+//   const clickCommentHandler = async (event, postId) => {
+//     await actionSendComment(postId, comment)(dispatch);
+//     setComments((prevComments) =>
+//         comment.length > 0
+//             ? prevComments.concat({
+//               _id: Math.random(),
+//               text: comment,
+//               owner: { avatar: { url: currentUser.avatar?.url } },
+//             })
+//             : prevComments
+//     );
+//     setComment("");
+//     setIsExistComments(true);
+//   };
+//
+//
+//   const clickLikeHandler = async (event, postId) => {
+//     const currentPost = (await getOnePost(post._id)(dispatch)).data.PostFindOne;
+//     const isLiked = currentPost.likes.some(
+//         (like) => like.owner._id === store.getState().auth.payload.id
+//     );
+//     if (isLiked) {
+//       event.target.src =
+//           "https://cdn-icons-png.flaticon.com/512/1077/1077035.png";
+//       const likeId = currentPost.likes.find(
+//           (like) => like.owner._id === currentUser._id
+//       );
+//       gqlDeleteLike(likeId._id, postId);
+//     } else {
+//       event.target.src = Heart;
+//       gqlAddLike(postId);
+//     }
+//     setLikesAmount(
+//         isLiked ? currentPost.likes.length - 1 : currentPost.likes.length + 1
+//     );
+//   };
+//
+//   return (
+//       <Post>
+//         <Info>
+//           <User>
+//             <Box>
+//               <Link href={`user/${post?.owner?._id}`}>
+//                 <Avatar src={post?.owner?.avatar?.url} />
+//               </Link>
+//             </Box>
+//             <Link href={`user/${post?.owner?._id}`}>
+//               <Box
+//                   sx={{
+//                     width: "auto",
+//                     fontWeight: "bold",
+//                     color: "#000",
+//                     fontSize: "14px",
+//                     marginLeft: "10px",
+//                   }}
+//               >
+//                 {post?.owner?.login}
+//               </Box>
+//             </Link>
+//           </User>
+//           <Icon src={Dots} />
+//         </Info>
+//         <PostImage src={post?.images?.[0]?.url}/>
+//         <PostContent>
+//           <ReactionWrapper>
+//             <ReactionIcon
+//                 src={
+//                   post.likes?.some(
+//                       (like) => like.owner._id === store.getState().auth.payload.id
+//                   )
+//                       ? Heart
+//                       : "https://cdn-icons-png.flaticon.com/512/1077/1077035.png"
+//                 }
+//                 alt="like-icon"
+//                 onClick={(event) => clickLikeHandler(event, post._id)}
+//             />
+//             <Box style={{ marginRight: "10px" }}>{likesAmount}</Box>
+//             <ReactionIcon
+//                 src="https://cdn-icons-png.flaticon.com/512/2462/2462719.png"
+//                 alt="comment-icon"
+//                 onClick={() =>
+//                     setIsShowExtendedComments((prevState) => !prevState)
+//                 }
+//             />
+//           </ReactionWrapper>
+//           <Description>{post?.text}</Description>
+//           {isExistComments && (
+//               <Comments
+//                   comments={comments}
+//                   currentUser={currentUser}
+//                   isShowExtendedComments={isShowExtendedComments}
+//                   setIsShowExtendedComments={setIsShowExtendedComments}
+//               />
+//           )}
+//           <PostTime>{date}</PostTime>
+//         </PostContent>
+//         <CommentWrapper className="comment-wrapper">
+//           <IconComment src="https://cdn-icons-png.flaticon.com/512/747/747402.png" />
+//           <CommentBox
+//               placeholder="Add a comment"
+//               value={comment}
+//               onChange={(event) => setComment(event.target.value)}
+//               inputProps={{ style: { backgroundColor: "transparent" } }}
+//           />
+//           <StyledButton onClick={(event) => clickCommentHandler(event, post._id)}>
+//             post
+//           </StyledButton>
+//         </CommentWrapper>
+//       </Post>
+//   );
+// };
+//
+// export const ConnectPostDraw = connect(null, { posts: PostDraw })(PostDraw);
+
+import { useState, useEffect } from "react";
+import { connect } from "react-redux";
+import { gqlAddLike, gqlDeleteLike } from "../../actions/like";
+import Comments from "../Comments";
+import "../../App.css";
+import Dots from "../../images/more.png";
+import Heart from "../../images/heart.png";
+import { actionSendComment, getOnePost } from "../../actions";
+import { Post, Info, User, Avatar, Icon, PostImage, PostContent, ReactionWrapper, Description, ReactionIcon, PostTime, CommentWrapper, CommentBox, IconComment, StyledButton } from "./post-draw.module";
+import { Link, Box } from "@mui/material";
+
+const PostDraw = ({ post, currentUser, dispatch }) => {
+    const [comment, setComment] = useState("");
+    const [comments, setComments] = useState(post.comments ?? []);
+    const [likesAmount, setLikesAmount] = useState(post.likes?.length);
+    const [isExistComments, setIsExistComments] = useState(false);
+    const [isShowExtendedComments, setIsShowExtendedComments] = useState(false);
+    const timestamp = post?.createdAt;
+    const date = new Date(+timestamp).toString().slice(0, 24);
+    useEffect(() => {
+        setIsExistComments(post.comments?.length > 0);
+    }, [post.comments]);
+
+    const clickCommentHandler = async (event, postId) => {
+        await dispatch(actionSendComment(postId, comment));
+        setComments((prevComments) =>
+            comment.length > 0
+                ? prevComments.concat({
+                    _id: Math.random(),
+                    text: comment,
+                    owner: { avatar: { url: currentUser.avatar?.url } },
+                })
+                : prevComments
+        );
+
+        setComment("");
+        setIsExistComments(true);
+    };
+
+    const clickLikeHandler = async (event, postId) => {
+        const currentPost = (await getOnePost(post._id)(dispatch)).data.PostFindOne;
+        const isLiked = currentPost.likes.some(
+            (like) => like.owner._id === currentUser._id
+        );
+        if (isLiked) {
+            event.target.src =
+                "https://cdn-icons-png.flaticon.com/512/1077/1077035.png";
+            const likeId = currentPost.likes.find(
+                (like) => like.owner._id === currentUser._id
+            );
+            gqlDeleteLike(likeId._id, postId);
+        } else {
+            event.target.src = Heart;
+            gqlAddLike(postId);
+        }
+        setLikesAmount(
+            isLiked ? currentPost.likes.length - 1 : currentPost.likes.length + 1
+        );
+    };
+
+    const handlePostComment = (event, postId) => {
+        event.preventDefault();
+        if (comment.trim() !== "") {
+            clickCommentHandler(event, postId);
+        }
+    };
+
+    return (
+        <Post>
+            <Info>
+                <User>
+                    <Box>
+                        <Link href={`user/${post?.owner?._id}`}>
+                            <Avatar src={post?.owner?.avatar?.url} />
+                        </Link>
+                    </Box>
+                    <Link href={`user/${post?.owner?._id}`}>
+                        <Box
+                            sx={{
+                                width: "auto",
+                                fontWeight: "bold",
+                                color: "#000",
+                                fontSize: "14px",
+                                marginLeft: "10px",
+                            }}
+                        >
+                            {post?.owner?.login}
+                        </Box>
+                    </Link>
+                </User>
+                <Icon src={Dots} />
+            </Info>
+            <PostImage src={post?.images?.[0]?.url} />
+            <PostContent>
+                <ReactionWrapper>
+                    <ReactionIcon
+                        src={
+                            post.likes?.some(
+                                (like) => like._id === currentUser._id
+                            )
+                                ? Heart
+                                : "https://cdn-icons-png.flaticon.com/512/1077/1077035.png"
+                        }
+                        alt="like-icon"
+                        onClick={(event) => clickLikeHandler(event, post._id)}
+                    />
+                    <Box style={{ marginRight: "10px" }}>{likesAmount}</Box>
+                    <ReactionIcon
+                        src="https://cdn-icons-png.flaticon.com/512/2462/2462719.png"
+                        alt="comment-icon"
+                        onClick={() =>
+                            setIsShowExtendedComments((prevState) => !prevState)
+                        }
+                    />
+                </ReactionWrapper>
+                <Description>{post?.text}</Description>
+               
+                    <Comments
+                        post={post}
+                        currentUser={currentUser}
+                        isShowExtendedComments={isShowExtendedComments}
+                        setIsShowExtendedComments={setIsShowExtendedComments}
+                    />
+            
+                <PostTime>{date}</PostTime>
+            </PostContent>
+        </Post>
+    );
+};
+
+export const ConnectPostDraw = connect(null)(PostDraw);

+ 140 - 0
hipstagram/src/components/PostDraw/post-draw.module.js

@@ -0,0 +1,140 @@
+import { Box, TextField } from "@mui/material";
+import { Button } from "react-bootstrap";
+import styled from "styled-components";
+
+export const Post = styled(Box)`
+  align-items: center;
+  width: 80%;
+  height: auto;
+  margin: 0 auto;
+  margin-top: 40px;
+  background: #fff;
+  border: 1px solid #dfdfdf;
+`;
+
+export const Info = styled(Box)`
+  width: 100%;
+  height: 60px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 20px;
+`;
+
+export const User = styled(Box)`
+  display: flex;
+  align-items: center;
+`;
+
+export const Avatar = styled((props) => (
+  <Box component="img" alt="profile-avatar" src={props.src} {...props} />
+))`
+  height: 40px;
+  width: 40px;
+  padding: 0;
+  background: none;
+  border-radius: 50%;
+`;
+
+export const Icon = styled((props) => (
+  <Box component="img" alt="icon" src={props.src} {...props} />
+))`
+  height: 10px;
+  cursor: pointer;
+`;
+
+export const PostImage = styled((props) => (
+  <Box component="img" alt="post" src={props.src} {...props} />
+))`
+  width: 100%;
+  height: 500px;
+  object-fit: cover;
+`;
+
+export const PostContent = styled(Box)`
+  width: 100%;
+  padding: 20px;
+`;
+
+export const ReactionWrapper = styled(Box)`
+  width: 100%;
+  height: 50px;
+  display: flex;
+  margin-top: -20px;
+  align-items: center;
+`;
+
+export const Description = styled(Box)`
+  display: flex;
+  flex-direction: row;
+  margin: 10px 0;
+  font-size: 16px;
+  line-height: 20px;
+`;
+
+export const ReactionIcon = styled((props) => (
+  <Box
+    component="img"
+    alt={props.alt}
+    src={props.src}
+    onClick={props.onClick}
+    {...props}
+  />
+))`
+  cursor: pointer;
+  height: 25px;
+  margin: 0;
+  margin-right: 5px;
+`;
+
+export const PostTime = styled(Box)`
+  color: rgba(0, 0, 0, 0.5);
+  font-size: 12px;
+`;
+
+export const CommentWrapper = styled(Box)`
+  padding: 5px 20px;
+  width: 100%;
+  height: 50px;
+  border-radius: 1px solid #dfdfdf;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+`;
+
+export const IconComment = styled((props) => (
+  <Box component="img" alt="smile-icon" src={props.src} {...props} />
+))`
+  height: 22px;
+`;
+
+export const CommentBox = styled((props) => (
+  <TextField {...props} placeholder="add a comment" variant="filled" />
+))`
+  & .MuiInputBase-root {
+    background-color: transparent !important;
+  }
+  &.MuiFormControl-root {
+    width: 80% !important;
+    height: 100% !important;
+    padding-top: 5px !important;
+    padding-left: 5px;
+    & .MuiInputBase-input {
+      padding: 5px !important;
+      font-size: 14px !important;
+      background-color: transparent !important;
+    }
+  }
+`;
+
+export const StyledButton = styled(Button)`
+  width: 70px;
+  height: 100%;
+  background: none;
+  border: none;
+  outline: none;
+  text-transform: capitalize;
+  font-size: 16px;
+  color: rgb(0, 162, 255);
+  opacity: 0.5;
+`;

+ 136 - 0
hipstagram/src/components/Profile/PostDetails.css

@@ -0,0 +1,136 @@
+.modal {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: rgba(0, 0, 0, 0.5);
+}
+
+.modal_block {
+    position: relative;
+    width: 1000px;
+    background-color: #fff;
+    padding: 20px;
+    border-radius: 8px;
+}
+
+.close_btn {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    padding: 5px;
+    background-color: transparent;
+    border: none;
+    font-size: 18px;
+    cursor: pointer;
+}
+
+.post_details {
+    display: flex;
+    gap: 20px;
+}
+
+.post_section {
+    flex: 1;
+}
+
+.post_content {
+    display: flex;
+    flex-direction: column;
+}
+
+.pd_img {
+    width: 100%;
+    max-height: 500px;
+    object-fit: cover;
+    border-radius: 8px;
+    margin-bottom: 10px;
+}
+
+.post_info {
+    margin-top: auto;
+}
+
+.post_title {
+    font-size: 24px;
+    margin-bottom: 10px;
+}
+
+.post_text {
+    font-size: 16px;
+}
+
+.user_section {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+}
+
+.user_info {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 20px;
+}
+
+.user_login {
+    font-size: 18px;
+}
+
+.comments_section {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+}
+
+.comments_title {
+    font-size: 20px;
+    margin-bottom: 10px;
+}
+
+.comment_list {
+    overflow-y: auto;
+}
+
+.comment {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 10px;
+}
+
+.comment_avatar {
+    width: 30px;
+    height: 30px;
+    border-radius: 50%;
+}
+
+.comment_text {
+    font-size: 14px;
+}
+
+.likes_section {
+    margin-top: 20px;
+}
+
+.likes_title {
+    font-size: 20px;
+}
+
+.likes_count {
+    font-weight: bold;
+}
+
+
+
+.flex{
+    display: flex;
+    gap: 5px;
+    margin-bottom: 10px;
+    align-items: center;
+}

+ 55 - 0
hipstagram/src/components/Profile/editePost.js

@@ -0,0 +1,55 @@
+import React, { useState } from "react";
+import { updatePost } from "../../actions/postEdite";
+import { Box, Button, TextField } from "@mui/material";
+
+
+const EditPost = ({ onCloseModall, post }) => {
+    const [title, setTitle] = useState(post.title);
+    const [text, setText] = useState(post.text);
+
+
+    const handleEdit = async () => {
+        try {
+            updatePost(post._id,title,text)
+        }
+        catch (err) {
+            console.log(err)
+        }finally{
+            window.location.reload()
+        }
+    };
+
+    return (
+        <div className="modal">
+            <div className="modal_block">
+                <h1>Edit Post</h1>
+
+                <div style={{ width:'400px', height:'400px', marginTop: 10, display: "flex", flexDirection: "column", gap: 15 }} className="edit_inputs">
+                    <img src={`../${post?.images?.[0]?.url}`} />
+                    <TextField
+                        type="text"
+                        value={title}
+                        onChange={(e) => setTitle(e.target.value)}
+                        label="Ttle"
+                    />
+                    <TextField
+                        type="text"
+                        value={text}
+                        onChange={(e) => setText(e.target.value)}
+                        label="Text"
+                    />
+                </div>
+                <Box sx={{ gap: "12px", display: "flex", marginTop: "10px" }}>
+                    <button className="edt_btn" onClick={handleEdit}>
+                        Save
+                    </button>
+                    <button className="cncl_btn" onClick={onCloseModall}>
+                        Cancel
+                    </button>
+                </Box>
+            </div>
+        </div>
+    );
+};
+
+export default EditPost;

+ 50 - 0
hipstagram/src/components/Profile/editeProfile.js

@@ -0,0 +1,50 @@
+import React, { useState } from "react";
+
+import { Box, Button, TextField } from "@mui/material";
+import { ConnectUploadPhotoForm } from "../UploadPhoto";
+import { updateProfileNickname } from "../../actions";
+
+
+const EditProfile = ({ onCloseModall, nickname, userId }) => {
+  const [name, setName] = useState(nickname);
+
+
+  const handleEdit = async () => {
+    try {
+      updateProfileNickname(userId, name)
+      window.location.reload()
+
+    }
+    catch (err) {
+      console.log(err)
+    }
+
+  };
+
+  return (
+    <div className="modal">
+      <div className="modal_block">
+        <h1>Edit Profile</h1>
+        <div className="edit_inputs">
+          <ConnectUploadPhotoForm />
+          <TextField
+            type="text"
+            value={name}
+            onChange={(e) => setName(e.target.value)}
+            label="Username"
+          />
+        </div>
+        <Box sx={{ gap: "12px", display: "flex", marginTop: "10px" }}>
+          <button className="edt_btn" onClick={handleEdit}>
+            Save
+          </button>
+          <button className="cncl_btn" onClick={onCloseModall}>
+            Cancel
+          </button>
+        </Box>
+      </div>
+    </div>
+  );
+};
+
+export default EditProfile;

+ 626 - 0
hipstagram/src/components/Profile/index.js

@@ -0,0 +1,626 @@
+// import {connect, useDispatch} from "react-redux";
+// import "../../App.css";
+// import {useHistory} from "react-router-dom";
+// import EditProfile from "./editeProfile";
+// import EditPost from "./editePost";
+// import React, {useEffect, useState} from "react";
+// import {Box} from "@mui/material";
+// import {actionPostById, actionShowFollowing, delete_post, actionShowFollowers} from "../../actions";
+// import {ConnectUploadPhotoForm} from "../UploadPhoto";
+// import {actionSubscribe, getUserFollowing, actionUnfollow} from "../../actions/user";
+// import {
+//     UserPage,
+//     StyledUserProfile,
+//     ProfileAvatar,
+//     UserProfileDescription,
+//     ProfileName,
+//     ProfileInfo,
+//     UserPagePosts,
+//     PostImage
+// } from "./profile.module";
+// import PostDetails from "./postDEtails";
+//
+// const UserProfile = ({
+//                          match: {
+//                              params: {id},
+//                          },
+//                          onProfile,
+//                          state,
+//                      }) => {
+//
+//     const dispatch = useDispatch()
+//
+//
+//     let posts = state?.promise?.PostById?.payload?.data?.PostFind;
+//     let token = state?.auth?.payload?.id;
+//
+//     const [followers, setFollowers] = useState([]);
+//     const [showModal, setShowModal] = useState(false);
+//     const [isFollowingModal, setIsFollowingModal] = useState(false);
+//     const [editeModalProfile, setEditeModalProfile] = useState(false);
+//     const [statePosts, setStatePosts] = useState([]);
+//
+//     useEffect(() => {
+//         setStatePosts(posts)
+//     })
+//
+//     const [following, setFollowing] = useState([]);
+//     const [followingIds, setFollowingIds] = useState([]);
+//     const [isFollowing, setIsFollowing] = useState('loading');
+//
+//     useEffect(() => {
+//         const followersArray = posts?.[0]?.owner?.followers || [];
+//         setIsFollowing(followersArray.some(f => f._id === token));
+//     }, [posts, token]);
+//
+//     const handleDeletePost = (id) => {
+//         delete_post(id)
+//         window.location.reload()
+//     }
+//
+//     const [showEditeModal, setShowEditeModal] = useState(false)
+//     const [editedPost, setEditedPost] = useState()
+//
+//     const handleEditePost = (post) => {
+//         console.log(post)
+//         setShowEditeModal(true)
+//         setEditedPost(post)
+//     }
+//
+//     const handleCloseModalEditePost = () => {
+//         setShowEditeModal(false)
+//     }
+//
+//     const history = useHistory();
+//
+//
+//     const showFollowers = async () => {
+//         setFollowers([]);
+//         const response = (await actionShowFollowers(id)(dispatch));
+//         const followersData = response?.data?.UserFindOne?.followers ?? [];
+//         setFollowers(followersData);
+//         setShowModal(!showModal);
+//         setIsFollowingModal(false);
+//     };
+//
+//     const showFollowing = async () => {
+//         setFollowers([]);
+//         const response = (await actionShowFollowing(id)(dispatch));
+//         const followersData = response?.data?.UserFindOne?.following ?? [];
+//         setFollowers(followersData);
+//         setShowModal(!showModal);
+//         setIsFollowingModal(true);
+//     };
+//
+//     const handleFollowerClick = (follower) => {
+//         history.push(`/user/${follower._id}`);
+//         setShowModal(false);
+//     };
+//
+//     useEffect(() => {
+//         onProfile(id);
+//     }, [id]);
+//
+//
+//     const handleFollowUser = async () => {
+//         try {
+//             const response = await getUserFollowing(token);
+//             const following = response?.data?.UserFindOne?.following;
+//             following.push({_id: id});
+//             actionSubscribe(token, following);
+//         } catch (error) {
+//             console.error(error);
+//         } finally {
+//             window.location.reload()
+//         }
+//     };
+//
+//     const handleUnfollowUser = async () => {
+//         try {
+//             await actionUnfollow(token, id)(dispatch);
+//         } finally {
+//             window.location.reload();
+//         }
+//     };
+//
+//     const handleCloseModal = () => {
+//         setShowModal(false);
+//     }
+//
+//     const handleEditeProfile = () => {
+//         setEditeModalProfile(true)
+//     }
+//     const handleCloseModalEdite = () => {
+//         setEditeModalProfile(false)
+//     }
+//
+//     const [showPostModal, setShowPostModal] = useState(false)
+//
+//     const [postDetails, setPostDetails] = useState([])
+//
+//     const showPost = (post) => {
+//         setShowPostModal(true)
+//         setPostDetails(post)
+//     }
+//
+//     const closePostDetails = (() => setShowPostModal(false))
+//
+//
+//     return (
+//         <UserPage>
+//             {showEditeModal && <EditPost post={editedPost} onCloseModall={handleCloseModalEditePost}/>}
+//             {showModal &&
+//                 <FollowersModal isFollowingModal={isFollowingModal} followers={followers}
+//                                 handleFollowerClick={handleFollowerClick} onCloseModal={handleCloseModal}/>}
+//             {editeModalProfile &&
+//                 <EditProfile nickname={posts?.[0]?.owner?.login} userId={token} onCloseModall={handleCloseModalEdite}/>}
+//             {showPostModal && <PostDetails onCloseModall={closePostDetails} post={postDetails}/>}
+//             <StyledUserProfile>
+//                 {!posts?.[0]?.owner?.avatar?.url && token === id ? (
+//                     <Box>
+//                         <ConnectUploadPhotoForm/>
+//                     </Box>
+//                 ) : (
+//                     <ProfileAvatar>
+//                         <Box
+//                             component="img"
+//                             src={`../${posts?.[0]?.owner?.avatar?.url}`}
+//                             alt="profile-avatar"
+//                         />
+//                     </ProfileAvatar>
+//                 )}
+//                 <UserProfileDescription>
+//                     <ProfileName>
+//                         <p className="profile-login">{`Username: ${posts?.[0]?.owner?.login || state?.auth?.payload?.login
+//                         }`}</p>
+//                     </ProfileName>
+//                     <ProfileInfo>
+//                         <Box component="p">{`Posts: ${posts ? posts?.length : "0"}`}</Box>
+//                         <Box onClick={showFollowers} component="p">{`Followers: ${posts?.[0]?.owner?.followers
+//                             ? posts?.[0]?.owner?.followers?.length
+//                             : "0"
+//                         }`}</Box>
+//                         <Box onClick={showFollowing} component="p">{`Following: ${posts?.[0]?.owner?.following?.length
+//                             ? posts?.[0]?.owner?.following.length
+//                             : "0"
+//                         }`}
+//                         </Box>
+//                     </ProfileInfo>
+//                     {token !== id ?
+//                         <div style={{
+//                             width: "100%"
+//                         }}>
+//                             {isFollowing === 'loading' ? (
+//                                 <button style={{textAlign: "center"}}>Loading...</button>
+//                             ) : isFollowing ? (
+//                                 <button className="unflw_btn" onClick={handleUnfollowUser}>Unfollow</button>
+//                             ) : (
+//                                 <button className="flw_btn" onClick={handleFollowUser}>Follow</button>
+//                             )}
+//                         </div>
+//
+//                         :
+//                         <button className="edite_profile" onClick={handleEditeProfile}>Edite Profile</button>
+//                     }
+//                 </UserProfileDescription>
+//
+//             </StyledUserProfile>
+//
+//             <UserPagePosts className="user-page-posts">
+//                 {statePosts?.map((post) => (
+//
+//                     <PostImage key={post._id} className="post-image">
+//                         <Box onClick={() => showPost(post)}
+//                             component="img"
+//                             src={`../${post?.images?.[0]?.url}`}
+//                             alt="post"
+//                         />
+//                         {token === id &&
+//
+//                             <div className="post_tool_block">
+//                                 <div onClick={() => handleEditePost(post)}>
+//                                     <svg style={{width: '20px', height: '20px'}} viewBox="0 0 24 24" fill="none"
+//                                          xmlns="http://www.w3.org/2000/svg">
+//                                         <path d="M13 21H21" stroke="#323232" stroke-width="2" stroke-linecap="round"
+//                                               stroke-linejoin="round"/>
+//                                         <path
+//                                             d="M20.0651 7.39423L7.09967 20.4114C6.72438 20.7882 6.21446 21 5.68265 21H4.00383C3.44943 21 3 20.5466 3 19.9922V18.2987C3 17.7696 3.20962 17.2621 3.58297 16.8873L16.5517 3.86681C19.5632 1.34721 22.5747 4.87462 20.0651 7.39423Z"
+//                                             stroke="#323232" stroke-width="2" stroke-linecap="round"
+//                                             stroke-linejoin="round"/>
+//                                         <path d="M15.3096 5.30981L18.7273 8.72755" stroke="#323232" stroke-width="2"
+//                                               stroke-linecap="round" stroke-linejoin="round"/>
+//                                         <path opacity="0.1"
+//                                               d="M18.556 8.90942L7.09967 20.4114C6.72438 20.7882 6.21446 21 5.68265 21H4.00383C3.44943 21 3 20.5466 3 19.9922V18.2987C3 17.7696 3.20962 17.2621 3.58297 16.8873L15.0647 5.35974C15.0742 5.4062 15.0969 5.45049 15.1329 5.48653L18.5506 8.90426C18.5524 8.90601 18.5542 8.90773 18.556 8.90942Z"
+//                                               fill="#323232"/>
+//                                     </svg>
+//                                 </div>
+//
+//                                 <div onClick={() => handleDeletePost(post._id)}>
+//                                     <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none"
+//                                          xmlns="http://www.w3.org/2000/svg"
+//                                          style={{width: '20px', height: '20px'}}>
+//                                         <path
+//                                             d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z"
+//                                             stroke="#292D32" stroke-width="1.5" stroke-linecap="round"
+//                                             stroke-linejoin="round"/>
+//                                         <g opacity="0.4">
+//                                             <path d="M9.16992 14.8299L14.8299 9.16992" stroke="#292D32"
+//                                                   stroke-width="1.5"
+//                                                   stroke-linecap="round" stroke-linejoin="round"/>
+//                                             <path d="M14.8299 14.8299L9.16992 9.16992" stroke="#292D32"
+//                                                   stroke-width="1.5"
+//                                                   stroke-linecap="round" stroke-linejoin="round"/>
+//                                         </g>
+//                                     </svg>
+//                                 </div>
+//                             </div>
+//                         }
+//                         <div>
+//                             {post.title ?
+//                                 <div>
+//                                     <h5>{post.title}</h5>
+//                                     <p>{post.text}</p></div>
+//                                 : <p>empty...</p>
+//                             }
+//                         </div>
+//                     </PostImage>
+//
+//                 ))}
+//             </UserPagePosts>
+//         </UserPage>
+//     );
+// };
+//
+// const FollowersModal = ({followers, isFollowingModal, onCloseModal, handleFollowerClick}) => {
+//     return (
+//         <div className="modal">
+//             <div className="modal_block" style={{width: '500px'}}>
+//                 <h2>{isFollowingModal ? "Following" : "Followers"}</h2>
+//                 {followers.length > 0 ? (
+//                     <div className="list_followers">
+//                         {followers.map((follower) => (
+//                             follower.login && (
+//                                 <div className="user" key={follower}>
+//                                     <img onClick={() => handleFollowerClick(follower)}
+//                                          src={`../${follower?.avatar?.url}`}/>
+//                                     <h4 onClick={() => handleFollowerClick(follower)}>{follower?.login}</h4>
+//                                     <button style={{
+//                                         backgroundColor: isFollowingModal ? "#ccc" : "#007bff",
+//                                         color: isFollowingModal ? "#000" : "#fff"
+//                                     }}>
+//                                         {isFollowingModal ? "unfollow" : "follow"}
+//                                     </button>
+//                                 </div>
+//                             )
+//                         ))}
+//                     </div>
+//                 ) : (
+//                     <p>No {isFollowingModal ? "following" : "followers"} to show</p>
+//                 )}
+//                 <button className="close_followers_modal" onClick={onCloseModal}>
+//                     x
+//                 </button>
+//             </div>
+//         </div>
+//     );
+//
+// };
+//
+// export const UserProfileDraw = connect((state) => ({state: state}), {
+//     onProfile: actionPostById,
+// })(UserProfile);
+
+import { connect, useDispatch } from "react-redux";
+import "../../App.css";
+import { useHistory } from "react-router-dom";
+import EditProfile from "./editeProfile";
+import EditPost from "./editePost";
+import React, { useEffect, useState } from "react";
+import { Box } from "@mui/material";
+import { actionPostById, actionShowFollowing, delete_post, actionShowFollowers } from "../../actions";
+import { ConnectUploadPhotoForm } from "../UploadPhoto";
+import { actionSubscribe, getUserFollowing, actionUnfollow,gqlAboutUser } from "../../actions/user";
+import {
+    UserPage,
+    StyledUserProfile,
+    ProfileAvatar,
+    UserProfileDescription,
+    ProfileName,
+    ProfileInfo,
+    UserPagePosts,
+    PostImage
+} from "./profile.module";
+import PostDetails from "./postDEtails";
+
+const UserProfile = ({
+    match: {
+        params: { id },
+    },
+    onProfile,
+    state,
+}) => {
+
+    const dispatch = useDispatch()
+
+
+    let posts = state?.promise?.PostById?.payload?.data?.PostFind;
+    let token = state?.auth?.payload?.id;
+
+    const [followers, setFollowers] = useState([]);
+    const [showModal, setShowModal] = useState(false);
+    const [isFollowingModal, setIsFollowingModal] = useState(false);
+    const [editeModalProfile, setEditeModalProfile] = useState(false);
+    const [statePosts, setStatePosts] = useState([]);
+    const [following, setFollowing] = useState([]);
+    const [followingIds, setFollowingIds] = useState([]);
+    const [isFollowing, setIsFollowing] = useState('loading');
+    const [showEditeModal, setShowEditeModal] = useState(false)
+    const [editedPost, setEditedPost] = useState()
+    const [showPostModal, setShowPostModal] = useState(false)
+    const [postDetails, setPostDetails] = useState([])
+    const [currentUser,setCurrentUser] = useState()
+
+    useEffect(() => {
+        setStatePosts(posts)
+    })
+
+    useEffect(() => {
+        (async () => {
+          const currentUser = (await gqlAboutUser(token)).data.UserFindOne;
+          setCurrentUser(currentUser);
+        })()
+      }, []);
+
+    useEffect(() => {
+        const followersArray = posts?.[0]?.owner?.followers || [];
+        setIsFollowing(followersArray.some(f => f._id === token));
+    }, [posts, token]);
+
+    const handleDeletePost = (id) => {
+        delete_post(id)
+        window.location.reload()
+    }
+
+    const handleEditePost = (post) => {
+        console.log(post)
+        setShowEditeModal(true)
+        setEditedPost(post)
+    }
+
+    const handleCloseModalEditePost = () => {
+        setShowEditeModal(false)
+    }
+
+    const history = useHistory();
+
+    const showFollowers = async () => {
+        setFollowers([]);
+        const response = (await actionShowFollowers(id)(dispatch));
+        const followersData = response?.data?.UserFindOne?.followers ?? [];
+        setFollowers(followersData);
+        setShowModal(!showModal);
+        setIsFollowingModal(false);
+    };
+
+    const showFollowing = async () => {
+        setFollowers([]);
+        const response = (await actionShowFollowing(id)(dispatch));
+        const followersData = response?.data?.UserFindOne?.following ?? [];
+        setFollowers(followersData);
+        setShowModal(!showModal);
+        setIsFollowingModal(true);
+    };
+
+    const handleFollowerClick = (follower) => {
+        history.push(`/user/${follower._id}`);
+        setShowModal(false);
+    };
+
+    useEffect(() => {
+        onProfile(id);
+    }, [id]);
+
+    const handleFollowUser = async () => {
+        try {
+            const response = await getUserFollowing(token);
+            const following = response?.data?.UserFindOne?.following;
+            following.push({ _id: id });
+            actionSubscribe(token, following);
+        } catch (error) {
+            console.error(error);
+        } finally {
+            window.location.reload()
+        }
+    };
+
+    const handleUnfollowUser = async () => {
+        try {
+            await actionUnfollow(token, id)(dispatch);
+        } finally {
+            window.location.reload();
+        }
+    };
+
+    const handleCloseModal = () => {
+        setShowModal(false);
+    }
+
+    const handleEditeProfile = () => {
+        setEditeModalProfile(true)
+    }
+    const handleCloseModalEdite = () => {
+        setEditeModalProfile(false)
+    }
+
+    const showPost = (post) => {
+        setShowPostModal(true)
+        setPostDetails(post)
+    }
+
+    const closePostDetails = (() => setShowPostModal(false))
+
+    return (
+        <UserPage>
+            {showEditeModal && <EditPost post={editedPost} onCloseModall={handleCloseModalEditePost} />}
+            {showModal &&
+                <FollowersModal isFollowingModal={isFollowingModal} followers={followers}
+                    handleFollowerClick={handleFollowerClick} onCloseModal={handleCloseModal} />}
+            {editeModalProfile &&
+                <EditProfile nickname={posts?.[0]?.owner?.login} userId={token} onCloseModall={handleCloseModalEdite} />}
+            {showPostModal && <PostDetails onCloseModall={closePostDetails} currentUser={currentUser} post={postDetails} />}
+            <StyledUserProfile>
+                {!posts?.[0]?.owner?.avatar?.url && token === id ? (
+                    <Box>
+                        <ConnectUploadPhotoForm />
+                    </Box>
+                ) : (
+                    <ProfileAvatar>
+                        <Box
+                            component="img"
+                            src={`../${posts?.[0]?.owner?.avatar?.url}`}
+                            alt="profile-avatar"
+                        />
+                    </ProfileAvatar>
+                )}
+                <UserProfileDescription>
+                    <ProfileName>
+                        <p className="profile-login">{`Username: ${posts?.[0]?.owner?.login || state?.auth?.payload?.login
+                            }`}</p>
+                    </ProfileName>
+                    <ProfileInfo>
+                        <Box component="p">{`Posts: ${posts ? posts?.length : "0"}`}</Box>
+                        <Box onClick={showFollowers} component="p">{`Followers: ${posts?.[0]?.owner?.followers
+                            ? posts?.[0]?.owner?.followers?.length
+                            : "0"
+                            }`}</Box>
+                        <Box onClick={showFollowing} component="p">{`Following: ${posts?.[0]?.owner?.following?.length
+                            ? posts?.[0]?.owner?.following.length
+                            : "0"
+                            }`}
+                        </Box>
+                    </ProfileInfo>
+                    {token !== id ?
+                        <div style={{
+                            width: "100%"
+                        }}>
+                            {isFollowing === 'loading' ? (
+                                <button style={{ textAlign: "center" }}>Loading...</button>
+                            ) : isFollowing ? (
+                                <button className="unflw_btn" onClick={handleUnfollowUser}>Unfollow</button>
+                            ) : (
+                                <button className="flw_btn" onClick={handleFollowUser}>Follow</button>
+                            )}
+                        </div>
+
+                        :
+                        <button className="edite_profile" onClick={handleEditeProfile}>Edite Profile</button>
+                    }
+                </UserProfileDescription>
+
+            </StyledUserProfile>
+
+            <UserPagePosts className="user-page-posts">
+                {statePosts?.map((post) => (
+
+                    <PostImage key={post._id} className="post-image">
+                        <Box onClick={() => showPost(post)}
+                            component="img"
+                            src={`../${post?.images?.[0]?.url}`}
+                            alt="post"
+                            style={{ height: '260px' }}
+                        />
+                        {token === id &&
+
+                            <div className="post_tool_block">
+                                <div onClick={() => handleEditePost(post)}>
+                                    <svg style={{ width: '20px', height: '20px' }} viewBox="0 0 24 24" fill="none"
+                                        xmlns="http://www.w3.org/2000/svg">
+                                        <path d="M13 21H21" stroke="#323232" stroke-width="2" stroke-linecap="round"
+                                            stroke-linejoin="round" />
+                                        <path
+                                            d="M20.0651 7.39423L7.09967 20.4114C6.72438 20.7882 6.21446 21 5.68265 21H4.00383C3.44943 21 3 20.5466 3 19.9922V18.2987C3 17.7696 3.20962 17.2621 3.58297 16.8873L16.5517 3.86681C19.5632 1.34721 22.5747 4.87462 20.0651 7.39423Z"
+                                            stroke="#323232" stroke-width="2" stroke-linecap="round"
+                                            stroke-linejoin="round" />
+                                        <path d="M15.3096 5.30981L18.7273 8.72755" stroke="#323232" stroke-width="2"
+                                            stroke-linecap="round" stroke-linejoin="round" />
+                                        <path opacity="0.1"
+                                            d="M18.556 8.90942L7.09967 20.4114C6.72438 20.7882 6.21446 21 5.68265 21H4.00383C3.44943 21 3 20.5466 3 19.9922V18.2987C3 17.7696 3.20962 17.2621 3.58297 16.8873L15.0647 5.35974C15.0742 5.4062 15.0969 5.45049 15.1329 5.48653L18.5506 8.90426C18.5524 8.90601 18.5542 8.90773 18.556 8.90942Z"
+                                            fill="#323232" />
+                                    </svg>
+                                </div>
+
+                                <div onClick={() => handleDeletePost(post._id)}>
+                                    <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none"
+                                        xmlns="http://www.w3.org/2000/svg"
+                                        style={{ width: '20px', height: '20px' }}>
+                                        <path
+                                            d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z"
+                                            stroke="#292D32" stroke-width="1.5" stroke-linecap="round"
+                                            stroke-linejoin="round" />
+                                        <g opacity="0.4">
+                                            <path d="M9.16992 14.8299L14.8299 9.16992" stroke="#292D32"
+                                                stroke-width="1.5"
+                                                stroke-linecap="round" stroke-linejoin="round" />
+                                            <path d="M14.8299 14.8299L9.16992 9.16992" stroke="#292D32"
+                                                stroke-width="1.5"
+                                                stroke-linecap="round" stroke-linejoin="round" />
+                                        </g>
+                                    </svg>
+                                </div>
+                            </div>
+                        }
+                        <div style={{ marginTop: '20px' }}>
+                            {post.title ?
+                                <div>
+                                    <h5>{post.title}</h5>
+                                    <p>{post.text}</p></div>
+                                : <p>empty...</p>
+                            }
+                        </div>
+                    </PostImage>
+
+                ))}
+            </UserPagePosts>
+        </UserPage>
+    );
+};
+
+const FollowersModal = ({ followers, isFollowingModal, onCloseModal, handleFollowerClick }) => {
+    return (
+        <div className="modal">
+            <div className="modal_block" style={{ width: '500px' }}>
+                <h2>{isFollowingModal ? "Following" : "Followers"}</h2>
+                {followers.length > 0 ? (
+                    <div className="list_followers">
+                        {followers.map((follower) => (
+                            follower.login && (
+                                <div className="user" key={follower}>
+                                    <img onClick={() => handleFollowerClick(follower)}
+                                        src={`../${follower?.avatar?.url}`}
+                                        style={{ width: '40px', height: '40px' }} />
+                                    <h4 onClick={() => handleFollowerClick(follower)}>{follower?.login}</h4>
+                                    <button style={{
+                                        backgroundColor: isFollowingModal ? "#ccc" : "#007bff",
+                                        color: isFollowingModal ? "#000" : "#fff"
+                                    }}>
+                                        {isFollowingModal ? "unfollow" : "follow"}
+                                    </button>
+                                </div>
+                            )
+                        ))}
+                    </div>
+                ) : (
+                    <p>No {isFollowingModal ? "following" : "followers"} to show</p>
+                )}
+                <button className="close_followers_modal" onClick={onCloseModal}>
+                    x
+                </button>
+            </div>
+        </div>
+    );
+
+};
+
+export const UserProfileDraw = connect((state) => ({ state: state }), {
+    onProfile: actionPostById,
+})(UserProfile);

+ 289 - 0
hipstagram/src/components/Profile/postDEtails.js

@@ -0,0 +1,289 @@
+// import React from "react";
+//
+// import { Avatar } from "../Comments/comment.module";
+//
+// const PostDetails = ({ onCloseModall, post }) => {
+//
+//
+//     console.log(post)
+//
+//
+//     return (
+//         <div className="modal">
+//             <div className="modal_block">
+//                 <button className="close_btn" onClick={onCloseModall}>X</button>
+//                 <div className="post_details">
+//                     <div>
+//                         <img className="pd_img" src={`../${post?.images?.[0]?.url}`} />
+//                         <h4>{post?.title}</h4>
+//                         <p>{post?.text}</p>
+//                     </div>
+//                     <div>
+//                         <div style={{display:"flex"}}>
+//                             <Avatar
+//                                 src={
+//                                     post?.owner?.avatar?.url
+//                                         ? `../${post?.owner?.avatar?.url}`
+//                                         : "https://w7.pngwing.com/pngs/336/946/png-transparent-avatar-user-medicine-surgery-patient-avatar-face-heroes-head.png"
+//                                 }
+//                             />
+//                             <h4>{post?.owner?.login}</h4>
+//                         </div>
+//                         <h4 style={{ margin: 20 }}>Comments</h4>
+//                         <div>
+//                             {post?.comments?.map((comment) =>
+//                                 <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 14 }} key={comment}>
+//                                     <Avatar
+//                                         src={
+//                                             comment.owner?.avatar?.url
+//                                                 ? `../${comment.owner?.avatar?.url}`
+//                                                 : "https://w7.pngwing.com/pngs/336/946/png-transparent-avatar-user-medicine-surgery-patient-avatar-face-heroes-head.png"
+//                                         }
+//                                     />
+//                                     <span> {comment.text}</span>
+//                                 </div>
+//                             )}
+//
+//                         </div>
+//                         <h4 style={{ margin: 20 }}>Likes <span>{post?.likes?.length}</span> </h4>
+//
+//                     </div>
+//
+//                 </div>
+//             </div>
+//         </div>
+//     );
+// };
+//
+// export default PostDetails;
+//
+// import React from "react";
+// import { Avatar } from "../Comments/comment.module";
+// import "./PostDetails.css";
+// import {PostTime} from "../PostDraw/post-draw.module"; // Импортируйте CSS-файл для стилей
+//
+// const PostDetails = ({ onCloseModall, post }) => {
+//     console.log(post);
+//
+//     const timestamp = post?.createdAt;
+//     const date = new Date(+timestamp).toString().slice(0, 24);
+//
+//
+//     return (
+//         <div className="modal">
+//             <div className="modal_block">
+//                 <button className="close_btn" onClick={onCloseModall}>
+//                     X
+//                 </button>
+//                 <div className="post_details">
+//                     <div className="post_section">
+//                         <div className="post_content">
+//                             <img className="pd_img" src={`../${post?.images?.[0]?.url}`} />
+//                             <div className="post_info">
+//                                 <h5 className="likes_title" style={{marginBottom:'15px'}}>
+//                                     Likes <span className="likes_count">{post?.likes?.length}</span>
+//                                 </h5>
+//                                 <h4 className="post_title">{post?.title}</h4>
+//                                 <p className="post_text">{post?.text}</p>
+//                                 <div style={{marginTop:'20px', fontSize:'14px', color:'grey'}}>{date}</div>
+//                             </div>
+//                         </div>
+//                     </div>
+//                     <div className="user_section">
+//                         <div className="user_info">
+//                             <Avatar
+//                                 className="user_avatar"
+//                                 src={
+//                                     post?.owner?.avatar?.url
+//                                         ? `../${post?.owner?.avatar?.url}`
+//                                         : "https://w7.pngwing.com/pngs/336/946/png-transparent-avatar-user-medicine-surgery-patient-avatar-face-heroes-head.png"
+//                                 }
+//                             />
+//                             <h4 className="user_login">{post?.owner?.login}</h4>
+//                         </div>
+//                         <div className="comments_section">
+//                             <h4 className="comments_title">Comments</h4>
+//                             <div className="comment_list">
+//                                 {post?.comments?.map((comment) => (
+//                                     <div className="comment" key={comment}>
+//                                         <Avatar
+//                                             className="comment_avatar"
+//                                             src={
+//                                                 comment.owner?.avatar?.url
+//                                                     ? `../${comment.owner?.avatar?.url}`
+//                                                     : "https://w7.pngwing.com/pngs/336/946/png-transparent-avatar-user-medicine-surgery-patient-avatar-face-heroes-head.png"
+//                                             }
+//                                         />
+//                                         <span className="comment_text">{comment.text}</span>
+//                                     </div>
+//                                 ))}
+//                             </div>
+//                         </div>
+//                     </div>
+//                 </div>
+//             </div>
+//         </div>
+//     );
+// };
+//
+// export default PostDetails;
+
+
+import React, { useState } from "react";
+import { Avatar } from "../Comments/comment.module";
+import { CommentWrapper, IconComment, CommentBox, StyledButton } from "../PostDraw/post-draw.module";
+import "./PostDetails.css";
+import { PostTime, ReactionIcon } from "../PostDraw/post-draw.module";
+import { getOnePost } from "../../actions";
+import { gqlAddLike, gqlDeleteLike } from "../../actions/like";
+import Heart from "../../images/heart.png";
+import { actionSendComment } from "../../actions";
+import { Box } from "@mui/material";
+
+const PostDetails = ({ onCloseModall, post, currentUser, dispatch }) => {
+
+    const [likesAmount, setLikesAmount] = useState(post.likes?.length)
+    const [comment, setComment] = useState("");
+    const [comments, setComments] = useState(post.comments ?? []);
+
+
+
+    const clickLikeHandler = async (event, postId) => {
+        try {
+            const isLiked = post?.likes.some(
+                (like) => like?._id === currentUser._id
+            );
+            if (isLiked) {
+                event.currentTarget.src =
+                    "https://cdn-icons-png.flaticon.com/512/1077/1077035.png";
+                setLikesAmount((prevLikes) => prevLikes - 1);
+                await gqlDeleteLike(postId);
+                console.log('post unliked')
+            } else {
+                event.currentTarget.src = Heart;
+                setLikesAmount((prevLikes) => prevLikes + 1);
+                await gqlAddLike(postId);
+            }
+        } catch (error) {
+            console.log(error);
+        }
+    };
+
+
+    const timestamp = post?.createdAt;
+    const date = new Date(+timestamp).toString().slice(0, 24);
+
+
+    const clickCommentHandler = async (event, postId) => {
+        try {
+            actionSendComment(postId, comment)
+        } catch (e) {
+            console.error(e)
+        }
+        finally {
+            setComment('')
+        }
+    };
+
+
+    const handlePostComment = (event, postId) => {
+        if (comment?.length === 0) {
+            alert("Please enter a comment")
+        }
+        else {
+            const newComment = {
+                _id: Math.random(),
+                text: comment,
+                owner: { avatar: { url: currentUser?.avatar?.url } },
+            };
+            setComments((prevComments) => [...prevComments, newComment]);
+            return clickCommentHandler(event, postId)
+        }
+    };
+
+    return (
+        <div className="modal">
+            <div className="modal_block">
+                <button className="close_btn" onClick={onCloseModall}>
+                    X
+                </button>
+                <div className="post_details">
+                    <div className="post_section">
+                        <div className="post_content">
+                            <img className="pd_img" src={`../${post?.images?.[0]?.url}`} />
+                            <div className="post_info">
+                                {/*<h5 className="likes_title" style={{marginBottom:'15px'}}>*/}
+                                {/*    Likes <span className="likes_count">{post?.likes?.length}</span>*/}
+                                {/*</h5>*/}
+                                <div className="flex">
+                                    <ReactionIcon
+                                        src={
+                                            post.likes?.some(
+                                                (like) => like._id === currentUser._id
+                                            )
+                                                ? Heart
+                                                : "https://cdn-icons-png.flaticon.com/512/1077/1077035.png"
+                                        }
+                                        alt="like-icon"
+                                        onClick={(event) => clickLikeHandler(event, post._id)}
+                                    />
+                                    <Box style={{ marginRight: "10px" }}>{likesAmount}</Box>
+                                </div>
+
+                                <h4 className="post_title">{post?.title}</h4>
+                                <p className="post_text">{post?.text}</p>
+                                <div style={{ marginTop: '20px', fontSize: '14px', color: 'grey' }}>{date}</div>
+                            </div>
+                        </div>
+                    </div>
+                    <div className="user_section">
+                        <div className="user_info">
+                            <Avatar
+                                className="user_avatar"
+                                src={
+                                    post?.owner?.avatar?.url
+                                        ? `../${post?.owner?.avatar?.url}`
+                                        : "https://w7.pngwing.com/pngs/336/946/png-transparent-avatar-user-medicine-surgery-patient-avatar-face-heroes-head.png"
+                                }
+                            />
+                            <h4 className="user_login">{post?.owner?.login}</h4>
+                        </div>
+                        <div className="comments_section">
+                            <div className="comment_list">
+                                <h4 className="comments_title">Comments</h4>
+                                {comments?.map((comment) => (
+                                    <div className="comment" key={comment}>
+                                        <Avatar
+                                            className="comment_avatar"
+                                            src={
+                                                comment.owner?.avatar?.url
+                                                    ? `../${comment.owner?.avatar?.url}`
+                                                    : "https://w7.pngwing.com/pngs/336/946/png-transparent-avatar-user-medicine-surgery-patient-avatar-face-heroes-head.png"
+                                            }
+                                        />
+                                        <span className="comment_text">{comment.text}</span>
+                                    </div>
+                                ))}
+                            </div>
+                            <CommentWrapper className="comment-wrapper">
+                                <IconComment src="https://cdn-icons-png.flaticon.com/512/747/747402.png" />
+                                <CommentBox
+                                    placeholder="Add a comment"
+                                    value={comment}
+                                    onChange={(event) => setComment(event.target.value)}
+                                    inputProps={{ style: { backgroundColor: "transparent" } }}
+                                />
+                                <StyledButton onClick={(event) => handlePostComment(event, post._id)}>
+                                    post
+                                </StyledButton>
+                            </CommentWrapper>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default PostDetails;
+

+ 59 - 0
hipstagram/src/components/Profile/profile.module.js

@@ -0,0 +1,59 @@
+import styled from "styled-components";
+import { Box  } from "@mui/material";
+
+export const UserPage = styled(Box)`
+  width: 90%;
+  margin: 80px auto;
+`;
+
+
+export const StyledUserProfile = styled(Box)`
+  display: flex;
+  width: 70%;
+  margin: 0 auto;
+`;
+
+export const ProfileAvatar = styled(Box)`
+  & img {
+    border-radius: 50%;
+    width: 300px;
+    height: 300px;
+  }
+`;
+
+export const UserProfileDescription = styled(Box)`
+  display: flex;
+  flex-direction: column;
+  margin: 0 auto;
+`;
+
+export const ProfileName = styled(Box)`
+  margin: 0 auto;
+`;
+
+export const ProfileInfo = styled(Box)`
+  display: flex;
+  cursor: pointer;
+
+  & p {
+    margin: 20px;
+  }
+`;
+
+export const UserPagePosts = styled(Box)`
+  display: flex;
+  flex-wrap: wrap;
+  border-top: 1px solid #b8b4b4;
+  margin-top: 30px;
+
+  & img {
+    width: 300px;
+    height: 300px;
+  }
+`;
+
+export const PostImage = styled(Box)`
+  width: 350px;
+  height: 350px;
+  margin: 20px;
+`;

+ 91 - 0
hipstagram/src/components/RegistrForm/index.js

@@ -0,0 +1,91 @@
+import React, { useState } from "react";
+import { connect } from "react-redux";
+import { actionFullRegister } from "../../actions";
+import { LoginPage, StyledLoginForm } from "../LoginForm/login-page.module";
+import { TextField, Button, Link, Box } from "@mui/material";
+
+const RegistrForm = ({ onReg }) => {
+  const [login, setLogin] = useState("");
+  const [password, setPassword] = useState("");
+  const [passwordRepeat, setPasswordRepeat] = useState("");
+  return (
+    <LoginPage>
+      <StyledLoginForm>
+        <TextField
+          sx={{ marginBottom: "5px" }}
+          type="text"
+          placeholder="Enter login"
+          value={login}
+          onChange={(e) => setLogin(e.target.value)}
+        />
+        <TextField
+          sx={{ marginBottom: "5px" }}
+          type="password"
+          placeholder="Password"
+          value={password}
+          onChange={(e) => setPassword(e.target.value)}
+        />
+        <TextField
+          sx={{ marginBottom: "5px" }}
+          type="password"
+          placeholder="Repeat password"
+          value={password}
+          onChange={(e) => setPasswordRepeat(e.target.value)}
+        />
+        <Button
+          sx={{ marginBottom: "5px" }}
+          variant="contained"
+          disabled={!login || !password}
+          onClick={() => onReg(login, password)}
+        >
+          Login
+        </Button>
+        <Box>
+          Есть аккаунт?
+          <Link href="/log">Вход</Link>
+        </Box>
+      </StyledLoginForm>
+    </LoginPage>
+  );
+  return (
+    <div className="login-page">
+      <div className="login-form">
+        <input
+          class="login"
+          type="login"
+          placeholder="Enter login"
+          value={login}
+          onChange={(e) => setLogin(e.target.value)}
+        />
+        <input
+          class="password"
+          type="password"
+          placeholder="Password"
+          value={password}
+          onChange={(e) => setPassword(e.target.value)}
+        />
+        <input
+          class="repeatpassword"
+          type="password"
+          placeholder="Password"
+          value={passwordRepeat}
+          onChange={(e) => setPasswordRepeat(e.target.value)}
+        />
+        <button
+          class="btn"
+          disabled={!login || !password || passwordRepeat !== password}
+          onClick={() => onReg(login, password)}
+        >
+          Submit
+        </button>
+        <p>
+          Есть аккаунт?<a href="/log">Вход</a>
+        </p>
+      </div>
+    </div>
+  );
+};
+
+export const ConnectRegistrForm = connect(null, { onReg: actionFullRegister })(
+  RegistrForm
+);

+ 44 - 0
hipstagram/src/components/Route.js

@@ -0,0 +1,44 @@
+import {connect} from 'react-redux';
+import {BrowserRouter as Router, Route, Switch, Redirect} from 'react-router-dom';
+import createHistory from "history/createBrowserHistory";
+import { ConnectLoginForm } from './LoginForm';
+import { ConnectRegistrForm } from './RegistrForm';
+import { ConnectUsersDraw } from './UserDraw'
+import  { ConnectPosts } from './Post'
+import { ConnectUploadForm } from './UploadForm';
+import { UserProfileDraw } from './Profile';
+import { ConnectNavBar } from './Header'
+
+function Routs({isLoggedIn}) {
+    return (
+        <>
+          <Router history={createHistory()}>
+          { !isLoggedIn && 
+            <Switch>
+              <Route path='/login' component={ConnectLoginForm}/>
+              <Route path="/reg" component={ConnectRegistrForm}/>
+              <Route path='/*'>
+                  <Redirect to="/login"/>
+              </Route>
+              </Switch>
+            }
+            { isLoggedIn &&
+            <>
+              <ConnectNavBar/>
+              <Switch>
+                <Route path='/login'>
+                    <Redirect to="/"/>
+                </Route>
+                <Route exact path="/" component={ConnectPosts}/>
+                <Route path="/input" component={ConnectUploadForm}/>
+                <Route path = {`/user/:id`} component={UserProfileDraw}/>
+                <Route path="/users" component={ConnectUsersDraw}/>
+              </Switch>
+            </>
+          }
+            </Router>
+        </>
+    )
+  }
+
+  export const ConnectedRouts = connect(state => ({isLoggedIn: state?.auth.token}), null)(Routs)

+ 156 - 0
hipstagram/src/components/Search/index.js

@@ -0,0 +1,156 @@
+import React from "react";
+import { SearchBox } from "./search.module";
+import { useState, useEffect } from "react";
+import { actionGetUsers } from "../../actions";
+import { useDispatch } from "react-redux";
+import { Link } from "react-router-dom/cjs/react-router-dom.min";
+
+//
+// const SearchBoxCommponent = () => {
+//
+//     const [Users, setUsers] = useState([])
+//     const [searchQuery, setSearchQuery] = useState("");
+//     const dispatch = useDispatch();
+//     const [show, setShow] = useState(false)
+//     const [loading, setLoading] = useState(false)
+//
+//
+//     useEffect(() => {
+//         setLoading(true);
+//         async function getUsersData() {
+//             try {
+//                 const response = await actionGetUsers()(dispatch);
+//                 setUsers(response.data.UserFind);
+//             } catch (error) {
+//                 console.log(error);
+//             } finally {
+//                 setLoading(false);
+//             }
+//         }
+//         getUsersData();
+//     }, [dispatch]);
+//
+//
+//
+//
+//
+//     const openSearchBlock = async () => {
+//         setShow(true)
+//     }
+//
+//     const closeSearch = () => {
+//         setShow(false)
+//     }
+//
+//     const handleSearchQueryChange = (e) => {
+//         const query = e.target.value;
+//         setSearchQuery(query);
+//     };
+//     const filteredUsers = searchQuery && Users?.filter((user) =>
+//     user.login?.toLowerCase().includes(searchQuery.toLowerCase())
+//   );
+//
+//     return (
+//         <div>
+//             {show && <div onClick={closeSearch} className="serachBG"></div>}
+//             <div style={{ display: "flex", flexDirection: "column", position: "relative" }}>
+//                 <SearchBox onClick={openSearchBlock}
+//                     value={searchQuery}
+//                     onChange={handleSearchQueryChange}
+//                 />
+//                 {show && <div className="SerachBoxPopup">
+//                 {loading ? (
+//               <h1>Loading..</h1>
+//             ) : filteredUsers.length  ? (
+//               filteredUsers.map((user) => (
+//                 <Link  onClick={closeSearch} to={`/user/${user._id}`} style={{ display:"flex",gap:12, alignItems:"center"}} className="serach_link_user"  key={user._id}>
+//                { user?.avatar?.url  ?  <img style={{ width:50, height:50, borderRadius:"50%"}} src={`../${user?.avatar?.url}`} />
+//                 : <img style={{ width:50, height:50, borderRadius:"50%"}} src='https://png.pngitem.com/pimgs/s/30-307416_profile-icon-png-image-free-download-searchpng-employee.png' />
+//               }
+//                   <span>{user.login}</span>
+//                 </Link>
+//               ))
+//             ) : (
+//               <div>No users found</div>
+//             )}
+//                 </div>}
+//             </div>
+//         </div>
+//     )
+// }
+//
+//
+// export default SearchBoxCommponent
+
+const SearchBoxComponent = () => {
+    const [users, setUsers] = useState([]);
+    const [searchQuery, setSearchQuery] = useState("");
+    const [show, setShow] = useState(false);
+    const [loading, setLoading] = useState(false);
+    const dispatch = useDispatch();
+
+    useEffect(() => {
+        setLoading(true);
+
+        async function getUsersData() {
+            try {
+                const response = await actionGetUsers()(dispatch);
+                setUsers(response.data.UserFind);
+            } catch (error) {
+                console.log(error);
+            } finally {
+                setLoading(false);
+            }
+        }
+
+        getUsersData();
+    }, [dispatch]);
+
+    const openSearchBlock = () => {
+        setShow(true);
+    };
+
+    const closeSearch = () => {
+        setShow(false);
+    };
+
+    const handleSearchQueryChange = (e) => {
+        const query = e.target.value;
+        setSearchQuery(query);
+    };
+
+    const filteredUsers = searchQuery && users?.filter((user) =>
+        user.login?.toLowerCase().includes(searchQuery.toLowerCase())
+    );
+
+    return (
+        <>
+            {show && <div onClick={closeSearch} className="serachBG"></div>}
+            <div style={{ display: "flex", flexDirection: "column", position: "relative" }}>
+                <SearchBox onClick={openSearchBlock} value={searchQuery} onChange={handleSearchQueryChange} />
+                {show && (
+                    <div className="SerachBoxPopup">
+                        {loading ? (
+                            <h1>Loading..</h1>
+                        ) : filteredUsers.length ? (
+                            filteredUsers.map((user) => (
+                                <Link onClick={closeSearch} to={`/user/${user._id}`} style={{ display: "flex", gap: 12, alignItems: "center" }} className="serach_link_user" key={user._id}>
+                                    {user?.avatar?.url ? (
+                                        <img style={{ width: 50, height: 50, borderRadius: "50%" }} src={`../${user?.avatar?.url}`} />
+                                    ) : (
+                                        <img style={{ width: 50, height: 50, borderRadius: "50%" }} src="https://png.pngitem.com/pimgs/s/30-307416_profile-icon-png-image-free-download-searchpng-employee.png" />
+                                    )}
+                                    <span>{user.login}</span>
+                                </Link>
+                            ))
+                        ) : (
+                            <div>No users found</div>
+                        )}
+                    </div>
+                )}
+            </div>
+        </>
+    );
+};
+
+export default SearchBoxComponent;

+ 29 - 0
hipstagram/src/components/Search/search.module.js

@@ -0,0 +1,29 @@
+import {  TextField } from "@mui/material";
+import styled from "styled-components";
+
+
+export const SearchBox = styled((props) => (
+  <TextField {...props} placeholder="search..." />
+))`
+  background: white;
+  & label.Mui-focused {
+    color: white;
+  }
+  & .MuiInput-underline:after {
+    border-bottom-color: white;
+  }
+  & .MuiOutlinedInput-root {
+    & fieldset {
+      border-color: black;
+    }
+    &.Mui-focused fieldset {
+      border-color: black;
+    }
+    & input {
+      padding: 5px;
+    }
+  }
+`;
+
+
+

+ 225 - 0
hipstagram/src/components/UploadForm/index.js

@@ -0,0 +1,225 @@
+import React, { useState, useEffect } from "react";
+import { useDropzone } from "react-dropzone";
+import { actionUploadPost } from "../../actions";
+import { useHistory } from "react-router-dom";
+import { connect } from "react-redux";
+import { Box, Button, TextField } from "@mui/material";
+import { PostInput, PostInputForm } from "./upload-form.module";
+//
+// const reorder = (list, startIndex, endIndex) => {
+//     if (startIndex > endIndex) {
+//     [startIndex, endIndex] = [endIndex, startIndex];
+//   }
+//   const result = Array.from(list);
+//   const [removed] = result.splice(startIndex, 1);
+//   result.splice(endIndex, 0, removed);
+//
+//   return result;
+// };
+//
+// const UploadForm = ({ onUpload, isUpload }) => {
+//
+//   const [acceptedFiles, setAcceptedFiles] = useState([]);
+//   const { getRootProps, getInputProps } = useDropzone({
+//     onDrop: (acceptedFiles) => {
+//       setAcceptedFiles(acceptedFiles);
+//     },
+//   });
+//   const [title, setTitle] = useState("");
+//   const [text, setText] = useState("");
+//   const [currentDragItem, setCurrentDragItem] = useState();
+//
+//   const history = useHistory();
+//   isUpload && history.push("/");
+//
+//   const drag = (event) => {
+//     setCurrentDragItem(event.target);
+//   };
+//
+//   function allowDrop(event) {
+//     event.preventDefault();
+//   }
+//
+//   function drop(event) {
+//     const items = reorder(acceptedFiles, currentDragItem.id, event.target.id);
+//
+//     setAcceptedFiles(items);
+//   }
+//
+//   return (
+//     <PostInput className="post-input">
+//       <Box {...getRootProps({ className: "dropzone" })}>
+//         <input {...getInputProps()} multiple />
+//         <Box
+//           component="img"
+//           src="https://cdn-icons-png.flaticon.com/512/1829/1829415.png"
+//           alt="img-icon"
+//         />
+//         <Box component="p">Переместите изображение для загрузки</Box>
+//       </Box>
+//       <Box
+//         style={{
+//           textAlign: "center",
+//           border: "1px solid black",
+//           width: "50%",
+//           minHeight: "100px",
+//           margin: "10px auto",
+//         }}
+//         onDrop={(event) => drop(event)}
+//         onDragOver={(event) => allowDrop(event)}
+//       >
+//         {acceptedFiles.map((file, index) => (
+//           <Box
+//             key={index}
+//             id={index}
+//             component="img"
+//             src={URL.createObjectURL(file)}
+//             alt="preview"
+//             draggable="true"
+//             onDragStart={drag}
+//             style={{
+//               width: 100,
+//               height: 100,
+//               marginRight: 10,
+//               objectFit: "cover",
+//             }}
+//           />
+//         ))}
+//       </Box>
+//       <PostInputForm className="post-input-form">
+//         <TextField
+//           type="text"
+//           placeholder="Title"
+//           value={title}
+//           onChange={(e) => setTitle(e.target.value)}
+//         />
+//         <TextField
+//           type="text"
+//           placeholder="Text"
+//           value={text}
+//           onChange={(e) => setText(e.target.value)}
+//         />
+//         <Button onClick={() => onUpload(text, title, acceptedFiles[0])}>
+//           Запостить
+//         </Button>
+//       </PostInputForm>
+//     </PostInput>
+//   );
+// };
+//
+// export const ConnectUploadForm = connect(
+//   (state) => ({isUpload: state?.promise?.InsertPosts?.payload?.data?.PostUpsert,}),
+//   { onUpload: actionUploadPost }
+// )(UploadForm);
+
+const reorder = (list, startIndex, endIndex) => {
+    if (startIndex > endIndex) {
+        [startIndex, endIndex] = [endIndex, startIndex];
+    }
+
+    const result = Array.from(list);
+    const [removed] = result.splice(startIndex, 1);
+    result.splice(endIndex, 0, removed);
+
+    return result;
+};
+
+const UploadForm = ({ onUpload, isUpload }) => {
+    const [acceptedFiles, setAcceptedFiles] = useState([]);
+    const [title, setTitle] = useState("");
+    const [text, setText] = useState("");
+    const [currentDragItem, setCurrentDragItem] = useState();
+
+    const history = useHistory();
+    useEffect(() => {
+        if (isUpload) {
+            history.push("/");
+        }
+    }, [isUpload, history]);
+
+    const { getRootProps, getInputProps } = useDropzone({
+        onDrop: (acceptedFiles) => {
+            setAcceptedFiles(acceptedFiles);
+        },
+    });
+
+    const handleDragStart = (event, index) => {
+        setCurrentDragItem(index);
+    };
+
+    const handleDragOver = (event) => {
+        event.preventDefault();
+    };
+
+    const handleDrop = (event, index) => {
+        const items = reorder(acceptedFiles, currentDragItem, index);
+        setAcceptedFiles(items);
+    };
+
+    return (
+        <PostInput className="post-input">
+            <Box {...getRootProps({ className: "dropzone" })}>
+                <input {...getInputProps()} multiple />
+                <Box
+                    component="img"
+                    src="https://cdn-icons-png.flaticon.com/512/1829/1829415.png"
+                    alt="img-icon"
+                />
+                <Box component="p">Переместите изображение для загрузки</Box>
+            </Box>
+            <Box
+                style={{
+                    textAlign: "center",
+                    border: "1px solid black",
+                    width: "50%",
+                    minHeight: "100px",
+                    margin: "10px auto",
+                }}
+                onDragOver={handleDragOver}
+                onDrop={handleDrop}
+            >
+                {acceptedFiles.map((file, index) => (
+                    <Box
+                        key={index}
+                        id={index}
+                        component="img"
+                        src={URL.createObjectURL(file)}
+                        alt="preview"
+                        draggable="true"
+                        onDragStart={(event) => handleDragStart(event, index)}
+                        style={{
+                            width: 100,
+                            height: 100,
+                            marginRight: 10,
+                            objectFit: "cover",
+                        }}
+                    />
+                ))}
+            </Box>
+            <PostInputForm className="post-input-form">
+                <TextField
+                    type="text"
+                    placeholder="Title"
+                    value={title}
+                    onChange={(e) => setTitle(e.target.value)}
+                />
+                <TextField
+                    type="text"
+                    placeholder="Text"
+                    value={text}
+                    onChange={(e) => setText(e.target.value)}
+                />
+                <Button onClick={() => onUpload(text, title, acceptedFiles[0])}>
+                    Запостить
+                </Button>
+            </PostInputForm>
+        </PostInput>
+    );
+};
+
+export const ConnectUploadForm = connect(
+    (state) => ({
+        isUpload: state?.promise?.InsertPosts?.payload?.data?.PostUpsert,
+    }),
+    { onUpload: actionUploadPost }
+)(UploadForm);

+ 105 - 0
hipstagram/src/components/UploadForm/upload-form.module.js

@@ -0,0 +1,105 @@
+import styled from "styled-components";
+import { Box } from "@mui/material";
+
+// .user-profile .profile-avatar-upload img {
+//   border-radius: 50%;
+//   width: 150px;
+//   height: 150px;
+// }
+
+// .user-profile .profile-avatar-upload .photo-input {
+//   display: flex;
+//   flex-direction: column;
+// }
+
+// .user-profile .profile-avatar-upload .photo-input button {
+//   cursor: pointer;
+//   margin: 0px auto;
+//   width: 150px;
+//   height: 40px;
+//   background-color: rgba(0, 149, 246, 1);
+//   border: 1px solid #dfdfdf;
+//   border-radius: 2px;
+//   color: #fff;
+//   text-align: center;
+//   text-transform: capitalize;
+// }
+
+export const PostInput = styled(Box)`
+  margin: 100px auto;
+  width: 50%;
+  display: flex;
+  flex-direction: column;
+`;
+
+export const PostInputForm = styled(Box)`
+  justify-content: center;
+  width: 500px;
+  display: flex;
+  margin: auto;
+  flex-direction: column;
+  & .MuiFormControl-root {
+    padding: 0;
+    margin: 0px auto;
+    width: 400px;
+    height: 30px;
+    background: #fafafa;
+    border-radius: 2px;
+    color: rgba(0, 0, 0, 0.5);
+
+    text-transform: capitalize;
+  }
+
+  & input {
+    text-align: center;
+    padding: 0;
+    border: none;
+    outline: none;
+  }
+
+  & button {
+    cursor: pointer;
+    margin: 5px auto;
+    width: 400px;
+    height: 40px;
+    background-color: rgba(0, 149, 246, 1);
+    border: 1px solid #dfdfdf;
+    border-radius: 2px;
+    color: #fff;
+    text-align: center;
+    text-transform: capitalize;
+  }
+`;
+
+// .post-input-form {
+//   justify-content: center;
+//   width: 500px;
+//   display: flex;
+//   margin: auto;
+//   flex-direction: column;
+// }
+
+// .post-input-form input {
+//   margin: 5px auto;
+//   width: 400px;
+//   height: 30px;
+//   background: #fafafa;
+//   border: 1px solid #dfdfdf;
+//   border-radius: 2px;
+//   color: rgba(0, 0, 0, 0.5);
+//   text-align: center;
+//   text-transform: capitalize;
+// }
+
+// .post-input-form button {
+//   cursor: pointer;
+//   margin: 5px auto;
+//   width: 400px;
+//   height: 40px;
+//   background-color: rgba(0, 149, 246, 1);
+//   border: 1px solid #dfdfdf;
+//   border-radius: 2px;
+//   color: #fff;
+//   text-align: center;
+//   text-transform: capitalize;
+// }

+ 60 - 0
hipstagram/src/components/UploadPhoto/index.js

@@ -0,0 +1,60 @@
+import React, { useState } from "react";
+import { useDropzone } from "react-dropzone";
+import { Box, Button } from "@mui/material";
+import { actionUploadPhoto } from "../../actions";
+import { connect } from "react-redux";
+
+const UploadForm = ({ onUpload, state }) => {
+  const [file, setFile] = useState(null);
+
+  const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
+    accept: "image/*",
+    multiple: false,
+    onDrop: (acceptedFiles) => {
+      setFile(acceptedFiles[0]);
+    },
+  });
+
+  let userId = state?.auth.payload?.id;
+
+  const isButtonDisabled = !file;
+
+  return (
+    <Box className="photo-input">
+      <Box
+        sx={{
+          border: "2px dashed gray",
+          borderRadius: "50%",
+          display: "flex",
+          flexDirection: "column",
+          alignItems: "center",
+          justifyContent: "center",
+          width: "fit-content",
+          height: "fit-content",
+          padding: "20px",
+          cursor: "pointer",
+        }}
+        {...getRootProps({ className: "dropzone" })}
+      >
+        <input {...getInputProps()} />
+        {file ? (
+          <Box component="img" src={URL.createObjectURL(file)} alt="img-icon" />
+        ) : (
+          <Box component="img" src="https://cdn-icons-png.flaticon.com/512/1829/1829415.png" alt="img-icon" />
+        )}
+      </Box>
+      {file ? (
+        <Button disabled={isButtonDisabled} onClick={() => onUpload(file, userId)}>
+          Запостить
+        </Button>
+      ) : (
+        <Box sx={{color: "red", textAlign: "center", marginBottom: "12px"}}>Выберите фото</Box>
+      )}
+    </Box>
+  );
+};
+
+export const ConnectUploadPhotoForm = connect(
+  (state) => ({ state: state }),
+  { onUpload: actionUploadPhoto }
+)(UploadForm);

+ 70 - 0
hipstagram/src/components/UserDraw/index.js

@@ -0,0 +1,70 @@
+import { connect } from "react-redux";
+import { useEffect, useState } from "react";
+import { Box, Link } from "@mui/material";
+import { Users, StyledUser } from "./user-draw.module";
+import { actionGetUsers } from "../../actions";
+
+const User = ({ getUsers }) => {
+  const [skip, setSkip] = useState(0);
+  const [users, setUsers] = useState([]);
+
+  const scrollHandler = async () => {
+    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
+    if (scrollTop + clientHeight >= scrollHeight) {
+      const newUsers = (await getUsers(skip + 20)).data.UserFind;
+      if (skip < newUsers.length + 20) {
+        setUsers((prevUsers) => prevUsers.concat(newUsers.slice(0, 20)));
+        setSkip((prevSkip) => prevSkip + 20);
+      }
+    }
+  };
+
+  useEffect(() => {
+    const fetchData = async () => {
+      const usersData = (await getUsers()).data.UserFind;
+      setUsers(usersData.slice(0, 20));
+      document.addEventListener("scroll", scrollHandler);
+    };
+
+    fetchData();
+
+    return () => {
+      document.removeEventListener("scroll", scrollHandler);
+    };
+  }, []);
+
+  return (
+      <Box component="main">
+        <Users className="users">
+          {users.map((user) => (
+              <StyledUser key={user._id} className="user">
+                <Box className="profile-pic">
+                  <Link href={`user/${user?._id}`}>
+                    <Box
+                        component="img"
+                        src={
+                          (user.avatar?.url)
+                              ? user.avatar?.url  : "https://w7.pngwing.com/pngs/336/946/png-transparent-avatar-user-medicine-surgery-patient-avatar-face-heroes-head.png"
+                        }
+                        alt="profile-avatar"
+                    />
+                  </Link>
+                </Box>
+                <Link href={`user/${user._id}`}>
+                  <Box component="p" className="username">
+                    {user?.login}
+                  </Box>
+                </Link>
+              </StyledUser>
+          ))}
+        </Users>
+      </Box>
+  );
+};
+
+export const ConnectUsersDraw = connect(
+    (state) => ({
+      users: state?.promise?.allUsers?.payload?.data?.UserFind,
+    }),
+    { getUsers: actionGetUsers }
+)(User);

+ 39 - 0
hipstagram/src/components/UserDraw/user-draw.module.js

@@ -0,0 +1,39 @@
+import styled from "styled-components";
+import { Box } from "@mui/material";
+
+export const Users = styled(Box)`
+  display: flex;
+  flex-wrap: wrap;
+  margin: 120px auto;
+  width: 55%;
+`;
+
+export const StyledUser = styled(Box)`
+  border-radius: 50%;
+  width: 200px;
+  height: 200px;
+  margin: 20px;
+  text-align: center;
+
+  & .profile-pic img {
+    border-radius: 50%;
+    width: 100px;
+    height: 100px;
+  }
+`;
+
+// .users .user .profile-pic img {
+//   border-radius: 50%;
+//   width: 100px;
+//   height: 100px;
+// }
+// .users .user .username {
+//   color: #262626;
+//   font-weight: 600;
+//   padding-top: 2px;
+//   outline: none;
+// }
+
+// .users .user a {
+//   text-decoration: none;
+// }

+ 0 - 0
hipstagram/src/images/envelope.png


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff