Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin Dashboard Components (Sidebar) #1316

Draft
wants to merge 10 commits into
base: development
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/client/app/components/HeaderButtonsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export default function HeaderButtonsComponent() {
disabled={state.shouldAdminButtonDisabled}
tag={Link}
to="/admin">
<FormattedMessage id='admin.panel' />
<FormattedMessage id='admin.settings' />
</DropdownItem>
<DropdownItem divider />
<DropdownItem
Expand Down
40 changes: 17 additions & 23 deletions src/client/app/components/admin/AdminComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,43 @@ import { FormattedMessage } from 'react-intl';
import TooltipHelpComponent from '../../components/TooltipHelpComponent';
import TooltipMarkerComponent from '../TooltipMarkerComponent';
import PreferencesComponent from './PreferencesComponent';
import ManageUsersLinkButtonComponent from './users/ManageUsersLinkButtonComponent';
import AdminSideBar from './AdminSideBar';

/**
* React component that defines the admin page
* @returns Admin page element
*/
export default function AdminComponent() {

const bottomPaddingStyle: React.CSSProperties = {
paddingBottom: '15px'
};
const [selectedPreference, setSelectedPreference] = React.useState<string>('graph');


const sectionTitleStyle: React.CSSProperties = {
fontWeight: 'bold',
margin: 0,
paddingBottom: '5px'
};
const titleStyle: React.CSSProperties = {
textAlign: 'center'
textAlign: 'start',
paddingLeft: '10px',
margin: 0
};
const tooltipStyle = {
display: 'inline',
fontSize: '50%'
};
return (
<div>
<TooltipHelpComponent page='admin' />
<div className='container-fluid'>
<h2 style={titleStyle}>
<FormattedMessage id='admin.panel' />
<div className='flexGrowOne d-flex flex-column'>
<div className='container-fluid flexGrowOne d-flex flex-column'>

<TooltipHelpComponent page='admin' />
<h2 style={titleStyle} className='p-2'>
<FormattedMessage id='admin.settings' />
<div style={tooltipStyle}>
<TooltipMarkerComponent page='admin' helpTextId='help.admin.header' />
</div>
</h2>
<div className='row'>
<div className='col-12 col-lg-6'>
<div style={bottomPaddingStyle}>
<p style={sectionTitleStyle}><FormattedMessage id='manage' />:</p>
<div>
<ManageUsersLinkButtonComponent />
</div>
<div className='row border flexGrowOne'>
<AdminSideBar onSelectPreference={setSelectedPreference} selectedPreference={selectedPreference}/>
<div className='col-9'>
<div className='col-12 col-lg-6 p-3 w-100'>
<PreferencesComponent selectedPreference={selectedPreference}/>
</div>
<PreferencesComponent />
</div>
</div>
</div>
Expand Down
54 changes: 54 additions & 0 deletions src/client/app/components/admin/AdminSideBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as React from 'react';
import translate from '../../utils/translate';

interface SidebarProps {
onSelectPreference: (preference: string) => void,
selectedPreference: string;
}

/**
* Admin navigation side bar
* @param props Props for side bar
* @returns Admin navigation side bar
*/
export default function AdminSideBar(props: SidebarProps): React.JSX.Element {
return (
<div className='col-3 border-end m-0 p-0'>
<div className="list-group position-sticky top-0">
<button
type="button"
className={`${props.selectedPreference === 'graph' ? 'btn btn-primary' : 'btn btn-light'}`}
onClick={() => props.onSelectPreference('graph')}
>
{translate('graph')}
</button>
<button
type="button"
className={`${props.selectedPreference === 'meter' ? 'btn btn-primary' : 'btn btn-light'}`}
onClick={() => props.onSelectPreference('meter')}
>
{translate('meter')}
</button>
<button
type="button"
className={`${props.selectedPreference === 'misc' ? 'btn btn-primary' : 'btn btn-light'}`}
onClick={() => props.onSelectPreference('misc')}
>
{translate('misc')}
</button>
<button
type="button"
className={`${props.selectedPreference === 'all' ? 'btn btn-primary' : 'btn btn-light'}`}
onClick={() => props.onSelectPreference('all')}
>
{translate('all.settings')}
</button>
</div>

</div>
);
}
167 changes: 167 additions & 0 deletions src/client/app/components/admin/CreateUserModalComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as React from 'react';
import { useState } from 'react';
import { Alert, Button, Col, Container, FormFeedback, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader, Row } from 'reactstrap';
import { FormattedMessage } from 'react-intl';
import { UserRole } from '../../types/items';
import { userApi } from '../../redux/api/userApi';
import { NewUser } from '../../types/items';
import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
import translate from '../../utils/translate';

/**
* Defines the create user modal form
* @returns CreateUserModal component
*/
export default function CreateUserModal() {
const [showModal, setShowModal] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [passwordMatch, setPasswordMatch] = useState(true);
const [role, setRole] = useState('');
const [createUser] = userApi.useCreateUserMutation();

const handleShowModal = () => setShowModal(true);
const handleCloseModal = () => {
setShowModal(false);
resetForm();
};

const resetForm = () => {
setEmail('');
setPassword('');
setConfirmPassword('');
setRole('');
setPasswordMatch(true);
};

const handleSubmit = async () => {
if (password === confirmPassword) {
setPasswordMatch(true);
const userRole: UserRole = UserRole[role as keyof typeof UserRole];
const newUser: NewUser = { email, role: userRole, password };
createUser(newUser)
.unwrap()
.then(() => {
showSuccessNotification(translate('users.successfully.create.user'));
handleCloseModal();
})
.catch(() => {
showErrorNotification(translate('users.failed.to.create.user'));
});
} else {
setPasswordMatch(false);
}
};

const isFormValid = email && password && confirmPassword === password && role;

return (
<>
<Button color="secondary" onClick={handleShowModal}>
<FormattedMessage id="create.user" />
</Button>
<Modal isOpen={showModal} toggle={handleCloseModal} size="lg">
<ModalHeader>
<FormattedMessage id="create.user" />
</ModalHeader>
<ModalBody>
<Container>
<Row>
<Col>
<FormGroup>
<Label for="email">Email</Label>
<Input
id="email"
name="email"
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</FormGroup>
</Col>
</Row>
{!passwordMatch && (
<Row>
<Col>
<Alert color="danger">{translate('user.password.mismatch')}</Alert>
</Col>
</Row>
)}
<Row>
<Col>
<FormGroup>
<Label for="password">Password</Label>
<Input
id="password"
name="password"
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
required
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
name="confirmPassword"
type="password"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
invalid={confirmPassword !== password && confirmPassword !== ''}
required
/>
<FormFeedback>
<FormattedMessage id="user.password.mismatch" />
</FormFeedback>
</FormGroup>
</Col>
</Row>
<Row>
<Col>
<FormGroup>
<Label for="role">Role</Label>
<Input
id="role"
name="role"
type="select"
value={role}
onChange={e => setRole(e.target.value)}
invalid={!role}
required
>
<option value="">Select Role</option>
{Object.entries(UserRole).map(([role, val]) => (
<option value={role} key={val}>
{role}
</option>
))}
</Input>
<FormFeedback>
<FormattedMessage id="error.required" />
</FormFeedback>
</FormGroup>
</Col>
</Row>
</Container>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={handleCloseModal}>
<FormattedMessage id="cancel" />
</Button>
<Button color="primary" onClick={handleSubmit} disabled={!isFormValid}>
<FormattedMessage id="create.user" />
</Button>
</ModalFooter>
</Modal>
</>
);
}
Loading
Loading