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

1171 - dashboard error refresh page #1312

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0de6f3b
removing user creation page and replacing it with a modal, consistent…
juanjoseguva Jun 11, 2024
0079ebb
Removing unused user creation page route
juanjoseguva Jun 11, 2024
6bc82f2
Adding the Users page in the top menu dropdown, accessible only to ad…
juanjoseguva Jun 11, 2024
a6abea9
Removing Manage Users section in the Admin preferences page
juanjoseguva Jun 11, 2024
a9e4115
Removing Create Users page button, replacing with the modal
juanjoseguva Jun 11, 2024
63b6947
Moving the user create button above the users table. Users table stil…
juanjoseguva Jun 11, 2024
0bf99df
Corrected password mismatch error message id
juanjoseguva Jun 11, 2024
19060c8
Updated option values to match UserRole types for easy mapping.
hazelcsumb Jun 12, 2024
0feaf03
Swap table for modals for admin user panel
Nireves333 Jun 14, 2024
26fee51
Adding delete user confirmation modal
juanjoseguva Jun 17, 2024
07df39f
Added restart session button for when user is stuck in an error state
hazeltonbw Jun 19, 2024
948c6a5
Admin panel overhaul to admin preferences, translations updated.
hazeltonbw Jun 26, 2024
a5cc4d4
Restoring deleted French messages
juanjoseguva Jun 26, 2024
394fb74
Fix overflowing div, flex-grow div to just below footer
hazeltonbw Jul 10, 2024
bdfd311
Orgnaziation of language translation files, de-clutter admin sidebar
hazeltonbw Jul 10, 2024
f9ef657
Add users management to admin settings
hazeltonbw Jul 15, 2024
3550cfd
Revert translation organization changes
hazeltonbw Jul 15, 2024
a2ea5ca
Remove users link from header component
hazeltonbw Jul 15, 2024
8276272
Merge remote-tracking branch 'upstream/development' into 890-Admin-Co…
hazeltonbw Jul 15, 2024
fd7214a
Add license to files
hazeltonbw Jul 16, 2024
f5a5adf
Merge branch '890-Admin-Components' into 1171
hazeltonbw Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/client/app/components/HeaderButtonsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default function HeaderButtonsComponent() {
// The should ones tell if see but not selectable.
shouldHomeButtonDisabled: true,
shouldAdminButtonDisabled: true,
shouldUsersButtonDisabled: true,
shouldGroupsButtonDisabled: true,
shouldMetersButtonDisabled: true,
shouldMapsButtonDisabled: true,
Expand Down Expand Up @@ -88,6 +89,7 @@ export default function HeaderButtonsComponent() {
...prevState,
shouldHomeButtonDisabled: pathname === '/',
shouldAdminButtonDisabled: pathname === '/admin',
shouldUsersButtonDisabled: pathname === '/users',
shouldGroupsButtonDisabled: pathname === '/groups',
shouldMetersButtonDisabled: pathname === '/meters',
shouldMapsButtonDisabled: pathname === '/maps',
Expand Down Expand Up @@ -209,7 +211,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
2 changes: 0 additions & 2 deletions src/client/app/components/RouteComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import RoleOutlet from './router/RoleOutlet';
import UnitsDetailComponent from './unit/UnitsDetailComponent';
import ErrorComponent from './router/ErrorComponent';
import { selectSelectedLanguage } from '../redux/slices/appStateSlice';
import CreateUserComponent from './admin/CreateUserComponent';

/**
* @returns the router component Responsible for client side routing.
Expand Down Expand Up @@ -57,7 +56,6 @@ const router = createBrowserRouter([
{ path: 'admin', element: <AdminComponent /> },
{ path: 'calibration', element: <MapCalibrationContainer /> },
{ path: 'maps', element: <MapsDetailContainer /> },
{ path: 'users/new', element: <CreateUserComponent /> },
{ path: 'units', element: <UnitsDetailComponent /> },
{ path: 'conversions', element: <ConversionsDetailComponent /> },
{ path: 'users', element: <UsersDetailComponent /> }
Expand Down
4 changes: 1 addition & 3 deletions src/client/app/components/UnsavedWarningComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import translate from '../utils/translate';
export interface UnsavedWarningProps {
changes: any;
hasUnsavedChanges: boolean;
successMessage: LocaleDataKey;
successMessage: LocaleDataKey;
failureMessage: LocaleDataKey;
submitChanges: MutationTrigger<any>;
}
Expand All @@ -31,14 +31,12 @@ export function UnsavedWarningComponent(props: UnsavedWarningProps) {
submitChanges(changes)
.unwrap()
.then(() => {
//TODO translate me
showSuccessNotification(translate('unsaved.success'));
if (blocker.state === 'blocked') {
blocker.proceed();
}
})
.catch(() => {
//TODO translate me
showErrorNotification(translate('unsaved.failure'));
if (blocker.state === 'blocked') {
blocker.proceed();
Expand Down
39 changes: 16 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,42 @@ 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">
<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 === 'users' ? 'btn btn-primary' : 'btn btn-light'}`}
onClick={() => props.onSelectPreference('users')}
>
{translate('users')}
</button>
<button
type="button"
className={`${props.selectedPreference === 'misc' ? 'btn btn-primary' : 'btn btn-light'}`}
onClick={() => props.onSelectPreference('misc')}
>
{translate('misc')}
</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