Skip to content

Commit

Permalink
rework notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
GreenWizard2015 committed Jan 10, 2024
1 parent 754d33d commit c59fca4
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 77 deletions.
24 changes: 14 additions & 10 deletions ui/src/components/NotificationsArea.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import React from 'react';
import { connect } from 'react-redux';
import { Alert } from 'react-bootstrap';
import { useNotificationsSystem } from '../contexts/NotificationsContext';
import { NotificationsSystemActions } from '../store/slices/Notifications';

function NotificationsArea() {
const NotificationsSystem = useNotificationsSystem();
const { currentNotifications } = NotificationsSystem;
if(!currentNotifications) return null;

const hideNotifications = () => { NotificationsSystem.clear(); };
function NotificationsArea({ hasNotifications, message, clearNotifications }) {
if(!hasNotifications) return null;

return (
<Alert variant="info" onClose={hideNotifications} dismissible>
{currentNotifications.message}
<Alert variant="info" onClose={clearNotifications} dismissible>
{message}
</Alert>
);
}

export default NotificationsArea;
export default connect(
(state) => ({
hasNotifications: state.notifications.currentNotifications != null,
message: state.notifications.currentNotifications?.message
}), {
clearNotifications: NotificationsSystemActions.clear
}
)(NotificationsArea);
15 changes: 2 additions & 13 deletions ui/src/components/SystemControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,18 @@ import { connect } from 'react-redux';
import { Button } from 'react-bootstrap';

import { useWaterPumpAPI } from '../contexts/WaterPumpAPIContext';
import { useNotificationsSystem } from '../contexts/NotificationsContext.js';
import { startPump, stopPump } from '../store/slices/SystemStatus.js';

export function SystemControlsComponent({
pouringTime, systemStatus, startPump, stopPump
}) {
const api = useWaterPumpAPI().API;
const NotificationsSystem = useNotificationsSystem();

const handleStart = async () => {
try {
await startPump({ api , pouringTime });
} catch (error) {
NotificationsSystem.alert('Error starting water pump: ' + error.message);
}
await startPump({ api , pouringTime });
};

const handleStop = async () => {
try {
await stopPump({ api });
} catch (error) {
NotificationsSystem.alert('Error stopping water pump: ' + error.message);
}
await stopPump({ api });
};

const isRunning = systemStatus.pump.running;
Expand Down
13 changes: 2 additions & 11 deletions ui/src/components/WaterPumpStatusProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ import React from 'react';
import { connect } from 'react-redux';
import { updateSystemStatus } from '../store/slices/SystemStatus';
import { useWaterPumpAPI } from '../contexts/WaterPumpAPIContext';
import { useNotificationsSystem } from '../contexts/NotificationsContext';

const FETCH_INTERVAL = 5000;
const CHECK_INTERVAL = Math.round(FETCH_INTERVAL / 10);

function WaterPumpStatusProviderComoponent({ children, updateStatus, systemStatus }) {
const { API } = useWaterPumpAPI();
const NotificationsSystem = useNotificationsSystem();
const nextFetchTime = React.useRef(0);

// Function to fetch water pump status
Expand All @@ -19,16 +17,10 @@ function WaterPumpStatusProviderComoponent({ children, updateStatus, systemStatu
if(null == API) return;

nextFetchTime.current = Number.MAX_SAFE_INTEGER; // prevent concurrent fetches
try {
const status = await API.status();
updateStatus(status);
} catch (error) {
NotificationsSystem.alert('Error fetching system status: ' + error.message);
updateStatus(null);
}
await updateStatus(API);
nextFetchTime.current = Date.now() + FETCH_INTERVAL;
},
[API, NotificationsSystem, updateStatus, nextFetchTime]
[API, updateStatus, nextFetchTime]
);

// Effect to start fetching periodically and when API changes
Expand Down Expand Up @@ -58,6 +50,5 @@ export default connect(
systemStatus: state.systemStatus
}), {
updateStatus: updateSystemStatus

}
)(WaterPumpStatusProviderComoponent);
23 changes: 0 additions & 23 deletions ui/src/contexts/NotificationsContext.js

This file was deleted.

9 changes: 3 additions & 6 deletions ui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import App from './App.js';
import 'bootstrap/dist/css/bootstrap.min.css'; // Importing Bootstrap CSS

import { NotificationsProvider } from './contexts/NotificationsContext.js';
import { WaterPumpAPIProvider } from './contexts/WaterPumpAPIContext.js';
// Redux store
import { AppStore } from './store';
Expand All @@ -12,11 +11,9 @@ const root = createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<AppStore>
<NotificationsProvider>
<WaterPumpAPIProvider>
<App />
</WaterPumpAPIProvider>
</NotificationsProvider>
<WaterPumpAPIProvider>
<App />
</WaterPumpAPIProvider>
</AppStore>
</React.StrictMode>
);
18 changes: 18 additions & 0 deletions ui/src/store/slices/Notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createSlice } from '@reduxjs/toolkit';

