Browse Source

pluged in account page

Skylsmoi 6 years ago
parent
commit
59ad8431ee

+ 80 - 28
frontend/src/action-creator.async.js View File

1
 import { FETCH_CONFIG } from './helper.js'
1
 import { FETCH_CONFIG } from './helper.js'
2
 import {
2
 import {
3
-  TIMEZONE,
4
-  setTimezone,
5
   USER_LOGIN,
3
   USER_LOGIN,
6
   USER_LOGOUT,
4
   USER_LOGOUT,
7
-  USER_ROLE,
8
   USER_CONNECTED,
5
   USER_CONNECTED,
9
   USER_KNOWN_MEMBER_LIST,
6
   USER_KNOWN_MEMBER_LIST,
10
-  setUserRole,
7
+  USER_NAME,
8
+  USER_EMAIL,
9
+  USER_PASSWORD,
11
   WORKSPACE,
10
   WORKSPACE,
12
   WORKSPACE_LIST,
11
   WORKSPACE_LIST,
13
   WORKSPACE_DETAIL,
12
   WORKSPACE_DETAIL,
20
   WORKSPACE_CONTENT_ARCHIVED,
19
   WORKSPACE_CONTENT_ARCHIVED,
21
   WORKSPACE_CONTENT_DELETED,
20
   WORKSPACE_CONTENT_DELETED,
22
   WORKSPACE_RECENT_ACTIVITY,
21
   WORKSPACE_RECENT_ACTIVITY,
23
-  WORKSPACE_READ_STATUS
22
+  WORKSPACE_READ_STATUS,
23
+  USER_WORKSPACE_DO_NOTIFY
24
 } from './action-creator.sync.js'
24
 } from './action-creator.sync.js'
25
 
25
 
