├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── client ├── .env.development ├── package.json ├── public │ ├── favicon.png │ ├── index.html │ └── manifest.json └── src │ ├── actions │ ├── index.js │ └── types.js │ ├── components │ ├── App.js │ ├── Auth │ │ ├── NewPassword │ │ │ ├── NewPassword.js │ │ │ └── index.js │ │ ├── Reset │ │ │ ├── Reset.js │ │ │ └── index.js │ │ ├── SignIn │ │ │ ├── SignIn.css │ │ │ ├── SignIn.js │ │ │ ├── SignIn.test.js │ │ │ └── index.js │ │ ├── SignOut │ │ │ ├── SignOut.css │ │ │ ├── SignOut.js │ │ │ ├── SignOut.test.js │ │ │ └── index.js │ │ ├── SignUp │ │ │ ├── SignUp.css │ │ │ ├── SignUp.js │ │ │ ├── SignUp.test.js │ │ │ └── index.js │ │ └── index.js │ ├── Common │ │ ├── Footer.js │ │ ├── Navbar.js │ │ ├── Pagination.js │ │ ├── ScrollToTop.js │ │ ├── SystemMessages.js │ │ └── ToggleButton.js │ ├── Features │ │ ├── Features.js │ │ ├── Features.scss │ │ └── index.js │ ├── Home │ │ ├── Home.css │ │ ├── Home.js │ │ ├── Home.test.js │ │ └── index.js │ ├── NoMatch │ │ ├── NoMatch.css │ │ ├── NoMatch.js │ │ ├── NoMatch.test.js │ │ └── index.js │ ├── Profile │ │ ├── Profile.css │ │ ├── Profile.js │ │ ├── Profile.test.js │ │ └── index.js │ ├── Settings │ │ ├── Account.js │ │ ├── ChangePassword.js │ │ ├── Images.js │ │ ├── Settings.js │ │ ├── Settings.scss │ │ ├── Settings.test.js │ │ └── index.js │ ├── StaticPages │ │ ├── PrivacyPolicy.js │ │ ├── StaticPages.scss │ │ └── TermsOfService.js │ ├── User │ │ ├── Edit.js │ │ ├── User.css │ │ ├── User.js │ │ ├── User.test.js │ │ └── index.js │ ├── Users │ │ ├── Edit.js │ │ ├── List.js │ │ ├── Users.js │ │ ├── Users.scss │ │ └── index.js │ └── Welcome │ │ ├── Welcome.js │ │ ├── Welcome.scss │ │ ├── Welcome.test.js │ │ └── index.js │ ├── history.js │ ├── index.js │ ├── reducers │ ├── auth.js │ ├── index.js │ ├── settings.js │ └── system.js │ ├── routes │ └── index.js │ ├── serviceWorker.js │ └── styles │ ├── App.scss │ ├── _forms.scss │ ├── _global.scss │ ├── _intro.scss │ ├── _keyframes.scss │ ├── _main.scss │ ├── _mixins.scss │ ├── _navbar.scss │ ├── _profile.scss │ ├── _sidebar.scss │ ├── _variables.scss │ └── img │ ├── intro-header-bg.jpg │ └── logo.svg ├── common ├── mixins │ ├── FullName.js │ ├── Tags.js │ └── TimeStamp.js └── models │ ├── file.js │ ├── file.json │ ├── user.js │ └── user.json ├── data └── README.md ├── package.json └── server ├── boot ├── autentication.js ├── init.js └── root.js ├── component-config.json ├── config.json ├── datasources.json ├── middleware.development.json ├── middleware.json ├── model-config.json ├── models ├── container.js └── container.json └── server.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2017 4 | }, 5 | 6 | "env": { 7 | "es6": true 8 | } 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.dat 3 | *.iml 4 | *.log 5 | *.out 6 | *.pid 7 | *.seed 8 | *.sublime-* 9 | *.swo 10 | *.swp 11 | *.tgz 12 | *.xml 13 | *.lock 14 | .DS_Store 15 | .idea 16 | .project 17 | .strong-pm 18 | coverage 19 | node_modules 20 | npm-debug.log 21 | package-lock.json 22 | client/build 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 goker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loopback-react playground 2 | loopback (mongodb) - react, router, redux, axios, formik, bootstrap 4 (reactstrap) fullstack playground with authentication and user management 3 | 4 | This a full stack playground for developing a Loopback - React application. And it's _under the development_ still. 5 | 6 | For more info you can check: 7 | - https://loopback.io/doc/en/lb3/index.html 8 | - https://github.com/facebook/create-react-app 9 | - https://reacttraining.com/react-router/web/guides/philosophy 10 | - https://redux.js.org/ 11 | - https://github.com/jaredpalmer/formik 12 | - https://getbootstrap.com/docs/4.1/getting-started/introduction/ 13 | - https://reactstrap.github.io/ 14 | 15 | ### Install 16 | ``` 17 | yarn install && cd client && yarn install 18 | 19 | ``` 20 | 21 | ### Run Loopback 22 | ``` 23 | yarn start 24 | ``` 25 | The server run on **localhost:3003** and **server/boot/init.js** will create three users _(admin, editor and user)_ 26 | 27 | You can reach the Api Explorer via **localhost:3003/explorer** 28 | 29 | ### Run Client (React) 30 | ``` 31 | cd client && yarn start 32 | ``` 33 | The client run on **localhost:3000** and talking with api on **localhost:3003** 34 | 35 | 36 | ### Run for Development 37 | ``` 38 | yarn watch 39 | ``` 40 | The watch command is running loopback at background. If you need to kill both services running on 3000 and 3003, you can use 41 | ``` 42 | yarn kill 43 | ``` 44 | 45 | 46 | ##### Add a React Component 47 | ``` 48 | cd client/ 49 | npx crcf src/components/NewComponent 50 | ``` 51 | 52 | #### Build and Serve the Client 53 | ``` 54 | cd client/ 55 | yarn build 56 | ``` 57 | After the build to serve the client you should edit the **server/middleware.json** like below 58 | ``` 59 | "files": { 60 | "loopback#static": [ 61 | { 62 | "paths": ["/"], 63 | "params": "$!../client/build" 64 | }, 65 | { 66 | "paths": ["*"], 67 | "params": "$!../client/build" 68 | } 69 | ] 70 | } 71 | ``` 72 | to more info https://loopback.io/doc/en/lb3/Defining-middleware.html 73 | 74 | ## Routes 75 | ``` 76 | / 77 | /feature 78 | /signin 79 | /signup 80 | /signout 81 | /reset 82 | /newpassword/:token 83 | /tos 84 | /privacy 85 | - /home 86 | /profile 87 | /settings/:page? (default /settings/account) 88 | /users/:page?/:id? (default /users/list) 89 | /user/:id 90 | /user/:id/edit 91 | /:username (run as /profile) 92 | ``` 93 | in client/src/routes/index.js 94 | -------------------------------------------------------------------------------- /client/.env.development: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | REACT_APP_API_URL=http://localhost:3003/api 3 | REACT_APP_FILE_URL=http://localhost:3003 -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback-react-playground", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.19.0", 7 | "bootstrap": "^4.3.1", 8 | "form-serialize": "^0.7.2", 9 | "formik": "^1.5.8", 10 | "history": "^4.9.0", 11 | "moment": "^2.24.0", 12 | "node-sass": "^4.12.0", 13 | "nprogress": "^0.2.0", 14 | "prop-types": "^15.7.2", 15 | "qs": "^6.7.0", 16 | "react": "^16.8.6", 17 | "react-check-auth": "^0.2.0-alpha.2", 18 | "react-dom": "^16.8.6", 19 | "react-number-format": "^4.0.8", 20 | "react-redux": "^7.1.0", 21 | "react-router": "^5.0.1", 22 | "react-router-dom": "^5.0.1", 23 | "react-scripts": "^3.0.1", 24 | "reactstrap": "^8.0.1", 25 | "reactstrap-typeahead": "^1.0.1", 26 | "redux": "^4.0.4", 27 | "redux-promise": "^0.6.0", 28 | "redux-thunk": "^2.3.0", 29 | "yup": "^0.27.0" 30 | }, 31 | "scripts": { 32 | "start": "react-scripts start", 33 | "build": "react-scripts build", 34 | "test": "react-scripts test --env=jsdom", 35 | "eject": "react-scripts eject" 36 | }, 37 | "eslintConfig": { 38 | "extends": "react-app" 39 | }, 40 | "browserslist": [ 41 | ">0.2%", 42 | "not dead", 43 | "not ie <= 11", 44 | "not op_mini all" 45 | ], 46 | "devDependencies": { 47 | "create-react-component-folder": "^0.1.30" 48 | }, 49 | "crcf": [ 50 | "scss" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /client/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gokerDEV/loopback-react/dd91ed094e6659f00eafbf221cd23d26a278b7c8/client/public/favicon.png -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | KodKafa | React-Loopback Full-stack Playground 10 | 13 | 14 | 15 | 17 | 19 | 20 | 21 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "128x128", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import Qs from 'qs'; 2 | import axios from 'axios'; 3 | import History from '../history.js'; 4 | import * as type from './types'; 5 | 6 | const API_URL = process.env.REACT_APP_API_URL; 7 | const FILE_URL = process.env.REACT_APP_FILE_URL; 8 | let TOKEN = localStorage.getItem('token'); 9 | let UID = localStorage.getItem('uid'); 10 | 11 | const errorBeautifier = error => { 12 | return error.hasOwnProperty('payload') 13 | ? error 14 | : {type: type.ERROR, payload: {data: {error: {name: 'Error', message: error.message}}}} 15 | }; 16 | 17 | axios.interceptors.response.use( 18 | response => response, 19 | error => Promise.reject(error && error.response 20 | ? {type: type.ERROR, payload: error.response} 21 | : errorBeautifier(error)) 22 | ); 23 | axios.interceptors.request.use(config => { 24 | console.log(config); 25 | config.paramsSerializer = params => { 26 | // Qs is already included in the Axios package 27 | return Qs.stringify(params, { 28 | arrayFormat: "brackets", 29 | encode: false 30 | }); 31 | }; 32 | return config; 33 | }); 34 | 35 | export const signUp = (data) => { 36 | return (dispatch) => { 37 | axios.post(`${API_URL}/users`, data) 38 | .then(() => { 39 | dispatch({ 40 | type: type.SUCCESS, 41 | payload: {name: 'SUCCESS', message: 'Check your email for confirmation link.'} 42 | }); 43 | setTimeout(() => { 44 | History.push('/signin') 45 | }, 1500); 46 | }) 47 | .catch(error => dispatch(error)); 48 | }; 49 | }; 50 | 51 | export const setSession = (response) => { 52 | //response.isAdmin = response.roles.find(x => x.name === 'admin'); 53 | //response.isEditor = response.roles.find(x => x.name === 'editor'); 54 | sessionStorage.setItem('me', JSON.stringify(response)); 55 | return response; 56 | }; 57 | 58 | export const signIn = (data) => { 59 | return async (dispatch) => { 60 | await axios.post(`${API_URL}/users/login`, data) 61 | .then(response => { 62 | localStorage.setItem('token', response.data.id); 63 | localStorage.setItem('uid', response.data.userId); 64 | localStorage.setItem('ttl', response.data.ttl); 65 | TOKEN = localStorage.getItem('token'); 66 | UID = localStorage.getItem('uid'); 67 | getUser(response.data.userId) 68 | .then(response => { 69 | dispatch({type: type.AUTH_USER, payload: setSession(response)}); 70 | History.push('/home'); 71 | }) 72 | .catch(error => dispatch(error)); 73 | }) 74 | .catch(error => dispatch(error)); 75 | }; 76 | }; 77 | 78 | export const signOut = () => { 79 | return (dispatch) => { 80 | localStorage.removeItem('token'); 81 | sessionStorage.removeItem('me'); 82 | TOKEN = null; 83 | dispatch({ 84 | type: type.UNAUTH_USER, 85 | payload: null 86 | }); 87 | History.push('/'); 88 | } 89 | }; 90 | 91 | export const resetPasswordRequest = (email) => { 92 | return (dispatch) => { 93 | axios.post(`${API_URL}/users/reset`, {email}) 94 | .then(() => { 95 | dispatch({ 96 | type: type.SUCCESS, 97 | payload: {name: 'SUCCESS', message: 'We sent an email to you. Please check your email.'} 98 | }) 99 | }) 100 | .catch(error => dispatch(error)); 101 | }; 102 | }; 103 | 104 | export const resetPassword = ({token, password}) => { 105 | return (dispatch) => { 106 | axios.post(`${API_URL}/users/reset-password`, {newPassword: password}, { 107 | headers: {authorization: token} 108 | }) 109 | .then(() => { 110 | dispatch({ 111 | type: type.SUCCESS, 112 | payload: {name: 'SUCCESS', message: 'You has been changed your password successfully.'} 113 | }); 114 | setTimeout(() => { 115 | History.push('/signin') 116 | }, 1500); 117 | }) 118 | .catch(error => dispatch(error)); 119 | }; 120 | }; 121 | 122 | function computeUser(user) { 123 | user = user || {roles: []}; 124 | user.isAdmin = user.roles.find(x => x.name === 'admin'); 125 | user.isEditor = user.roles.find(x => x.name === 'editor'); 126 | user.isManager = user.roles.find(x => x.name === 'manager'); 127 | user.isWorker = user.roles.find(x => x.name === 'worker'); 128 | user.icon = user.isAdmin ? 'fas fa-user-astronaut' 129 | : user.isEditor ? 'fa fa-user-secret' 130 | : user.isManager || user.isWorker ? 'fa fa-user-tie' : 'fa fa-user'; 131 | if (typeof user.image === 'object') { 132 | user.image.thumb = FILE_URL + user.image.normal; 133 | user.image.normal = FILE_URL + user.image.normal; 134 | user.image.url = FILE_URL + user.image.url; 135 | } else { 136 | user.image = { 137 | thumb: 'http://holder.ninja/50x50,P.svg', 138 | normal: 'http://holder.ninja/250x250,PROFILE.svg', 139 | url: 'http://holder.ninja/500x500,PROFILE.svg' 140 | }; 141 | } 142 | if (typeof user.cover === 'object') { 143 | user.cover.thumb = FILE_URL + user.cover.normal; 144 | user.cover.normal = FILE_URL + user.cover.normal; 145 | user.cover.url = FILE_URL + user.cover.url; 146 | } else { 147 | user.cover = { 148 | thumb: 'http://holder.ninja/400x120,COVER-1200x360.svg', 149 | normal: 'http://holder.ninja/1200x360,COVER-1200x360.svg', 150 | url: 'http://holder.ninja/1200x360,COVER-1200x360.svg' 151 | }; 152 | } 153 | return user; 154 | } 155 | 156 | export const getUser = (uid) => { 157 | return new Promise((resolve, reject) => { 158 | axios.get(`${API_URL}/users/${uid}`, { 159 | headers: {authorization: localStorage.getItem('token')} 160 | }) 161 | .then(response => { 162 | resolve(computeUser(response.data)) 163 | }) 164 | .catch(error => { 165 | reject(error.response && error.response.data && error.response.data.error) 166 | }); 167 | }); 168 | }; 169 | 170 | export const fetchUser = (id) => { 171 | return (dispatch) => { 172 | axios.get(`${API_URL}/users/${id}`, { 173 | headers: {authorization: localStorage.getItem('token')} 174 | }) 175 | .then(response => { 176 | dispatch({type: type.DATA, payload: computeUser(response.data)}); 177 | }) 178 | .catch(error => dispatch(error)); 179 | } 180 | }; 181 | 182 | export const searchUserByName = async (name) => { 183 | const query = JSON.stringify({where: {name: {like: name, options: 'i'}}}); 184 | try { 185 | const response = await axios.get(`${API_URL}/users?filter=${query}`, { 186 | headers: {authorization: localStorage.getItem('token')} 187 | }); 188 | console.log('searchUserByName', response); 189 | return response.data 190 | } catch (error) { 191 | throw error 192 | } 193 | }; 194 | 195 | export const getProfile = (username) => { 196 | return (dispatch) => { 197 | axios.get(`${API_URL}/users/profile/${username}`, { 198 | headers: {authorization: localStorage.getItem('token')} 199 | }) 200 | .then(response => { 201 | dispatch({type: type.DATA, payload: computeUser(response.data.user)}); 202 | }) 203 | .catch(error => { 204 | dispatch(errorBeautifier(error)) 205 | }); 206 | } 207 | 208 | // return new Promise((resolve, reject) => { 209 | // axios.get(`${API_URL}/users/profile/${username}`, { 210 | // headers: {authorization: localStorage.getItem('token')} 211 | // }) 212 | // .then(response => { 213 | // resolve(computeUser(response.data.user)) 214 | // }) 215 | // .catch(error => { 216 | // reject(error.response.data.error) 217 | // }); 218 | // }); 219 | }; 220 | 221 | export const getSession = (callback) => { 222 | const token = localStorage.getItem('token'); 223 | const me = sessionStorage.getItem('me'); 224 | if (token) { 225 | if (me) { 226 | callback(JSON.parse(me)); 227 | } else { 228 | getUser(localStorage.getItem('uid')) 229 | .then((response) => { 230 | callback(setSession(response)) 231 | }) 232 | .catch((error) => { 233 | localStorage.removeItem('token'); 234 | localStorage.removeItem('uid'); 235 | localStorage.removeItem('ttl'); 236 | window.location.reload(true); 237 | throw error; 238 | }); 239 | } 240 | } else callback(null) 241 | }; 242 | 243 | export const addUser = (data) => { 244 | return (dispatch) => { 245 | axios.post(`${API_URL}/users`, data) 246 | .then(() => { 247 | History.push('/users'); 248 | }) 249 | .catch(error => dispatch(error)); 250 | }; 251 | }; 252 | 253 | export const settingsAccount = (data) => { 254 | return (dispatch) => { 255 | axios.patch(`${API_URL}/users/${UID}`, data, 256 | {headers: {authorization: TOKEN}}) 257 | .then(response => { 258 | dispatch({type: type.AUTH_USER, payload: setSession(computeUser(response.data))}); 259 | dispatch({ 260 | type: type.SUCCESS, 261 | payload: {name: 'SUCCESS', message: 'Your account has been updated successfully!'} 262 | }); 263 | }) 264 | .catch(error => dispatch(error)); 265 | }; 266 | }; 267 | 268 | export const settingsChangePassword = ({oldPassword, newPassword}) => { 269 | return (dispatch) => { 270 | axios.post(`${API_URL}/users/change-password`, {oldPassword, newPassword}, 271 | {headers: {authorization: localStorage.getItem('token')}}) 272 | .then(() => { 273 | dispatch({ 274 | type: type.SUCCESS, 275 | payload: {name: 'SUCCESS', message: 'Your password has been changed successfully!'} 276 | }); 277 | }) 278 | .catch(error => dispatch(error)); 279 | } 280 | }; 281 | 282 | export const getUsers = () => { 283 | return (dispatch) => { 284 | axios.get(`${API_URL}/users`, { 285 | headers: {authorization: localStorage.getItem('token')} 286 | }) 287 | .then(response => { 288 | dispatch({type: type.DATA, payload: response.data.map(user => computeUser(user))}); 289 | }) 290 | .catch(error => dispatch(error)); 291 | } 292 | }; 293 | 294 | export const fetchUsers = (params = {}) => { 295 | return (dispatch) => { 296 | axios.get(`${API_URL}/users/list`, { 297 | headers: {authorization: localStorage.getItem('token')}, 298 | params 299 | }) 300 | .then(response => { 301 | response.data.list.items.map(user => computeUser(user)); 302 | dispatch({type: type.DATA, payload: response.data.list}); 303 | }) 304 | .catch(error => dispatch(error)); 305 | } 306 | }; 307 | 308 | export const updateUser = async (data) => { 309 | await axios.patch(`${API_URL}/users/${data.id}`, data, 310 | {headers: {authorization: TOKEN}}) 311 | .then(response => { 312 | return response.data 313 | }) 314 | .catch(error => { 315 | throw error.response.data.error.message; 316 | }); 317 | }; 318 | 319 | export const toggleAdmin = (id, toggleType = 'Admin') => { 320 | return (dispatch) => { 321 | axios.post(`${API_URL}/users/${id}/toggle${toggleType}`, {id}, 322 | {headers: {authorization: TOKEN}}) 323 | .then(response => { 324 | dispatch({type: type.DATA, payload: computeUser(response.data.data)}); 325 | }) 326 | .catch(error => { 327 | dispatch({ 328 | type: type.ERROR, 329 | payload: error.response 330 | }); 331 | }); 332 | } 333 | }; 334 | 335 | export const toggleEditor = (id) => { 336 | return toggleAdmin(id, 'Editor') 337 | }; 338 | 339 | export const toggleManager = (id) => { 340 | return toggleAdmin(id, 'Manager') 341 | }; 342 | 343 | export const toggleWorker = (id) => { 344 | return toggleAdmin(id, 'Worker') 345 | }; 346 | 347 | export const toggleStatus = (id) => { 348 | return toggleAdmin(id, 'Status') 349 | }; 350 | 351 | export const uploadCoverImage = (file) => { 352 | return async (dispatch) => { 353 | await axios.post(`${API_URL}/users/${UID}/cover`, file, { 354 | headers: {authorization: TOKEN}, 355 | onUploadProgress: progressEvent => { 356 | console.log(progressEvent.loaded, progressEvent.total) 357 | } 358 | }) 359 | .then(response => { 360 | dispatch({type: type.AUTH_USER, payload: setSession(computeUser(response.data.user))}); 361 | }) 362 | .catch(error => dispatch(error)); 363 | }; 364 | }; 365 | 366 | export const uploadProfileImage = (file) => { 367 | return async (dispatch) => { 368 | await axios.post(`${API_URL}/users/${UID}/image`, file, { 369 | headers: {authorization: TOKEN}, 370 | onUploadProgress: progressEvent => { 371 | console.log(progressEvent.loaded, progressEvent.total) 372 | } 373 | }) 374 | .then(response => { 375 | dispatch({type: type.AUTH_USER, payload: setSession(computeUser(response.data.user))}); 376 | }) 377 | .catch(error => dispatch(error)); 378 | }; 379 | }; 380 | 381 | -------------------------------------------------------------------------------- /client/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const ERROR = 'error'; 2 | export const SUCCESS = 'success'; 3 | export const DATA = 'data'; 4 | export const FILE = 'file'; 5 | 6 | export const AUTH_USER = 'auth_user'; 7 | export const UNAUTH_USER = 'unauth_user'; 8 | export const AUTH_ERROR = 'auth_error'; 9 | export const CHANGEPASSWORD_ERROR = 'changepassword_error'; 10 | export const CHANGEPASSWORD_SUCCESS = 'changepassword_success'; 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from 'react'; 2 | import Navbar from './Common/Navbar' 3 | import Footer from './Common/Footer' 4 | 5 | import '../styles/App.scss'; 6 | 7 | class App extends PureComponent { 8 | constructor(props) { 9 | super(props) 10 | this.state = { 11 | me: props.me 12 | } 13 | } 14 | 15 | render() { 16 | console.log('App render', this.state.me); 17 | return ( 18 | 19 | 20 |
21 | {this.props.children} 22 |
23 |