feat(gui): Remember acknowledged alerts and do not show them again

This commit is contained in:
Binarybaron 2025-10-05 22:51:20 +02:00
parent bbdae0c18c
commit 65a46a4205
5 changed files with 65 additions and 12 deletions

View file

@ -1,14 +1,14 @@
import { Box } from "@mui/material";
import { Alert, AlertTitle } from "@mui/material";
import { removeAlert } from "store/features/alertsSlice";
import { useAppDispatch, useAppSelector } from "store/hooks";
import { acknowledgeAlert } from "store/features/alertsSlice";
import { useAlerts, useAppDispatch } from "store/hooks";
export default function ApiAlertsBox() {
const alerts = useAppSelector((state) => state.alerts.alerts);
const alerts = useAlerts();
const dispatch = useAppDispatch();
function onRemoveAlert(id: number) {
dispatch(removeAlert(id));
function onAcknowledgeAlert(id: number) {
dispatch(acknowledgeAlert(id));
}
if (alerts.length === 0) return null;
@ -20,7 +20,7 @@ export default function ApiAlertsBox() {
variant="filled"
severity={alert.severity}
key={alert.id}
onClose={() => onRemoveAlert(alert.id)}
onClose={() => onAcknowledgeAlert(alert.id)}
>
<AlertTitle>{alert.title}</AlertTitle>
{alert.body}

View file

@ -17,7 +17,7 @@ import { LazyStore } from "@tauri-apps/plugin-store";
const rootPersistConfig = {
key: "gui-global-state-store",
storage: sessionStorage,
blacklist: ["settings", "conversations", "logs"],
blacklist: ["settings", "conversations", "alerts", "logs"],
};
// Use Tauri's store plugin for persistent settings
@ -58,6 +58,12 @@ const conversationsPersistConfig = {
storage: createTauriStorage(),
};
// Persist alerts across application restarts
const alertsPersistConfig = {
key: "alerts",
storage: createTauriStorage(),
};
// Create a persisted version of the settings reducer
const persistedSettingsReducer = persistReducer(
settingsPersistConfig,
@ -70,11 +76,18 @@ const persistedConversationsReducer = persistReducer(
reducers.conversations,
);
// Create a persisted version of the alerts reducer
const persistedAlertsReducer = persistReducer(
alertsPersistConfig,
reducers.alerts,
);
// Combine all reducers, using the persisted settings reducer
const rootReducer = combineReducers({
...reducers,
settings: persistedSettingsReducer,
conversations: persistedConversationsReducer,
alerts: persistedAlertsReducer,
});
// Enable persistence for the entire application state

View file

@ -1,14 +1,28 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Alert } from "models/apiModel";
import { fnv1a } from "utils/hash";
export interface AlertsSlice {
alerts: Alert[];
/// The ids of the alerts that have been acknowledged
/// by the user and should not be shown again
acknowledgedAlerts: AcknowledgementKey[];
}
const initialState: AlertsSlice = {
alerts: [],
acknowledgedAlerts: [],
};
/// We use the key in combination with the fnv1a hash of the title
/// to uniquely identify an alert
///
/// If the title changes, the hash will change and the alert will be shown again
interface AcknowledgementKey {
id: number;
titleHash: string;
}
const alertsSlice = createSlice({
name: "alerts",
initialState,
@ -16,13 +30,21 @@ const alertsSlice = createSlice({
setAlerts(slice, action: PayloadAction<Alert[]>) {
slice.alerts = action.payload;
},
removeAlert(slice, action: PayloadAction<number>) {
slice.alerts = slice.alerts.filter(
(alert) => alert.id !== action.payload,
);
acknowledgeAlert(slice, action: PayloadAction<number>) {
const alertTitle = slice.alerts.find(
(alert) => alert.id === action.payload,
)?.title;
// If we cannot find the alert, we cannot acknowledge it
if (alertTitle != null) {
slice.acknowledgedAlerts.push({
id: action.payload,
titleHash: fnv1a(alertTitle),
});
}
},
},
});
export const { setAlerts, removeAlert } = alertsSlice.actions;
export const { setAlerts, acknowledgeAlert } = alertsSlice.actions;
export default alertsSlice.reducer;

View file

@ -30,6 +30,8 @@ import {
TauriBackgroundProgress,
TauriBitcoinSyncProgress,
} from "models/tauriModel";
import { Alert } from "models/apiModel";
import { fnv1a } from "utils/hash";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
@ -327,3 +329,17 @@ export function useTotalUnreadMessagesCount(): number {
return totalUnreadCount;
}
/// Returns all the alerts that have not been acknowledged
export function useAlerts(): Alert[] {
return useAppSelector((state) =>
state.alerts.alerts.filter(
(alert) =>
// Check if there is an acknowledgement with
// the same id and the same title hash
!state.alerts.acknowledgedAlerts.some(
(ack) => ack.id === alert.id && ack.titleHash === fnv1a(alert.title),
),
),
);
}