export const NotificationsSlice = createSlice({
name: 'notifications',
initialState: {
currentNotifications: null
},
reducers: {
alert: (state, action) => {
state.currentNotifications = action.payload;
},
clear: state => {
state.currentNotifications = null;
}
}
});

export const NotificationsSystemActions = NotificationsSlice.actions;
54 changes: 41 additions & 13 deletions ui/src/store/slices/SystemStatus.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { NotificationsSystemActions } from './Notifications';

function withNotification(action, message) {
return async (params, { dispatch }) => {
try {
return await action(params);
} catch(error) {
dispatch(NotificationsSystemActions.alert({
type: 'error',
message: `${message} (${error.message})`
}));
throw error;
}
};
}

// Async thunks
export const startPump = createAsyncThunk(
'systemStatus/startPump',
async ({ api, pouringTime }, { dispatch }) => {
const response = await api.start(pouringTime);
return response;
}
withNotification(
async ({ api, pouringTime }) => {
return await api.start(pouringTime);
},
'Failed to start pump'
)
);

export const stopPump = createAsyncThunk(
'systemStatus/stopPump',
async ({ api }, { dispatch }) => {
const response = await api.stop();
return response;
}
withNotification(
async ({ api }) => {
return await api.stop();
},
'Failed to stop pump'
)
);

export const updateSystemStatus = createAsyncThunk(
'systemStatus/update',
withNotification(
async ( api ) => {
return await api.status();
},
'Failed to update system status'
)
);

// slice for system status
Expand All @@ -25,18 +54,17 @@ const bindStatus = (state, action) => {
export const SystemStatusSlice = createSlice({
name: 'systemStatus',
initialState: null,
reducers: {
updateSystemStatus: bindStatus,
},
reducers: {},
extraReducers: (builder) => {
// update system status on start/stop pump
builder.addCase(startPump.fulfilled, bindStatus);
builder.addCase(stopPump.fulfilled, bindStatus);
builder.addCase(updateSystemStatus.fulfilled, bindStatus);
// on error, do not update system status
builder.addCase(startPump.rejected, (state, action) => state);
builder.addCase(stopPump.rejected, (state, action) => state);
builder.addCase(updateSystemStatus.rejected, (state, action) => state);
}
});

export const actions = SystemStatusSlice.actions;
export const { updateSystemStatus } = actions;
export const actions = SystemStatusSlice.actions;
3 changes: 2 additions & 1 deletion ui/src/store/slices/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { SystemStatusSlice } from "./SystemStatus";
import { UISlice } from "./UI";
import { NotificationsSlice } from "./Notifications";

const slices = [ SystemStatusSlice, UISlice ];
const slices = [ SystemStatusSlice, UISlice, NotificationsSlice ];
// export all slices as an object { [sliceName]: slice }
export const ALL_APP_SLICES = slices.reduce((acc, slice) => {
acc[slice.name] = slice;
Expand Down

0 comments on commit c59fca4

Please sign in to comment.