From 68e944a6a24a50b651277049d32e3d9a6e2907b0 Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Wed, 1 Oct 2025 15:21:07 +0100 Subject: [PATCH] Refactor sound context and update sound settings functionality; remove console logs and improve sound file handling --- .../CameraSettings/CameraSettingFields.tsx | 3 +- .../Sound/SoundSettingsFields.tsx | 25 ++++-------- .../SettingForms/Sound/SoundUpload.tsx | 39 +++++++++++++++---- .../SightingsWidget/SightingWidget.tsx | 6 +-- src/context/SoundContext.ts | 4 +- .../providers/SoundContextProvider.tsx | 11 +++--- src/context/reducers/SoundContextReducer.ts | 25 +++++++++--- src/hooks/useSightingFeed.ts | 18 +++++++-- src/pages/Dashboard.tsx | 1 - src/types/types.ts | 19 +++++++-- src/utils/utils.ts | 2 +- 11 files changed, 100 insertions(+), 53 deletions(-) diff --git a/src/components/CameraSettings/CameraSettingFields.tsx b/src/components/CameraSettings/CameraSettingFields.tsx index 90cf47b..284d249 100644 --- a/src/components/CameraSettings/CameraSettingFields.tsx +++ b/src/components/CameraSettings/CameraSettingFields.tsx @@ -24,7 +24,7 @@ const CameraSettingFields = ({ onZoomLevelChange, }: CameraSettingsProps) => { const [showPwd, setShowPwd] = useState(false); - console.log(initialData); + const initialValues = useMemo( () => ({ friendlyName: initialData?.id ?? "", @@ -48,7 +48,6 @@ const CameraSettingFields = ({ }; const handleSubmit = (values: CameraSettingValues) => { - console.log(values); updateCameraConfig(values); }; diff --git a/src/components/SettingForms/Sound/SoundSettingsFields.tsx b/src/components/SettingForms/Sound/SoundSettingsFields.tsx index 0b5c5d0..af5e8f1 100644 --- a/src/components/SettingForms/Sound/SoundSettingsFields.tsx +++ b/src/components/SettingForms/Sound/SoundSettingsFields.tsx @@ -12,20 +12,10 @@ const SoundSettingsFields = () => { { name: "hotlist2", sound: "" }, ]; - const soundOptions = [ - { - value: "switch", - label: "Switch (Default)", - }, - { - value: "notification", - label: "Notification", - }, - { - value: "popup", - label: "popup", - }, - ]; + const soundOptions = state?.soundOptions?.map((soundOption) => ({ + value: soundOption?.name, + label: soundOption?.name, + })); const initialValues: FormValues = { sightingSound: state.sightingSound ?? "switch", @@ -37,7 +27,6 @@ const SoundSettingsFields = () => { dispatch({ type: "UPDATE", payload: values }); toast.success("Sound settings updated"); }; - console.log(state); return ( {({ values }) => ( @@ -49,7 +38,7 @@ const SoundSettingsFields = () => { name="sightingSound" className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445] w-full md:w-60" > - {soundOptions.map(({ value, label }) => { + {soundOptions?.map(({ value, label }) => { return ( @@ -97,7 +86,7 @@ const SoundSettingsFields = () => { id={`hotlists.${index}.sound`} className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445] w-full md:w-60" > - {soundOptions.map(({ value, label }) => ( + {soundOptions?.map(({ value, label }) => ( diff --git a/src/components/SettingForms/Sound/SoundUpload.tsx b/src/components/SettingForms/Sound/SoundUpload.tsx index 46dc654..4326746 100644 --- a/src/components/SettingForms/Sound/SoundUpload.tsx +++ b/src/components/SettingForms/Sound/SoundUpload.tsx @@ -1,38 +1,61 @@ import { Form, Formik } from "formik"; import FormGroup from "../components/FormGroup"; import type { SoundUploadValue } from "../../../types/types"; +import { useSoundContext } from "../../../context/SoundContext"; +import { toast } from "sonner"; const SoundUpload = () => { + const { dispatch } = useSoundContext(); const initialValues: SoundUploadValue = { + name: "", soundFile: null, }; const handleSubmit = (values: SoundUploadValue) => { - console.log(values); + if (!values.soundFile) { + toast.warning("Please select an audio file"); + } else { + dispatch({ type: "ADD", payload: values }); + toast.success("Sound file upload successfully"); + } }; return ( - - {({ setFieldValue }) => ( + + {({ setFieldValue, errors, setFieldError }) => (
- + { if ( - e.target.files && - e.target.files[0].type.lastIndexOf(".mp3") - ) - setFieldValue("sightingSound", e.target.files[0]); + e.target?.files && + e.target?.files[0]?.type === "audio/mpeg" + ) { + setFieldValue("name", e.target.files[0].name); + setFieldValue("soundFile", e.target.files[0]); + } else { + setFieldError("soundFile", "Not an mp3 file"); + toast.error("Not an mp3 file"); + } }} /> + {errors.soundFile && ( +

Not an mp3 file

+ )} diff --git a/src/components/SightingsWidget/SightingWidget.tsx b/src/components/SightingsWidget/SightingWidget.tsx index 2ea65b7..ee5936d 100644 --- a/src/components/SightingsWidget/SightingWidget.tsx +++ b/src/components/SightingsWidget/SightingWidget.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { SightingType } from "../../types/types"; -import { BLANK_IMG, getSoundFileName } from "../../utils/utils"; +import { BLANK_IMG, getSoundFileURL } from "../../utils/utils"; import NumberPlate from "../PlateStack/NumberPlate"; import Card from "../UI/Card"; import CardHeader from "../UI/CardHeader"; @@ -43,8 +43,8 @@ export default function SightingHistoryWidget({ const { state } = useSoundContext(); const soundSrc = useMemo(() => { - return getSoundFileName(state.sightingSound) ?? popup; - }, [state.sightingSound]); + return getSoundFileURL(state.NPEDsound) ?? popup; + }, [state.NPEDsound]); const { play } = useSound(soundSrc); const { diff --git a/src/context/SoundContext.ts b/src/context/SoundContext.ts index e7938f3..d115d42 100644 --- a/src/context/SoundContext.ts +++ b/src/context/SoundContext.ts @@ -1,9 +1,9 @@ import { createContext, useContext, type Dispatch } from "react"; -import type { SoundPayload, SoundState } from "../types/types"; +import type { SoundAction, SoundState } from "../types/types"; type SoundContextType = { state: SoundState; - dispatch: Dispatch; + dispatch: Dispatch; }; export const SoundContext = createContext( diff --git a/src/context/providers/SoundContextProvider.tsx b/src/context/providers/SoundContextProvider.tsx index b7e1b6a..20211df 100644 --- a/src/context/providers/SoundContextProvider.tsx +++ b/src/context/providers/SoundContextProvider.tsx @@ -1,17 +1,16 @@ -import { useReducer, type ReactNode } from "react"; +import { useMemo, useReducer, type ReactNode } from "react"; import { SoundContext } from "../SoundContext"; -import { initalState, reducer } from "../reducers/SoundContextReducer"; +import { initialState, reducer } from "../reducers/SoundContextReducer"; type SoundContextProviderProps = { children: ReactNode; }; const SoundContextProvider = ({ children }: SoundContextProviderProps) => { - const [state, dispatch] = useReducer(reducer, initalState); + const [state, dispatch] = useReducer(reducer, initialState); + const value = useMemo(() => ({ state, dispatch }), [state, dispatch]); return ( - - {children} - + {children} ); }; diff --git a/src/context/reducers/SoundContextReducer.ts b/src/context/reducers/SoundContextReducer.ts index 1887518..d5b4b2c 100644 --- a/src/context/reducers/SoundContextReducer.ts +++ b/src/context/reducers/SoundContextReducer.ts @@ -1,14 +1,19 @@ -import type { SoundPayload, SoundState } from "../../types/types"; +import type { SoundAction, SoundState } from "../../types/types"; -export const initalState: SoundState = { +export const initialState: SoundState = { sightingSound: "switch", NPEDsound: "popup", hotlists: [], + soundOptions: [ + { name: "switch (Default)", soundFile: null }, + { name: "popup", soundFile: null }, + { name: "notification", soundFile: null }, + ], }; -export function reducer(state: SoundState, action: SoundPayload) { +export function reducer(state: SoundState, action: SoundAction): SoundState { switch (action.type) { - case "UPDATE": + case "UPDATE": { return { ...state, sightingSound: action.payload.sightingSound, @@ -18,6 +23,16 @@ export function reducer(state: SoundState, action: SoundPayload) { sound: hotlist.sound, })), }; + } + + case "ADD": { + return { + ...state, + soundOptions: [...(state.soundOptions ?? []), action.payload], + }; + } + + default: + return state; } - return state; } diff --git a/src/hooks/useSightingFeed.ts b/src/hooks/useSightingFeed.ts index 102fb46..8f742ff 100644 --- a/src/hooks/useSightingFeed.ts +++ b/src/hooks/useSightingFeed.ts @@ -2,9 +2,8 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import type { SightingType } from "../types/types"; import { useSoundOnChange } from "react-sounds"; - import { useSoundContext } from "../context/SoundContext"; -import { getSoundFileName } from "../utils/utils"; +import { getSoundFileURL } from "../utils/utils"; import switchSound from "../assets/sounds/ui/switch.mp3"; async function fetchSighting( @@ -27,11 +26,22 @@ export function useSightingFeed(url: string | undefined) { const [selectedSighting, setSelectedSighting] = useState( null ); + const first = useRef(true); + + const trigger = useMemo(() => { + if (latestRef == null) return null; + if (first.current) { + first.current = false; + return Symbol("skip"); + } + return latestRef; + }, [latestRef]); const soundSrc = useMemo(() => { - return getSoundFileName(state.sightingSound) ?? switchSound; + return getSoundFileURL(state.sightingSound) ?? switchSound; }, [state.sightingSound]); - useSoundOnChange(soundSrc, latestRef, { + //use latestref instead of trigger to revert back + useSoundOnChange(soundSrc, trigger, { volume: 1, }); diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index b46ffe1..fb0182a 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -7,7 +7,6 @@ const Dashboard = () => { const mode = import.meta.env.MODE; const base_url = `${CAM_BASE}/SightingList/sightingSummary?mostRecentRef=`; console.log(mode); - console.log(base_url); return (
diff --git a/src/types/types.ts b/src/types/types.ts index 2f0cbf9..c3036aa 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -276,6 +276,7 @@ export type FormValues = { }; export type SoundUploadValue = { + name: string; soundFile: File | null; }; @@ -283,9 +284,21 @@ export type SoundState = { sightingSound: SoundValue; NPEDsound: SoundValue; hotlists: Hotlist[]; + soundOptions?: SoundUploadValue[]; }; -export type SoundPayload = { - type: string; - payload: SoundState; +type UpdateAction = { + type: "UPDATE"; + payload: { + sightingSound: SoundValue; + NPEDsound: SoundValue; + hotlists: Hotlist[]; + }; }; + +type AddAction = { + type: "ADD"; + payload: SoundUploadValue; +}; + +export type SoundAction = UpdateAction | AddAction; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index fd4a7d3..296a4a3 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,7 +2,7 @@ import switchSound from "../assets/sounds/ui/switch.mp3"; import popup from "../assets/sounds/ui/popup_open.mp3"; import notification from "../assets/sounds/ui/notification.mp3"; -export function getSoundFileName(name: string) { +export function getSoundFileURL(name: string) { const sounds: Record = { switch: switchSound, popup: popup,