diff --git a/package-lock.json b/package-lock.json index b4dd154..361019f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "maurice-plugin", - "version": "0.3.2", + "version": "0.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "maurice-plugin", - "version": "0.3.2", + "version": "0.3.3", "dependencies": { "plasmo": "^0.90.5", "react": "^18.3.1", @@ -15,7 +15,7 @@ "devDependencies": { "@eslint/js": "^9.39.0", "@ianvs/prettier-plugin-sort-imports": "4.1.1", - "@playwright/test": "^1.58.0", + "@playwright/test": "^1.58.2", "@types/chrome": "0.0.258", "@types/node": "^20.19.30", "@types/react": "^19.1.10", @@ -64,7 +64,6 @@ "node_modules/@babel/core": { "version": "7.28.5", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1902,7 +1901,6 @@ "node_modules/@parcel/core": { "version": "2.9.3", "license": "MIT", - "peer": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", "@parcel/cache": "2.9.3", @@ -4724,13 +4722,13 @@ "license": "ISC" }, "node_modules/@playwright/test": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.0.tgz", - "integrity": "sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.58.0" + "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -4925,7 +4923,6 @@ "node_modules/@svgr/core": { "version": "6.5.1", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.19.6", "@svgr/babel-preset": "^6.5.1", @@ -5331,7 +5328,6 @@ "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5401,7 +5397,6 @@ "version": "8.46.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -5693,7 +5688,6 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6066,7 +6060,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -7437,7 +7430,6 @@ "version": "0.18.20", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -7491,7 +7483,6 @@ "version": "9.39.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8332,7 +8323,6 @@ "version": "4.7.8", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -9185,7 +9175,6 @@ "version": "2.6.1", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -9749,8 +9738,7 @@ "node_modules/lodash": { "version": "4.17.21", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.ismatch": { "version": "4.4.0", @@ -10730,13 +10718,13 @@ } }, "node_modules/playwright": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.0.tgz", - "integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.58.0" + "playwright-core": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -10749,9 +10737,9 @@ } }, "node_modules/playwright-core": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.0.tgz", - "integrity": "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10786,7 +10774,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -10896,7 +10883,6 @@ "version": "3.6.2", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11007,7 +10993,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -11020,7 +11005,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -12519,7 +12503,6 @@ "version": "5.9.3", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12664,7 +12647,6 @@ "node_modules/vue": { "version": "3.3.4", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.3.4", "@vue/compiler-sfc": "3.3.4", diff --git a/package.json b/package.json index 27caa8b..e13c4a0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@eslint/js": "^9.39.0", "@ianvs/prettier-plugin-sort-imports": "4.1.1", - "@playwright/test": "^1.58.0", + "@playwright/test": "^1.58.2", "@types/chrome": "0.0.258", "@types/node": "^20.19.30", "@types/react": "^19.1.10", diff --git a/views/BackToAnOldFlame.tsx b/views/BackToAnOldFlame.tsx index 5d499d0..cee1deb 100644 --- a/views/BackToAnOldFlame.tsx +++ b/views/BackToAnOldFlame.tsx @@ -12,7 +12,7 @@ * - Button options: "Yes, I want it" / "I don't need it" / "I'm still not sure" */ -import { useState } from "react" +import { useEffect, useState } from "react" import thoughtfulIcon from "url:../assets/icons/Icons/Thoughtful.svg" import Button from "../components/ui/Button" @@ -51,10 +51,41 @@ const BackToAnOldFlame = ({ "dontNeed" | "needIt" | null >(null) const [processing, setProcessing] = useState(false) + const [countdown, setCountdown] = useState(null) // Calculate actual time waited const timeWaitedFormatted = formatDuration(Date.now() - reminderStartTime) + // Handle countdown and tab close + useEffect(() => { + if (countdown === null) return + + if (countdown === 0) { + // Close the tab when countdown reaches 0 + const closeTab = async () => { + console.log("[BackToAnOldFlame] Requesting tab close...") + try { + await ChromeMessaging.closeCurrentTab() + console.log("[BackToAnOldFlame] Tab close request successful") + } catch (error) { + console.error("[BackToAnOldFlame] Tab close failed:", error) + if (onClose) { + onClose() + } + } + } + closeTab() + return + } + + // Decrement countdown every second + const timer = setTimeout(() => { + setCountdown(countdown - 1) + }, 1000) + + return () => clearTimeout(timer) + }, [countdown, onClose]) + const handleDontNeedIt = async () => { setProcessing(true) @@ -69,6 +100,7 @@ const BackToAnOldFlame = ({ } setCelebrationType("dontNeed") + setCountdown(5) // Start 5-second countdown } const handleINeedIt = async () => { @@ -91,30 +123,18 @@ const BackToAnOldFlame = ({ setCelebrationType("needIt") } - const handleCelebrationClose = async () => { - console.log("[BackToAnOldFlame] Requesting tab close after celebration...") - - try { - await ChromeMessaging.closeCurrentTab() - console.log("[BackToAnOldFlame] Tab close request successful") - } catch (error) { - console.error("[BackToAnOldFlame] Tab close failed:", error) - // Fallback: hide the overlay - if (onClose) { - onClose() - } - } - } - if (celebrationType === "dontNeed") { return ( 0 + ? `Closing tab in ${countdown}` + : "Closing tab..." + } + onClose={onClose} /> ) } diff --git a/views/EarlyReturnFromSleep.tsx b/views/EarlyReturnFromSleep.tsx index e25397d..8acf5d7 100644 --- a/views/EarlyReturnFromSleep.tsx +++ b/views/EarlyReturnFromSleep.tsx @@ -12,7 +12,7 @@ * - Button options: "I need this now" / "I'll wait" / "I don't need it" */ -import { useState } from "react" +import { useEffect, useState } from "react" import clockIcon from "url:../assets/icons/Icons/Clock.svg" import Button from "../components/ui/Button" @@ -54,29 +54,45 @@ const EarlyReturnFromSleep = ({ }: EarlyReturnFromSleepProps) => { const [showCelebration, setShowCelebration] = useState(false) const [processing, setProcessing] = useState(false) + const [countdown, setCountdown] = useState(null) // Calculate actual time waited const timeWaitedFormatted = formatDuration(Date.now() - reminderStartTime) + // Handle countdown and tab close + useEffect(() => { + if (countdown === null) return + + if (countdown === 0) { + // Close the tab when countdown reaches 0 + const closeTab = async () => { + console.log("[EarlyReturnFromSleep] Requesting tab close...") + try { + await ChromeMessaging.closeCurrentTab() + console.log("[EarlyReturnFromSleep] Tab close request successful") + } catch (error) { + console.error("[EarlyReturnFromSleep] Tab close failed:", error) + if (onClose) { + onClose() + } + } + } + closeTab() + return + } + + // Decrement countdown every second + const timer = setTimeout(() => { + setCountdown(countdown - 1) + }, 1000) + + return () => clearTimeout(timer) + }, [countdown, onClose]) + const handleKeepWaiting = async () => { setProcessing(true) setShowCelebration(true) - - // Wait 2 seconds to show celebration message, then close tab - setTimeout(async () => { - console.log("[EarlyReturnFromSleep] Requesting tab close...") - - try { - await ChromeMessaging.closeCurrentTab() - console.log("[EarlyReturnFromSleep] Tab close request successful") - } catch (error) { - console.error("[EarlyReturnFromSleep] Tab close failed:", error) - // Fallback: hide the overlay - if (onClose) { - onClose() - } - } - }, 2000) + setCountdown(5) // Start 5-second countdown } const handleINeedIt = async () => { @@ -113,16 +129,7 @@ const EarlyReturnFromSleep = ({ } } setShowCelebration(true) - - // Wait 2 seconds to show celebration message, then close tab - setTimeout(async () => { - try { - await ChromeMessaging.closeCurrentTab() - } catch (error) { - console.error("[EarlyReturnFromSleep] Tab close failed:", error) - if (onClose) onClose() - } - }, 2000) + setCountdown(5) // Start 5-second countdown } if (showCelebration) { @@ -131,7 +138,11 @@ const EarlyReturnFromSleep = ({ icon={clockIcon} iconAlt="clock" title="🎉 Great choice! Keep it up!" - subtitle="Closing tab..." + subtitle={ + countdown !== null && countdown > 0 + ? `Closing tab in ${countdown}` + : "Closing tab..." + } onClose={onClose} /> ) diff --git a/views/IDontNeedIt.tsx b/views/IDontNeedIt.tsx index ee2df0e..b9388b4 100644 --- a/views/IDontNeedIt.tsx +++ b/views/IDontNeedIt.tsx @@ -36,19 +36,44 @@ const optionsContainerStyle: React.CSSProperties = { } const IDontNeedIt = ({ onBack, onClose }: IDontNeedItProps) => { - // Handle tab close after celebration - const handleCelebrationClose = async () => { - console.log("[IDontNeedIt] Requesting tab close...") - try { - await ChromeMessaging.closeCurrentTab() - console.log("[IDontNeedIt] Tab close successful") - } catch (error) { - console.error("[IDontNeedIt] Tab close failed:", error) - if (onClose) { - onClose() + const [countdown, setCountdown] = React.useState(null) + + // Handle countdown and tab close + React.useEffect(() => { + if (countdown === null && !SHOW_INVESTMENT_OPTIONS) { + // Start countdown after component mounts + setCountdown(5) + } + }, []) + + React.useEffect(() => { + if (countdown === null) return + + if (countdown === 0) { + // Close the tab when countdown reaches 0 + const closeTab = async () => { + console.log("[IDontNeedIt] Requesting tab close...") + try { + await ChromeMessaging.closeCurrentTab() + console.log("[IDontNeedIt] Tab close successful") + } catch (error) { + console.error("[IDontNeedIt] Tab close failed:", error) + if (onClose) { + onClose() + } + } } + closeTab() + return } - } + + // Decrement countdown every second + const timer = setTimeout(() => { + setCountdown(countdown - 1) + }, 1000) + + return () => clearTimeout(timer) + }, [countdown, onClose]) // When feature flag is disabled, show celebration instead of investment options if (!SHOW_INVESTMENT_OPTIONS) { @@ -57,10 +82,13 @@ const IDontNeedIt = ({ onBack, onClose }: IDontNeedItProps) => { icon={trophyIcon} iconAlt="trophy" title="Well done for choosing not to buy! 🎉" - subtitle="Your future self will thank you for being so thoughtful." - autoCloseDelay={4000} + subtitle={ + countdown !== null + ? `Closing tab in ${countdown}` + : "Your future self will thank you for being so thoughtful." + } onBack={onBack} - onClose={handleCelebrationClose} + onClose={onClose} /> ) } diff --git a/views/SleepOnIt.tsx b/views/SleepOnIt.tsx index e40da11..5f9e6e4 100644 --- a/views/SleepOnIt.tsx +++ b/views/SleepOnIt.tsx @@ -50,6 +50,7 @@ const SleepOnIt = ({ ) // Default: 24 hours const [saved, setSaved] = useState(false) const [saving, setSaving] = useState(false) + const [countdown, setCountdown] = useState(null) const durationOptions = [ { label: "1 minute", value: 1 * 60 * 1000 }, @@ -97,6 +98,7 @@ const SleepOnIt = ({ console.log("[SleepOnIt] Reminder saved successfully") setSaved(true) + setCountdown(5) // Start countdown when saved } catch (error) { console.error("[SleepOnIt] Failed to save reminder:", error) alert("Failed to save reminder. Please check the console for details.") @@ -105,30 +107,35 @@ const SleepOnIt = ({ } } - // Auto-close tab after 4 seconds when reminder is saved + // Handle countdown and tab close useEffect(() => { - if (saved) { - console.log( - "[SleepOnIt] Reminder saved, scheduling tab close in 4 seconds..." - ) - const timer = setTimeout(async () => { - console.log("[SleepOnIt] Requesting tab close...") + if (countdown === null) return + if (countdown === 0) { + // Close the tab when countdown reaches 0 + const closeTab = async () => { + console.log("[SleepOnIt] Requesting tab close...") try { await ChromeMessaging.closeCurrentTab() console.log("[SleepOnIt] Tab close request successful") } catch (error) { console.error("[SleepOnIt] Tab close failed:", error) - // Fallback: hide the overlay if (onClose) { onClose() } } - }, 4000) // 4 seconds - - return () => clearTimeout(timer) + } + closeTab() + return } - }, [saved, onClose]) + + // Decrement countdown every second + const timer = setTimeout(() => { + setCountdown(countdown - 1) + }, 1000) + + return () => clearTimeout(timer) + }, [countdown, onClose]) return ( }> @@ -176,7 +183,9 @@ const SleepOnIt = ({ ) : (

- ✓ Reminder saved! Hold tight and remember about the goal! + {countdown !== null && countdown > 0 + ? `Closing tab in ${countdown}` + : "✓ Reminder saved!"}

)}