Преглед на файлове

working exemple with login through private routes

Skylsmoi преди 6 години
родител
ревизия
9ec1e32115

+ 1 - 0
jsonserver/server.js Целия файл

@@ -17,6 +17,7 @@ server.post('/user/login', (req, res) => {
17 17
 })
18 18
 server.get('/user_logged', (req, res) => res.jsonp(jsonDb.user_logged))
19 19
 server.delete('/deletenodata', (req,res) => res.status(204).jsonp(''))
20
+server.patch('/user', (req, res) => res.jsonp({lang: 'fr'}))
20 21
 
21 22
 // server.put('/api/data/raw_materials_vendors/:vendorid', (req, res) => {
22 23
 //  res.jsonp(jsonVendorColorData.vendorVariableData)

+ 2 - 2
package.json Целия файл

@@ -21,13 +21,14 @@
21 21
     "babel-polyfill": "^6.26.0",
22 22
     "babel-preset-env": "^1.6.1",
23 23
     "babel-preset-react": "^6.24.1",
24
+    "classnames": "^2.2.5",
24 25
     "css-loader": "^0.28.7",
25 26
     "file-loader": "^1.1.5",
26 27
     "prop-types": "^15.6.0",
27 28
     "react": "^16.0.0",
28 29
     "react-dom": "^16.0.0",
29 30
     "react-redux": "^5.0.6",
30
-    "react-router": "^4.2.0",
31
+    "react-router-dom": "^4.2.2",
31 32
     "redux": "^3.7.2",
32 33
     "redux-logger": "^3.0.6",
33 34
     "redux-thunk": "^2.2.0",
@@ -42,7 +43,6 @@
42 43
   },
43 44
   "devDependencies": {
44 45
     "json-server": "^0.12.0",
45
-    "react-router-dom": "^4.2.2",
46 46
     "webpack-dev-server": "^2.9.2"
47 47
   },
