Add client notification (#771)

* ClientNotification WIP

* Test

* ClientNotification WIP

* Add client notification dialog and related functionality

* Update ClientNotificationDialog.tsx

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
This commit is contained in:
Jeremy Gallant
2025-08-10 21:59:03 -04:00
committed by GitHub
co-authored by philon-
parent 176d554ef9
commit 32f31cb502
8 changed files with 160 additions and 0 deletions
@@ -0,0 +1,72 @@
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@components/UI/Dialog.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import { useTranslation } from "react-i18next";
export interface ClientNotificationDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export const ClientNotificationDialog = ({
open,
onOpenChange,
}: ClientNotificationDialogProps) => {
const { t } = useTranslation("dialog");
const { getClientNotification, removeClientNotification } = useDevice();
const localOnOpenChange = (open: boolean) => {
removeClientNotification(0);
if (!getClientNotification(0)) {
onOpenChange(open);
}
};
const dialogContent = (() => {
if (!getClientNotification(0)) {
return;
}
switch (getClientNotification(0)?.payloadVariant.case) {
// TODO: Add KeyVerification logic
/*case "keyVerificationNumberInform":
return <></>;
case "keyVerificationNumberRequest":
return <></>;
case "keyVerificationFinal":
return <></>;
case "duplicatedPublicKey":
return <></>;
case "lowEntropyKey":
return <></>;*/
default:
return (
<DialogHeader>
<DialogTitle>{t("clientNotification.title")}</DialogTitle>
<DialogDescription>
{t([
`clientNotification.${getClientNotification(0)?.message}`,
getClientNotification(0)?.message ?? "",
])}
</DialogDescription>
</DialogHeader>
);
}
})();
return (
<Dialog open={open} onOpenChange={localOnOpenChange}>
<DialogContent>
<DialogClose />
{dialogContent}
</DialogContent>
</Dialog>
);
};
@@ -1,3 +1,4 @@
import { ClientNotificationDialog } from "@components/Dialog/ClientNotificationDialog/ClientNotificationDialog.tsx";
import { DeleteMessagesDialog } from "@components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx";
import { DeviceNameDialog } from "@components/Dialog/DeviceNameDialog.tsx";
import { ImportDialog } from "@components/Dialog/ImportDialog.tsx";
@@ -84,6 +85,12 @@ export const DialogManager = () => {
setDialogOpen("deleteMessages", open);
}}
/>
<ClientNotificationDialog
open={dialog.clientNotification}
onOpenChange={(open) => {
setDialogOpen("clientNotification", open);
}}
/>
</>
);
};
@@ -44,7 +44,10 @@ export const mockDeviceStore: Device = {
refreshKeys: false,
deleteMessages: false,
managedMode: false,
clientNotification: false,
},
clientNotifications: [],
setStatus: vi.fn(),
setConfig: vi.fn(),
setModuleConfig: vi.fn(),
@@ -85,6 +88,9 @@ export const mockDeviceStore: Device = {
sendAdminMessage: vi.fn(),
updateFavorite: vi.fn(),
updateIgnored: vi.fn(),
addClientNotification: vi.fn(),
removeClientNotification: vi.fn(),
getClientNotification: vi.fn(),
getAllUnreadCount: vi.fn().mockReturnValue(0),
getUnreadCount: vi.fn().mockReturnValue(0),
};
@@ -62,7 +62,9 @@ export interface Device {
refreshKeys: boolean;
deleteMessages: boolean;
managedMode: boolean;
clientNotification: boolean;
};
clientNotifications: Protobuf.Mesh.ClientNotification[];
setStatus: (status: Types.DeviceStatusEnum) => void;
setConfig: (config: Protobuf.Config.Config) => void;
@@ -125,6 +127,13 @@ export interface Device {
sendAdminMessage: (message: Protobuf.Admin.AdminMessage) => void;
updateFavorite: (nodeNum: number, isFavorite: boolean) => void;
updateIgnored: (nodeNum: number, isIgnored: boolean) => void;
addClientNotification: (
clientNotificationPacket: Protobuf.Mesh.ClientNotification,
) => void;
removeClientNotification: (index: number) => void;
getClientNotification: (
index: number,
) => Protobuf.Mesh.ClientNotification | undefined;
}
export interface DeviceState {
@@ -173,12 +182,14 @@ export const useDeviceStore = createStore<PrivateDeviceState>((set, get) => ({
refreshKeys: false,
deleteMessages: false,
managedMode: false,
clientNotification: false,
},
pendingSettingsChanges: false,
messageDraft: "",
nodeErrors: new Map(),
unreadCounts: new Map(),
nodesMap: new Map(),
clientNotifications: [],
setStatus: (status: Types.DeviceStatusEnum) => {
set(
@@ -847,6 +858,37 @@ export const useDeviceStore = createStore<PrivateDeviceState>((set, get) => ({
}),
);
},
addClientNotification: (
clientNotificationPacket: Protobuf.Mesh.ClientNotification,
) => {
set(
produce<PrivateDeviceState>((draft) => {
const device = draft.devices.get(id);
if (!device) {
return;
}
device.clientNotifications.push(clientNotificationPacket);
}),
);
},
removeClientNotification: (index: number) => {
set(
produce<PrivateDeviceState>((draft) => {
const device = draft.devices.get(id);
if (!device) {
return;
}
device.clientNotifications.splice(index, 1);
}),
);
},
getClientNotification: (index: number) => {
const device = get().devices.get(id);
if (!device) {
return;
}
return device.clientNotifications[index];
},
});
}),
);
+7
View File
@@ -114,6 +114,13 @@ export const subscribeAll = (
});
});
connection.events.onClientNotificationPacket.subscribe(
(clientNotificationPacket) => {
device.addClientNotification(clientNotificationPacket);
device.setDialogOpen("clientNotification", true);
},
);
connection.events.onRoutingPacket.subscribe((routingPacket) => {
if (routingPacket.data.variant.case === "errorReason") {
switch (routingPacket.data.variant.value) {