Jelajahi Sumber

added settings

Ata 3 tahun lalu
induk
melakukan
8f04624807
54 mengubah file dengan 1328 tambahan dan 126 penghapusan
  1. 1 1
      final-modul-react/.eslintcache
  2. 86 0
      final-modul-react/package-lock.json
  3. 2 0
      final-modul-react/package.json
  4. TEMPAT SAMPAH
      final-modul-react/public/img/cat-avatar.jpg
  5. 19 7
      final-modul-react/src/App.js
  6. 4 0
      final-modul-react/src/api/auth.js
  7. 13 0
      final-modul-react/src/api/users.js
  8. 0 24
      final-modul-react/src/components/Input/Input.jsx
  9. 12 2
      final-modul-react/src/components/Input/index.js
  10. 18 0
      final-modul-react/src/components/Input/styled.js
  11. 11 0
      final-modul-react/src/constants/users.js
  12. 0 2
      final-modul-react/src/container/Auth/index.js
  13. 0 32
      final-modul-react/src/container/Header/Header.jsx
  14. 0 2
      final-modul-react/src/container/Header/index.js
  15. 101 0
      final-modul-react/src/container/Header/index.jsx
  16. 0 18
      final-modul-react/src/container/Header/style.css
  17. 43 0
      final-modul-react/src/container/Header/styled.js
  18. 0 2
      final-modul-react/src/container/HomePage/index.js
  19. 0 16
      final-modul-react/src/container/Search/Search.jsx
  20. 0 2
      final-modul-react/src/container/Search/index.js
  21. 0 7
      final-modul-react/src/container/Search/style.css
  22. 125 0
      final-modul-react/src/container/forms/UserFormInfo/index.jsx
  23. 117 0
      final-modul-react/src/container/forms/UserFormInfo/styled.js
  24. 72 0
      final-modul-react/src/container/forms/UserFormPassword/index.jsx
  25. 67 0
      final-modul-react/src/container/forms/UserFormPassword/styled.js
  26. TEMPAT SAMPAH
      final-modul-react/src/img/cat-avatar.jpg
  27. TEMPAT SAMPAH
      final-modul-react/src/img/found-null.jpg
  28. TEMPAT SAMPAH
      final-modul-react/src/img/user.png
  29. 3 3
      final-modul-react/src/container/Login/Login.jsx
  30. 0 0
      final-modul-react/src/pages/Auth/Login/index.js
  31. 0 0
      final-modul-react/src/pages/Auth/Login/style.css
  32. 3 3
      final-modul-react/src/container/Registration/Registration.jsx
  33. 0 0
      final-modul-react/src/pages/Auth/Registration/index.js
  34. 0 0
      final-modul-react/src/pages/Auth/Registration/style.css
  35. 1 1
      final-modul-react/src/container/Auth/Auth.jsx
  36. 0 0
      final-modul-react/src/pages/Auth/style.css
  37. 86 0
      final-modul-react/src/pages/Main/containers/CurrentUser/CurrentUserProfile/index.jsx
  38. 13 0
      final-modul-react/src/pages/Main/containers/CurrentUser/CurrentUserProfile/styled.js
  39. 57 0
      final-modul-react/src/pages/Main/containers/CurrentUser/CurrentUserSettings/index.jsx
  40. 80 0
      final-modul-react/src/pages/Main/containers/CurrentUser/CurrentUserSettings/styled.js
  41. 49 0
      final-modul-react/src/pages/Main/containers/Users/FoundUsers/index.jsx
  42. 33 0
      final-modul-react/src/pages/Main/containers/Users/FoundUsers/styled.js
  43. 89 0
      final-modul-react/src/pages/Main/containers/Users/UserProfile/index.jsx
  44. 59 0
      final-modul-react/src/pages/Main/containers/Users/UserProfile/styled.js
  45. 2 2
      final-modul-react/src/container/HomePage/HomePage.jsx
  46. 0 0
      final-modul-react/src/pages/Main/style.css
  47. 2 0
      final-modul-react/src/store/index.js
  48. 4 2
      final-modul-react/src/store/ui/reducer.js
  49. 5 0
      final-modul-react/src/store/users/actionTypes.js
  50. 12 0
      final-modul-react/src/store/users/actions.js
  51. 40 0
      final-modul-react/src/store/users/reducer.js
  52. 36 0
      final-modul-react/src/store/users/thunks.js
  53. 25 0
      final-modul-react/src/utils/useDebounce.jsx
  54. 38 0
      final-modul-react/src/utils/validation.js

File diff ditekan karena terlalu besar
+ 1 - 1
final-modul-react/.eslintcache


+ 86 - 0
final-modul-react/package-lock.json

@@ -1136,6 +1136,29 @@
       "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
       "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
     },
+    "@emotion/is-prop-valid": {
+      "version": "0.8.8",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+      "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+      "requires": {
+        "@emotion/memoize": "0.7.4"
+      }
+    },
+    "@emotion/memoize": {
+      "version": "0.7.4",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
+      "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
+    },
+    "@emotion/stylis": {
+      "version": "0.8.5",
+      "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+      "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
+    },
+    "@emotion/unitless": {
+      "version": "0.7.5",
+      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+      "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+    },
     "@eslint/eslintrc": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
@@ -3197,6 +3220,22 @@
       "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz",
       "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw=="
     },
+    "babel-plugin-styled-components": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz",
+      "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==",
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.0.0",
+        "@babel/helper-module-imports": "^7.0.0",
+        "babel-plugin-syntax-jsx": "^6.18.0",
+        "lodash": "^4.17.11"
+      }
+    },
+    "babel-plugin-syntax-jsx": {
+      "version": "6.18.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+      "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
+    },
     "babel-plugin-syntax-object-rest-spread": {
       "version": "6.13.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
@@ -3844,6 +3883,11 @@
       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
       "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg=="
     },
+    "camelize": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+      "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+    },
     "caniuse-api": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -4421,6 +4465,11 @@
         "postcss": "^7.0.5"
       }
     },
+    "css-color-keywords": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+      "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
+    },
     "css-color-names": {
       "version": "0.0.4",
       "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
@@ -4504,6 +4553,16 @@
       "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
       "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
     },
+    "css-to-react-native": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
+      "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
+      "requires": {
+        "camelize": "^1.0.0",
+        "css-color-keywords": "^1.0.0",
+        "postcss-value-parser": "^4.0.2"
+      }
+    },
     "css-tree": {
       "version": "1.0.0-alpha.37",
       "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -12326,6 +12385,11 @@
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz",
       "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw=="
     },
