From 8e3ddafe379b0b1370c41c33aafec15fa2614712 Mon Sep 17 00:00:00 2001 From: kmanish527 Date: Sun, 2 Nov 2025 17:04:44 +0530 Subject: [PATCH] Implemented standalone pomodoro --- frontend/package-lock.json | 64 ++++++- frontend/src/context/TimerContext.jsx | 245 ++++++++++++++++++++------ 2 files changed, 247 insertions(+), 62 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0549481..0dc823b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -532,6 +532,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -548,6 +549,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -564,6 +566,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -580,6 +583,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -596,6 +600,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -612,6 +617,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -628,6 +634,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -644,6 +651,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -660,6 +668,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -676,6 +685,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -692,6 +702,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -708,6 +719,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -724,6 +736,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -740,6 +753,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -756,6 +770,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -772,6 +787,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -788,6 +804,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -804,6 +821,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -820,6 +838,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -836,6 +855,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -852,6 +872,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -868,6 +889,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -884,6 +906,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -900,6 +923,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -916,6 +940,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -932,6 +957,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3119,6 +3145,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3132,6 +3159,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3145,6 +3173,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3158,6 +3187,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3171,6 +3201,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3184,6 +3215,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3197,6 +3229,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3210,6 +3243,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3223,6 +3257,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3236,6 +3271,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3249,6 +3285,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3262,6 +3299,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3275,6 +3313,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3288,6 +3327,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3301,6 +3341,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3314,6 +3355,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3327,6 +3369,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3340,6 +3383,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3353,6 +3397,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3366,6 +3411,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3875,6 +3921,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -3888,7 +3935,7 @@ "version": "24.1.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -3898,7 +3945,7 @@ "version": "19.1.9", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz", "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -3908,7 +3955,7 @@ "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -4985,6 +5032,7 @@ "version": "0.25.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -5666,6 +5714,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7801,6 +7850,7 @@ "version": "4.46.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.1.tgz", "integrity": "sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -8347,6 +8397,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -8363,6 +8414,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -8380,6 +8432,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8516,7 +8569,7 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -8709,6 +8762,7 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -8783,6 +8837,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -8800,6 +8855,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" diff --git a/frontend/src/context/TimerContext.jsx b/frontend/src/context/TimerContext.jsx index d36b0d6..15915d3 100644 --- a/frontend/src/context/TimerContext.jsx +++ b/frontend/src/context/TimerContext.jsx @@ -11,8 +11,12 @@ export function useTimer() { const SESSIONS_BEFORE_LONG_BREAK = 4; +const isExtensionInstalled = () => { + return window.chrome && window.chrome.runtime && EXTENSION_ID; +}; + const sendStartTimerMessage = (state) => { - if (window.chrome && window.chrome.runtime && EXTENSION_ID) { + if (isExtensionInstalled()) { chrome.runtime.sendMessage(EXTENSION_ID, { command: "startTimer", endTime: Date.now() + state.timeLeft * 1000, @@ -25,33 +29,27 @@ const sendStartTimerMessage = (state) => { SESSIONS_BEFORE_LONG_BREAK: SESSIONS_BEFORE_LONG_BREAK } }, (response) => { - if (chrome.runtime.lastError) { - console.error(`Could not start timer: ${chrome.runtime.lastError.message}`); - } else { - console.log("Extension acknowledged timer start.", response); - } + if (chrome.runtime.lastError) console.error(`Could not start timer: ${chrome.runtime.lastError.message}`); + else console.log("Extension acknowledged timer start.", response); }); - } else { - console.warn("Chrome Extension API not found."); } }; const sendStopTimerMessage = () => { - if (window.chrome && window.chrome.runtime && EXTENSION_ID) { + if (isExtensionInstalled()) { chrome.runtime.sendMessage(EXTENSION_ID, { command: "stopTimer" }, (response) => { - if (chrome.runtime.lastError) { - console.error(`Could not stop timer: ${chrome.runtime.lastError.message}`); - } else { - console.log("Extension acknowledged timer stop.", response); - } + if (chrome.runtime.lastError) console.error(`Could not stop timer: ${chrome.runtime.lastError.message}`); + else console.log("Extension acknowledged timer stop.", response); }); } }; export function TimerProvider({ children }) { + const [isExtensionConnected, setIsExtensionConnected] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [workTime, setWorkTime] = useState(() => { const saved = localStorage.getItem("devsync-workTime"); return saved ? Number(saved) : 25 * 60; @@ -68,37 +66,165 @@ export function TimerProvider({ children }) { const [isRunning, setIsRunning] = useState(false); const [sessionType, setSessionType] = useState('work'); const [sessions, setSessions] = useState(0); - const [timeLeft, setTimeLeft] = useState(workTime); + const [timeLeft, setTimeLeft] = useState(workTime); + const [endTimestamp, setEndTimestamp] = useState(null); + + useEffect(() => localStorage.setItem("devsync-workTime", workTime), [workTime]); + useEffect(() => localStorage.setItem("devsync-shortBreak", shortBreak), [shortBreak]); + useEffect(() => localStorage.setItem("devsync-longBreak", longBreak), [longBreak]); + + useEffect(() => { + if (isLoading || isExtensionConnected) { + return; + } + + localStorage.setItem("pomodoroTimeLeft", timeLeft); + localStorage.setItem("pomodoroSessionType", sessionType); + localStorage.setItem("pomodoroSessions", sessions); + if (endTimestamp) { + localStorage.setItem("pomodoroEndTimestamp", endTimestamp); + } else { + localStorage.removeItem("pomodoroEndTimestamp"); + } + }, [timeLeft, sessionType, sessions, endTimestamp, isRunning, isExtensionConnected, isLoading]); useEffect(() => { - if (window.chrome && window.chrome.runtime && EXTENSION_ID) { + const savedWorkTime = Number(localStorage.getItem("devsync-workTime")) || 25 * 60; + const savedPausedTime = Number(localStorage.getItem("pomodoroTimeLeft")); + const savedType = localStorage.getItem("pomodoroSessionType") || 'work'; + const savedSessions = Number(localStorage.getItem("pomodoroSessions")) || 0; + + const setFinalState = (mode, state) => { + setIsExtensionConnected(mode === 'extension'); + setIsRunning(state.isRunning); + setSessionType(state.sessionType); + setSessions(state.sessions); + setTimeLeft(state.timeLeft); + setEndTimestamp(state.endTimestamp); + setIsLoading(false); + }; + + if (isExtensionInstalled()) { chrome.runtime.sendMessage(EXTENSION_ID, { command: "getState" }, (state) => { if (chrome.runtime.lastError) { - console.warn(`Could not get state from extension: ${chrome.runtime.lastError.message}`); - } else if (state) { - console.log("Got initial state from extension:", state); - setIsRunning(state.isRunning || false); - setSessionType(state.timerSessionType || 'work'); - setSessions(state.timerSessionCount || 0); - - if (state.isRunning && state.timerEndTime) { - const remaining = Math.max(Math.ceil((state.timerEndTime - Date.now()) / 1000), 0); - setTimeLeft(remaining); + console.warn(`Extension connection failed: ${chrome.runtime.lastError.message}. Running in standalone mode.`); + const savedEndTimestamp = Number(localStorage.getItem("pomodoroEndTimestamp")); + if (savedEndTimestamp && savedEndTimestamp > Date.now()) { + setFinalState('standalone', { + isRunning: true, + sessionType: savedType, + sessions: savedSessions, + timeLeft: Math.max(Math.ceil((savedEndTimestamp - Date.now()) / 1000), 0), + endTimestamp: savedEndTimestamp + }); } else { - const currentWorkTime = Number(localStorage.getItem("devsync-workTime")) || 25 * 60; - setTimeLeft(currentWorkTime); + setFinalState('standalone', { + isRunning: false, + sessionType: savedType, + sessions: savedSessions, + timeLeft: savedPausedTime || savedWorkTime, + endTimestamp: null + }); } + } else if (state) { + console.log("Extension connected. Running in extension-controlled mode.", state); + setFinalState('extension', { + isRunning: state.isRunning || false, + sessionType: state.timerSessionType || 'work', + sessions: state.timerSessionCount || 0, + timeLeft: (state.isRunning && state.timerEndTime) + ? Math.max(Math.ceil((state.timerEndTime - Date.now()) / 1000), 0) + : (savedPausedTime || savedWorkTime), + endTimestamp: null + }); } }); + } else { + console.log("No extension found. Running in standalone mode."); + const savedEndTimestamp = Number(localStorage.getItem("pomodoroEndTimestamp")); + if (savedEndTimestamp && savedEndTimestamp > Date.now()) { + setFinalState('standalone', { + isRunning: true, + sessionType: savedType, + sessions: savedSessions, + timeLeft: Math.max(Math.ceil((savedEndTimestamp - Date.now()) / 1000), 0), + endTimestamp: savedEndTimestamp + }); + } else { + setFinalState('standalone', { + isRunning: false, + sessionType: savedType, + sessions: savedSessions, + timeLeft: savedPausedTime || savedWorkTime, + endTimestamp: null + }); + } + } + }, []); + useEffect(() => { + if (isRunning && !isExtensionConnected) { + const timerId = setInterval(() => { + if (endTimestamp) { + const now = Date.now(); + const remaining = Math.max(Math.ceil((endTimestamp - now) / 1000), 0); + setTimeLeft(remaining); + + if (remaining <= 0) { + handleLocalSessionEnd(); + } + } + }, 250); + return () => clearInterval(timerId); + } + }, [isRunning, isExtensionConnected, endTimestamp]); + + const handleLocalSessionEnd = () => { + if ("Notification" in window && Notification.permission === "granted") { + new Notification( + sessionType === 'work' ? "Work session complete! Time for a break" : "Break over! Back to work" + ); + } + + let nextSessionType; + let nextTime; + + if (sessionType === 'work') { + const nextSessionNum = sessions + 1; + setSessions(nextSessionNum); + if (nextSessionNum % SESSIONS_BEFORE_LONG_BREAK === 0) { + nextSessionType = 'longBreak'; + nextTime = longBreak; + } else { + nextSessionType = 'shortBreak'; + nextTime = shortBreak; + } + } else { + if (sessionType === 'longBreak') { + setIsRunning(false); + setEndTimestamp(null); + setSessions(0); + setSessionType('work'); + setTimeLeft(workTime); + return; + } else { + nextSessionType = 'work'; + nextTime = workTime; + } } - }, []); + + const newEndTimestamp = Date.now() + nextTime * 1000; + setSessionType(nextSessionType); + setTimeLeft(nextTime); + setEndTimestamp(newEndTimestamp); + }; useEffect(() => { + if (!isExtensionConnected) return; + const handleMessage = (event) => { if (event.source !== window || !event.data || event.data.type !== "FROM_DEVSYNC_EXTENSION") { return; } - const message = event.data.payload; if (message.action === "updateTime") { @@ -125,32 +251,39 @@ export function TimerProvider({ children }) { window.addEventListener("message", handleMessage); return () => window.removeEventListener("message", handleMessage); - }, []); - - useEffect(() => localStorage.setItem("devsync-workTime", workTime), [workTime]); - useEffect(() => localStorage.setItem("devsync-shortBreak", shortBreak), [shortBreak]); - useEffect(() => localStorage.setItem("devsync-longBreak", longBreak), [longBreak]); + }, [isExtensionConnected]); const startTimer = () => { setIsRunning(true); - const savedTimeLeft = localStorage.getItem("pomodoroTimeLeft"); const timeToStart = savedTimeLeft ? Number(savedTimeLeft) : (timeLeft > 0 ? timeLeft : workTime); localStorage.removeItem("pomodoroTimeLeft"); - - sendStartTimerMessage({ - timeLeft: timeToStart, - sessionType, - sessions, - workTime, - shortBreak, - longBreak - }); + + if (isExtensionConnected) { + sendStartTimerMessage({ + timeLeft: timeToStart, + sessionType, + sessions, + workTime, + shortBreak, + longBreak + }); + } else { + console.warn( + "DevSync: Extension not detected. Running in standalone mode.\n" + ); + setEndTimestamp(Date.now() + timeToStart * 1000); + setTimeLeft(timeToStart); + } }; const pauseTimer = () => { setIsRunning(false); - sendStopTimerMessage(); + if (isExtensionConnected) { + sendStopTimerMessage(); + } else { + setEndTimestamp(null); + } localStorage.setItem("pomodoroTimeLeft", timeLeft); }; @@ -159,32 +292,28 @@ export function TimerProvider({ children }) { setSessionType('work'); setTimeLeft(workTime); setSessions(0); - sendStopTimerMessage(); + setEndTimestamp(null); + + if (isExtensionConnected) { + sendStopTimerMessage(); + } localStorage.removeItem("pomodoroTimeLeft"); }; - const updateWorkTime = (minutes) => { const secs = Math.max(1, minutes) * 60; setWorkTime(secs); - if (sessionType === 'work' && !isRunning) { - setTimeLeft(secs); - } + if (sessionType === 'work' && !isRunning) setTimeLeft(secs); }; const updateShortBreak = (minutes) => { const secs = Math.max(1, minutes) * 60; setShortBreak(secs); - if (sessionType === 'shortBreak' && !isRunning) { - setTimeLeft(secs); - } + if (sessionType === 'shortBreak' && !isRunning) setTimeLeft(secs); }; const updateLongBreak = (minutes) => { const secs = Math.max(1, minutes) * 60; setLongBreak(secs); - if (sessionType === 'longBreak' && !isRunning) { - setTimeLeft(secs); - } + if (sessionType === 'longBreak' && !isRunning) setTimeLeft(secs); }; - useEffect(() => { if ("Notification" in window && Notification.permission !== "granted") { Notification.requestPermission();