From 2528aced80da5d7015b4f0ae10ed134a9428d0c4 Mon Sep 17 00:00:00 2001 From: sayali-2308 Date: Sat, 16 May 2026 19:41:46 -0400 Subject: [PATCH 1/4] fix: resolve Firefox timer not stopping and audio not playing --- src/components/Timer/Timer.jsx | 89 ++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/src/components/Timer/Timer.jsx b/src/components/Timer/Timer.jsx index 1af28c5442..355ca4e951 100644 --- a/src/components/Timer/Timer.jsx +++ b/src/components/Timer/Timer.jsx @@ -1,30 +1,29 @@ /* eslint-disable jsx-a11y/media-has-caption */ +import cs from 'classnames'; import moment from 'moment'; -import { useState, useCallback, useEffect, useRef, useMemo } from 'react'; import PropTypes from 'prop-types'; -import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Progress } from 'reactstrap'; -import useWebSocket, { ReadyState } from 'react-use-websocket'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { BsAlarmFill, BsArrowClockwise } from 'react-icons/bs'; import { - FaPlusCircle, FaMinusCircle, - FaPlayCircle, FaPauseCircle, + FaPlayCircle, + FaPlusCircle, FaStopCircle, FaUndoAlt, } from 'react-icons/fa'; -import { toast } from 'react-toastify'; -import cs from 'classnames'; import { connect, useDispatch } from 'react-redux'; -import css from './Timer.module.css'; -import '../Header/index.css'; +import { toast } from 'react-toastify'; +import useWebSocket, { ReadyState } from 'react-use-websocket'; +import { Button, Modal, ModalBody, ModalFooter, ModalHeader, Progress } from 'reactstrap'; import { ENDPOINTS } from '~/utils/URL'; import config from '../../config.json'; +import '../Header/index.css'; import TimeEntryForm from '../Timelog/TimeEntryForm'; import Countdown from './Countdown'; -import TimerStatus from './TimerStatus'; +import css from './Timer.module.css'; import TimerPopout from './TimerPopout'; -import { postTimeEntry, editTimeEntry } from '../../actions/timeEntries'; +import TimerStatus from './TimerStatus'; function Timer({ authUser, darkMode, isPopout }) { const dispatch = useDispatch(); @@ -118,6 +117,7 @@ function Timer({ authUser, darkMode, isPopout }) { const isWSOpenRef = useRef(0); const timeIsOverAudioRef = useRef(null); const forcedPausedAudioRef = useRef(null); + const audioUnlockedRef = useRef(false); const timeToLog = moment.duration(goal - remaining); const logHours = timeToLog.hours(); @@ -127,7 +127,6 @@ function Timer({ authUser, darkMode, isPopout }) { // Enhanced function to clear submitted time with better logging const clearSubmittedTime = useCallback(() => { - console.log(' Clearing submitted time - Timer reset or user change detected'); setLastSubmittedTime(null); setLogTimer({ hours: 0, minutes: 0 }); setTimerState('idle'); @@ -147,8 +146,6 @@ function Timer({ authUser, darkMode, isPopout }) { remaining, }; - console.log('✅ Time submitted successfully:', submissionRecord); - setLastSubmittedTime(timeKey); setSubmissionHistory(prev => [...prev, submissionRecord]); setLogTimer({ hours: 0, minutes: 0 }); @@ -159,9 +156,7 @@ function Timer({ authUser, darkMode, isPopout }) { const existingHistory = JSON.parse(localStorage.getItem('timerSubmissionHistory') || '[]'); const updatedHistory = [...existingHistory, submissionRecord].slice(-10); // Keep last 10 entries localStorage.setItem('timerSubmissionHistory', JSON.stringify(updatedHistory)); - } catch (error) { - console.warn('Could not save submission history to localStorage:', error); - } + } catch (error) {} }, [goal, remaining, sessionId, viewingUserId, authUser?.userid], ); @@ -221,7 +216,6 @@ function Timer({ authUser, darkMode, isPopout }) { // Enhanced function to handle timer state changes const updateTimerState = useCallback( newState => { - console.log(`🔄 Timer state changed: ${timerState} → ${newState}`); setTimerState(newState); }, [timerState], @@ -252,7 +246,6 @@ function Timer({ authUser, darkMode, isPopout }) { } const newSessionId = `session_${Date.now()}_${randomPart}`; setSessionId(newSessionId); - console.log('🚀 Timer session initialized:', newSessionId); }, []); // Enhanced useEffect for timer state management @@ -266,6 +259,24 @@ function Timer({ authUser, darkMode, isPopout }) { } }, [running, paused, started, updateTimerState]); + useEffect(() => { + const unlockAudio = () => { + if (audioUnlockedRef.current) return; + [timeIsOverAudioRef, forcedPausedAudioRef].forEach(ref => { + if (ref.current) { + ref.current + .play() + .then(() => ref.current.pause()) + .catch(() => {}); + ref.current.currentTime = 0; + } + }); + audioUnlockedRef.current = true; + }; + document.addEventListener('click', unlockAudio, { once: true }); + return () => document.removeEventListener('click', unlockAudio); + }, []); + useEffect(() => { const handleStorageEvent = () => { const sessionStorageData = JSON.parse(window.sessionStorage.getItem('viewingUser')); @@ -474,7 +485,6 @@ function Timer({ authUser, darkMode, isPopout }) { return; } - console.log('🛑 Stop button clicked - preparing to log time:', timeToSubmit); toggleLogTimeModal(); }, [logHours, logMinutes, validateTimeForSubmission, toggleLogTimeModal]); @@ -567,22 +577,14 @@ function Timer({ authUser, darkMode, isPopout }) { }, [running]); useEffect(() => { - /** - * This useEffect will run upon message change, - * and message is a state as a copy of the lastJsonMessage, - * here message works as a buffer, for details see above - */ - let interval; - if (running) { - updateRemaining(); - interval = setInterval(() => { - updateRemaining(); - }, 1000); - } else { + if (!running) { setRemaining(time); - clearInterval(interval); + return undefined; } - + updateRemaining(); + const interval = setInterval(() => { + updateRemaining(); + }, 1000); return () => clearInterval(interval); }, [running, message]); @@ -597,7 +599,6 @@ function Timer({ authUser, darkMode, isPopout }) { if (lastSubmittedTime !== timeKey && (logHours > 0 || logMinutes > 0)) { if (validateTimeForSubmission(currentTimeToLog)) { setLogTimer(currentTimeToLog); - console.log('⏱️ Time to log updated:', currentTimeToLog); } } }, [remaining, logHours, logMinutes, goal, lastSubmittedTime, validateTimeForSubmission]); @@ -606,14 +607,16 @@ function Timer({ authUser, darkMode, isPopout }) { useEffect(() => { if (!started || goal !== lastSubmittedTime?.goal) { clearSubmittedTime(); - console.log('🔄 Timer reset detected - clearing submitted time'); } }, [started, goal, clearSubmittedTime]); useEffect(() => { if (timeIsOverModalOpen) { window.focus(); - timeIsOverAudioRef.current.play(); + const playPromise = timeIsOverAudioRef.current.play(); + if (playPromise !== undefined) { + playPromise.catch(() => {}); + } } else { window.focus(); timeIsOverAudioRef.current.pause(); @@ -631,7 +634,10 @@ function Timer({ authUser, darkMode, isPopout }) { useEffect(() => { if (inacModal) { window.focus(); - forcedPausedAudioRef.current.play(); + const playPromise = forcedPausedAudioRef.current.play(); + if (playPromise !== undefined) { + playPromise.catch(() => {}); + } } else { window.focus(); forcedPausedAudioRef.current.pause(); @@ -642,7 +648,10 @@ function Timer({ authUser, darkMode, isPopout }) { useEffect(() => { if (weekEndModal) { window.focus(); - timeIsOverAudioRef.current.play(); + const playPromise = timeIsOverAudioRef.current.play(); + if (playPromise !== undefined) { + playPromise.catch(() => {}); + } } else { window.focus(); timeIsOverAudioRef.current.pause(); @@ -659,14 +668,12 @@ function Timer({ authUser, darkMode, isPopout }) { // Enhanced cleanup when viewing user changes useEffect(() => { clearSubmittedTime(); - console.log('👤 User changed - clearing timer state'); }, [viewingUserId, clearSubmittedTime]); // Enhanced cleanup on component unmount useEffect(() => { return () => { clearSubmittedTime(); - console.log('🔚 Timer component unmounting - cleanup complete'); }; }, [clearSubmittedTime]); From 512ecb8ed8ac90dd5ab889f5a38fce969443a006 Mon Sep 17 00:00:00 2001 From: sayali-2308 Date: Sat, 16 May 2026 20:22:49 -0400 Subject: [PATCH 2/4] fix: resolve SonarCloud issues in Timer.jsx --- src/components/Timer/Timer.jsx | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/Timer/Timer.jsx b/src/components/Timer/Timer.jsx index 355ca4e951..3cf3e96dc0 100644 --- a/src/components/Timer/Timer.jsx +++ b/src/components/Timer/Timer.jsx @@ -156,7 +156,9 @@ function Timer({ authUser, darkMode, isPopout }) { const existingHistory = JSON.parse(localStorage.getItem('timerSubmissionHistory') || '[]'); const updatedHistory = [...existingHistory, submissionRecord].slice(-10); // Keep last 10 entries localStorage.setItem('timerSubmissionHistory', JSON.stringify(updatedHistory)); - } catch (error) {} + } catch (_error) { + // Ignore localStorage errors + } }, [goal, remaining, sessionId, viewingUserId, authUser?.userid], ); @@ -262,15 +264,18 @@ function Timer({ authUser, darkMode, isPopout }) { useEffect(() => { const unlockAudio = () => { if (audioUnlockedRef.current) return; - [timeIsOverAudioRef, forcedPausedAudioRef].forEach(ref => { - if (ref.current) { - ref.current - .play() - .then(() => ref.current.pause()) - .catch(() => {}); - ref.current.currentTime = 0; - } - }); + const tryUnlockRef = ref => { + if (!ref.current) return; + ref.current + .play() + .then(() => { + ref.current.pause(); + }) + .catch(() => {}); + ref.current.currentTime = 0; + }; + tryUnlockRef(timeIsOverAudioRef); + tryUnlockRef(forcedPausedAudioRef); audioUnlockedRef.current = true; }; document.addEventListener('click', unlockAudio, { once: true }); From 8830da394dc97cddb37328d92472d0a25e76ff18 Mon Sep 17 00:00:00 2001 From: sayali-2308 Date: Sat, 16 May 2026 20:28:47 -0400 Subject: [PATCH 3/4] fix: move pauseAudioRef outside useEffect to reduce nesting depth for SonarCloud --- src/components/Timer/Timer.jsx | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/Timer/Timer.jsx b/src/components/Timer/Timer.jsx index 3cf3e96dc0..a1bc4102be 100644 --- a/src/components/Timer/Timer.jsx +++ b/src/components/Timer/Timer.jsx @@ -157,7 +157,8 @@ function Timer({ authUser, darkMode, isPopout }) { const updatedHistory = [...existingHistory, submissionRecord].slice(-10); // Keep last 10 entries localStorage.setItem('timerSubmissionHistory', JSON.stringify(updatedHistory)); } catch (_error) { - // Ignore localStorage errors + // localStorage unavailable - non-critical, safe to ignore + void _error; } }, [goal, remaining, sessionId, viewingUserId, authUser?.userid], @@ -261,21 +262,23 @@ function Timer({ authUser, darkMode, isPopout }) { } }, [running, paused, started, updateTimerState]); + const pauseAudioRef = ref => { + if (!ref.current) return; + ref.current.pause(); + ref.current.currentTime = 0; + }; + useEffect(() => { const unlockAudio = () => { if (audioUnlockedRef.current) return; - const tryUnlockRef = ref => { - if (!ref.current) return; - ref.current - .play() - .then(() => { - ref.current.pause(); - }) - .catch(() => {}); - ref.current.currentTime = 0; - }; - tryUnlockRef(timeIsOverAudioRef); - tryUnlockRef(forcedPausedAudioRef); + if (timeIsOverAudioRef.current) { + timeIsOverAudioRef.current.play().catch(() => {}); + pauseAudioRef(timeIsOverAudioRef); + } + if (forcedPausedAudioRef.current) { + forcedPausedAudioRef.current.play().catch(() => {}); + pauseAudioRef(forcedPausedAudioRef); + } audioUnlockedRef.current = true; }; document.addEventListener('click', unlockAudio, { once: true }); From f797da609a77bf576e084bfe1c619bc19b287510 Mon Sep 17 00:00:00 2001 From: sayali-2308 Date: Sat, 23 May 2026 15:53:56 -0400 Subject: [PATCH 4/4] fix: trigger time complete modal and chime locally when timer reaches zero in Firefox --- src/components/Timer/Timer.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Timer/Timer.jsx b/src/components/Timer/Timer.jsx index a1bc4102be..28e818e901 100644 --- a/src/components/Timer/Timer.jsx +++ b/src/components/Timer/Timer.jsx @@ -506,6 +506,10 @@ function Timer({ authUser, darkMode, isPopout }) { const checkRemainingTime = () => { if (remaining === 0) { sendPause(); + if (!timeIsOverModalOpen && !weekEndModal) { + setTimeIsOverModalIsOpen(true); + sendStartChime(true); + } } };