Quellcode durchsuchen

Merge branch 'connection_to_real_api' of github.com:tracim/tracim_frontend into develop

Skylsmoi vor 6 Jahren
Ursprung
Commit
56f4188616

+ 2 - 2
jsonserver/server.js Datei anzeigen

@@ -40,14 +40,14 @@ server.patch('/user', (req, res) => res.jsonp({lang: 'fr'}))
40 40
 
41 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 44
   if (req.body.login !== '' && req.body.password !== '') return res.jsonp(jsonDb.user_logged)
45 45
   else return res.jsonp('error')
46 46
 })
47 47
 
48 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 51
   // res.jsonp({"logged": false})
52 52
   res.jsonp(jsonDb.user_logged)
53 53
 )

+ 12 - 11
jsonserver/static_db.json Datei anzeigen

@@ -11,17 +11,18 @@
11 11
     "active": false
12 12
   }],
13 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 27
   "app_config": [{
27 28
     "name": "PageHtml",

+ 30 - 20
src/action-creator.async.js Datei anzeigen

@@ -5,10 +5,10 @@ import {
5 5
   LANG,
6 6
   updateLangList,
7 7
   USER_LOGIN,
8
+  USER_LOGOUT,
8 9
   USER_DATA,
9 10
   USER_ROLE,
10 11
   USER_CONNECTED,
11
-  setUserConnected,
12 12
   updateUserData,
13 13
   setUserRole,
14 14
   WORKSPACE,
@@ -70,7 +70,7 @@ const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) =
70 70
 
71 71
 export const getLangList = () => async dispatch => {
72 72
   const fetchGetLangList = await fetchWrapper({
73
-    url: `${FETCH_CONFIG.apiUrl}/lang`,
73
+    url: `${FETCH_CONFIG.mockApiUrl}/lang`,
74 74
     param: {...FETCH_CONFIG.header, method: 'GET'},
75 75
     actionName: LANG,
76 76
     dispatch
@@ -80,7 +80,7 @@ export const getLangList = () => async dispatch => {
80 80
 
81 81
 export const getTimezone = () => async dispatch => {
82 82
   const fetchGetTimezone = await fetchWrapper({
83
-    url: `${FETCH_CONFIG.apiUrl}/timezone`,
83
+    url: `${FETCH_CONFIG.mockApiUrl}/timezone`,
84 84
     param: {...FETCH_CONFIG.header, method: 'GET'},
85 85
     actionName: TIMEZONE,
86 86
     dispatch
@@ -88,37 +88,47 @@ export const getTimezone = () => async dispatch => {
88 88
   if (fetchGetTimezone.status === 200) dispatch(setTimezone(fetchGetTimezone.json))
89 89
 }
90 90
 
91
-export const userLogin = (login, password, rememberMe) => async dispatch => {
92
-  const fetchUserLogin = await fetchWrapper({
93
-    url: `${FETCH_CONFIG.apiUrl}/user/login`,
91
+export const postUserLogin = (login, password, rememberMe) => async dispatch => {
92
+  return fetchWrapper({
93
+    url: `${FETCH_CONFIG.mockApiUrl}/sessions/login`,
94 94
     param: {
95
-      ...FETCH_CONFIG.header,
95
+      headers: {...FETCH_CONFIG.headers},
96 96
       method: 'POST',
97 97
       body: JSON.stringify({
98
-        login,
99
-        password,
98
+        email: login,
99
+        password: password,
100 100
         remember_me: rememberMe
101 101
       })
102 102
     },
103 103
     actionName: USER_LOGIN,
104 104
     dispatch
105 105
   })
106
-  if (fetchUserLogin.status === 200) dispatch(setUserConnected(fetchUserLogin.json))
107 106
 }
108 107
 
109
-export const getIsUserConnected = () => async dispatch => {
110
-  const fetchUserLogged = await fetchWrapper({
111
-    url: `${FETCH_CONFIG.apiUrl}/user/is_logged_in`,
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
+  })
118
+}
119
+
120
+export const getUserIsConnected = () => async dispatch => {
121
+  return fetchWrapper({
122
+    url: `${FETCH_CONFIG.mockApiUrl}/sessions/whoami`,
112 123
     param: {...FETCH_CONFIG.header, method: 'GET'},
113 124
     actionName: USER_CONNECTED,
114 125
     dispatch
115 126
   })
116
-  if (fetchUserLogged.status === 200) dispatch(setUserConnected(fetchUserLogged.json))
117 127
 }
118 128
 
119 129
 export const getUserRole = user => async dispatch => {
120 130
   const fetchGetUserRole = await fetchWrapper({
121
-    url: `${FETCH_CONFIG.apiUrl}/user/${user.id}/roles`,
131
+    url: `${FETCH_CONFIG.mockApiUrl}/user/${user.id}/roles`,
122 132
     param: {...FETCH_CONFIG.header, method: 'GET'},
123 133
     actionName: USER_ROLE,
124 134
     dispatch
@@ -128,7 +138,7 @@ export const getUserRole = user => async dispatch => {
128 138
 
129 139
 export const updateUserLang = newLang => async dispatch => { // unused
130 140
   const fetchUpdateUserLang = await fetchWrapper({
131
-    url: `${FETCH_CONFIG.apiUrl}/user`,
141
+    url: `${FETCH_CONFIG.mockApiUrl}/user`,
132 142
     param: {...FETCH_CONFIG.header, method: 'PATCH', body: JSON.stringify({lang: newLang})},
133 143
     actionName: USER_DATA,
134 144
     dispatch
@@ -148,7 +158,7 @@ export const updateUserLang = newLang => async dispatch => { // unused
148 158
 
149 159
 export const getWorkspaceList = (userId, workspaceIdToOpen) => async dispatch => {
150 160
   const fetchGetWorkspaceList = await fetchWrapper({
151
-    url: `${FETCH_CONFIG.apiUrl}/user/${userId}/workspace`,
161
+    url: `${FETCH_CONFIG.mockApiUrl}/user/${userId}/workspace`,
152 162
     param: {...FETCH_CONFIG.header, method: 'GET'},
153 163
     actionName: WORKSPACE_LIST,
154 164
     dispatch
@@ -161,7 +171,7 @@ export const getWorkspaceList = (userId, workspaceIdToOpen) => async dispatch =>
161 171
 
162 172
 export const getWorkspaceContent = (workspaceId, filterStr) => async dispatch => {
163 173
   const fetchGetWorkspaceContent = await fetchWrapper({
164
-    url: `${FETCH_CONFIG.apiUrl}/workspace/${workspaceId}`,
174
+    url: `${FETCH_CONFIG.mockApiUrl}/workspace/${workspaceId}`,
165 175
     param: {...FETCH_CONFIG.header, method: 'GET'},
166 176
     actionName: WORKSPACE,
167 177
     dispatch
@@ -171,7 +181,7 @@ export const getWorkspaceContent = (workspaceId, filterStr) => async dispatch =>
171 181
 
172 182
 export const getFolderContent = (workspaceId, folderId) => async dispatch => {
173 183
   const fetchGetFolderContent = await fetchWrapper({
174
-    url: `${FETCH_CONFIG.apiUrl}/workspace/${workspaceId}/folder/${folderId}`,
184
+    url: `${FETCH_CONFIG.mockApiUrl}/workspace/${workspaceId}/folder/${folderId}`,
175 185
     param: {...FETCH_CONFIG.header, method: 'GET'},
176 186
     actionName: `${WORKSPACE}/${FOLDER}`,
177 187
     dispatch
@@ -181,7 +191,7 @@ export const getFolderContent = (workspaceId, folderId) => async dispatch => {
181 191
 
182 192
 export const getAppList = () => async dispatch => {
183 193
   const fetchGetAppList = await fetchWrapper({
184
-    url: `${FETCH_CONFIG.apiUrl}/app/config`,
194
+    url: `${FETCH_CONFIG.mockApiUrl}/app/config`,
185 195
     param: {...FETCH_CONFIG.header, method: 'GET'},
186 196
     actionName: APP_LIST,
187 197
     dispatch

+ 12 - 1
src/action-creator.sync.js Datei anzeigen

@@ -1,11 +1,22 @@
1 1
 export const TIMEZONE = 'Timezone'
2 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 12
 export const USER_LOGIN = 'User/Login'
13
+export const USER_LOGOUT = 'User/Logout'
5 14
 export const USER_DATA = 'User/Data'
6 15
 export const USER_ROLE = 'User/Role'
7 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 20
 export const updateUserData = userData => ({ type: `Update/${USER_DATA}`, data: userData })
10 21
 export const setUserRole = userRole => ({ type: `Set/${USER_ROLE}`, userRole }) // this actually update workspaceList state
11 22
 export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>

+ 1 - 1
src/appFactory.js Datei anzeigen

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

src/container/FlashMessage.jsx → src/component/FlashMessage.jsx Datei anzeigen

@@ -1,38 +1,37 @@
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 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 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 13
               <i className='fa fa-times' />
14 14
             </div>
15 15
 
16 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 18
                 <i className='fa fa-times-circle' />
19 19
               </div>
20 20
 
21 21
               <div className='flashmessage__container__content__text'>
22 22
                 <div className='flashmessage__container__content__text__title'>
23
-                  Sorry !
23
+                  {props.t('FlashMessage.error')}
24 24
                 </div>
25 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 27
                 </div>
29 28
               </div>
30 29
             </div>
31 30
           </div>
32 31
         </div>
33
-      </div>
34
-    )
35
-  }
32
+      )}
33
+    </div>
34
+  )
36 35
 }
37 36
 
38 37
 export default FlashMessage

+ 2 - 2
src/component/Header/MenuActionListItem/MenuProfil.jsx Datei anzeigen

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

+ 3 - 1
src/component/common/Input/InputGroupText.jsx Datei anzeigen

@@ -10,7 +10,7 @@ const InputGroupText = props => {
10 10
       </div>
11 11
       <input
12 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 14
         placeholder={props.placeHolder}
15 15
         value={props.value}
16 16
         onChange={props.onChange}
@@ -32,6 +32,7 @@ InputGroupText.propTypes = {
32 32
   icon: PropTypes.string,
33 33
   placeHolder: PropTypes.string,
34 34
   invalidMsg: PropTypes.string,
35
+  isInvalid: PropTypes.bool,
35 36
   onChange: PropTypes.func
36 37
 }
37 38
 
@@ -40,5 +41,6 @@ InputGroupText.defaultProps = {
40 41
   icon: false,
41 42
   placeHolder: '',
42 43
   invalidMsg: false,
44
+  isInvalid: false,
43 45
   onChange: () => {}
44 46
 }

+ 17 - 4
src/container/Header.jsx Datei anzeigen

@@ -1,6 +1,6 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
-import i18n from '../i18n.js'
3
+import { translate } from 'react-i18next'
4 4
 import Logo from '../component/Header/Logo.jsx'
5 5
 import NavbarToggler from '../component/Header/NavbarToggler.jsx'
6 6
 import MenuLinkList from '../component/Header/MenuLinkList.jsx'
@@ -10,7 +10,14 @@ import MenuActionListItemDropdownLang from '../component/Header/MenuActionListIt
10 10
 import MenuActionListItemHelp from '../component/Header/MenuActionListItem/Help.jsx'
11 11
 import MenuActionListItemMenuProfil from '../component/Header/MenuActionListItem/MenuProfil.jsx'
12 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 22
 class Header extends React.Component {
16 23
   handleClickLogo = () => {}
@@ -29,7 +36,13 @@ class Header extends React.Component {
29 36
 
30 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 47
   render () {
35 48
     const { lang, user } = this.props
@@ -79,4 +92,4 @@ class Header extends React.Component {
79 92
 }
80 93
 
81 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 Datei anzeigen

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

+ 32 - 12
src/container/Login.jsx Datei anzeigen

@@ -1,9 +1,10 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3 3
 import { Redirect } from 'react-router'
4
+import { translate } from 'react-i18next'
4 5
 import LoginLogo from '../component/Login/LoginLogo.jsx'
5 6
 import LoginLogoImg from '../img/logoTracimWhite.svg'
6
-import { userLogin } from '../action-creator.async.js'
7
+import { postUserLogin } from '../action-creator.async.js'
7 8
 import Card from '../component/common/Card/Card.jsx'
8 9
 import CardHeader from '../component/common/Card/CardHeader.jsx'
9 10
 import CardBody from '../component/common/Card/CardBody.jsx'
@@ -11,31 +12,48 @@ import InputGroupText from '../component/common/Input/InputGroupText.jsx'
11 12
 import InputCheckbox from '../component/common/Input/InputCheckbox.jsx'
12 13
 import Button from '../component/common/Input/Button.jsx'
13 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 21
 class Login extends React.Component {
16 22
   constructor (props) {
17 23
     super(props)
18 24
     this.state = {
19
-      inputLogin: '',
20
-      inputPassword: '',
25
+      inputLogin: {
26
+        value: '',
27
+        isInvalid: false
28
+      },
29
+      inputPassword: {
30
+        value: '',
31
+        isInvalid: false
32
+      },
21 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 39
   handleChangeRememberMe = () => this.setState(prev => ({inputRememberMe: !prev.inputRememberMe}))
28 40
 
29 41
   handleClickSubmit = async () => {
30
-    const { history, dispatch } = this.props
42
+    const { history, dispatch, t } = this.props
31 43
     const { inputLogin, inputPassword, inputRememberMe } = this.state
32 44
 
33
-    await dispatch(userLogin(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 55
   render () {
38
-    if (this.props.user.isLoggedIn) return <Redirect to={{pathname: '/'}} />
56
+    if (this.props.user.logged) return <Redirect to={{pathname: '/'}} />
39 57
     else {
40 58
       return (
41 59
         <section className='loginpage'>
@@ -58,7 +76,8 @@ class Login extends React.Component {
58 76
                         type='email'
59 77
                         placeHolder='Adresse Email'
60 78
                         invalidMsg='Email invalide.'
61
-                        value={this.state.inputLogin}
79
+                        isInvalid={this.state.inputLogin.isInvalid}
80
+                        value={this.state.inputLogin.value}
62 81
                         onChange={this.handleChangeLogin}
63 82
                       />
64 83
 
@@ -69,7 +88,8 @@ class Login extends React.Component {
69 88
                         type='password'
70 89
                         placeHolder='Mot de passe'
71 90
                         invalidMsg='Mot de passe invalide.'
72
-                        value={this.state.inputPassword}
91
+                        isInvalid={this.state.inputPassword.isInvalid}
92
+                        value={this.state.inputPassword.value}
73 93
                         onChange={this.handleChangePassword}
74 94
                       />
75 95
 
@@ -114,4 +134,4 @@ class Login extends React.Component {
114 134
 }
115 135
 
116 136
 const mapStateToProps = ({ user }) => ({ user })
117
-export default connect(mapStateToProps)(Login)
137
+export default connect(mapStateToProps)(translate()(Login))

+ 2 - 2
src/container/PrivateRoute.jsx Datei anzeigen

@@ -8,7 +8,7 @@ import { Route, Redirect, withRouter } from 'react-router-dom'
8 8
 // /!\ you shall destruct props.component otherwise you get a warning:
9 9
 // "You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored
10 10
 const PrivateRoute = ({ component: Component, ...rest }) => (
11
-  <Route {...rest} render={props => rest.user.isLoggedIn
11
+  <Route {...rest} render={props => rest.user.logged
12 12
     ? <Component {...props} />
13 13
     : <Redirect to={{pathname: '/login', state: {from: props.location}}} />
14 14
   } />
@@ -20,6 +20,6 @@ export default withRouter(connect(mapStateToProps)(PrivateRoute))
20 20
 PrivateRoute.propTypes = {
21 21
   component: PropTypes.func.isRequired,
22 22
   user: PropTypes.shape({ // user is get with redux connect
23
-    isLoggedIn: PropTypes.bool.isRequired
23
+    logged: PropTypes.bool.isRequired
24 24
   })
25 25
 }

+ 27 - 9
src/container/Tracim.jsx Datei anzeigen

@@ -1,11 +1,12 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
+import { translate } from 'react-i18next'
3 4
 import Footer from '../component/Footer.jsx'
4 5
 import Header from './Header.jsx'
5 6
 import Login from './Login.jsx'
6 7
 import Dashboard from './Dashboard.jsx'
7 8
 import Account from './Account.jsx'
8
-// import FlashMessage from './FlashMessage.jsx'
9
+import FlashMessage from '../component/FlashMessage.jsx'
9 10
 import WorkspaceContent from './WorkspaceContent.jsx'
10 11
 import {
11 12
   Route,
@@ -15,23 +16,40 @@ import PrivateRoute from './PrivateRoute.jsx'
15 16
 import { PAGE_NAME } from '../helper.js'
16 17
 import {
17 18
   getLangList,
18
-  getIsUserConnected
19
+  getUserIsConnected
19 20
 } from '../action-creator.async.js'
21
+import {
22
+  removeFlashMessage, setUserConnected
23
+} from '../action-creator.sync.js'
20 24
 
21 25
 class Tracim extends React.Component {
22
-  componentDidMount () {
23
-    this.props.dispatch(getIsUserConnected())
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 44
   render () {
28
-    const { user } = this.props
45
+    const { flashMessage, user, t } = this.props
29 46
 
30 47
     return (
31 48
       <div className='tracim'>
32 49
         <Header />
50
+        <FlashMessage flashMessage={flashMessage} removeFlashMessage={this.handleRemoveFlashMessage} t={t} />
33 51
 
34
-        { user.isLoggedIn === undefined
52
+        { user.logged === undefined
35 53
           ? (<div />) // while we dont know if user is connected, display nothing but the header @TODO show loader
36 54
           : (
37 55
             <div className='tracim__content'>
@@ -51,5 +69,5 @@ class Tracim extends React.Component {
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 Datei anzeigen

@@ -3,9 +3,9 @@
3 3
   display flex
4 4
   justify-content center
5 5
   width 100%
6
-  z-index 3
6
+  z-index 10
7 7
   &__container
8
-    margin-top 120px
8
+    margin-top 50px
9 9
     border 0
10 10
     border-radius 10px
11 11
     width 800px
@@ -16,7 +16,12 @@
16 16
       border-top-left-radius 10px
17 17
       width 100%
18 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 25
     &__close
21 26
       display flex
22 27
       justify-content flex-end
@@ -25,14 +30,18 @@
25 30
     &__content
26 31
       display flex
27 32
       align-items center
28
-      margin 10px 0 35px 0
33
+      margin 10px 0 25px 0
29 34
       &__icon
30 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 43
       &__text
34 44
         &__title
35
-          margin-bottom 10px
36 45
           font-size 20px
37 46
           font-weight 600
38 47
           color calendar

+ 1 - 0
src/css/Generic.styl Datei anzeigen

@@ -107,6 +107,7 @@
107 107
     cursor pointer
108 108
     .setting__link
109 109
       padding 10px
110
+      cursor pointer
110 111
 
111 112
 .fa-file-image-o
112 113
   color previewColor

+ 6 - 4
src/css/Header.styl Datei anzeigen

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

+ 5 - 0
src/css/Variable.styl Datei anzeigen

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

+ 3 - 2
src/helper.js Datei anzeigen

@@ -1,9 +1,10 @@
1 1
 export const FETCH_CONFIG = {
2
-  header: {
2
+  headers: {
3 3
     'Accept': 'application/json',
4 4
     'Content-Type': 'application/json'
5 5
   },
6
-  apiUrl: 'http://localhost:3001'
6
+  apiUrl: 'http://localhost:6543/api/v2',
7
+  mockApiUrl: 'http://localhost:3001'
7 8
 }
8 9
 
9 10
 export const PAGE_NAME = {

+ 19 - 0
src/reducer/flashMessage.js Datei anzeigen

@@ -0,0 +1,19 @@
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 Datei anzeigen

@@ -1,5 +1,6 @@
1 1
 import { combineReducers } from 'redux'
2 2
 import lang from './lang.js'
3
+import flashMessage from './flashMessage.js'
3 4
 import user from './user.js'
4 5
 import workspace from './workspace.js'
5 6
 import workspaceList from './workspaceList.js'
@@ -7,6 +8,6 @@ import activeFileContent from './activeFileContent.js'
7 8
 import app from './app.js'
8 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 13
 export default rootReducer

+ 17 - 23
src/reducer/user.js Datei anzeigen

@@ -1,38 +1,32 @@
1 1
 import {
2 2
   USER_CONNECTED,
3
+  USER_DISCONNECTED,
3 4
   USER_DATA
4 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 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 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 23
 export default function user (state = defaultUser, action) {
31 24
   switch (action.type) {
32 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 31
     case `Update/${USER_DATA}`:
38 32
       return {...state, ...action.data}

+ 7 - 0
src/translate/en.js Datei anzeigen

@@ -7,6 +7,13 @@ const en = {
7 7
       marketing_msg: 'Create your own collaborative workspaces on trac.im',
8 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 17
     FileItemHeader: {
11 18
       type: 'Type',
12 19
       document_name: "Document's name",

+ 7 - 0
src/translate/fr.js Datei anzeigen

@@ -7,6 +7,13 @@ const fr = {
7 7
       marketing_msg: 'Créer votre propre espace de travail collaboratif sur trac.im',
8 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 17
     FileItemHeader: {
11 18
       type: 'Type',
12 19
       document_name: 'Nom du document',