Browse Source

connected login and logout on real api + integration of flash messages

Skylsmoi 6 years ago
parent
commit
67cebc1baf

+ 2 - 2
jsonserver/server.js View File

40
 
40
 
41
 server.get('/lang', (req, res) => res.jsonp(jsonDb.lang))
41
 server.get('/lang', (req, res) => res.jsonp(jsonDb.lang))
42
 
42
 
43
-server.post('/user/login', (req, res) => {
43
+server.post('/sessions/login', (req, res) => {
44
   if (req.body.login !== '' && req.body.password !== '') return res.jsonp(jsonDb.user_logged)
44
   if (req.body.login !== '' && req.body.password !== '') return res.jsonp(jsonDb.user_logged)
45
   else return res.jsonp('error')
45
   else return res.jsonp('error')
46
 })
46
 })
47
 
47
 
48
 server.get('/app/config', (req, res) => res.jsonp(jsonDb.app_config))
48
 server.get('/app/config', (req, res) => res.jsonp(jsonDb.app_config))
49
 
49
 
50
-server.get('/user/is_logged_in', (req, res) =>
50
+server.get('/sessions/whoami', (req, res) =>
51
   // res.jsonp({"logged": false})
51
   // res.jsonp({"logged": false})
52
   res.jsonp(jsonDb.user_logged)
52
   res.jsonp(jsonDb.user_logged)
53
 )
53
 )

+ 12 - 11
jsonserver/static_db.json View File

11
     "active": false
11
     "active": false
12
   }],
12
   }],
13
   "user_logged": {
13
   "user_logged": {
14
-    "logged": true,
15
-    "user": {
16
-      "id": 5,
17
-      "username": "franclecompte",
18
-      "firstname": "François",
19
-      "lastname": "Lecompte",
20
-      "email": "francois.lecompte@algoo.fr",
21
-      "avatar": "https://rawgit.com/tracim/tracim_front/develop/src/img/imgProfil.png",
22
-      "role": "Utilisateur",
23
-      "caldav_url": "http://algoo.trac.im/caldav/user/5.ics/"
24
-    }
14
+    "timezone": "",
15
+    "profile": {
16
+      "id": 3,
17
+      "slug": "administrators"
18
+    },
19
+    "email": "admin@admin.admin",
20
+    "is_active": true,
21
+    "caldav_url": null,
22
+    "user_id": 1,
23
+    "avatar_url": null,
24
+    "created": "2018-05-17T08:43:03.745219+00:00",
25
+    "display_name": "Global manager"
25
   },
26
   },
