From 97fb8d8561144450249fe38a612ba18492600cb9 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sat, 21 Jun 2025 01:24:14 -0400 Subject: [PATCH 1/4] chore: update package.json to specify Node and npm engine requirements, and add build-cf script for clean build --- .npmrc | 3 + .nvmrc | 1 + package-lock.json | 643 +--------------------------------------------- package.json | 5 + 4 files changed, 14 insertions(+), 638 deletions(-) create mode 100644 .npmrc create mode 100644 .nvmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..4bbdf00 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +optional=false +fund=false +audit=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/package-lock.json b/package-lock.json index 5b8be7d..eb9741d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3274,80 +3274,6 @@ "prettier": ">=2.4.0" } }, - "node_modules/@puppeteer/browsers": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", - "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.1", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.2", - "tar-fs": "^3.0.8", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@puppeteer/browsers/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@puppeteer/browsers/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@puppeteer/browsers/node_modules/tar-fs": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.10.tgz", - "integrity": "sha512-C1SwlQGNLe/jPNqapK8epDsXME7CAJR5RL3GcE6KWx1d9OUByzoHVcbu1VPI8tevg9H8Alae0AApHHFGzrD5zA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/@puppeteer/browsers/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -3601,13 +3527,6 @@ "react": "^18 || ^19" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@trapezedev/gradle-parse": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@trapezedev/gradle-parse/-/gradle-parse-7.1.3.tgz", @@ -3942,17 +3861,6 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/scope-manager": { "version": "8.32.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", @@ -4183,16 +4091,6 @@ "dev": true, "license": "MIT" }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -4462,19 +4360,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -4642,6 +4527,7 @@ "version": "2.5.4", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "dev": true, "license": "Apache-2.0", "optional": true }, @@ -4649,6 +4535,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { @@ -4672,6 +4559,7 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "dev": true, "license": "Apache-2.0", "optional": true, "engines": { @@ -4682,6 +4570,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { @@ -4692,6 +4581,7 @@ "version": "2.6.5", "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "dev": true, "license": "Apache-2.0", "optional": true, "dependencies": { @@ -4730,16 +4620,6 @@ ], "license": "MIT" }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -5133,20 +5013,6 @@ "node": ">=10" } }, - "node_modules/chromium-bidi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", - "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, "node_modules/chrono-node": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.8.0.tgz", @@ -5807,16 +5673,6 @@ "node": ">=8" } }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -6063,21 +5919,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/del": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", @@ -6173,13 +6014,6 @@ "node": ">=8" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1452169", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz", - "integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -6685,39 +6519,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -7147,20 +6948,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -7233,27 +7020,6 @@ "node": ">=6" } }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, "node_modules/farmhash": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/farmhash/-/farmhash-4.0.1.tgz", @@ -7613,33 +7379,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/framer-motion": { - "version": "12.18.1", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.18.1.tgz", - "integrity": "sha512-6o4EDuRPLk4LSZ1kRnnEOurbQ86MklVk+Y1rFBUKiF+d2pCdvMjWVu0ZkyMVCTwl5UyTH2n/zJEJx+jvTYuxow==", - "license": "MIT", - "dependencies": { - "motion-dom": "^12.18.1", - "motion-utils": "^12.18.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -7876,22 +7615,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -7909,21 +7632,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-uri": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", - "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/get-user-locale": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.2.tgz", @@ -8324,34 +8032,6 @@ "node": ">=10" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/husky": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", @@ -8503,20 +8183,6 @@ "node": ">=12" } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -9149,13 +8815,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true, - "license": "MIT" - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -9926,13 +9585,6 @@ "node": ">=8" } }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -9971,21 +9623,6 @@ "node": "*" } }, - "node_modules/motion-dom": { - "version": "12.18.1", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.18.1.tgz", - "integrity": "sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w==", - "license": "MIT", - "dependencies": { - "motion-utils": "^12.18.1" - } - }, - "node_modules/motion-utils": { - "version": "12.18.1", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.18.1.tgz", - "integrity": "sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA==", - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -10101,16 +9738,6 @@ "dev": true, "license": "MIT" }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/node-abi": { "version": "3.75.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", @@ -10467,40 +10094,6 @@ "node": ">=6" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -11028,16 +10621,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -11079,43 +10662,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -11135,98 +10681,6 @@ "node": ">=6" } }, - "node_modules/puppeteer": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.10.1.tgz", - "integrity": "sha512-7T3rfSaaPt5A31VITV5YKQ4wPCCv4aPn8byDaV+9lhDU9v7BWYY4Ncwerw3ZR5mIolrh/PvzGdIDK7yiBth75g==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.5", - "chromium-bidi": "5.1.0", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1452169", - "puppeteer-core": "24.10.1", - "typed-query-selector": "^2.12.0" - }, - "bin": { - "puppeteer": "lib/cjs/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.10.1.tgz", - "integrity": "sha512-AE6doA9znmEEps/pC5lc9p0zejCdNLR6UBp3EZ49/15Nbvh+uklXxGox7Qh8/lFGqGVwxInl0TXmsOmIuIMwiQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.5", - "chromium-bidi": "5.1.0", - "debug": "^4.4.1", - "devtools-protocol": "0.0.1452169", - "typed-query-selector": "^2.12.0", - "ws": "^8.18.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/puppeteer-core/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/puppeteer/node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -12912,53 +12366,12 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/smob": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", "license": "MIT" }, - "node_modules/socks": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", - "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -13077,13 +12490,6 @@ "node": ">= 6" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/sql.js": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.13.0.tgz", @@ -14061,13 +13467,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "dev": true, - "license": "MIT" - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -15080,28 +14479,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xcode": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", @@ -15288,16 +14665,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "3.25.67", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", - "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index de3c2a0..0cfa9b4 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,10 @@ "private": true, "version": "0.1.103", "type": "module", + "engines": { + "node": ">=20.0.0", + "npm": ">=9.0.0" + }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ "eslint --fix", @@ -13,6 +17,7 @@ "start": "vite --host", "dev": "vite", "build": "vite build", + "build-cf": "rm -rf node_modules package-lock.json && npm install && vite build", "build-selfhosted": "vite build --mode selfhosted", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", From 5b83bf35d099475fec42e7588652eefaafe4a7ae Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sun, 22 Jun 2025 17:21:22 -0400 Subject: [PATCH 2/4] update notification handling and improve sorting logic in NotificationTemplate component --- src/components/NotificationTemplate.jsx | 471 ++++++++++++------------ src/components/RealTimeSettings.jsx | 4 +- src/hooks/useSSE.js | 46 ++- src/queries/UserQueries.jsx | 5 +- src/utils/TokenManager.jsx | 1 - src/views/Authorization/LoginView.jsx | 18 +- src/views/ChoreEdit/ChoreEdit.jsx | 29 +- src/views/Chores/CompactChoreCard.jsx | 5 +- 8 files changed, 316 insertions(+), 263 deletions(-) diff --git a/src/components/NotificationTemplate.jsx b/src/components/NotificationTemplate.jsx index bb02182..20386a6 100644 --- a/src/components/NotificationTemplate.jsx +++ b/src/components/NotificationTemplate.jsx @@ -15,23 +15,52 @@ import Typography from '@mui/joy/Typography' import { useCallback, useEffect, useState } from 'react' const timeUnits = [ - { label: 'Mins', value: 'minutes' }, - { label: 'Hours', value: 'hours' }, - { label: 'Days', value: 'days' }, + { label: 'Mins', value: 'm' }, + { label: 'Hours', value: 'h' }, + { label: 'Days', value: 'd' }, ] -const beforeAfterOptions = [ +const timingOptions = [ { label: 'Before', value: 'before' }, { label: 'On Due', value: 'ondue' }, { label: 'After', value: 'after' }, ] function getRelativeLabel(notification) { - const { value, unit, type } = notification - if (type === 'ondue') { + const { value, unit } = notification + const numericValue = Number(value) + if (numericValue === 0) { return 'On due date' } - return `${value} ${unit} ${type === 'before' ? 'before' : 'after'} due` + const unitName = unit === 'm' ? 'minutes' : unit === 'h' ? 'hours' : 'days' + const absValue = Math.abs(numericValue) + return `${absValue} ${unitName} ${numericValue < 0 ? 'before' : 'after'} due` +} + +// Helper functions to convert between internal value and UI representation +function getUIRepresentation(notification) { + const numericValue = Number(notification.value) + if (numericValue === 0) { + return { timing: 'ondue', displayValue: 0, unit: notification.unit } + } else if (numericValue < 0) { + return { + timing: 'before', + displayValue: Math.abs(numericValue), + unit: notification.unit, + } + } else { + return { + timing: 'after', + displayValue: numericValue, + unit: notification.unit, + } + } +} + +function getInternalValue(timing, displayValue) { + if (timing === 'ondue') return 0 + if (timing === 'before') return -Math.abs(displayValue) + return Math.abs(displayValue) // 'after' } const NotificationTemplate = ({ @@ -40,9 +69,6 @@ const NotificationTemplate = ({ value, showTimeline = true, }) => { - const [templateName, setTemplateName] = useState( - value?.name || 'New Notification Template', - ) const [notifications, setNotifications] = useState( value?.templates || JSON.parse(localStorage.getItem('defaultNotificationTemplate')) || @@ -55,45 +81,33 @@ const NotificationTemplate = ({ const [notificationIndexMap, setNotificationIndexMap] = useState({}) const updateNotificationIndices = useCallback(() => { - // Sort notifications for consistent ordering + // Convert notifications to minutes for proper chronological sorting + const convertToMinutes = (value, unit) => { + const numericValue = Number(value) + if (numericValue === 0) return 0 + let minutes = Math.abs(numericValue) + if (unit === 'h') minutes *= 60 + if (unit === 'd') minutes *= 24 * 60 + return numericValue < 0 ? -minutes : minutes + } + + // Sort notifications for consistent ordering by actual time duration const sorted = [...notifications].sort((a, b) => { - // Always ensure correct ordering: Before Due -> On Due -> After Due - if (a.type !== b.type) { - // Before Due first - if (a.type === 'before') return -1 - if (b.type === 'before') return 1 - - // On Due comes before After Due - if (a.type === 'ondue') return -1 - if (b.type === 'ondue') return 1 - // DEFAULT CASE ( NOT SURE FOR FUTURE )? - return 0 - } - - const getMinutes = notif => { - const { value, unit } = notif - let minutes = value - if (unit === 'hours') minutes *= 60 - if (unit === 'days') minutes *= 24 * 60 - return minutes - } - - // For Before Due: sort in descending order (furthest from due first) - // For After Due: sort in ascending order (closest to due first) - const aMinutes = getMinutes(a) - const bMinutes = getMinutes(b) - return a.type === 'before' ? bMinutes - aMinutes : aMinutes - bMinutes + const aMinutes = convertToMinutes(a.value, a.unit) + const bMinutes = convertToMinutes(b.value, b.unit) + return aMinutes - bMinutes }) const indexMap = {} - sorted.forEach((item, index) => { - const originalIdx = notifications.findIndex( - n => - n.value === item.value && - n.unit === item.unit && - n.type === item.type, + // Map original array indices to their chronological position numbers + notifications.forEach((originalNotification, originalIdx) => { + const chronologicalPosition = sorted.findIndex( + sortedNotification => + Number(sortedNotification.value) === + Number(originalNotification.value) && + sortedNotification.unit === originalNotification.unit, ) - indexMap[originalIdx] = index + 1 + indexMap[originalIdx] = chronologicalPosition + 1 }) setNotificationIndexMap(indexMap) @@ -118,31 +132,48 @@ const NotificationTemplate = ({ if (idx === currentIdx) return false return ( - n.value === notification.value && - n.unit === notification.unit && - n.type === notification.type + Number(n.value) === Number(notification.value) && + n.unit === notification.unit ) }) } const handleChange = (idx, field, value) => { - let updatedNotification = { - ...notifications[idx], - [field]: value, + const currentNotification = notifications[idx] + const uiRep = getUIRepresentation(currentNotification) + + let updatedUIRep = { ...uiRep } + let updatedNotification = { ...currentNotification } + + // Update the UI representation based on the field being changed + if (field === 'timing') { + updatedUIRep.timing = value + // Reset display value when switching to "On Due" + if (value === 'ondue') { + updatedUIRep.displayValue = 0 + } + } else if (field === 'displayValue') { + updatedUIRep.displayValue = Math.max(0, Number(value)) + } else if (field === 'unit') { + updatedUIRep.unit = value + updatedNotification.unit = value } - // Special handling for "On Due" option - if (field === 'type' && value === 'ondue') { - // Set default values for On Due (not applicable) - updatedNotification = { - ...updatedNotification, - value: 1, - unit: 'minutes', - } + // Convert back to internal representation + const newInternalValue = getInternalValue( + updatedUIRep.timing, + updatedUIRep.displayValue, + ) + updatedNotification = { + ...updatedNotification, + value: newInternalValue, + unit: updatedUIRep.unit, + } - // Check if another notification is already "On Due" + // Check if another notification is already "On Due" (value = 0) + if (newInternalValue === 0) { const existingOnDue = notifications.findIndex( - (n, i) => i !== idx && n.type === 'ondue', + (n, i) => i !== idx && Number(n.value) === 0, ) if (existingOnDue !== -1) { @@ -177,28 +208,25 @@ const NotificationTemplate = ({ case 'reminder': // Suggest common reminder times that don't exist suggestions = [ - { value: 1, unit: 'hours', type: 'before' }, - { value: 1, unit: 'days', type: 'before' }, - { value: 30, unit: 'minutes', type: 'before' }, - { value: 2, unit: 'hours', type: 'before' }, - { value: 3, unit: 'days', type: 'before' }, + { value: -1, unit: 'd' }, // 1 day before + { value: -3, unit: 'h' }, // 3 hours before + { value: -30, unit: 'm' }, // 3 days before ] break case 'due': - if (notifications.some(n => n.type === 'ondue')) { + if (notifications.some(n => Number(n.value) === 0)) { setError('Only one "Due Alert" notification is allowed.') return } - newNotification = { value: 0, unit: 'minutes', type: 'ondue' } + newNotification = { value: 0, unit: 'm' } break case 'followup': suggestions = [ - { value: 1, unit: 'hours', type: 'after' }, - { value: 1, unit: 'days', type: 'after' }, - { value: 3, unit: 'days', type: 'after' }, - { value: 1, unit: 'weeks', type: 'after' }, + { value: 1, unit: 'd' }, // 1 day after + { value: 3, unit: 'd' }, // 3 days after + { value: 7, unit: 'd' }, // 1 week after ] break } @@ -213,24 +241,8 @@ const NotificationTemplate = ({ } } - // Insert the new notification in the correct chronological position - const updatedNotifications = [...notifications, newNotification].sort( - (a, b) => { - // Convert everything to minutes for consistent comparison - const getMinutes = notif => { - const { value, unit, type } = notif - // On Due is exactly at due date (0 minutes) - if (type === 'ondue') return 0 - - let minutes = value - if (unit === 'hours') minutes *= 60 - if (unit === 'days') minutes *= 24 * 60 - return type === 'before' ? -minutes : minutes - } - - return getMinutes(a) - getMinutes(b) - }, - ) + // Add the new notification to the end (don't sort, keep form order) + const updatedNotifications = [...notifications, newNotification] setNotifications(updatedNotifications) setError(null) @@ -240,61 +252,45 @@ const NotificationTemplate = ({ const updated = notifications.filter((_, i) => i !== idx) setNotifications(updated) onChange && onChange(updated) + setShowSaveDefault(true) } const renderTimeline = () => { - // Sort notifications chronologically + // Convert notifications to minutes for proper chronological sorting + const convertToMinutes = (value, unit) => { + const numericValue = Number(value) + if (numericValue === 0) return 0 + let minutes = Math.abs(numericValue) + if (unit === 'h') minutes *= 60 + if (unit === 'd') minutes *= 24 * 60 + return numericValue < 0 ? -minutes : minutes + } + + // Sort notifications chronologically by actual time (in minutes) const sorted = [...notifications].sort((a, b) => { - // Convert everything to minutes for consistent comparison - const getMinutes = notif => { - const { value, unit, type } = notif - // On Due is exactly at due date (0 minutes) - if (type === 'ondue') return 0 - - let minutes = value - if (unit === 'hours') minutes *= 60 - if (unit === 'days') minutes *= 24 * 60 - return type === 'before' ? -minutes : minutes - } - - return getMinutes(a) - getMinutes(b) - }) - - // Create a map to track sorted indices for original notifications - const notificationIndexMap = {} - sorted.forEach((item, index) => { - // Find the original index of this item in notifications array - const originalIdx = notifications.findIndex( - n => - n.value === item.value && - n.unit === item.unit && - n.type === item.type, - ) - notificationIndexMap[originalIdx] = index + 1 - }) // Get min and max notification times for dynamic scaling - const minutesValues = sorted.map(n => { - if (n.type === 'ondue') return 0 - - let minutes = n.value - if (n.unit === 'hours') minutes *= 60 - if (n.unit === 'days') minutes *= 24 * 60 - return n.type === 'before' ? -minutes : minutes + const aMinutes = convertToMinutes(a.value, a.unit) + const bMinutes = convertToMinutes(b.value, b.unit) + return aMinutes - bMinutes }) + // Get min and max notification times in minutes for dynamic scaling + const minutesValues = sorted.map(n => convertToMinutes(n.value, n.unit)) const minBefore = Math.min(0, ...minutesValues) // Default to 0 if no "before" notifications const maxAfter = Math.max(0, ...minutesValues) // Default to 0 if no "after" notifications - const getPositionPercent = minutes => { + const getPositionPercent = (value, unit) => { + const minutes = convertToMinutes(value, unit) + // Due date is always at center (50%) if (minutes === 0) return 50 - // For notifications before due date + // For notifications before due date (negative values) if (minutes < 0) { if (minBefore === 0) return 30 // Default position if no before notifications // Scale between 10% (furthest left) and 45% (closest to due) return 45 - (Math.abs(minutes) / Math.abs(minBefore)) * 35 } - // For notifications after due date + // For notifications after due date (positive values) if (maxAfter === 0) return 70 // Default position if no after notifications // Scale between 55% (closest to due) and 90% (furthest right) return 55 + (minutes / maxAfter) * 35 @@ -361,18 +357,8 @@ const NotificationTemplate = ({ {/* Notification markers */} {sorted.map((n, i) => { - // Convert to minutes for consistent scale - let minutes = 0 - if (n.type !== 'ondue') { - minutes = n.value - if (n.unit === 'hours') minutes *= 60 - if (n.unit === 'days') minutes *= 24 * 60 - if (n.type === 'before') minutes = -minutes - } - // On Due notifications are always at the due date (0 minutes) - - // Calculate position based on dynamic scaling - const percent = getPositionPercent(minutes) + // Calculate position based on actual time duration + const percent = getPositionPercent(n.value, n.unit) return ( + Number(original.value) === Number(n.value) && + original.unit === n.unit, + ) + ] || i + 1 + } size={'sm'} variant={'solid'} color={ - n.type === 'before' + Number(n.value) < 0 ? 'success' - : n.type === 'ondue' + : Number(n.value) === 0 ? 'warning' : 'danger' } @@ -480,105 +474,108 @@ const NotificationTemplate = ({ )} - {notifications.map((n, idx) => { - // Get ordered badge number from timeline sorting - const badgeNumber = notificationIndexMap[idx] + {notifications + .map((n, idx) => ({ notification: n, originalIndex: idx })) + .sort((a, b) => { + const aBadgeNumber = notificationIndexMap[a.originalIndex] || 0 + const bBadgeNumber = notificationIndexMap[b.originalIndex] || 0 + return aBadgeNumber - bBadgeNumber + }) + .map(({ notification: n, originalIndex: idx }) => { + // Get ordered badge number from timeline sorting + const badgeNumber = notificationIndexMap[idx] + const uiRep = getUIRepresentation(n) - return ( - + return ( - - {/* Empty box to attach badge to */} - + + {/* Empty box to attach badge to */} + + + + + handleChange(idx, 'displayValue', e.target.value) + } + sx={{ + width: 80, + mr: 1, + opacity: uiRep.timing === 'ondue' ? 0.6 : 1, + }} + size={'sm'} + placeholder='0' + /> + + removeNotification(idx)} + disabled={notifications.length === 1} + color={'danger'} + size={'sm'} + sx={{ mr: 1 }} + variant={'soft'} + > + + - - {/* Show disabled fields for "On Due" option for visual consistency */} - - handleChange(idx, 'value', Math.max(1, Number(e.target.value))) - } - sx={{ - width: 70, - mr: 1, - opacity: n.type === 'ondue' ? 0.6 : 1, - ...(n.type === 'ondue' && { - '& input': { - textAlign: 'center', - }, - }), - }} - size={'sm'} - placeholder={n.type === 'ondue' ? '—' : ''} - /> - - removeNotification(idx)} - disabled={notifications.length === 1} - color={'danger'} - size={'sm'} - sx={{ mr: 1 }} - variant={'soft'} - > - - - - ) - })} + ) + })}