26
 /*
26
 /*
82
   return fetchResult
82
   return fetchResult
83
 }
83
 }
84
 
84
 
85
-export const getTimezone = () => async dispatch => {
86
-  const fetchGetTimezone = await fetchWrapper({
87
-    url: `${FETCH_CONFIG.apiUrl}/timezone`,
88
-    param: {
89
-      headers: {...FETCH_CONFIG.headers},
90
-      method: 'GET'
91
-    },
92
-    actionName: TIMEZONE,
93
-    dispatch
94
-  })
95
-  if (fetchGetTimezone.status === 200) dispatch(setTimezone(fetchGetTimezone.json))
96
-}
97
-
98
 export const postUserLogin = (login, password, rememberMe) => async dispatch => {
85
 export const postUserLogin = (login, password, rememberMe) => async dispatch => {
99
   return fetchWrapper({
86
   return fetchWrapper({
100
     url: `${FETCH_CONFIG.apiUrl}/sessions/login`, // FETCH_CONFIG.apiUrl
87
     url: `${FETCH_CONFIG.apiUrl}/sessions/login`, // FETCH_CONFIG.apiUrl
139
   })
126
   })
140
 }
127
 }
141
 
128
 
142
-export const getUserRole = user => async dispatch => {
143
-  const fetchGetUserRole = await fetchWrapper({
144
-    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/roles`,
129
+export const getUserKnownMember = (user, userNameToSearch) => dispatch => {
130
+  return fetchWrapper({
131
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/known_members?acp=${userNameToSearch}`,
145
     param: {
132
     param: {
146
-      headers: {...FETCH_CONFIG.headers},
133
+      headers: {
134
+        ...FETCH_CONFIG.headers,
135
+        'Authorization': 'Basic ' + user.auth
136
+      },
147
       method: 'GET'
137
       method: 'GET'
148
     },
138
     },
149
-    actionName: USER_ROLE,
139
+    actionName: USER_KNOWN_MEMBER_LIST,
150
     dispatch
140
     dispatch
151
   })
141
   })
152
-  if (fetchGetUserRole.status === 200) dispatch(setUserRole(fetchGetUserRole.json))
153
 }
142
 }
154
 
143
 
155
-export const getUserKnownMember = (user, userNameToSearch) => dispatch => {
144
+export const putUserName = (user, newName) => dispatch => {
156
   return fetchWrapper({
145
   return fetchWrapper({
157
-    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/known_members?acp=${userNameToSearch}`,
146
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}`,
158
     param: {
147
     param: {
159
       headers: {
148
       headers: {
160
         ...FETCH_CONFIG.headers,
149
         ...FETCH_CONFIG.headers,
161
         'Authorization': 'Basic ' + user.auth
150
         'Authorization': 'Basic ' + user.auth
162
       },
151
       },
163
-      method: 'GET'
152
+      method: 'PUT',
153
+      body: JSON.stringify({
154
+        public_name: newName,
155
+        timezone: user.timezone
156
+      })
164
     },
157
     },
165
-    actionName: USER_KNOWN_MEMBER_LIST,
158
+    actionName: USER_NAME,
159
+    dispatch
160
+  })
161
+}
162
+
163
+export const putUserEmail = (user, newEmail, checkPassword) => dispatch => {
164
+  return fetchWrapper({
165
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/email`,
166
+    param: {
167
+      headers: {
168
+        ...FETCH_CONFIG.headers,
169
+        'Authorization': 'Basic ' + user.auth
170
+      },
171
+      method: 'PUT',
172
+      body: JSON.stringify({
173
+        email: newEmail,
174
+        loggedin_user_password: checkPassword
175
+      })
176
+    },
177
+    actionName: USER_EMAIL,
178
+    dispatch
179
+  })
180
+}
181
+
182
+export const putUserPassword = (user, oldPassword, newPassword, newPassword2) => dispatch => {
183
+  return fetchWrapper({
184
+    url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/password`,
185
+    param: {
186
+      headers: {
187
+        ...FETCH_CONFIG.headers,
188
+        'Authorization': 'Basic ' + user.auth
189
+      },
190
+      method: 'PUT',
191
+      body: JSON.stringify({
192
+        loggedin_user_password: oldPassword,
193
+        new_password: newPassword,
194
+        new_password2: newPassword2
195
+      })
196
+    },
197
+    actionName: USER_PASSWORD,
166
     dispatch
198
     dispatch
167
   })
199
   })
168
 }
200
 }
182
   })
214
   })
183
 }
215
 }
184
 
216
 
217
+export const putUserWorkspaceDoNotify = (user, idWorkspace, doNotify) => dispatch => {
218
+  return fetchWrapper({
219
+    // @TODO Côme - 2018/08/23 - this is the wrong endpoint, but backend hasn't implemented it yet
220
+    url: `${FETCH_CONFIG.apiUrl}/workspaces/${idWorkspace}/members/${user.user_id}`,
221
+    param: {
222
+      headers: {
223
+        ...FETCH_CONFIG.headers,
224
+        'Authorization': 'Basic ' + user.auth
225
+      },
226
+      method: 'PUT',
227
+      body: JSON.stringify({
228
+        do_notify: doNotify
229
+      })
230
+
231
+    },
232
+    actionName: USER_WORKSPACE_DO_NOTIFY,
233
+    dispatch
234
+  })
235
+}
236
+
185
 export const getWorkspaceList = user => dispatch => {
237
 export const getWorkspaceList = user => dispatch => {
186
   return fetchWrapper({
238
   return fetchWrapper({
187
     url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces`,
239
     url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces`,

+ 20 - 9
frontend/src/action-creator.sync.js View File

1
-export const SET = 'Set'
2
-export const UPDATE = 'Update'
1
+export const SET = 'Set' // save data from api
2
+export const UPDATE = 'Update' // edit data from api
3
 export const ADD = 'Add'
3
 export const ADD = 'Add'
4
 export const REMOVE = 'Remove'
4
 export const REMOVE = 'Remove'
5
 export const APPEND = 'Append'
5
 export const APPEND = 'Append'
22
 export const USER_DISCONNECTED = `${USER}/Disconnected`
22
 export const USER_DISCONNECTED = `${USER}/Disconnected`
23
 export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
23
 export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
24
 export const setUserDisconnected = () => ({ type: `${SET}/${USER}/Disconnected` })
24
 export const setUserDisconnected = () => ({ type: `${SET}/${USER}/Disconnected` })
25
-export const USER_DATA = `${USER}/Data`
26
-export const updateUserData = userData => ({ type: `${UPDATE}/${USER}/Data`, data: userData })
27
-export const USER_ROLE = `${USER}/Role`
28
-export const setUserRole = userRole => ({ type: `${SET}/${USER}/Role`, userRole }) // this actually update workspaceList state
29
-export const updateUserWorkspaceSubscriptionNotif = (workspaceId, subscriptionNotif) =>
30
-  ({ type: `${UPDATE}/${USER_ROLE}/SubscriptionNotif`, workspaceId, subscriptionNotif })
25
+
31
 export const USER_LANG = `${USER}/Lang`
26
 export const USER_LANG = `${USER}/Lang`
32
 export const setUserLang = lang => ({ type: `${SET}/${USER}/Lang`, lang })
27
 export const setUserLang = lang => ({ type: `${SET}/${USER}/Lang`, lang })
33
 export const USER_KNOWN_MEMBER = `${USER}/KnownMember`
28
 export const USER_KNOWN_MEMBER = `${USER}/KnownMember`
34
 export const USER_KNOWN_MEMBER_LIST = `${USER_KNOWN_MEMBER}/List`
29
 export const USER_KNOWN_MEMBER_LIST = `${USER_KNOWN_MEMBER}/List`
35
 
30
 
31
+export const USER_NAME = `${USER}/PublicName`
32
+export const updateUserName = newName => ({ type: `${UPDATE}/${USER_NAME}`, newName })
33
+export const USER_EMAIL = `${USER}/Email`
34
+export const updateUserEmail = newEmail => ({ type: `${UPDATE}/${USER_EMAIL}`, newEmail })
35
+export const USER_PASSWORD = `${USER}/Password`
36
+export const USER_AUTH = `${USER}/Auth`
37
+export const updateUserAuth = newAuth => ({ type: `${UPDATE}/${USER_AUTH}`, newAuth })
38
+
36
 export const WORKSPACE = 'Workspace'
39
 export const WORKSPACE = 'Workspace'
37
 export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
40
 export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
38
 export const setWorkspaceContentList = workspaceContentList => ({ type: `${SET}/${WORKSPACE_CONTENT}`, workspaceContentList })
41
 export const setWorkspaceContentList = workspaceContentList => ({ type: `${SET}/${WORKSPACE_CONTENT}`, workspaceContentList })
39
 export const updateWorkspaceFilter = filterList => ({ type: `${UPDATE}/${WORKSPACE}/Filter`, filterList })
42
 export const updateWorkspaceFilter = filterList => ({ type: `${UPDATE}/${WORKSPACE}/Filter`, filterList })
40
 
43
 
44
+export const USER_WORKSPACE_DO_NOTIFY = `${USER}/${WORKSPACE}/SubscriptionNotif`
45
+export const updateUserWorkspaceSubscriptionNotif = (idUser, idWorkspace, doNotify) =>
46
+  ({ type: `${UPDATE}/${USER_WORKSPACE_DO_NOTIFY}`, idUser, idWorkspace, doNotify })
47
+
41
 export const WORKSPACE_CONTENT_ARCHIVED = `${WORKSPACE_CONTENT}/Archived`
48
 export const WORKSPACE_CONTENT_ARCHIVED = `${WORKSPACE_CONTENT}/Archived`
42
 export const WORKSPACE_CONTENT_DELETED = `${WORKSPACE_CONTENT}/Deleted`
49
 export const WORKSPACE_CONTENT_DELETED = `${WORKSPACE_CONTENT}/Deleted`
43
 export const setWorkspaceContentArchived = (idWorkspace, idContent) => ({ type: `${SET}/${WORKSPACE_CONTENT_ARCHIVED}`, idWorkspace, idContent })
50
 export const setWorkspaceContentArchived = (idWorkspace, idContent) => ({ type: `${SET}/${WORKSPACE_CONTENT_ARCHIVED}`, idWorkspace, idContent })
44
 export const setWorkspaceContentDeleted = (idWorkspace, idContent) => ({ type: `${SET}/${WORKSPACE_CONTENT_DELETED}`, idWorkspace, idContent })
51
 export const setWorkspaceContentDeleted = (idWorkspace, idContent) => ({ type: `${SET}/${WORKSPACE_CONTENT_DELETED}`, idWorkspace, idContent })
45
 
52
 
46
 export const WORKSPACE_LIST = `${WORKSPACE}/List`
53
 export const WORKSPACE_LIST = `${WORKSPACE}/List`
47
-export const updateWorkspaceListData = workspaceList => ({ type: `${UPDATE}/${WORKSPACE_LIST}`, workspaceList })
54
+export const setWorkspaceList = workspaceList => ({ type: `${SET}/${WORKSPACE_LIST}`, workspaceList })
48
 export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
55
 export const setWorkspaceListIsOpenInSidebar = (workspaceId, isOpenInSidebar) => ({ type: `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`, workspaceId, isOpenInSidebar })
49
 
56
 
57
+export const WORKSPACE_LIST_MEMBER = `${WORKSPACE_LIST}/Member/List`
58
+export const setWorkspaceListMemberList = workspaceListMemberList => ({ type: `${SET}/${WORKSPACE_LIST_MEMBER}`, workspaceListMemberList })
59
+
60
+// workspace related const bellow is for currentWorkspace
50
 export const WORKSPACE_DETAIL = `${WORKSPACE}/Detail`
61
 export const WORKSPACE_DETAIL = `${WORKSPACE}/Detail`
51
 export const setWorkspaceDetail = workspaceDetail => ({ type: `${SET}/${WORKSPACE_DETAIL}`, workspaceDetail })
62
 export const setWorkspaceDetail = workspaceDetail => ({ type: `${SET}/${WORKSPACE_DETAIL}`, workspaceDetail })
52
 
63
 

+ 14 - 12
frontend/src/component/Account/MenuSubComponent.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import classnames from 'classnames'
2
 import classnames from 'classnames'
3
 
3
 
4
-export const Navbar = props => {
4
+require('./MenuSubComponent.styl')
5
+
6
+export const MenuSubComponent = props => {
5
   return (
7
   return (
6
-    <nav className='account__userpreference__menu navbar d-flex align-items-start'>
8
+    <nav className='menusubcomponent navbar d-flex align-items-start'>
7
 
9
 
8
-      <div className='account__userpreference__menu__responsive d-lg-none'>
9
-        <button className='hamburger hamburger--spring account__userpreference__menu__responsive__hamburger' type='button'>
10
-          <span className='hamburger-box account__userpreference__menu__responsive__hamburger__box'>
11
-            <span className='hamburger-inner account__userpreference__menu__responsive__hamburger__box__icon' />
10
+      <div className='menusubcomponent__responsive d-lg-none'>
11
+        <button className='hamburger hamburger--spring menusubcomponent__responsive__hamburger' type='button'>
12
+          <span className='hamburger-box menusubcomponent__responsive__hamburger__box'>
13
+            <span className='hamburger-inner menusubcomponent__responsive__hamburger__box__icon' />
12
           </span>
14
           </span>
13
         </button>
15
         </button>
14
       </div>
16
       </div>
15
 
17
 
16
-      <ul className='account__userpreference__menu__list nav flex-column'>
18
+      <ul className='menusubcomponent__list nav flex-column'>
17
 
19
 
18
-        <li className='account__userpreference__menu__list__close nav-link'>
20
+        <li className='menusubcomponent__list__close nav-link'>
19
           <i className='fa fa-times' />
21
           <i className='fa fa-times' />
20
         </li>
22
         </li>
21
 
23
 
22
-        <li className='account__userpreference__menu__list__disabled'>Menu</li>
24
+        <li className='menusubcomponent__list__disabled'>Menu</li>
23
         { props.subMenuList.map(sm =>
25
         { props.subMenuList.map(sm =>
24
           <li
26
           <li
25
-            className={classnames('account__userpreference__menu__list__item nav-item', {'active': sm.active})}
27
+            className={classnames('menusubcomponent__list__item nav-item', {'active': sm.active})}
26
             onClick={() => props.onClickMenuItem(sm.name)}
28
             onClick={() => props.onClickMenuItem(sm.name)}
27
             key={sm.name}
29
             key={sm.name}
28
           >
30
           >
29
-            <div className='account__userpreference__menu__list__item__link nav-link'>{sm.menuLabel}</div>
31
+            <div className='menusubcomponent__list__item__link nav-link'>{sm.menuLabel}</div>
30
           </li>
32
           </li>
31
         )}
33
         )}
32
       </ul>
34
       </ul>
34
   )
36
   )
35
 }
37
 }
36
 
38
 
37
-export default Navbar
39
+export default MenuSubComponent

+ 34 - 0
frontend/src/component/Account/MenuSubComponent.styl View File

1
+@import "../../../node_modules/tracim_frontend_lib/src/css/Variable.styl"
2
+
3
+.menusubcomponent
4
+  margin-right 30px
5
+  border-radius 10px
6
+  padding 0
7
+  width 20%
8
+  min-height 600px
9
+  background-color off-white
10
+  &__responsive
11
+    font-size 20px
12
+  &__list
13
+    width 100%
14
+    margin-bottom 20px
15
+    &__close
16
+      display none
17
+      justify-content flex-end
18
+      margin 10px 0 15px 0
19
+      font-size 20px
20
+      cursor pointer
21
+    &__disabled
22
+      padding 25px 20px 20px 16px
23
+      color grey
24
+      font-size 18px
25
+    &__item
26
+      margin-top 10px
27
+      font-weight 500
28
+      font-size 18px
29
+      cursor pointer
30
+      &:hover
31
+        background-color lightGrey
32
+      &.active
33
+        background-color thirdColor
34
+        color white

+ 34 - 33
frontend/src/component/Account/Notification.jsx View File

3
 import { BtnSwitch } from 'tracim_frontend_lib'
3
 import { BtnSwitch } from 'tracim_frontend_lib'
4
 import { ROLE } from '../../helper.js'
4
 import { ROLE } from '../../helper.js'
5
 
5
 
6
-export const Notification = props => {
7
-  const getRole = role => ROLE.find(r => r.slug === role)
8
-
9
-  return (
10
-    <div className='account__userpreference__setting__notification'>
11
-      <div className='notification__sectiontitle subTitle ml-2 ml-sm-0'>
12
-        {props.t('Workspace and notifications')}
13
-      </div>
6
+export const Notification = props =>
7
+  <div className='account__userpreference__setting__notification'>
8
+    <div className='notification__sectiontitle subTitle ml-2 ml-sm-0'>
9
+      {props.t('Workspace and notifications')}
10
+    </div>
14
 
11
 
15
-      <div className='notification__text ml-2 ml-sm-0'>
16
-        NYI
17
-      </div>
12
+    <div className='notification__text ml-2 ml-sm-0' />
18
 
13
 
19
-      <div className='notification__table'>
20
-        <table className='table'>
21
-          <thead>
22
-            <tr>
23
-              <th>{props.t('Workspace')}</th>
24
-              <th>{props.t('Role')}</th>
25
-              <th>{props.t('Notification')}</th>
26
-            </tr>
27
-          </thead>
28
-          <tbody>
14
+    <div className='notification__table'>
15
+      <table className='table'>
16
+        <thead>
17
+          <tr>
18
+            <th>{props.t('Workspace')}</th>
19
+            <th>{props.t('Role')}</th>
20
+            <th>{props.t('Notification')}</th>
21
+          </tr>
22
+        </thead>
29
 
23
 
30
-            { props.workspaceList.map(ws =>
24
+        <tbody>
25
+          { props.workspaceList.map(ws => {
26
+            const mySelf = ws.memberList.find(u => u.user_id === props.idMyself)
27
+            const myRole = ROLE.find(r => r.slug === mySelf.role)
28
+            return (
31
               <tr key={ws.id}>
29
               <tr key={ws.id}>
32
                 <td>
30
                 <td>
33
                   <div className='notification__table__wksname'>
31
                   <div className='notification__table__wksname'>
34
-                    {ws.title}
32
+                    {ws.label}
35
                   </div>
33
                   </div>
36
                 </td>
34
                 </td>
35
+
37
                 <td>
36
                 <td>
38
                   <div className='notification__table__role'>
37
                   <div className='notification__table__role'>
39
                     <div className='notification__table__role__icon'>
38
                     <div className='notification__table__role__icon'>
40
-                      <i className={`fa fa-fw ${getRole(ws.role).icon}`} />
39
+                      <i className={`fa fa-fw ${myRole.faIcon}`} />
41
                     </div>
40
                     </div>
42
                     <div className='notification__table__role__text d-none d-sm-flex'>
41
                     <div className='notification__table__role__text d-none d-sm-flex'>
43
-                      {props.t(getRole(ws.role).translationKey)}
42
+                      {myRole.label}
44
                     </div>
43
                     </div>
45
                   </div>
44
                   </div>
46
                 </td>
45
                 </td>
46
+
47
                 <td>
47
                 <td>
48
-                  <BtnSwitch checked={ws.notif} onChange={() => props.onChangeSubscriptionNotif(ws.id, !ws.notif)} />
48
+                  <BtnSwitch
49
+                    checked={mySelf.do_notify}
50
+                    onChange={() => props.onChangeSubscriptionNotif(ws.id, !mySelf.do_notify)}
51
+                  />
49
                 </td>
52
                 </td>
50
               </tr>
53
               </tr>
51
-            )}
52
-
53
-          </tbody>
54
-        </table>
55
-      </div>
54
+            )
55
+          })}
56
+        </tbody>
57
+      </table>
56
     </div>
58
     </div>
57
-  )
58
-}
59
+  </div>
59
 
60
 
60
 export default translate()(Notification)
61
 export default translate()(Notification)

+ 74 - 29
frontend/src/component/Account/Password.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { translate } from 'react-i18next'
2
 import { translate } from 'react-i18next'
3
+import { newFlashMessage } from '../../action-creator.sync.js'
3
 
4
 
4
-export const Password = props => {
5
-  return (
6
-    <div className='account__userpreference__setting__personaldata'>
7
-      <div className='personaldata__sectiontitle subTitle ml-2 ml-sm-0'>
8
-        {props.t('Change your password')}
9
-      </div>
5
+export class Password extends React.Component {
6
+  constructor (props) {
7
+    super(props)
8
+    this.state = {
9
+      oldPassword: '',
10
+      newPassword: '',
11
+      newPassword2: ''
12
+    }
13
+  }
10
 
14
 
11
-      <div className='personaldata__text ml-2 ml-sm-0'>
12
-        NYI
13
-      </div>
15
+  handleChangeOldPassword = e => this.setState({oldPassword: e.target.value})
16
+
17
+  handleChangeNewPassword = e => this.setState({newPassword: e.target.value})
18
+
19
+  handleChangeNewPassword2 = e => this.setState({newPassword2: e.target.value})
20
+
21
+  handleClickSubmit = () => {
22
+    const { props, state } = this
23
+
24
+    if (state.newPassword !== state.newPassword2) {
25
+      props.dispatch(newFlashMessage('New passwords are differents'))
26
+      return
27
+    }
28
+
29
+    props.onClickSubmit(state.oldPassword, state.newPassword, state.newPassword2)
30
+  }
31
+
32
+  render () {
33
+    const { props } = this
14
 
34
 
15
-      <form className='personaldata__form mr-5'>
16
-        <div className='personaldata__form__title'>
17
-          {props.t('Password')}
35
+    return (
36
+      <div className='account__userpreference__setting__personaldata'>
37
+        <div className='personaldata__sectiontitle subTitle ml-2 ml-sm-0'>
38
+          {props.t('Change your password')}
18
         </div>
39
         </div>
19
-        <input
20
-          className='personaldata__form__txtinput primaryColorBorderLighten form-control'
21
-          type='password'
22
-          placeholder={props.t('Old password')}
23
-        />
24
-        <input
25
-          className='personaldata__form__txtinput primaryColorBorderLighten form-control mt-4'
26
-          type='password'
27
-          placeholder={props.t('New password')}
28
-        />
29
-        <button type='submit' className='personaldata__form__button primaryColorBorderLighten btn btn-outline-primary mt-4'>
30
-          {props.t('Send')}
31
-        </button>
32
-      </form>
33
-
34
-    </div>
35
-  )
40
+
41
+        <div className='personaldata__text ml-2 ml-sm-0' />
42
+
43
+        <form className='personaldata__form mr-5'>
44
+          <div className='personaldata__form__title'>
45
+            {props.t('Password')}
46
+          </div>
47
+
48
+          <input
49
+            className='personaldata__form__txtinput primaryColorBorderLighten form-control'
50
+            type='password'
51
+            placeholder={props.t('Old password')}
52
+            onChange={this.handleChangeOldPassword}
53
+          />
54
+
55
+          <input
56
+            className='personaldata__form__txtinput primaryColorBorderLighten form-control mt-4'
57
+            type='password'
58
+            placeholder={props.t('New password')}
59
+            onChange={this.handleChangeNewPassword}
60
+          />
61
+
62
+          <input
63
+            className='personaldata__form__txtinput primaryColorBorderLighten form-control mt-4'
64
+            type='password'
65
+            placeholder={props.t('Repeat new password')}
66
+            onChange={this.handleChangeNewPassword2}
67
+          />
68
+
69
+          <button
70
+            type='button'
71
+            className='personaldata__form__button primaryColorBorderLighten btn btn-outline-primary mt-4'
72
+            onClick={this.handleClickSubmit}
73
+          >
74
+            {props.t('Send')}
75
+          </button>
76
+        </form>
77
+
78
+      </div>
79
+    )
80
+  }
36
 }
81
 }
37
 
82
 
38
 export default translate()(Password)
83
 export default translate()(Password)

+ 86 - 38
frontend/src/component/Account/PersonalData.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
+import { connect } from 'react-redux'
2
 import PropTypes from 'prop-types'
3
 import PropTypes from 'prop-types'
3
 import { translate } from 'react-i18next'
4
 import { translate } from 'react-i18next'
5
+import {newFlashMessage} from '../../action-creator.sync.js'
4
 
6
 
5
-export const PersonalData = props => {
6
-  return (
7
-    <div className='account__userpreference__setting__personaldata'>
8
-      <div className='personaldata__sectiontitle subTitle ml-2 ml-sm-0'>
9
-        {props.t('Account information')}
10
-      </div>
7
+require('./PersonalData.styl')
11
 
8
 
12
-      <div className='personaldata__text ml-2 ml-sm-0'>
13
-        NYI
14
-      </div>
9
+export class PersonalData extends React.Component {
10
+  constructor (props) {
11
+    super(props)
12
+    this.state = {
13
+      newName: '',
14
+      newEmail: '',
15
+      checkPassword: ''
16
+    }
17
+  }
15
 
18
 
16
-      <form className='personaldata__form'>
17
-        <div className='personaldata__form__title'>
18
-          {props.t('Name:')}
19
-        </div>
20
-        <div className='d-flex align-items-center justify-content-between flex-wrap mb-4'>
21
-          <input
22
-            className='personaldata__form__txtinput primaryColorBorderLighten form-control mt-3 mt-sm-0'
23
-            type='text'
24
-            placeholder={props.t('Change your name')}
25
-          />
26
-        </div>
27
-        <div className='personaldata__form__title'>
28
-          {props.t('Email Adress:')}
29
-        </div>
30
-        <div className='d-flex align-items-center justify-content-between flex-wrap mb-4'>
31
-          <input
32
-            className='personaldata__form__txtinput primaryColorBorderLighten form-control mt-3 mt-sm-0'
33
-            type='email'
34
-            placeholder={props.t('Change your email')}
35
-          />
19
+  handleChangeName = e => this.setState({newName: e.target.value})
20
+
21
+  handleChangeEmail = e => this.setState({newEmail: e.target.value})
22
+
23
+  handleChangeCheckPassword = e => this.setState({checkPassword: e.target.value})
24
+
25
+  handleClickSubmit = () => {
26
+    const { props, state } = this
27
+
28
+    if (state.newEmail !== '' && state.checkPassword === '') {
29
+      props.dispatch(newFlashMessage(props.t('Please type your password in order to change your email. (For security reasons)'), 'info'))
30
+      return
31
+    }
32
+
33
+    props.onClickSubmit(state.newName, state.newEmail, state.checkPassword)
34
+  }
35
+
36
+  render () {
37
+    const { props } = this
38
+    return (
39
+      <div className='account__userpreference__setting__personaldata'>
40
+        <div className='personaldata__sectiontitle subTitle ml-2 ml-sm-0'>
41
+          {props.t('Account information')}
36
         </div>
42
         </div>
37
-        <button type='submit' className='personaldata__form__button primaryColorBorderLighten btn btn-outline-primary'>
38
-          {props.t('Send')}
39
-        </button>
40
-      </form>
41
-    </div>
42
-  )
43
+
44
+        <div className='personaldata__text ml-2 ml-sm-0' />
45
+
46
+        <form className='personaldata__form'>
47
+          <div className='personaldata__form__title'>
48
+            {props.t('Name:')}
49
+          </div>
50
+
51
+          <div className='d-flex align-items-center flex-wrap mb-4'>
52
+            <input
53
+              className='personaldata__form__txtinput primaryColorBorderLighten form-control mt-3 mt-sm-0'
54
+              type='text'
55
+              placeholder={props.t('Change your name')}
56
+              onChange={this.handleChangeName}
57
+            />
58
+          </div>
59
+
60
+          <div className='personaldata__form__title'>
61
+            {props.t('Email Address:')}
62
+          </div>
63
+
64
+          <div className='d-flex align-items-center flex-wrap mb-4'>
65
+            <input
66
+              className='personaldata__form__txtinput primaryColorBorderLighten form-control mt-3 mt-sm-0'
67
+              type='email'
68
+              placeholder={props.t('Change your email')}
69
+              onChange={this.handleChangeEmail}
70
+            />
71
+
72
+            <input
73
+              className='personaldata__form__txtinput checkPassword primaryColorBorderLighten form-control mt-3 mt-sm-0'
74
+              type='password'
75
+              placeholder={props.t('Check your password')}
76
+              onChange={this.handleChangeCheckPassword}
77
+            />
78
+          </div>
79
+
80
+          <button
81
+            type='button'
82
+            className='personaldata__form__button primaryColorBorderLighten btn'
83
+            onClick={this.handleClickSubmit}
84
+          >
85
+            {props.t('Send')}
86
+          </button>
87
+        </form>
88
+      </div>
89
+    )
90
+  }
43
 }
91
 }
44
 
92
 
45
 PersonalData.propTypes = {
93
 PersonalData.propTypes = {
46
-  inputPlaceholderNameUser: PropTypes.string,
47
-  inputPlaceholderEmailUser: PropTypes.string
94
+  onClickSubmit: PropTypes.func
48
 }
95
 }
49
 
96
 
50
-export default translate()(PersonalData)
97
+const mapStateToProps = () => ({})
98
+export default connect(mapStateToProps)(translate()(PersonalData))

+ 23 - 0
frontend/src/component/Account/PersonalData.styl View File

1
+.personaldata
2
+  &__text
3
+    settingText()
4
+  &__form
5
+    margin 25px 0
6
+    &__title
7
+      margin-bottom 15px
8
+      font-size 18px
9
+    &__txtinput
10
+      display block
11
+      width auto
12
+      border-width 1px
13
+      border-style solid
14
+      border-radius 5px
15
+      &.checkPassword
16
+        margin-left 15px
17
+    &__button
18
+      vertical-align top
19
+      border-width 1px
20
+      border-style solid
21
+      border-radius 5px
22
+      padding 8px 25px
23
+      cursor pointer

+ 19 - 23
frontend/src/component/Account/UserInfo.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 
2
 
3
-export const UserInfo = props => {
4
-  return (
5
-    <div className='account__userinformation mr-5 ml-5 mb-5'>
6
-      <div className='account__userinformation__avatar'>
7
-        <img src={props.user.avatar} alt='avatar' />
3
+require('./UserInfo.styl')
4
+
5
+export const UserInfo = props =>
6
+  <div className='userinfo mr-5 ml-5 mb-5'>
7
+    <div className='userinfo__avatar'>
8
+      <img src={props.user.avatar_url} />
9
+    </div>
10
+
11
+    <div className='userinfo__wrapper'>
12
+      <div className='userinfo__name mb-3'>
13
+        {`${props.user.public_name}`}
8
       </div>
14
       </div>
9
-      <div className='account__userinformation__wrapper'>
10
-        <div className='account__userinformation__name mb-3'>
11
-          {`${props.user.firstname} ${props.user.lastname}`}
12
-        </div>
13
-        <a href={`mailto:${props.user.email}`} className='account__userinformation__email d-block primaryColorFontLighten mb-3'>
14
-          {props.user.email}
15
-        </a>
16
-        <div className='account__userinformation__role mb-3'>
17
-          {props.user.role}
18
-        </div>
19
-        { /* <div className='account__userinformation__job mb-3'>
20
-          {props.user.job}
21
-        </div>
22
-        <a href='http://www.algoo.fr' className='account__userinformation__company primaryColorFontLighten'>
23
-          {props.user.company}
24
-        </a> */ }
15
+
16
+      <a href={`mailto:${props.user.email}`} className='userinfo__email d-block primaryColorFontLighten mb-3'>
17
+        {props.user.email}
18
+      </a>
19
+
20
+      <div className='userinfo__profile mb-3'>
21
+        {props.user.profile}
25
       </div>