26
   "app_config": [{
27
   "app_config": [{
27
     "name": "PageHtml",
28
     "name": "PageHtml",

+ 19 - 11
src/action-creator.async.js View File

5
   LANG,
5
   LANG,
6
   updateLangList,
6
   updateLangList,
7
   USER_LOGIN,
7
   USER_LOGIN,
8
+  USER_LOGOUT,
8
   USER_DATA,
9
   USER_DATA,
9
   USER_ROLE,
10
   USER_ROLE,
10
   USER_CONNECTED,
11
   USER_CONNECTED,
11
-  setUserConnected,
12
   updateUserData,
12
   updateUserData,
13
   setUserRole,
13
   setUserRole,
14
   WORKSPACE,
14
   WORKSPACE,
89
 }
89
 }
90
 
90
 
91
 export const postUserLogin = (login, password, rememberMe) => async dispatch => {
91
 export const postUserLogin = (login, password, rememberMe) => async dispatch => {
92
-  const fetchUserLogin = await fetchWrapper({
93
-    url: `${FETCH_CONFIG.apiUrl}/sessions/login`,
92
+  return fetchWrapper({
93
+    url: `${FETCH_CONFIG.mockApiUrl}/sessions/login`,
94
     param: {
94
     param: {
95
       headers: {...FETCH_CONFIG.headers},
95
       headers: {...FETCH_CONFIG.headers},
96
       method: 'POST',
96
       method: 'POST',
97
       body: JSON.stringify({
97
       body: JSON.stringify({
98
         email: login,
98
         email: login,
99
-        password: password
100
-        // remember_me: rememberMe
99
+        password: password,
100
+        remember_me: rememberMe
101
       })
101
       })
102
     },
102
     },
103
     actionName: USER_LOGIN,
103
     actionName: USER_LOGIN,
104
     dispatch
104
     dispatch
105
   })
105
   })
106
-  if (fetchUserLogin.status === 200) dispatch(setUserConnected(fetchUserLogin.json))
106
+}
107
+
108
+export const postUserLogout = () => async dispatch => {
109
+  return fetchWrapper({
110
+    url: `${FETCH_CONFIG.mockApiUrl}/sessions/logout`,
111
+    param: {
112
+      headers: {...FETCH_CONFIG.headers},
113
+      method: 'POST'
114
+    },
115
+    actionName: USER_LOGOUT,
116
+    dispatch
117
+  })
107
 }
118
 }
108
 
119
 
109
 export const getUserIsConnected = () => async dispatch => {
120
 export const getUserIsConnected = () => async dispatch => {
110
-  const fetchUserLogged = await fetchWrapper({
111
-    url: `${FETCH_CONFIG.apiUrl}/sessions/whoami`,
121
+  return fetchWrapper({
122
+    url: `${FETCH_CONFIG.mockApiUrl}/sessions/whoami`,
112
     param: {...FETCH_CONFIG.header, method: 'GET'},
123
     param: {...FETCH_CONFIG.header, method: 'GET'},
113
     actionName: USER_CONNECTED,
124
     actionName: USER_CONNECTED,
114
     dispatch
125
     dispatch
115
   })
126
   })
116
-  if (fetchUserLogged.status === 200) dispatch(setUserConnected(fetchUserLogged.json))
117
-  else if (fetchUserLogged.status === 401) dispatch(setUserConnected({logged: false}))
118
-  else dispatch(setUserConnected({logged: undefined}))
119
 }
127
 }
120
 
128
 
121
 export const getUserRole = user => async dispatch => {
129
 export const getUserRole = user => async dispatch => {

+ 12 - 1
src/action-creator.sync.js View File

1
 export const TIMEZONE = 'Timezone'
1
 export const TIMEZONE = 'Timezone'
2
 export const setTimezone = timezone => ({ type: `Set/${TIMEZONE}`, timezone })
2
 export const setTimezone = timezone => ({ type: `Set/${TIMEZONE}`, timezone })
3
 
3
 
4
+export const FLASH_MESSAGE = 'FlashMessage'
5
+export const newFlashMessage = (msgText = '', msgType = 'info', msgDelay = 5000) => dispatch => {
6
+  msgDelay !== 0 && window.setTimeout(() => dispatch(removeFlashMessage(msgText)), msgDelay)
7
+  return dispatch(addFlashMessage({message: msgText, type: msgType}))
8
+}
9
+export const addFlashMessage = msg => ({ type: `Add/${FLASH_MESSAGE}`, msg })
10
+export const removeFlashMessage = msg => ({ type: `Remove/${FLASH_MESSAGE}`, msg })
11
+
4
 export const USER_LOGIN = 'User/Login'
12
 export const USER_LOGIN = 'User/Login'
13
+export const USER_LOGOUT = 'User/Logout'
5
 export const USER_DATA = 'User/Data'
14
 export const USER_DATA = 'User/Data'
6
 export const USER_ROLE = 'User/Role'
15
 export const USER_ROLE = 'User/Role'
7
 export const USER_CONNECTED = 'User/Connected'
16
 export const USER_CONNECTED = 'User/Connected'
8
-export const setUserConnected = data => ({ type: `Set/${USER_CONNECTED}`, data })
17
+export const USER_DISCONNECTED = 'User/Disconnected'
18
+export const setUserConnected = user => ({ type: `Set/${USER_CONNECTED}`, user })
19
+export const setUserDisconnected = () => ({ type: `Set/${USER_DISCONNECTED}` })
9
 export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data: userData })
20
 export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data: userData })
10
 export const setUserRole = userRole => ({ type: `Set/${USER_ROLE}`, userRole }) // this actually update workspaceList state
21
 export const setUserRole = userRole => ({ type: `Set/${USER_ROLE}`, userRole }) // this actually update workspaceList state
11
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
22
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>

+ 1 - 1
src/appFactory.js View File

