Edit user, edit profile, create user form

This commit is contained in:
bourquecharles
2021-07-20 16:53:27 -04:00
parent b69527cc2f
commit c3562b48bd
12 changed files with 554 additions and 258 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "ucentral-libs", "name": "ucentral-libs",
"version": "0.8.15", "version": "0.8.16",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ucentral-libs", "name": "ucentral-libs",
"version": "0.8.15", "version": "0.8.16",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.6", "@babel/core": "^7.14.6",
"@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-class-properties": "^7.14.5",

View File

@@ -1,6 +1,6 @@
{ {
"name": "ucentral-libs", "name": "ucentral-libs",
"version": "0.8.15", "version": "0.8.16",
"main": "dist/index.js", "main": "dist/index.js",
"source": "src/index.js", "source": "src/index.js",
"engines": { "engines": {

View File

@@ -28,7 +28,7 @@ const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies
return ( return (
<CForm> <CForm>
<CFormGroup row> <CFormGroup row className="pb-3">
<CLabel sm="2" col htmlFor="email"> <CLabel sm="2" col htmlFor="email">
{t('user.email_address')} {t('user.email_address')}
</CLabel> </CLabel>
@@ -42,14 +42,41 @@ const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies
/> />
<CInvalidFeedback>{t('user.provide_email')}</CInvalidFeedback> <CInvalidFeedback>{t('user.provide_email')}</CInvalidFeedback>
</CCol> </CCol>
<CLabel sm="2" col htmlFor="userRole">
{t('user.user_role')}
</CLabel>
<CCol sm="4">
<CSelect custom id="userRole" defaultValue="Admin" onChange={updateField}>
<option value="admin">Admin</option>
<option value="csr">CSR</option>
<option value="root">Root</option>
<option value="special">Special</option>
<option value="sub">Sub</option>
<option value="system">System</option>
</CSelect>
</CCol>
</CFormGroup>
<CFormGroup row>
<CLabel sm="2" col htmlFor="name"> <CLabel sm="2" col htmlFor="name">
{t('user.name')} {t('user.name')}
</CLabel> </CLabel>
<CCol sm="4"> <CCol sm="4">
<CInput id="name" value={fields.name.value} onChange={updateField} maxLength="20" /> <CInput id="name" value={fields.name.value} onChange={updateField} maxLength="20" />
</CCol> </CCol>
<CLabel sm="2" col htmlFor="description">
{t('user.description')}
</CLabel>
<CCol sm="4">
<CInput
id="description"
value={fields.description.value}
onChange={updateField}
maxLength="50"
/>
<small className="text-muted">{t('common.optional')}</small>
</CCol>
</CFormGroup> </CFormGroup>
<CFormGroup row> <CFormGroup row className="pb-3">
<CLabel sm="2" col htmlFor="currentPassword"> <CLabel sm="2" col htmlFor="currentPassword">
{t('user.password')} {t('user.password')}
</CLabel> </CLabel>
@@ -89,19 +116,6 @@ const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies
</CCol> </CCol>
</CFormGroup> </CFormGroup>
<CFormGroup row> <CFormGroup row>
<CLabel sm="2" col htmlFor="userRole">
{t('user.user_role')}
</CLabel>
<CCol sm="4">
<CSelect custom id="userRole" defaultValue="Admin" onChange={updateField}>
<option value="admin">Admin</option>
<option value="csr">CSR</option>
<option value="root">Root</option>
<option value="special">Special</option>
<option value="sub">Sub</option>
<option value="system">System</option>
</CSelect>
</CCol>
<CLabel sm="2" col htmlFor="notes"> <CLabel sm="2" col htmlFor="notes">
{t('user.note')} {t('user.note')}
</CLabel> </CLabel>
@@ -110,35 +124,9 @@ const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies
<small className="text-muted">{t('common.optional')}</small> <small className="text-muted">{t('common.optional')}</small>
</CCol> </CCol>
</CFormGroup> </CFormGroup>
<CFormGroup row>
<CLabel sm="2" col htmlFor="description">
{t('user.description')}
</CLabel>
<CCol sm="4">
<CInput
id="description"
value={fields.description.value}
onChange={updateField}
maxLength="50"
/>
<small className="text-muted">{t('common.optional')}</small>
</CCol>
<CLabel sm="2" col />
<CCol sm="4" />
</CFormGroup>
<CRow> <CRow>
<CCol /> <CCol />
<CCol xs={3} className="mt-2 text-right"> <CCol xs={2} className="mt-2 text-right">
<CLink
className="c-subheader-nav-link"
aria-current="page"
href={policies.accessPolicy}
target="_blank"
style={{ paddingRight: '30px' }}
hidden={policies.accessPolicy.length === 0}
>
{t('common.access_policy')}
</CLink>
<CLink <CLink
className="c-subheader-nav-link" className="c-subheader-nav-link"
aria-current="page" aria-current="page"
@@ -149,10 +137,10 @@ const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies
{t('common.password_policy')} {t('common.password_policy')}
</CLink> </CLink>
</CCol> </CCol>
<CCol xs={1} className="text-right"> <CCol xs={2} className="text-center">
<LoadingButton <LoadingButton
label={t('user.create')} label={t('user.create')}
isLoadingLabel={t('common.loading_ellipsis')} isLoadingLabel={t('user.creating')}
isLoading={loading} isLoading={loading}
action={createUser} action={createUser}
block={false} block={false}

View File

@@ -0,0 +1,140 @@
import React, { useState } from 'react';
import {
CButton,
CCol,
CForm,
CFormGroup,
CInput,
CInputGroup,
CInputGroupAppend,
CInvalidFeedback,
CLabel,
CLink,
CPopover,
CRow,
CSelect,
} from '@coreui/react';
import PropTypes from 'prop-types';
import CIcon from '@coreui/icons-react';
import NotesTable from '../NotesTable';
import LoadingButton from '../LoadingButton';
const EditMyProfile = ({ t, user, updateUserWithId, loading, saveUser, policies, addNote }) => {
const [showPassword, setShowPassword] = useState(false);
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
return (
<CForm>
<CFormGroup row>
<CLabel sm="2" col htmlFor="name">
{t('user.name')}
</CLabel>
<CCol sm="4">
<CInput id="name" value={user.name.value} onChange={updateUserWithId} maxLength="20" />
</CCol>
<CLabel sm="2" col htmlFor="description">
{t('user.description')}
</CLabel>
<CCol sm="4">
<CInput
id="description"
value={user.description.value}
onChange={updateUserWithId}
maxLength="50"
/>
</CCol>
</CFormGroup>
<CFormGroup row>
<CLabel sm="2" col htmlFor="userRole">
{t('user.user_role')}
</CLabel>
<CCol sm="4">
<CSelect custom id="userRole" onChange={updateUserWithId} value={user.userRole.value}>
<option value="admin">Admin</option>
<option value="csr">CSR</option>
<option value="root">Root</option>
<option value="special">Special</option>
<option value="sub">Sub</option>
<option value="system">System</option>
</CSelect>
</CCol>
<CLabel sm="2" col htmlFor="currentPassword">
{t('login.new_password')}
</CLabel>
<CCol sm="4">
<CInputGroup>
<CInput
type={showPassword ? 'text' : 'password'}
id="currentPassword"
value={user.currentPassword.value}
onChange={updateUserWithId}
invalid={user.currentPassword.error}
maxLength="50"
/>
<CInputGroupAppend>
<CPopover content={t('user.show_hide_password')}>
<CButton type="button" onClick={toggleShowPassword} color="secondary">
<CIcon
name={showPassword ? 'cil-envelope-open' : 'cil-envelope-closed'}
size="sm"
/>
</CButton>
</CPopover>
</CInputGroupAppend>
<CInvalidFeedback>{t('user.provide_password')}</CInvalidFeedback>
</CInputGroup>
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol sm="6">
<NotesTable
t={t}
notes={user.notes.value}
addNote={addNote}
loading={loading}
size="lg"
/>
</CCol>
</CFormGroup>
<CRow>
<CCol />
<CCol xs={1} className="mt-2 text-right">
<CLink
className="c-subheader-nav-link"
aria-current="page"
href={policies.passwordPolicy}
target="_blank"
hidden={policies.passwordPolicy.length === 0}
>
{t('common.password_policy')}
</CLink>
</CCol>
<CCol xs={1} className="text-center">
<LoadingButton
label={t('common.save')}
isLoadingLabel={t('common.saving')}
isLoading={loading}
action={saveUser}
block={false}
disabled={loading}
/>
</CCol>
</CRow>
</CForm>
);
};
EditMyProfile.propTypes = {
t: PropTypes.func.isRequired,
user: PropTypes.instanceOf(Object).isRequired,
updateUserWithId: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
saveUser: PropTypes.func.isRequired,
policies: PropTypes.instanceOf(Object).isRequired,
addNote: PropTypes.func.isRequired,
};
export default React.memo(EditMyProfile);

View File

@@ -0,0 +1,149 @@
import React, { useState } from 'react';
import {
CButton,
CCol,
CForm,
CFormGroup,
CInput,
CInputGroup,
CInputGroupAppend,
CInvalidFeedback,
CLabel,
CLink,
CPopover,
CRow,
CSelect,
CSwitch,
} from '@coreui/react';
import PropTypes from 'prop-types';
import CIcon from '@coreui/icons-react';
import NotesTable from '../NotesTable';
import LoadingButton from '../LoadingButton';
const EditUserForm = ({ t, user, updateUserWithId, loading, saveUser, policies, addNote }) => {
const [showPassword, setShowPassword] = useState(false);
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
return (
<CForm>
<CFormGroup row>
<CLabel sm="2" col htmlFor="name">
{t('user.name')}
</CLabel>
<CCol sm="4">
<CInput id="name" value={user.name.value} onChange={updateUserWithId} maxLength="20" />
</CCol>
<CLabel sm="2" col htmlFor="description">
{t('user.description')}
</CLabel>
<CCol sm="4">
<CInput
id="description"
value={user.description.value}
onChange={updateUserWithId}
maxLength="50"
/>
</CCol>
</CFormGroup>
<CFormGroup row>
<CLabel sm="2" col htmlFor="userRole">
{t('user.user_role')}
</CLabel>
<CCol sm="4">
<CSelect custom id="userRole" onChange={updateUserWithId} value={user.userRole.value}>
<option value="admin">Admin</option>
<option value="csr">CSR</option>
<option value="root">Root</option>
<option value="special">Special</option>
<option value="sub">Sub</option>
<option value="system">System</option>
</CSelect>
</CCol>
<CLabel sm="2" col htmlFor="currentPassword">
{t('login.new_password')}
</CLabel>
<CCol sm="4">
<CInputGroup>
<CInput
type={showPassword ? 'text' : 'password'}
id="currentPassword"
value={user.currentPassword.value}
onChange={updateUserWithId}
invalid={user.currentPassword.error}
maxLength="50"
/>
<CInputGroupAppend>
<CPopover content={t('user.show_hide_password')}>
<CButton type="button" onClick={toggleShowPassword} color="secondary">
<CIcon
name={showPassword ? 'cil-envelope-open' : 'cil-envelope-closed'}
size="sm"
/>
</CButton>
</CPopover>
</CInputGroupAppend>
<CInvalidFeedback>{t('user.provide_password')}</CInvalidFeedback>
</CInputGroup>
</CCol>
</CFormGroup>
<CFormGroup row>
<CLabel sm="3" col htmlFor="changePassword">
{t('user.force_password_change')}
</CLabel>
<CCol sm="1">
<CInputGroup>
<CSwitch
id="changePassword"
color="success"
defaultChecked={user.changePassword.value}
onClick={updateUserWithId}
size="lg"
/>
</CInputGroup>
</CCol>
<CCol sm="8">
<NotesTable t={t} notes={user.notes.value} addNote={addNote} loading={loading} />
</CCol>
</CFormGroup>
<CRow>
<CCol />
<CCol xs={2} className="mt-2 text-right">
<CLink
className="c-subheader-nav-link"
aria-current="page"
href={policies.passwordPolicy}
target="_blank"
hidden={policies.passwordPolicy.length === 0}
>
{t('common.password_policy')}
</CLink>
</CCol>
<CCol xs={2} className="text-center">
<LoadingButton
label={t('common.save')}
isLoadingLabel={t('common.saving')}
isLoading={loading}
action={saveUser}
block={false}
disabled={loading}
/>
</CCol>
</CRow>
</CForm>
);
};
EditUserForm.propTypes = {
t: PropTypes.func.isRequired,
user: PropTypes.instanceOf(Object).isRequired,
updateUserWithId: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
saveUser: PropTypes.func.isRequired,
policies: PropTypes.instanceOf(Object).isRequired,
addNote: PropTypes.func.isRequired,
};
export default React.memo(EditUserForm);

View File

@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CModal, CModalBody, CModalHeader, CModalTitle } from '@coreui/react';
import EditUserForm from '../EditUserForm';
const EditUserModal = ({
t,
user,
updateUserWithId,
saveUser,
loading,
policies,
show,
toggle,
addNote,
}) => (
<CModal show={show} onClose={toggle} size="xl">
<CModalHeader>
<CModalTitle>
{t('user.edit')} {user.email.value}
</CModalTitle>
</CModalHeader>
<CModalBody>
<EditUserForm
t={t}
user={user}
updateUserWithId={updateUserWithId}
saveUser={saveUser}
loading={loading}
policies={policies}
addNote={addNote}
/>
</CModalBody>
</CModal>
);
EditUserModal.propTypes = {
t: PropTypes.func.isRequired,
user: PropTypes.instanceOf(Object).isRequired,
updateUserWithId: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
saveUser: PropTypes.func.isRequired,
policies: PropTypes.instanceOf(Object).isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired,
addNote: PropTypes.func.isRequired,
};
export default React.memo(EditUserModal);

View File

@@ -0,0 +1,87 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { CDataTable, CRow, CCol, CLabel, CInput } from '@coreui/react';
import { prettyDate } from '../../utils/formatting';
import LoadingButton from '../LoadingButton';
const NotesTable = ({ t, notes, addNote, loading, size }) => {
const [currentNote, setCurrentNote] = useState('');
const columns = [
{ key: 'created', label: t('common.date'), _style: { width: '30%' } },
{ key: 'createdBy', label: t('common.created_by'), _style: { width: '20%' } },
{ key: 'note', label: t('configuration.note'), _style: { width: '50%' } },
];
const saveNote = () => {
addNote(currentNote);
};
useEffect(() => {
setCurrentNote('');
}, [notes]);
return (
<div>
<CRow>
<CLabel col sm="2">
{t('configuration.notes')} :
</CLabel>
<CCol sm={size === 'm' ? '7' : '8'}>
<CInput
id="notes-input"
name="text-input"
value={currentNote}
onChange={(e) => setCurrentNote(e.target.value)}
/>
</CCol>
<CCol sm={size === 'm' ? '3' : '2'}>
<LoadingButton
label={t('common.add')}
isLoadingLabel={t('common.adding_ellipsis')}
isLoading={loading}
action={saveNote}
disabled={loading || currentNote === ''}
/>
</CCol>
</CRow>
<CRow className="pt-3">
<CCol>
<div className="overflow-auto" style={{ height: '200px' }}>
<CDataTable
striped
responsive
border
loading={loading}
fields={columns}
items={notes || []}
noItemsView={{ noItems: t('common.no_items') }}
sorterValue={{ column: 'created', desc: 'true' }}
scopedSlots={{
created: (item) => (
<td>
{item.created && item.created !== 0 ? prettyDate(item.created) : t('common.na')}
</td>
),
}}
/>
</div>
</CCol>
</CRow>
</div>
);
};
NotesTable.propTypes = {
t: PropTypes.func.isRequired,
notes: PropTypes.instanceOf(Array).isRequired,
addNote: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
size: PropTypes.string,
};
NotesTable.defaultProps = {
size: 'm',
};
export default NotesTable;

View File

@@ -2,18 +2,17 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ReactPaginate from 'react-paginate'; import ReactPaginate from 'react-paginate';
import { import {
CButton,
CCard, CCard,
CCardHeader,
CSelect,
CCol,
CRow,
CCardBody, CCardBody,
CCardHeader,
CCol,
CDataTable, CDataTable,
CPopover, CPopover,
CButton, CRow,
CLink, CSelect,
} from '@coreui/react'; } from '@coreui/react';
import { cilBan, cilCheckCircle, cilInfo, cilPlus, cilTrash } from '@coreui/icons'; import { cilBan, cilCheckCircle, cilPencil, cilSync, cilTrash } from '@coreui/icons';
import CIcon from '@coreui/icons-react'; import CIcon from '@coreui/icons-react';
import { capitalizeFirstLetter, prettyDate } from '../../utils/formatting'; import { capitalizeFirstLetter, prettyDate } from '../../utils/formatting';
import DeleteModal from '../DeleteModal'; import DeleteModal from '../DeleteModal';
@@ -28,6 +27,9 @@ const UserListTable = ({
setPage, setPage,
deleteUser, deleteUser,
deleteLoading, deleteLoading,
toggleCreate,
toggleEdit,
refreshUsers,
}) => { }) => {
const [idToDelete, setIdToDelete] = useState(''); const [idToDelete, setIdToDelete] = useState('');
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
@@ -50,13 +52,20 @@ const UserListTable = ({
{ key: 'email', label: t('user.login_id'), _style: { width: '20%' } }, { key: 'email', label: t('user.login_id'), _style: { width: '20%' } },
{ key: 'name', label: t('user.name'), _style: { width: '20%' } }, { key: 'name', label: t('user.name'), _style: { width: '20%' } },
{ key: 'userRole', label: t('user.user_role'), _style: { width: '5%' } }, { key: 'userRole', label: t('user.user_role'), _style: { width: '5%' } },
{ key: 'description', label: t('user.description'), _style: { width: '25%' } }, { key: 'description', label: t('user.description'), _style: { width: '24%' } },
{ key: 'validated', label: t('user.validated'), _style: { width: '5%' } }, { key: 'validated', label: t('user.validated'), _style: { width: '5%' } },
{ key: 'lastLogin', label: t('user.last_login'), _style: { width: '20%' } }, { key: 'lastLogin', label: t('user.last_login'), _style: { width: '20%' } },
{ {
key: 'user_actions', key: 'user_details',
label: '', label: '',
_style: { width: '5%' }, _style: { width: '3%' },
sorter: false,
filter: false,
},
{
key: 'user_delete',
label: '',
_style: { width: '3%' },
sorter: false, sorter: false,
filter: false, filter: false,
}, },
@@ -68,17 +77,44 @@ const UserListTable = ({
<CCardHeader> <CCardHeader>
<CRow> <CRow>
<CCol /> <CCol />
<CCol xs={1}> <CCol xs={2}>
<CSelect <CRow>
custom <CCol xs={5}>
defaultValue={usersPerPage} <div className="text-right">
onChange={(e) => setUsersPerPage(e.target.value)} <CSelect
disabled={loading} custom
> defaultValue={usersPerPage}
<option value="10">10</option> onChange={(e) => setUsersPerPage(e.target.value)}
<option value="25">25</option> disabled={loading}
<option value="50">50</option> >
</CSelect> <option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</CSelect>
</div>
</CCol>
<CCol xs={5}>
<div className="text-right">
<CButton
color="primary"
variant="outline"
shape="square"
onClick={toggleCreate}
>
{t('user.create')}
</CButton>
</div>
</CCol>
<CCol xs={2}>
<div className="text-center">
<CPopover content={t('common.refresh')}>
<CButton onClick={refreshUsers} color="primary" variant="outline">
<CIcon name="cil-sync" content={cilSync} />
</CButton>
</CPopover>
</div>
</CCol>
</CRow>
</CCol> </CCol>
</CRow> </CRow>
</CCardHeader> </CCardHeader>
@@ -89,23 +125,6 @@ const UserListTable = ({
loading={loading} loading={loading}
hover hover
border border
columnHeaderSlot={{
user_actions: (
<div className="text-center">
<CPopover content={t('user.create')}>
<CLink
className="c-subheader-nav-link"
aria-current="page"
to={() => `/users/create`}
>
<CButton color="primary" variant="outline" shape="square" size="sm">
<CIcon name="cil-info" content={cilPlus} size="sm" />
</CButton>
</CLink>
</CPopover>
</div>
),
}}
scopedSlots={{ scopedSlots={{
validated: (item) => ( validated: (item) => (
<td className="text-center"> <td className="text-center">
@@ -120,35 +139,33 @@ const UserListTable = ({
userRole: (item) => ( userRole: (item) => (
<td>{item.userRole ? capitalizeFirstLetter(item.userRole) : ''}</td> <td>{item.userRole ? capitalizeFirstLetter(item.userRole) : ''}</td>
), ),
user_actions: (item) => ( user_details: (item) => (
<td className="py-2 text-center"> <td className="py-2 text-center">
<CRow> <CPopover content={t('common.edit')}>
<CCol> <CButton
<CPopover content={t('configuration.details')}> color="primary"
<CLink variant="outline"
className="c-subheader-nav-link" shape="square"
aria-current="page" size="sm"
to={() => `/users/${item.Id}`} onClick={() => toggleEdit(item.Id)}
> >
<CButton color="primary" variant="outline" shape="square" size="sm"> <CIcon name="cil-pencil" content={cilPencil} size="sm" />
<CIcon name="cil-info" content={cilInfo} size="sm" /> </CButton>
</CButton> </CPopover>
</CLink> </td>
</CPopover> ),
</CCol> user_delete: (item) => (
<CCol> <td className="py-2 text-center">
<CPopover content={t('common.delete')}> <CPopover content={t('common.delete')}>
<CButton <CButton
onClick={() => handleDeleteClick(item.Id)} onClick={() => handleDeleteClick(item.Id)}
color="primary" color="primary"
variant="outline" variant="outline"
size="sm" size="sm"
> >
<CIcon content={cilTrash} size="sm" /> <CIcon content={cilTrash} size="sm" />
</CButton> </CButton>
</CPopover> </CPopover>
</CCol>
</CRow>
</td> </td>
), ),
}} }}
@@ -196,6 +213,9 @@ UserListTable.propTypes = {
setPage: PropTypes.func.isRequired, setPage: PropTypes.func.isRequired,
deleteUser: PropTypes.func.isRequired, deleteUser: PropTypes.func.isRequired,
deleteLoading: PropTypes.bool.isRequired, deleteLoading: PropTypes.bool.isRequired,
toggleCreate: PropTypes.func.isRequired,
toggleEdit: PropTypes.func.isRequired,
refreshUsers: PropTypes.func.isRequired,
}; };
export default React.memo(UserListTable); export default React.memo(UserListTable);

View File

@@ -1,139 +0,0 @@
import React, { useState } from 'react';
import {
CButton,
CCard,
CCardBody,
CCardHeader,
CCol,
CForm,
CFormGroup,
CInput,
CInputGroup,
CInputGroupAppend,
CInvalidFeedback,
CLabel,
CPopover,
CRow,
CSelect,
CSwitch,
} from '@coreui/react';
import PropTypes from 'prop-types';
import LoadingButton from 'components/LoadingButton';
import CIcon from '@coreui/icons-react';
const CreateUserForm = ({ t, user, updateUserWithId, loading, saveUser }) => {
const [showPassword, setShowPassword] = useState(false);
const toggleShowPassword = () => {
setShowPassword(!showPassword);
};
return (
<CCard>
<CCardHeader>{t('common.details')}</CCardHeader>
<CCardBody>
<CForm>
<CFormGroup row>
<CCol>
<CLabel htmlFor="email">{t('user.email_address')}</CLabel>
<p className="form-control-static mt-2">{user.email.value}</p>
</CCol>
<CCol>
<CLabel htmlFor="name">{t('user.name')}</CLabel>
<CInput
id="name"
value={user.name.value}
onChange={updateUserWithId}
maxLength="20"
/>
</CCol>
<CCol>
<CLabel htmlFor="changePassword">{t('user.force_password_change')}</CLabel>
<CInputGroup>
<CSwitch
id="changePassword"
color="success"
defaultChecked={user.changePassword.value}
onClick={updateUserWithId}
size="lg"
/>
</CInputGroup>
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol>
<CLabel htmlFor="userRole">{t('user.user_role')}</CLabel>
<CSelect custom id="userRole" defaultValue="Admin" onChange={updateUserWithId}>
<option value="admin">Admin</option>
<option value="csr">CSR</option>
<option value="root">Root</option>
<option value="special">Special</option>
<option value="sub">Sub</option>
<option value="system">System</option>
</CSelect>
</CCol>
<CCol>
<CLabel htmlFor="newPascurrentPasswordsword">{t('login.new_password')}</CLabel>
<CInputGroup>
<CInput
type={showPassword ? 'text' : 'password'}
id="currentPassword"
value={user.currentPassword.value}
onChange={updateUserWithId}
invalid={user.currentPassword.error}
maxLength="50"
/>
<CInputGroupAppend>
<CPopover content={t('user.show_hide_password')}>
<CButton type="button" onClick={toggleShowPassword} color="secondary">
<CIcon
name={showPassword ? 'cil-envelope-open' : 'cil-envelope-closed'}
size="sm"
/>
</CButton>
</CPopover>
</CInputGroupAppend>
<CInvalidFeedback>{t('user.provide_password')}</CInvalidFeedback>
</CInputGroup>
</CCol>
</CFormGroup>
<CFormGroup row>
<CCol>
<CLabel htmlFor="description">{t('user.description')}</CLabel>
<CInput
id="description"
value={user.description.value}
onChange={updateUserWithId}
maxLength="50"
/>
</CCol>
<CCol />
</CFormGroup>
<CRow>
<CCol />
<CCol xs={3} className="text-right">
<LoadingButton
label={t('common.save')}
isLoadingLabel={t('common.saving')}
isLoading={loading}
action={saveUser}
block={false}
disabled={loading}
/>
</CCol>
</CRow>
</CForm>
</CCardBody>
</CCard>
);
};
CreateUserForm.propTypes = {
t: PropTypes.func.isRequired,
user: PropTypes.instanceOf(Object).isRequired,
updateUserWithId: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
saveUser: PropTypes.func.isRequired,
};
export default React.memo(CreateUserForm);

View File

@@ -18,7 +18,7 @@ export default (initialState) => {
(key, newValues) => { (key, newValues) => {
setUser({ setUser({
...user, ...user,
[user]: { [key]: {
...user[key], ...user[key],
...newValues, ...newValues,
}, },

View File

@@ -10,7 +10,9 @@ export { default as UserListTable } from './components/UserListTable';
export { default as CreateUserForm } from './components/CreateUserForm'; export { default as CreateUserForm } from './components/CreateUserForm';
export { default as LoadingButton } from './components/LoadingButton'; export { default as LoadingButton } from './components/LoadingButton';
export { default as ConfirmFooter } from './components/ConfirmFooter'; export { default as ConfirmFooter } from './components/ConfirmFooter';
export { default as UserProfileCard } from './components/UserProfileCard'; export { default as EditUserModal } from './components/EditUserModal';
export { default as EditUserForm } from './components/EditUserForm';
export { default as EditMyProfile } from './components/EditMyProfile';
// Pages // Pages
export { default as LoginPage } from './components/LoginPage'; export { default as LoginPage } from './components/LoginPage';

View File

@@ -62,7 +62,7 @@ const Header = ({
<CHeaderNav className="px-1"> <CHeaderNav className="px-1">
<CDropdown inNav className="c-header-nav-items mx-2" direction="down"> <CDropdown inNav className="c-header-nav-items mx-2" direction="down">
<CDropdownToggle className="c-header-nav-link" caret={false}> <CDropdownToggle className="c-header-nav-link" caret={false}>
<div className="c-avatar"> <div className="c-avatar avatar">
<ImgWithFallback <ImgWithFallback
src={user.avatar && user.avatar !== '' ? user.avatar : '/'} src={user.avatar && user.avatar !== '' ? user.avatar : '/'}
fallback={() => emailToName(user.email)} fallback={() => emailToName(user.email)}
@@ -70,11 +70,11 @@ const Header = ({
</div> </div>
</CDropdownToggle> </CDropdownToggle>
<CDropdownMenu className="pt-0" placement="bottom-end"> <CDropdownMenu className="pt-0" placement="bottom-end">
<CDropdownItem> <CDropdownItem to={() => '/myprofile'}>
<div className="px-3">My Account</div> <div className="px-3">{t('user.my_profile')}</div>
</CDropdownItem> </CDropdownItem>
<CDropdownItem onClick={() => logout(authToken, endpoints.ucentralsec)}> <CDropdownItem onClick={() => logout(authToken, endpoints.ucentralsec)}>
<strong className="px-3">Logout</strong> <strong className="px-3">{t('common.logout')}</strong>
<CIcon name="cilAccountLogout" content={cilAccountLogout} /> <CIcon name="cilAccountLogout" content={cilAccountLogout} />
</CDropdownItem> </CDropdownItem>
</CDropdownMenu> </CDropdownMenu>