22
       </div>
26
     </div>
23
     </div>
27
-  )
28
-}
24
+  </div>
29
 
25
 
30
 export default UserInfo
26
 export default UserInfo

+ 27 - 0
frontend/src/component/Account/UserInfo.styl View File

1
+.userinfo
2
+  display flex
3
+  justify-content center
4
+  align-items center
5
+  flex-wrap wrap
6
+  padding 25px
7
+  font-size 18px
8
+  &__wrapper
9
+    flex-direction column
10
+  &__avatar
11
+    margin-right 50px
12
+    img
13
+      width 100px
14
+      height 100px
15
+  &__name
16
+    font-size 22px
17
+
18
+@media (max-width: max-xs)
19
+  .hamburger-box
20
+    width 45px
21
+    height 22px
22
+
23
+  .userinfo
24
+    &__avatar
25
+      margin 0 0 20px 0
26
+    &__wrapper
27
+      text-align center

+ 7 - 1
frontend/src/component/FlashMessage.jsx View File

22
 
22
 
23
               <div className='flashmessage__container__content__text'>
23
               <div className='flashmessage__container__content__text'>
24
                 <div className='flashmessage__container__content__text__title'>
24
                 <div className='flashmessage__container__content__text__title'>
25
-                  {props.t('Error')}
25
+                  {(() => {
26
+                    switch (props.flashMessage[0].type) {
27
+                      case 'info': return props.t('Information')
28
+                      case 'warning': return props.t('Warning')
29
+                      case 'danger': return props.t('Error')
30
+                    }
31
+                  })()}
26
                 </div>
