+ {hotlistNames.map((hotlistName, index) => (
+
{hotlistName ? hotlistName?.replace(/\.csv$/i, "") : "-"}
diff --git a/src/components/SightingsWidget/SightingWidget.tsx b/src/components/SightingsWidget/SightingWidget.tsx
index e093748..3f59679 100644
--- a/src/components/SightingsWidget/SightingWidget.tsx
+++ b/src/components/SightingsWidget/SightingWidget.tsx
@@ -15,7 +15,7 @@ import NPED_CAT_C from "/NPED_Cat_C.svg";
import popup from "../../assets/sounds/ui/popup_open.mp3";
import notification from "../../assets/sounds/ui/notification.mp3";
import { useSound } from "react-sounds";
-import { useNPEDContext } from "../../context/NPEDUserContext";
+import { useIntegrationsContext } from "../../context/IntegrationsContext";
import { useSoundContext } from "../../context/SoundContext";
import Loading from "../UI/Loading";
import { checkIsHotListHit, getNPEDCategory } from "../../utils/utils";
@@ -72,7 +72,9 @@ export default function SightingHistoryWidget({ className, title }: SightingHist
} = useSightingFeedContext();
const { dispatch } = useAlertHitContext();
- const { sessionStarted, setSessionList, sessionList, sessionPaused } = useNPEDContext();
+ const { state: integrationState, dispatch: integrationDispatch } = useIntegrationsContext();
+ const sessionStarted = integrationState.sessionStarted;
+ const sessionPaused = integrationState.sessionPaused;
const processedRefs = useRef
>(new Set());
@@ -99,10 +101,10 @@ export default function SightingHistoryWidget({ className, title }: SightingHist
if (!mostRecent) return;
if (sessionPaused) return;
const reducedMostRecent = reduceObject(mostRecent);
- setSessionList([...sessionList, reducedMostRecent]);
+ integrationDispatch({ type: "ADD", payload: reducedMostRecent });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [mostRecent, sessionStarted, setSessionList]);
+ }, [mostRecent, sessionStarted]);
const onRowClick = useCallback(
(sighting: SightingType) => {
diff --git a/src/components/UI/Header.tsx b/src/components/UI/Header.tsx
index 6fdf67a..c093949 100644
--- a/src/components/UI/Header.tsx
+++ b/src/components/UI/Header.tsx
@@ -4,11 +4,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGear, faHome, faListCheck, faMaximize, faMinimize, faRotate } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import SoundBtn from "./SoundBtn";
-import { useNPEDContext } from "../../context/NPEDUserContext";
+import { useIntegrationsContext } from "../../context/IntegrationsContext";
export default function Header() {
const [isFullscreen, setIsFullscreen] = useState(false);
- const { sessionStarted, sessionPaused } = useNPEDContext();
+ const { state } = useIntegrationsContext();
+
+ const sessionStarted = state.sessionStarted;
+
+ const sessionPaused = state.sessionPaused;
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
diff --git a/src/context/IntegrationsContext.ts b/src/context/IntegrationsContext.ts
new file mode 100644
index 0000000..a99c596
--- /dev/null
+++ b/src/context/IntegrationsContext.ts
@@ -0,0 +1,14 @@
+import { createContext, useContext, type ActionDispatch } from "react";
+import type { NPEDACTION, NPEDSTATE } from "../types/types";
+
+type IntegrationsValue = {
+ state: NPEDSTATE;
+ dispatch: ActionDispatch<[action: NPEDACTION]>;
+};
+
+export const IntegrationsContext = createContext(undefined);
+export const useIntegrationsContext = () => {
+ const ctx = useContext(IntegrationsContext);
+ if (!ctx) throw new Error("useNPEDContext must be used within ");
+ return ctx;
+};
diff --git a/src/context/NPEDUserContext.ts b/src/context/NPEDUserContext.ts
deleted file mode 100644
index d4fba95..0000000
--- a/src/context/NPEDUserContext.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { createContext, useContext, type SetStateAction } from "react";
-import type { DedupedSightings, NPEDUser, ReducedSightingType } from "../types/types";
-
-type UserContextValue = {
- user: NPEDUser | null;
- setUser: React.Dispatch>;
- sessionStarted: boolean;
- sessionPaused: boolean;
- setSessionPaused: React.Dispatch>;
- setSessionStarted: React.Dispatch>;
- sessionList: ReducedSightingType[];
- setSessionList: React.Dispatch>;
- savedSightings: DedupedSightings;
- setSavedSightings: React.Dispatch>;
-};
-
-export const NPEDUserContext = createContext(undefined);
-export const useNPEDContext = () => {
- const ctx = useContext(NPEDUserContext);
- if (!ctx) throw new Error("useNPEDContext must be used within ");
- return ctx;
-};
diff --git a/src/context/providers/IntegrationsContextProvider.tsx b/src/context/providers/IntegrationsContextProvider.tsx
new file mode 100644
index 0000000..d12540c
--- /dev/null
+++ b/src/context/providers/IntegrationsContextProvider.tsx
@@ -0,0 +1,36 @@
+import { useEffect, useReducer, type ReactNode } from "react";
+import { IntegrationsContext } from "../IntegrationsContext";
+import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
+import { initialState, reducer } from "../reducers/IntegrationsContextReducer";
+
+type IntegrationsProviderType = {
+ children: ReactNode;
+};
+
+export const IntegrationsProvider = ({ children }: IntegrationsProviderType) => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+ const { mutation } = useCameraBlackboard();
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const result = await mutation.mutateAsync({
+ operation: "VIEW",
+ path: "sessionStats",
+ });
+ if (!result.result || typeof result.result === "string") return;
+
+ dispatch({ type: "UPDATE", payload: result?.result });
+ };
+ fetchData();
+ }, []);
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/context/providers/NPEDUserContextProvider.tsx b/src/context/providers/NPEDUserContextProvider.tsx
deleted file mode 100644
index 17b0d69..0000000
--- a/src/context/providers/NPEDUserContextProvider.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { useEffect, useReducer, useState, type ReactNode } from "react";
-import type { DedupedSightings, NPEDUser, ReducedSightingType } from "../../types/types";
-import { NPEDUserContext } from "../NPEDUserContext";
-import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
-import { initialState, reducer } from "../reducers/NPEDContextReducer";
-
-type NPEDUserProviderType = {
- children: ReactNode;
-};
-
-export const NPEDUserProvider = ({ children }: NPEDUserProviderType) => {
- const [state, dispatch] = useReducer(reducer, initialState);
- const { mutation } = useCameraBlackboard();
- const [user, setUser] = useState(null);
- const [sessionStarted, setSessionStarted] = useState(false);
- const [sessionList, setSessionList] = useState([]);
- const [sessionPaused, setSessionPaused] = useState(false);
- const [savedSightings, setSavedSightings] = useState([]);
-
- useEffect(() => {
- const fetchData = async () => {
- const result = await mutation.mutateAsync({
- operation: "VIEW",
- path: "sessionStats",
- });
- if (!result.result) return;
- setSavedSightings(result?.result);
- };
- fetchData();
- }, []);
- console.log(savedSightings);
- return (
-
- {children}
-
- );
-};
diff --git a/src/context/reducers/IntegrationsContextReducer.ts b/src/context/reducers/IntegrationsContextReducer.ts
new file mode 100644
index 0000000..f740f4c
--- /dev/null
+++ b/src/context/reducers/IntegrationsContextReducer.ts
@@ -0,0 +1,46 @@
+import type { NPEDACTION, NPEDSTATE } from "../../types/types";
+
+export const initialState = {
+ sessionStarted: false,
+ sessionList: [],
+ sessionPaused: false,
+ savedSightings: [],
+ npedUser: null,
+};
+
+export function reducer(state: NPEDSTATE, action: NPEDACTION) {
+ switch (action.type) {
+ case "SESSIONSTART":
+ return {
+ ...state,
+ sessionStarted: action.payload,
+ };
+ case "LOGIN":
+ return {
+ ...state,
+ npedUser: action.payload,
+ };
+ case "LOGOUT":
+ return {
+ ...state,
+ npedUser: action.payload,
+ };
+ case "SESSIONPAUSE":
+ return {
+ ...state,
+ sessionPaused: action.payload,
+ };
+ case "ADD":
+ return {
+ ...state,
+ sessionList: [...state.sessionList, action.payload],
+ };
+ case "UPDATE":
+ return {
+ ...state,
+ sessionList: action.payload,
+ };
+ default:
+ return { ...state };
+ }
+}
diff --git a/src/context/reducers/NPEDContextReducer.ts b/src/context/reducers/NPEDContextReducer.ts
deleted file mode 100644
index 4349bbc..0000000
--- a/src/context/reducers/NPEDContextReducer.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { NPEDACTION, NPEDSTATE } from "../../types/types";
-
-export const initialState = {
- sessionStarted: false,
- sessionList: [],
- sessionPaused: false,
- savedSightings: [],
-};
-
-export function reducer(state: NPEDSTATE, action: NPEDACTION) {
- switch (action.type) {
- case "SESSION":
- return {
- ...state,
- sessionStarted: action.payload,
- };
- default:
- return { ...state };
- }
-}
diff --git a/src/hooks/useNPEDAuth.ts b/src/hooks/useNPEDAuth.ts
index 619f4e5..31c7ea7 100644
--- a/src/hooks/useNPEDAuth.ts
+++ b/src/hooks/useNPEDAuth.ts
@@ -1,6 +1,6 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import type { NPEDFieldType } from "../types/types";
-import { useNPEDContext } from "../context/NPEDUserContext";
+import { useIntegrationsContext } from "../context/IntegrationsContext";
import { useEffect } from "react";
import { CAM_BASE } from "../utils/config";
import { toast } from "sonner";
@@ -42,8 +42,7 @@ async function signIn(loginDetails: NPEDFieldType) {
}),
]);
- if (!frontRes.ok || !rearRes.ok)
- throw new Error("Cannot reach NPED endpoint");
+ if (!frontRes.ok || !rearRes.ok) throw new Error("Cannot reach NPED endpoint");
return {
frontResponse: frontRes.json(),
@@ -73,7 +72,7 @@ async function signOut() {
}
export const useNPEDAuth = () => {
- const { setUser, user } = useNPEDContext();
+ const { dispatch } = useIntegrationsContext();
const signInMutation = useMutation({
mutationKey: ["NPEDSignin"],
@@ -84,7 +83,8 @@ export const useNPEDAuth = () => {
onSuccess: async (data) => {
toast.dismiss();
toast.success("Signed in successfully!");
- setUser(await data.frontResponse);
+
+ dispatch({ type: "LOGIN", payload: await data.frontResponse });
},
onError: (error) => {
toast.dismiss();
@@ -101,7 +101,7 @@ export const useNPEDAuth = () => {
mutationFn: signOut,
onSuccess: () => {
toast.success("Signed out successfully");
- setUser(null);
+ dispatch({ type: "LOGOUT", payload: null });
},
onError: (error) => {
toast.error(`Sign-out failed: ${error.message}`);
@@ -115,11 +115,11 @@ export const useNPEDAuth = () => {
useEffect(() => {
if (fetchdataQuery.isSuccess && fetchdataQuery.data) {
- setUser(fetchdataQuery.data);
+ dispatch({ type: "LOGIN", payload: fetchdataQuery.data });
} else {
- setUser(null);
+ dispatch({ type: "LOGOUT", payload: null });
}
- }, [fetchdataQuery.data, fetchdataQuery.isSuccess, setUser]);
+ }, [dispatch, fetchdataQuery.data, fetchdataQuery.isSuccess]);
useEffect(() => {
if (fetchdataQuery.isError) toast.error(fetchdataQuery.error.message);
@@ -134,8 +134,6 @@ export const useNPEDAuth = () => {
data: signInMutation.data,
fetchdataQueryError: fetchdataQuery.error,
fetchdataQueryLoading: fetchdataQuery.isLoading,
- user,
- setUser,
signOut: signOutMutation.mutate,
};
};
diff --git a/src/types/types.ts b/src/types/types.ts
index ef5e406..45d8c79 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -387,6 +387,7 @@ export type NPEDSTATE = {
sessionList: ReducedSightingType[];
sessionPaused: boolean;
savedSightings: DedupedSightings;
+ npedUser: NPEDUser;
};
export type NPEDACTION = {