48 48
   "standard": {

+ 16 - 7
src/action-creator.async.js Целия файл

@@ -1,8 +1,10 @@
1 1
 import { FETCH_CONFIG } from './helper.js'
2 2
 import {
3 3
   USER_LOGIN,
4
+  USER_DATA,
4 5
   USER_CONNECTED,
5
-  updateUserConnected
6
+  updateUserConnected,
7
+  updateUserData
6 8
 } from './action-creator.sync.js'
7 9
 
8 10
 /*
@@ -32,24 +34,21 @@ const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) =
32 34
       case 304:
33 35
         return fetchResult.json()
34 36
       case 204:
35
-        return ''
36 37
       case 400:
37 38
       case 404:
38
-        return ''
39 39
       case 409:
40
-        return ''
41 40
       case 500:
42 41
       case 501:
43 42
       case 502:
44 43
       case 503:
45 44
       case 504:
46
-        const jsonRes = await fetchResult.json()
47
-        return jsonRes.errorHandled ? jsonRes.errorMsg : ''
45
+        return '' // @TODO : handle errors
48 46
     }
49 47
   })()
50 48
   if (debug) console.log(`fetch ${param.method}/${actionName} result: `, fetchResult)
51 49
 
52
-  dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
50
+  if ([200, 204, 304].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
51
+  else if ([400, 404, 500].includes(fetchResult.status)) dispatch({type: `${param.method}/${actionName}/FAILED`, data: fetchResult.json})
53 52
 
54 53
   return fetchResult
55 54
 }
@@ -74,6 +73,16 @@ export const getUserConnected = () => async dispatch => {
74 73
   if (fetchUserLogged.status === 200) dispatch(updateUserConnected(fetchUserLogged.json))
75 74
 }
76 75
 
76
+export const updateUserLang = newLang => async dispatch => {
77
+  const fetchUpdateUserLang = await fetchWrapper({
78
+    url: 'http://localhost:3001/user',
79
+    param: {...FETCH_CONFIG, method: 'PATCH', body: JSON.stringify({lang: newLang})},
80
+    actionName: USER_DATA,
81
+    dispatch
82
+  })
83
+  if (fetchUpdateUserLang.status === 200) dispatch(updateUserData({lang: fetchUpdateUserLang.json.lang}))
84
+}
85
+
77 86
 // export const testResponseNoData = () => async dispatch => {
78 87
 //   const fetchResponseNoData = await fetchWrapper({
79 88
 //     url: 'http://localhost:3001/deletenodata',

+ 2 - 0
src/action-creator.sync.js Целия файл

@@ -1,4 +1,6 @@
1 1
 export const USER_LOGIN = 'User/Login'
2
+export const USER_DATA = 'User/Data'
2 3
 
3 4
 export const USER_CONNECTED = 'User/Connected'
4 5
 export const updateUserConnected = user => ({ type: `Update/${USER_CONNECTED}`, user })
6
+export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data: userData })

+ 21 - 3
src/component/ConnectionForm.jsx Целия файл

@@ -1,11 +1,29 @@
1 1
 import React from 'react'
2
+import PropTypes from 'prop-types'
2 3
 
3 4
 export const ConnectionForm = props => {
4 5
   return (
5 6
     <div>
6
-      <input type='text' onChange={props.onChangeLogin} placeholder='Login' />
7
-      <input type='text' onChange={props.onChangePassword} placeholder='Password' />
8
-      <button type='button' onClick={props.onClickSubmit}>Connect</button>
7
+      { props.user.isLoggedIn
8
+        ? 'You are already logged in.'
9
+        : (
10
+          <div>
11
+            Login:
12
+            <input type='text' onChange={props.onChangeLogin} placeholder='Login' />
13
+            <input type='text' onChange={props.onChangePassword} placeholder='Password' />
14
+            <button type='button' onClick={props.onClickSubmit}>Connect</button>
15
+          </div>
16
+        )
17
+      }
9 18
     </div>
10 19
   )
11 20
 }
21
+
22
+ConnectionForm.PropTypes = {
23
+  user: PropTypes.shape({
24
+    isLoggedIn: PropTypes.bool.isRequired
25
+  }),
26
+  onChangeLogin: PropTypes.func,
27
+  onChangePassword: PropTypes.func,
28
+  onClickSubmit: PropTypes.func
29
+}

src/component/Footer.jsx → src/component/FooterTpl.jsx Целия файл

@@ -1,9 +1,10 @@
1 1
 import React from 'react'
2 2
 
3
-export const Footer = props => {
3
+const Footer = props => {
4 4
   return (
5 5
     <div>
6 6
       Footer
7 7
     </div>
8 8
   )
9 9
 }
10
+export default Footer

+ 0 - 12
src/component/Header.jsx Целия файл

@@ -1,12 +0,0 @@
1
-import React from 'react'
2
-
3
-export const Header = props => {
4
-  return (
5
-    <div>
6
-      { props.user.isLogedIn
7
-        ? `'Soir ${props.user.firstname} ${props.user.lastname}.`
8
-        : 'Why dont you connect yourself ?'
9
-      }
10
-    </div>
11
-  )
12
-}

+ 29 - 0
src/component/HeaderTpl.jsx Целия файл

@@ -0,0 +1,29 @@
1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import { Link } from 'react-router-dom'
4
+
5
+const HeaderTpl = props => {
6
+  return (
7
+    <div>
8
+      { props.user.isLoggedIn
9
+        ? `'Soir ${props.user.firstname} ${props.user.lastname}.`
10
+        : 'Why dont you connect yourself ?'
11
+      }
12
+      <ul>
13
+        <li><Link to={'/'}>Home</Link></li>
14
+        <li><Link to={'/login'}>Login</Link></li>
15
+        <li><Link to={'/page'}>Page</Link></li>
16
+      </ul>
17
+      <button onClick={e => props.onChangeLang('fr')}>Click Me</button>
18
+    </div>
19
+  )
20
+}
21
+export default HeaderTpl
22
+
23
+HeaderTpl.PropTypes = {
24
+  user: PropTypes.shape({
25
+    isLoggedIn: PropTypes.bool.isRequired
26
+  }),
27
+  onChangeLang: PropTypes.func,
28
+  onSubmitSearch: PropTypes.func
29
+}

+ 20 - 0
src/container/Header.jsx Целия файл

@@ -0,0 +1,20 @@
1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import HeaderTpl from '../component/HeaderTpl.jsx'
4
+import { updateUserLang } from '../action-creator.async.js'
5
+
6
+class Header extends React.Component {
7
+  handleChangeLang = newLang => this.props.dispatch(updateUserLang(newLang))
8
+  handleSubmitSearch = search => console.log('search submited : ', search)
9
+
10
+  render () {
11
+    return (
12
+      <HeaderTpl
13
+        user={this.props.user}
14
+        onChangeLang={this.handleChangeLang}
15
+        onSubmitSearch={this.handleSubmitSearch}
16
+      />
17
+    )
18
+  }
19
+}
20
+export default connect(({ user }) => ({ user }))(Header)

+ 17 - 0
src/container/Home.jsx Целия файл

@@ -0,0 +1,17 @@
1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+
4
+class Home extends React.Component {
5
+  render () {
6
+    const { user } = this.props
7
+    return (
8
+      <div>
9
+        Home.<br />
10
+        User logged in : {user.isLoggedIn.toString()}
11
+      </div>
12
+    )
13
+  }
14
+}
15
+
16
+const mapStateToProps = ({ user }) => ({ user })
17
+export default connect(mapStateToProps)(Home)

+ 1 - 4
src/container/Login.jsx Целия файл

@@ -1,8 +1,6 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
-import { Header } from '../component/Header.jsx'
4 3
 import { ConnectionForm } from '../component/ConnectionForm.jsx'
5
-import { Footer } from '../component/Footer.jsx'
6 4
 import { userLogin } from '../action-creator.async.js'
7 5
 
8 6
 class Login extends React.Component {
@@ -23,13 +21,12 @@ class Login extends React.Component {
23 21
     const { user } = this.props
24 22
     return (
25 23
       <div>
26
-        <Header user={user} />
27 24
         <ConnectionForm
25
+          user={user}
28 26
           onChangeLogin={this.handleChangeLogin}
29 27
           onChangePassword={this.handleChangePassword}
30 28
           onClickSubmit={this.handleClickSubmit}
31 29
         />
32
-        <Footer />
33 30
       </div>
34 31
     )
35 32
   }

+ 1 - 5
src/container/Page.jsx Целия файл

@@ -1,16 +1,12 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
-import { Header } from '../component/Header.jsx'
4
-import { Footer } from '../component/Footer.jsx'
5 3
 
6 4
 class Page extends React.Component {
7 5
   render () {
8 6
     const { user } = this.props
9 7
     return (
10 8
       <div>
11
-        <Header user={user} />
12
-        <div>You are now connected, gg</div>
13
-        <Footer />
9
+        Page ! user Logged in : {user.isLoggedIn.toString()}
14 10
       </div>
15 11
     )
16 12
   }

+ 29 - 0
src/container/PrivateRoute.jsx Целия файл

@@ -0,0 +1,29 @@
1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import { connect } from 'react-redux'
4
+import { Route, Redirect, withRouter } from 'react-router-dom'
5
+
6
+// component inspired from https://reacttraining.com/react-router/web/example/auth-workflow
7
+
8
+// /!\ you shall destruct props.component otherwise you get a warning:
9
+// "You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored
10
+const PrivateRoute = ({ component: Component, ...rest }) => {
11
+  return (
12
+    <Route {...rest} render={props => rest.user.isLoggedIn
13
+      ? <Component {...props} />
14
+      : <Redirect to={{pathname: '/login', state: {from: props.location}}} />
15
+    } />
16
+  )
17
+}
18
+
19
+const mapStateToProps = ({ user }) => ({ user })
20
+export default withRouter(connect(mapStateToProps)(PrivateRoute))
21
+
22
+PrivateRoute.PropTypes = {
23
+  component: PropTypes.shape({
24
+    Component: PropTypes.object.isRequired
25
+  }),
26
+  user: PropTypes.shape({ // user is get with redux connect
27
+    isLoggedIn: PropTypes.bool.isRequired
28
+  })
29
+}

+ 18 - 12
src/container/Tracim.jsx Целия файл

@@ -1,27 +1,33 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
+import Footer from '../component/FooterTpl.jsx'
4
+import HeaderContainer from './Header.jsx'
3 5
 import Login from './Login.jsx'
4 6
 import Page from './Page.jsx'
7
+import Home from './Home.jsx'
5 8
 import {
6
-  BrowserRouter,
7
-  Route
9
+  Route,
10
+  withRouter
8 11
 } from 'react-router-dom'
12
+import PrivateRoute from './PrivateRoute.jsx'
9 13
 
10 14
 class Tracim extends React.Component {
11 15
   render () {
12 16
     return (
13
-      <BrowserRouter>
14
-        <div>
15
-          <Route path='/' render={() =>
16
-            this.props.user.isLogedIn
17
-              ? <Page />
18
-              : <Login />
19
-          } />
20
-        </div>
21
-      </BrowserRouter>
17
+      <div>
18
+        <HeaderContainer />
19
+
20
+        <br /><hr /><br />
21
+
22
+        <PrivateRoute exact path='/' component={Home} />
23
+        <Route path='/login' component={Login} />
24
+        <PrivateRoute path='/page' component={Page} />
25
+
26
+        <Footer />
27
+      </div>
22 28
     )
23 29
   }
24 30
 }
25 31
 
26 32
 const mapStateToProps = ({ user }) => ({ user })
27
-export default connect(mapStateToProps)(Tracim)
33
+export default withRouter(connect(mapStateToProps)(Tracim))

+ 4 - 1
src/index.js Целия файл

@@ -3,12 +3,15 @@ import ReactDOM from 'react-dom'
3 3
 import { Provider } from 'react-redux'
4 4
 import { store } from './store.js'
5 5
 import Tracim from './container/Tracim.jsx'
6
+import { BrowserRouter } from 'react-router-dom'
6 7
 
7 8
 // require('./css/index.styl')
8 9
 
9 10
 ReactDOM.render(
10 11
   <Provider store={store}>
11
-    <Tracim />
12
+    <BrowserRouter>
13
+      <Tracim />
14
+    </BrowserRouter>
12 15
   </Provider>
13 16
   , document.getElementById('content')
14 17
 )

+ 9 - 3
src/reducer/user.js Целия файл

@@ -1,13 +1,19 @@
1
-import { USER_CONNECTED } from '../action-creator.sync.js'
1
+import {
2
+  USER_CONNECTED,
3
+  USER_DATA
4
+} from '../action-creator.sync.js'
2 5
 
3 6
 export default function user (state = {
4
-  isLogedIn: false,
7
+  isLoggedIn: false,
5 8
   username: '',
6 9
   email: ''
7 10
 }, action) {
8 11
   switch (action.type) {
9 12
     case `Update/${USER_CONNECTED}`:
10
-      return {...state, isLogedIn: true, ...action.user}
13
+      return {...state, isLoggedIn: true, ...action.user}
14
+
15
+    case `Update/${USER_DATA}`:
16
+      return {...state, ...action.data}
11 17
 
12 18
     default:
13 19
       return state

+ 19 - 19
webpack.config.js Целия файл

@@ -16,7 +16,6 @@ module.exports = {
16 16
       'react',
17 17
       'react-dom',
18 18
       'react-redux',
19
-      // 'react-router',
20 19
       'react-router-dom',
21 20
       // 'react-select',
22 21
       'redux',
@@ -24,7 +23,7 @@ module.exports = {
24 23
       // 'redux-saga',
25 24
       'redux-thunk',
26 25
       'whatwg-fetch',
27
-      // 'classnames'
26
+      'classnames'
28 27
     ]
29 28
   },
30 29
   output: {
@@ -40,12 +39,13 @@ module.exports = {
40 39
     overlay: {
41 40
       warnings: false,
42 41
       errors: true
43
-    }
42
+    },
43
+    historyApiFallback: true
44 44
     // headers: {
45 45
     //   'Access-Control-Allow-Origin': '*'
46 46
     // }
47 47
   },
48
-  devtool: isProduction ? false : 'cheap-eval-source-map',
48
+  devtool: isProduction ? false : 'cheap-module-source-map',
49 49
   module: {
50 50
     rules: [{
51 51
       test: /\.jsx?$/,
@@ -77,23 +77,23 @@ module.exports = {
77 77
   resolve: {
78 78
     extensions: ['.js', '.jsx']
79 79
   },
80
-  plugins: isProduction
81
-    ? [
80
+  plugins:[
81
+    ...[ // generic plugins always present
82 82
       new webpack.optimize.CommonsChunkPlugin({
83 83
         name: 'vendor',
84 84
         filename: 'tracim.vendor.bundle.js'
85
-      }),
86
-      new webpack.DefinePlugin({
87
-        'process.env': { 'NODE_ENV': JSON.stringify('production') }
88
-      }),
89
-      new webpack.optimize.UglifyJsPlugin({
90
-        compress: { warnings: false }
91 85
       })
92
-    ]
93
-    : [
94
-      new webpack.optimize.CommonsChunkPlugin({
95
-        name: 'vendor',
96
-        filename: 'tracim.vendor.bundle.js'
97
-      })
98
-    ]
86
+    ],
87
+    ...(isProduction
88
+      ? [ // production specific plugins
89
+        new webpack.DefinePlugin({
90
+          'process.env': { 'NODE_ENV': JSON.stringify('production') }
91
+        }),
92
+        new webpack.optimize.UglifyJsPlugin({
93
+          compress: { warnings: false }
94
+        })
95
+      ]
96
+      : [] // development specific plugins
97
+    )
98
+  ]
99 99
 }