32
                 </div>
27
                 <div className='flashmessage__container__content__text__paragraph'>
33
                 <div className='flashmessage__container__content__text__paragraph'>
28
                   {props.flashMessage[0].message}
34
                   {props.flashMessage[0].message}

+ 123 - 40
frontend/src/container/Account.jsx View File

13
   PageTitle,
13
   PageTitle,
14
   PageContent
14
   PageContent
15
 } from 'tracim_frontend_lib'
15
 } from 'tracim_frontend_lib'
16
-import { updateUserWorkspaceSubscriptionNotif } from '../action-creator.sync.js'
17
 import {
16
 import {
18
-  getTimezone,
19
-  getUserRole
17
+  newFlashMessage,
18
+  setWorkspaceListMemberList,
19
+  updateUserName,
20
+  updateUserEmail,
21
+  updateUserAuth,
22
+  updateUserWorkspaceSubscriptionNotif
23
+} from '../action-creator.sync.js'
24
+import {
25
+  getWorkspaceMemberList,
26
+  putUserName,
27
+  putUserEmail,
28
+  putUserPassword,
29
+  putUserWorkspaceDoNotify
20
 } from '../action-creator.async.js'
30
 } from '../action-creator.async.js'
21
 import { translate } from 'react-i18next'
31
 import { translate } from 'react-i18next'
