Skip to content

Commit

Permalink
Added notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
rmathis committed Apr 27, 2021
1 parent fcc8485 commit 0ed5247
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 37 deletions.
45 changes: 23 additions & 22 deletions ui/src/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import Dashboard from './dashboard/container/Dashboard';
import Header from './core/components/Header';
import { UserProvider } from './core/user/UserContext';
import { Metadata } from './metadata/Metadata';
import { Notifications } from './notifications/hoc/Notifications';
import { NotificationList } from './notifications/component/NotificationList';

function App() {

Expand All @@ -29,27 +31,28 @@ function App() {
};

return (
<div className="shibui">
<div className="app-root d-flex flex-column justify-content-between">
<HttpProvider options={httpOptions}>


<UserProvider>
<I18nProvider>
<Router>
<Header />
<main className="pad-content">
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route path="/dashboard" component={Dashboard} />
<Route path="/metadata/:type/:id" component={Metadata} />
</Switch>
</main>
<Footer />
</Router>
</I18nProvider>
</UserProvider>
<Notifications>
<UserProvider>
<I18nProvider>
<Router>
<Header />
<main className="pad-content">
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route path="/dashboard" component={Dashboard} />
<Route path="/metadata/:type/:id" component={Metadata} />
</Switch>
<NotificationList />
</main>
<Footer />
</Router>
</I18nProvider>
</UserProvider>
</Notifications>
</HttpProvider>
</div>
);
Expand All @@ -58,8 +61,6 @@ function App() {
/*
<main >
<page-title className="sr-only sr-only-focusable"></page-title>
<router-outlet></router-outlet>
<notification-list></notification-list>
</main>
*/

Expand Down
24 changes: 22 additions & 2 deletions ui/src/app/admin/container/SourcesActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,40 @@ import React from 'react';
import SourceList from '../../metadata/domain/source/component/SourceList';
import { useMetadataEntity } from '../../metadata/hooks/api';

import { NotificationContext, createNotificationAction } from '../../notifications/hoc/Notifications';

export function SourcesActions ({sources, reloadSources}) {

const { del, response } = useMetadataEntity('source', {
const { dispatch } = React.useContext(NotificationContext);

const { put, del, response } = useMetadataEntity('source', {
cachePolicy: 'no-cache'
});

async function deleteSource(id) {
await del(`/${id}`);
if (response.ok) {
dispatch(createNotificationAction(
`Metadata Source has been removed.`
));
reloadSources();
}
}

async function enableSource(source) {
await put(`/${source.id}`, {
...source,
serviceEnabled: true
});
if (response.ok) {
dispatch(createNotificationAction(
`Metadata Source has been enabled.`
));
reloadSources();
}
}

return (
<SourceList entities={sources} onDelete={ deleteSource } />
<SourceList entities={sources} onDelete={ deleteSource } onEnable={ enableSource } />
);
}
9 changes: 9 additions & 0 deletions ui/src/app/admin/container/UserManagement.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';

import Translate from '../../i18n/components/translate';
import API_BASE_PATH from '../../App.constant';
import { NotificationContext, createNotificationAction} from '../../notifications/hoc/Notifications';

export default function UserManagement({ users, children, reload }) {

const [roles, setRoles] = React.useState([]);

const { dispatch } = React.useContext(NotificationContext);

const { get, patch, del, response } = useFetch(`${API_BASE_PATH}`, {});

async function loadRoles() {
Expand All @@ -27,13 +30,19 @@ export default function UserManagement({ users, children, reload }) {
role
});
if (response.ok && reload) {
dispatch(createNotificationAction(
`User update successful for ${user.username}.`
));
reload();
}
}

async function deleteUserRequest(id) {
await del(`/admin/users/${id}`);
if (response.ok && reload) {
dispatch(createNotificationAction(
`User deleted.`
));
reload();
}
}
Expand Down
9 changes: 2 additions & 7 deletions ui/src/app/core/hooks/utils.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : ((r & 0x3) | 0x8);
return v.toString(16);
});
}
import {uuid} from '../utility/uuid';

export function useGuid() {
return uuidv4();
return uuid();
}
6 changes: 6 additions & 0 deletions ui/src/app/core/utility/uuid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function uuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : ((r & 0x3) | 0x8);
return v.toString(16);
});
}
20 changes: 15 additions & 5 deletions ui/src/app/metadata/domain/source/component/SourceList.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom';
import { Badge, UncontrolledPopover, PopoverBody, Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { faTrash, faExclamationTriangle, faCheck } from '@fortawesome/free-solid-svg-icons';



Expand All @@ -13,7 +13,7 @@ import { Scroller } from '../../../../dashboard/component/Scroller';



export default function SourceList({ entities, onDelete }) {
export default function SourceList({ entities, onDelete, onEnable }) {

const [modal, setModal] = React.useState(false);

Expand Down Expand Up @@ -57,9 +57,19 @@ export default function SourceList({ entities, onDelete }) {
</td>
<td><FormattedDate date={source.createdDate} /></td>
<td className="text-center">
<Badge color={source.serviceEnabled ? 'success' : 'danger'}>
<Translate value={source.serviceEnabled ? 'value.enabled' : 'value.disabled'}></Translate>
</Badge>
{onEnable ?
<button
className="btn btn-success btn-sm"
onClick={() => onEnable(source)}
aria-label="Enable this service provider">
<Translate value={ source.enabled ? 'label.disable' : 'label.enable' }>Disable</Translate>
{!source.enabled && <>&nbsp;<FontAwesomeIcon icon={faCheck} size="lg" /></> }
</button>
:
<Badge color={source.serviceEnabled ? 'success' : 'danger'}>
<Translate value={source.serviceEnabled ? 'value.enabled' : 'value.disabled'}></Translate>
</Badge>
}
</td>
<td className="text-right" id={`delete-source-btn-${idx}`}>
<button className="btn btn-outline btn-sm btn-danger"
Expand Down
22 changes: 22 additions & 0 deletions ui/src/app/notifications/component/NotificationItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { Alert } from 'reactstrap';
import { NotificationContext, removeNotificationAction } from '../hoc/Notifications';

export function NotificationItem ({ type, body, timeout, id }) {

const { dispatch } = React.useContext(NotificationContext);

React.useEffect(() => {
if (timeout) {
setTimeout(() => {
dispatch(removeNotificationAction(id));
}, timeout);
}
}, [timeout, id, dispatch]);

return (
<Alert color={type} toggle={() => dispatch(removeNotificationAction(id))}>
{body}
</Alert>
)
}
18 changes: 18 additions & 0 deletions ui/src/app/notifications/component/NotificationList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import { NotificationContext } from '../hoc/Notifications';
import { NotificationItem } from './NotificationItem';

export function NotificationList () {

const { state } = React.useContext(NotificationContext);

return (
<ul className="notification-list list-unstyled position-fixed p-4 w-25">
{state.notifications.map((n) => (
<li key={n.id}>
<NotificationItem id={n.id} type={n.type} body={n.body} timeout={n.timeout} />
</li>
))}
</ul>
);
}
82 changes: 82 additions & 0 deletions ui/src/app/notifications/hoc/Notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from "react";
import { uuid } from '../../core/utility/uuid';

const NotificationContext = React.createContext();

const { Provider, Consumer } = NotificationContext;

const initialState = {
notifications: []
};

export const NotificationActions = {
ADD_NOTIFICATION: 'add notification',
REMOVE_NOTIFICATION: 'remove notification'
};

export const NotificationTypes = {
SUCCESS: 'success',
DANGER: 'danger',
ERROR: 'danger',
WARNING: 'warn',
INFO: 'info'
};

export const createNotificationAction = (body, type = NotificationTypes.SUCCESS, timeout=20000) => {
return {
type: NotificationActions.ADD_NOTIFICATION,
payload: {
id: uuid(),
type,
body,
timeout
}
}
}

export const removeNotificationAction = (id) => {
return {
type: NotificationActions.REMOVE_NOTIFICATION,
payload: id
}
}

function reducer(state, action) {
switch (action.type) {
case NotificationActions.ADD_NOTIFICATION:
return {
notifications: [
...state.notifications,
{
...action.payload
}
]
};
case NotificationActions.REMOVE_NOTIFICATION:
return {
notifications: [
...state.notifications.filter(n => n.id !== action.payload)
]
};
default:
throw new Error();
}
}

/*eslint-disable react-hooks/exhaustive-deps*/
function Notifications ({ children }) {
const [state, dispatch] = React.useReducer(reducer, initialState);

const contextValue = React.useMemo(() => ({ state, dispatch }), [state, dispatch]);

return (
<Provider value={contextValue}>{children}</Provider>
);
}

export {
Notifications,
NotificationContext,
Provider as NotificationProvider,
Consumer as NotificationConsumer
};
7 changes: 6 additions & 1 deletion ui/src/theme/project/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@
@import './tabs';
@import './table.scss';
@import './utility';
@import './notifications.scss';

html, body {
height: 100%;
}

body {
background-color: theme-color("light");
padding-top: 56px;
}

app-root {
.app-root {
min-height: calc(100vh - 56px);
}

Expand Down
5 changes: 5 additions & 0 deletions ui/src/theme/project/notifications.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.notification-list {
bottom: 50px;
right: 0px;
z-index: 2000;
}

0 comments on commit 0ed5247

Please sign in to comment.