+    "react-hook-form": {
+      "version": "6.15.1",
+      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.15.1.tgz",
+      "integrity": "sha512-bL0LQuQ3OlM3JYfbacKtBPLOHhmgYz8Lj6ivMrvu2M6e1wnt4sbGRtPEPYCc/8z3WDbjrMwfAfLX92OsB65pFA=="
+    },
     "react-is": {
       "version": "16.13.1",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -13487,6 +13551,11 @@
         "kind-of": "^6.0.2"
       }
     },
+    "shallowequal": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+      "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+    },
     "shebang-command": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -14172,6 +14241,23 @@
         "schema-utils": "^2.7.0"
       }
     },
+    "styled-components": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.2.1.tgz",
+      "integrity": "sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==",
+      "requires": {
+        "@babel/helper-module-imports": "^7.0.0",
+        "@babel/traverse": "^7.4.5",
+        "@emotion/is-prop-valid": "^0.8.8",
+        "@emotion/stylis": "^0.8.4",
+        "@emotion/unitless": "^0.7.4",
+        "babel-plugin-styled-components": ">= 1",
+        "css-to-react-native": "^3.0.0",
+        "hoist-non-react-statics": "^3.0.0",
+        "shallowequal": "^1.1.0",
+        "supports-color": "^5.5.0"
+      }
+    },
     "stylehacks": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz",

+ 2 - 0
final-modul-react/package.json

@@ -9,12 +9,14 @@
     "axios": "^0.21.1",
     "react": "^17.0.1",
     "react-dom": "^17.0.1",
+    "react-hook-form": "^6.15.1",
     "react-redux": "7.2.2",
     "react-router-dom": "^5.2.0",
     "react-scripts": "4.0.1",
     "redux": "4.0.5",
     "redux-devtools-extension": "^2.13.8",
     "redux-thunk": "^2.3.0",
