Browse Source

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

Luc 6 years ago
parent
commit
50531dc4c3
46 changed files with 515 additions and 253 deletions
  1. 9 0
      README.md
  2. 1 0
      dist/index.html
  3. 2 2
      jsonserver/server.js
  4. 16 15
      jsonserver/static_db.json
  5. 30 20
      src/action-creator.async.js
  6. 12 1
      src/action-creator.sync.js
  7. 3 2
      src/appFactory.js
  8. 39 0
      src/component/FlashMessage.jsx
  9. 2 2
      src/component/Header/MenuActionListItem/MenuProfil.jsx
  10. 0 31
      src/component/HomepageCard/HomepageCard.jsx
  11. 1 0
      src/component/Workspace/BtnExtandedAction.jsx
  12. 8 22
      src/component/Workspace/ContentItem.jsx
  13. 4 4
      src/component/Workspace/ContentItemHeader.jsx
  14. 2 0
      src/component/Workspace/FileType/deprecated_folder
  15. 2 2
      src/component/Workspace/Folder.jsx
  16. 54 0
      src/component/common/CardPopup/CardPopup.jsx
  17. 39 0
      src/component/common/CardPopup/CardPopup.styl
  18. 3 1
      src/component/common/Input/InputGroupText.jsx
  19. 1 1
      src/container/Dashboard.jsx
  20. 0 38
      src/container/FlashMessage.jsx
  21. 17 3
      src/container/Header.jsx
  22. 34 10
      src/container/Home.jsx
  23. 0 16
      src/container/Homepage.jsx
  24. 32 12
      src/container/Login.jsx
  25. 37 4
      src/container/PopupCreateContainer.jsx
  26. 2 2
      src/container/PrivateRoute.jsx
  27. 4 4
      src/container/Sidebar.jsx
  28. 30 9
      src/container/Tracim.jsx
  29. 30 0
      src/container/WIPcomponent.jsx
  30. 5 5
      src/container/WorkspaceContent.jsx
  31. 2 2
      src/css/ContentItem.styl
  32. 1 1
      src/css/ContentItemHeader.styl
  33. 1 0
      src/css/File.styl
  34. 19 8
      src/css/FlashMessage.styl
  35. 3 3
      src/css/Folder.styl
  36. 1 0
      src/css/Generic.styl
  37. 6 4
      src/css/Header.styl
  38. 5 0
      src/css/Variable.styl
  39. 2 2
      src/css/index.styl
  40. 3 2
      src/helper.js
  41. 1 1
      src/index.js
  42. 19 0
      src/reducer/flashMessage.js
  43. 2 1
      src/reducer/root.js
  44. 17 23
      src/reducer/user.js
  45. 7 0
      src/translate/en.js
  46. 7 0
      src/translate/fr.js

+ 9 - 0
README.md View File

59
 - add an entry for you App in tracim_frontend/jsonserver/static_db.json in the `app_config` property
59
 - add an entry for you App in tracim_frontend/jsonserver/static_db.json in the `app_config` property
60
 - reload your mock api server
60
 - reload your mock api server
61
 - add the source of your app in tracim_frontend/dist/index.html and an entry to the switch case of the function `GLOBAL_renderApp`. All of this will be handled by backend later on, this is all work in progress stuffs.
61
 - add the source of your app in tracim_frontend/dist/index.html and an entry to the switch case of the function `GLOBAL_renderApp`. All of this will be handled by backend later on, this is all work in progress stuffs.
62
+
63
+
64
+#### Urls list
65
+- __/__ => detail of the first workspace 
66
+- __/login__ => login page
67
+- __/workspace/:idws__ => detail of the workspace :idws
68
+- __/workspace/:idws/content/:idc__ => detail of the workspace :idws with the app of the content :idc openned
69
+- __/account__ => profile page of the connected user
70
+- __/dashboard__ => dashboard of a workspace (code not plugged in therefore no :idws in url) 

+ 1 - 0
dist/index.html View File

40
       let prevSelectedApp = {name: ''} // default value
40
       let prevSelectedApp = {name: ''} // default value
41
 
41
 
