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

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

Luc преди 6 години
родител
ревизия
50531dc4c3
променени са 46 файла, в които са добавени 515 реда и са изтрити 253 реда
  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 Целия файл

@@ -59,3 +59,12 @@ You also need to make the mock api able to tell tracim_frontend that it handle y
59 59
 - add an entry for you App in tracim_frontend/jsonserver/static_db.json in the `app_config` property
60 60
 - reload your mock api server
61 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 Целия файл

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

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

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

+ 16 - 15
jsonserver/static_db.json Целия файл

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

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

@@ -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 Целия файл

@@ -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) =>

+ 3 - 2
src/appFactory.js Целия файл

@@ -4,10 +4,11 @@ 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
-        apiUrl: FETCH_CONFIG.apiUrl
10
+        apiUrl: FETCH_CONFIG.apiUrl,
11
+        mockApiUrl: FETCH_CONFIG.mockApiUrl
11 12
       },
12 13
       content
13 14
     })

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

@@ -0,0 +1,39 @@
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 Целия файл

@@ -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'>

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

@@ -1,31 +0,0 @@
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 Целия файл

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

src/component/Workspace/FileItem.jsx → src/component/Workspace/ContentItem.jsx Целия файл

@@ -44,40 +44,26 @@ const FileItem = props => {
44 44
   })()
45 45
 
46 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 49
         <i className={props.icon} />
50 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 54
           { props.name }
55 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 56
       </div>
72 57
 
73 58
       <div className='d-none d-md-flex'>
74 59
         <BtnExtandedAction onClickExtendedAction={props.onClickExtendedAction} />
75 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 64
           <i className={iconStatus} />
79 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 67
           {textStatus}
82 68
         </div>
83 69
       </div>

src/component/Workspace/FileItemHeader.jsx → src/component/Workspace/ContentItemHeader.jsx Целия файл

@@ -3,14 +3,14 @@ import { translate } from 'react-i18next'
3 3
 
