diff --git a/package-lock.json b/package-lock.json index 698f4edb..41f82087 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@radix-ui/react-tooltip": "^1.2.7", "@supabase/supabase-js": "^2.57.4", "@tanstack/react-query": "^5.83.0", + "@tanstack/react-query-devtools": "^5.90.2", "@uppy/core": "^5.0.2", "@uppy/dashboard": "^5.0.2", "@uppy/image-editor": "^4.0.1", @@ -92,6 +93,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -891,6 +893,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -908,6 +911,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -918,6 +922,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -927,12 +932,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1054,6 +1061,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -1067,6 +1075,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -1076,6 +1085,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -1127,6 +1137,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "license": "MIT", "optional": true, "engines": { @@ -3290,6 +3301,16 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/query-devtools": { + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.90.1.tgz", + "integrity": "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/react-query": { "version": "5.90.2", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.2.tgz", @@ -3306,6 +3327,23 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.90.2.tgz", + "integrity": "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.90.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.90.2", + "react": "^18 || ^19" + } + }, "node_modules/@transloadit/prettier-bytes": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.3.5.tgz", @@ -3449,12 +3487,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.26", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -3465,7 +3505,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -4199,6 +4239,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4211,6 +4252,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4226,12 +4268,14 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -4245,6 +4289,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -4318,6 +4363,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -4334,6 +4380,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4357,6 +4404,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -4413,6 +4461,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4510,6 +4559,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -4534,6 +4584,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -4589,6 +4640,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4601,6 +4653,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/comma-separated-tokens": { @@ -4617,6 +4670,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -4639,6 +4693,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -4653,6 +4708,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -4873,12 +4929,14 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, "license": "MIT" }, "node_modules/dom-helpers": { @@ -4895,6 +4953,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { @@ -4936,6 +4995,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/engine.io-client": { @@ -5305,6 +5365,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -5321,6 +5382,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -5347,6 +5409,7 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -5369,6 +5432,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -5419,6 +5483,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -5449,6 +5514,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, @@ -5463,6 +5529,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5481,6 +5548,7 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -5501,6 +5569,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -5513,6 +5582,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -5522,6 +5592,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -5567,6 +5638,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5721,6 +5793,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -5733,6 +5806,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -5758,6 +5832,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5767,6 +5842,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5776,6 +5852,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -5816,6 +5893,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5837,12 +5915,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -5858,6 +5938,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -5931,6 +6012,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -5943,6 +6025,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -6018,6 +6101,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, "license": "ISC" }, "node_modules/lucide-react": { @@ -6196,6 +6280,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -6647,6 +6732,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -6682,6 +6768,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -6703,6 +6790,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -6762,6 +6850,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6790,6 +6879,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -6894,6 +6984,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -6957,6 +7048,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6966,12 +7058,14 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -6988,12 +7082,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -7006,6 +7102,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7015,6 +7112,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -7024,6 +7122,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -7052,6 +7151,7 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -7069,6 +7169,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -7094,6 +7195,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, "funding": [ { "type": "opencollective", @@ -7136,6 +7238,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -7161,6 +7264,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -7188,12 +7292,14 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, "license": "MIT" }, "node_modules/postcss/node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -7281,6 +7387,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -7531,6 +7638,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -7540,6 +7648,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -7623,6 +7732,7 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -7662,6 +7772,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -7714,6 +7825,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -7786,6 +7898,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -7798,6 +7911,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7807,6 +7921,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -7956,6 +8071,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7975,6 +8091,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -7993,6 +8110,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -8007,6 +8125,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8016,12 +8135,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8048,6 +8169,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -8064,6 +8186,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8076,6 +8199,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8116,6 +8240,7 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -8151,6 +8276,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8173,6 +8299,7 @@ "version": "3.4.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -8219,6 +8346,7 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -8232,6 +8360,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -8241,6 +8370,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -8259,6 +8389,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -8310,6 +8441,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/tslib": { @@ -8559,6 +8691,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/vaul": { @@ -9134,6 +9267,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -9165,6 +9299,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -9183,6 +9318,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -9200,6 +9336,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9209,12 +9346,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -9229,6 +9368,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9241,6 +9381,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" diff --git a/package.json b/package.json index a24aae25..50d7d9df 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@radix-ui/react-tooltip": "^1.2.7", "@supabase/supabase-js": "^2.57.4", "@tanstack/react-query": "^5.83.0", + "@tanstack/react-query-devtools": "^5.90.2", "@uppy/core": "^5.0.2", "@uppy/dashboard": "^5.0.2", "@uppy/image-editor": "^4.0.1", diff --git a/src/App.tsx b/src/App.tsx index 1f59af6d..4eaee7ff 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { BrowserRouter, Routes, Route } from "react-router-dom"; import { AuthProvider } from "@/hooks/useAuth"; import { LocationAutoDetectProvider } from "@/components/providers/LocationAutoDetectProvider"; @@ -122,6 +123,12 @@ const App = () => ( + {import.meta.env.DEV && ( + + )} ); diff --git a/src/components/moderation/QueueItem.tsx b/src/components/moderation/QueueItem.tsx index a007d7c2..14832463 100644 --- a/src/components/moderation/QueueItem.tsx +++ b/src/components/moderation/QueueItem.tsx @@ -11,47 +11,10 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import { format } from 'date-fns'; import { SubmissionItemsList } from './SubmissionItemsList'; import { MeasurementDisplay } from '@/components/ui/measurement-display'; - -interface ModerationItem { - id: string; - type: 'review' | 'content_submission'; - content: any; - created_at: string; - updated_at?: string; - user_id: string; - status: string; - submission_type?: string; - user_profile?: { - username: string; - display_name?: string; - avatar_url?: string; - }; - entity_name?: string; - park_name?: string; - reviewed_at?: string; - reviewed_by?: string; - reviewer_notes?: string; - reviewer_profile?: { - username: string; - display_name?: string; - avatar_url?: string; - }; - escalated?: boolean; - assigned_to?: string; - locked_until?: string; - _removing?: boolean; - submission_items?: Array<{ - id: string; - item_type: string; - item_data: any; - status: string; - }>; -} - import { ValidationSummary } from './ValidationSummary'; import type { ValidationResult } from '@/lib/entityValidationSchemas'; - import type { LockStatus } from '@/lib/moderation/lockHelpers'; +import type { ModerationItem } from '@/types/moderation'; interface QueueItemProps { item: ModerationItem; diff --git a/src/hooks/moderation/useEntityCache.ts b/src/hooks/moderation/useEntityCache.ts index b14a586c..f55b893f 100644 --- a/src/hooks/moderation/useEntityCache.ts +++ b/src/hooks/moderation/useEntityCache.ts @@ -1,5 +1,7 @@ -import { useRef, useCallback, useMemo } from 'react'; +import { useRef, useCallback } from 'react'; import { supabase } from '@/integrations/supabase/client'; +import { logger } from '@/lib/logger'; +import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; /** * Entity types supported by the cache @@ -61,10 +63,21 @@ export function useEntityCache() { }, []); /** - * Set a cached entity + * Set a cached entity with LRU eviction */ const setCached = useCallback((type: EntityType, id: string, data: any): void => { - cacheRef.current[type].set(id, data); + const cache = cacheRef.current[type]; + + // LRU eviction: remove oldest entry if cache is full + if (cache.size >= MODERATION_CONSTANTS.MAX_ENTITY_CACHE_SIZE) { + const firstKey = cache.keys().next().value; + if (firstKey) { + cache.delete(firstKey); + logger.log(`♻️ [EntityCache] Evicted ${type}/${firstKey} (LRU)`); + } + } + + cache.set(id, data); }, []); /** @@ -119,7 +132,7 @@ export function useEntityCache() { .in('id', uncachedIds); if (error) { - console.error(`Error fetching ${type}:`, error); + logger.error(`Error fetching ${type}:`, error); return []; } @@ -132,7 +145,7 @@ export function useEntityCache() { return data || []; } catch (error) { - console.error(`Failed to bulk fetch ${type}:`, error); + logger.error(`Failed to bulk fetch ${type}:`, error); return []; } }, [getCached, setCached, getUncachedIds]); @@ -221,7 +234,8 @@ export function useEntityCache() { */ const getCacheRef = useCallback(() => cacheRef.current, []); - return useMemo(() => ({ + // Return without useMemo wrapper (OPTIMIZED) + return { getCached, has, setCached, @@ -233,17 +247,5 @@ export function useEntityCache() { getSize, getTotalSize, getCacheRef, - }), [ - getCached, - has, - setCached, - getUncachedIds, - bulkFetch, - fetchRelatedEntities, - clear, - clearAll, - getSize, - getTotalSize, - getCacheRef, - ]); + }; } diff --git a/src/hooks/moderation/useModerationFilters.ts b/src/hooks/moderation/useModerationFilters.ts index 44d8db5a..0b0a4ce6 100644 --- a/src/hooks/moderation/useModerationFilters.ts +++ b/src/hooks/moderation/useModerationFilters.ts @@ -8,8 +8,10 @@ * - Filter persistence and clearing */ -import { useState, useCallback, useEffect, useMemo } from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { useDebounce } from '@/hooks/useDebounce'; +import { logger } from '@/lib/logger'; +import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; import type { EntityFilter, StatusFilter, QueueTab, SortConfig, SortField } from '@/types/moderation'; export interface ModerationFiltersConfig { @@ -109,7 +111,7 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode initialEntityFilter = 'all', initialStatusFilter = 'pending', initialTab = 'mainQueue', - debounceDelay = 300, + debounceDelay = MODERATION_CONSTANTS.FILTER_DEBOUNCE_MS, persist = true, storageKey = 'moderationQueue_filters', initialSortConfig = { field: 'created_at', direction: 'asc' }, @@ -204,25 +206,25 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode // Set entity filter with logging const setEntityFilter = useCallback((filter: EntityFilter) => { - console.log('🔍 Entity filter changed:', filter); + logger.log('🔍 Entity filter changed:', filter); setEntityFilterState(filter); }, []); // Set status filter with logging const setStatusFilter = useCallback((filter: StatusFilter) => { - console.log('🔍 Status filter changed:', filter); + logger.log('🔍 Status filter changed:', filter); setStatusFilterState(filter); }, []); // Set active tab with logging const setActiveTab = useCallback((tab: QueueTab) => { - console.log('🔍 Tab changed:', tab); + logger.log('🔍 Tab changed:', tab); setActiveTabState(tab); }, []); // Sort callbacks const setSortConfig = useCallback((config: SortConfig) => { - console.log('📝 [SORT] Sort config changed:', config); + logger.log('📝 [SORT] Sort config changed:', config); setSortConfigState(config); }, []); @@ -248,7 +250,7 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode // Clear all filters const clearFilters = useCallback(() => { - console.log('🔍 Filters cleared'); + logger.log('🔍 Filters cleared'); setEntityFilterState(initialEntityFilter); setStatusFilterState(initialStatusFilter); setActiveTabState(initialTab); @@ -263,7 +265,8 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode sortConfig.field !== initialSortConfig.field || sortConfig.direction !== initialSortConfig.direction; - return useMemo(() => ({ + // Return without useMemo wrapper (OPTIMIZED) + return { entityFilter, statusFilter, activeTab, @@ -280,22 +283,5 @@ export function useModerationFilters(config: ModerationFiltersConfig = {}): Mode sortBy, toggleSortDirection, resetSort, - }), [ - entityFilter, - statusFilter, - activeTab, - debouncedEntityFilter, - debouncedStatusFilter, - setEntityFilter, - setStatusFilter, - setActiveTab, - clearFilters, - hasActiveFilters, - sortConfig, - debouncedSortConfig, - setSortConfig, - sortBy, - toggleSortDirection, - resetSort, - ]); + }; } diff --git a/src/hooks/moderation/useModerationQueueManager.ts b/src/hooks/moderation/useModerationQueueManager.ts index d2b0e33d..a126acb6 100644 --- a/src/hooks/moderation/useModerationQueueManager.ts +++ b/src/hooks/moderation/useModerationQueueManager.ts @@ -1,6 +1,8 @@ import { useState, useCallback, useRef, useEffect, useMemo } from "react"; import { supabase } from "@/integrations/supabase/client"; import { useToast } from "@/hooks/use-toast"; +import { logger } from "@/lib/logger"; +import { MODERATION_CONSTANTS } from "@/lib/moderation/constants"; import type { User } from "@supabase/supabase-js"; import { useEntityCache, @@ -71,7 +73,7 @@ export interface ModerationQueueManager { * Consolidates all queue-related logic into a single hook */ export function useModerationQueueManager(config: ModerationQueueManagerConfig): ModerationQueueManager { - console.log('🚀 [QUEUE MANAGER] Hook mounting/rendering', { + logger.log('🚀 [QUEUE MANAGER] Hook mounting/rendering', { hasUser: !!config.user, isAdmin: config.isAdmin, timestamp: new Date().toISOString() @@ -169,7 +171,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): useEffect(() => { if (queueQuery.items) { setItems(queueQuery.items); - console.log('✅ Queue items updated from TanStack Query:', queueQuery.items.length); + logger.log('✅ Queue items updated from TanStack Query:', queueQuery.items.length); } }, [queueQuery.items]); @@ -187,7 +189,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): // Show error toast when query fails useEffect(() => { if (queueQuery.error) { - console.error('❌ Queue query error:', queueQuery.error); + logger.error('❌ Queue query error:', queueQuery.error); toast({ variant: 'destructive', title: 'Failed to Load Queue', @@ -205,7 +207,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): useEffect(() => { if (!queueQuery.isLoading && !initialFetchCompleteRef.current) { initialFetchCompleteRef.current = true; - console.log('✅ Initial queue fetch complete'); + logger.log('✅ Initial queue fetch complete'); } }, [queueQuery.isLoading]); @@ -213,7 +215,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): * Manual refresh function */ const refresh = useCallback(async () => { - console.log('🔄 Manual refresh triggered'); + logger.log('🔄 Manual refresh triggered'); await queueQuery.refetch(); }, [queueQuery]); @@ -221,7 +223,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): * Show pending new items by invalidating query */ const showNewItems = useCallback(async () => { - console.log('✅ Showing new items via query invalidation'); + logger.log('✅ Showing new items via query invalidation'); await queueQuery.invalidate(); setPendingNewItems([]); setNewItemsCount(0); @@ -521,16 +523,25 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): [filters.statusFilter, toast], ); + // Extract stable callbacks for dependencies + const invalidateQuery = useCallback(() => { + queueQuery.invalidate(); + }, [queueQuery.invalidate]); + + const resetPagination = useCallback(() => { + pagination.reset(); + }, [pagination.reset]); + // Mark initial fetch as complete when query loads useEffect(() => { if (!queueQuery.isLoading && !initialFetchCompleteRef.current) { initialFetchCompleteRef.current = true; isMountingRef.current = false; - console.log('✅ Initial queue fetch complete'); + logger.log('✅ Initial queue fetch complete'); } }, [queueQuery.isLoading]); - // Invalidate query when filters or sort changes + // Invalidate query when filters or sort changes (OPTIMIZED) useEffect(() => { if ( !user || @@ -538,36 +549,41 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): isMountingRef.current ) return; - console.log('🔄 Filters/sort changed, invalidating query'); - pagination.reset(); - queueQuery.invalidate(); + logger.log('🔄 Filters/sort changed, invalidating query'); + resetPagination(); + invalidateQuery(); }, [ filters.debouncedEntityFilter, filters.debouncedStatusFilter, filters.debouncedSortConfig.field, filters.debouncedSortConfig.direction, user, - queueQuery, - pagination + invalidateQuery, + resetPagination ]); - // Polling effect (when realtime disabled) + // Polling effect (when realtime disabled) - MUTUALLY EXCLUSIVE useEffect(() => { - if (!user || settings.refreshMode !== "auto" || loadingState === "initial" || settings.useRealtimeQueue) { + const shouldPoll = settings.refreshMode === 'auto' + && !settings.useRealtimeQueue + && loadingState !== 'initial' + && !!user; + + if (!shouldPoll) { return; } - console.log("⚠️ Polling ENABLED - interval:", settings.pollInterval); + logger.log("⚠️ Polling ENABLED - interval:", settings.pollInterval); const interval = setInterval(() => { - console.log("🔄 Polling refresh triggered"); + logger.log("🔄 Polling refresh triggered"); queueQuery.refetch(); }, settings.pollInterval); return () => { clearInterval(interval); - console.log("🛑 Polling stopped"); + logger.log("🛑 Polling stopped"); }; - }, [user, settings.refreshMode, settings.pollInterval, loadingState, settings.useRealtimeQueue, queueQuery]); + }, [user, settings.refreshMode, settings.pollInterval, loadingState, settings.useRealtimeQueue, queueQuery.refetch]); // Initialize realtime subscriptions useRealtimeSubscriptions({ @@ -591,21 +607,18 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig): if (recentlyRemovedRef.current.has(item.id)) return; if (interactingWith.has(item.id)) return; - if (shouldRemove) { - setItems((prev) => prev.filter((i) => i.id !== item.id)); - } else { - setItems((prev) => { - const exists = prev.some((i) => i.id === item.id); - if (exists) { - return prev.map((i) => (i.id === item.id ? item : i)); - } else { - return [item, ...prev]; - } - }); + // Only track removals for optimistic update protection + if (shouldRemove && !recentlyRemovedRef.current.has(item.id)) { + recentlyRemovedRef.current.add(item.id); + setTimeout(() => recentlyRemovedRef.current.delete(item.id), MODERATION_CONSTANTS.REALTIME_OPTIMISTIC_REMOVAL_TIMEOUT); } + // TanStack Query handles actual state updates via invalidation }, onItemRemoved: (itemId: string) => { - setItems((prev) => prev.filter((i) => i.id !== itemId)); + // Track for optimistic update protection + recentlyRemovedRef.current.add(itemId); + setTimeout(() => recentlyRemovedRef.current.delete(itemId), MODERATION_CONSTANTS.REALTIME_OPTIMISTIC_REMOVAL_TIMEOUT); + // TanStack Query handles removal via invalidation }, entityCache, profileCache, diff --git a/src/hooks/moderation/usePagination.ts b/src/hooks/moderation/usePagination.ts index a929f767..0fd045de 100644 --- a/src/hooks/moderation/usePagination.ts +++ b/src/hooks/moderation/usePagination.ts @@ -5,6 +5,7 @@ */ import { useState, useCallback, useEffect, useMemo } from 'react'; +import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; export interface PaginationConfig { /** Initial page number (1-indexed) */ @@ -104,7 +105,7 @@ export interface PaginationState { export function usePagination(config: PaginationConfig = {}): PaginationState { const { initialPage = 1, - initialPageSize = 25, + initialPageSize = MODERATION_CONSTANTS.DEFAULT_PAGE_SIZE, persist = false, storageKey = 'pagination_state', onPageChange, @@ -231,6 +232,7 @@ export function usePagination(config: PaginationConfig = {}): PaginationState { [currentPage, totalPages] ); + // Return without useMemo wrapper (OPTIMIZED) return { currentPage, pageSize, diff --git a/src/hooks/moderation/useProfileCache.ts b/src/hooks/moderation/useProfileCache.ts index c3cc839b..3d3eca89 100644 --- a/src/hooks/moderation/useProfileCache.ts +++ b/src/hooks/moderation/useProfileCache.ts @@ -1,5 +1,7 @@ -import { useRef, useCallback, useMemo } from 'react'; +import { useRef, useCallback } from 'react'; import { supabase } from '@/integrations/supabase/client'; +import { logger } from '@/lib/logger'; +import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; /** * Profile data structure returned from the database @@ -55,10 +57,21 @@ export function useProfileCache() { }, []); /** - * Set a cached profile + * Set a cached profile with LRU eviction */ const setCached = useCallback((userId: string, profile: CachedProfile): void => { - cacheRef.current.set(userId, profile); + const cache = cacheRef.current; + + // LRU eviction + if (cache.size >= MODERATION_CONSTANTS.MAX_PROFILE_CACHE_SIZE) { + const firstKey = cache.keys().next().value; + if (firstKey) { + cache.delete(firstKey); + logger.log(`♻️ [ProfileCache] Evicted ${firstKey} (LRU)`); + } + } + + cache.set(userId, profile); }, []); /** @@ -92,7 +105,7 @@ export function useProfileCache() { .in('user_id', uncachedIds); if (error) { - console.error('Error fetching profiles:', error); + logger.error('Error fetching profiles:', error); return []; } @@ -105,7 +118,7 @@ export function useProfileCache() { return data || []; } catch (error) { - console.error('Failed to bulk fetch profiles:', error); + logger.error('Failed to bulk fetch profiles:', error); return []; } }, [getCached, setCached, getUncachedIds]); @@ -181,7 +194,8 @@ export function useProfileCache() { */ const getCacheRef = useCallback(() => cacheRef.current, []); - return useMemo(() => ({ + // Return without useMemo wrapper (OPTIMIZED) + return { getCached, has, setCached, @@ -195,19 +209,5 @@ export function useProfileCache() { getSize, getAllCachedIds, getCacheRef, - }), [ - getCached, - has, - setCached, - getUncachedIds, - bulkFetch, - fetchAsMap, - fetchForSubmissions, - getDisplayName, - invalidate, - clear, - getSize, - getAllCachedIds, - getCacheRef, - ]); + }; } diff --git a/src/hooks/moderation/useQueueQuery.ts b/src/hooks/moderation/useQueueQuery.ts index 1adb08d8..6d09b744 100644 --- a/src/hooks/moderation/useQueueQuery.ts +++ b/src/hooks/moderation/useQueueQuery.ts @@ -8,6 +8,8 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { fetchSubmissions, type QueryConfig } from '@/lib/moderation/queries'; import { supabase } from '@/integrations/supabase/client'; +import { logger } from '@/lib/logger'; +import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; import type { ModerationItem, EntityFilter, @@ -116,22 +118,22 @@ export function useQueueQuery(config: UseQueueQueryConfig): UseQueueQueryReturn const query = useQuery({ queryKey, queryFn: async () => { - console.log('🔍 [TanStack Query] Fetching queue data:', queryKey); + logger.log('🔍 [TanStack Query] Fetching queue data:', queryKey); const result = await fetchSubmissions(supabase, queryConfig); if (result.error) { - console.error('❌ [TanStack Query] Error:', result.error); + logger.error('❌ [TanStack Query] Error:', result.error); throw result.error; } - console.log('✅ [TanStack Query] Fetched', result.submissions.length, 'items'); + logger.log('✅ [TanStack Query] Fetched', result.submissions.length, 'items'); return result; }, enabled: config.enabled !== false && !!config.userId, - staleTime: 30000, // 30 seconds - gcTime: 5 * 60 * 1000, // 5 minutes - retry: 2, // Retry failed requests up to 2 times - retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), // Exponential backoff + staleTime: MODERATION_CONSTANTS.QUERY_STALE_TIME, + gcTime: MODERATION_CONSTANTS.QUERY_GC_TIME, + retry: MODERATION_CONSTANTS.QUERY_RETRY_COUNT, + retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), }); // Invalidate helper diff --git a/src/hooks/moderation/useRealtimeSubscriptions.ts b/src/hooks/moderation/useRealtimeSubscriptions.ts index 96c1b5ec..543a5e59 100644 --- a/src/hooks/moderation/useRealtimeSubscriptions.ts +++ b/src/hooks/moderation/useRealtimeSubscriptions.ts @@ -8,6 +8,8 @@ import { useEffect, useRef, useState, useCallback } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { supabase } from '@/integrations/supabase/client'; +import { logger } from '@/lib/logger'; +import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; import type { RealtimeChannel } from '@supabase/supabase-js'; import type { ModerationItem, EntityFilter, StatusFilter } from '@/types/moderation'; import type { useEntityCache } from './useEntityCache'; @@ -47,7 +49,7 @@ export interface RealtimeSubscriptionConfig { /** Pause subscriptions when tab is hidden (default: true) */ pauseWhenHidden?: boolean; - /** Debounce delay for UPDATE events in milliseconds (default: 500) */ + /** Debounce delay for UPDATE events in milliseconds */ debounceMs?: number; /** Entity cache for resolving entity names */ @@ -95,7 +97,7 @@ export function useRealtimeSubscriptions( onUpdateItem, onItemRemoved, pauseWhenHidden = true, - debounceMs = 500, + debounceMs = MODERATION_CONSTANTS.REALTIME_DEBOUNCE_MS, entityCache, profileCache, recentlyRemovedIds, @@ -151,7 +153,7 @@ export function useRealtimeSubscriptions( .single(); if (error || !submission) { - console.error('Error fetching submission details:', error); + logger.error('Error fetching submission details:', error); return null; } @@ -243,17 +245,17 @@ export function useRealtimeSubscriptions( const handleInsert = useCallback(async (payload: any) => { const newSubmission = payload.new as any; - console.log('🆕 Realtime INSERT:', newSubmission.id); + logger.log('🆕 Realtime INSERT:', newSubmission.id); // Queue updates if tab is hidden if (pauseWhenHidden && document.hidden) { - console.log('📴 Realtime event received while hidden - queuing for later'); + logger.log('📴 Realtime event received while hidden - queuing for later'); return; } // Ignore if recently removed (optimistic update) if (recentlyRemovedIds.has(newSubmission.id)) { - console.log('⏭️ Ignoring INSERT for recently removed submission:', newSubmission.id); + logger.log('⏭️ Ignoring INSERT for recently removed submission:', newSubmission.id); return; } @@ -271,7 +273,7 @@ export function useRealtimeSubscriptions( return; } - console.log('✅ NEW submission matches filters, invalidating query:', newSubmission.id); + logger.log('✅ NEW submission matches filters, invalidating query:', newSubmission.id); // Invalidate the query to trigger background refetch await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] }); @@ -296,7 +298,7 @@ export function useRealtimeSubscriptions( onNewItem(fullItem); } catch (error) { - console.error('Error building new item notification:', error); + logger.error('Error building new item notification:', error); } }, [ filters, @@ -316,23 +318,23 @@ export function useRealtimeSubscriptions( const updatedSubmission = payload.new as any; const oldSubmission = payload.old as any; - console.log('🔄 Realtime UPDATE:', updatedSubmission.id); + logger.log('🔄 Realtime UPDATE:', updatedSubmission.id); // Queue updates if tab is hidden if (pauseWhenHidden && document.hidden) { - console.log('📴 Realtime UPDATE received while hidden - queuing for later'); + logger.log('📴 Realtime UPDATE received while hidden - queuing for later'); return; } // Ignore if recently removed (optimistic update in progress) if (recentlyRemovedIds.has(updatedSubmission.id)) { - console.log('⏭️ Ignoring UPDATE for recently removed submission:', updatedSubmission.id); + logger.log('⏭️ Ignoring UPDATE for recently removed submission:', updatedSubmission.id); return; } // Ignore if currently being interacted with if (interactingWithIds.has(updatedSubmission.id)) { - console.log('⏭️ Ignoring UPDATE for interacting submission:', updatedSubmission.id); + logger.log('⏭️ Ignoring UPDATE for interacting submission:', updatedSubmission.id); return; } @@ -340,7 +342,7 @@ export function useRealtimeSubscriptions( const isStatusChange = oldSubmission?.status !== updatedSubmission.status; if (isStatusChange) { - console.log('⚡ Status change detected, invalidating immediately'); + logger.log('⚡ Status change detected, invalidating immediately'); await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] }); const matchesEntity = matchesEntityFilter(updatedSubmission, filters.entityFilter); @@ -355,7 +357,7 @@ export function useRealtimeSubscriptions( // Use debounce for non-critical updates debouncedUpdate(updatedSubmission.id, async () => { - console.log('🔄 Invalidating query due to UPDATE:', updatedSubmission.id); + logger.log('🔄 Invalidating query due to UPDATE:', updatedSubmission.id); // Simply invalidate the query - TanStack Query handles the rest await queryClient.invalidateQueries({ queryKey: ['moderation-queue'] }); @@ -388,7 +390,7 @@ export function useRealtimeSubscriptions( return; } - console.log('📡 Setting up INSERT subscription'); + logger.log('📡 Setting up INSERT subscription'); const channel = supabase .channel('moderation-new-submissions') @@ -402,7 +404,7 @@ export function useRealtimeSubscriptions( handleInsert ) .subscribe((status) => { - console.log('INSERT subscription status:', status); + logger.log('INSERT subscription status:', status); if (status === 'SUBSCRIBED') { setChannelStatus('connected'); } else if (status === 'CHANNEL_ERROR') { @@ -413,7 +415,7 @@ export function useRealtimeSubscriptions( insertChannelRef.current = channel; return () => { - console.log('🛑 Cleaning up INSERT subscription'); + logger.log('🛑 Cleaning up INSERT subscription'); supabase.removeChannel(channel); insertChannelRef.current = null; }; @@ -425,7 +427,7 @@ export function useRealtimeSubscriptions( useEffect(() => { if (!enabled) return; - console.log('📡 Setting up UPDATE subscription'); + logger.log('📡 Setting up UPDATE subscription'); const channel = supabase .channel('moderation-updated-submissions') @@ -439,7 +441,7 @@ export function useRealtimeSubscriptions( handleUpdate ) .subscribe((status) => { - console.log('UPDATE subscription status:', status); + logger.log('UPDATE subscription status:', status); if (status === 'SUBSCRIBED') { setChannelStatus('connected'); } else if (status === 'CHANNEL_ERROR') { @@ -450,7 +452,7 @@ export function useRealtimeSubscriptions( updateChannelRef.current = channel; return () => { - console.log('🛑 Cleaning up UPDATE subscription'); + logger.log('🛑 Cleaning up UPDATE subscription'); supabase.removeChannel(channel); updateChannelRef.current = null; }; @@ -470,7 +472,7 @@ export function useRealtimeSubscriptions( * Manual reconnect function */ const reconnect = useCallback(() => { - console.log('🔄 Manually reconnecting subscriptions...'); + logger.log('🔄 Manually reconnecting subscriptions...'); setReconnectTrigger(prev => prev + 1); }, []); diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 00000000..558b6714 --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,20 @@ +/** + * Logger Utility + * + * Provides conditional logging based on environment. + * Prevents console noise in production builds. + */ + +const isDev = import.meta.env.DEV; + +export const logger = { + log: (...args: any[]) => { + if (isDev) console.log(...args); + }, + error: (...args: any[]) => { + console.error(...args); // Always log errors + }, + warn: (...args: any[]) => { + if (isDev) console.warn(...args); + }, +}; diff --git a/src/lib/moderation/constants.ts b/src/lib/moderation/constants.ts new file mode 100644 index 00000000..6bb8ac61 --- /dev/null +++ b/src/lib/moderation/constants.ts @@ -0,0 +1,33 @@ +/** + * Moderation Queue Constants + * + * Centralized configuration values for the moderation system. + */ + +export const MODERATION_CONSTANTS = { + // TanStack Query configuration + QUERY_STALE_TIME: 30000, // 30 seconds + QUERY_GC_TIME: 5 * 60 * 1000, // 5 minutes + QUERY_RETRY_COUNT: 2, + + // Realtime configuration + REALTIME_DEBOUNCE_MS: 500, // 500ms + REALTIME_OPTIMISTIC_REMOVAL_TIMEOUT: 5000, // 5 seconds + + // Lock configuration + LOCK_DURATION_MS: 15 * 60 * 1000, // 15 minutes + LOCK_EXTENSION_MS: 10 * 60 * 1000, // 10 minutes + + // Cache configuration + MAX_ENTITY_CACHE_SIZE: 500, + MAX_PROFILE_CACHE_SIZE: 500, + + // Pagination + DEFAULT_PAGE_SIZE: 25, + MAX_PAGE_SIZE: 100, + + // Filter debounce + FILTER_DEBOUNCE_MS: 300, +} as const; + +export type ModerationConstants = typeof MODERATION_CONSTANTS; diff --git a/src/lib/moderation/index.ts b/src/lib/moderation/index.ts index 5e81e3de..3854e33c 100644 --- a/src/lib/moderation/index.ts +++ b/src/lib/moderation/index.ts @@ -64,3 +64,7 @@ export { } from './lockHelpers'; export type { LockStatus, LockUrgency } from './lockHelpers'; + +// Constants +export { MODERATION_CONSTANTS } from './constants'; +export type { ModerationConstants } from './constants';