32
+import { setCookie } from '../helper.js'
22
 
33
 
23
 class Account extends React.Component {
34
 class Account extends React.Component {
24
   constructor (props) {
35
   constructor (props) {
27
     this.state = {
38
     this.state = {
28
       subComponentMenu: [{
39
       subComponentMenu: [{
29
         name: 'personalData',
40
         name: 'personalData',
30
-        menuLabel: 'Mon profil',
41
+        menuLabel: props.t('My profil'),
31
         active: true
42
         active: true
32
       }, {
43
       }, {
33
         name: 'notification',
44
         name: 'notification',
34
-        menuLabel: 'Espace de travail & Notifications',
45
+        menuLabel: props.t('Workspaces and notifications'),
35
         active: false
46
         active: false
36
       }, {
47
       }, {
37
         name: 'password',
48
         name: 'password',
38
-        menuLabel: 'Mot de passe',
49
+        menuLabel: props.t('Password'),
39
         active: false
50
         active: false
40
       }, {
51
       }, {
41
         name: 'timezone',
52
         name: 'timezone',
42
-        menuLabel: 'Fuseau Horaire',
53
+        menuLabel: props.t('Timezone'),
43
         active: false
54
         active: false
44
       }]
55
       }]
45
       // {
56
       // {
51
   }
62
   }
52
 
63
 
53
   componentDidMount () {
64
   componentDidMount () {
54
-    const { user, workspaceList, timezone, dispatch } = this.props
55
-
56
-    if (user.id !== -1 && workspaceList.length > 0) dispatch(getUserRole(user))
57
-    if (timezone.length === 0) dispatch(getTimezone())
65
+    const { props } = this
66
+    if (props.system.workspaceListLoaded && props.workspaceList.length > 0) this.loadWorkspaceListMemberList()
58
   }
67
   }
59
 
68
 
60
-  componentDidUpdate () {
61
-    const { user, workspaceList, dispatch } = this.props
69
+  loadWorkspaceListMemberList = async () => {
70
+    const { props } = this
71
+
72
+    const fetchWorkspaceListMemberList = await Promise.all(
73
+      props.workspaceList.map(async ws => ({
74
+        idWorkspace: ws.id,
75
+        fetchMemberList: await props.dispatch(getWorkspaceMemberList(props.user, ws.id))
76
+      }))
77
+    )
78
+
79
+    const workspaceListMemberList = fetchWorkspaceListMemberList.map(wsMemberList => ({
80
+      idWorkspace: wsMemberList.idWorkspace,
81
+      memberList: wsMemberList.fetchMemberList.status === 200
82
+        ? wsMemberList.fetchMemberList.json
83
+        : [] // handle error ?
84
+    }))
62
 
85
 
63
-    if (user.id !== -1 && workspaceList.length > 0 && workspaceList.some(ws => ws.role === undefined)) dispatch(getUserRole(user))
86
+    props.dispatch(setWorkspaceListMemberList(workspaceListMemberList))
64
   }
87
   }
65
 
88
 
66
   handleClickSubComponentMenuItem = subMenuItemName => this.setState(prev => ({
89
   handleClickSubComponentMenuItem = subMenuItemName => this.setState(prev => ({
67
     subComponentMenu: prev.subComponentMenu.map(m => ({...m, active: m.name === subMenuItemName}))
90
     subComponentMenu: prev.subComponentMenu.map(m => ({...m, active: m.name === subMenuItemName}))
68
   }))
91
   }))
69
 
92
 
70
-  handleChangeSubscriptionNotif = (workspaceId, subscriptionNotif) =>
71
-    this.props.dispatch(updateUserWorkspaceSubscriptionNotif(workspaceId, subscriptionNotif))
93
+  handleSubmitNameOrEmail = async (newName, newEmail, checkPassword) => {
94
+    const { props } = this
95
+
96
+    if (newName !== '') {
97
+      const fetchPutUserName = await props.dispatch(putUserName(props.user, newName))
98
+      switch (fetchPutUserName.status) {
99
+        case 200:
100
+          props.dispatch(updateUserName(newName))
101
+          if (newEmail === '' )props.dispatch(newFlashMessage(props.t('Your name has been changed'), 'info'))
102
+          // else, if email also has been changed, flash msg is handled bellow to not display 2 flash msg
103
+          break
104
+        default: props.dispatch(newFlashMessage(props.t('Error while changing name'), 'warning')); break
105
+      }
106
+    }
107
+
108
+    if (newEmail !== '') {
109
+      const fetchPutUserEmail = await props.dispatch(putUserEmail(props.user, newEmail, checkPassword))
110
+      switch (fetchPutUserEmail.status) {
111
+        case 200:
112
+          props.dispatch(updateUserEmail(fetchPutUserEmail.json.email))
113
+          const newAuth = setCookie(fetchPutUserEmail.json.email, checkPassword)
114
+          props.dispatch(updateUserAuth(newAuth))
115
+          if (newName !== '') props.dispatch(newFlashMessage(props.t('Your name and email has been changed'), 'info'))
116
+          else props.dispatch(newFlashMessage(props.t('Your email has been changed'), 'info'))
117
+          break
118
+        default: props.dispatch(newFlashMessage(props.t('Error while changing email'), 'warning')); break
119
+      }
120
+    }
121
+  }
122
+
123
+  handleChangeSubscriptionNotif = async (idWorkspace, doNotify) => {
124
+    const { props } = this
125
+
126
+    const fetchPutUserWorkspaceDoNotify = await props.dispatch(putUserWorkspaceDoNotify(props.user, idWorkspace, doNotify))
127
+    switch (fetchPutUserWorkspaceDoNotify.status) {
128
+      // @TODO: Côme - 2018/08/23 - uncomment this when fetch implements the right endpoint (blocked by backend)
129
+      // case 200:
130
+      //   break
131
+      default:
132
+        props.dispatch(updateUserWorkspaceSubscriptionNotif(props.user.user_id, idWorkspace, doNotify))
133
+        // props.dispatch(newFlashMessage(props.t('Error while changing subscription'), 'warning'))
134
+        break
135
+    }
136
+  }
137
+
138
+  handleSubmitPassword = async (oldPassword, newPassword, newPassword2) => {
139
+    const { props } = this
140
+
141
+    const fetchPutUserPassword = await props.dispatch(putUserPassword(props.user, oldPassword, newPassword, newPassword2))
142
+    switch (fetchPutUserPassword.status) {
143
+      case 204:
144
+        const newAuth = setCookie(props.user.email, newPassword)
145
+        props.dispatch(updateUserAuth(newAuth))
146
+        props.dispatch(newFlashMessage(props.t('Your password has been changed'), 'info'))
147
+        break
148
+      default: props.dispatch(newFlashMessage(props.t('Error while changing password'), 'warning')); break
149
+    }
150
+  }
72
 
151
 
73
   handleChangeTimezone = newTimezone => console.log('(NYI) new timezone : ', newTimezone)
152
   handleChangeTimezone = newTimezone => console.log('(NYI) new timezone : ', newTimezone)
74
 
153
 
75
   render () {
154
   render () {
155
+    const { props, state } = this
156
+
76
     const subComponent = (() => {
157
     const subComponent = (() => {
77
-      switch (this.state.subComponentMenu.find(({active}) => active).name) {
158
+      switch (state.subComponentMenu.find(({active}) => active).name) {
78
         case 'personalData':
159
         case 'personalData':
79
-          return <PersonalData />
160
+          return <PersonalData onClickSubmit={this.handleSubmitNameOrEmail} />
80
 
161
 
81
         // case 'calendar':
162
         // case 'calendar':
82
-        //   return <Calendar user={this.props.user} />
163
+        //   return <Calendar user={props.user} />
83
 
164
 
84
         case 'notification':
165
         case 'notification':
85
           return <Notification
166
           return <Notification
86
-            workspaceList={this.props.workspaceList}
167
+            idMyself={props.user.user_id}
168
+            workspaceList={props.workspaceList}
87
             onChangeSubscriptionNotif={this.handleChangeSubscriptionNotif}
169
             onChangeSubscriptionNotif={this.handleChangeSubscriptionNotif}
88
           />
170
           />
89
 
171
 
90
         case 'password':
172
         case 'password':
91
-          return <Password />
173
+          return <Password onClickSubmit={this.handleSubmitPassword} />
92
 
174
 
93
         case 'timezone':
175
         case 'timezone':
94
-          return <Timezone timezone={this.props.timezone} onChangeTimezone={this.handleChangeTimezone} />
176
+          return <Timezone timezone={props.timezone} onChangeTimezone={this.handleChangeTimezone} />
95
       }
177
       }
96
     })()
178
     })()
97
 
179
 
98
     return (
180
     return (
99
-      <div className='account'>
100
-        <PageWrapper customClass='account'>
101
-          <PageTitle
102
-            parentClass={'account'}
103
-            title={'Mon Compte'}
104
-          />
181
+      <PageWrapper customClass='account'>
182
+        <PageTitle
183
+          parentClass={'account'}
184
+          title={props.t('My account')}
185
+        />
105
 
186
 
106
-          <PageContent parentClass='account'>
107
-            <UserInfo user={this.props.user} />
187
+        <PageContent parentClass='account'>
188
+          <UserInfo user={props.user} />
108
 
189
 
109
-            <Delimiter customClass={'account__delimiter'} />
190
+          <Delimiter customClass={'account__delimiter'} />
110
 
191
 
111
-            <div className='account__userpreference'>
112
-              <MenuSubComponent subMenuList={this.state.subComponentMenu} onClickMenuItem={this.handleClickSubComponentMenuItem} />
192
+          <div className='account__userpreference'>
193
+            <MenuSubComponent
194
+              subMenuList={state.subComponentMenu}
195
+              onClickMenuItem={this.handleClickSubComponentMenuItem}
196
+            />
113
 
197
 
114
-              <div className='account__userpreference__setting'>
115
-                { subComponent }
116
-              </div>
198
+            <div className='account__userpreference__setting'>
199
+              { subComponent }
117
             </div>
200
             </div>
201
+          </div>
118
 
202
 
119
-          </PageContent>
120
-        </PageWrapper>
121
-      </div>
203
+        </PageContent>
204
+      </PageWrapper>
122
     )
205
     )
123
   }
206
   }
124
 }
207
 }
125
 
208
 
126
-const mapStateToProps = ({ user, workspaceList, timezone }) => ({ user, workspaceList, timezone })
209
+const mapStateToProps = ({ user, workspaceList, timezone, system }) => ({ user, workspaceList, timezone, system })
127
 export default connect(mapStateToProps)(translate()(Account))
210
 export default connect(mapStateToProps)(translate()(Account))

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

50
     }
50
     }
