Browse Source

working exemple with login through private routes

Skylsmoi 7 years ago
parent
commit
9ec1e32115

+ 1 - 0
jsonserver/server.js View File

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

+ 2 - 2
package.json View File

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

+ 16 - 7
src/action-creator.async.js View File

1
 import { FETCH_CONFIG } from './helper.js'
1
 import { FETCH_CONFIG } from './helper.js'
2
 import {
2
 import {
3
   USER_LOGIN,
3
   USER_LOGIN,
4
+  USER_DATA,
4
   USER_CONNECTED,
5
   USER_CONNECTED,
5
-  updateUserConnected
6
+  updateUserConnected,
7
+  updateUserData
6
 } from './action-creator.sync.js'
8
 } from './action-creator.sync.js'
7
 
9
 
8
 /*
10
 /*
32
       case 304:
34
       case 304:
33
         return fetchResult.json()
35
         return fetchResult.json()
34
       case 204:
36
       case 204:
35
-        return ''
36
       case 400:
37
       case 400:
37
       case 404:
38
       case 404:
38
-        return ''
39
       case 409:
39
       case 409:
40
-        return ''
41
       case 500:
40
       case 500:
42
       case 501:
41
       case 501:
43
       case 502:
42
       case 502:
44
       case 503:
43
       case 503:
45
       case 504:
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
   if (debug) console.log(`fetch ${param.method}/${actionName} result: `, fetchResult)
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
   return fetchResult
53
   return fetchResult
55
 }
54
 }
74
   if (fetchUserLogged.status === 200) dispatch(updateUserConnected(fetchUserLogged.json))
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
 // export const testResponseNoData = () => async dispatch => {
86
 // export const testResponseNoData = () => async dispatch => {
78
 //   const fetchResponseNoData = await fetchWrapper({
87
 //   const fetchResponseNoData = await fetchWrapper({
79
 //     url: 'http://localhost:3001/deletenodata',
88
 //     url: 'http://localhost:3001/deletenodata',

+ 2 - 0
src/action-creator.sync.js View File

1
 export const USER_LOGIN = 'User/Login'
1
 export const USER_LOGIN = 'User/Login'
2
+export const USER_DATA = 'User/Data'
2
 
3
 
3
 export const USER_CONNECTED = 'User/Connected'
4
 export const USER_CONNECTED = 'User/Connected'
4
 export const updateUserConnected = user => ({ type: `Update/${USER_CONNECTED}`, user })
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 View File

1
 import React from 'react'
1
 import React from 'react'
2
+import PropTypes from 'prop-types'
2
 
3
 
3
 export const ConnectionForm = props => {
4
 export const ConnectionForm = props => {
4
   return (
5
   return (
5
     <div>
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
     </div>
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 View File

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

+ 0 - 12
src/component/Header.jsx View File

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 View File

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 View File

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 View File

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 View File

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

+ 1 - 5
src/container/Page.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
-import { Header } from '../component/Header.jsx'
4
-import { Footer } from '../component/Footer.jsx'
5
 
3
 
6
 class Page extends React.Component {
4
 class Page extends React.Component {
7
   render () {
5
   render () {
8
     const { user } = this.props
6
     const { user } = this.props
9
     return (
7
     return (
10
       <div>
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
       </div>
10
       </div>
15
     )
11
     )
16
   }
12
   }

+ 29 - 0
src/container/PrivateRoute.jsx View File

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 View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
+import Footer from '../component/FooterTpl.jsx'
4
+import HeaderContainer from './Header.jsx'
3
 import Login from './Login.jsx'
5
 import Login from './Login.jsx'
4
 import Page from './Page.jsx'
6
 import Page from './Page.jsx'
7
+import Home from './Home.jsx'
5
 import {
8
 import {
6
-  BrowserRouter,
7
-  Route
9
+  Route,
10
+  withRouter
8
 } from 'react-router-dom'
11
 } from 'react-router-dom'
12
+import PrivateRoute from './PrivateRoute.jsx'
9
 
13
 
10
 class Tracim extends React.Component {
14
 class Tracim extends React.Component {
11
   render () {
15
   render () {
12
     return (
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
 const mapStateToProps = ({ user }) => ({ user })
32
 const mapStateToProps = ({ user }) => ({ user })
27
-export default connect(mapStateToProps)(Tracim)
33
+export default withRouter(connect(mapStateToProps)(Tracim))

+ 4 - 1
src/index.js View File

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

+ 9 - 3
src/reducer/user.js View File

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
 export default function user (state = {
6
 export default function user (state = {
4
-  isLogedIn: false,
7
+  isLoggedIn: false,
5
   username: '',
8
   username: '',
6
   email: ''
9
   email: ''
7
 }, action) {
10
 }, action) {
8
   switch (action.type) {
11
   switch (action.type) {
9
     case `Update/${USER_CONNECTED}`:
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
     default:
18
     default:
13
       return state
19
       return state

+ 19 - 19
webpack.config.js View File

16
       'react',
16
       'react',
17
       'react-dom',
17
       'react-dom',
18
       'react-redux',
18
       'react-redux',
19
-      // 'react-router',
20
       'react-router-dom',
19
       'react-router-dom',
21
       // 'react-select',
20
       // 'react-select',
22
       'redux',
21
       'redux',
24
       // 'redux-saga',
23
       // 'redux-saga',
25
       'redux-thunk',
24
       'redux-thunk',
26
       'whatwg-fetch',
25
       'whatwg-fetch',
27
-      // 'classnames'
26
+      'classnames'
28
     ]
27
     ]
29
   },
28
   },
30
   output: {
29
   output: {
40
     overlay: {
39
     overlay: {
41
       warnings: false,
40
       warnings: false,
42
       errors: true
41
       errors: true
43
-    }
42
+    },
43
+    historyApiFallback: true
44
     // headers: {
44
     // headers: {
45
     //   'Access-Control-Allow-Origin': '*'
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
   module: {
49
   module: {
50
     rules: [{
50
     rules: [{
51
       test: /\.jsx?$/,
51
       test: /\.jsx?$/,
77
   resolve: {
77
   resolve: {
78
     extensions: ['.js', '.jsx']
78
     extensions: ['.js', '.jsx']
79
   },
79
   },
80
-  plugins: isProduction
81
-    ? [
80
+  plugins:[
81
+    ...[ // generic plugins always present
82
       new webpack.optimize.CommonsChunkPlugin({
82
       new webpack.optimize.CommonsChunkPlugin({
83
         name: 'vendor',
83
         name: 'vendor',
84
         filename: 'tracim.vendor.bundle.js'
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
 }