mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 05:07:03 -05:00
Compare commits
2 Commits
09de0772ea
...
6e1ff944c8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e1ff944c8 | ||
|
|
1f93e7433b |
128
package-lock.json
generated
128
package-lock.json
generated
@@ -8,7 +8,6 @@
|
||||
"name": "vite_react_shadcn_ts",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@cronitorio/cronitor-rum": "^0.4.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -109,7 +108,6 @@
|
||||
"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"
|
||||
@@ -655,15 +653,6 @@
|
||||
"solid-js": "^1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@cronitorio/cronitor-rum": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@cronitorio/cronitor-rum/-/cronitor-rum-0.4.1.tgz",
|
||||
"integrity": "sha512-myOOC8Cvv+881hasEV/fsnUswKXMVKfHtioZIy5SCcRPvH2jCV7T/DyZLXjm97FaeWIfTlu4s5P0dKW1zi7T3A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"web-vitals": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
@@ -1602,7 +1591,6 @@
|
||||
"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",
|
||||
@@ -1628,7 +1616,6 @@
|
||||
"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",
|
||||
@@ -4467,7 +4454,7 @@
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.14.0.tgz",
|
||||
"integrity": "sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -4676,7 +4663,7 @@
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
@@ -4692,7 +4679,7 @@
|
||||
"version": "0.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
||||
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
@@ -4989,14 +4976,12 @@
|
||||
"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": "*",
|
||||
@@ -5007,7 +4992,7 @@
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
@@ -5853,14 +5838,12 @@
|
||||
"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",
|
||||
@@ -5874,7 +5857,6 @@
|
||||
"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": {
|
||||
@@ -6025,7 +6007,6 @@
|
||||
"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"
|
||||
@@ -6150,7 +6131,6 @@
|
||||
"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"
|
||||
@@ -6248,7 +6228,6 @@
|
||||
"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",
|
||||
@@ -6273,7 +6252,6 @@
|
||||
"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"
|
||||
@@ -6423,7 +6401,6 @@
|
||||
"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"
|
||||
@@ -6495,7 +6472,6 @@
|
||||
"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"
|
||||
@@ -6747,7 +6723,6 @@
|
||||
"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/diff": {
|
||||
@@ -6763,7 +6738,6 @@
|
||||
"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": {
|
||||
@@ -7970,7 +7944,6 @@
|
||||
"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,
|
||||
@@ -8060,7 +8033,6 @@
|
||||
"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"
|
||||
@@ -8402,7 +8374,6 @@
|
||||
"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"
|
||||
@@ -8415,7 +8386,6 @@
|
||||
"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"
|
||||
@@ -8528,6 +8498,17 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/isomorphic.js": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
|
||||
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
@@ -8547,7 +8528,6 @@
|
||||
"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"
|
||||
@@ -8641,11 +8621,32 @@
|
||||
"integrity": "sha512-3VuV8xXhh5xJA6tzvfDvE0YBCMkIZUmxtRilJQDDdCgJCc+eut6qAv2qbN+pbqvarqcQqPN1UF+8YvsjmyOZpw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lib0": {
|
||||
"version": "0.2.114",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz",
|
||||
"integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
|
||||
"0gentesthtml": "bin/gentesthtml.js",
|
||||
"0serve": "bin/0serve.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"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"
|
||||
@@ -8658,7 +8659,6 @@
|
||||
"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": {
|
||||
@@ -9910,7 +9910,6 @@
|
||||
"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",
|
||||
@@ -10028,7 +10027,6 @@
|
||||
"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"
|
||||
@@ -10057,7 +10055,6 @@
|
||||
"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"
|
||||
@@ -10255,7 +10252,6 @@
|
||||
"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": {
|
||||
@@ -10291,7 +10287,6 @@
|
||||
"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": {
|
||||
@@ -10310,7 +10305,6 @@
|
||||
"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"
|
||||
@@ -10320,7 +10314,6 @@
|
||||
"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"
|
||||
@@ -10374,7 +10367,6 @@
|
||||
"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",
|
||||
@@ -10403,7 +10395,6 @@
|
||||
"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",
|
||||
@@ -10421,7 +10412,6 @@
|
||||
"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",
|
||||
@@ -10447,7 +10437,6 @@
|
||||
"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",
|
||||
@@ -10490,7 +10479,6 @@
|
||||
"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",
|
||||
@@ -10516,7 +10504,6 @@
|
||||
"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",
|
||||
@@ -10544,14 +10531,12 @@
|
||||
"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",
|
||||
@@ -10943,7 +10928,6 @@
|
||||
"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"
|
||||
@@ -10953,7 +10937,6 @@
|
||||
"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"
|
||||
@@ -11066,7 +11049,6 @@
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.1",
|
||||
@@ -11116,7 +11098,7 @@
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
||||
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
@@ -11420,7 +11402,6 @@
|
||||
"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"
|
||||
@@ -11605,7 +11586,6 @@
|
||||
"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",
|
||||
@@ -11641,7 +11621,6 @@
|
||||
"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"
|
||||
@@ -11670,7 +11649,6 @@
|
||||
"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",
|
||||
@@ -11717,7 +11695,6 @@
|
||||
"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",
|
||||
@@ -11747,7 +11724,6 @@
|
||||
"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"
|
||||
@@ -11757,7 +11733,6 @@
|
||||
"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"
|
||||
@@ -11842,7 +11817,6 @@
|
||||
"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/ts-morph": {
|
||||
@@ -11948,7 +11922,6 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -12230,7 +12203,6 @@
|
||||
"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/uuid": {
|
||||
@@ -12829,12 +12801,6 @@
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/web-vitals": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz",
|
||||
"integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
@@ -13014,6 +12980,24 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/yjs": {
|
||||
"version": "13.6.27",
|
||||
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz",
|
||||
"integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.99"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cronitorio/cronitor-rum": "^0.4.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
|
||||
26
src/App.tsx
26
src/App.tsx
@@ -5,11 +5,10 @@ import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
|
||||
import * as Cronitor from '@cronitorio/cronitor-rum';
|
||||
import { AuthProvider } from "@/hooks/useAuth";
|
||||
import { AuthModalProvider } from "@/contexts/AuthModalContext";
|
||||
import { MFAStepUpProvider } from "@/contexts/MFAStepUpContext";
|
||||
import { CronitorHealthProvider, useCronitorHealth } from "@/contexts/CronitorHealthContext";
|
||||
import { APIConnectivityProvider, useAPIConnectivity } from "@/contexts/APIConnectivityContext";
|
||||
import { LocationAutoDetectProvider } from "@/components/providers/LocationAutoDetectProvider";
|
||||
import { AnalyticsWrapper } from "@/components/analytics/AnalyticsWrapper";
|
||||
import { Footer } from "@/components/layout/Footer";
|
||||
@@ -135,8 +134,8 @@ function NavigationTracker() {
|
||||
|
||||
function AppContent(): React.JSX.Element {
|
||||
// Check if API status banner is visible to add padding
|
||||
const { passing, isBannerDismissed } = useCronitorHealth();
|
||||
const showBanner = passing === false && !isBannerDismissed;
|
||||
const { isAPIReachable, isBannerDismissed } = useAPIConnectivity();
|
||||
const showBanner = !isAPIReachable && !isBannerDismissed;
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
@@ -399,31 +398,16 @@ function AppContent(): React.JSX.Element {
|
||||
}
|
||||
|
||||
const App = (): React.JSX.Element => {
|
||||
// Initialize Cronitor RUM before router mounts
|
||||
useEffect(() => {
|
||||
try {
|
||||
Cronitor.load("0b5d17d3f7625ce8766c2c4c85c1895d", {
|
||||
debug: import.meta.env.DEV, // Enable debug logs in development only
|
||||
trackMode: 'history', // Automatically track page views with React Router
|
||||
});
|
||||
|
||||
// Log successful initialization
|
||||
console.log('[Cronitor] RUM initialized');
|
||||
} catch (error) {
|
||||
console.error('[Cronitor] Failed to initialize:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<AuthModalProvider>
|
||||
<MFAStepUpProvider>
|
||||
<CronitorHealthProvider>
|
||||
<APIConnectivityProvider>
|
||||
<BrowserRouter>
|
||||
<AppContent />
|
||||
</BrowserRouter>
|
||||
</CronitorHealthProvider>
|
||||
</APIConnectivityProvider>
|
||||
</MFAStepUpProvider>
|
||||
</AuthModalProvider>
|
||||
</AuthProvider>
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { AlertTriangle, X, ExternalLink } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useCronitorHealth } from '@/contexts/CronitorHealthContext';
|
||||
import { useAPIConnectivity } from '@/contexts/APIConnectivityContext';
|
||||
|
||||
/**
|
||||
* Banner displayed when Cronitor detects Supabase API is down
|
||||
* Banner displayed when Supabase API is unreachable
|
||||
* Includes link to status page and dismissal option
|
||||
*/
|
||||
export function APIStatusBanner() {
|
||||
const { passing, isLoading, isBannerDismissed, dismissBanner } = useCronitorHealth();
|
||||
const { isAPIReachable, isBannerDismissed, dismissBanner } = useAPIConnectivity();
|
||||
|
||||
// Don't show if:
|
||||
// - Still loading initial data
|
||||
// - API is healthy (passing === true)
|
||||
// - User dismissed it
|
||||
// - Status unknown (passing === null) after initial load
|
||||
if (isLoading || passing === true || passing === null || isBannerDismissed) {
|
||||
// Show banner when API is down AND not dismissed
|
||||
if (isAPIReachable || isBannerDismissed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -25,9 +21,9 @@ export function APIStatusBanner() {
|
||||
<div className="flex items-center gap-3 flex-1">
|
||||
<AlertTriangle className="h-5 w-5 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold">API Monitoring Alert</p>
|
||||
<p className="font-semibold">API Connection Issue</p>
|
||||
<p className="text-sm opacity-90">
|
||||
Supabase services are experiencing issues. Some features may be temporarily unavailable.
|
||||
Unable to reach the Supabase API. The service may be experiencing an outage or your connection may be interrupted.
|
||||
</p>
|
||||
<a
|
||||
href="https://status.thrillwiki.com"
|
||||
@@ -35,7 +31,7 @@ export function APIStatusBanner() {
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm inline-flex items-center gap-1 mt-1 underline hover:opacity-80 transition-opacity"
|
||||
>
|
||||
View Status Page
|
||||
Check Status Page
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
87
src/contexts/APIConnectivityContext.tsx
Normal file
87
src/contexts/APIConnectivityContext.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { createContext, useContext, ReactNode, useState, useEffect } from 'react';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
interface APIConnectivityContextType {
|
||||
isAPIReachable: boolean;
|
||||
isBannerDismissed: boolean;
|
||||
dismissBanner: () => void;
|
||||
}
|
||||
|
||||
const APIConnectivityContext = createContext<APIConnectivityContextType | undefined>(undefined);
|
||||
|
||||
const DISMISSAL_DURATION = 15 * 60 * 1000; // 15 minutes
|
||||
const DISMISSAL_KEY = 'api-connectivity-dismissed-until';
|
||||
const REACHABILITY_KEY = 'api-reachable';
|
||||
|
||||
export function APIConnectivityProvider({ children }: { children: ReactNode }) {
|
||||
const [isAPIReachable, setIsAPIReachable] = useState<boolean>(() => {
|
||||
const stored = sessionStorage.getItem(REACHABILITY_KEY);
|
||||
return stored !== 'false'; // Default to true, only false if explicitly set
|
||||
});
|
||||
|
||||
const [dismissedUntil, setDismissedUntil] = useState<number | null>(() => {
|
||||
const stored = localStorage.getItem(DISMISSAL_KEY);
|
||||
return stored ? parseInt(stored) : null;
|
||||
});
|
||||
|
||||
const dismissBanner = () => {
|
||||
const until = Date.now() + DISMISSAL_DURATION;
|
||||
localStorage.setItem(DISMISSAL_KEY, until.toString());
|
||||
setDismissedUntil(until);
|
||||
logger.info('API status banner dismissed', { until: new Date(until).toISOString() });
|
||||
};
|
||||
|
||||
const isBannerDismissed = dismissedUntil ? Date.now() < dismissedUntil : false;
|
||||
|
||||
// Auto-clear dismissal when API is healthy again
|
||||
useEffect(() => {
|
||||
if (isAPIReachable && dismissedUntil) {
|
||||
localStorage.removeItem(DISMISSAL_KEY);
|
||||
setDismissedUntil(null);
|
||||
logger.info('API status banner dismissal cleared (API recovered)');
|
||||
}
|
||||
}, [isAPIReachable, dismissedUntil]);
|
||||
|
||||
// Listen for custom events from error handler
|
||||
useEffect(() => {
|
||||
const handleAPIDown = () => {
|
||||
logger.warn('API connectivity lost');
|
||||
setIsAPIReachable(false);
|
||||
sessionStorage.setItem(REACHABILITY_KEY, 'false');
|
||||
};
|
||||
|
||||
const handleAPIUp = () => {
|
||||
logger.info('API connectivity restored');
|
||||
setIsAPIReachable(true);
|
||||
sessionStorage.setItem(REACHABILITY_KEY, 'true');
|
||||
};
|
||||
|
||||
window.addEventListener('api-connectivity-down', handleAPIDown);
|
||||
window.addEventListener('api-connectivity-up', handleAPIUp);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('api-connectivity-down', handleAPIDown);
|
||||
window.removeEventListener('api-connectivity-up', handleAPIUp);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<APIConnectivityContext.Provider
|
||||
value={{
|
||||
isAPIReachable,
|
||||
isBannerDismissed,
|
||||
dismissBanner,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</APIConnectivityContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAPIConnectivity() {
|
||||
const context = useContext(APIConnectivityContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAPIConnectivity must be used within APIConnectivityProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { createContext, useContext, ReactNode, useState, useEffect } from 'react';
|
||||
import { useCronitorHealth as useCronitorHealthQuery } from '@/hooks/useCronitorHealth';
|
||||
|
||||
interface CronitorHealthContextType {
|
||||
passing: boolean | null; // null = loading/unknown
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
lastChecked: Date | null;
|
||||
isBannerDismissed: boolean;
|
||||
dismissBanner: () => void;
|
||||
}
|
||||
|
||||
const CronitorHealthContext = createContext<CronitorHealthContextType | undefined>(undefined);
|
||||
|
||||
const DISMISSAL_DURATION = 15 * 60 * 1000; // 15 minutes
|
||||
const DISMISSAL_KEY = 'cronitor-banner-dismissed';
|
||||
|
||||
export function CronitorHealthProvider({ children }: { children: ReactNode }) {
|
||||
const { data, isLoading, error } = useCronitorHealthQuery();
|
||||
const [dismissedUntil, setDismissedUntil] = useState<number | null>(() => {
|
||||
const stored = localStorage.getItem(DISMISSAL_KEY);
|
||||
return stored ? parseInt(stored) : null;
|
||||
});
|
||||
|
||||
const dismissBanner = () => {
|
||||
const until = Date.now() + DISMISSAL_DURATION;
|
||||
localStorage.setItem(DISMISSAL_KEY, until.toString());
|
||||
setDismissedUntil(until);
|
||||
};
|
||||
|
||||
const isBannerDismissed = dismissedUntil ? Date.now() < dismissedUntil : false;
|
||||
|
||||
// Auto-clear dismissal when API is healthy again
|
||||
useEffect(() => {
|
||||
if (data?.passing === true && dismissedUntil) {
|
||||
localStorage.removeItem(DISMISSAL_KEY);
|
||||
setDismissedUntil(null);
|
||||
}
|
||||
}, [data?.passing, dismissedUntil]);
|
||||
|
||||
return (
|
||||
<CronitorHealthContext.Provider
|
||||
value={{
|
||||
passing: data?.passing ?? null,
|
||||
isLoading,
|
||||
error: error as Error | null,
|
||||
lastChecked: data ? new Date() : null,
|
||||
isBannerDismissed,
|
||||
dismissBanner,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CronitorHealthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useCronitorHealth() {
|
||||
const context = useContext(CronitorHealthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useCronitorHealth must be used within CronitorHealthProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { isRetryableError } from '@/lib/retryHelpers';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
const CRONITOR_API_URL = 'https://cronitor.io/api/monitors/88kG4W?env=production&format=json';
|
||||
const POLL_INTERVAL = 60000; // 60 seconds
|
||||
|
||||
// Retry configuration
|
||||
const MAX_RETRIES = 3;
|
||||
const BASE_DELAY = 1000; // 1 second
|
||||
const MAX_DELAY = 10000; // 10 seconds
|
||||
|
||||
interface CronitorResponse {
|
||||
passing: boolean;
|
||||
[key: string]: any; // Other fields we don't need
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate exponential backoff delay with jitter
|
||||
*/
|
||||
function calculateRetryDelay(failureCount: number): number {
|
||||
const exponentialDelay = BASE_DELAY * Math.pow(2, failureCount - 1);
|
||||
const cappedDelay = Math.min(exponentialDelay, MAX_DELAY);
|
||||
|
||||
// Add ±30% jitter to prevent thundering herd
|
||||
const jitterAmount = cappedDelay * 0.3;
|
||||
const jitterOffset = (Math.random() * 2 - 1) * jitterAmount;
|
||||
|
||||
return Math.max(0, cappedDelay + jitterOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to poll Cronitor API for health status
|
||||
* Returns the monitor's passing status (true = healthy, false = down)
|
||||
* Implements exponential backoff retry with jitter
|
||||
*/
|
||||
export function useCronitorHealth() {
|
||||
return useQuery({
|
||||
queryKey: ['cronitor-health'],
|
||||
queryFn: async (): Promise<CronitorResponse> => {
|
||||
const response = await fetch(CRONITOR_API_URL, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Cronitor API error: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
retry: (failureCount, error) => {
|
||||
// Use existing retry logic to determine if error is retryable
|
||||
if (!isRetryableError(error)) {
|
||||
logger.warn('Cronitor health check: Non-retryable error', { error });
|
||||
|
||||
// Log non-retryable errors to error monitoring (non-critical)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Cronitor Health Check - Non-Retryable Error',
|
||||
metadata: {
|
||||
failureCount,
|
||||
errorType: 'non_retryable',
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retry up to MAX_RETRIES times
|
||||
if (failureCount >= MAX_RETRIES) {
|
||||
logger.error('Cronitor health check: Max retries exhausted', {
|
||||
error,
|
||||
totalAttempts: MAX_RETRIES,
|
||||
});
|
||||
|
||||
// Track exhausted retries in Cronitor RUM and error monitoring
|
||||
if (typeof window !== 'undefined' && window.cronitor) {
|
||||
window.cronitor.track('cronitor_health_check_failed', {
|
||||
totalAttempts: MAX_RETRIES,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
severity: 'high',
|
||||
});
|
||||
}
|
||||
|
||||
// Log to error monitoring system (non-critical, background operation)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Cronitor Health Check - Max Retries Exhausted',
|
||||
metadata: {
|
||||
totalAttempts: MAX_RETRIES,
|
||||
errorType: 'max_retries_exhausted',
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Track retry attempt in Cronitor RUM
|
||||
if (typeof window !== 'undefined' && window.cronitor) {
|
||||
window.cronitor.track('cronitor_health_check_retry', {
|
||||
attempt: failureCount + 1,
|
||||
maxAttempts: MAX_RETRIES,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`Cronitor health check: Retry attempt ${failureCount + 1}/${MAX_RETRIES}`, {
|
||||
attempt: failureCount + 1,
|
||||
maxAttempts: MAX_RETRIES,
|
||||
error,
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
retryDelay: calculateRetryDelay, // Use exponential backoff with jitter
|
||||
refetchInterval: POLL_INTERVAL, // Auto-poll every 60 seconds
|
||||
staleTime: 30000, // Consider data stale after 30 seconds
|
||||
gcTime: 5 * 60 * 1000, // Keep in cache for 5 minutes
|
||||
});
|
||||
}
|
||||
@@ -58,18 +58,6 @@ export const breadcrumb = {
|
||||
level: 'info',
|
||||
data,
|
||||
});
|
||||
|
||||
// Track critical user actions in Cronitor
|
||||
const criticalActions = ['submit', 'delete', 'update', 'create', 'login', 'logout'];
|
||||
const isCritical = criticalActions.some(ca => action.toLowerCase().includes(ca));
|
||||
|
||||
if (isCritical && typeof window !== 'undefined' && window.cronitor) {
|
||||
window.cronitor.track('critical_user_action', {
|
||||
action,
|
||||
component,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
apiCall: (endpoint: string, method: string, status?: number) => {
|
||||
@@ -81,19 +69,6 @@ export const breadcrumb = {
|
||||
level: isError ? 'error' : 'info',
|
||||
data: { endpoint, method, status },
|
||||
});
|
||||
|
||||
// Track significant API calls in Cronitor
|
||||
if (typeof window !== 'undefined' && window.cronitor) {
|
||||
// Only track errors and slow requests
|
||||
if (isError || (status && status >= 500)) {
|
||||
window.cronitor.track('api_error', {
|
||||
endpoint,
|
||||
method,
|
||||
status,
|
||||
severity: status >= 500 ? 'high' : 'medium',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stateChange: (description: string, data?: Record<string, any>) => {
|
||||
|
||||
@@ -4,16 +4,6 @@ import { supabase } from '@/integrations/supabase/client';
|
||||
import { breadcrumbManager } from './errorBreadcrumbs';
|
||||
import { captureEnvironmentContext } from './environmentContext';
|
||||
|
||||
// Cronitor RUM integration
|
||||
declare global {
|
||||
interface Window {
|
||||
cronitor?: {
|
||||
track: (eventName: string, data?: Record<string, any>) => void;
|
||||
error: (error: Error | string, metadata?: Record<string, any>) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type ErrorContext = {
|
||||
action: string;
|
||||
userId?: string;
|
||||
@@ -32,6 +22,35 @@ export class AppError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error is a Supabase connection/API error
|
||||
*/
|
||||
function isSupabaseConnectionError(error: unknown): boolean {
|
||||
if (error && typeof error === 'object') {
|
||||
const supabaseError = error as { code?: string; status?: number; message?: string };
|
||||
|
||||
// Connection timeout errors
|
||||
if (supabaseError.code === 'PGRST301') return true; // Timeout
|
||||
if (supabaseError.code === 'PGRST000') return true; // Connection error
|
||||
|
||||
// 5xx server errors
|
||||
if (supabaseError.status && supabaseError.status >= 500) return true;
|
||||
|
||||
// Database connection errors (08xxx codes)
|
||||
if (supabaseError.code?.startsWith('08')) return true;
|
||||
}
|
||||
|
||||
// Network fetch errors
|
||||
if (error instanceof TypeError) {
|
||||
const message = error.message.toLowerCase();
|
||||
if (message.includes('fetch') || message.includes('network') || message.includes('failed to fetch')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export const handleError = (
|
||||
error: unknown,
|
||||
context: ErrorContext
|
||||
@@ -40,6 +59,11 @@ export const handleError = (
|
||||
const errorId = (context.metadata?.requestId as string) || crypto.randomUUID();
|
||||
const shortErrorId = errorId.slice(0, 8);
|
||||
|
||||
// Check if this is a connection error and dispatch event
|
||||
if (isSupabaseConnectionError(error)) {
|
||||
window.dispatchEvent(new CustomEvent('api-connectivity-down'));
|
||||
}
|
||||
|
||||
// Enhanced error message and stack extraction
|
||||
let errorMessage: string;
|
||||
let stack: string | undefined;
|
||||
@@ -133,25 +157,6 @@ export const handleError = (
|
||||
});
|
||||
}
|
||||
|
||||
// Track error in Cronitor RUM
|
||||
if (typeof window !== 'undefined' && window.cronitor) {
|
||||
try {
|
||||
window.cronitor.error(
|
||||
error instanceof Error ? error : new Error(errorMessage),
|
||||
{
|
||||
errorId,
|
||||
errorName,
|
||||
action: context.action,
|
||||
userId: context.userId,
|
||||
supabaseError: supabaseErrorDetails,
|
||||
breadcrumbCount: breadcrumbManager.getAll().length,
|
||||
}
|
||||
);
|
||||
} catch (cronitorError) {
|
||||
logger.error('Failed to track error in Cronitor', { cronitorError });
|
||||
}
|
||||
}
|
||||
|
||||
// Log to database with breadcrumbs (non-blocking)
|
||||
try {
|
||||
const envContext = captureEnvironmentContext();
|
||||
@@ -247,21 +252,6 @@ export const handleNonCriticalError = (
|
||||
severity: 'low',
|
||||
});
|
||||
|
||||
// Track non-critical error in Cronitor (lower severity)
|
||||
if (typeof window !== 'undefined' && window.cronitor) {
|
||||
try {
|
||||
window.cronitor.track('non_critical_error', {
|
||||
errorId,
|
||||
errorMessage,
|
||||
action: context.action,
|
||||
userId: context.userId,
|
||||
severity: 'low',
|
||||
});
|
||||
} catch (cronitorError) {
|
||||
logger.error('Failed to track non-critical error in Cronitor', { cronitorError });
|
||||
}
|
||||
}
|
||||
|
||||
// Log to database with breadcrumbs (non-blocking, fire-and-forget)
|
||||
try {
|
||||
const envContext = captureEnvironmentContext();
|
||||
|
||||
@@ -243,28 +243,9 @@ export async function withRetry<T>(
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
|
||||
// Track exhausted retries in Cronitor
|
||||
if (typeof window !== 'undefined' && window.cronitor) {
|
||||
window.cronitor.track('retries_exhausted', {
|
||||
totalAttempts: config.maxAttempts,
|
||||
error: error instanceof Error ? error.name : 'UnknownError',
|
||||
severity: 'high',
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Track retry attempts in Cronitor
|
||||
if (attempt > 0 && typeof window !== 'undefined' && window.cronitor) {
|
||||
window.cronitor.track('retry_attempt', {
|
||||
attempt: attempt + 1,
|
||||
maxAttempts: config.maxAttempts,
|
||||
error: error instanceof Error ? error.name : 'UnknownError',
|
||||
willRetry: attempt < config.maxAttempts - 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate delay for next attempt
|
||||
const delay = calculateBackoffDelay(attempt, config);
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@ function createQueryProxy(queryBuilder: any, endpoint: string, operations: strin
|
||||
fullOperation || 'query',
|
||||
response?.error ? 400 : 200
|
||||
);
|
||||
|
||||
// Dispatch API connectivity up event on successful requests
|
||||
if (!response?.error) {
|
||||
window.dispatchEvent(new CustomEvent('api-connectivity-up'));
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
(error: any) => {
|
||||
|
||||
23
src/types/cronitor.d.ts
vendored
23
src/types/cronitor.d.ts
vendored
@@ -1,23 +0,0 @@
|
||||
declare module '@cronitorio/cronitor-rum' {
|
||||
export interface CronitorConfig {
|
||||
debug?: boolean;
|
||||
trackMode?: 'history' | 'off';
|
||||
}
|
||||
|
||||
export function load(apiKey: string, config?: CronitorConfig): void;
|
||||
|
||||
export function track(eventName: string, data?: Record<string, any>): void;
|
||||
|
||||
export function error(error: Error | string, metadata?: Record<string, any>): void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
cronitor?: {
|
||||
track: (eventName: string, data?: Record<string, any>) => void;
|
||||
error: (error: Error | string, metadata?: Record<string, any>) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user