51
   }
51
   }
52
 
52
 
53
-  async componentDidMount () {
53
+  componentDidMount () {
54
     this.loadWorkspaceDetail()
54
     this.loadWorkspaceDetail()
55
     this.loadMemberList()
55
     this.loadMemberList()
56
     this.loadRecentActivity()
56
     this.loadRecentActivity()

+ 8 - 13
frontend/src/container/Login.jsx View File

2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
 import { withRouter, Redirect } from 'react-router'
3
 import { withRouter, Redirect } from 'react-router'
4
 import { translate } from 'react-i18next'
4
 import { translate } from 'react-i18next'
5
-import Cookies from 'js-cookie'
6
 import LoginLogo from '../component/Login/LoginLogo.jsx'
5
 import LoginLogo from '../component/Login/LoginLogo.jsx'
7
 import LoginLogoImg from '../img/logoTracimWhite.svg'
6
 import LoginLogoImg from '../img/logoTracimWhite.svg'
8
 import Card from '../component/common/Card/Card.jsx'
7
 import Card from '../component/common/Card/Card.jsx'
14
 import {
13
 import {
15
   newFlashMessage,
14
   newFlashMessage,
16
   setUserConnected,
15
   setUserConnected,
17
-  updateWorkspaceListData,
16
+  setWorkspaceList,
18
   setWorkspaceListIsOpenInSidebar,
17
   setWorkspaceListIsOpenInSidebar,
19
   setContentTypeList,
18
   setContentTypeList,
20
   setAppList
19
   setAppList
25
   getWorkspaceList,
24
   getWorkspaceList,
26
   postUserLogin
25
   postUserLogin
27
 } from '../action-creator.async.js'
26
 } from '../action-creator.async.js'
28
-import { COOKIE, PAGE } from '../helper.js'
27
+import { setCookie, PAGE } from '../helper.js'
29
 import { Checkbox } from 'tracim_frontend_lib'
28
 import { Checkbox } from 'tracim_frontend_lib'
30
 
29
 
31
 class Login extends React.Component {
30
 class Login extends React.Component {
59
     const { inputLogin, inputPassword, inputRememberMe } = this.state
58
     const { inputLogin, inputPassword, inputRememberMe } = this.state
60
 
59
 
61
     const fetchPostUserLogin = await dispatch(postUserLogin(inputLogin.value, inputPassword.value, inputRememberMe))
60
     const fetchPostUserLogin = await dispatch(postUserLogin(inputLogin.value, inputPassword.value, inputRememberMe))
62
-    const userAuth = btoa(`${inputLogin.value}:${inputPassword.value}`)
63
 
61
 
64
     if (fetchPostUserLogin.status === 200) {
62
     if (fetchPostUserLogin.status === 200) {
63
+      let userAuth = ''
64
+
65
+      if (inputRememberMe) userAuth = setCookie(inputLogin.value, inputPassword.value, 365)
66
+      else userAuth = setCookie(inputLogin.value, inputPassword.value)
67
+
65
       const loggedUser = {
68
       const loggedUser = {
66
         ...fetchPostUserLogin.json,
69
         ...fetchPostUserLogin.json,
67
         auth: userAuth,
70
         auth: userAuth,
73
       this.loadAppConfig(loggedUser)
76
       this.loadAppConfig(loggedUser)
74
       this.loadWorkspaceList(loggedUser)
77
       this.loadWorkspaceList(loggedUser)
75
 
78
 
76
-      if (inputRememberMe) {
77
-        Cookies.set(COOKIE.USER_LOGIN, inputLogin.value, {expires: 365})
78
-        Cookies.set(COOKIE.USER_AUTH, userAuth, {expires: 365})
79
-      } else {
80
-        Cookies.set(COOKIE.USER_LOGIN, inputLogin.value)
81
-        Cookies.set(COOKIE.USER_AUTH, userAuth)
82
-      }
83
-
84
       history.push(PAGE.WORKSPACE.ROOT)
79
       history.push(PAGE.WORKSPACE.ROOT)
85
     } else if (fetchPostUserLogin.status === 403) {
80
     } else if (fetchPostUserLogin.status === 403) {
86
       dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
81
       dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
105
     const fetchGetWorkspaceList = await props.dispatch(getWorkspaceList(user))
100
     const fetchGetWorkspaceList = await props.dispatch(getWorkspaceList(user))
106
 
101
 
107
     if (fetchGetWorkspaceList.status === 200) {
102
     if (fetchGetWorkspaceList.status === 200) {
108
-      props.dispatch(updateWorkspaceListData(fetchGetWorkspaceList.json))
103
+      props.dispatch(setWorkspaceList(fetchGetWorkspaceList.json))
109
 
104
 
110
       const idWorkspaceToOpen = (() =>
105
       const idWorkspaceToOpen = (() =>
111
         props.match && props.match.params.idws !== undefined && !isNaN(props.match.params.idws)
106
         props.match && props.match.params.idws !== undefined && !isNaN(props.match.params.idws)

+ 13 - 3
frontend/src/container/Tracim.jsx View File

23
   removeFlashMessage,
23
   removeFlashMessage,
24
   setAppList,
24
   setAppList,
25
   setContentTypeList,
25
   setContentTypeList,
26
-  setUserConnected, setWorkspaceListIsOpenInSidebar, updateWorkspaceListData
26
+  setUserConnected,
27
+  setWorkspaceListIsOpenInSidebar,
28
+  setWorkspaceList
27
 } from '../action-creator.sync.js'
29
 } from '../action-creator.sync.js'
28
 import Cookies from 'js-cookie'
30
 import Cookies from 'js-cookie'
29
 import Dashboard from './Dashboard.jsx'
31
 import Dashboard from './Dashboard.jsx'
69
         this.loadWorkspaceList()
71
         this.loadWorkspaceList()
70
         break
72
         break
71
       case 401:
73
       case 401:
74
+        Cookies.remove(COOKIE.USER_LOGIN)
75
+        Cookies.remove(COOKIE.USER_AUTH)
72
         dispatch(setUserConnected({logged: false})); break
76
         dispatch(setUserConnected({logged: false})); break
73
       default:
77
       default:
74
         dispatch(setUserConnected({logged: null})); break
78
         dispatch(setUserConnected({logged: null})); break
93
     if (fetchGetWorkspaceList.status === 200) {
97
     if (fetchGetWorkspaceList.status === 200) {
94
       this.setState({workspaceListLoaded: true})
98
       this.setState({workspaceListLoaded: true})
95
 
99
 
96
-      props.dispatch(updateWorkspaceListData(fetchGetWorkspaceList.json))
100
+      props.dispatch(setWorkspaceList(fetchGetWorkspaceList.json))
97
 
101
 
98
       const idWorkspaceToOpen = (() =>
102
       const idWorkspaceToOpen = (() =>
99
         props.match && props.match.params.idws !== undefined && !isNaN(props.match.params.idws)
103
         props.match && props.match.params.idws !== undefined && !isNaN(props.match.params.idws)
116
       return <Redirect to={{pathname: '/login', state: {from: props.location}}} />
120
       return <Redirect to={{pathname: '/login', state: {from: props.location}}} />
117
     }
121
     }
118
 
122
 
123
+    if (props.location.pathname !== '/login' && (
124
+      !props.system.workspaceListLoaded ||
125
+      !props.system.appListLoaded ||
126
+      !props.system.contentTypeListLoaded
127
+    )) return null // @TODO Côme - 2018/08/22 - should show loader here
128
+
119
     return (
129
     return (
120
       <div className='tracim'>
130
       <div className='tracim'>
121
         <Header />
131
         <Header />
175
   }
185
   }
176
 }
186
 }
177
 
187
 
178
-const mapStateToProps = ({ user, appList, contentType, workspaceList, flashMessage }) => ({ user, appList, contentType, workspaceList, flashMessage })
188
+const mapStateToProps = ({ user, appList, contentType, workspaceList, flashMessage, system }) => ({ user, appList, contentType, workspaceList, flashMessage, system })
179
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))
189
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))

+ 1 - 1
frontend/src/container/WorkspaceContent.jsx View File

63
     }
63
     }
64
   }
64
   }
65
 
65
 
66
-  async componentDidMount () {
66
+  componentDidMount () {
67
     const { workspaceList, match } = this.props
67
     const { workspaceList, match } = this.props
68
 
68
 
69
     console.log('%c<WorkspaceContent> componentDidMount', 'color: #c17838')
69
     console.log('%c<WorkspaceContent> componentDidMount', 'color: #c17838')

+ 1 - 23
frontend/src/css/AccountPage.styl View File

119
       width 80%
119
       width 80%
120
       min-height 600px
120
       min-height 600px
121
       background-color off-white
121
       background-color off-white
122
-      .personaldata
123
-        &__text
124
-          settingText()
125
-        &__form
126
-          margin 25px 0
127
-          &__title
128
-            margin-bottom 15px
129
-            font-size 18px
130
-          &__txtinput
131
-            display block
132
-            width auto
133
-            border-width 1px
134
-            border-style solid
135
-            border-radius 5px
136
-          &__button
137
-            vertical-align top
138
-            border-width 1px
139
-            border-style solid
140
-            border-radius 5px
141
-            padding 8px 25px
142
-            cursor pointer
143
       .calendar
122
       .calendar
144
         &__text
123
         &__text
145
           settingText()
124
           settingText()
171
 
150
 
172
 /**** MEDIA 576px & 1199px ****/
151
 /**** MEDIA 576px & 1199px ****/
173
 
152
 
174
-// Regroup the common rules
175
-
153
+// @TODO Côme - 2018/08/22 - dont bypass breakpoints (from sm to md)
176
 @media (min-width: min-sm) and (max-width: max-md)
154
 @media (min-width: min-sm) and (max-width: max-md)
177
 
155
 
178
   .account
156
   .account

+ 25 - 9
frontend/src/helper.js View File

1
+import i18n from './i18n.js'
2
+import Cookies from 'js-cookie'
3
+
1
 const configEnv = require('../configEnv.json')
4
 const configEnv = require('../configEnv.json')
2
 
5
 
3
 export const FETCH_CONFIG = {
6
 export const FETCH_CONFIG = {
14
   USER_AUTH: 'user_auth'
17
   USER_AUTH: 'user_auth'
15
 }
18
 }
16
 
19
 
20
+export const setCookie = (login, password, expires = undefined) => {
21
+  const auth = btoa(`${login}:${password}`)
22
+  if (expires) {
23
+    Cookies.set(COOKIE.USER_LOGIN, login, {expires})
24
+    Cookies.set(COOKIE.USER_AUTH, auth, {expires})
25
+  } else {
26
+    Cookies.set(COOKIE.USER_LOGIN, login)
27
+    Cookies.set(COOKIE.USER_AUTH, auth)
28
+  }
29
+
30
+  return auth
31
+}
32
+
17
 // Côme - 2018/08/02 - shouldn't this come from api ?
33
 // Côme - 2018/08/02 - shouldn't this come from api ?
18
 export const workspaceConfig = {
34
 export const workspaceConfig = {
19
   slug: 'workspace',
35
   slug: 'workspace',
20
   faIcon: 'bank',
36
   faIcon: 'bank',
21
   hexcolor: '#7d4e24',
37
   hexcolor: '#7d4e24',
22
-  creationLabel: 'Create a workspace',
38
+  creationLabel: i18n.t('Create a workspace'),
23
   domContainer: 'appFeatureContainer'
39
   domContainer: 'appFeatureContainer'
24
 }
40
 }
25
 
41
 
48
   slug: 'reader',
64
   slug: 'reader',
49
   faIcon: 'eye',
65
   faIcon: 'eye',
50
   hexcolor: '#15d948',
66
   hexcolor: '#15d948',
51
-  label: 'Reader'
67
+  label: i18n.t('Reader')
52
 }, {
68
 }, {
53
   id: 2,
69
   id: 2,
54
   slug: 'contributor',
70
   slug: 'contributor',
55
   faIcon: 'pencil',
71
   faIcon: 'pencil',
56
   hexcolor: '#3145f7',
72
   hexcolor: '#3145f7',
57
-  label: 'Contributor'
73
+  label: i18n.t('Contributor')
58
 }, {
74
 }, {
59
   id: 4,
75
   id: 4,
60
   slug: 'content-manager',
76
   slug: 'content-manager',
61
   faIcon: 'graduation-cap',
77
   faIcon: 'graduation-cap',
62
   hexcolor: '#f2af2d',
78
   hexcolor: '#f2af2d',
63
-  label: 'Content manager'
79
+  label: i18n.t('Content manager')
64
 }, {
80
 }, {
65
   id: 8,
81
   id: 8,
66
   slug: 'workspace-manager',
82
   slug: 'workspace-manager',
67
   faIcon: 'gavel',
83
   faIcon: 'gavel',
68
   hexcolor: '#ed0007',
84
   hexcolor: '#ed0007',
69
-  label: 'Workspace manager'
85
+  label: i18n.t('Workspace manager')
70
 }]
86
 }]