+    "styled-components": "^5.2.1",
     "web-vitals": "^0.2.4"
   },
   "scripts": {

TEMPAT SAMPAH
final-modul-react/public/img/cat-avatar.jpg


+ 19 - 7
final-modul-react/src/App.js

@@ -1,11 +1,15 @@
 import './App.css';
 import store from './store';
 import {Provider} from 'react-redux';
-import HomePage from './container/HomePage';
+import HomePage from './pages/Main';
 import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom';
-import Auth from './container/Auth/Auth';
-import Login from './container/Login';
-import Registration from './container/Registration';
+import Auth from './pages/Auth';
+import Login from './pages/Auth/Login';
+import Registration from './pages/Auth/Registration';
+import FoundUsers from './pages/Main/containers/Users/FoundUsers';
+import UserProfile from './pages/Main/containers/Users/UserProfile';
+import CurrentUserProfile from './pages/Main/containers/CurrentUser/CurrentUserProfile/index.jsx';
+import CurrentUserSettings from './pages/Main/containers/CurrentUser/CurrentUserSettings';
 
 
 const App = () => {
@@ -16,13 +20,21 @@ const App = () => {
     <Router>
    
 
-    
+    <Switch>
             <Route exact path='/' component={HomePage}/>
             <Route exact path='/auth' component={Auth}/> 
             <Route exact path='/auth/login' component={Login}/> 
-            <Route exact path='/auth/registration' component={Registration}/> 
+            <Route exact path='/auth/registration' component={Registration}/>
+            <Route exact path='/users' component={FoundUsers}/>
+            <Route exact path='/users/current' component={ CurrentUserProfile }/>
+            <Route exact path='/users/current/settings' component={ CurrentUserSettings }/>
+            <Route exact path='/users/:userId' render={({match})=> {
+              const { userId } = match.params;
+         
+              return <UserProfile userId = {userId}/>
+            }}/>  
   
-   
+  </Switch>
     </Router>
     </Provider>
   );

+ 4 - 0
final-modul-react/src/api/auth.js

@@ -13,6 +13,10 @@ class Auth {
     login(body){
         return this.api.post('/auth/login', body);
     }
+
+    updatePassword(body){
+        return this.api.post('/auth/updatePassword', body);
+    }
 }
 
 export default new Auth();

+ 13 - 0
final-modul-react/src/api/users.js

@@ -14,6 +14,19 @@ class Users {
         return this.api.get('/users');
     }
 
+
+    getUsersByLogin(login){
+        return this.api.get('/users/?search='+login);
+    }
+
+    getUsersById(id){
+        return this.api.get('/users/'+id);
+    }
+
+    updateUsersInfo(body){
+        return this.api.patch('/users/current', body);
+    }
+
     deleteUser(id) {
         return this.api.delete('/users/'+id)
     }

+ 0 - 24
final-modul-react/src/components/Input/Input.jsx

@@ -1,24 +0,0 @@
-
-import React from "react";
-const Input =({
-  value,
-  name,
-  placeholder,
-  onChange,
-id,
-type
-
-}) => { 
-    
-      return (
-       
-     
-        <input value={value} name={name} id={id}  placeholder={ placeholder} type={type}
-                onChange={(e) => onChange && onChange(e)}
-         />
-    
-      );
-    }
-  
- 
-export default Input

+ 12 - 2
final-modul-react/src/components/Input/index.js

@@ -1,2 +1,12 @@
-import Input from './Input'
-export default Input;
+import {Input as InputWrapper, Label as LabelWrapper} from './styled'
+import React from "react";
+const Input = React.forwardRef(({label, error, ...props}, ref) => {
+  return(
+    <LabelWrapper >
+      <p>{label}</p>
+      <InputWrapper ref={ref}{...props}/>
+      {error&& <p>{error.message}</p>}
+    </LabelWrapper>
+  )
+})
+export default Input

+ 18 - 0
final-modul-react/src/components/Input/styled.js

@@ -0,0 +1,18 @@
+import styled from 'styled-components';
+import React from 'react'
+
+export const Input  = styled.input`
+    
+    margin: 0 auto;
+    width: 100%;
+    padding: 10px;
+   border: 1px solid gray;
+    
+`;
+export const Label  = styled.label`
+    
+    margin: 0 auto;
+   width: 90%;
+ 
+    
+`;

+ 11 - 0
final-modul-react/src/constants/users.js

@@ -9,5 +9,16 @@ export const userConstants = {
    
 
     LOGOUT: 'LOGOUT',
+
+    SEARCH: 'SEARCH',
+
+    GET_CURRENT_USER: 'GET_CURRENT_USER',
+
+    GET_USER_BY_ID: 'GET_USER_BY_ID',
+
+    LOADER_START:'LOADER_START',
+
+    LOADER_STOP: 'LOADER_STOP',
+
   
 };

+ 0 - 2
final-modul-react/src/container/Auth/index.js

@@ -1,2 +0,0 @@
-import Auth from  './Auth.jsx';
-export default Auth;

+ 0 - 32
final-modul-react/src/container/Header/Header.jsx

@@ -1,32 +0,0 @@
-import './style.css';
-import React from 'react';
-import Search from '../Search';
-import {connect} from 'react-redux';
-import {Link} from 'react-router-dom';
-
-const mapDispatchToProps = dispatch => {
-    return {
-    
-        logOut: () => dispatch({type: 'LOGOUT'}),
-      
-    }
-}
-
-
-
-const Header = ({logOut}) => {
-    const handleLogOut = () => {
-        logOut()
-    }
-    
-    return (
-        <>
-         <header>
-             <Search/>
-             <Link to='/auth/login' className='log-out-btn' onClick={handleLogOut}/>
-         </header>
-        </>
-    );
-}
-
-export default connect(null, mapDispatchToProps) (Header);

+ 0 - 2
final-modul-react/src/container/Header/index.js

@@ -1,2 +0,0 @@
-import Header from  './Header.jsx';
-export default Header;

+ 101 - 0
final-modul-react/src/container/Header/index.jsx

@@ -0,0 +1,101 @@
+import './styled.js';
+import React, { useEffect, useState } from 'react';
+import {connect} from 'react-redux';
+import {Link, withRouter} from 'react-router-dom';
+import { usersApi } from '../../api';
+import { userConstants } from '../../constants/users';
+import useDebounce from '../../utils/useDebounce';
+import {Header as BaseHeader, LogOut, UserBlock, UserIcon, Input as BaseInput} from './styled'
+
+const mapDispatchToProps = dispatch => {
+    return {
+    
+        logOut: () => dispatch({type: userConstants.LOGOUT}),
+        searchResult: (users) => dispatch({type: userConstants.SEARCH, payload: users}),
+        getUser: (user) =>  dispatch({type: userConstants.GET_CURRENT_USER, payload: user}),
+      
+    }
+}
+
+
+
+const Header = ({logOut, searchResult, history, children}) => {
+  
+    const [searchTerm, setSearchTerm] = useState('');
+    const [results, setResults] = useState([]);
+    const [isSearching, setIsSearching] = useState(false);
+    const debouncedSearchTerm = useDebounce(searchTerm, 500);
+
+    const handleLogOut = () => {
+        logOut()
+        debugger;
+    }
+    
+
+
+
+    useEffect(
+            () => {
+            
+            if (debouncedSearchTerm) {
+            
+                setIsSearching(true);
+                searchCharacters(debouncedSearchTerm)
+                .then(results => {
+                    setIsSearching(false);
+                    setResults(results);
+                });
+            } else {
+                setResults([]);
+            }
+            },
+        
+            [debouncedSearchTerm]
+            );
+  
+    const searchCharacters = async (search) => {
+        try {
+                    const foundUsers = await usersApi.getUsersByLogin(search)
+                    searchResult(foundUsers)
+                    
+            
+                } catch(err) {
+                    const error = err.response.data
+                    alert(`Oops, something went wrong!!!  ${error} . `)
+                    console.log(error)
+            
+                } finally  {
+                
+                    history.push('/users')
+                }
+    }
+
+
+    return (
+        <>
+         <BaseHeader>
+            
+             <BaseInput name='search' placeholder='search for user' onChange={e => setSearchTerm(e.target.value)} />
+           
+             {children}
+            <UserBlock>
+                <UserIcon onClick={()=>{
+                        
+                        history.push(`/users/current`)
+                        }}> 
+                   
+                </UserIcon>
+                 
+                    <Link to='/auth/login'>
+                        <LogOut onClick={handleLogOut} tabIndex='0'/>
+                    
+                    
+                    </Link>
+              
+            </UserBlock>
+         </BaseHeader>
+        </>
+    );
+}
+
+export default connect(null, mapDispatchToProps) (withRouter ((Header)));

+ 0 - 18
final-modul-react/src/container/Header/style.css

@@ -1,18 +0,0 @@
-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    background-color: rgb(0, 153, 255);
-    padding: 20px;
-}
-
-.log-out-btn {
-    border: none;
-    background-color:  rgb(0, 153, 255);;
-    background-image: url('../../img/exit.svg');
-    margin: 0;
-    padding: 0;
-    width: 60px;
-    height: 60px;
-}
-

+ 43 - 0
final-modul-react/src/container/Header/styled.js

@@ -0,0 +1,43 @@
+import styled from 'styled-components';
+import user from '../../img/user.png';
+import logout from '../../img/exit.svg'
+
+export const Header = styled.header `
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    background-color: rgb(0, 153, 255);
+    padding: 20px;
+`;
+
+export const UserBlock = styled.div `
+    display: flex;
+`;
+
+export const Input = styled.input `
+    width: 200px;
+`;
+
+export const UserIcon = styled.div `
+    border: none;
+    background-color:  rgb(0, 153, 255);
+    background-image: url(${user});
+    margin: 0;
+    margin-right: 10px;
+    padding: 0;
+    width: 60px;
+    height: 60px;
+`;
+
+
+export const LogOut = styled.div ` 
+    border: none;
+    background-color:  rgb(0, 153, 255);;
+    background-image: url(${logout});
+    margin: 0;
+    padding: 0;
+    width: 60px;
+    height: 66px;
+`;
+
+

+ 0 - 2
final-modul-react/src/container/HomePage/index.js

@@ -1,2 +0,0 @@
-import HomePage from  './HomePage.jsx';
-export default HomePage;

+ 0 - 16
final-modul-react/src/container/Search/Search.jsx

@@ -1,16 +0,0 @@
-import './style.css';
-import Input from '../../components/Input'
-
-
-
-
-
-const Search =({valueInput}) => {
-
-  return (
-    <Input placeholder="Search for users"  value={valueInput}/>
-
-  )
-}
-
-export default Search

+ 0 - 2
final-modul-react/src/container/Search/index.js

@@ -1,2 +0,0 @@
-import Search from './Search'
-export default Search;

+ 0 - 7
final-modul-react/src/container/Search/style.css

@@ -1,7 +0,0 @@
-input {
-    width: 300px;
-    padding: 5px;
-    max-height: 70px;
-    margin: 0;
-    border: 1px solid  gray;
-}

+ 125 - 0
final-modul-react/src/container/forms/UserFormInfo/index.jsx

@@ -0,0 +1,125 @@
+import { SettingsFormWrapper,  SettingsFormInfo, SettingsInfoBlockWrapper, SettingsFormPassword, SettingsBtn, SettingsInfoBlock, InputPhoto, SettingsFormImage, BtnPhoto, SettingsAvatarBlock} from './styled.js';
+import {useForm} from 'react-hook-form';
+import {textNameValidation, emailValidation, passwordValidation} from '../../../utils/validation'
+import Input from '../../../components/Input'
+import { connect, useSelector } from 'react-redux';
+import { userConstants } from '../../../constants/users.js';
+import { usersApi } from '../../../api/index.js';
+import catavatar from '../../../img/cat-avatar.jpg'
+import { useRef, useState } from 'react';
+
+const mapDispatchToProps = dispatch => {
+    return {
+        getCurrentUser: user => dispatch({type:  userConstants.INIT , payload: user}),
+        startLoader: () => dispatch({type: userConstants.LOADER_START}),
+        stopLoader: () => dispatch({type: userConstants.LOADER_STOP}),
+        initDispatch: currentUser => dispatch({type: userConstants.INIT, payload: currentUser}),
+    }
+}
+
+const UserFormInfo = ({initDispatch, startLoader, stopLoader}) => {
+    const currentUser = useSelector(store => store.auth.currentUser);
+    const {register, handleSubmit, errors} = useForm()
+    const [pictureN, setPicture] = useState(null);
+    const hiddenFileInput = useRef(null)
+  const handleClick =()=> {
+    hiddenFileInput.current.click();
+  }
+    const onChange = (e) => {
+        e.preventDefault();
+    debugger;
+        let reader = new FileReader();
+        let file = e.target.files[0];
+    
+        reader.onloadend = () => {
+          setPicture({
+            file: file,
+            imagePreviewUrl: reader.result
+          });
+        }
+    
+        reader.readAsDataURL(file)
+      }
+
+    const loginForm = async data => {
+      startLoader()
+      const {login, firstName, lastName, email} = data;
+
+      const body = {
+        login: login, 
+        firstName: firstName, 
+        lastName: lastName, 
+        email: email, 
+        avatar: pictureN.imagePreviewUrl
+      }
+      debugger;
+console.log(pictureN)
+
+
+  try {
+    const updatedUser = await usersApi.updateUsersInfo(body);
+    initDispatch(updatedUser)
+  
+  } catch(err) {
+    
+    alert(`Oops, something went wrong!!!   ${err.response.data} . `)
+    console.log(err.response.data)
+  
+  } finally  {
+    stopLoader()
+  
+  }
+  } 
+  
+  return (
+      <>
+       <SettingsFormInfo onSubmit={handleSubmit(loginForm)}>
+          <SettingsInfoBlockWrapper>
+          <SettingsInfoBlock>
+           <SettingsAvatarBlock>
+           <SettingsFormImage src={currentUser.avatar||catavatar} width='100px'alt="Default-avatar"/>
+           <InputPhoto ref={hiddenFileInput} type="file" name="picture" onChange={onChange}/>
+           <BtnPhoto onClick={handleClick}>Change avatar</BtnPhoto>
+           </SettingsAvatarBlock>    
+        <Input
+          error= {errors.login}
+          name="login"
+          ref={register(textNameValidation)} 
+          type="text"
+          placeholder="Enter login"
+          label="Login"
+          defaultValue={currentUser.login}/>
+          </SettingsInfoBlock>
+          <SettingsInfoBlock>             <Input 
+          error= {errors.firstName}
+          name="firstName"
+          ref={register(textNameValidation)} 
+          type="text"
+          placeholder="Enter Your name"
+          label="First name"
+          defaultValue={currentUser.firstName}/>
+             <Input 
+          error= {errors.lastName}
+          name="lastName"
+          ref={register(textNameValidation)} 
+          type="text"
+          placeholder="Enter your last name"
+          label="Last Name"
+          defaultValue={currentUser.lastName}/>
+             <Input
+          error= {errors.email}
+          name="email"
+          ref={register(emailValidation)} 
+          type="text"
+          placeholder="Enter Email"
+          label="Email"
+          defaultValue={currentUser.email}/>
+        
+        </SettingsInfoBlock>
+        </SettingsInfoBlockWrapper>
+        <SettingsBtn type="submit">Save profile</SettingsBtn>
+      </SettingsFormInfo>
+      </>
+  )
+}
+export default connect(null, mapDispatchToProps)(UserFormInfo);

+ 117 - 0
final-modul-react/src/container/forms/UserFormInfo/styled.js

@@ -0,0 +1,117 @@
+import styled from 'styled-components';
+
+export const SettingsFormWrapper  = styled.div`
+    
+    margin: 0 auto;
+    width: 100%;
+    padding: 10px;
+    display: flex;
+    flex-direction: column;
+    
+`;
+export const SettingsFormInfo  = styled.form`
+    
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-content:center;
+`;
+
+export const SettingsInfoBlock  = styled.div`
+    width: 50%;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+  align-content:space-between;
+    
+`;
+
+export const SettingsAvatarBlock  = styled.div`
+    
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  align-content:center;
+  
+    
+`;
+export const SettingsFormImage  = styled.img`
+    
+  border-radius: 50%;
+  align-self: center;
+
+`;
+
+export const InputPhoto  = styled.input`
+    
+   display: none;
+    
+`;
+
+
+export const SettingsInfoBlockWrapper  = styled.div`
+    
+    display: flex;
+    flex-direction: row;
+    width: 100%;
+  
+    
+`;
+export const SettingsFormPassword  = styled.form`
+    
+    display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-content: space-between;
+    
+`;
+
+export const SettingsBtn = styled.button`
+    
+    
+display: inline-block;
+  width: 100%;
+  padding: 10px;
+  background-color: green;
+  color: #ffffff;
+  font-size: 14px;
+  line-height: 18px;
+  font-weight: 400;
+  list-style: none;
+  text-transform: uppercase;
+  text-align: center;
+    
+    align-self: flex-end;
+    @media (min-width: 768px) {
+        background-color: blue;
+        width: 200px;
+        
+        &:hover {
+            background-color: green;
+        }
+        &:active {
+            background-color: darken(green, 10%);
+            color: rgba(255, 255, 255, 0.3);
+        }
+
+}
+
+@media (min-width: 1300px) {
+  
+    padding: 20px 20px;
+  
+}
+ 
+    
+`;
+export const BtnPhoto  = styled(SettingsBtn)`
+    width: 150px;
+    padding: 10px 5px;
+    border-radius: 20px;
+    background-color: blue;
+    color: white;
+    list-style: none;
+  text-transform: uppercase;
+  align-self: center;
+    
+`;

+ 72 - 0
final-modul-react/src/container/forms/UserFormPassword/index.jsx

@@ -0,0 +1,72 @@
+import {  SettingsFormPassword, SettingsPasswordWrapper} from './styled.js';
+import {  SettingsBtn, SettingsInfoBlock } from '../UserFormInfo/styled';
+import {useForm} from 'react-hook-form';
+import { passwordValidation} from '../../../utils/validation'
+import Input from '../../../components/Input'
+import { connect } from 'react-redux';
+import { userConstants } from '../../../constants/users.js';
+import { authApi } from '../../../api/index.js';
+
+const mapDispatchToProps = dispatch => {
+    return {
+        startLoader: () => dispatch({type: userConstants.LOADER_START}),
+        stopLoader: () => dispatch({type: userConstants.LOADER_STOP}),
+       
+    }
+}
+
+const UserFormPassword = ( { startLoader, stopLoader, pass}) => {
+    
+    const {register, handleSubmit, errors} = useForm()
+   
+    const passwordUpdateForm = async data => {
+      startLoader()
+    try {
+    
+    const {password, confirmPassword} = data;
+    const body = {
+      password: password,
+      confirmPassword: confirmPassword
+    }  
+    await authApi.updatePassword(body);
+    
+    } catch(err) {
+    
+    alert(`Oops, something went wrong!!!   ${err.response.data} . `)
+    console.log(err.response.data)
+    
+    } finally  {
+    stopLoader()
+    
+    }
+    } 
+  
+  return (
+      <>
+      <SettingsFormPassword onSubmit={handleSubmit(passwordUpdateForm)}>
+      <SettingsPasswordWrapper>
+      <SettingsInfoBlock>
+        <Input pass
+         error= {errors.password}
+         name="password"
+         ref={register(passwordValidation)} 
+         type="password" 
+         placeholder="enter Password" 
+         label="Password"/>
+          </SettingsInfoBlock>
+          <SettingsInfoBlock>
+         <Input  pass={pass === true}
+         error= {errors.password}
+         name="confirmPassword"
+         ref={register(passwordValidation)} 
+         type="password" 
+         placeholder="confirm Password" 
+         label="Confirm Password"/>
+         </SettingsInfoBlock>
+          </SettingsPasswordWrapper>
+         <SettingsBtn type="submit">Save password</SettingsBtn>
+       </SettingsFormPassword > 
+       </>
+  )
+}
+export default connect(null, mapDispatchToProps)(UserFormPassword);

+ 67 - 0
final-modul-react/src/container/forms/UserFormPassword/styled.js

@@ -0,0 +1,67 @@
+import styled from 'styled-components';
+import { SettingsInfoBlockWrapper } from '../UserFormInfo/styled';
+export const SettingsFormWrapper  = styled.div`
+    
+    margin: 0 auto;
+    width: 100%;
+    padding: 30px;
+    display: flex;
+    flex-direction: column;
+    
+    
+`;
+
+export const SettingsPasswordWrapper  = styled(SettingsInfoBlockWrapper)`
+    
+ color: black;
+ }
+    
+    
+`;
+export const SettingsFormPassword  = styled.form`
+    width:70%;
+    padding: 30px;
+    display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-content: space-between;
+    border-top: 1px solid gray;
+`;
+
+export const SettingsBtn = styled.button`
+    
+    
+display: inline-block;
+  width: 100%;
+  padding: 10px;
+  background-color: green;
+  color: #ffffff;
+  font-size: 14px;
+  line-height: 18px;
+  font-weight: 400;
+  list-style: none;
+  text-transform: uppercase;
+  text-align: center;
+    display: flex;
+    align-self: flex-end;
+    @media (min-width: 768px) {
+        background-color: blue;
+        width: 200px;
+        &:hover {
+            background-color: green;
+        }
+        &:active {
+            background-color: darken(green, 10%);
+            color: rgba(255, 255, 255, 0.3);
+        }
+
+}
+
+@media (min-width: 1300px) {
+  
+    padding: 20px 20px;
+  
+}
+ 
+    
+`;

TEMPAT SAMPAH
final-modul-react/src/img/cat-avatar.jpg


TEMPAT SAMPAH
final-modul-react/src/img/found-null.jpg


TEMPAT SAMPAH
final-modul-react/src/img/user.png


+ 3 - 3
final-modul-react/src/container/Login/Login.jsx

@@ -1,10 +1,10 @@
 import './style.css';
-import { userConstants } from '../../constants/users';
+import { userConstants } from '../../../constants/users';
 import React from 'react';
 import {connect} from 'react-redux';
-import {authApi, usersApi} from '../../api'; 
+import {authApi, usersApi} from '../../../api'; 
 import {Link, withRouter } from 'react-router-dom';
-import withAuthWrapper from '../../components/withAuthWrapper';
+import withAuthWrapper from '../../../components/withAuthWrapper';
 
 
 const mapDispatchToProps = dispatch => {

final-modul-react/src/container/Login/index.js → final-modul-react/src/pages/Auth/Login/index.js


final-modul-react/src/container/Login/style.css → final-modul-react/src/pages/Auth/Login/style.css


+ 3 - 3
final-modul-react/src/container/Registration/Registration.jsx

@@ -1,10 +1,10 @@
 import './style.css';
 import React from 'react';
 import { connect } from 'react-redux';
-import {authApi} from '../../api'; 
+import {authApi} from '../../../api'; 
 import {Link, withRouter} from 'react-router-dom';
-import withAuthWrapper from '../../components/withAuthWrapper';
-import { userConstants } from '../../constants/users';
+import withAuthWrapper from '../../../components/withAuthWrapper';
+import { userConstants } from '../../../constants/users';
 
 const mapDispatchToProps = dispatch => {
     return {

final-modul-react/src/container/Registration/index.js → final-modul-react/src/pages/Auth/Registration/index.js


final-modul-react/src/container/HomePage/style.css → final-modul-react/src/pages/Auth/Registration/style.css


+ 1 - 1
final-modul-react/src/container/Auth/Auth.jsx

@@ -6,7 +6,7 @@ import { Redirect} from 'react-router-dom'
 
 const Auth = () => {
  const  isRegist = useSelector(store => store.reg.isRegist)
-    debugger;
+   
     return (
         isRegist ? <Redirect to="/auth/login" /> : <Redirect to="/auth/registration" />
     )

final-modul-react/src/container/Auth/style.css → final-modul-react/src/pages/Auth/style.css


+ 86 - 0
final-modul-react/src/pages/Main/containers/CurrentUser/CurrentUserProfile/index.jsx

@@ -0,0 +1,86 @@
+import './styled.js';
+import React, { useEffect } from 'react';
+import { connect, useSelector } from 'react-redux';
+import Header from '../../../../../container/Header';
+import catavatar from '../../../../../img/cat-avatar.jpg';
+import { SettingsBtn } from './styled.js';
+
+import { FoundUserMsg } from '../../Users/FoundUsers/styled';
+
+import {UserProfileMain, UserAbout, UserInfo, UserInfoWrapper, UserPosts} from '../../Users/UserProfile/styled'
+import { userConstants } from '../../../../../constants/users.js';
+import { withRouter } from 'react-router-dom';
+import { usersApi } from '../../../../../api/index.js';
+
+const mapDispatchToProps = dispatch => {
+    return {
+        startLoader: () => dispatch({type: userConstants.LOADER_START}),
+        stopLoader: () => dispatch({type: userConstants.LOADER_STOP}),
+        getCurrentUser: user => dispatch({type:  userConstants.INIT , payload: user}),
+        initDispatch: currentUser => dispatch({type: userConstants.INIT, payload: currentUser}),
+    }
+}
+
+
+const CurrentUserProfile = ({initDispatch, history, startLoader, stopLoader}) => {
+    useEffect(() => {
+        startLoader();
+        usersApi.getCurrentUser()
+            .then((user) => initDispatch(user))
+            .catch(e => console.log(e))
+            .finally(() => {
+                stopLoader()
+            })
+    }, [])
+ 
+    const currentUser = useSelector(store => store.auth.currentUser);
+   
+    debugger;
+
+
+   
+    if (currentUser!==null){
+
+    stopLoader();
+    return (
+        <>
+        <Header>
+            <FoundUserMsg >{currentUser.login}</FoundUserMsg>
+        </Header>
+        <UserProfileMain>
+            <UserAbout>
+            <img src={currentUser.avatar||catavatar} width='100px'alt="Default-avatar"/>
+            <UserInfoWrapper>
+                <UserInfo>
+                <div>{currentUser.posts.length} posts</div>
+                <div>{currentUser.followers.length} followers</div>
+                <div>{currentUser.following.length} followings</div>
+                </UserInfo>
+                <SettingsBtn  onClick={() => {
+                            
+                            history.push(`/users/current/settings`)
+                                                    }}>Settings</SettingsBtn>
+
+            </UserInfoWrapper>
+            </UserAbout>
+            <UserPosts>{ 
+            currentUser.posts.map((post)=> {
+                return (
+                    <img src={post.imgUrl||catavatar} key={post.id}width='150px'alt="Default-post-img"/>
+                )
+            }) }
+            </UserPosts>
+        </UserProfileMain>
+        
+        </>
+    );
+}
+else{
+    startLoader()
+    return(
+      
+        <h1>loading</h1>
+    )
+}
+}
+export default connect(null, mapDispatchToProps) ( withRouter( CurrentUserProfile));

+ 13 - 0
final-modul-react/src/pages/Main/containers/CurrentUser/CurrentUserProfile/styled.js

@@ -0,0 +1,13 @@
+import styled from 'styled-components';
+
+
+
+
+export const SettingsBtn = styled.button`
+    
+    margin: 0 auto;
+    width: 100px;
+    padding: 10px;
+    background-color: green;
+    
+`;

+ 57 - 0
final-modul-react/src/pages/Main/containers/CurrentUser/CurrentUserSettings/index.jsx

@@ -0,0 +1,57 @@
+import './styled.js';
+import React, { useEffect } from 'react';
+import { connect, useSelector } from 'react-redux';
+
+import Header from '../../../../../container/Header';
+
+import { SettingsFormWrapper,  SettingsFormInfo, SettingsInfoBlockWrapper, SettingsFormPassword, SettingsBtn, SettingsInfoBlock } from './styled.js';
+
+import { FoundUserMsg } from '../../Users/FoundUsers/styled';
+import {UserProfileMain, UserAbout, UserInfo, UserInfoWrapper, UserPosts} from '../../Users/UserProfile/styled'
+import { userConstants } from '../../../../../constants/users';
+
+import UserFormInfo from '../../../../../container/forms/UserFormInfo/index.jsx';
+import UserFormPassword from '../../../../../container/forms/UserFormPassword';
+
+const mapDispatchToProps = dispatch => {
+    return {
+        startLoader: () => dispatch({type: userConstants.LOADER_START}),
+        stopLoader: () => dispatch({type: userConstants.LOADER_STOP}),
+       
+    }
+}
+
+
+const CurrentUserSettings = ({startLoader, stopLoader}) => {
+    const currentUser = useSelector(store => store.auth.currentUser);
+
+    if (currentUser!==null){
+
+        stopLoader();
+
+    return (
+        <>
+        <Header>
+            <FoundUserMsg >Settings</FoundUserMsg>
+        </Header>
+   <SettingsFormWrapper>
+       
+     <UserFormInfo/>
+     <UserFormPassword/>
+      
+     
+    
+  </SettingsFormWrapper> 
+        </>
+    );
+}
+else{
+    startLoader()
+    return(
+      
+        <h1>loading</h1>
+    )
+}
+}
+
+export default connect(null, mapDispatchToProps) (CurrentUserSettings);

+ 80 - 0
final-modul-react/src/pages/Main/containers/CurrentUser/CurrentUserSettings/styled.js

@@ -0,0 +1,80 @@
+import styled from 'styled-components';
+
+export const SettingsFormWrapper  = styled.div`
+    
+    margin: 0 auto;
+    width: 100%;
+    padding: 0;
+    display: flex;
+    flex-direction: column;
+    
+`;
+export const SettingsFormInfo  = styled.form`
+    
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-content: space-between;
+`;
+
+export const SettingsInfoBlock  = styled.div`
+    
+    display: flex;
+    flex-direction: column;
+  align-items: flex-end;
+    
+`;
+
+export const SettingsInfoBlockWrapper  = styled.div`
+    
+    display: flex;
+    flex-direction: row;
+  
+    
+`;
+export const SettingsFormPassword  = styled.form`
+    
+    display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-content: space-between;
+    
+`;
+
+export const SettingsBtn = styled.button`
+    
+    
+display: inline-block;
+  width: 100%;
+  padding: 10px;
+  background-color: green;
+  color: #ffffff;
+  font-size: 14px;
+  line-height: 18px;
+  font-weight: 400;
+  list-style: none;
+  text-transform: uppercase;
+  text-align: center;
+    display: flex;
+    align-self: flex-end;
+    @media (min-width: 768px) {
+        background-color: blue;
+        width: 200px;
+        &:hover {
+            background-color: green;
+        }
+        &:active {
+            background-color: darken(green, 10%);
+            color: rgba(255, 255, 255, 0.3);
+        }
+
+}
+
+@media (min-width: 1300px) {
+  
+    padding: 20px 20px;
+  
+}
+ 
+    
+`;

+ 49 - 0
final-modul-react/src/pages/Main/containers/Users/FoundUsers/index.jsx

@@ -0,0 +1,49 @@
+import './styled.js';
+import React from 'react';
+import { useSelector } from 'react-redux';
+import Header from '../../../../../container/Header';
+import catavatar from '../../../../../img/cat-avatar.jpg';
+import { FoundUserCard, FoundUserMsg, FoundUsers as FoundUsersList, FoundUsersNull } from './styled.js';
+import { Link, withRouter } from 'react-router-dom';
+ 
+      
+
+const FoundUsers = ({history}) => {
+    const foundUsers = useSelector(store => store.users.foundUsers);
+    const isFound = useSelector(store => store.users.isFound);
+    console.log(foundUsers)
+    const foundUsersResult = foundUsers.length ? <FoundUserMsg>Found {foundUsers.length} users</FoundUserMsg> : <FoundUserMsg>`Users not found`</FoundUserMsg> 
+
+    return (
+        <>
+        <Header>
+            <div >{foundUsersResult}</div>
+        </Header>
+        <FoundUsersList>
+        {
+        isFound && foundUsers.map(user => {
+           const userId = user._id
+                return (
+                   <FoundUserCard key={userId}>
+                       
+                        <img src={user.avatar||catavatar} width='70px'alt="Default-avatar"/>
+                     
+                        <h6>{user.login} </h6>
+                       
+                        <button  onClick={() => {
+                            
+history.push(`/users/${userId}`)
+                        }}>Show user</button>
+                        <button >Follow</button>
+                        
+                    </FoundUserCard>
+                )
+            })}  
+           {!isFound && <FoundUsersNull ></FoundUsersNull>} 
+        </FoundUsersList>
+        </>
+    );
+       
+}
+
+export default withRouter( FoundUsers) ;

+ 33 - 0
final-modul-react/src/pages/Main/containers/Users/FoundUsers/styled.js

@@ -0,0 +1,33 @@
+import styled from 'styled-components';
+import userNull from '../../../../../img/found-null.jpg';
+
+export const FoundUsers = styled.main`
+    display: flex;
+    flex-direction: column;
+`;
+ 
+export const FoundUserCard = styled.div`
+    display: flex;
+    justify-content: space-between;
+    border: 1px solid green;
+    width: 50%;
+    margin: 10px auto;
+    background-color: rgb(228, 238, 222);
+`;
+export const FoundUserMsg = styled.div`
+  color: white;
+  font-size: 28px;
+  font-weight: 700;
+  text-transform: uppercase;
+   
+`;
+
+export const FoundUsersNull = styled.div`
+    display: flex;
+    margin: 0 auto;
+    width: 50%;
+    height: 400px;
+    background-image: url(${userNull});
+    background-repeat: no-repeat;
+    background-position: center;
+`;

+ 89 - 0
final-modul-react/src/pages/Main/containers/Users/UserProfile/index.jsx

@@ -0,0 +1,89 @@
+import './styled.js';
+import {  FoundUserMsg } from '../FoundUsers/styled';
+import React, { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import Header from '../../../../../container/Header';
+import catavatar from '../../../../../img/cat-avatar.jpg';
+import {connect} from 'react-redux';
+import { usersApi } from '../../../../../api/index.js';
+import { userConstants } from '../../../../../constants/users.js';
+import { UserProfileMain, UserAbout, UserInfo, UserInfoWrapper, UserPosts, FollowBtn } from './styled.js';
+
+ 
+
+const mapDispatchToProps = dispatch => {
+    return {
+        startLoader: () => dispatch({type: userConstants.LOADER_START}),
+        stopLoader: () => dispatch({type: userConstants.LOADER_STOP}),
+        getUserById: user => dispatch({type:  userConstants.GET_USER_BY_ID, payload: user}),
+      
+    }
+}
+
+      
+
+const UserProfile = ( {getUserById, userId, startLoader, stopLoader}) => {
+  
+    useEffect(() => {
+        startLoader();
+        usersApi.getUsersById(userId)
+        .then(user => 
+          getUserById(user)
+        )
+        .catch(err => console.log(err.response.data))
+        .finally(() => {
+            stopLoader()
+        })  
+            
+    
+    }, [])
+  debugger;
+    const user = useSelector(store => store.users.User);
+    //const posts = user.posts.length;
+
+    //Check me out
+    // if(user.post){
+    //     const posts = user.posts.length;
+    // }
+
+    //const postsArr = user.posts; 
+
+    if (user.posts ){
+        stopLoader();
+    return (
+        <>
+        <Header>
+        <FoundUserMsg>{user.login}</FoundUserMsg>
+        </Header>
+        <UserProfileMain>
+            <UserAbout>
+            <img src={user.avatar||catavatar} width='100px'alt="Default-avatar"/>
+            <UserInfoWrapper>
+                <UserInfo>
+                <div>{user.posts.length} posts</div>
+                <div>{user.followersCount} followers</div>
+                <div>{user.followingsCount} followings</div>
+                </UserInfo>
+                <FollowBtn>Follow</FollowBtn>
+
+            </UserInfoWrapper>
+            </UserAbout>
+            <UserPosts>{user.posts.map((post)=> {
+                return (
+                    <img src={post.imgUrl||catavatar} key={post.id}width='150px'alt="Default-post-img"/>
+                )
+            })}</UserPosts>
+        </UserProfileMain>
+        </>
+    );
+}
+else{
+    startLoader()
+    return(
+      
+        <h1>loading</h1>
+    )
+}
+}
+
+export default connect(null, mapDispatchToProps)( UserProfile);

+ 59 - 0
final-modul-react/src/pages/Main/containers/Users/UserProfile/styled.js

@@ -0,0 +1,59 @@
+import styled from 'styled-components';
+
+
+export const UserProfileMain = styled.main`
+    display: flex;
+    flex-direction: column;
+    margin: 0 auto;
+    width: 70%;
+    height: 100%;
+    background-color:lightgray;
+    
+`;
+ 
+export const UserAbout = styled.div`
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    width: 80%;
+    margin: 10px auto;
+     
+    background-color: white;
+`;
+export const UserInfoWrapper = styled.div`
+  display:flex;
+  flex-direction: column;
+  justify-content: space-between;
+  flex: 1;
+  margin: 20px;
+  color: gray;
+  font-size: 20px;
+  font-weight: 700;
+ 
+   
+`;
+export const UserInfo = styled.div`
+  display:flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+ 
+  margin-bottom: 20px
+ 
+   
+`;
+export const FollowBtn = styled.button`
+    padding: 10px;
+    margin: 0 auto;
+    width: 100%;
+   background-color: blue;
+    
+`;
+
+export const UserPosts = styled.div`
+  display:flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  width: 80%;
+ 
+   
+`;

+ 2 - 2
final-modul-react/src/container/HomePage/HomePage.jsx

@@ -1,14 +1,14 @@
 import './style.css';
 import React from 'react';
 import { useSelector } from 'react-redux';
-import Dashboard from '../Dashboard';
+import Dashboard from '../../container/Dashboard';
 import { Redirect} from 'react-router-dom'
 
       
 
 const HomePage = () => {
     const isAuth = useSelector(store => store.auth.isAuth);
-debugger;
+
     return (
         isAuth ?  <Dashboard /> : <Redirect to="/auth" />
     );

final-modul-react/src/container/Registration/style.css → final-modul-react/src/pages/Main/style.css


+ 2 - 0
final-modul-react/src/store/index.js

@@ -5,12 +5,14 @@ import { regReducer } from "./regist/reducer";
 import {authReducer} from './auth/reducer';
 import {uiReducer} from './ui/reducer';
 import { initThunk } from './thunks';
+import { usersReducer } from './users/reducer';
 
 
 const rootReducer = combineReducers({
     auth: authReducer,
     ui: uiReducer,
     reg: regReducer,
+    users: usersReducer,
    
 })
 

+ 4 - 2
final-modul-react/src/store/ui/reducer.js

@@ -1,15 +1,17 @@
+import { userConstants } from "../../constants/users"
+
 const initialState = {
     isLoader: false
 }
 
 export const uiReducer = (state=initialState, action) => {
     switch (action.type) {
-        case 'LOADER_START':
+        case userConstants.LOADER_START:
             return {
                 ...state,
                 isLoader: true,
             } 
-        case 'LOADER_STOP':
+        case userConstants.LOADER_STOP:
             return {
                 ...state,
                 isLoader: false,

+ 5 - 0
final-modul-react/src/store/users/actionTypes.js

@@ -0,0 +1,5 @@
+export const ActionTypes = {
+    ADD_USER: 'ADD_USER',
+    DELETE_USER: 'DELETE_USER',
+    GET_USERS: 'GET_USERS',
+}

+ 12 - 0
final-modul-react/src/store/users/actions.js

@@ -0,0 +1,12 @@
+import { ActionTypes } from "./actionTypes"
+
+let id = 1;
+
+export const addUserAction = data => {
+    data = {...data, id: id++}
+    return {type: ActionTypes.ADD_USER, payload: data}
+}
+
+export const deleteUserAction = id => {
+    return {type: ActionTypes.DELETE_USER, payload: id}
+}

+ 40 - 0
final-modul-react/src/store/users/reducer.js

@@ -0,0 +1,40 @@
+import store from ".."
+import { userConstants } from "../../constants/users"
+
+const initialState = {
+    foundUsers: [],
+    isFound: false,
+    //currentUser: null,
+    User: {},
+    
+}
+
+export const usersReducer = (state=initialState, action) => {
+    switch (action.type) {
+        // case userConstants.GET_CURRENT_USER:
+        //     return {
+        //         ...state,
+        //         currentUser: action.payload
+        //     }
+        case userConstants.GET_USER_BY_ID:
+            const user = action.payload
+            return {
+                ...state,
+                User: user,
+            }
+        // case ActionTypes.DELETE_USER:
+        //     return {
+        //         ...state,
+        //         list: state.list.filter(u => u._id !== action.payload)
+        //     }
+        case userConstants.SEARCH:
+        const isFound = action.payload.length ? true : false
+                return {
+                    ...state,
+                    foundUsers: action.payload,
+                    isFound: isFound,
+                }
+        default:
+            return state    
+    }
+}

+ 36 - 0
final-modul-react/src/store/users/thunks.js

@@ -0,0 +1,36 @@
+import {usersApi} from '../../api';
+import { userConstants } from '../../constants/users';
+
+
+export const getAllUsersThunk = () => {
+    return async (dispatch) => {
+        try {
+            const users = await usersApi.getAllUsers();
+            dispatch({type: 'GET_USERS', payload: users});
+        } catch(e) {
+            console.log(e);
+        }
+    }
+}
+
+export const deleteUserThunk = (id) => {
+    return async (dispatch) => {
+        try {
+            const users = await usersApi.deleteUser(id);
+            dispatch({type: 'DELETE_USER', payload: id});
+        } catch(e) {
+            console.log(e);
+        }
+    }
+}
+
+export const getUserIdThunk = (id) => {
+    return async (dispatch) => {
+        try {
+            const user = await usersApi.getUsersById (id);
+            dispatch({type: userConstants.GET_USER_BY_ID, payload: user});
+        } catch(e) {
+            console.log(e);
+        }
+    }
+}

+ 25 - 0
final-modul-react/src/utils/useDebounce.jsx

@@ -0,0 +1,25 @@
+import { useState, useEffect } from 'react';
+
+
+export default function useDebounce(value, delay) {
+
+  const [debouncedValue, setDebouncedValue] = useState(value);
+
+  useEffect(
+    () => {
+     
+      const handler = setTimeout(() => {
+        setDebouncedValue(value);
+      }, delay);
+
+      
+      return () => {
+        clearTimeout(handler);
+      };
+    },
+
+    [value]
+  );
+
+  return debouncedValue;
+}

+ 38 - 0
final-modul-react/src/utils/validation.js

@@ -0,0 +1,38 @@
+const emailRegExp = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
+
+const userNameRegExp = /^[a-zA-Z][a-zA-Z0-9-_\.]{1,16}$/;
+
+
+ export const emailValidation =  {
+  required: {
+    value: true,
+    message: 'Email is require',
+
+  },
+  pattern: {
+    value: emailRegExp,
+    message: 'Email should consist @'
+  }
+}
+ export const passwordValidation =  {
+  required: {
+    value: true,
+    message: 'password is require',
+
+  },
+  minLength: {
+    value: 8, 
+    message: 'Password should have min 8 symbols'
+  },
+  maxLength: {
+    value: 16, 
+    message: 'Password should have max 16 symbols'
+  }
+}
+export const textNameValidation =  {
+
+    pattern: {
+      value: userNameRegExp,
+      message: 'This name can consist of letters and numbers. First symbol is letter!!! '
+    }
+  }