Browse Source

pluged in admin pages

Skylsmoi 5 years ago
parent
commit
39f5a7263d

+ 1 - 0
frontend/src/action-creator.async.js View File

@@ -73,6 +73,7 @@ const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) =
73 73
       dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
74 74
       break
75 75
     case 400:
76
+    case 401:
76 77
     case 404:
77 78
     case 500:
78 79
       dispatch({type: `${param.method}/${actionName}/FAILED`, data: fetchResult.json})

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

@@ -98,7 +98,7 @@ class Account extends React.Component {
98 98
       switch (fetchPutUserName.status) {
99 99
         case 200:
100 100
           props.dispatch(updateUserName(newName))
101
-          if (newEmail === '' )props.dispatch(newFlashMessage(props.t('Your name has been changed'), 'info'))
101
+          if (newEmail === '') props.dispatch(newFlashMessage(props.t('Your name has been changed'), 'info'))
102 102
           // else, if email also has been changed, flash msg is handled bellow to not display 2 flash msg
103 103
           break
104 104
         default: props.dispatch(newFlashMessage(props.t('Error while changing name'), 'warning')); break

+ 15 - 4
frontend/src/container/AppFullscreenRouter.jsx View File

@@ -25,16 +25,27 @@ class AppFullscreenRouter extends React.Component {
25 25
         {this.state.isMounted && (// we must wait for the component to be fully mounted to be sure the div#appFullscreenContainer exists in DOM
26 26
           <div className='emptyDivForRoute'>
27 27
             <Route path={PAGE.ADMIN.WORKSPACE} render={() => {
28
-              if (props.user.profile !== PROFILE.ADMINISTRATOR) return <Redirect to={{pathname: '/'}} />
28
+              if (props.user.profile !== PROFILE.ADMINISTRATOR.slug) return <Redirect to={{pathname: '/'}} />
29 29
 
30
-              props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, {})
30
+              const content = {
31
+                workspaceList: [],
32
+                userList: []
33
+              }
34
+
35
+              props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, content)
31 36
               return null
32 37
             }} />
33 38
 
34 39
             <Route path={PAGE.ADMIN.USER} render={() => {
35
-              if (props.user.profile !== PROFILE.ADMINISTRATOR) return <Redirect to={{pathname: '/'}} />
40
+              if (props.user.profile !== PROFILE.ADMINISTRATOR.slug) return <Redirect to={{pathname: '/'}} />
41
+
42
+              const content = {
43
+                profile: PROFILE,
44
+                workspaceList: [],
45
+                userList: []
46
+              }
36 47
 
37
-              props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'user'}, props.user, {})
48
+              props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'user'}, props.user, content)
38 49
               return null
39 50
             }} />
40 51
           </div>

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

