diff --git a/extension/alarm.mp3 b/extension/alarm.mp3 new file mode 100644 index 0000000..e3cb67d Binary files /dev/null and b/extension/alarm.mp3 differ diff --git a/extension/background.js b/extension/background.js index 072fbac..dd167fc 100644 --- a/extension/background.js +++ b/extension/background.js @@ -1,70 +1,213 @@ const ALLOWED_DOMAINS = [ - 'localhost', - 'your-devsync-domain.com', + 'localhost', + 'devsync-one.vercel.app', 'stackoverflow.com', 'developer.mozilla.org', - - // --- Social Profiles --- 'github.com', 'gitlab.com', 'linkedin.com', - - // --- Competitive Coding --- 'leetcode.com', 'codechef.com', 'hackerrank.com', 'codeforces.com', 'hackerearth.com' - - // Add any other sites you need (e.G., 'vercel.app', 'aws.amazon.com') ]; +let creating; +async function setupOffscreenDocument(path) { + if (await chrome.offscreen.hasDocument()) return; + if (creating) { + await creating; + } else { + creating = chrome.offscreen.createDocument({ + url: path, + reasons: ['AUDIO_PLAYBACK'], + justification: 'To play alarm sound for Pomodoro timer', + }); + await creating; + creating = null; + } +} -chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) => { - if (request.command === "startFocus") { - console.log(`Focus session starting for ${request.duration} minutes.`); - chrome.storage.local.set({ isFocusActive: true }); - closeUnwantedTabs(); - if (request.duration && request.duration > 0) { - chrome.alarms.create("stopFocus", { delayInMinutes: request.duration }); +async function playSound() { + await setupOffscreenDocument('offscreen.html'); + await chrome.runtime.sendMessage({ action: "playSound" }); +} + +chrome.alarms.onAlarm.addListener(async (alarm) => { + if (alarm.name === "devsync-tick") { + + const data = await chrome.storage.local.get([ + 'timerEndTime', 'timerSessionType', 'timerSessionCount', + 'workTime', 'shortBreak', 'longBreak', 'SESSIONS_BEFORE_LONG_BREAK' + ]); + + if (!data.timerEndTime) { + stopTimerSession(false); + return; } - sendResponse({ status: "Focus started" }); + const now = Date.now(); + const remaining = data.timerEndTime - now; + const timeLeftInSeconds = Math.ceil(remaining / 1000); + + if (timeLeftInSeconds > 0) { + broadcastToAllTabs({ + action: "updateTime", + timeLeft: timeLeftInSeconds, + }); + chrome.alarms.create("devsync-tick", { delayInMinutes: 1/60 }); + } else { + handleSessionEnd(data); + } + } +}); + + +function handleSessionEnd(data) { + playSound(); + + let nextSessionType; + let nextTime; + let nextSessionCount = data.timerSessionCount; - } else if (request.command === "stopFocus") { - console.log("Focus session stopping by user request."); - stopFocusSession(); - sendResponse({ status: "Focus stopped" }); + if (data.timerSessionType === 'work') { + nextSessionCount = data.timerSessionCount + 1; + if (nextSessionCount % data.SESSIONS_BEFORE_LONG_BREAK === 0) { + nextSessionType = 'longBreak'; + nextTime = data.longBreak; + } else { + nextSessionType = 'shortBreak'; + nextTime = data.shortBreak; + } + } else { + if (data.timerSessionType === 'longBreak') { + chrome.notifications.create({ + type: 'basic', + iconUrl: 'icon128.png', + title: 'Cycle Complete!', + message: 'Great work! You finished all 4 sessions. Time for a well-deserved break.' + }); + + stopTimerSession(true); + return; + } else { + nextSessionType = 'work'; + nextTime = data.workTime; + } + } + + const newEndTime = Date.now() + nextTime * 1000; + startTimer(newEndTime, nextSessionType, nextSessionCount, data.workTime, data.shortBreak, data.longBreak, data.SESSIONS_BEFORE_LONG_BREAK); +} + +function startTimer(endTime, sessionType, sessionCount, workTime, shortBreak, longBreak, sessionsBeforeLongBreak) { + const isWorkSession = sessionType === 'work'; + + chrome.storage.local.set({ + isRunning: true, + isFocusActive: isWorkSession, + timerEndTime: endTime, + timerSessionType: sessionType, + timerSessionCount: sessionCount, + workTime: workTime, + shortBreak: shortBreak, + longBreak: longBreak, + SESSIONS_BEFORE_LONG_BREAK: sessionsBeforeLongBreak + }, () => { + + if (isWorkSession) { + closeUnwantedTabs(); + } + + broadcastToAllTabs({ + action: "startTimer", + endTime: endTime, + sessionType: sessionType, + sessionCount: sessionCount + }); + + chrome.alarms.create("devsync-tick", { delayInMinutes: 1/60 }); + }); +} + +chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) => { + if (request.command === "startTimer") { + console.log("BG: Received startTimer from React"); + startTimer( + request.endTime, + request.sessionType, + request.sessionCount, + request.settings.workTime, + request.settings.shortBreak, + request.settings.longBreak, + request.settings.SESSIONS_BEFORE_LONG_BREAK + ); + sendResponse({ status: "Timer started" }); + + } else if (request.command === "stopTimer") { + console.log("BG: Received stopTimer from React"); + stopTimerSession(true); + sendResponse({ status: "Timer stopped" }); + + } else if (request.command === "getState") { + console.log("BG: Received getState from React"); + chrome.storage.local.get([ + 'isRunning', 'timerEndTime', 'timerSessionType', 'timerSessionCount' + ], (data) => { + sendResponse(data); + }); + return true; } return true; }); -chrome.alarms.onAlarm.addListener((alarm) => { - if (alarm.name === "stopFocus") { - console.log("Alarm fired: Focus session stopping."); - stopFocusSession(); +function stopTimerSession(broadcastUpdate) { + chrome.alarms.clear("devsync-tick"); + + chrome.storage.local.set({ + isRunning: false, + isFocusActive: false, + timerEndTime: null, + timerSessionType: 'work', + timerSessionCount: 0 + }); + + if (broadcastUpdate) { + broadcastToAllTabs({ action: "stopTimer" }); } -}); + + console.log("Timer session deactivated. All sites unblocked."); +} -function stopFocusSession() { - chrome.storage.local.set({ isFocusActive: false }); - chrome.alarms.clear("stopFocus"); - console.log("Focus session deactivated. All sites unblocked."); +function broadcastToAllTabs(message) { + chrome.tabs.query({}, (tabs) => { + for (let tab of tabs) { + chrome.tabs.sendMessage(tab.id, message, (response) => { + if (chrome.runtime.lastError) { } + }); + } + }); } function closeUnwantedTabs() { chrome.tabs.query({}, (tabs) => { for (let tab of tabs) { if (!tab.url) continue; - try { const url = new URL(tab.url); - const domain = url.hostname.replace('www.', ''); - const isAllowed = ALLOWED_DOMAINS.some(allowedDomain => domain.endsWith(allowedDomain)); - + const domain = url.hostname; + const isAllowed = ALLOWED_DOMAINS.some(allowedDomain => + domain === allowedDomain || domain.endsWith('.' + allowedDomain) + ); if (!isAllowed) { if (!tab.pinned && !tab.url.startsWith('chrome://')) { - chrome.tabs.remove(tab.id); + console.log(`DevSync: Closing distraction tab: ${tab.url}`); + chrome.tabs.remove(tab.id, () => { + if (chrome.runtime.lastError) { + console.warn(`DevSync: Error closing tab (ignoring): ${chrome.runtime.lastError.message}`); + } + }); } } } catch (e) { @@ -75,22 +218,24 @@ function closeUnwantedTabs() { } chrome.webNavigation.onBeforeNavigate.addListener((details) => { - if (details.frameId !== 0) { return; } - chrome.storage.local.get("isFocusActive", (data) => { if (data.isFocusActive) { const url = new URL(details.url); - const domain = url.hostname.replace('www.', ''); - - const isAllowed = ALLOWED_DOMAINS.some(allowedDomain => domain.endsWith(allowedDomain)); - + const domain = url.hostname; + const isAllowed = ALLOWED_DOMAINS.some(allowedDomain => + domain === allowedDomain || domain.endsWith('.' + allowedDomain) + ); if (!isAllowed && !details.url.startsWith('chrome://')) { console.log(`Blocking navigation to: ${details.url}`); - chrome.tabs.remove(details.tabId); + chrome.tabs.remove(details.tabId, () => { + if (chrome.runtime.lastError) { + console.warn(`DevSync: Error blocking navigation (ignoring): ${chrome.runtime.lastError.message}`); + } + }); } } }); -}, { url: [{ hostContains: '.' }] }); \ No newline at end of file +}, { url: [{ hostContains: '.' }] }); diff --git a/extension/content.js b/extension/content.js new file mode 100644 index 0000000..5c959db --- /dev/null +++ b/extension/content.js @@ -0,0 +1,108 @@ +let timerDiv = null; +let removalTimeout = null; +const TIMER_DIV_ID = 'devsync-floating-timer'; +let timeSpan = null; +let sessionSpan = null; + +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === 'startTimer') { + createOrUpdateTimerUI(message.endTime, message.sessionType, message.sessionCount); + } + else if (message.action === 'updateTime') { + chrome.storage.local.get(['timerEndTime', 'timerSessionType', 'timerSessionCount'], (data) => { + if(data.timerEndTime) { + updateTimerText(data.timerEndTime, data.timerSessionType, data.timerSessionCount); + } + }); + } + else if (message.action === 'hideTimer' || message.action === 'stopTimer') { + removeTimerUI(); + } + + const isAppPage = window.location.host === "localhost:5173" || + window.location.host === "your-devsync-domain.com"; + if (isAppPage) { + window.postMessage({ type: "FROM_DEVSYNC_EXTENSION", payload: message }, "*"); + } + sendResponse(true); +}); + +chrome.storage.local.get(['isRunning', 'timerEndTime', 'timerSessionType', 'timerSessionCount'], (result) => { + if (result.isRunning && result.timerEndTime) { + if (result.timerEndTime > Date.now()) { + createOrUpdateTimerUI(result.timerEndTime, result.timerSessionType, result.timerSessionCount); + } + } +}); + +function createOrUpdateTimerUI(endTime, sessionType, sessionCount) { + if (removalTimeout) { + clearTimeout(removalTimeout); + removalTimeout = null; + } + + if (!timerDiv) { + timerDiv = document.createElement('div'); + timerDiv.id = TIMER_DIV_ID; + timeSpan = document.createElement('span'); + timeSpan.className = 'devsync-time'; + sessionSpan = document.createElement('span'); + sessionSpan.className = 'devsync-session'; + timerDiv.appendChild(timeSpan); + timerDiv.appendChild(sessionSpan); + document.body.appendChild(timerDiv); + } + + if (sessionType === 'work') { + timerDiv.classList.add('devsync-timer-work'); + timerDiv.classList.remove('devsync-timer-break'); + } else { + timerDiv.classList.add('devsync-timer-break'); + timerDiv.classList.remove('devsync-timer-work'); + } + timerDiv.classList.remove('devsync-timer-flashing'); + + updateTimerText(endTime, sessionType, sessionCount); +} + +function updateTimerText(endTime, sessionType, sessionCount) { + if (!timerDiv) return; + + const now = Date.now(); + const remaining = endTime - now; + const totalSeconds = Math.ceil(remaining / 1000); + + if (sessionType === 'work') { + sessionSpan.textContent = `Session ${sessionCount + 1}`; + } else { + sessionSpan.textContent = 'Break'; + } + + if (totalSeconds <= 0) { + timeSpan.textContent = '00:00'; + timerDiv.classList.add('devsync-timer-flashing'); + removalTimeout = setTimeout(removeTimerUI, 3000); + } else { + const minutes = Math.floor(totalSeconds / 60); + const seconds = Math.floor(totalSeconds % 60); + timeSpan.textContent = + `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; + } +} + +function removeTimerUI() { + if (removalTimeout) { + clearTimeout(removalTimeout); + removalTimeout = null; + } + if (timerDiv) { + timerDiv.remove(); + timerDiv = null; + timeSpan = null; + sessionSpan = null; + } + const existingDiv = document.getElementById(TIMER_DIV_ID); + if (existingDiv) { + existingDiv.remove(); + } +} \ No newline at end of file diff --git a/extension/manifest.json b/extension/manifest.json index 0461473..c600528 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,13 +1,14 @@ { "manifest_version": 3, "name": "DevSync Enforcer", - "version": "1.0", + "version": "1.1", "description": "Companion extension for the DevSync Pomodoro app.", "permissions": [ "tabs", "storage", "webNavigation", - "alarms" + "offscreen", + "alarms" ], "background": { "service_worker": "background.js" @@ -15,10 +16,33 @@ "host_permissions": [ "" ], + "content_scripts": [ + { + "matches": [ + "" + ], + "js": [ + "content.js" + ], + "css": [ + "timer.css" + ] + } + ], "externally_connectable": { "matches": [ "*://localhost/*", - "*://your-devsync-domain.com/*" + "*://devsync-one.vercel.app/*" ] - } + }, + "web_accessible_resources": [ + { + "resources": [ + "alarm.mp3" + ], + "matches": [ + "" + ] + } + ] } \ No newline at end of file diff --git a/extension/offscreen.html b/extension/offscreen.html new file mode 100644 index 0000000..712e76f --- /dev/null +++ b/extension/offscreen.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/extension/offscreen.js b/extension/offscreen.js new file mode 100644 index 0000000..20adc71 --- /dev/null +++ b/extension/offscreen.js @@ -0,0 +1,7 @@ +chrome.runtime.onMessage.addListener((message) => { + if (message.action === "playSound") { + const sound = new Audio('alarm.mp3'); + sound.play() + .catch((e) => console.error("Offscreen Audio Error:", e)); + } +}); \ No newline at end of file diff --git a/extension/timer.css b/extension/timer.css new file mode 100644 index 0000000..55dbb6c --- /dev/null +++ b/extension/timer.css @@ -0,0 +1,53 @@ +#devsync-floating-timer { + position: fixed; + bottom: 20px; + right: 20px; + padding: 10px 18px; + border-radius: 8px; + font-family: 'Courier New', Courier, monospace; + z-index: 99999999; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + user-select: none; + opacity: 0.9; + display: flex; + flex-direction: column; + align-items: flex-end; + line-height: 1.1; +} + +.devsync-time { + font-size: 1.5rem; + font-weight: 600; +} + +.devsync-session { + font-size: 0.9rem; + font-weight: 400; + opacity: 0.8; + margin-top: 2px; +} + +.devsync-timer-work { + background-color: #2a0a0a; + color: #ff4d4d; + border: 1px solid #ff4d4d; +} + +.devsync-timer-break { + background-color: #0a2a0a; + color: #00ff99; + border: 1px solid #00ff99; +} + +.devsync-timer-flashing { + animation: devsync-flash 0.5s infinite alternate; +} + +@keyframes devsync-flash { + from { + opacity: 1; + } + to { + opacity: 0.4; + } +} \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e5ae97d..87e32b9 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -32,6 +32,7 @@ import FeedbackReviewPage from "./Components/feedback/FeedbackReviewPage"; import 'react-toastify/dist/ReactToastify.css'; import { ToastContainer } from 'react-toastify'; +import NotFound from "./Components/ui/NotFound"; function Home() { const [showTop, setShowTop] = useState(false); @@ -141,6 +142,7 @@ function App() { } /> } /> } /> + }/> { +export default function NotFound() { const navigate = useNavigate(); - - // Check if user is logged in const isAuthenticated = !!localStorage.getItem("token"); const handleRedirect = () => { if (isAuthenticated) { - navigate("/dashboard"); // authenticated users go to dashboard + navigate("/dashboard"); } else { - navigate("/"); // unauthenticated users go to landing page + navigate("/"); } }; return ( - -

404

-

- Oops! The page you’re looking for doesn’t exist. +

+

+ • DevSync Error • +

+ +

+ 404 +

+ +

+ Oops! Page Not Found +

+ +

+ It looks like the page you're trying to reach doesn't exist or has been moved. + Don't worry, we'll help you get back on track.

- - +
); -}; - -export default NotFound; +} diff --git a/frontend/src/context/TimerContext.jsx b/frontend/src/context/TimerContext.jsx index 15afac2..d36b0d6 100644 --- a/frontend/src/context/TimerContext.jsx +++ b/frontend/src/context/TimerContext.jsx @@ -11,33 +11,40 @@ export function useTimer() { const SESSIONS_BEFORE_LONG_BREAK = 4; -const sendStartFocusMessage = (durationInSeconds) => { - if (window.chrome && window.chrome.runtime) { - const durationInMinutes = Math.floor(durationInSeconds / 60); +const sendStartTimerMessage = (state) => { + if (window.chrome && window.chrome.runtime && EXTENSION_ID) { chrome.runtime.sendMessage(EXTENSION_ID, { - command: "startFocus", - duration: durationInMinutes + command: "startTimer", + endTime: Date.now() + state.timeLeft * 1000, + sessionType: state.sessionType, + sessionCount: state.sessions, + settings: { + workTime: state.workTime, + shortBreak: state.shortBreak, + longBreak: state.longBreak, + SESSIONS_BEFORE_LONG_BREAK: SESSIONS_BEFORE_LONG_BREAK + } }, (response) => { if (chrome.runtime.lastError) { - console.error("Could not connect to extension. Is it installed and is the ID correct?"); + console.error(`Could not start timer: ${chrome.runtime.lastError.message}`); } else { - console.log("Extension acknowledged start.", response); + console.log("Extension acknowledged timer start.", response); } }); } else { - console.warn("Chrome Extension API not found. Please install the DevSync companion extension."); + console.warn("Chrome Extension API not found."); } }; -const sendStopFocusMessage = () => { - if (window.chrome && window.chrome.runtime) { +const sendStopTimerMessage = () => { + if (window.chrome && window.chrome.runtime && EXTENSION_ID) { chrome.runtime.sendMessage(EXTENSION_ID, { - command: "stopFocus" + command: "stopTimer" }, (response) => { if (chrome.runtime.lastError) { - console.error("Error sending stop message to extension."); + console.error(`Could not stop timer: ${chrome.runtime.lastError.message}`); } else { - console.log("Extension acknowledged stop.", response); + console.log("Extension acknowledged timer stop.", response); } }); } @@ -45,126 +52,115 @@ const sendStopFocusMessage = () => { export function TimerProvider({ children }) { - // --- Timer Settings State --- - const [workTime, setWorkTime] = useState(25 * 60); - const [shortBreak, setShortBreak] = useState(5 * 60); - const [longBreak, setLongBreak] = useState(15 * 60); - - // --- Timer Operation State --- - const [isRunning, setIsRunning] = useState(false); - const [sessionType, setSessionType] = useState('work'); // 'work', 'shortBreak', 'longBreak' - const [sessions, setSessions] = useState(() => Number(localStorage.getItem("pomodoroSessions")) || 0); - - const [endTimestamp, setEndTimestamp] = useState(() => { - const saved = localStorage.getItem("pomodoroEndTimestamp"); - return saved ? Number(saved) : null; + const [workTime, setWorkTime] = useState(() => { + const saved = localStorage.getItem("devsync-workTime"); + return saved ? Number(saved) : 25 * 60; }); - - const [timeLeft, setTimeLeft] = useState(() => { - const saved = localStorage.getItem("pomodoroTimeLeft"); - return saved ? Number(saved) : workTime; + const [shortBreak, setShortBreak] = useState(() => { + const saved = localStorage.getItem("devsync-shortBreak"); + return saved ? Number(saved) : 5 * 60; + }); + const [longBreak, setLongBreak] = useState(() => { + const saved = localStorage.getItem("devsync-longBreak"); + return saved ? Number(saved) : 15 * 60; }); - // Re-sync timeLeft if workTime changes and timer is not running - useEffect(() => { - if (sessionType === 'work' && !isRunning) { - setTimeLeft(workTime); - } - }, [workTime]); + const [isRunning, setIsRunning] = useState(false); + const [sessionType, setSessionType] = useState('work'); + const [sessions, setSessions] = useState(0); + const [timeLeft, setTimeLeft] = useState(workTime); - // --- Timer Tick Logic (using requestAnimationFrame) --- useEffect(() => { - let raf; - const tick = () => { - if (isRunning && endTimestamp) { - const now = Date.now(); - const remaining = Math.max(Math.ceil((endTimestamp - now) / 1000), 0); - setTimeLeft(remaining); - - if (remaining <= 0) { - handleSessionEnd(); - } else { - raf = requestAnimationFrame(tick); + if (window.chrome && window.chrome.runtime && EXTENSION_ID) { + 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); + } else { + const currentWorkTime = Number(localStorage.getItem("devsync-workTime")) || 25 * 60; + setTimeLeft(currentWorkTime); + } } - } - }; - - if (isRunning) { - raf = requestAnimationFrame(tick); + }); } - return () => cancelAnimationFrame(raf); - }, [isRunning, endTimestamp]); + }, []); - // --- Persist Timer Info to localStorage --- - useEffect(() => localStorage.setItem("pomodoroTimeLeft", timeLeft), [timeLeft]); - useEffect(() => localStorage.setItem("pomodoroSessions", sessions), [sessions]); useEffect(() => { - if (endTimestamp) { - localStorage.setItem("pomodoroEndTimestamp", endTimestamp); - } else { - localStorage.removeItem("pomodoroEndTimestamp"); - } - }, [endTimestamp]); - - // --- Session End Logic --- - const handleSessionEnd = () => { - setIsRunning(false); - setEndTimestamp(null); - - let nextSessionType; - let nextTime; - - if (sessionType === 'work') { - sendStopFocusMessage(); - const nextSessionNum = sessions + 1; - setSessions(nextSessionNum); - - if (nextSessionNum % SESSIONS_BEFORE_LONG_BREAK === 0) { - nextSessionType = 'longBreak'; - nextTime = longBreak; - } else { - nextSessionType = 'shortBreak'; - nextTime = shortBreak; + const handleMessage = (event) => { + if (event.source !== window || !event.data || event.data.type !== "FROM_DEVSYNC_EXTENSION") { + return; } - } else { - nextSessionType = 'work'; - nextTime = workTime; - } - - setSessionType(nextSessionType); - setTimeLeft(nextTime); + + const message = event.data.payload; + + if (message.action === "updateTime") { + setTimeLeft(message.timeLeft); + } + else if (message.action === "startTimer") { + setIsRunning(true); + setSessionType(message.sessionType); + setSessions(message.sessionCount); + const remaining = Math.max(Math.ceil((message.endTime - Date.now()) / 1000), 0); + setTimeLeft(remaining); + } + else if (message.action === "stopTimer" || message.action === "hideTimer") { + setIsRunning(false); + setSessionType('work'); + setSessions(0); + setWorkTime(currentWorkTime => { + setTimeLeft(currentWorkTime); + return currentWorkTime; + }); + localStorage.removeItem("pomodoroTimeLeft"); + } + }; + + window.addEventListener("message", handleMessage); + return () => window.removeEventListener("message", handleMessage); + }, []); - // Notification - if ("Notification" in window && Notification.permission === "granted") { - new Notification( - sessionType === 'work' ? "Work session complete! Time for a break 🎉" : "Break over! Back to work 💻" - ); - } - }; + useEffect(() => localStorage.setItem("devsync-workTime", workTime), [workTime]); + useEffect(() => localStorage.setItem("devsync-shortBreak", shortBreak), [shortBreak]); + useEffect(() => localStorage.setItem("devsync-longBreak", longBreak), [longBreak]); const startTimer = () => { - setEndTimestamp(Date.now() + timeLeft * 1000); setIsRunning(true); - - if (sessionType === 'work') { - sendStartFocusMessage(timeLeft); - } + + 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 + }); }; const pauseTimer = () => { setIsRunning(false); - localStorage.setItem("pomodoroTimeLeft", timeLeft); - setEndTimestamp(null); - sendStopFocusMessage(); + sendStopTimerMessage(); + localStorage.setItem("pomodoroTimeLeft", timeLeft); }; const resetTimer = () => { setIsRunning(false); setSessionType('work'); - setTimeLeft(workTime); + setTimeLeft(workTime); setSessions(0); - setEndTimestamp(null); - sendStopFocusMessage(); + sendStopTimerMessage(); + localStorage.removeItem("pomodoroTimeLeft"); }; const updateWorkTime = (minutes) => { @@ -172,7 +168,6 @@ export function TimerProvider({ children }) { setWorkTime(secs); if (sessionType === 'work' && !isRunning) { setTimeLeft(secs); - setEndTimestamp(null); } }; const updateShortBreak = (minutes) => { @@ -180,7 +175,6 @@ export function TimerProvider({ children }) { setShortBreak(secs); if (sessionType === 'shortBreak' && !isRunning) { setTimeLeft(secs); - setEndTimestamp(null); } }; const updateLongBreak = (minutes) => { @@ -188,18 +182,15 @@ export function TimerProvider({ children }) { setLongBreak(secs); if (sessionType === 'longBreak' && !isRunning) { setTimeLeft(secs); - setEndTimestamp(null); } }; - useEffect(() => { if ("Notification" in window && Notification.permission !== "granted") { Notification.requestPermission(); } }, []); - const isWork = sessionType === 'work'; return (