71
 
87
 
72
 export const findIdRoleUserWorkspace = (idUser, memberList, roleList) => {
88
 export const findIdRoleUserWorkspace = (idUser, memberList, roleList) => {
81
     sluf: 'reader',
97
     sluf: 'reader',
82
     faIcon: 'eye',
98
     faIcon: 'eye',
83
     hexcolor: '#15D948',
99
     hexcolor: '#15D948',
84
-    label: 'Reader'
100
+    label: i18n.t('Reader')
85
   },
101
   },
86
   contributor: {
102
   contributor: {
87
     id: 2,
103
     id: 2,
88
     slug: 'contributor',
104
     slug: 'contributor',
89
     faIcon: 'pencil',
105
     faIcon: 'pencil',
90
     hexcolor: '#3145f7',
106
     hexcolor: '#3145f7',
91
-    label: 'Contributor'
107
+    label: i18n.t('Contributor')
92
   },
108
   },
93
   contentManager: {
109
   contentManager: {
94
     id: 4,
110
     id: 4,
95
     slug: 'content-manager',
111
     slug: 'content-manager',
96
     faIcon: 'graduation-cap',
112
     faIcon: 'graduation-cap',
97
     hexcolor: '#f2af2d',
113
     hexcolor: '#f2af2d',
98
-    label: 'Content manager'
114
+    label: i18n.t('Content manager')
99
   },
115
   },