4
 export function appFactory (WrappedComponent) {
4
 export function appFactory (WrappedComponent) {
5
   return class AppFactory extends React.Component {
5
   return class AppFactory extends React.Component {
6
     renderApp = (appConfig, user, content) => GLOBAL_renderApp({
6
     renderApp = (appConfig, user, content) => GLOBAL_renderApp({
7
-      loggedUser: user.isLoggedIn ? user : {},
7
+      loggedUser: user.logged ? user : {},
8
       config: {
8
       config: {
9
         ...appConfig,
9
         ...appConfig,
10
         apiUrl: FETCH_CONFIG.apiUrl
10
         apiUrl: FETCH_CONFIG.apiUrl

src/container/FlashMessage.jsx → src/component/FlashMessage.jsx View File

1
-import React, { Component } from 'react'
1
+import React from 'react'
2
+import classnames from 'classnames'
2
 
3
 
3
-class FlashMessage extends Component {
4
-  render() {
5
-    return(
6
-      <div className='flashmessage'>
4
+const FlashMessage = props => {
5
+  return (
6
+    <div className='flashmessage'>
7
+      {props.flashMessage.length > 0 && (
7
         <div className='flashmessage__container card'>
8
         <div className='flashmessage__container card'>
8
-          <div className='flashmessage__container__header' />
9
+          <div className={classnames('flashmessage__container__header', props.flashMessage[0].type)} />
9
 
10
 
10
           <div className='card-body nopadding'>
11
           <div className='card-body nopadding'>
11
-
12
-            <div className='flashmessage__container__close'>
12
+            <div className='flashmessage__container__close' onClick={() => props.removeFlashMessage(props.flashMessage[0].message)}>
13
               <i className='fa fa-times' />
13
               <i className='fa fa-times' />
14
             </div>
14
             </div>
15
 
15
 
16
             <div className='flashmessage__container__content'>
16
             <div className='flashmessage__container__content'>
17
-              <div className='flashmessage__container__content__icon'>
17
+              <div className={classnames('flashmessage__container__content__icon', props.flashMessage[0].type)}>
18
                 <i className='fa fa-times-circle' />
18
                 <i className='fa fa-times-circle' />
19
               </div>
19
               </div>
20
 
20
 
21
               <div className='flashmessage__container__content__text'>
21
               <div className='flashmessage__container__content__text'>
22
                 <div className='flashmessage__container__content__text__title'>
22
                 <div className='flashmessage__container__content__text__title'>
23
-                  Sorry !
23
+                  {props.t('FlashMessage.error')}
24
                 </div>
24
                 </div>
25
                 <div className='flashmessage__container__content__text__paragraph'>
25
                 <div className='flashmessage__container__content__text__paragraph'>
26
-                  Reprehenderit reprehenderit veniam dolore velit dolor velit in occaecat dolor veniam nisi officia velit consequat amet cupidatat.
27
-                  Reprehenderit reprehenderit veniam dolore velit dolor velit in occaecat dolor veniam nisi officia velit consequat amet cupidatat.
26
+                  {props.flashMessage[0].message}
28
                 </div>
27
                 </div>
29
               </div>
28
               </div>
30
             </div>
29
             </div>
31
           </div>
30
           </div>
32
         </div>
31
         </div>
33
-      </div>
34
-    )
35
-  }
32
+      )}
33
+    </div>
34
+  )
36
 }
35
 }
37
 
36
 
38
 export default FlashMessage
37
 export default FlashMessage

+ 2 - 2
src/component/Header/MenuActionListItem/MenuProfil.jsx View File

4
 import { PAGE_NAME } from '../../../helper.js'
4
 import { PAGE_NAME } from '../../../helper.js'
5
 
5
 
6
 const MenuProfil = props => {
6
 const MenuProfil = props => {
7
-  return props.user.isLoggedIn
7
+  return props.user.logged
8
     ? (
8
     ? (
9
       <li className='header__menu__rightside__itemprofil'>
9
       <li className='header__menu__rightside__itemprofil'>
10
-        <div className='header__menu__rightside__itemprofil__profilgroup dropdown'>
10
+        <div className='profilgroup dropdown'>
11
           <button className='profilgroup__name btn btn-secondary dropdown-toggle' type='button' id='dropdownMenuButton' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>
11
           <button className='profilgroup__name btn btn-secondary dropdown-toggle' type='button' id='dropdownMenuButton' data-toggle='dropdown' aria-haspopup='true' aria-expanded='false'>
12
             <img className='profilgroup__name__imgprofil' src={props.user.avatar} alt='avatar' />
12
             <img className='profilgroup__name__imgprofil' src={props.user.avatar} alt='avatar' />
13
             <div className='profilgroup__name__text'>
13
             <div className='profilgroup__name__text'>

+ 3 - 1
src/component/common/Input/InputGroupText.jsx View File

10
       </div>
10
       </div>
11
       <input
11
       <input
12
         type={props.type}
12
         type={props.type}
13
-        className={classnames(`${props.parentClassName}__input`, 'form-control')}
13
+        className={classnames(`${props.parentClassName}__input`, 'form-control', {'is-invalid': props.isInvalid})}
14
         placeholder={props.placeHolder}
14
         placeholder={props.placeHolder}
15
         value={props.value}
15
         value={props.value}
16
         onChange={props.onChange}
16
         onChange={props.onChange}
32
   icon: PropTypes.string,
32
   icon: PropTypes.string,
33
   placeHolder: PropTypes.string,
33
   placeHolder: PropTypes.string,
34
   invalidMsg: PropTypes.string,
34
   invalidMsg: PropTypes.string,
35
+  isInvalid: PropTypes.bool,
35
   onChange: PropTypes.func
36
   onChange: PropTypes.func
36
 }
37
 }
37
 
38
 
40
   icon: false,
41
   icon: false,
41
   placeHolder: '',
42
   placeHolder: '',
42
   invalidMsg: false,
43
   invalidMsg: false,
44
+  isInvalid: false,
43
   onChange: () => {}
45
   onChange: () => {}
44
 }
46
 }

+ 17 - 4
src/container/Header.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 i18n from '../i18n.js'
3
+import { translate } from 'react-i18next'
4
 import Logo from '../component/Header/Logo.jsx'
4
 import Logo from '../component/Header/Logo.jsx'
5
 import NavbarToggler from '../component/Header/NavbarToggler.jsx'
5
 import NavbarToggler from '../component/Header/NavbarToggler.jsx'
6
 import MenuLinkList from '../component/Header/MenuLinkList.jsx'
6
 import MenuLinkList from '../component/Header/MenuLinkList.jsx'
10
 import MenuActionListItemHelp from '../component/Header/MenuActionListItem/Help.jsx'
10
 import MenuActionListItemHelp from '../component/Header/MenuActionListItem/Help.jsx'
11
 import MenuActionListItemMenuProfil from '../component/Header/MenuActionListItem/MenuProfil.jsx'
11
 import MenuActionListItemMenuProfil from '../component/Header/MenuActionListItem/MenuProfil.jsx'
12
 import logoHeader from '../img/logo-tracim.png'
12
 import logoHeader from '../img/logo-tracim.png'
13
-import { setLangActive } from '../action-creator.sync.js'
13
+import {
14
+  newFlashMessage,
15
+  setLangActive,
16
+  setUserDisconnected
17
+} from '../action-creator.sync.js'
18
+import {
19
+  postUserLogout
20
+} from '../action-creator.async.js'
14
 
21
 
15
 class Header extends React.Component {
22
 class Header extends React.Component {
16
   handleClickLogo = () => {}
23
   handleClickLogo = () => {}
29
 
36
 
30
   handleClickHelp = () => {}
37
   handleClickHelp = () => {}
31
 
38
 
32
-  handleClickLogout = () => {}
39
+  handleClickLogout = async () => {
40
+    const { dispatch, t } = this.props
41
+
42
+    const fetchPostUserLogout = await dispatch(postUserLogout())
43
+    if (fetchPostUserLogout.status === 204) dispatch(setUserDisconnected())
44
+    else dispatch(newFlashMessage(t('Login.logout_error', 'danger')))
45
+  }
33
 
46
 
34
   render () {
47
   render () {
35
     const { lang, user } = this.props
48
     const { lang, user } = this.props
79
 }
92
 }
80
 
93
 
81
 const mapStateToProps = ({ lang, user }) => ({ lang, user })
94
 const mapStateToProps = ({ lang, user }) => ({ lang, user })
82
-export default connect(mapStateToProps)(Header)
95
+export default connect(mapStateToProps)(translate()(Header))

+ 1 - 1
src/container/Home.jsx View File

7
     return (
7
     return (
8
       <div>
8
       <div>
9
         Home.<br />
9
         Home.<br />
10
-        User logged in : {user.isLoggedIn.toString()}
10
+        User logged in : {user.logged.toString()}
11
       </div>
11
       </div>
12
     )
12
     )
13
   }
13
   }

+ 31 - 11
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 { Redirect } from 'react-router'
3
 import { Redirect } from 'react-router'
4
+import { translate } from 'react-i18next'
4
 import LoginLogo from '../component/Login/LoginLogo.jsx'
5
 import LoginLogo from '../component/Login/LoginLogo.jsx'
5
 import LoginLogoImg from '../img/logoTracimWhite.svg'
6
 import LoginLogoImg from '../img/logoTracimWhite.svg'
6
 import { postUserLogin } from '../action-creator.async.js'
7
 import { postUserLogin } from '../action-creator.async.js'
11
 import InputCheckbox from '../component/common/Input/InputCheckbox.jsx'
12
 import InputCheckbox from '../component/common/Input/InputCheckbox.jsx'
12
 import Button from '../component/common/Input/Button.jsx'
13
 import Button from '../component/common/Input/Button.jsx'
13
 import LoginBtnForgotPw from '../component/Login/LoginBtnForgotPw.jsx'
14
 import LoginBtnForgotPw from '../component/Login/LoginBtnForgotPw.jsx'
15
+import {
16
+  newFlashMessage,
17
+  setUserConnected
18
+} from '../action-creator.sync.js'
19
+import {PAGE_NAME} from '../helper.js'
14
 
20
 
15
 class Login extends React.Component {
21
 class Login extends React.Component {
16
   constructor (props) {
22
   constructor (props) {
17
     super(props)
23
     super(props)
18
     this.state = {
24
     this.state = {
19
-      inputLogin: '',
20
-      inputPassword: '',
25
+      inputLogin: {
26
+        value: '',
27
+        isInvalid: false
28
+      },
29
+      inputPassword: {
30
+        value: '',
31
+        isInvalid: false
32
+      },
21
       inputRememberMe: false
33
       inputRememberMe: false
22
     }
34
     }
23
   }
35
   }
24
 
36
 
25
-  handleChangeLogin = e => this.setState({inputLogin: e.target.value})
26
-  handleChangePassword = e => this.setState({inputPassword: e.target.value})
37
+  handleChangeLogin = e => this.setState({inputLogin: {...this.state.inputLogin, value: e.target.value}})
38
+  handleChangePassword = e => this.setState({inputPassword: {...this.state.inputPassword, value: e.target.value}})
27
   handleChangeRememberMe = () => this.setState(prev => ({inputRememberMe: !prev.inputRememberMe}))
39
   handleChangeRememberMe = () => this.setState(prev => ({inputRememberMe: !prev.inputRememberMe}))
28
 
40
 
29
   handleClickSubmit = async () => {
41
   handleClickSubmit = async () => {
30
-    const { history, dispatch } = this.props
42
+    const { history, dispatch, t } = this.props
31
     const { inputLogin, inputPassword, inputRememberMe } = this.state
43
     const { inputLogin, inputPassword, inputRememberMe } = this.state
32
 
44
 
33
-    await dispatch(postUserLogin(inputLogin, inputPassword, inputRememberMe))
34
-    history.push('/')
45
+    const fetchPostUserLogin = await dispatch(postUserLogin(inputLogin.value, inputPassword.value, inputRememberMe))
46
+
47
+    if (fetchPostUserLogin.status === 200) {
48
+      dispatch(setUserConnected({...fetchPostUserLogin.json, logged: true}))
49
+      history.push(PAGE_NAME.HOME)
50
+    } else if (fetchPostUserLogin.status === 400) {
51
+      dispatch(newFlashMessage(t('Login.fail'), 'danger'))
52
+    }
35
   }
53
   }
36
 
54
 
37
   render () {
55
   render () {
38
-    if (this.props.user.isLoggedIn) return <Redirect to={{pathname: '/'}} />
56
+    if (this.props.user.logged) return <Redirect to={{pathname: '/'}} />
39
     else {
57
     else {
40
       return (
58
       return (
41
         <section className='loginpage'>
59
         <section className='loginpage'>
58
                         type='email'
76
                         type='email'
59
                         placeHolder='Adresse Email'
77
                         placeHolder='Adresse Email'
60
                         invalidMsg='Email invalide.'
78
                         invalidMsg='Email invalide.'
61
-                        value={this.state.inputLogin}
79
+                        isInvalid={this.state.inputLogin.isInvalid}
80
+                        value={this.state.inputLogin.value}
62
                         onChange={this.handleChangeLogin}
81
                         onChange={this.handleChangeLogin}
63
                       />
82
                       />
64
 
83
 
69
                         type='password'
88
                         type='password'
70
                         placeHolder='Mot de passe'
89
                         placeHolder='Mot de passe'
71
                         invalidMsg='Mot de passe invalide.'
90
                         invalidMsg='Mot de passe invalide.'
72
-                        value={this.state.inputPassword}
91
+                        isInvalid={this.state.inputPassword.isInvalid}
92
+                        value={this.state.inputPassword.value}
73
                         onChange={this.handleChangePassword}
93
                         onChange={this.handleChangePassword}
74
                       />
94
                       />
75
 
95
 
114
 }
134
 }
115
 
135
 
116
 const mapStateToProps = ({ user }) => ({ user })
136
 const mapStateToProps = ({ user }) => ({ user })
117
-export default connect(mapStateToProps)(Login)
137
+export default connect(mapStateToProps)(translate()(Login))

+ 2 - 2
src/container/PrivateRoute.jsx View File

8
 // /!\ you shall destruct props.component otherwise you get a warning:
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
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 }) => (
10
 const PrivateRoute = ({ component: Component, ...rest }) => (
11
-  <Route {...rest} render={props => rest.user.isLoggedIn
11
+  <Route {...rest} render={props => rest.user.logged
12
     ? <Component {...props} />
12
     ? <Component {...props} />
13
     : <Redirect to={{pathname: '/login', state: {from: props.location}}} />
13
     : <Redirect to={{pathname: '/login', state: {from: props.location}}} />
14
   } />
14
   } />
20
 PrivateRoute.propTypes = {
20
 PrivateRoute.propTypes = {
21
   component: PropTypes.func.isRequired,
21
   component: PropTypes.func.isRequired,
22
   user: PropTypes.shape({ // user is get with redux connect
22
   user: PropTypes.shape({ // user is get with redux connect
23
-    isLoggedIn: PropTypes.bool.isRequired
23
+    logged: PropTypes.bool.isRequired
24
   })
24
   })
25
 }
25
 }

+ 26 - 8
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 { translate } from 'react-i18next'
3
 import Footer from '../component/Footer.jsx'
4
 import Footer from '../component/Footer.jsx'
4
 import Header from './Header.jsx'
5
 import Header from './Header.jsx'
5
 import Login from './Login.jsx'
6
 import Login from './Login.jsx'
6
 import Dashboard from './Dashboard.jsx'
7
 import Dashboard from './Dashboard.jsx'
7
 import Account from './Account.jsx'
8
 import Account from './Account.jsx'
8
-// import FlashMessage from './FlashMessage.jsx'
9
+import FlashMessage from '../component/FlashMessage.jsx'
9
 import WorkspaceContent from './WorkspaceContent.jsx'
10
 import WorkspaceContent from './WorkspaceContent.jsx'
10
 import {
11
 import {
11
   Route,
12
   Route,
17
   getLangList,
18
   getLangList,
18
   getUserIsConnected
19
   getUserIsConnected
19
 } from '../action-creator.async.js'
20
 } from '../action-creator.async.js'
21
+import {
22
+  removeFlashMessage, setUserConnected
23
+} from '../action-creator.sync.js'
20
 
24
 
21
 class Tracim extends React.Component {
25
 class Tracim extends React.Component {
22
-  componentDidMount () {
23
-    this.props.dispatch(getUserIsConnected())
24
-    this.props.dispatch(getLangList())
26
+  async componentDidMount () {
27
+    const { dispatch } = this.props
28
+
29
+    dispatch(getLangList())
30
+
31
+    const fetchGetUserIsConnected = await dispatch(getUserIsConnected())
32
+    switch (fetchGetUserIsConnected.status) {
33
+      case 200:
34
+        dispatch(setUserConnected({...fetchGetUserIsConnected.json, logged: true})); break
35
+      case 401:
36
+        dispatch(setUserConnected({logged: false})); break
37
+      default:
38
+        dispatch(setUserConnected({logged: undefined})); break
39
+    }
25
   }
40
   }
26
 
41
 
42
+  handleRemoveFlashMessage = msg => this.props.dispatch(removeFlashMessage(msg))
43
+
27
   render () {
44
   render () {
28
-    const { user } = this.props
45
+    const { flashMessage, user, t } = this.props
29
 
46
 
30
     return (
47
     return (
31
       <div className='tracim'>
48
       <div className='tracim'>
32
         <Header />
49
         <Header />
50
+        <FlashMessage flashMessage={flashMessage} removeFlashMessage={this.handleRemoveFlashMessage} t={t} />
33
 
51
 
34
-        { user.isLoggedIn === undefined
52
+        { user.logged === undefined
35
           ? (<div />) // while we dont know if user is connected, display nothing but the header @TODO show loader
53
           ? (<div />) // while we dont know if user is connected, display nothing but the header @TODO show loader
36
           : (
54
           : (
37
             <div className='tracim__content'>
55
             <div className='tracim__content'>
51
   }
69
   }
52
 }
70
 }
53
 
71
 
54
-const mapStateToProps = ({ user }) => ({ user })
55
-export default withRouter(connect(mapStateToProps)(Tracim))
72
+const mapStateToProps = ({ flashMessage, user }) => ({ flashMessage, user })
73
+export default withRouter(connect(mapStateToProps)(translate()(Tracim)))

+ 16 - 7
src/css/FlashMessage.styl View File

3
   display flex
3
   display flex
4
   justify-content center
4
   justify-content center
5
   width 100%
5
   width 100%
6
-  z-index 3
6
+  z-index 10
7
   &__container
7
   &__container
8
-    margin-top 120px
8
+    margin-top 50px
9
     border 0
9
     border 0
10
     border-radius 10px
10
     border-radius 10px
11
     width 800px
11
     width 800px
16
       border-top-left-radius 10px
16
       border-top-left-radius 10px
17
       width 100%
17
       width 100%
18
       height 8px
18
       height 8px
19
-      background-color calendar
19
+      &.success
20
+        background-color success
21
+      &.info
22
+        background-color info
23
+      &.danger
24
+        background-color danger
20
     &__close
25
     &__close
21
       display flex
26
       display flex
22
       justify-content flex-end
27
       justify-content flex-end
25
     &__content
30
     &__content
26
       display flex
31
       display flex
27
       align-items center
32
       align-items center
28
-      margin 10px 0 35px 0
33
+      margin 10px 0 25px 0
29
       &__icon
34
       &__icon
30
         padding 0 30px
35
         padding 0 30px
31
-        font-size 60px
32
-        color calendar
36
+        font-size 40px
37
+        &.success
38
+          color success
39
+        &.info
40
+          color info
41
+        &.danger
42
+          color danger
33
       &__text
43
       &__text
34
         &__title
44
         &__title
35
-          margin-bottom 10px
36
           font-size 20px
45
           font-size 20px
37
           font-weight 600
46
           font-weight 600
38
           color calendar
47
           color calendar

+ 1 - 0
src/css/Generic.styl View File

109
     cursor pointer
109
     cursor pointer
110
     .setting__link
110
     .setting__link
111
       padding 10px
111
       padding 10px
112
+      cursor pointer
112
 
113
 
113
 .fa-file-image-o
114
 .fa-file-image-o
114
   color previewColor
115
   color previewColor

+ 6 - 4
src/css/Header.styl View File

68
           .btnquestion__icon
68
           .btnquestion__icon
69
             color darkGrey
69
             color darkGrey
70
       &__itemprofil
70
       &__itemprofil
71
-        &__profilgroup
72
-          .profilgroup__name
71
+        .profilgroup
72
+          &__name
73
             border 0
73
             border 0
74
             padding 0 5px
74
             padding 0 5px
75
             background-color transparent
75
             background-color transparent
76
+            cursor pointer
76
             &:hover
77
             &:hover
77
               background-color lightGrey
78
               background-color lightGrey
78
             &:focus
79
             &:focus
91
               vertical-align middle
92
               vertical-align middle
92
               margin-right 10px
93
               margin-right 10px
93
               color darkGrey
94
               color darkGrey
94
-          .profilgroup__sub
95
+          &__sub
95
             color darkGrey
96
             color darkGrey
96
             cursor pointer
97
             cursor pointer
97
             &:focus
98
             &:focus
98
               box-shadow none
99
               box-shadow none
99
-          .profilgroup__setting
100
+          &__setting
100
             padding 0
101
             padding 0
101
             left inherit
102
             left inherit
102
             right 0
103
             right 0
104
+            cursor pointer
103
             .dropdown-item
105
             .dropdown-item
104
               color fontColor
106
               color fontColor
105
             &__link
107
             &__link

+ 5 - 0
src/css/Variable.styl View File

70
 cancelColor = red
70
 cancelColor = red
71
 outdateColor = grey
71
 outdateColor = grey
72
 
72
 
73
+/** Flash Message Color **/
74
+success = #30ce4e
75
+info = #0094bc
76
+danger = #e81a32
77
+
73
 /*************************/
78
 /*************************/
74
 /**** BOX SHADOW ****/
79
 /**** BOX SHADOW ****/
75
 shadow-bottom = 0px 0px 5px 1px #606060
80
 shadow-bottom = 0px 0px 5px 1px #606060

+ 19 - 0
src/reducer/flashMessage.js View File

1
+import {
2
+  FLASH_MESSAGE
3
+} from '../action-creator.sync.js'
4
+
5
+export default function flashMessage (state = [], action) {
6
+  switch (action.type) {
7
+    case `Add/${FLASH_MESSAGE}`:
8
+      return [...state, {
9
+        message: action.msg.message,
10
+        type: action.msg.type || 'info' // may be info, success, danger
11
+      }]
12
+
13
+    case `Remove/${FLASH_MESSAGE}`:
14
+      return state.filter(fm => fm.message === action.message)
15
+
16
+    default:
17
+      return state
18
+  }
19
+}

+ 2 - 1
src/reducer/root.js View File

1
 import { combineReducers } from 'redux'
1
 import { combineReducers } from 'redux'
2
 import lang from './lang.js'
2
 import lang from './lang.js'
3
+import flashMessage from './flashMessage.js'
3
 import user from './user.js'
4
 import user from './user.js'
4
 import workspace from './workspace.js'
5
 import workspace from './workspace.js'
5
 import workspaceList from './workspaceList.js'
6
 import workspaceList from './workspaceList.js'
7
 import app from './app.js'
8
 import app from './app.js'
8
 import timezone from './timezone.js'
9
 import timezone from './timezone.js'
9
 
10
 
10
-const rootReducer = combineReducers({ lang, user, workspace, workspaceList, activeFileContent, app, timezone })
11
+const rootReducer = combineReducers({ lang, flashMessage, user, workspace, workspaceList, activeFileContent, app, timezone })
11
 
12
 
12
 export default rootReducer
13
 export default rootReducer

+ 17 - 23
src/reducer/user.js View File

1
 import {
1
 import {
2
   USER_CONNECTED,
2
   USER_CONNECTED,
3
+  USER_DISCONNECTED,
3
   USER_DATA
4
   USER_DATA
4
 } from '../action-creator.sync.js'
5
 } from '../action-creator.sync.js'
5
 
6
 
6
-const serializeUser = data => ({
7
-  id: data.user.id,
8
-  isLoggedIn: data.logged,
9
-  username: data.user.username,
10
-  firstname: data.user.firstname,
11
-  lastname: data.user.lastname,
12
-  email: data.user.email,
13
-  avatar: data.user.avatar,
14
-  role: data.user.role,
15
-  job: data.user.job,
16
-  company: data.user.company,
17
-  caldavUrl: data.user.caldav_url
18
-})
19
-
20
 const defaultUser = {
7
 const defaultUser = {
21
-  id: -1,
22
-  isLoggedIn: undefined, // undefined means the api (/is_logged_in) has not responded yet
23
-  username: '',
24
-  firstname: '',
25
-  lastname: '',
8
+  user_id: -1,
9
+  logged: false,
10
+  timezone: '',
11
+  profile: {
12
+    id: 1,
13
+    slug: 'user'
14
+  },
26
   email: '',
15
   email: '',
27
-  avatar: ''
16
+  is_active: true,
17
+  caldav_url: null,
18
+  avatar_url: null,
19
+  created: '',
20
+  display_name: ''
28
 }
21
 }
29
 
22
 
30
 export default function user (state = defaultUser, action) {
23
 export default function user (state = defaultUser, action) {
31
   switch (action.type) {
24
   switch (action.type) {
32
     case `Set/${USER_CONNECTED}`:
25
     case `Set/${USER_CONNECTED}`:
33
-      return action.data.logged
34
-        ? serializeUser(action.data)
35
-        : {...defaultUser, isLoggedIn: false}
26
+      return action.user
27
+
28
+    case `Set/${USER_DISCONNECTED}`:
29
+      return defaultUser
36
 
30
 
37
     case `Update/${USER_DATA}`:
31
     case `Update/${USER_DATA}`:
38
       return {...state, ...action.data}
32
       return {...state, ...action.data}

+ 7 - 0
src/translate/en.js View File

7
       marketing_msg: 'Create your own collaborative workspaces on trac.im',
7
       marketing_msg: 'Create your own collaborative workspaces on trac.im',
8
       copyright: 'Copyright 2013 - 2017'
8
       copyright: 'Copyright 2013 - 2017'
9
     },
9
     },
10
+    FlashMessage: {
11
+      error: 'Error'
12
+    },
13
+    Login: {
14
+      fail: 'Unknown email or password',
15
+      logout_error: 'Disconnection error'
16
+    },
10
     FileItemHeader: {
17
     FileItemHeader: {
11
       type: 'Type',
18
       type: 'Type',
12
       document_name: "Document's name",
19
       document_name: "Document's name",

+ 7 - 0
src/translate/fr.js View File

7
       marketing_msg: 'Créer votre propre espace de travail collaboratif sur trac.im',
7
       marketing_msg: 'Créer votre propre espace de travail collaboratif sur trac.im',
8
       copyright: 'Copyright 2013 - 2017'
8
       copyright: 'Copyright 2013 - 2017'
9
     },
9
     },
10
+    FlashMessage: {
11
+      error: 'Erreur'
12
+    },
13
+    Login: {
14
+      fail: 'Email ou mot de passe inconnu.',
15
+      logout_error: 'Erreur de déconnexion.'
16
+    },
10
     FileItemHeader: {
17
     FileItemHeader: {
11
       type: 'Type',
18
       type: 'Type',
12
       document_name: 'Nom du document',
19
       document_name: 'Nom du document',