@@ -87,7 +87,7 @@ class Header extends React.Component {
87 87
                 onClickSubmit={this.handleClickSubmit}
88 88
               />
89 89
 
90
-              {user.profile === PROFILE.ADMINISTRATOR &&
90
+              {user.profile === PROFILE.ADMINISTRATOR.slug &&
91 91
                 <MenuActionListAdminLink t={this.props.t} />
92 92
               }
93 93
 

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

@@ -26,10 +26,8 @@ class Sidebar extends React.Component {
26 26
 
27 27
   customEventReducer = async ({ detail: { type, data } }) => {
28 28
     switch (type) {
29
-      case 'refreshWorkspaceList':
30
-        console.log('%c<Sidebar> Custom event', 'color: #28a745', type, data)
31
-        this.loadAppConfigAndWorkspaceList()
32
-        break
29
+      default:
30
+        return
33 31
     }
34 32
   }
35 33
 

+ 4 - 0
frontend/src/container/Tracim.jsx View File

@@ -47,6 +47,10 @@ class Tracim extends React.Component {
47 47
         console.log('%c<Tracim> Custom event', 'color: #28a745', type, data)
48 48
         this.props.dispatch(newFlashMessage(data.msg, data.type, data.delay))
49 49
         break
50
+      case 'refreshWorkspaceList':
51
+        console.log('%c<Tracim> Custom event', 'color: #28a745', type, data)
52
+        this.loadWorkspaceList()
53
+        break
50 54
     }
51 55
   }
52 56
 

+ 21 - 3
frontend/src/helper.js View File

@@ -123,9 +123,27 @@ export const ROLE2 = {
123 123
 }
124 124
 
125 125
 export const PROFILE = {
126
-  ADMINISTRATOR: 'administrators',
127
-  MANAGER: 'managers',
128
-  USER: 'users'
126
+  ADMINISTRATOR: {
127
+    id: 1,
128
+    slug: 'administrators',
129
+    faIcon: 'rocket',
130
+    hexcolor: '#123456',
131
+    label: i18n.t('Administrator')
132
+  },
133
+  MANAGER: {
134
+    id: 2,
135
+    slug: 'managers',
136
+    faIcon: 'car',
137
+    hexcolor: '#654321',
138
+    label: i18n.t('Manager')
139
+  },
140
+  USER: {
141
+    id: 4,
142
+    slug: 'users',
143
+    faIcon: 'bicycle',
144
+    hexcolor: '#123123',
145
+    label: i18n.t('User')
146
+  }
129 147
 }
130 148
 
131 149
 export const handleRouteFromApi = route => route.startsWith('/#') ? route.slice(2) : route

+ 91 - 1
frontend_app_admin_workspace_user/src/action.async.js View File

@@ -1 +1,91 @@
1
-// import { FETCH_CONFIG } from './helper.js'
1
+import { FETCH_CONFIG } from './helper.js'
2
+
3
+export const getWorkspaceList = (user, apiUrl) =>
4
+  // @FIXME - Côme - 2018/08/23 - wrong end point, this one only returns workspaces of logged user
5
+  fetch(`${apiUrl}/users/${user.user_id}/workspaces`, {
6
+    headers: {
7
+      'Authorization': 'Basic ' + user.auth,
8
+      ...FETCH_CONFIG.headers
9
+    },
10
+    method: 'GET'
11
+  })
12
+
13
+export const getWorkspaceMemberList = (user, apiUrl, idWorkspace) =>
14
+  fetch(`${apiUrl}/workspaces/${idWorkspace}/members`, {
15
+    headers: {
16
+      'Authorization': 'Basic ' + user.auth,
17
+      ...FETCH_CONFIG.headers
18
+    },
19
+    method: 'GET'
20
+  })
21
+
22
+export const deleteWorkspace = (user, apiUrl, idWorkspace) =>
23
+  fetch(`${apiUrl}/workspaces/${idWorkspace}/delete`, {
24
+    headers: {
25
+      'Authorization': 'Basic ' + user.auth,
26
+      ...FETCH_CONFIG.headers
27
+    },
28
+    method: 'PUT'
29
+  })
30
+
31
+export const getUserList = (user, apiUrl) =>
32
+  fetch(`${apiUrl}/users`, {
33
+    headers: {
34
+      'Authorization': 'Basic ' + user.auth,
35
+      ...FETCH_CONFIG.headers
36
+    },
37
+    method: 'GET'
38
+  })
39
+
40
+export const getUserDetail = (user, apiUrl, idUser) =>
41
+  fetch(`${apiUrl}/users/${idUser}`, {
42
+    headers: {
43
+      'Authorization': 'Basic ' + user.auth,
44
+      ...FETCH_CONFIG.headers
45
+    },
46
+    method: 'GET'
47
+  })
48
+
49
+export const putUserDisable = (user, apiUrl, idUser) =>
50
+  fetch(`${apiUrl}/users/${idUser}/disable`, {
51
+    headers: {
52
+      'Authorization': 'Basic ' + user.auth,
53
+      ...FETCH_CONFIG.headers
54
+    },
55
+    method: 'PUT'
56
+  })
57
+
58
+export const putUserEnable = (user, apiUrl, idUser) =>
59
+  fetch(`${apiUrl}/users/${idUser}/enable`, {
60
+    headers: {
61
+      'Authorization': 'Basic ' + user.auth,
62
+      ...FETCH_CONFIG.headers
63
+    },
64
+    method: 'PUT'
65
+  })
66
+
67
+export const putUserProfile = (user, apiUrl, idUser, newProfile) =>
68
+  fetch(`${apiUrl}/users/${idUser}/profile`, {
69
+    headers: {
70
+      'Authorization': 'Basic ' + user.auth,
71
+      ...FETCH_CONFIG.headers
72
+    },
73
+    body: JSON.stringify({
74
+      profile: newProfile
75
+    }),
76
+    method: 'PUT'
77
+  })
78
+
79
+export const postAddUser = (user, apiUrl, email, profile) =>
80
+  fetch(`${apiUrl}/users`, {
81
+    headers: {
82
+      'Authorization': 'Basic ' + user.auth,
83
+      ...FETCH_CONFIG.headers
84
+    },
85
+    body: JSON.stringify({
86
+      email,
87
+      email_notification: false,
88
+      profile
89
+    }),
90
+    method: 'POST'
91
+  })

+ 95 - 61
frontend_app_admin_workspace_user/src/component/AddMemberForm.jsx View File

@@ -1,74 +1,108 @@
1 1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2 3
 
3
-export const AddMemberForm = props =>
4
-  <form className='adminUserPage__adduser__form'>
5
-    <div className='adminUserPage__adduser__form__username'>
6
-      <label className='username__text' htmlFor='adduser'>
7
-        Ajouter un membre
8
-      </label>
4
+export class AddMemberForm extends React.Component {
5
+  constructor (props) {
6
+    super(props)
9 7
 
10
-      <input
11
-        type='text'
12
-        className='username__input form-control'
13
-        id='adduser'
14
-        placeholder='Nom ou Email'
15
-      />
8
+    this.state = {
9
+      newUserEmail: '',
10
+      newUserProfile: ''
11
+    }
12
+  }
16 13
 
17
-      <div className='username__createaccount'>
18
-        <input type='radio' id='createuseraccount' />
19
-        <label className='ml-2' htmlFor='createuseraccount'>Create an account for this member</label>
20
-      </div>
21
-    </div>
14
+  handleChangeNewUserEmail = e => this.setState({newUserEmail: e.target.value})
22 15
 
23
-    <div className='adminUserPage__adduser__form__userrole'>
24
-      <div className='userrole__text'>
25
-        Choose the role of the member
26
-      </div>
16
+  handleChangeNewUserProfile = e => this.setState({newUserProfile: e.currentTarget.value})
27 17
 
28
-      <div className='userrole__role'>
29
-        <div className='userrole__role__workspacemanager mt-3 d-flex align-items-center'>
30
-          <input type='radio' name='adminuser' />
31
-          <div className='d-flex align-items-center'>
32
-            <div className='userrole__role__icon mx-2'>
33
-              <i className='fa fa-fw fa-gavel' />
34
-            </div>
35
-            Workspace manager
18
+  handleClickAddUser = () => {
19
+    const { props, state } = this
20
+
21
+    if (state.newUserEmail === '' || state.newUserProfile === '') {
22
+      GLOBAL_dispatchEvent({
23
+        type: 'addFlashMsg',
24
+        data: {
25
+          msg: props.t('Please type a name and select a profile'),
26
+          type: 'warning',
27
+          delay: undefined
28
+        }
29
+      })
30
+      return
31
+    }
32
+
33
+    props.onClickAddUser(state.newUserEmail, state.newUserProfile)
34
+  }
35
+
36
+  render () {
37
+    const { props, state } = this
38
+
39
+    return (
40
+      <form className='adminUserPage__adduser__form'>
41
+        <div className='adminUserPage__adduser__form__username'>
42
+          <label className='username__text' htmlFor='adduser'>
43
+            {props.t('Add a user')}
44
+          </label>
45
+
46
+          <input
47
+            type='text'
48
+            className='username__input form-control'
49
+            id='adduser'
50
+            placeholder={props.t('Email')}
51
+            value={state.newUserEmail}
52
+            onChange={this.handleChangeNewUserEmail}
53
+          />
54
+
55
+          {/*
56
+          <div className='username__createaccount'>
57
+            <input type='radio' id='createuseraccount' />
58
+            <label className='ml-2' htmlFor='createuseraccount'>Create an account for this member</label>
36 59
           </div>
60
+          */}
37 61
         </div>
38
-        <div className='userrole__role__contentmanager mt-3 d-flex align-items-center'>
39
-          <input type='radio' name='adminuser' />
40
-          <div className='d-flex align-items-center'>
41
-            <div className='userrole__role__icon mx-2'>
42
-              <i className='fa fa-fw fa-graduation-cap' />
43
-            </div>
44
-            Content manager
62
+
63
+        <div className='adminUserPage__adduser__form__profile'>
64
+          <div className='profile__text'>
65
+            {props.t('Choose the profile of the user')}
45 66
           </div>
46
-        </div>
47
-        <div className='userrole__role__contributor mt-3 d-flex align-items-center'>
48
-          <input type='radio' name='adminuser' />
49
-          <div className='d-flex align-items-center'>
50
-            <div className='userrole__role__icon mx-2'>
51
-              <i className='fa fa-fw fa-pencil' />
52
-            </div>
53
-            Contributor
67
+
68
+          <div className='profile__list'>
69
+            {Object.keys(props.profile).map(p => props.profile[p]).map(p =>
70
+              <label
71
+                className='profile__list__item'
72
+                htmlFor={p.slug}
73
+                key={p.id}
74
+              >
75
+                <input
76
+                  type='radio'
77
+                  name='newUserProfile'
78
+                  id={p.slug}
79
+                  value={p.slug}
80
+                  checked={state.newUserProfile === p.slug}
81
+                  onChange={this.handleChangeNewUserProfile}
82
+                />
83
+
84
+                <div className='d-flex align-items-center'>
85
+                  <div className='userrole__role__icon mx-2' style={{color: p.hexcolor}}>
86
+                    <i className={`fa fa-fw fa-${p.faIcon}`} />
87
+                  </div>
88
+                  {p.label}
89
+                </div>
90
+              </label>
91
+            )}
54 92
           </div>
55 93
         </div>
56
-        <div className='userrole__role__reader mt-3 d-flex align-items-center'>
57
-          <input type='radio' name='adminuser' />
58
-          <div className='d-flex align-items-center'>
59
-            <div className='userrole__role__icon mx-2'>
60
-              <i className='fa fa-fw fa-eye' />
61
-            </div>
62
-            Reader
63
-          </div>
94
+        <div className='adminUserPage__adduser__form__submit'>
95
+          <button
96
+            type='button'
97
+            className='btn'
98
+            onClick={this.handleClickAddUser}
99
+          >
100
+            {props.t('Add the user')}
101
+          </button>
64 102
         </div>
65
-      </div>
66
-    </div>
67
-    <div className='adminUserPage__adduser__form__submit'>
68
-      <button className='btn'>
69
-        Add the member
70
-      </button>
71
-    </div>
72
-  </form>
103
+      </form>
104
+    )
105
+  }
106
+}
73 107
 
74
-export default AddMemberForm
108
+export default translate()(AddMemberForm)

+ 78 - 87
frontend_app_admin_workspace_user/src/component/AdminUser.jsx View File

@@ -1,4 +1,5 @@
1 1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2 3
 import {
3 4
   Delimiter,
4 5
   PageWrapper,
@@ -22,7 +23,50 @@ export class AdminUser extends React.Component {
22 23
     displayAddMember: !prevState.displayAddMember
23 24
   }))
24 25
 
26
+  handleToggleUser = (e, idUser, toggle) => {
27
+    e.preventDefault()
28
+    e.stopPropagation()
29
+    this.props.onClickToggleUserBtn(idUser, toggle)
30
+  }
31
+
32
+  handleToggleProfileManager = (e, idUser, toggle) => {
33
+    e.preventDefault()
34
+    e.stopPropagation()
35
+
36
+    const { props } = this
37
+
38
+    if (props.userList.find(u => u.user_id === idUser).profile === 'administrators') {
39
+      GLOBAL_dispatchEvent({
40
+        type: 'addFlashMsg',
41
+        data: {
42
+          msg: props.t('An administrator can always create workspaces'),
43
+          type: 'warning',
44
+          delay: undefined
45
+        }
46
+      })
47
+      return
48
+    }
49
+
50
+    if (toggle) props.onChangeProfile(idUser, 'managers')
51
+    else props.onChangeProfile(idUser, 'users')
52
+  }
53
+
54
+  handleToggleProfileAdministrator = (e, idUser, toggle) => {
55
+    e.preventDefault()
56
+    e.stopPropagation()
57
+
58
+    if (toggle) this.props.onChangeProfile(idUser, 'administrators')
59
+    else this.props.onChangeProfile(idUser, 'managers')
60
+  }
61
+
62
+  handleClickAddUser = (email, profile) => {
63
+    this.props.onClickAddUser(email, profile)
64
+    this.handleToggleAddMember()
65
+  }
66
+
25 67
   render () {
68
+    const { props } = this
69
+
26 70
     return (
27 71
       <PageWrapper customClass='adminUserPage'>
28 72
         <PageTitle
@@ -33,16 +77,19 @@ export class AdminUser extends React.Component {
33 77
         <PageContent parentClass='adminUserPage'>
34 78
 
35 79
           <div className='adminUserPage__description'>
36
-            On this page you can manage the members of your instance Tracim.
80
+            {props.t('On this page you can manage the members of your Tracim instance.')}
37 81
           </div>
38 82
 
39 83
           <div className='adminUserPage__adduser'>
40 84
             <button className='adminUserPage__adduser__button btn' onClick={this.handleToggleAddMember}>
41
-              Add a member
85
+              {props.t('Add a member')}
42 86
             </button>
43 87
 
44 88
             {this.state.displayAddMember &&
45
-              <AddMemberForm />
89
+              <AddMemberForm
90
+                profile={props.profile}
91
+                onClickAddUser={this.handleClickAddUser}
92
+              />
46 93
             }
47 94
           </div>
48 95
 
@@ -52,92 +99,36 @@ export class AdminUser extends React.Component {
52 99
             <table className='table'>
53 100
               <thead>
54 101
                 <tr>
55
-                  <th scope='col'>Active</th>
56
-                  <th scope='col'>Member</th>
57
-                  <th scope='col'>Email</th>
58
-                  <th scope='col'>Member can create workspace</th>
59
-                  <th scope='col'>Administrator</th>
102
+                  <th scope='col'>{props.t('Active')}</th>
103
+                  <th scope='col'>{props.t('Member')}</th>
104
+                  <th scope='col'>{props.t('Email')}</th>
105
+                  <th scope='col'>{props.t('Can create workspace')}</th>
106
+                  <th scope='col'>{props.t('Administrator')}</th>
60 107
                 </tr>
61 108
               </thead>
109
+
62 110
               <tbody>
63
-                <tr>
64
-                  <td>
65
-                    <BtnSwitch />
66
-                  </td>
67
-                  <th scope='row'>Joe Delavaiga</th>
68
-                  <td><a href='#'>joedelavaiga@mail.com</a></td>
69
-                  <td>
70
-                    <BtnSwitch />
71
-                  </td>
72
-                  <td>
73
-                    <BtnSwitch />
74
-                  </td>
75
-                </tr>
76
-                <tr>
77
-                  <td>
78
-                    <BtnSwitch />
79
-                  </td>
80
-                  <th scope='row'>Susie Washington</th>
81
-                  <td><a href='#'>susiewash@mail.com</a></td>
82
-                  <td>
83
-                    <BtnSwitch />
84
-                  </td>
85
-                  <td>
86
-                    <BtnSwitch />
87
-                  </td>
88
-                </tr>
89
-                <tr>
90
-                  <td>
91
-                    <BtnSwitch />
92
-                  </td>
93
-                  <th scope='row'>Marty MacJoe</th>
94
-                  <td><a href='#'>martymac@mail.com</a></td>
95
-                  <td>
96
-                    <BtnSwitch />
97
-                  </td>
98
-                  <td>
99
-                    <BtnSwitch />
100
-                  </td>
101
-                </tr>
102
-                <tr>
103
-                  <td>
104
-                    <BtnSwitch />
105
-                  </td>
106
-                  <th scope='row'>Joe Delavaiga</th>
107
-                  <td><a href='#'>joedelavaiga@mail.com</a></td>
108
-                  <td>
109
-                    <BtnSwitch />
110
-                  </td>
111
-                  <td>
112
-                    <BtnSwitch />
113
-                  </td>
114
-                </tr>
115
-                <tr>
116
-                  <td>
117
-                    <BtnSwitch />
118
-                  </td>
119
-                  <th scope='row'>Susie Washington</th>
120
-                  <td><a href='#'>susiewash@mail.com</a></td>
121
-                  <td>
122
-                    <BtnSwitch />
123
-                  </td>
124
-                  <td>
125
-                    <BtnSwitch />
126
-                  </td>
127
-                </tr>
128
-                <tr>
129
-                  <td>
130
-                    <BtnSwitch />
131
-                  </td>
132
-                  <th scope='row'>Marty MacJoe</th>
133
-                  <td><a href='#'>martymac@mail.com</a></td>
134
-                  <td>
135
-                    <BtnSwitch />
136
-                  </td>
137
-                  <td>
138
-                    <BtnSwitch />
139
-                  </td>
140
-                </tr>
111
+                {props.userList.map(u =>
112
+                  <tr key={u.user_id}>
113
+                    <td>
114
+                      <BtnSwitch checked={u.is_active} onChange={e => this.handleToggleUser(e, u.user_id, !u.is_active)} />
115
+                    </td>
116
+                    <th scope='row'>{u.public_name}</th>
117
+                    <td>{u.email}</td>
118
+                    <td>
119
+                      <BtnSwitch
120
+                        checked={u.profile === 'managers' || u.profile === 'administrators'}
121
+                        onChange={e => this.handleToggleProfileManager(e, u.user_id, !(u.profile === 'managers' || u.profile === 'administrators'))}
122
+                      />
123
+                    </td>
124
+                    <td>
125
+                      <BtnSwitch
126
+                        checked={u.profile === 'administrators'}
127
+                        onChange={e => this.handleToggleProfileAdministrator(e, u.user_id, !(u.profile === 'administrators'))}
128
+                      />
129
+                    </td>
130
+                  </tr>
131
+                )}
141 132
               </tbody>
142 133
             </table>
143 134
           </div>
@@ -149,4 +140,4 @@ export class AdminUser extends React.Component {
149 140
   }
150 141
 }
151 142
 
152
-export default AdminUser
143
+export default translate()(AdminUser)

+ 56 - 178
frontend_app_admin_workspace_user/src/component/AdminWorkspace.jsx View File

@@ -1,4 +1,5 @@
1 1
 import React from 'react'
2
+import { translate } from 'react-i18next'
2 3
 import {
3 4
   Delimiter,
4 5
   PageWrapper,
@@ -6,189 +7,66 @@ import {
6 7
   PageContent
7 8
 } from 'tracim_frontend_lib'
8 9
 
9
-export class AdminWorkspace extends React.Component {
10
-  render () {
11
-    return (
12
-      <PageWrapper customClass='adminWorkspacePage'>
13
-        <PageTitle
14
-          parentClass={'adminWorkspacePage'}
15
-          title={'Workspace management'}
16
-        />
10
+const AdminWorkspace = props =>
11
+  <PageWrapper customClass='adminWorkspacePage'>
12
+    <PageTitle
13
+      parentClass={'adminWorkspacePage'}
14
+      title={'Workspace management'}
15
+    />
17 16
 
18
-        <PageContent parentClass='adminWorkspacePage'>
17
+    <PageContent parentClass='adminWorkspacePage'>
18
+      <div className='adminWorkspacePage__description'>
19
+        {props.t('List of every workspaces')}
20
+      </div>
19 21
 
20
-          <div className='adminWorkspacePage__description'>
21
-            List of every workspaces
22
-          </div>
22
+      <Delimiter customClass={'adminWorkspacePage__delimiter'} />
23 23
 
24
-          { /*
25
-            Alexi Cauvin 08/08/2018 => desactivate create workspace button due to redundancy
24
+      <div className='adminWorkspacePage__workspaceTable'>
26 25
 
27
-            <div className='adminWorkspacePage__createworkspace'>
28
-              <button className='adminWorkspacePage__createworkspace__btncreate btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover'>
29
-                {this.props.t('Create a workspace')}
30
-              </button>
31
-            </div>
32
-          */ }
26
+        <table className='table'>
27
+          <thead>
28
+            <tr>
29
+              <th scope='col'>Id</th>
30
+              <th scope='col'>{props.t('Workspace')}</th>
31
+              <th scope='col'>{props.t('Description')}</th>
32
+              <th scope='col'>{props.t('Member count')}</th>
33
+              {/* <th scope='col'>Calendar</th> */}
34
+              <th scope='col'>{props.t('Delete workspace')}</th>
35
+            </tr>
36
+          </thead>
33 37
 
34
-          <Delimiter customClass={'adminWorkspacePage__delimiter'} />
35
-
36
-          <div className='adminWorkspacePage__workspaceTable'>
37
-
38
-            <table className='table'>
39
-              <thead>
40
-                <tr>
41
-                  <th scope='col'>ID</th>
42
-                  <th scope='col'>Workspace</th>
43
-                  <th scope='col'>Description</th>
44
-                  <th scope='col'>Member count</th>
45
-                  { /*
46
-                      <th scope='col'>Calendar</th>
47
-                    */ }
48
-                  <th scope='col'>Delete workspace</th>
49
-                </tr>
50
-              </thead>
51
-              <tbody>
52
-                <tr>
53
-                  <th>1</th>
54
-                  <td>Design v_2</td>
55
-                  <td>Workspace about tracim v2 design</td>
56
-                  { /*
57
-                      <td className='d-flex align-items-center flex-wrap'>
58
-                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
59
-                          <i className='fa fa-fw fa-check-square-o' />
60
-                        </div>
61
-                        Enable
62
-                      </td>
63
-                    */ }
64
-                  <td>8</td>
65
-                  <td>
66
-                    <div className='adminWorkspacePage__workspaceTable__deleteworkspace primaryColorFont primaryColorFontDarkenHover'>
67
-                      <div className='adminWorkspacePage__workspaceTable__deleteworkspace__removalicon mr-3'>
68
-                        <i className='fa fa-fw fa-trash-o' />
69
-                      </div>
70
-                      Delete
71
-                    </div>
72
-                  </td>
73
-                </tr>
74
-                <tr>
75
-                  <th>2</th>
76
-                  <td>New features</td>
77
-                  <td>Add a new features : Annotated files</td>
78
-                  { /*
79
-                      <td className='d-flex align-items-center flex-wrap'>
80
-                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
81
-                          <i className='fa fa-fw fa-square-o' />
82
-                        </div>
83
-                        Disable
84
-                      </td>
85
-                    */ }
86
-                  <td>5</td>
87
-                  <td>
88
-                    <div className='adminWorkspacePage__workspaceTable__deleteworkspace primaryColorFont primaryColorFontDarkenHover'>
89
-                      <div className='adminWorkspacePage__workspaceTable__deleteworkspace__removalicon mr-3'>
90
-                        <i className='fa fa-fw fa-trash-o' />
91
-                      </div>
92
-                      Delete
38
+          <tbody>
39
+            {props.workspaceList/* .sort((a, b) => a.workspace_id > b.workspace_id) */.map(ws =>
40
+              <tr key={ws.slug}>
41
+                <th>{ws.workspace_id}</th>
42
+                <td>{ws.label}</td>
43
+                <td>"(nyi) blocked by backend"</td>
44
+                {/*
45
+                  <td className='d-flex align-items-center flex-wrap'>
46
+                    <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
47
+                      <i className='fa fa-fw fa-check-square-o' />
93 48
                     </div>
49
+                    Enable
94 50
                   </td>
95
-                </tr>
96
-                <tr>
97
-                  <th>3</th>
98
-                  <td>Fix Backend</td>
99
-                  <td>workspace referring to multiple issues on the backend </td>
100
-                  { /*
101
-                      <td className='d-flex align-items-center flex-wrap'>
102
-                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
103
-                          <i className='fa fa-fw fa-check-square-o' />
104
-                        </div>
105
-                        Enable
106
-                      </td>
107
-                    */ }
108
-                  <td>3</td>
109
-                  <td>
110
-                    <div className='adminWorkspacePage__workspaceTable__deleteworkspace primaryColorFont primaryColorFontDarkenHover'>
111
-                      <div className='adminWorkspacePage__workspaceTable__deleteworkspace__removalicon mr-3'>
112
-                        <i className='fa fa-fw fa-trash-o' />
113
-                      </div>
114
-                      Delete
115
-                    </div>
116
-                  </td>
117
-                </tr>
118
-                <tr>
119
-                  <th>4</th>
120
-                  <td>Design v_2</td>
121
-                  <td>Workspace about tracim v2 design</td>
122
-                  { /*
123
-                      <td className='d-flex align-items-center flex-wrap'>
124
-                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
125
-                          <i className='fa fa-fw fa-square-o' />
126
-                        </div>
127
-                        Disable
128
-                      </td>
129
-                    */ }
130
-                  <td>8</td>
131
-                  <td>
132
-                    <div className='adminWorkspacePage__workspaceTable__deleteworkspace primaryColorFont primaryColorFontDarkenHover'>
133
-                      <div className='adminWorkspacePage__workspaceTable__deleteworkspace__removalicon mr-3'>
134
-                        <i className='fa fa-fw fa-trash-o' />
135
-                      </div>
136
-                      Delete
137
-                    </div>
138
-                  </td>
139
-                </tr>
140
-                <tr>
141
-                  <th>5</th>
142
-                  <td>New features</td>
143
-                  <td>Add a new features : Annotated files</td>
144
-                  { /*
145
-                      <td className='d-flex align-items-center flex-wrap'>
146
-                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
147
-                          <i className='fa fa-fw fa-square-o' />
148
-                        </div>
149
-                        Disable
150
-                      </td>
151
-                    */ }
152
-                  <td>5</td>
153
-                  <td>
154
-                    <div className='adminWorkspacePage__workspaceTable__deleteworkspace primaryColorFont primaryColorFontDarkenHover'>
155
-                      <div className='adminWorkspacePage__workspaceTable__deleteworkspace__removalicon mr-3'>
156
-                        <i className='fa fa-fw fa-trash-o' />
157
-                      </div>
158
-                      Delete
159
-                    </div>
160
-                  </td>
161
-                </tr>
162
-                <tr>
163
-                  <th>6</th>
164
-                  <td>Fix Backend</td>
165
-                  <td>workspace referring to multiple issues on the backend </td>
166
-                  { /*
167
-                      <td className='d-flex align-items-center flex-wrap'>
168
-                        <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
169
-                          <i className='fa fa-fw fa-check-square-o' />
170
-                        </div>
171
-                        Enable
172
-                      </td>
173
-                    */ }
174
-                  <td>3</td>
175
-                  <td>
176
-                    <div className='adminWorkspacePage__workspaceTable__deleteworkspace primaryColorFont primaryColorFontDarkenHover'>
177
-                      <div className='adminWorkspacePage__workspaceTable__deleteworkspace__removalicon mr-3'>
178
-                        <i className='fa fa-fw fa-trash-o' />
179
-                      </div>
180
-                      Delete
181
-                    </div>
182
-                  </td>
183
-                </tr>
184
-              </tbody>
185
-            </table>
186
-          </div>
187
-
188
-        </PageContent>
189
-      </PageWrapper>
190
-    )
191
-  }
192
-}
51
+                */}
52
+                <td>{ws.memberList.length}</td>
53
+                <td>
54
+                  <div className='adminWorkspacePage__table__delete primaryColorFont primaryColorFontDarkenHover'>
55
+                    <button
56
+                      type='button'
57
+                      className='adminWorkspacePage__table__delete__icon btn mr-3'
58
+                      onClick={() => props.onClickDeleteWorkspace(ws.workspace_id)}
59
+                    >
60
+                      <i className='fa fa-fw fa-trash-o' />
61
+                    </button>
62
+                  </div>
63
+                </td>
64
+              </tr>
65
+            )}
66
+          </tbody>
67
+        </table>
68
+      </div>
69
+    </PageContent>
70
+  </PageWrapper>
193 71
 
194
-export default AdminWorkspace
72
+export default translate()(AdminWorkspace)

+ 217 - 14
frontend_app_admin_workspace_user/src/container/AdminWorkspaceUser.jsx View File

@@ -2,11 +2,21 @@ import React from 'react'
2 2
 import { translate } from 'react-i18next'
3 3
 import i18n from '../i18n.js'
4 4
 import {
5
-  addAllResourceI18n
6
-  // handleFetchResult
5
+  addAllResourceI18n,
6
+  handleFetchResult,
7
+  CardPopup
7 8
 } from 'tracim_frontend_lib'
8 9
 import { debug } from '../helper.js'
9 10
 import {
11
+  getWorkspaceList,
12
+  getWorkspaceMemberList,
13
+  deleteWorkspace,
14
+  getUserList,
15
+  getUserDetail,
16
+  putUserDisable,
17
+  putUserEnable,
18
+  putUserProfile,
19
+  postAddUser
10 20
 } from '../action.async.js'
11 21
 import AdminWorkspace from '../component/AdminWorkspace.jsx'
12 22
 import AdminUser from '../component/AdminUser.jsx'
@@ -16,12 +26,15 @@ require('../css/index.styl')
16 26
 class AdminWorkspaceUser extends React.Component {
17 27
   constructor (props) {
18 28
     super(props)
29
+
19 30
     this.state = {
20 31
       appName: 'admin_workspace_user',
21 32
       isVisible: true,
22 33
       config: props.data ? props.data.config : debug.config,
23 34
       loggedUser: props.data ? props.data.loggedUser : debug.loggedUser,
24
-      content: props.data ? props.data.content : debug.content
35
+      content: props.data ? props.data.content : debug.content,
36
+      popupDeleteWorkspaceDisplay: false,
37
+      workspaceToDelete: null
25 38
     }
26 39
 
27 40
     // i18n has been init, add resources from frontend
@@ -45,14 +58,18 @@ class AdminWorkspaceUser extends React.Component {
45 58
   componentDidMount () {
46 59
     console.log('%c<AdminWorkspaceUser> did mount', `color: ${this.state.config.hexcolor}`)
47 60
 
48
-    this.loadContent()
61
+    if (this.state.config.type === 'workspace') this.loadWorkspaceContent()
62
+    else if (this.state.config.type === 'user') this.loadUserContent()
49 63
   }
50 64
 
51 65
   componentDidUpdate (prevProps, prevState) {
52 66
     const { state } = this
53 67
 
54 68
     console.log('%c<AdminWorkspaceUser> did update', `color: ${this.state.config.hexcolor}`, prevState, state)
55
-    if (prevState.config.type !== state.config.type) this.loadContent()
69
+    if (prevState.config.type !== state.config.type) {
70
+      if (state.config.type === 'workspace') this.loadWorkspaceContent()
71
+      else if (state.config.type === 'user') this.loadUserContent()
72
+    }
56 73
   }
57 74
 
58 75
   componentWillUnmount () {
@@ -60,24 +77,210 @@ class AdminWorkspaceUser extends React.Component {
60 77
     document.removeEventListener('appCustomEvent', this.customEventReducer)
61 78
   }
62 79
 
63
-  loadContent = () => {
64
-    return null
80
+  loadWorkspaceContent = async () => {
81
+    const { props, state } = this
82
+
83
+    const fetchWorkspaceList = getWorkspaceList(state.loggedUser, state.config.apiUrl)
84
+    const workspaceList = await handleFetchResult(await fetchWorkspaceList)
85
+
86
+    switch (workspaceList.apiResponse.status) {
87
+      case 200:
88
+        const fetchWorkspaceListMemberList = await Promise.all(
89
+          workspaceList.body.map(async ws =>
90
+            handleFetchResult(await getWorkspaceMemberList(state.loggedUser, state.config.apiUrl, ws.workspace_id))
91
+          )
92
+        )
93
+        this.setState(prev => ({
94
+          content: {
95
+            ...prev.content,
96
+            workspaceList: workspaceList.body.map(ws => ({
97
+              ...ws,
98
+              memberList: (fetchWorkspaceListMemberList.find(fws => fws.body[0].workspace_id === ws.workspace_id) || {body: []}).body
99
+            }))
100
+          }
101
+        }))
102
+        break
103
+
104
+      default: GLOBAL_dispatchEvent({
105
+        type: 'addFlashMsg',
106
+        data: {
107
+          msg: props.t('Error while loading workspaces list'),
108
+          type: 'warning',
109
+          delay: undefined
110
+        }
111
+      })
112
+    }
113
+  }
114
+
115
+  loadUserContent = async () => {
116
+    const { props, state } = this
117
+
118
+    const userList = await handleFetchResult(await getUserList(state.loggedUser, state.config.apiUrl))
119
+
120
+    switch (userList.apiResponse.status) {
121
+      case 200:
122
+        const fetchUserDetailList = await Promise.all(
123
+          userList.body.map(async u =>
124
+            handleFetchResult(await getUserDetail(state.loggedUser, state.config.apiUrl, u.user_id))
125
+          )
126
+        )
127
+        this.setState(prev => ({
128
+          content: {
129
+            ...prev.content,
130
+            userList: fetchUserDetailList.map(fu => fu.body)
131
+          }
132
+        }))
133
+        break
134
+
135
+      default: GLOBAL_dispatchEvent({
136
+        type: 'addFlashMsg',
137
+        data: {
138
+          msg: props.t('Error while loading users list'),
139
+          type: 'warning',
140
+          delay: undefined
141
+        }
142
+      })
143
+    }
144
+  }
145
+
146
+  handleDeleteWorkspace = async () => {
147
+    const { props, state } = this
148
+
149
+    const deleteWorkspaceResponse = await handleFetchResult(await deleteWorkspace(state.loggedUser, state.config.apiUrl, state.workspaceToDelete))
150
+    switch (deleteWorkspaceResponse.status) {
151
+      case 204:
152
+        this.loadWorkspaceContent()
153
+        GLOBAL_dispatchEvent({
154
+          type: 'refreshWorkspaceList',
155
+          data: {}
156
+        })
157
+        break
158
+      default: GLOBAL_dispatchEvent({
159
+        type: 'addFlashMsg',
160
+        data: {
161
+          msg: props.t('Error while deleting workspace'),
162
+          type: 'warning',
163
+          delay: undefined
164
+        }
165
+      })
166
+    }
167
+    this.handleClosePopupDeleteWorkspace()
168
+  }
169
+
170
+  handleOpenPopupDeleteWorkspace = idWorkspace => this.setState({
171
+    popupDeleteWorkspaceDisplay: true,
172
+    workspaceToDelete: idWorkspace
173
+  })
174
+
175
+  handleClosePopupDeleteWorkspace = () => this.setState({popupDeleteWorkspaceDisplay: false})
176
+
177
+  handleToggleUser = async (idUser, toggle) => {
178
+    const { props, state } = this
179
+
180
+    const activateOrDelete = toggle ? putUserEnable : putUserDisable
181
+
182
+    const toggleUser = await handleFetchResult(await activateOrDelete(state.loggedUser, state.config.apiUrl, idUser))
183
+    switch (toggleUser.status) {
184
+      case 204: this.loadUserContent(); break
185
+      default: GLOBAL_dispatchEvent({
186
+        type: 'addFlashMsg',
187
+        data: {
188
+          msg: props.t('Error while enabling or disabling user'),
189
+          type: 'warning',
190
+          delay: undefined
191
+        }
192
+      })
193
+    }
194
+  }
195
+
196
+  handleUpdateProfile = async (idUser, newProfile) => {
197
+    const { props, state } = this
198
+
199
+    const toggleManager = await handleFetchResult(await putUserProfile(state.loggedUser, state.config.apiUrl, idUser, newProfile))
200
+    switch (toggleManager.status) {
201
+      case 204: this.loadUserContent(); break
202
+      default: GLOBAL_dispatchEvent({
203
+        type: 'addFlashMsg',
204
+        data: {
205
+          msg: props.t('Error while saving new profile'),
206
+          type: 'warning',
207
+          delay: undefined
208
+        }
209
+      })
210
+    }
211
+  }
212
+
213
+  handleClickAddUser = async (email, profile) => {
214
+    const { props, state } = this
215
+
216
+    const newUserResult = await handleFetchResult(await postAddUser(state.loggedUser, state.config.apiUrl, email, profile))
217
+    switch (newUserResult.apiResponse.status) {
218
+      case 200:
219
+        this.loadUserContent()
220
+
221
+        break
222
+      default: GLOBAL_dispatchEvent({
223
+        type: 'addFlashMsg',
224
+        data: {
225
+          msg: props.t('Error while saving new user'),
226
+          type: 'warning',
227
+          delay: undefined
228
+        }
229
+      })
230
+    }
65 231
   }
66 232
 
67 233
   render () {
68
-    const { isVisible } = this.state
69
-    // const { t } = this.props
234
+    const { props, state } = this
70 235
 
71
-    if (!isVisible) return null
236
+    if (!state.isVisible) return null
72 237
 
73 238
     return (
74 239
       <div>
75
-        {this.state.config.type === 'workspace' &&
76
-          <AdminWorkspace />
240
+        {state.config.type === 'workspace' &&
241
+          <AdminWorkspace
242
+            workspaceList={state.content.workspaceList}
243
+            onClickDeleteWorkspace={this.handleOpenPopupDeleteWorkspace}
244
+          />
77 245
         }
78 246
 
79
-        {this.state.config.type === 'user' &&
80
-          <AdminUser />
247
+        {state.config.type === 'user' &&
248
+          <AdminUser
249
+            userList={state.content.userList}
250
+            profile={state.content.profile}
251
+            onClickToggleUserBtn={this.handleToggleUser}
252
+            onChangeProfile={this.handleUpdateProfile}
253
+            onClickAddUser={this.handleClickAddUser}
254
+          />
255
+        }
256
+
257
+        {state.popupDeleteWorkspaceDisplay &&
258
+          <CardPopup
259
+            customClass='adminworkspaceuser__popup'
260
+            customHeaderClass='primaryColorBg'
261
+            onClose={this.handleClosePopupDeleteWorkspace}
262
+          >
263
+            <div className='adminworkspaceuser__popup__body'>
264
+              <div className='adminworkspaceuser__popup__body__msg'>{props.t('Are you sure ?')}</div>
265
+              <div className='adminworkspaceuser__popup__body__btn'>
266
+                <button
267
+                  type='button'
268
+                  className='btn'
269
+                  onClick={this.handleClosePopupDeleteWorkspace}
270
+                >
271
+                  {props.t('Cancel')}
272
+                </button>
273
+
274
+                <button
275
+                  type='button'
276
+                  className='btn'
277
+                  onClick={this.handleDeleteWorkspace}
278
+                >
279
+                  {props.t('Delete')}
280
+                </button>
281
+              </div>
282
+            </div>
283
+          </CardPopup>
81 284
         }
82 285
       </div>
83 286
     )

+ 26 - 9
frontend_app_admin_workspace_user/src/css/index.styl View File

@@ -15,9 +15,9 @@
15 15
     font-size 20px
16 16
   &__delimiter
17 17
     margin 65px auto
18
-  &__workspaceTable
18
+  &__table
19 19
     margin 25px 15px
20
-    &__deleteworkspace
20
+    &__delete
21 21
       display flex
22 22
       align-items center
23 23
       flex-wrap wrap
@@ -41,14 +41,31 @@
41 41
         &__input
42 42
           margin 15px 0 20px 0
43 43
           width 40%
44
-      &__userrole
45
-        .userrole
46
-        &__text
47
-          margin 20px 0
48
-          font-weight bold
49
-        &__role
50
-          margin 25px 0
44
+      &__profile
45
+        .profile
46
+          &__text
47
+            margin 20px 0
48
+            font-weight bold
49
+          &__list
50
+            margin 25px 0
51
+            &__item
52
+              display flex
53
+              align-items center
54
+              margin-top 10px
55
+              cursor pointer
51 56
   &__delimiter
52 57
     margin 50px auto
53 58
   &__table
54 59
     margin-bottom 100px
60
+
61
+.adminworkspaceuser__popup
62
+  top 0
63
+  .cardPopup__container
64
+    width 400px
65
+  &__body
66
+    width 100%
67
+    &__btn
68
+      display flex
69
+      justify-content flex-end
70
+      .btn:first-child
71
+        margin-right 15px

+ 37 - 31
frontend_app_admin_workspace_user/src/helper.js View File

@@ -1,3 +1,5 @@
1
+import i18n from '../../frontend/src/i18n.js'
2
+
1 3
 export const FETCH_CONFIG = {
2 4
   headers: {
3 5
     'Accept': 'application/json',
@@ -11,44 +13,48 @@ export const debug = {
11 13
     slug: 'admin_workspace_user',
12 14
     faIcon: 'file-text-o',
13 15
     hexcolor: '#7d4e24',
14
-    type: 'user',
15
-    translation: {en: {}, fr: {}}
16
+    type: 'user', // 'user' or 'workspace'
17
+    translation: {en: {}, fr: {}},
18
+    apiUrl: 'http://localhost:6543/api/v2',
19
+    apiHeader: {
20
+      'Accept': 'application/json',
21
+      'Content-Type': 'application/json'
22
+      // 'Authorization': 'Basic ' + btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
23
+    }
16 24
   },
17
-  loggedUser: { // @FIXME this object is outdated
18
-    user_id: 5,
19
-    username: 'Smoi',
20
-    firstname: 'Côme',
21
-    lastname: 'Stoilenom',
25
+  loggedUser: {
26
+    user_id: 1,
27
+    public_name: 'Global Manager',
22 28
     email: 'osef@algoo.fr',
23 29
     lang: 'en',
24 30
     avatar_url: 'https://avatars3.githubusercontent.com/u/11177014?s=460&v=4',
25 31
     auth: btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
26 32
   },
27 33
   content: {
28
-    author: {
29
-      avatar_url: null,
30
-      public_name: 'Global manager',
31
-      user_id: 1 // -1 or 1 for debug
32
-    },
33
-    content_id: 22, // 1 or 22 for debug
34
-    content_type: 'html-document',
35
-    created: '2018-06-18T14:59:26Z',
36
-    current_revision_id: 11,
37
-    is_archived: false,
38
-    is_deleted: false,
39
-    label: 'Current Menu',
40
-    last_modifier: {
41
-      avatar_url: null,
42
-      public_name: 'Global manager',
43
-      user_id: 1
34
+    profile: {
35
+      ADMINISTRATOR: {
36
+        id: 1,
37
+        slug: 'administrators',
38
+        faIcon: 'rocket',
39
+        hexcolor: '#123456',
40
+        label: i18n.t('Administrator')
41
+      },
42
+      MANAGER: {
43
+        id: 2,
44
+        slug: 'managers',
45
+        faIcon: 'car',
46
+        hexcolor: '#654321',
47
+        label: i18n.t('Manager')
48
+      },
49
+      USER: {
50
+        id: 4,
51
+        slug: 'users',
52
+        faIcon: 'bicycle',
53
+        hexcolor: '#123123',
54
+        label: i18n.t('User')
55
+      }
44 56
     },
45
-    modified: '2018-06-18T14:59:26Z',
46
-    parent_id: 2,
47
-    raw_content: '<div>bonjour, je suis un lapin.</div>',
48
-    show_in_ui: true,
49
-    slug: 'current-menu',
50
-    status: 'open',
51
-    sub_content_types: ['thread', 'html-document', 'file', 'folder'],
52
-    workspace_id: 1
57
+    workspaceList: [],
58
+    userList: []
53 59
   }
54 60
 }

+ 0 - 5
frontend_app_html-document/src/helper.js View File

@@ -22,11 +22,6 @@ export const debug = {
22 22
     creationLabel: 'Write a document',
23 23
     domContainer: 'appFeatureContainer',
24 24
     apiUrl: 'http://localhost:6543/api/v2',
25
-    apiHeader: {
26
-      'Accept': 'application/json',
27
-      'Content-Type': 'application/json'
28
-      // 'Authorization': 'Basic ' + btoa(`${'admin@admin.admin'}:${'admin@admin.admin'}`)
29
-    },
30 25
     availableStatuses: [{
31 26
       label: 'Open',
32 27
       slug: 'open',

+ 3 - 1
frontend_lib/src/component/CardPopup/CardPopup.jsx View File

@@ -8,7 +8,7 @@ const CardPopup = props => {
8 8
   return (
9 9
     <div className={classnames(props.customClass, 'cardPopup')}>
10 10
       <div className='cardPopup__container'>
11
-        <div className='cardPopup__header' style={{backgroundColor: props.customColor}} />
11
+        <div className={classnames(props.customHeaderClass, 'cardPopup__header')} style={{backgroundColor: props.customColor}} />
12 12
 
13 13
         <div className='cardPopup__close' onClick={props.onClose}>
14 14
           <i className='fa fa-times' />
@@ -26,12 +26,14 @@ export default CardPopup
26 26
 
27 27
 CardPopup.propTypes = {
28 28
   customClass: PropTypes.string,
29
+  customHeaderClass: PropTypes.string,
29 30
   customColor: PropTypes.string,
30 31
   onClose: PropTypes.func
31 32
 }
32 33
 
33 34
 CardPopup.defaultProps = {
34 35
   customClass: 'defaultCustomClass',
36
+  customHeaderClass: '',
35 37
   customColor: '',
36 38
   onClose: () => {}
37 39
 }

+ 3 - 0
frontend_lib/src/component/CardPopup/CardPopup.styl View File

@@ -2,6 +2,9 @@
2 2
 
3 3
 .cardPopup
4 4
   position fixed
5
+  top 0
6
+  left 0
7
+  margin-top 120px
5 8
   display flex
6 9
   justify-content center
7 10
   width 100%