mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs.git
synced 2025-11-03 04:08:05 +00:00
New login, create user form, user list components
This commit is contained in:
@@ -18,7 +18,6 @@ import {
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import LoadingButton from 'components/LoadingButton';
|
import LoadingButton from 'components/LoadingButton';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
import styles from './index.module.scss';
|
|
||||||
|
|
||||||
const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies }) => {
|
const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies }) => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
@@ -129,7 +128,7 @@ const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies
|
|||||||
</CFormGroup>
|
</CFormGroup>
|
||||||
<CRow>
|
<CRow>
|
||||||
<CCol />
|
<CCol />
|
||||||
<CCol xs={3} className={styles.linksColumn}>
|
<CCol xs={3} className="mt-2 text-right">
|
||||||
<CLink
|
<CLink
|
||||||
className="c-subheader-nav-link"
|
className="c-subheader-nav-link"
|
||||||
aria-current="page"
|
aria-current="page"
|
||||||
@@ -150,13 +149,13 @@ const CreateUserForm = ({ t, fields, updateField, createUser, loading, policies
|
|||||||
{t('common.password_policy')}
|
{t('common.password_policy')}
|
||||||
</CLink>
|
</CLink>
|
||||||
</CCol>
|
</CCol>
|
||||||
<CCol xs={2}>
|
<CCol xs={1} className="text-right">
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
label={t('user.create')}
|
label={t('user.create')}
|
||||||
isLoadingLabel={t('common.loading_ellipsis')}
|
isLoadingLabel={t('common.loading_ellipsis')}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
action={createUser}
|
action={createUser}
|
||||||
block
|
block={false}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
</CCol>
|
</CCol>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
.linksColumn {
|
|
||||||
text-align: right;
|
|
||||||
padding-top: 5px;
|
|
||||||
}
|
|
||||||
19
src/components/ImgWithFallback/index.js
Normal file
19
src/components/ImgWithFallback/index.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { CImg } from '@coreui/react';
|
||||||
|
|
||||||
|
const ImgWithFallback = ({ src, fallback }) => {
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div className="avatar bg-secondary">{fallback()}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <CImg className="avatar" src={src} onError={() => setError(true)} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
ImgWithFallback.propTypes = {
|
||||||
|
src: PropTypes.string.isRequired,
|
||||||
|
fallback: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
export default React.memo(ImgWithFallback);
|
||||||
@@ -42,4 +42,4 @@ LoadingButton.defaultProps = {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoadingButton;
|
export default React.memo(LoadingButton);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
CPopover,
|
CPopover,
|
||||||
CAlert,
|
CAlert,
|
||||||
CInvalidFeedback,
|
CInvalidFeedback,
|
||||||
|
CLink,
|
||||||
} from '@coreui/react';
|
} from '@coreui/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
@@ -29,6 +30,7 @@ const ChangePasswordForm = ({
|
|||||||
updateField,
|
updateField,
|
||||||
changePasswordResponse,
|
changePasswordResponse,
|
||||||
cancelPasswordChange,
|
cancelPasswordChange,
|
||||||
|
policies,
|
||||||
}) => (
|
}) => (
|
||||||
<CForm onKeyDown={(e) => onKeyDown(e, signIn)}>
|
<CForm onKeyDown={(e) => onKeyDown(e, signIn)}>
|
||||||
<h1>
|
<h1>
|
||||||
@@ -88,13 +90,31 @@ const ChangePasswordForm = ({
|
|||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
<CRow>
|
<CRow>
|
||||||
<CCol xs="6">
|
<CCol>
|
||||||
<CButton color="primary" className="px-4" onClick={() => signIn(true)} disabled={loading}>
|
<CButton color="primary" className="px-4" onClick={() => signIn(true)} disabled={loading}>
|
||||||
{loading ? t('login.changing_password') : t('login.change_password')}
|
{loading ? t('login.changing_password') : t('login.change_password')}
|
||||||
<CSpinner hidden={!loading} color="light" component="span" size="sm" />
|
<CSpinner hidden={!loading} color="light" component="span" size="sm" />
|
||||||
</CButton>
|
</CButton>
|
||||||
|
<CLink
|
||||||
|
className="c-subheader-nav-link px-3"
|
||||||
|
aria-current="page"
|
||||||
|
href={policies.accessPolicy}
|
||||||
|
target="_blank"
|
||||||
|
hidden={policies.accessPolicy.length === 0}
|
||||||
|
>
|
||||||
|
{t('common.access_policy')}
|
||||||
|
</CLink>
|
||||||
|
<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>
|
||||||
<CCol xs="6" className={styles.forgotPassword}>
|
<CCol xs="5" className={styles.forgotPassword}>
|
||||||
<CButton variant="ghost" color="primary" onClick={cancelPasswordChange}>
|
<CButton variant="ghost" color="primary" onClick={cancelPasswordChange}>
|
||||||
{t('common.cancel')}
|
{t('common.cancel')}
|
||||||
</CButton>
|
</CButton>
|
||||||
@@ -113,6 +133,7 @@ ChangePasswordForm.propTypes = {
|
|||||||
updateField: PropTypes.func.isRequired,
|
updateField: PropTypes.func.isRequired,
|
||||||
changePasswordResponse: PropTypes.instanceOf(Object).isRequired,
|
changePasswordResponse: PropTypes.instanceOf(Object).isRequired,
|
||||||
cancelPasswordChange: PropTypes.func.isRequired,
|
cancelPasswordChange: PropTypes.func.isRequired,
|
||||||
|
policies: PropTypes.instanceOf(Object).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ChangePasswordForm;
|
export default React.memo(ChangePasswordForm);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
CPopover,
|
CPopover,
|
||||||
CAlert,
|
CAlert,
|
||||||
CInvalidFeedback,
|
CInvalidFeedback,
|
||||||
|
CLink,
|
||||||
} from '@coreui/react';
|
} from '@coreui/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
@@ -29,6 +30,7 @@ const ForgotPasswordForm = ({
|
|||||||
forgotResponse,
|
forgotResponse,
|
||||||
updateField,
|
updateField,
|
||||||
toggleForgotPassword,
|
toggleForgotPassword,
|
||||||
|
policies,
|
||||||
}) => (
|
}) => (
|
||||||
<CForm onKeyDown={(e) => onKeyDown(e, sendForgotPasswordEmail)}>
|
<CForm onKeyDown={(e) => onKeyDown(e, sendForgotPasswordEmail)}>
|
||||||
<h1>
|
<h1>
|
||||||
@@ -87,7 +89,7 @@ const ForgotPasswordForm = ({
|
|||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
<CRow>
|
<CRow>
|
||||||
<CCol xs="6">
|
<CCol>
|
||||||
<CButton
|
<CButton
|
||||||
color="primary"
|
color="primary"
|
||||||
className="px-4"
|
className="px-4"
|
||||||
@@ -97,8 +99,26 @@ const ForgotPasswordForm = ({
|
|||||||
{loading ? t('login.sending_ellipsis') : t('login.send_forgot')}
|
{loading ? t('login.sending_ellipsis') : t('login.send_forgot')}
|
||||||
<CSpinner hidden={!loading} color="light" component="span" size="sm" />
|
<CSpinner hidden={!loading} color="light" component="span" size="sm" />
|
||||||
</CButton>
|
</CButton>
|
||||||
|
<CLink
|
||||||
|
className="c-subheader-nav-link px-3"
|
||||||
|
aria-current="page"
|
||||||
|
href={policies.accessPolicy}
|
||||||
|
target="_blank"
|
||||||
|
hidden={policies.accessPolicy.length === 0}
|
||||||
|
>
|
||||||
|
{t('common.access_policy')}
|
||||||
|
</CLink>
|
||||||
|
<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>
|
||||||
<CCol xs="6" className={styles.forgotPassword}>
|
<CCol xs="5" className={styles.forgotPassword}>
|
||||||
<CButton variant="ghost" color="primary" onClick={toggleForgotPassword}>
|
<CButton variant="ghost" color="primary" onClick={toggleForgotPassword}>
|
||||||
{t('common.back_to_login')}
|
{t('common.back_to_login')}
|
||||||
</CButton>
|
</CButton>
|
||||||
@@ -117,6 +137,7 @@ ForgotPasswordForm.propTypes = {
|
|||||||
fields: PropTypes.instanceOf(Object).isRequired,
|
fields: PropTypes.instanceOf(Object).isRequired,
|
||||||
updateField: PropTypes.func.isRequired,
|
updateField: PropTypes.func.isRequired,
|
||||||
toggleForgotPassword: PropTypes.func.isRequired,
|
toggleForgotPassword: PropTypes.func.isRequired,
|
||||||
|
policies: PropTypes.instanceOf(Object).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ForgotPasswordForm;
|
export default React.memo(ForgotPasswordForm);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
CPopover,
|
CPopover,
|
||||||
CAlert,
|
CAlert,
|
||||||
CInvalidFeedback,
|
CInvalidFeedback,
|
||||||
|
CLink,
|
||||||
} from '@coreui/react';
|
} from '@coreui/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
@@ -29,6 +30,7 @@ const LoginForm = ({
|
|||||||
updateField,
|
updateField,
|
||||||
loginResponse,
|
loginResponse,
|
||||||
toggleForgotPassword,
|
toggleForgotPassword,
|
||||||
|
policies,
|
||||||
}) => (
|
}) => (
|
||||||
<CForm onKeyDown={(e) => onKeyDown(e, signIn)}>
|
<CForm onKeyDown={(e) => onKeyDown(e, signIn)}>
|
||||||
<h1>
|
<h1>
|
||||||
@@ -105,13 +107,31 @@ const LoginForm = ({
|
|||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
<CRow>
|
<CRow>
|
||||||
<CCol xs="6">
|
<CCol>
|
||||||
<CButton color="primary" className="px-4" onClick={signIn} disabled={loading}>
|
<CButton color="primary" className="px-4" onClick={signIn} disabled={loading}>
|
||||||
{loading ? t('login.logging_in') : t('login.login')}
|
{loading ? t('login.logging_in') : t('login.login')}
|
||||||
<CSpinner hidden={!loading} color="light" component="span" size="sm" />
|
<CSpinner hidden={!loading} color="light" component="span" size="sm" />
|
||||||
</CButton>
|
</CButton>
|
||||||
|
<CLink
|
||||||
|
className="c-subheader-nav-link px-3"
|
||||||
|
aria-current="page"
|
||||||
|
href={policies.accessPolicy}
|
||||||
|
target="_blank"
|
||||||
|
hidden={policies.accessPolicy.length === 0}
|
||||||
|
>
|
||||||
|
{t('common.access_policy')}
|
||||||
|
</CLink>
|
||||||
|
<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>
|
||||||
<CCol xs="6" className={styles.forgotPassword}>
|
<CCol xs="5" className="text-right">
|
||||||
<CButton variant="ghost" color="primary" onClick={toggleForgotPassword}>
|
<CButton variant="ghost" color="primary" onClick={toggleForgotPassword}>
|
||||||
{t('common.forgot_password')}
|
{t('common.forgot_password')}
|
||||||
</CButton>
|
</CButton>
|
||||||
@@ -130,6 +150,7 @@ LoginForm.propTypes = {
|
|||||||
updateField: PropTypes.func.isRequired,
|
updateField: PropTypes.func.isRequired,
|
||||||
loginResponse: PropTypes.instanceOf(Object).isRequired,
|
loginResponse: PropTypes.instanceOf(Object).isRequired,
|
||||||
toggleForgotPassword: PropTypes.func.isRequired,
|
toggleForgotPassword: PropTypes.func.isRequired,
|
||||||
|
policies: PropTypes.instanceOf(Object).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoginForm;
|
export default React.memo(LoginForm);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const LoginPage = ({
|
|||||||
sendForgotPasswordEmail,
|
sendForgotPasswordEmail,
|
||||||
changePasswordResponse,
|
changePasswordResponse,
|
||||||
cancelPasswordChange,
|
cancelPasswordChange,
|
||||||
|
policies,
|
||||||
}) => {
|
}) => {
|
||||||
const getForm = () => {
|
const getForm = () => {
|
||||||
if (!isLogin) {
|
if (!isLogin) {
|
||||||
@@ -37,6 +38,7 @@ const LoginPage = ({
|
|||||||
forgotResponse={forgotResponse}
|
forgotResponse={forgotResponse}
|
||||||
toggleForgotPassword={toggleForgotPassword}
|
toggleForgotPassword={toggleForgotPassword}
|
||||||
sendForgotPasswordEmail={sendForgotPasswordEmail}
|
sendForgotPasswordEmail={sendForgotPasswordEmail}
|
||||||
|
policies={policies}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,7 @@ const LoginPage = ({
|
|||||||
updateField={updateField}
|
updateField={updateField}
|
||||||
changePasswordResponse={changePasswordResponse}
|
changePasswordResponse={changePasswordResponse}
|
||||||
cancelPasswordChange={cancelPasswordChange}
|
cancelPasswordChange={cancelPasswordChange}
|
||||||
|
policies={policies}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -66,6 +69,7 @@ const LoginPage = ({
|
|||||||
updateField={updateField}
|
updateField={updateField}
|
||||||
loginResponse={loginResponse}
|
loginResponse={loginResponse}
|
||||||
toggleForgotPassword={toggleForgotPassword}
|
toggleForgotPassword={toggleForgotPassword}
|
||||||
|
policies={policies}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -81,7 +85,7 @@ const LoginPage = ({
|
|||||||
alt="OpenWifi"
|
alt="OpenWifi"
|
||||||
/>
|
/>
|
||||||
<CCardGroup>
|
<CCardGroup>
|
||||||
<CCard className="p-4">
|
<CCard className="p-4" color={isLogin && isPasswordChange ? 'secondary' : ''}>
|
||||||
<CCardBody>{getForm()}</CCardBody>
|
<CCardBody>{getForm()}</CCardBody>
|
||||||
</CCard>
|
</CCard>
|
||||||
</CCardGroup>
|
</CCardGroup>
|
||||||
@@ -108,6 +112,7 @@ LoginPage.propTypes = {
|
|||||||
sendForgotPasswordEmail: PropTypes.func.isRequired,
|
sendForgotPasswordEmail: PropTypes.func.isRequired,
|
||||||
changePasswordResponse: PropTypes.instanceOf(Object).isRequired,
|
changePasswordResponse: PropTypes.instanceOf(Object).isRequired,
|
||||||
cancelPasswordChange: PropTypes.func.isRequired,
|
cancelPasswordChange: PropTypes.func.isRequired,
|
||||||
|
policies: PropTypes.instanceOf(Object).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(LoginPage);
|
export default React.memo(LoginPage);
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import {
|
|||||||
CDataTable,
|
CDataTable,
|
||||||
CPopover,
|
CPopover,
|
||||||
CButton,
|
CButton,
|
||||||
|
CLink,
|
||||||
} from '@coreui/react';
|
} from '@coreui/react';
|
||||||
import { cilBan, cilCheckCircle, cilTrash } from '@coreui/icons';
|
import { cilBan, cilCheckCircle, cilInfo, cilPlus, 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';
|
||||||
@@ -49,13 +50,13 @@ 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: '26%' } },
|
{ key: 'description', label: t('user.description'), _style: { width: '25%' } },
|
||||||
{ 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_actions',
|
||||||
label: '',
|
label: '',
|
||||||
_style: { width: '4%' },
|
_style: { width: '5%' },
|
||||||
sorter: false,
|
sorter: false,
|
||||||
filter: false,
|
filter: false,
|
||||||
},
|
},
|
||||||
@@ -88,9 +89,26 @@ 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="centeredColumn">
|
<td className="text-center">
|
||||||
<CPopover
|
<CPopover
|
||||||
content={item.validated ? t('user.validated') : t('user.not_validated')}
|
content={item.validated ? t('user.validated') : t('user.not_validated')}
|
||||||
>
|
>
|
||||||
@@ -103,17 +121,34 @@ const UserListTable = ({
|
|||||||
<td>{item.userRole ? capitalizeFirstLetter(item.userRole) : ''}</td>
|
<td>{item.userRole ? capitalizeFirstLetter(item.userRole) : ''}</td>
|
||||||
),
|
),
|
||||||
user_actions: (item) => (
|
user_actions: (item) => (
|
||||||
<td className="py-2">
|
<td className="py-2 text-center">
|
||||||
<CPopover content={t('common.delete')}>
|
<CRow>
|
||||||
<CButton
|
<CCol>
|
||||||
onClick={() => handleDeleteClick(item.Id)}
|
<CPopover content={t('configuration.details')}>
|
||||||
color="primary"
|
<CLink
|
||||||
variant="outline"
|
className="c-subheader-nav-link"
|
||||||
size="sm"
|
aria-current="page"
|
||||||
>
|
to={() => `/users/${item.Id}`}
|
||||||
<CIcon content={cilTrash} size="sm" />
|
>
|
||||||
</CButton>
|
<CButton color="primary" variant="outline" shape="square" size="sm">
|
||||||
</CPopover>
|
<CIcon name="cil-info" content={cilInfo} size="sm" />
|
||||||
|
</CButton>
|
||||||
|
</CLink>
|
||||||
|
</CPopover>
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<CPopover content={t('common.delete')}>
|
||||||
|
<CButton
|
||||||
|
onClick={() => handleDeleteClick(item.Id)}
|
||||||
|
color="primary"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<CIcon content={cilTrash} size="sm" />
|
||||||
|
</CButton>
|
||||||
|
</CPopover>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
</td>
|
</td>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
|||||||
139
src/components/UserProfileCard/index.js
Normal file
139
src/components/UserProfileCard/index.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
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);
|
||||||
31
src/hooks/useUser/index.js
Normal file
31
src/hooks/useUser/index.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export default (initialState) => {
|
||||||
|
const [user, setUser] = useState(initialState);
|
||||||
|
|
||||||
|
return [
|
||||||
|
user,
|
||||||
|
(e) => {
|
||||||
|
setUser({
|
||||||
|
...user,
|
||||||
|
[e.target.id]: {
|
||||||
|
...user[e.target.id],
|
||||||
|
value: e.target.value,
|
||||||
|
error: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(key, newValues) => {
|
||||||
|
setUser({
|
||||||
|
...user,
|
||||||
|
[user]: {
|
||||||
|
...user[key],
|
||||||
|
...newValues,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(newUser) => {
|
||||||
|
setUser(newUser);
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
@@ -10,9 +10,11 @@ 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';
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
export { default as LoginPage } from './components/LoginPage';
|
export { default as LoginPage } from './components/LoginPage';
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
export { default as useFormFields } from './hooks/FormFields';
|
export { default as useFormFields } from './hooks/useFormFields';
|
||||||
|
export { default as useUser } from './hooks/useUser';
|
||||||
|
|||||||
@@ -6,15 +6,29 @@ import {
|
|||||||
CHeaderNav,
|
CHeaderNav,
|
||||||
CSubheader,
|
CSubheader,
|
||||||
CBreadcrumbRouter,
|
CBreadcrumbRouter,
|
||||||
CLink,
|
CDropdown,
|
||||||
CPopover,
|
CDropdownToggle,
|
||||||
|
CDropdownMenu,
|
||||||
|
CDropdownItem,
|
||||||
} from '@coreui/react';
|
} from '@coreui/react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
import { cilAccountLogout } from '@coreui/icons';
|
import { cilAccountLogout } from '@coreui/icons';
|
||||||
import LanguageSwitcher from '../../components/LanguageSwitcher';
|
import LanguageSwitcher from '../../components/LanguageSwitcher';
|
||||||
|
import ImgWithFallback from '../../components/ImgWithFallback';
|
||||||
|
import { emailToName } from '../../utils/formatting';
|
||||||
|
|
||||||
const Header = ({ showSidebar, setShowSidebar, routes, t, i18n, logout, authToken, endpoints }) => {
|
const Header = ({
|
||||||
|
showSidebar,
|
||||||
|
setShowSidebar,
|
||||||
|
routes,
|
||||||
|
t,
|
||||||
|
i18n,
|
||||||
|
logout,
|
||||||
|
authToken,
|
||||||
|
endpoints,
|
||||||
|
user,
|
||||||
|
}) => {
|
||||||
const [translatedRoutes, setTranslatedRoutes] = useState(routes);
|
const [translatedRoutes, setTranslatedRoutes] = useState(routes);
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
@@ -45,17 +59,26 @@ const Header = ({ showSidebar, setShowSidebar, routes, t, i18n, logout, authToke
|
|||||||
<LanguageSwitcher i18n={i18n} />
|
<LanguageSwitcher i18n={i18n} />
|
||||||
</CHeaderNav>
|
</CHeaderNav>
|
||||||
|
|
||||||
<CHeaderNav className="px-3">
|
<CHeaderNav className="px-1">
|
||||||
<CPopover content={t('common.logout')}>
|
<CDropdown inNav className="c-header-nav-items mx-2" direction="down">
|
||||||
<CLink className="c-subheader-nav-link">
|
<CDropdownToggle className="c-header-nav-link" caret={false}>
|
||||||
<CIcon
|
<div className="c-avatar">
|
||||||
name="cilAccountLogout"
|
<ImgWithFallback
|
||||||
content={cilAccountLogout}
|
src={user.avatar && user.avatar !== '' ? user.avatar : '/'}
|
||||||
size="2xl"
|
fallback={() => emailToName(user.email)}
|
||||||
onClick={() => logout(authToken, endpoints.ucentralsec)}
|
/>
|
||||||
/>
|
</div>
|
||||||
</CLink>
|
</CDropdownToggle>
|
||||||
</CPopover>
|
<CDropdownMenu className="pt-0" placement="bottom-end">
|
||||||
|
<CDropdownItem>
|
||||||
|
<div className="px-3">My Account</div>
|
||||||
|
</CDropdownItem>
|
||||||
|
<CDropdownItem onClick={() => logout(authToken, endpoints.ucentralsec)}>
|
||||||
|
<strong className="px-3">Logout</strong>
|
||||||
|
<CIcon name="cilAccountLogout" content={cilAccountLogout} />
|
||||||
|
</CDropdownItem>
|
||||||
|
</CDropdownMenu>
|
||||||
|
</CDropdown>
|
||||||
</CHeaderNav>
|
</CHeaderNav>
|
||||||
|
|
||||||
<CSubheader className="px-3 justify-content-between">
|
<CSubheader className="px-3 justify-content-between">
|
||||||
@@ -77,6 +100,7 @@ Header.propTypes = {
|
|||||||
logout: PropTypes.func.isRequired,
|
logout: PropTypes.func.isRequired,
|
||||||
authToken: PropTypes.string.isRequired,
|
authToken: PropTypes.string.isRequired,
|
||||||
endpoints: PropTypes.instanceOf(Object).isRequired,
|
endpoints: PropTypes.instanceOf(Object).isRequired,
|
||||||
|
user: PropTypes.instanceOf(Object).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(Header);
|
export default React.memo(Header);
|
||||||
|
|||||||
@@ -17,3 +17,15 @@ export const prettyDate = (dateString) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const capitalizeFirstLetter = (string) => string.charAt(0).toUpperCase() + string.slice(1);
|
export const capitalizeFirstLetter = (string) => string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
|
||||||
|
export const emailToName = (email) => {
|
||||||
|
if (email) {
|
||||||
|
const pre = email.split('@')[0];
|
||||||
|
if (!pre.includes('.')) {
|
||||||
|
return `${pre.substring(0, 2).toUpperCase()}`;
|
||||||
|
}
|
||||||
|
const parts = pre.split('.');
|
||||||
|
return `${parts[0].charAt(0).toUpperCase()}${parts[1].charAt(0).toUpperCase()}`;
|
||||||
|
}
|
||||||
|
return 'N/A';
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user