4 4
 const FileItemHeader = props => {
5 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 8
         {props.t('FileItemHeader.type')}
9 9
       </div>
10
-      <div className='file__header__name'>
10
+      <div className='content__header__name'>
11 11
         {props.t('FileItemHeader.document_name')}
12 12
       </div>
13
-      <div className='file__header__status'>
13
+      <div className='content__header__status'>
14 14
         {props.t('FileItemHeader.status')}
15 15
       </div>
16 16
     </div>

+ 2 - 0
src/component/Workspace/FileType/deprecated_folder Целия файл

@@ -0,0 +1,2 @@
1
+this folder is deprecated.
2
+content types are handled by respective apps

+ 2 - 2
src/component/Workspace/Folder.jsx Целия файл

@@ -2,7 +2,7 @@ import React from 'react'
2 2
 import { translate } from 'react-i18next'
3 3
 import PropTypes from 'prop-types'
4 4
 import classnames from 'classnames'
5
-import FileItem from './FileItem.jsx'
5
+import FileItem from './ContentItem.jsx'
6 6
 // import PopupExtandedAction from '../../container/PopupExtandedAction.jsx'
7 7
 import BtnExtandedAction from './BtnExtandedAction.jsx'
8 8
 
@@ -36,7 +36,7 @@ class Folder extends React.Component {
36 36
     } = this.props
37 37
 
38 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 40
         <div className='folder__header align-items-center' onClick={this.handleClickToggleFolder}>
41 41
 
42 42
           <div className='folder__header__triangleborder'>

+ 54 - 0
src/component/common/CardPopup/CardPopup.jsx Целия файл

@@ -0,0 +1,54 @@
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 Целия файл

@@ -0,0 +1,39 @@
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 Целия файл

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

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

@@ -476,7 +476,7 @@ class Dashboard extends Component {
476 476
                         </ul>
477 477
                       </div>
478 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 480
                       </div>
481 481
                     </form>
482 482
                   }

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

@@ -1,38 +0,0 @@
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 Целия файл

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

@@ -1,17 +1,41 @@
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 8
   render () {
6
-    const { user } = this.props
7 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 Целия файл

@@ -1,16 +0,0 @@
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 Целия файл

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

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

@@ -1,7 +1,7 @@
1 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 6
 class PopupCreateContainer extends Component {
7 7
   render () {
@@ -10,7 +10,40 @@ class PopupCreateContainer extends Component {
10 10
         <div className='popupcontent__container card'>
11 11
           <div className='popupcontent__container__header' />
12 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 47
           </div>
15 48
         </div>
16 49
       </div>

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

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

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

@@ -64,11 +64,11 @@ class Sidebar extends React.Component {
64 64
 
65 65
     return (
66 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 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 72
           <nav className='sidebar__navigation'>
73 73
             <ul className='sidebar__navigation__workspace'>
74 74
               { workspaceList.map((ws, i) =>

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

@@ -1,12 +1,14 @@
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'
11
+import WIPcomponent from './WIPcomponent.jsx'
10 12
 import {
11 13
   Route,
12 14
   withRouter
@@ -15,23 +17,41 @@ import PrivateRoute from './PrivateRoute.jsx'
15 17
 import { PAGE_NAME } from '../helper.js'
16 18
 import {
17 19
   getLangList,
18
-  getIsUserConnected
20
+  getUserIsConnected
19 21
 } from '../action-creator.async.js'
22
+import {
23
+  removeFlashMessage,
24
+  setUserConnected
25
+} from '../action-creator.sync.js'
20 26
 
21 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 46
   render () {
28
-    const { user } = this.props
47
+    const { flashMessage, user, t } = this.props
29 48
 
30 49
     return (
31 50
       <div className='tracim'>
32 51
         <Header />
52
+        <FlashMessage flashMessage={flashMessage} removeFlashMessage={this.handleRemoveFlashMessage} t={t} />
33 53
 
34
-        { user.isLoggedIn === undefined
54
+        { user.logged === undefined
35 55
           ? (<div />) // while we dont know if user is connected, display nothing but the header @TODO show loader
36 56
           : (
37 57
             <div className='tracim__content'>
@@ -41,6 +61,7 @@ class Tracim extends React.Component {
41 61
               <PrivateRoute path={`${PAGE_NAME.WS_CONTENT}/:idws/:filter?`} component={WorkspaceContent} />
42 62
               <PrivateRoute exact path={PAGE_NAME.ACCOUNT} component={Account} />
43 63
               <PrivateRoute exact path={PAGE_NAME.DASHBOARD} component={Dashboard} />
64
+              <PrivateRoute path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
44 65
 
45 66
               <Footer />
46 67
             </div>
@@ -51,5 +72,5 @@ class Tracim extends React.Component {
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 Целия файл

@@ -0,0 +1,30 @@
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 Целия файл

@@ -3,8 +3,8 @@ import { connect } from 'react-redux'
3 3
 import appFactory from '../appFactory.js'
4 4
 import Sidebar from './Sidebar.jsx'
5 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 8
 import PageWrapper from '../component/common/layout/PageWrapper.jsx'
9 9
 import PageTitle from '../component/common/layout/PageTitle.jsx'
10 10
 import PageContent from '../component/common/layout/PageContent.jsx'
@@ -76,7 +76,7 @@ class WorkspaceContent extends React.Component {
76 76
     ? contentList
77 77
     : contentList.filter(c => c.type === 'folder' || filter.includes(c.type)) // keep unfiltered files and folders
78 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 81
   render () {
82 82
     const { workspace, app } = this.props
@@ -98,7 +98,7 @@ class WorkspaceContent extends React.Component {
98 98
 
99 99
           <PageContent parentClass='workspace__content'>
100 100
             <div className='workspace__content__fileandfolder folder__content active'>
101
-              <FileItemHeader />
101
+              <ContentItemHeader />
102 102
 
103 103
               { filteredWorkspaceContent.map((c, i) => c.type === 'folder'
104 104
                 ? (
@@ -119,7 +119,7 @@ class WorkspaceContent extends React.Component {
119 119
                   />
120 120
                 )
121 121
                 : (
122
-                  <FileItem
122
+                  <ContentItem
123 123
                     name={c.title}
124 124
                     type={c.type}
125 125
                     icon={(app[c.type] || {icon: ''}).icon}

src/css/FileItem.styl → src/css/ContentItem.styl Целия файл

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

src/css/FileItemHeader.styl → src/css/ContentItemHeader.styl Целия файл

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

+ 1 - 0
src/css/File.styl Целия файл

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

+ 19 - 8
src/css/FlashMessage.styl Целия файл

@@ -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,23 +16,34 @@
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
23 28
       margin 5px
24
-      cursor pointer
29
+      &__icon
30
+        padding-right 10px
31
+        cursor pointer
25 32
     &__content
26 33
       display flex
27 34
       align-items center
28
-      margin 10px 0 35px 0
35
+      margin 10px 0 25px 0
29 36
       &__icon
30 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 45
       &__text
34 46
         &__title
35
-          margin-bottom 10px
36 47
           font-size 20px
37 48
           font-weight 600
38 49
           color calendar

+ 3 - 3
src/css/Folder.styl Целия файл

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

+ 1 - 0
src/css/Generic.styl Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

@@ -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

+ 2 - 2
src/css/index.styl Целия файл

@@ -14,8 +14,8 @@ html, body, #content
14 14
 @import 'Login.styl'
15 15
 @import 'Workspace.styl'
16 16
 
17
-@import 'FileItem.styl'
18
-@import 'FileItemHeader.styl'
17
+@import 'ContentItem.styl'
18
+@import 'ContentItemHeader.styl'
19 19
 @import 'Folder.styl'
20 20
 
21 21
 @import 'File.styl'

+ 3 - 2
src/helper.js Целия файл

@@ -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 = {

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

@@ -11,7 +11,7 @@ require('./css/index.styl')
11 11
 
12 12
 ReactDOM.render(
13 13
   <Provider store={store}>
14
-    <BrowserRouter>
14
+    <BrowserRouter basename={'/#'}>
15 15
       <I18nextProvider i18n={i18n}>
16 16
         <Tracim />
17 17
       </I18nextProvider>

+ 19 - 0
src/reducer/flashMessage.js Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

@@ -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: 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 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 Целия файл

@@ -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 Целия файл

@@ -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',