Browse Source

pluged in account page

Skylsmoi 5 years ago
parent
commit
59ad8431ee

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

@@ -1,13 +1,12 @@
1 1
 import { FETCH_CONFIG } from './helper.js'
2 2
 import {
3
-  TIMEZONE,
4
-  setTimezone,
5 3
   USER_LOGIN,
6 4
   USER_LOGOUT,
7
-  USER_ROLE,
8 5
   USER_CONNECTED,
9 6
   USER_KNOWN_MEMBER_LIST,
10
-  setUserRole,
7
+  USER_NAME,
8
+  USER_EMAIL,
9
+  USER_PASSWORD,
11 10
   WORKSPACE,
12 11
   WORKSPACE_LIST,
13 12
   WORKSPACE_DETAIL,
@@ -20,7 +19,8 @@ import {
20 19
   WORKSPACE_CONTENT_ARCHIVED,
21 20
   WORKSPACE_CONTENT_DELETED,
22 21
   WORKSPACE_RECENT_ACTIVITY,
23
-  WORKSPACE_READ_STATUS
22
+  WORKSPACE_READ_STATUS,
23
+  USER_WORKSPACE_DO_NOTIFY
24 24
 } from './action-creator.sync.js'
25 25
 
26 26
 /*
@@ -82,19 +82,6 @@ const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) =
82 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 85
 export const postUserLogin = (login, password, rememberMe) => async dispatch => {
99 86
   return fetchWrapper({
100 87
     url: `${FETCH_CONFIG.apiUrl}/sessions/login`, // FETCH_CONFIG.apiUrl
@@ -139,30 +126,75 @@ export const getUserIsConnected = user => async dispatch => {
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 132
     param: {
146
-      headers: {...FETCH_CONFIG.headers},
133
+      headers: {
134
+        ...FETCH_CONFIG.headers,
135
+        'Authorization': 'Basic ' + user.auth
136
+      },
147 137
       method: 'GET'
148 138
     },
149
-    actionName: USER_ROLE,
139
+    actionName: USER_KNOWN_MEMBER_LIST,
150 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 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 147
     param: {
159 148
       headers: {
160 149
         ...FETCH_CONFIG.headers,
161 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 198
     dispatch
167 199
   })
168 200
 }
@@ -182,6 +214,26 @@ export const putUserWorkspaceRead = (user, idWorkspace) => dispatch => {
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 237
 export const getWorkspaceList = user => dispatch => {
186 238
   return fetchWrapper({
187 239
     url: `${FETCH_CONFIG.apiUrl}/users/${user.user_id}/workspaces`,

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

@@ -1,5 +1,5 @@
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 3
 export const ADD = 'Add'
4 4
 export const REMOVE = 'Remove'
5 5
 export const APPEND = 'Append'
@@ -22,31 +22,42 @@ export const USER_CONNECTED = `${USER}/Connected`
22 22
 export const USER_DISCONNECTED = `${USER}/Disconnected`
23 23
 export const setUserConnected = user => ({ type: `${SET}/${USER}/Connected`, user })
24 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 26
 export const USER_LANG = `${USER}/Lang`
32 27
 export const setUserLang = lang => ({ type: `${SET}/${USER}/Lang`, lang })
33 28
 export const USER_KNOWN_MEMBER = `${USER}/KnownMember`
34 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 39
 export const WORKSPACE = 'Workspace'
37 40
 export const WORKSPACE_CONTENT = `${WORKSPACE}/Content`
38 41
 export const setWorkspaceContentList = workspaceContentList => ({ type: `${SET}/${WORKSPACE_CONTENT}`, workspaceContentList })
39 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 48
 export const WORKSPACE_CONTENT_ARCHIVED = `${WORKSPACE_CONTENT}/Archived`
42 49
 export const WORKSPACE_CONTENT_DELETED = `${WORKSPACE_CONTENT}/Deleted`
43 50
 export const setWorkspaceContentArchived = (idWorkspace, idContent) => ({ type: `${SET}/${WORKSPACE_CONTENT_ARCHIVED}`, idWorkspace, idContent })
44 51
 export const setWorkspaceContentDeleted = (idWorkspace, idContent) => ({ type: `${SET}/${WORKSPACE_CONTENT_DELETED}`, idWorkspace, idContent })
45 52
 
46 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 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 61
 export const WORKSPACE_DETAIL = `${WORKSPACE}/Detail`
51 62
 export const setWorkspaceDetail = workspaceDetail => ({ type: `${SET}/${WORKSPACE_DETAIL}`, workspaceDetail })
52 63
 

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

@@ -1,32 +1,34 @@
1 1
 import React from 'react'
2 2
 import classnames from 'classnames'
3 3
 
4
-export const Navbar = props => {
4
+require('./MenuSubComponent.styl')
5
+
6
+export const MenuSubComponent = props => {
5 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 14
           </span>
13 15
         </button>
14 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 21
           <i className='fa fa-times' />
20 22
         </li>
21 23
 
22
-        <li className='account__userpreference__menu__list__disabled'>Menu</li>
24
+        <li className='menusubcomponent__list__disabled'>Menu</li>
23 25
         { props.subMenuList.map(sm =>
24 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 28
             onClick={() => props.onClickMenuItem(sm.name)}
27 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 32
           </li>
31 33
         )}
32 34
       </ul>
@@ -34,4 +36,4 @@ export const Navbar = props => {
34 36
   )
35 37
 }
36 38
 
37
-export default Navbar
39
+export default MenuSubComponent

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

@@ -0,0 +1,34 @@
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,58 +3,59 @@ import { translate } from 'react-i18next'
3 3
 import { BtnSwitch } from 'tracim_frontend_lib'
4 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 29
               <tr key={ws.id}>
32 30
                 <td>
33 31
                   <div className='notification__table__wksname'>
34
-                    {ws.title}
32
+                    {ws.label}
35 33
                   </div>
36 34
                 </td>
35
+
37 36
                 <td>
38 37
                   <div className='notification__table__role'>
39 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 40
                     </div>
42 41
                     <div className='notification__table__role__text d-none d-sm-flex'>
43
-                      {props.t(getRole(ws.role).translationKey)}
42
+                      {myRole.label}
44 43
                     </div>
45 44
                   </div>
46 45
                 </td>
46
+
47 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 52
                 </td>
50 53
               </tr>
51
-            )}
52
-
53
-          </tbody>
54
-        </table>
55
-      </div>
54
+            )
55
+          })}
56
+        </tbody>
57
+      </table>
56 58
     </div>
57
-  )
58
-}
59
+  </div>
59 60
 
60 61
 export default translate()(Notification)

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

@@ -1,38 +1,83 @@
1 1
 import React from 'react'
2 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 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 83
 export default translate()(Password)

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

@@ -1,50 +1,98 @@
1 1
 import React from 'react'
2
+import { connect } from 'react-redux'
2 3
 import PropTypes from 'prop-types'
3 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 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 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

@@ -0,0 +1,23 @@
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,30 +1,26 @@
1 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 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 22
       </div>
26 23
     </div>
27
-  )
28
-}
24
+  </div>
29 25
 
30 26
 export default UserInfo

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

@@ -0,0 +1,27 @@
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,7 +22,13 @@ const FlashMessage = props => {
22 22
 
23 23
               <div className='flashmessage__container__content__text'>
24 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 32
                 </div>
27 33
                 <div className='flashmessage__container__content__text__paragraph'>
28 34
                   {props.flashMessage[0].message}

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

@@ -13,12 +13,23 @@ import {
13 13
   PageTitle,
14 14
   PageContent
15 15
 } from 'tracim_frontend_lib'
16
-import { updateUserWorkspaceSubscriptionNotif } from '../action-creator.sync.js'
17 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 30
 } from '../action-creator.async.js'
21 31
 import { translate } from 'react-i18next'
32
+import { setCookie } from '../helper.js'
22 33
 
23 34
 class Account extends React.Component {
24 35
   constructor (props) {
@@ -27,19 +38,19 @@ class Account extends React.Component {
27 38
     this.state = {
28 39
       subComponentMenu: [{
29 40
         name: 'personalData',
30
-        menuLabel: 'Mon profil',
41
+        menuLabel: props.t('My profil'),
31 42
         active: true
32 43
       }, {
33 44
         name: 'notification',
34
-        menuLabel: 'Espace de travail & Notifications',
45
+        menuLabel: props.t('Workspaces and notifications'),
35 46
         active: false
36 47
       }, {
37 48
         name: 'password',
38
-        menuLabel: 'Mot de passe',
49
+        menuLabel: props.t('Password'),
39 50
         active: false
40 51
       }, {
41 52
         name: 'timezone',
42
-        menuLabel: 'Fuseau Horaire',
53
+        menuLabel: props.t('Timezone'),
43 54
         active: false
44 55
       }]
45 56
       // {
@@ -51,77 +62,149 @@ class Account extends React.Component {
51 62
   }
52 63
 
53 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 89
   handleClickSubComponentMenuItem = subMenuItemName => this.setState(prev => ({
67 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 152
   handleChangeTimezone = newTimezone => console.log('(NYI) new timezone : ', newTimezone)
74 153
 
75 154
   render () {
155
+    const { props, state } = this
156
+
76 157
     const subComponent = (() => {
77
-      switch (this.state.subComponentMenu.find(({active}) => active).name) {
158
+      switch (state.subComponentMenu.find(({active}) => active).name) {
78 159
         case 'personalData':
79
-          return <PersonalData />
160
+          return <PersonalData onClickSubmit={this.handleSubmitNameOrEmail} />
80 161
 
81 162
         // case 'calendar':
82
-        //   return <Calendar user={this.props.user} />
163
+        //   return <Calendar user={props.user} />
83 164
 
84 165
         case 'notification':
85 166
           return <Notification
86
-            workspaceList={this.props.workspaceList}
167
+            idMyself={props.user.user_id}
168
+            workspaceList={props.workspaceList}
87 169
             onChangeSubscriptionNotif={this.handleChangeSubscriptionNotif}
88 170
           />
89 171
 
90 172
         case 'password':
91
-          return <Password />
173
+          return <Password onClickSubmit={this.handleSubmitPassword} />
92 174
 
93 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 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 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 210
 export default connect(mapStateToProps)(translate()(Account))

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

@@ -50,7 +50,7 @@ class Dashboard extends React.Component {
50 50
     }
51 51
   }
52 52
 
53
-  async componentDidMount () {
53
+  componentDidMount () {
54 54
     this.loadWorkspaceDetail()
55 55
     this.loadMemberList()
56 56
     this.loadRecentActivity()

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

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

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

@@ -23,7 +23,9 @@ import {
23 23
   removeFlashMessage,
24 24
   setAppList,
25 25
   setContentTypeList,
26
-  setUserConnected, setWorkspaceListIsOpenInSidebar, updateWorkspaceListData
26
+  setUserConnected,
27
+  setWorkspaceListIsOpenInSidebar,
28
+  setWorkspaceList
27 29
 } from '../action-creator.sync.js'
28 30
 import Cookies from 'js-cookie'
29 31
 import Dashboard from './Dashboard.jsx'
@@ -69,6 +71,8 @@ class Tracim extends React.Component {
69 71
         this.loadWorkspaceList()
70 72
         break
71 73
       case 401:
74
+        Cookies.remove(COOKIE.USER_LOGIN)
75
+        Cookies.remove(COOKIE.USER_AUTH)
72 76
         dispatch(setUserConnected({logged: false})); break
73 77
       default:
74 78
         dispatch(setUserConnected({logged: null})); break
@@ -93,7 +97,7 @@ class Tracim extends React.Component {
93 97
     if (fetchGetWorkspaceList.status === 200) {
94 98
       this.setState({workspaceListLoaded: true})
95 99
 
96
-      props.dispatch(updateWorkspaceListData(fetchGetWorkspaceList.json))
100
+      props.dispatch(setWorkspaceList(fetchGetWorkspaceList.json))
97 101
 
98 102
       const idWorkspaceToOpen = (() =>
99 103
         props.match && props.match.params.idws !== undefined && !isNaN(props.match.params.idws)
@@ -116,6 +120,12 @@ class Tracim extends React.Component {
116 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 129
     return (
120 130
       <div className='tracim'>
121 131
         <Header />
@@ -175,5 +185,5 @@ class Tracim extends React.Component {
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 189
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))

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

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

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

@@ -119,27 +119,6 @@ settingText()
119 119
       width 80%
120 120
       min-height 600px
121 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 122
       .calendar
144 123
         &__text
145 124
           settingText()
@@ -171,8 +150,7 @@ settingText()
171 150
 
172 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 154
 @media (min-width: min-sm) and (max-width: max-md)
177 155
 
178 156
   .account

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

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

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

@@ -0,0 +1,30 @@
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,8 +3,9 @@ import {
3 3
   UPDATE,
4 4
   USER_CONNECTED,
5 5
   USER_DISCONNECTED,
6
-  USER_DATA,
7
-  USER_LANG
6
+  USER_LANG,
7
+  USER_NAME,
8
+  USER_AUTH
8 9
 } from '../action-creator.sync.js'
9 10
 import { generateAvatarFromPublicName } from 'tracim_frontend_lib'
10 11
 
@@ -40,12 +41,15 @@ export default function user (state = defaultUser, action) {
40 41
     case `${SET}/${USER_DISCONNECTED}`:
41 42
       return {...defaultUser, logged: false}
42 43
 
43
-    case `${UPDATE}/${USER_DATA}`:
44
-      return {...state, ...action.data}
45
-
46 44
     case `${SET}/${USER_LANG}`:
47 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 53
     default:
50 54
       return state
51 55
   }

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

@@ -1,14 +1,15 @@
1 1
 import {
2 2
   SET,
3 3
   UPDATE,
4
+  USER_WORKSPACE_DO_NOTIFY,
4 5
   WORKSPACE_LIST,
5
-  USER_ROLE
6
+  WORKSPACE_LIST_MEMBER
6 7
 } from '../action-creator.sync.js'
7 8
 import { handleRouteFromApi } from '../helper.js'
8 9
 
9 10
 export function workspaceList (state = [], action) {
10 11
   switch (action.type) {
11
-    case `${UPDATE}/${WORKSPACE_LIST}`:
12
+    case `${SET}/${WORKSPACE_LIST}`:
12 13
       return action.workspaceList.map(ws => ({
13 14
         id: ws.workspace_id,
14 15
         label: ws.label,
@@ -21,7 +22,8 @@ export function workspaceList (state = [], action) {
21 22
           hexcolor: sbe.hexcolor,
22 23
           label: sbe.label
23 24
         })),
24
-        isOpenInSidebar: false
25
+        isOpenInSidebar: false,
26
+        memberList: []
25 27
       }))
26 28
 
27 29
     case `${SET}/${WORKSPACE_LIST}/isOpenInSidebar`:
@@ -30,19 +32,21 @@ export function workspaceList (state = [], action) {
30 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 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 50
         : ws
47 51
       )
48 52