42
       GLOBAL_renderApp = app => {
42
       GLOBAL_renderApp = app => {
43
+        console.log('GLOBAL_renderApp => ', app)
43
         const selectedApp = (() => {
44
         const selectedApp = (() => {
44
           switch (app.config.name) {
45
           switch (app.config.name) {
45
             case 'PageHtml':
46
             case 'PageHtml':

+ 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
 )

+ 16 - 15
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",
28
     "label": {
29
     "label": {
29
-      "fr": "Page Html",
30
-      "en": "Html page"
30
+      "fr": "Document",
31
+      "en": "Document"
31
     },
32
     },
32
     "componentLeft": "PageHtml",
33
     "componentLeft": "PageHtml",
33
     "componentRight": "Timeline",
34
     "componentRight": "Timeline",
38
   }, {
39
   }, {
39
     "name": "PageMarkdown",
40
     "name": "PageMarkdown",
40
     "label": {
41
     "label": {
41
-      "fr": "Page Markdown",
42
-      "en": "Markdown page"
42
+      "fr": "Document markdown",
43
+      "en": "Markdown document"
43
     },
44
     },
44
     "componentLeft": "PageMarkdown",
45
     "componentLeft": "PageMarkdown",
45
     "componentRight": "undefined",
46
     "componentRight": "undefined",

+ 30 - 20
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,
70
 
70
 
71
 export const getLangList = () => async dispatch => {
71
 export const getLangList = () => async dispatch => {
72
   const fetchGetLangList = await fetchWrapper({
72
   const fetchGetLangList = await fetchWrapper({
73
-    url: `${FETCH_CONFIG.apiUrl}/lang`,
73
+    url: `${FETCH_CONFIG.mockApiUrl}/lang`,
74
     param: {...FETCH_CONFIG.header, method: 'GET'},
74
     param: {...FETCH_CONFIG.header, method: 'GET'},
75
     actionName: LANG,
75
     actionName: LANG,
76
     dispatch
76
     dispatch
80
 
80
 
81
 export const getTimezone = () => async dispatch => {
81
 export const getTimezone = () => async dispatch => {
82
   const fetchGetTimezone = await fetchWrapper({
82
   const fetchGetTimezone = await fetchWrapper({
83
-    url: `${FETCH_CONFIG.apiUrl}/timezone`,
83
+    url: `${FETCH_CONFIG.mockApiUrl}/timezone`,
84
     param: {...FETCH_CONFIG.header, method: 'GET'},
84
     param: {...FETCH_CONFIG.header, method: 'GET'},
85
     actionName: TIMEZONE,
85
     actionName: TIMEZONE,
86
     dispatch
86
     dispatch
88
   if (fetchGetTimezone.status === 200) dispatch(setTimezone(fetchGetTimezone.json))
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
     param: {
94
     param: {
95
-      ...FETCH_CONFIG.header,
95
+      headers: {...FETCH_CONFIG.headers},
96
       method: 'POST',
96
       method: 'POST',
97
       body: JSON.stringify({
97
       body: JSON.stringify({
98
-        login,
99
-        password,
98
+        email: login,
99
+        password: password,
100
         remember_me: rememberMe
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))
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
     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
 }
127
 }
118
 
128
 
119
 export const getUserRole = user => async dispatch => {
129
 export const getUserRole = user => async dispatch => {
120
   const fetchGetUserRole = await fetchWrapper({
130
   const fetchGetUserRole = await fetchWrapper({
121
-    url: `${FETCH_CONFIG.apiUrl}/user/${user.id}/roles`,
131
+    url: `${FETCH_CONFIG.mockApiUrl}/user/${user.id}/roles`,
122
     param: {...FETCH_CONFIG.header, method: 'GET'},
132
     param: {...FETCH_CONFIG.header, method: 'GET'},
123
     actionName: USER_ROLE,
133
     actionName: USER_ROLE,
124
     dispatch
134
     dispatch
128
 
138
 
129
 export const updateUserLang = newLang => async dispatch => { // unused
139
 export const updateUserLang = newLang => async dispatch => { // unused
130
   const fetchUpdateUserLang = await fetchWrapper({
140
   const fetchUpdateUserLang = await fetchWrapper({
131
-    url: `${FETCH_CONFIG.apiUrl}/user`,
141
+    url: `${FETCH_CONFIG.mockApiUrl}/user`,
132
     param: {...FETCH_CONFIG.header, method: 'PATCH', body: JSON.stringify({lang: newLang})},
142
     param: {...FETCH_CONFIG.header, method: 'PATCH', body: JSON.stringify({lang: newLang})},
133
     actionName: USER_DATA,
143
     actionName: USER_DATA,
134
     dispatch
144
     dispatch
148
 
158
 
149
 export const getWorkspaceList = (userId, workspaceIdToOpen) => async dispatch => {
159
 export const getWorkspaceList = (userId, workspaceIdToOpen) => async dispatch => {
150
   const fetchGetWorkspaceList = await fetchWrapper({
160
   const fetchGetWorkspaceList = await fetchWrapper({
151
-    url: `${FETCH_CONFIG.apiUrl}/user/${userId}/workspace`,
161
+    url: `${FETCH_CONFIG.mockApiUrl}/user/${userId}/workspace`,
152
     param: {...FETCH_CONFIG.header, method: 'GET'},
162
     param: {...FETCH_CONFIG.header, method: 'GET'},
153
     actionName: WORKSPACE_LIST,
163
     actionName: WORKSPACE_LIST,
154
     dispatch
164
     dispatch
161
 
171
 
162
 export const getWorkspaceContent = (workspaceId, filterStr) => async dispatch => {
172
 export const getWorkspaceContent = (workspaceId, filterStr) => async dispatch => {
163
   const fetchGetWorkspaceContent = await fetchWrapper({
173
   const fetchGetWorkspaceContent = await fetchWrapper({
164
-    url: `${FETCH_CONFIG.apiUrl}/workspace/${workspaceId}`,
174
+    url: `${FETCH_CONFIG.mockApiUrl}/workspace/${workspaceId}`,
165
     param: {...FETCH_CONFIG.header, method: 'GET'},
175
     param: {...FETCH_CONFIG.header, method: 'GET'},
166
     actionName: WORKSPACE,
176
     actionName: WORKSPACE,
167
     dispatch
177
     dispatch
171
 
181
 
172
 export const getFolderContent = (workspaceId, folderId) => async dispatch => {
182
 export const getFolderContent = (workspaceId, folderId) => async dispatch => {
173
   const fetchGetFolderContent = await fetchWrapper({
183
   const fetchGetFolderContent = await fetchWrapper({
174
-    url: `${FETCH_CONFIG.apiUrl}/workspace/${workspaceId}/folder/${folderId}`,
184
+    url: `${FETCH_CONFIG.mockApiUrl}/workspace/${workspaceId}/folder/${folderId}`,
175
     param: {...FETCH_CONFIG.header, method: 'GET'},
185
     param: {...FETCH_CONFIG.header, method: 'GET'},
176
     actionName: `${WORKSPACE}/${FOLDER}`,
186
     actionName: `${WORKSPACE}/${FOLDER}`,
177
     dispatch
187
     dispatch
181
 
191
 
182
 export const getAppList = () => async dispatch => {
192
 export const getAppList = () => async dispatch => {
183
   const fetchGetAppList = await fetchWrapper({
193
   const fetchGetAppList = await fetchWrapper({
184
-    url: `${FETCH_CONFIG.apiUrl}/app/config`,
194
+    url: `${FETCH_CONFIG.mockApiUrl}/app/config`,
185
     param: {...FETCH_CONFIG.header, method: 'GET'},
195
     param: {...FETCH_CONFIG.header, method: 'GET'},
186
     actionName: APP_LIST,
196
     actionName: APP_LIST,
187
     dispatch
197
     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) =>

+ 3 - 2
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,
11
+        mockApiUrl: FETCH_CONFIG.mockApiUrl
11
       },
12
       },
12
       content
13
       content
13
     })
14
     })

+ 39 - 0
src/component/FlashMessage.jsx View File

1
+import React from 'react'
2
+import classnames from 'classnames'
3
+
4
+const FlashMessage = props => {
5
+  return (
6
+    <div className='flashmessage'>
7
+      {props.flashMessage.length > 0 && (
8
+        <div className='flashmessage__container card'>
9
+          <div className={classnames('flashmessage__container__header', props.flashMessage[0].type)} />
10
+
11
+          <div className='card-body nopadding'>
12
+            <div className='flashmessage__container__close'>
13
+              <div className='flashmessage__container__close__icon' onClick={() => props.removeFlashMessage(props.flashMessage[0].message)}>
14
+                <i className='fa fa-times' />
15
+              </div>
16
+            </div>
17
+
18
+            <div className='flashmessage__container__content'>
19
+              <div className={classnames('flashmessage__container__content__icon', props.flashMessage[0].type)}>
20
+                <i className='fa fa-times-circle' />
21
+              </div>
22
+
23
+              <div className='flashmessage__container__content__text'>
24
+                <div className='flashmessage__container__content__text__title'>
25
+                  {props.t('FlashMessage.error')}
26
+                </div>
27
+                <div className='flashmessage__container__content__text__paragraph'>
28
+                  {props.flashMessage[0].message}
29
+                </div>
30
+              </div>
31
+            </div>
32
+          </div>
33
+        </div>
34
+      )}
35
+    </div>
36
+  )
37
+}
38
+
39
+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'>

+ 0 - 31
src/component/HomepageCard/HomepageCard.jsx View File

1
-import React, { Component } from 'react'
2
-import LogoHomepage from '../../img/logoHeader.svg'
3
-
4
-class HomepageCard extends Component {
5
-  render () {
6
-    return (
7
-      <div className='card homepagecard'>
8
-        <div className='card-body homepagecard__body'>
9
-          <div className='homepagecard__title text-center my-4'>
10
-            Bienvenue sur Tracim
11
-          </div>
12
-          <div className='homepagecard__thanks text-center'>
13
-            Merci de nous faire confiance et d'utiliser notre outil collaboratif
14
-          </div>
15
-          <div className='homepagecard__delimiter delimiter' />
16
-          <div className='homepagecard__text text-center mb-5'>
17
-            Vous allez créez votre premier espace de travail
18
-          </div>
19
-          <div className='homepagecard__btn btn btn-outline-primary'>
20
-            Créer votre espace de travail
21
-          </div>
22
-          <div className='homepagecard__logo mt-5 mb-3'>
23
-            <img src={LogoHomepage} alt='logo homepage' />
24
-          </div>
25
-        </div>
26
-      </div>
27
-    )
28
-  }
29
-}
30
-
31
-export default HomepageCard

+ 1 - 0
src/component/Workspace/BtnExtandedAction.jsx View File

15
       >
15
       >
16
         <i className='fa fa-fw fa-ellipsis-h' />
16
         <i className='fa fa-fw fa-ellipsis-h' />
17
       </button>
17
       </button>
18
+
18
       <div className='extandedaction__subdropdown dropdown-menu' aria-labelledby='dropdownMenuButton'>
19
       <div className='extandedaction__subdropdown dropdown-menu' aria-labelledby='dropdownMenuButton'>
19
         <div className='subdropdown__item dropdown-item d-flex align-items-center' onClick={props.onClickExtendedAction.edit}>
20
         <div className='subdropdown__item dropdown-item d-flex align-items-center' onClick={props.onClickExtendedAction.edit}>
20
           <div className='subdropdown__item__icon mr-3'>
21
           <div className='subdropdown__item__icon mr-3'>

src/component/Workspace/FileItem.jsx → src/component/Workspace/ContentItem.jsx View File

44
   })()
44
   })()
45
 
45
 
46
   return (
46
   return (
47
-    <div className={classnames('file', 'align-items-center', {'item-last': props.isLast}, props.customClass)} onClick={props.onClickItem}>
48
-      <div className='file__type'>
47
+    <div className={classnames('content', 'align-items-center', {'item-last': props.isLast}, props.customClass)} onClick={props.onClickItem}>
48
+      <div className='content__type'>
49
         <i className={props.icon} />
49
         <i className={props.icon} />
50
       </div>
50
       </div>
51
 
51
 
52
-      <div className='file__name'>
53
-        <div className='file__name__text'>
52
+      <div className='content__name'>
53
+        <div className='content__name__text'>
54
           { props.name }
54
           { props.name }
55
         </div>
55
         </div>
56
-
57
-
58
-        {/*
59
-          <div className='file__name__icons d-none d-md-flex'>
60
-            <div className='file__name__icons__download'>
61
-              <i className='fa fa-download' />
62
-            </div>
63
-            <div className='file__name__icons__archive'>
64
-              <i className='fa fa-archive' />
65
-            </div>
66
-            <div className='file__name__icons__delete'>
67
-              <i className='fa fa-trash-o' />
68
-            </div>
69
-          </div>
70
-        */ }
71
       </div>
56
       </div>
72
 
57
 
73
       <div className='d-none d-md-flex'>
58
       <div className='d-none d-md-flex'>
74
         <BtnExtandedAction onClickExtendedAction={props.onClickExtendedAction} />
59
         <BtnExtandedAction onClickExtendedAction={props.onClickExtendedAction} />
75
       </div>
60
       </div>
76
-      <div className={classnames('file__status d-flex align-items-center justify-content-start') + colorStatus}>
77
-        <div className='file__status__icon d-block '>
61
+
62
+      <div className={classnames('content__status d-flex align-items-center justify-content-start') + colorStatus}>
63
+        <div className='content__status__icon d-block '>
78
           <i className={iconStatus} />
64
           <i className={iconStatus} />
79
         </div>
65
         </div>
80
-        <div className='file__status__text d-none d-xl-block'>
66
+        <div className='content__status__text d-none d-xl-block'>
81
           {textStatus}
67
           {textStatus}
82
         </div>
68
         </div>
83
       </div>
69
       </div>

src/component/Workspace/FileItemHeader.jsx → src/component/Workspace/ContentItemHeader.jsx View File

3
 
3
 
4
 const FileItemHeader = props => {
4
 const FileItemHeader = props => {
5
   return (
5
   return (
6
-    <div className='file__header'>
7
-      <div className='file__header__type'>
6
+    <div className='content__header'>
7
+      <div className='content__header__type'>
8
         {props.t('FileItemHeader.type')}
8
         {props.t('FileItemHeader.type')}
9
       </div>
9
       </div>
10
-      <div className='file__header__name'>
10
+      <div className='content__header__name'>
11
         {props.t('FileItemHeader.document_name')}
11
         {props.t('FileItemHeader.document_name')}
12
       </div>
12
       </div>
13
-      <div className='file__header__status'>
13
+      <div className='content__header__status'>
14
         {props.t('FileItemHeader.status')}
14
         {props.t('FileItemHeader.status')}
15
       </div>
15
       </div>
16
     </div>
16
     </div>

+ 2 - 0
src/component/Workspace/FileType/deprecated_folder View File

1
+this folder is deprecated.
2
+content types are handled by respective apps

+ 2 - 2
src/component/Workspace/Folder.jsx View File

2
 import { translate } from 'react-i18next'
2
 import { translate } from 'react-i18next'
3
 import PropTypes from 'prop-types'
3
 import PropTypes from 'prop-types'
4
 import classnames from 'classnames'
4
 import classnames from 'classnames'
5
-import FileItem from './FileItem.jsx'
5
+import FileItem from './ContentItem.jsx'
6
 // import PopupExtandedAction from '../../container/PopupExtandedAction.jsx'
6
 // import PopupExtandedAction from '../../container/PopupExtandedAction.jsx'
7
 import BtnExtandedAction from './BtnExtandedAction.jsx'
7
 import BtnExtandedAction from './BtnExtandedAction.jsx'
8
 
8
 
36
     } = this.props
36
     } = this.props
37
 
37
 
38
     return (
38
     return (
39
-      <div className={classnames('folder', {'active': this.state.open, 'item-last': isLast})}>
39
+      <div className={classnames('folder', {'active': this.state.open && folderData.content.length > 0, 'item-last': isLast})}>
40
         <div className='folder__header align-items-center' onClick={this.handleClickToggleFolder}>
40
         <div className='folder__header align-items-center' onClick={this.handleClickToggleFolder}>
41
 
41
 
42
           <div className='folder__header__triangleborder'>
42
           <div className='folder__header__triangleborder'>

+ 54 - 0
src/component/common/CardPopup/CardPopup.jsx View File

1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import classnames from 'classnames'
4
+
5
+require('./CardPopup.styl')
6
+
7
+const CardPopup = props => {
8
+  return (
9
+    <div className={classnames(props.customClass, 'cardPopup')}>
10
+      <div className='cardPopup__container'>
11
+        <div className='cardPopup__header' />
12
+
13
+        <div className='nopadding'>
14
+          <div className='cardPopup__close' onClick={props.onClose}>
15
+            <i className='fa fa-times' />
16
+          </div>
17
+
18
+          <div className='cardPopup__body'>
19
+            <div className='cardPopup__body__icon'>
20
+              <i className={props.icon} />
21
+            </div>
22
+
23
+            <div className='cardPopup__body__text'>
24
+              <div className='cardPopup__body__text__title'>
25
+                {props.title}
26
+              </div>
27
+              <div className='cardPopup__body__text__message'>
28
+                {props.message}
29
+              </div>
30
+            </div>
31
+          </div>
32
+        </div>
33
+      </div>
34
+    </div>
35
+  )
36
+}
37
+
38
+export default CardPopup
39
+
40
+CardPopup.propTypes = {
41
+  customClass: PropTypes.string,
42
+  title: PropTypes.string,
43
+  message: PropTypes.string,
44
+  icon: PropTypes.string,
45
+  onClose: PropTypes.func
46
+}
47
+
48
+CardPopup.defaultProps = {
49
+  customClass: 'defaultCustomClass',
50
+  title: 'Default title',
51
+  message: 'Default message',
52
+  icon: 'fa fa-times-circle',
53
+  onClose: () => {}
54
+}

+ 39 - 0
src/component/common/CardPopup/CardPopup.styl View File

1
+@import '../../../css/Variable.styl'
2
+
3
+.cardPopup
4
+  position fixed
5
+  display flex
6
+  justify-content center
7
+  width 100%
8
+  z-index 10
9
+  &__container
10
+    margin-top 50px
11
+    border 0
12
+    border-radius 10px
13
+    width 800px
14
+    background lightGrey
15
+    box-shadow shadow-all
16
+  &__header
17
+    border-top-right-radius 10px
18
+    border-top-left-radius 10px
19
+    width 100%
20
+    height 8px
21
+  &__close
22
+    display flex
23
+    justify-content flex-end
24
+    margin 5px
25
+    cursor pointer
26
+  &__body
27
+    display flex
28
+    align-items center
29
+    margin 10px 0 25px 0
30
+    &__icon
31
+      padding 0 30px
32
+      font-size 40px
33
+    &__text
34
+      &__title
35
+        font-size 20px
36
+        font-weight 600
37
+      &__message
38
+        padding-right 20px
39
+        font-weight 500

+ 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
 }

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

476
                         </ul>
476
                         </ul>
477
                       </div>
477
                       </div>
478
                       <div className='dashboard__memberlist__form__submitbtn'>
478
                       <div className='dashboard__memberlist__form__submitbtn'>
479
-                        <button type='submit' className='btn btn-outline-primary'>Valider</button>
479
+                        <button className='btn btn-outline-primary'>Valider</button>
480
                       </div>
480
                       </div>
481
                     </form>
481
                     </form>
482
                   }
482
                   }

+ 0 - 38
src/container/FlashMessage.jsx View File

1
-import React, { Component } from 'react'
2
-
3
-class FlashMessage extends Component {
4
-  render() {
5
-    return(
6
-      <div className='flashmessage'>
7
-        <div className='flashmessage__container card'>
8
-          <div className='flashmessage__container__header' />
9
-
10
-          <div className='card-body nopadding'>
11
-
12
-            <div className='flashmessage__container__close'>
13
-              <i className='fa fa-times' />
14
-            </div>
15
-
16
-            <div className='flashmessage__container__content'>
17
-              <div className='flashmessage__container__content__icon'>
18
-                <i className='fa fa-times-circle' />
19
-              </div>
20
-
21
-              <div className='flashmessage__container__content__text'>
22
-                <div className='flashmessage__container__content__text__title'>
23
-                  Sorry !
24
-                </div>
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.
28
-                </div>
29
-              </div>
30
-            </div>
31
-          </div>
32
-        </div>
33
-      </div>
34
-    )
35
-  }
36
-}
37
-
38
-export default FlashMessage

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

+ 34 - 10
src/container/Home.jsx View File

1
-import React from 'react'
2
-import { connect } from 'react-redux'
1
+import React, { Component } from 'react'
2
+import Card from '../component/common/Card/Card.jsx'
3
+import CardHeader from '../component/common/Card/CardHeader.jsx'
4
+import CardBody from '../component/common/Card/CardBody.jsx'
5
+import LogoHomepage from '../img/logoHeader.svg'
3
 
6
 
4
-class Home extends React.Component {
7
+class Home extends Component {
5
   render () {
8
   render () {
6
-    const { user } = this.props
7
     return (
9
     return (
8
-      <div>
9
-        Home.<br />
10
-        User logged in : {user.isLoggedIn.toString()}
11
-      </div>
10
+      <section className='homepage'>
11
+        <div className='container-fluid nopadding'>
12
+          <Card customClass='homepagecard'>
13
+            <CardHeader /> {/* @TODO fix architecture of this component */}
14
+            <CardBody customClass='homepagecard'>
15
+              <div>
16
+                <div className='homepagecard__title text-center my-4'>
17
+                  Bienvenue sur Tracim
18
+                </div>
19
+                <div className='homepagecard__thanks text-center'>
20
+                  Merci de nous faire confiance et d'utiliser notre outil collaboratif
21
+                </div>
22
+                <div className='homepagecard__delimiter delimiter' />
23
+                <div className='homepagecard__text text-center mb-5'>
24
+                  Vous allez créez votre premier espace de travail
25
+                </div>
26
+                <div className='homepagecard__btn btn btn-outline-primary'>
27
+                  Créer votre espace de travail
28
+                </div>
29
+                <div className='homepagecard__logo mt-5 mb-3'>
30
+                  <img src={LogoHomepage} alt='logo homepage' />
31
+                </div>
32
+              </div>
33
+            </CardBody>
34
+          </Card>
35
+        </div>
36
+      </section>
12
     )
37
     )
13
   }
38
   }
14
 }
39
 }
15
 
40
 
16
-const mapStateToProps = ({ user }) => ({ user })
17
-export default connect(mapStateToProps)(Home)
41
+export default Home

+ 0 - 16
src/container/Homepage.jsx View File

1
-import React, { Component } from 'react'
2
-import HomepageCard from '../Component/HomepageCard/HomepageCard.jsx'
3
-
4
-class Homepage extends Component {
5
-  render () {
6
-    return (
7
-      <section className='homepage'>
8
-        <div className='container-fluid nopadding'>
9
-          <HomepageCard />
10
-        </div>
11
-      </section>
12
-    )
13
-  }
14
-}
15
-
16
-export default Homepage

+ 32 - 12
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 { userLogin } from '../action-creator.async.js'
7
+import { postUserLogin } from '../action-creator.async.js'
7
 import Card from '../component/common/Card/Card.jsx'
8
 import Card from '../component/common/Card/Card.jsx'
8
 import CardHeader from '../component/common/Card/CardHeader.jsx'
9
 import CardHeader from '../component/common/Card/CardHeader.jsx'
9
 import CardBody from '../component/common/Card/CardBody.jsx'
10
 import CardBody from '../component/common/Card/CardBody.jsx'
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(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
   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))

+ 37 - 4
src/container/PopupCreateContainer.jsx View File

1
 import React, { Component } from 'react'
1
 import React, { Component } from 'react'
2
-import GenericContent from '../Component/PopupContent/GenericContent.jsx'
3
-import FileContent from '../Component/PopupContent/FileContent.jsx'
4
-import WksContent from '../Component/PopupContent/WksContent.jsx'
2
+// import GenericContent from '../component/PopupContent/GenericContent.jsx'
3
+// import FileContent from '../component/PopupContent/FileContent.jsx'
4
+// import WksContent from '../component/PopupContent/WksContent.jsx'
5
 
5
 
6
 class PopupCreateContainer extends Component {
6
 class PopupCreateContainer extends Component {
7
   render () {
7
   render () {
10
         <div className='popupcontent__container card'>
10
         <div className='popupcontent__container card'>
11
           <div className='popupcontent__container__header' />
11
           <div className='popupcontent__container__header' />
12
           <div className='card-body nopadding'>
12
           <div className='card-body nopadding'>
13
-            <FileContent />
13
+            <div className='filecontent p-3'>
14
+              <div className='filecontent__close d-flex justify-content-end'>
15
+                <i className='fa fa-times' />
16
+              </div>
17
+              <div className='filecontent__contentname d-flex align-items-center mb-4'>
18
+                <div className='filecontent__contentname__icon mr-3'>
19
+                  <i className='fa fa-file-text-o' />
20
+                </div>
21
+                <div className='filecontent__contentname__title'>
22
+                  Fichier de prévisualisation
23
+                </div>
24
+              </div>
25
+
26
+              <div className='filecontent__text'>Importer votre fichier :</div>
27
+              <div className='filecontent__form mb-4' drop='true'>
28
+                <div className='filecontent__form__icon d-flex justify-content-center'>
29
+                  <label htmlFor='filecontentUpload' type='file'>
30
+                    <i className='fa fa-download' />
31
+                  </label>
32
+                  <input type='file' className='d-none' id='filecontentUpload' />
33
+                </div>
34
+                <div className='filecontent__form__instruction text-center'>
35
+                  Glisser votre fichier ici
36
+                </div>
37
+                <div className='filecontent__form__text text-center'>
38
+                  Vous pouvez également importer votre fichier en cliquant sur l'icon
39
+                </div>
40
+              </div>
41
+              <div className='filecontent__button d-flex justify-content-end'>
42
+                <button className='filecontent__form__button btn btn-outline-primary'>
43
+                  Créer et Valider
44
+                </button>
45
+              </div>
46
+            </div>
14
           </div>
47
           </div>
15
         </div>
48
         </div>
16
       </div>
49
       </div>

+ 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
 }

+ 4 - 4
src/container/Sidebar.jsx View File

64
 
64
 
65
     return (
65
     return (
66
       <div className={classnames('sidebar', {'sidebarclose': sidebarClose})}>
66
       <div className={classnames('sidebar', {'sidebarclose': sidebarClose})}>
67
-        <div className='sidebar__expand' onClick={this.handleClickToggleSidebar}>
68
-          <i className={classnames('fa fa-chevron-left', {'fa-chevron-right': sidebarClose, 'fa-chevron-left': !sidebarClose})} />
69
-        </div>
70
-
71
         <div className='sidebarSticky'>
67
         <div className='sidebarSticky'>
68
+          <div className='sidebar__expand' onClick={this.handleClickToggleSidebar}>
69
+            <i className={classnames('fa fa-chevron-left', {'fa-chevron-right': sidebarClose, 'fa-chevron-left': !sidebarClose})} />
70
+          </div>
71
+
72
           <nav className='sidebar__navigation'>
72
           <nav className='sidebar__navigation'>
73
             <ul className='sidebar__navigation__workspace'>
73
             <ul className='sidebar__navigation__workspace'>
74
               { workspaceList.map((ws, i) =>
74
               { workspaceList.map((ws, i) =>

+ 30 - 9
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'
11
+import WIPcomponent from './WIPcomponent.jsx'
10
 import {
12
 import {
11
   Route,
13
   Route,
12
   withRouter
14
   withRouter
15
 import { PAGE_NAME } from '../helper.js'
17
 import { PAGE_NAME } from '../helper.js'
16
 import {
18
 import {
17
   getLangList,
19
   getLangList,
18
-  getIsUserConnected
20
+  getUserIsConnected
19
 } from '../action-creator.async.js'
21
 } from '../action-creator.async.js'
22
+import {
23
+  removeFlashMessage,
24
+  setUserConnected
25
+} from '../action-creator.sync.js'
20
 
26
 
21
 class Tracim extends React.Component {
27
 class Tracim extends React.Component {
22
-  componentDidMount () {
23
-    this.props.dispatch(getIsUserConnected())
24
-    this.props.dispatch(getLangList())
28
+  async componentDidMount () {
29
+    const { dispatch } = this.props
30
+
31
+    dispatch(getLangList())
32
+
33
+    const fetchGetUserIsConnected = await dispatch(getUserIsConnected())
34
+    switch (fetchGetUserIsConnected.status) {
35
+      case 200:
36
+        dispatch(setUserConnected({...fetchGetUserIsConnected.json, logged: true})); break
37
+      case 401:
38
+        dispatch(setUserConnected({logged: false})); break
39
+      default:
40
+        dispatch(setUserConnected({logged: undefined})); break
41
+    }
25
   }
42
   }
26
 
43
 
44
+  handleRemoveFlashMessage = msg => this.props.dispatch(removeFlashMessage(msg))
45
+
27
   render () {
46
   render () {
28
-    const { user } = this.props
47
+    const { flashMessage, user, t } = this.props
29
 
48
 
30
     return (
49
     return (
31
       <div className='tracim'>
50
       <div className='tracim'>
32
         <Header />
51
         <Header />
52
+        <FlashMessage flashMessage={flashMessage} removeFlashMessage={this.handleRemoveFlashMessage} t={t} />
33
 
53
 
34
-        { user.isLoggedIn === undefined
54
+        { user.logged === undefined
35
           ? (<div />) // while we dont know if user is connected, display nothing but the header @TODO show loader
55
           ? (<div />) // while we dont know if user is connected, display nothing but the header @TODO show loader
36
           : (
56
           : (
37
             <div className='tracim__content'>
57
             <div className='tracim__content'>
41
               <PrivateRoute path={`${PAGE_NAME.WS_CONTENT}/:idws/:filter?`} component={WorkspaceContent} />
61
               <PrivateRoute path={`${PAGE_NAME.WS_CONTENT}/:idws/:filter?`} component={WorkspaceContent} />
42
               <PrivateRoute exact path={PAGE_NAME.ACCOUNT} component={Account} />
62
               <PrivateRoute exact path={PAGE_NAME.ACCOUNT} component={Account} />
43
               <PrivateRoute exact path={PAGE_NAME.DASHBOARD} component={Dashboard} />
63
               <PrivateRoute exact path={PAGE_NAME.DASHBOARD} component={Dashboard} />
64
+              <PrivateRoute path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
44
 
65
 
45
               <Footer />
66
               <Footer />
46
             </div>
67
             </div>
51
   }
72
   }
52
 }
73
 }
53
 
74
 
54
-const mapStateToProps = ({ user }) => ({ user })
55
-export default withRouter(connect(mapStateToProps)(Tracim))
75
+const mapStateToProps = ({ flashMessage, user }) => ({ flashMessage, user })
76
+export default withRouter(connect(mapStateToProps)(translate()(Tracim)))

+ 30 - 0
src/container/WIPcomponent.jsx View File

1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import PopupCreateContainer from './PopupCreateContainer.jsx'
4
+import ProgressBar from './ProgressBar.jsx'
5
+import Home from './Home.jsx'
6
+import CardPopup from '../component/common/CardPopup/CardPopup.jsx'
7
+
8
+export class WIPcomponent extends React.Component {
9
+  render () {
10
+    const MyComponent = {
11
+      PopupCreateContainer,
12
+      ProgressBar,
13
+      Home,
14
+      CardPopup
15
+    }
16
+
17
+    // this.props.dispatch(newFlashMessage('TEST', 'info', 0))
18
+
19
+    const ComponentToDisplay = MyComponent[this.props.match.params.cp]
20
+
21
+    return (
22
+      <div>
23
+        <ComponentToDisplay />
24
+      </div>
25
+    )
26
+  }
27
+}
28
+
29
+const mapStateToProps = () => ({})
30
+export default connect(mapStateToProps)(WIPcomponent)

+ 5 - 5
src/container/WorkspaceContent.jsx View File

3
 import appFactory from '../appFactory.js'
3
 import appFactory from '../appFactory.js'
4
 import Sidebar from './Sidebar.jsx'
4
 import Sidebar from './Sidebar.jsx'
5
 import Folder from '../component/Workspace/Folder.jsx'
5
 import Folder from '../component/Workspace/Folder.jsx'
6
-import FileItem from '../component/Workspace/FileItem.jsx'
7
-import FileItemHeader from '../component/Workspace/FileItemHeader.jsx'
6
+import ContentItem from '../component/Workspace/ContentItem.jsx'
7
+import ContentItemHeader from '../component/Workspace/ContentItemHeader.jsx'
8
 import PageWrapper from '../component/common/layout/PageWrapper.jsx'
8
 import PageWrapper from '../component/common/layout/PageWrapper.jsx'
9
 import PageTitle from '../component/common/layout/PageTitle.jsx'
9
 import PageTitle from '../component/common/layout/PageTitle.jsx'
10
 import PageContent from '../component/common/layout/PageContent.jsx'
10
 import PageContent from '../component/common/layout/PageContent.jsx'
76
     ? contentList
76
     ? contentList
77
     : contentList.filter(c => c.type === 'folder' || filter.includes(c.type)) // keep unfiltered files and folders
77
     : contentList.filter(c => c.type === 'folder' || filter.includes(c.type)) // keep unfiltered files and folders
78
       .map(c => c.type !== 'folder' ? c : {...c, content: this.filterWorkspaceContent(c.content, filter)}) // recursively filter folder content
78
       .map(c => c.type !== 'folder' ? c : {...c, content: this.filterWorkspaceContent(c.content, filter)}) // recursively filter folder content
79
-      .filter(c => c.type !== 'folder' || c.content.length > 0) // remove empty folder
79
+      // .filter(c => c.type !== 'folder' || c.content.length > 0) // remove empty folder => 2018/05/21 - since we load only one lvl of content, don't remove empty folders
80
 
80
 
81
   render () {
81
   render () {
82
     const { workspace, app } = this.props
82
     const { workspace, app } = this.props
98
 
98
 
99
           <PageContent parentClass='workspace__content'>
99
           <PageContent parentClass='workspace__content'>
100
             <div className='workspace__content__fileandfolder folder__content active'>
100
             <div className='workspace__content__fileandfolder folder__content active'>
101
-              <FileItemHeader />
101
+              <ContentItemHeader />
102
 
102
 
103
               { filteredWorkspaceContent.map((c, i) => c.type === 'folder'
103
               { filteredWorkspaceContent.map((c, i) => c.type === 'folder'
104
                 ? (
104
                 ? (
119
                   />
119
                   />
120
                 )
120
                 )
121
                 : (
121
                 : (
122
-                  <FileItem
122
+                  <ContentItem
123
                     name={c.title}
123
                     name={c.title}
124
                     type={c.type}
124
                     type={c.type}
125
                     icon={(app[c.type] || {icon: ''}).icon}
125
                     icon={(app[c.type] || {icon: ''}).icon}

src/css/FileItem.styl → src/css/ContentItem.styl View File

10
 .outdateColor
10
 .outdateColor
11
   color outdateColor
11
   color outdateColor
12
 
12
 
13
-.file
13
+.content
14
   display flex
14
   display flex
15
   align-items center
15
   align-items center
16
   padding 10px 0
16
   padding 10px 0
50
 
50
 
51
 
51
 
52
 @media (min-width: max-xs) and (max-width: max-lg)
52
 @media (min-width: max-xs) and (max-width: max-lg)
53
-  .file
53
+  .content
54
     border-right 1px solid grey
54
     border-right 1px solid grey
55
     &__name__icons
55
     &__name__icons
56
         margin-top 5px
56
         margin-top 5px

src/css/FileItemHeader.styl → src/css/ContentItemHeader.styl View File

1
-.file__header
1
+.content__header
2
   display flex
2
   display flex
3
   font-size 17px
3
   font-size 17px
4
   &__type
4
   &__type

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

1
+// @Côme - this file is deprecated as its associated jsx file is deprecated
1
 .wsFileFile
2
 .wsFileFile
2
   width 1200px
3
   width 1200px
3
   height calc(100% - 85px)
4
   height calc(100% - 85px)

+ 19 - 8
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
23
       margin 5px
28
       margin 5px
24
-      cursor pointer
29
+      &__icon
30
+        padding-right 10px
31
+        cursor pointer
25
     &__content
32
     &__content
26
       display flex
33
       display flex
27
       align-items center
34
       align-items center
28
-      margin 10px 0 35px 0
35
+      margin 10px 0 25px 0
29
       &__icon
36
       &__icon
30
         padding 0 30px
37
         padding 0 30px
31
-        font-size 60px
32
-        color calendar
38
+        font-size 40px
39
+        &.success
40
+          color success
41
+        &.info
42
+          color info
43
+        &.danger
44
+          color danger
33
       &__text
45
       &__text
34
         &__title
46
         &__title
35
-          margin-bottom 10px
36
           font-size 20px
47
           font-size 20px
37
           font-weight 600
48
           font-weight 600
38
           color calendar
49
           color calendar

+ 3 - 3
src/css/Folder.styl View File

3
 border-style = 1px solid secondColor
3
 border-style = 1px solid secondColor
4
 .folder__header
4
 .folder__header
5
   border border-style
5
   border border-style
6
-.folder + .file, .file + .file
6
+.folder + .content, .content + .content
7
   border-bottom 0
7
   border-bottom 0
8
-.folder__content > .file, .folder__content > .folder
8
+.folder__content > .content, .folder__content > .folder
9
   border-bottom 0
9
   border-bottom 0
10
 .folder:not(.active).item-last
10
 .folder:not(.active).item-last
11
   border-bottom border-style
11
   border-bottom border-style
12
-.file
12
+.content
13
   border border-style
13
   border border-style
14
   &.item-last
14
   &.item-last
15
     border-bottom border-style
15
     border-bottom border-style

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

107
     cursor pointer
107
     cursor pointer
108
     .setting__link
108
     .setting__link
109
       padding 10px
109
       padding 10px
110
+      cursor pointer
110
 
111
 
111
 .fa-file-image-o
112
 .fa-file-image-o
112
   color previewColor
113
   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

+ 2 - 2
src/css/index.styl View File

14
 @import 'Login.styl'
14
 @import 'Login.styl'
15
 @import 'Workspace.styl'
15
 @import 'Workspace.styl'
16
 
16
 
17
-@import 'FileItem.styl'
18
-@import 'FileItemHeader.styl'
17
+@import 'ContentItem.styl'
18
+@import 'ContentItemHeader.styl'
19
 @import 'Folder.styl'
19
 @import 'Folder.styl'
20
 
20
 
21
 @import 'File.styl'
21
 @import 'File.styl'

+ 3 - 2
src/helper.js View File

1
 export const FETCH_CONFIG = {
1
 export const FETCH_CONFIG = {
2
-  header: {
2
+  headers: {
3
     'Accept': 'application/json',
3
     'Accept': 'application/json',
4
     'Content-Type': 'application/json'
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
 export const PAGE_NAME = {
10
 export const PAGE_NAME = {

+ 1 - 1
src/index.js View File

11
 
11
 
12
 ReactDOM.render(
12
 ReactDOM.render(
13
   <Provider store={store}>
13
   <Provider store={store}>
14
-    <BrowserRouter>
14
+    <BrowserRouter basename={'/#'}>
15
       <I18nextProvider i18n={i18n}>
15
       <I18nextProvider i18n={i18n}>
16
         <Tracim />
16
         <Tracim />
17
       </I18nextProvider>
17
       </I18nextProvider>

+ 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: undefined, // undefined avoid to be redirected to /login while whoami ep has not responded yet
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',