100
   workspaceManager: {
116
   workspaceManager: {
101
     id: 8,
117
     id: 8,
102
     slug: 'workspace-manager',
118
     slug: 'workspace-manager',
103
     faIcon: 'gavel',
119
     faIcon: 'gavel',
104
     hexcolor: '#ed0007',
120
     hexcolor: '#ed0007',
105
-    label: 'Workspace manager'
121
+    label: i18n.t('Workspace manager')
106
   }
122
   }
107
 }
123
 }
108
 
124
 

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

8
 import appList from './appList.js'
8
 import appList from './appList.js'
9
 import contentType from './contentType.js'
9
 import contentType from './contentType.js'
10
 import timezone from './timezone.js'
10
 import timezone from './timezone.js'
11
+import system from './system.js'
11
 
12
 
12
-const rootReducer = combineReducers({ lang, flashMessage, user, currentWorkspace, workspaceContentList, workspaceList, appList, contentType, timezone })
13
+const rootReducer = combineReducers({ lang, flashMessage, user, currentWorkspace, workspaceContentList, workspaceList, appList, contentType, timezone, system })
13
 
14
 
14
 export default rootReducer
15
 export default rootReducer

+ 30 - 0
frontend/src/reducer/system.js View File

1
+import {
2
+  APP_LIST,
3
+  CONTENT_TYPE_LIST,
4
+  SET,
5
+  WORKSPACE_LIST
6
+} from '../action-creator.sync.js'
7
+
8
+const defaultSystem = {
9
+  workspaceListLoaded: false,
10
+  appListLoaded: false,
11
+  contentTypeListLoaded: false
12
+}
13
+
14
+export function system (state = defaultSystem, action) {
15
+  switch (action.type) {
16
+    case `${SET}/${WORKSPACE_LIST}`:
17
+      return {...state, workspaceListLoaded: true}
18
+
19
+    case `${SET}/${APP_LIST}`:
20
+      return {...state, appListLoaded: true}
21
+
22
+    case `${SET}/${CONTENT_TYPE_LIST}`:
23
+      return {...state, contentTypeListLoaded: true}
24
+
25
+    default:
26
+      return state
27
+  }
28
+}
29
+
30
+export default system

+ 9 - 5
frontend/src/reducer/user.js View File

3
   UPDATE,
3
   UPDATE,
4
   USER_CONNECTED,
4
   USER_CONNECTED,
5
   USER_DISCONNECTED,
5
   USER_DISCONNECTED,
6
-  USER_DATA,
7
-  USER_LANG
6
+  USER_LANG,
7
+  USER_NAME,
8
+  USER_AUTH
8
 } from '../action-creator.sync.js'
9
 } from '../action-creator.sync.js'
9
 import { generateAvatarFromPublicName } from 'tracim_frontend_lib'
10
 import { generateAvatarFromPublicName } from 'tracim_frontend_lib'
10
 
11
 
40
     case `${SET}/${USER_DISCONNECTED}`:
41
     case `${SET}/${USER_DISCONNECTED}`:
41
       return {...defaultUser, logged: false}
42
       return {...defaultUser, logged: false}
42
 
43
 
43
-    case `${UPDATE}/${USER_DATA}`:
44
-      return {...state, ...action.data}
45
-
46
     case `${SET}/${USER_LANG}`:
44
     case `${SET}/${USER_LANG}`:
47
       return {...state, lang: action.lang}
45
       return {...state, lang: action.lang}
48
 
46
 
47
+    case `${UPDATE}/${USER_NAME}`:
48
+      return {...state, public_name: action.newName}
49
+
50
+    case `${UPDATE}/${USER_AUTH}`:
51
+      return {...state, auth: action.auth}
52
+
49
     default:
53
     default:
50
       return state
54
       return state
51
   }
55
   }

+ 18 - 14
frontend/src/reducer/workspaceList.js View File

1
 import {
1
 import {
2
   SET,
2
   SET,
3
   UPDATE,
3
   UPDATE,
4
+  USER_WORKSPACE_DO_NOTIFY,
4
   WORKSPACE_LIST,
5
   WORKSPACE_LIST,
5
-  USER_ROLE
6
+  WORKSPACE_LIST_MEMBER
6
 } from '../action-creator.sync.js'
7
 } from '../action-creator.sync.js'
7
 import { handleRouteFromApi } from '../helper.js'
8
 import { handleRouteFromApi } from '../helper.js'
8
 
9
 
9
 export function workspaceList (state = [], action) {
10
 export function workspaceList (state = [], action) {
10
   switch (action.type) {
11
   switch (action.type) {
11
-    case `${UPDATE}/${WORKSPACE_LIST}`:
12
+    case `${SET}/${WORKSPACE_LIST}`:
12
       return action.workspaceList.map(ws => ({
13
       return action.workspaceList.map(ws => ({
13
         id: ws.workspace_id,
14
         id: ws.workspace_id,
14
         label: ws.label,
15
         label: ws.label,
21
           hexcolor: sbe.hexcolor,
22
           hexcolor: sbe.hexcolor,
22
           label: sbe.label
23
           label: sbe.label
23
         })),
24
         })),
24
-        isOpenInSidebar: false
25
+        isOpenInSidebar: false,
26
+        memberList: []
25
       }))
27
       }))
26
 
28
 
27
     case `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`:
29
     case `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`:
30
         : ws
32
         : ws
31
       )
33
       )
32
 
34
 
33
-    case `${SET}/${USER_ROLE}`: // not used yet
34
-      return state.map(ws => {
35
-        const foundWorkspace = action.userRole.find(r => ws.id === r.workspace.id) || {role: '', subscribed_to_notif: ''}
36
-        return {
35
+    case `${SET}/${WORKSPACE_LIST_MEMBER}`:
36
+      return state.map(ws => ({
37
+        ...ws,
38
+        memberList: action.workspaceListMemberList.find(wlml => wlml.idWorkspace === ws.id).memberList
39
+      }))
40
+
41
+    case `${UPDATE}/${USER_WORKSPACE_DO_NOTIFY}`:
42
+      return state.map(ws => ws.id === action.idWorkspace
43
+        ? {
37
           ...ws,
44
           ...ws,
38
-          role: foundWorkspace.role,
39
-          notif: foundWorkspace.subscribed_to_notif
45
+          memberList: ws.memberList.map(u => u.user_id === action.idUser
46
+            ? {...u, do_notify: action.doNotify}
47
+            : u
48
+          )
40
         }
49
         }
41
-      })
42
-
43
-    case `${UPDATE}/${USER_ROLE}/SubscriptionNotif`: // not used yet
44
-      return state.map(ws => ws.id === action.workspaceId
45
-        ? {...ws, notif: action.subscriptionNotif}
46
         : ws
50
         : ws
47
       )
51
       )
48
 
52