aboutsummaryrefslogtreecommitdiff
path: root/174bg/manager
diff options
context:
space:
mode:
Diffstat (limited to '174bg/manager')
-rw-r--r--174bg/manager/.gitignore3
-rw-r--r--174bg/manager/index.html18
-rw-r--r--174bg/manager/package-lock.json1906
-rw-r--r--174bg/manager/package.json18
-rw-r--r--174bg/manager/public/index.html1163
-rw-r--r--174bg/manager/src/App.jsx182
-rw-r--r--174bg/manager/src/components/Ledger.jsx73
-rw-r--r--174bg/manager/src/components/OnCallSchedule.jsx186
-rw-r--r--174bg/manager/src/components/RequiredInfo.jsx66
-rw-r--r--174bg/manager/src/components/RolePreferences.jsx171
-rw-r--r--174bg/manager/src/components/Welcome.jsx14
-rw-r--r--174bg/manager/src/lib/oncallTime.js242
-rw-r--r--174bg/manager/src/lib/otherJsonData.js13
-rw-r--r--174bg/manager/src/lib/pocketbase.js3
-rw-r--r--174bg/manager/src/lib/roles.js142
-rw-r--r--174bg/manager/src/main.jsx10
-rw-r--r--174bg/manager/src/theme.css (renamed from 174bg/manager/public/theme.css)0
-rw-r--r--174bg/manager/vite.config.js9
-rw-r--r--174bg/manager/wrangler.jsonc2
19 files changed, 3050 insertions, 1171 deletions
diff --git a/174bg/manager/.gitignore b/174bg/manager/.gitignore
index 9397005..da996ac 100644
--- a/174bg/manager/.gitignore
+++ b/174bg/manager/.gitignore
@@ -1,2 +1,3 @@
/node_modules/
-/.wrangler \ No newline at end of file
+/.wrangler
+/dist \ No newline at end of file
diff --git a/174bg/manager/index.html b/174bg/manager/index.html
new file mode 100644
index 0000000..2527574
--- /dev/null
+++ b/174bg/manager/index.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>174th Battle Group: Manager</title>
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+ <link
+ href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Rajdhani:wght@400;500;600;700&display=swap"
+ rel="stylesheet"
+ />
+ </head>
+ <body>
+ <div id="root"></div>
+ <script type="module" src="/src/main.jsx"></script>
+ </body>
+</html>
diff --git a/174bg/manager/package-lock.json b/174bg/manager/package-lock.json
index 72956e6..505b3ad 100644
--- a/174bg/manager/package-lock.json
+++ b/174bg/manager/package-lock.json
@@ -5,13 +5,334 @@
"packages": {
"": {
"dependencies": {
+ "pocketbase": "^0.27.0",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.4",
+ "vite": "^6.2.0",
"wrangler": "^4.95.0"
}
},
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator/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",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@cloudflare/kv-asset-handler": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz",
"integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==",
+ "dev": true,
"license": "MIT OR Apache-2.0",
"engines": {
"node": ">=22.0.0"
@@ -21,6 +342,7 @@
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz",
"integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==",
+ "dev": true,
"license": "MIT OR Apache-2.0",
"peerDependencies": {
"unenv": "2.0.0-rc.24",
@@ -39,6 +361,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -55,6 +378,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -71,6 +395,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -87,6 +412,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -103,6 +429,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -116,6 +443,7 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
@@ -128,6 +456,7 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -141,6 +470,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -157,6 +487,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -173,6 +504,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -189,6 +521,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -205,6 +538,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -221,6 +555,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -237,6 +572,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -253,6 +589,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -269,6 +606,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -285,6 +623,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -301,6 +640,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -317,6 +657,7 @@
"cpu": [
"loong64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -333,6 +674,7 @@
"cpu": [
"mips64el"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -349,6 +691,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -365,6 +708,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -381,6 +725,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -397,6 +742,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -413,6 +759,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -429,6 +776,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -445,6 +793,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -461,6 +810,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -477,6 +827,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -493,6 +844,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -509,6 +861,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -525,6 +878,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -541,6 +895,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -554,6 +909,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -566,6 +922,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -588,6 +945,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -610,6 +968,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -626,6 +985,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -642,6 +1002,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -658,6 +1019,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -674,6 +1036,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -690,6 +1053,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -706,6 +1070,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -722,6 +1087,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -738,6 +1104,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -754,6 +1121,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -770,6 +1138,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -792,6 +1161,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -814,6 +1184,7 @@
"cpu": [
"ppc64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -836,6 +1207,7 @@
"cpu": [
"riscv64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -858,6 +1230,7 @@
"cpu": [
"s390x"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -880,6 +1253,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -902,6 +1276,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -924,6 +1299,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -946,6 +1322,7 @@
"cpu": [
"wasm32"
],
+ "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
@@ -965,6 +1342,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -984,6 +1362,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1003,6 +1382,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -1015,10 +1395,55 @@
"url": "https://opencollective.com/libvips"
}
},
+ "node_modules/@jridgewell/gen-mapping": {
+ "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",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping/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",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping/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",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"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"
@@ -1028,12 +1453,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.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
@@ -1044,6 +1471,7 @@
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
"integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"kleur": "^4.1.5"
@@ -1053,6 +1481,7 @@
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz",
"integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@poppinss/colors": "^4.1.5",
@@ -1064,12 +1493,410 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz",
"integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
"license": "MIT"
},
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@sindresorhus/is": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz",
"integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -1082,18 +1909,169 @@
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.15.tgz",
"integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==",
+ "dev": true,
"license": "CC0-1.0"
},
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.3.tgz",
+ "integrity": "sha512-sbT0Ui/CZwyAyy7icT1Gw5P1LKRlFaHwaF6tDCW5YHq2X5SeeZFphBuIagopSfwSSZq3sQcbmEL072yphxm7ew==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/blake3-wasm": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
"integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.7",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz",
+ "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.44",
+ "caniuse-lite": "^1.0.30001806",
+ "electron-to-chromium": "^1.5.393",
+ "node-releases": "^2.0.51",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001806",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
+ "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -1103,19 +2081,46 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.396",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz",
+ "integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/error-stack-parser-es": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
"integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==",
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
@@ -1125,6 +2130,7 @@
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -1162,10 +2168,39 @@
"@esbuild/win32-x64": "0.27.3"
}
},
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fsevents": {
"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,
@@ -1176,19 +2211,74 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
"node_modules/miniflare": {
"version": "4.20260526.0",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260526.0.tgz",
"integrity": "sha512-JYQ7jPZZWoaaj9jWHb8Ucp6Cu2SbDVqIsAJhumqdzzLkkfq0pYkDeino/sZfW1ixJWPjv/C44zjm9gVJC2izCA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "0.8.1",
@@ -1205,22 +2295,192 @@
"node": ">=22.0.0"
}
},
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.51",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz",
+ "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/path-to-regexp": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
"integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
"license": "MIT"
},
+ "node_modules/picocolors": {
+ "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": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pocketbase": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.27.0.tgz",
+ "integrity": "sha512-K5N6d93UP/BNMbMnlZ6BUfy9VPCIvLyqhJFOsNI8OsZwzvKWEAfyD36boi5K4ECIOl5HMlo0TzuaeGdKpMwizQ==",
+ "license": "MIT"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.23",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
+ "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.16",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
+ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.8"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
+ "@rollup/rollup-android-arm64": "4.62.2",
+ "@rollup/rollup-darwin-arm64": "4.62.2",
+ "@rollup/rollup-darwin-x64": "4.62.2",
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
+ "@rollup/rollup-freebsd-x64": "4.62.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
+ "@rollup/rollup-openbsd-x64": "4.62.2",
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/rosie-skills": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/rosie-skills/-/rosie-skills-0.6.4.tgz",
"integrity": "sha512-ojfhSiQRdZ2QyWbmKAHOSAUbaLYrTc5zIH7mS1jKoP8KCFSQddwVhMyFqldckTeybTfW3zNcsZzyOTzGTN1SBA==",
+ "dev": true,
"license": "BSD-3-Clause",
"bin": {
"rosie-skills": "dist/bin.js"
@@ -1241,6 +2501,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "BSD-3-Clause",
"optional": true,
"os": [
@@ -1254,6 +2515,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "BSD-3-Clause",
"optional": true,
"os": [
@@ -1267,16 +2529,24 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "BSD-3-Clause",
"optional": true,
"os": [
"linux"
]
},
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
"node_modules/semver": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
+ "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -1289,6 +2559,7 @@
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1329,10 +2600,21 @@
"@img/sharp-win32-x64": "0.34.5"
}
},
+ "node_modules/source-map-js": {
+ "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"
+ }
+ },
"node_modules/supports-color": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -1341,10 +2623,28 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
"license": "0BSD",
"optional": true
},
@@ -1352,6 +2652,7 @@
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.24.8.tgz",
"integrity": "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=20.18.1"
@@ -1361,19 +2662,609 @@
"version": "2.0.0-rc.24",
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
"integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==",
+ "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"pathe": "^2.0.3"
}
},
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
"node_modules/workerd": {
"version": "1.20260526.1",
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260526.1.tgz",
"integrity": "sha512-IHzymht98p10JH1zzwdCpbViAqw97HrwKl7+KfZeASFMsYSrIsAULWdPn0LRC5FTUzBpamLNyKCCKxbgXHgRHQ==",
+ "dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"workerd": "bin/workerd"
},
@@ -1392,6 +3283,7 @@
"version": "4.95.0",
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.95.0.tgz",
"integrity": "sha512-vgXzFVSCdUbeCadgVXvu8fK5tzNm8T9W+7lriyGWZMx0B1+CAdr4d8JTlZszHfgjypRAHmAxb49etZGIRD9pgg==",
+ "dev": true,
"license": "MIT OR Apache-2.0",
"dependencies": {
"@cloudflare/kv-asset-handler": "0.5.0",
@@ -1427,6 +3319,7 @@
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -1444,10 +3337,18 @@
}
}
},
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/youch": {
"version": "4.1.0-beta.10",
"resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz",
"integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@poppinss/colors": "^4.1.5",
@@ -1461,6 +3362,7 @@
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz",
"integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@poppinss/exception": "^1.2.2",
diff --git a/174bg/manager/package.json b/174bg/manager/package.json
index 076395b..0e4769e 100644
--- a/174bg/manager/package.json
+++ b/174bg/manager/package.json
@@ -1,9 +1,19 @@
{
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "deploy": "npm run build && npx wrangler deploy --minify"
+ },
"dependencies": {
- "wrangler": "^4.95.0"
+ "pocketbase": "^0.27.0",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0"
},
- "scripts": {
- "deploy": "npx wrangler deploy --minify",
- "dev": "npx wrangler dev"
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.3.4",
+ "vite": "^6.2.0",
+ "wrangler": "^4.95.0"
}
}
diff --git a/174bg/manager/public/index.html b/174bg/manager/public/index.html
deleted file mode 100644
index 002417a..0000000
--- a/174bg/manager/public/index.html
+++ /dev/null
@@ -1,1163 +0,0 @@
-<!doctype html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <link rel="preconnect" href="https://fonts.googleapis.com" />
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
- <link
- href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Rajdhani:wght@400;500;600;700&display=swap"
- rel="stylesheet"
- />
- <link rel="stylesheet" href="theme.css" />
- </head>
- <body>
- <h1>174th Battle Group: Manager</h1>
-
- <section id="anonymous">
- <p>You are not logged in. Please log in to access this site:</p>
- <button id="login">Login with Discord</button>
- </section>
-
- <p id="oauth-status" style="font-size: 0.9rem"></p>
-
- <section
- id="authed"
- style="display: none; flex-direction: column; gap: 1em"
- >
- <div id="welcome">
- <img id="welcome-avatar" src="" alt="" />
- <span id="welcome-text"></span>
- </div>
- <div id="required-info" style="display: flex; flex-direction: column">
- <h2 style="margin-bottom: 0">Required Information</h2>
- <div><b>174BG ID:</b> <span id="174bg-id"></span></div>
- <div>
- <b>UEE Citizen Record ID:</b> <span id="uee-citizen-record-id"></span>
- </div>
- <div><b>RSI Display Name:</b> <span id="rsi-display-name"></span></div>
- <div><b>RSI Handle:</b> <span id="rsi-handle"></span></div>
- <div>
- <b>Joined RSI Organisation:</b> <span id="joined-rsi-org"></span>
- </div>
- <div><b>Email:</b> <span id="user-email"></span></div>
- <div><b>Branch:</b> <span id="174bg-branch"></span></div>
- <div><b>Rank Number:</b> <span id="174bg-rank-number"></span></div>
- <div><b>Rank Name:</b> <span id="174bg-rank-name"></span></div>
- <div><b>Staff Roles:</b> <span id="174bg-staff-roles"></span></div>
- <div id="profile-warning" hidden>
- ⚠️ Your profile is incomplete. Message an officer ASAP!
- </div>
- </div>
- <div id="preferences"></div>
- <div id="oncall"></div>
- <div id="ledger"></div>
- <div>
- <button id="logout">Logout</button>
- </div>
- </section>
- </body>
- <script type="module">
- import PocketBase from "https://esm.sh/pocketbase@0.27.0";
-
- const pb = new PocketBase("https://db.174bg.net");
-
- function populateWelcome() {
- const record = pb.authStore.record ?? {};
- const avatarEl = document.getElementById("welcome-avatar");
- const textEl = document.getElementById("welcome-text");
-
- const name = record["RSI_Handle"] || record["name"] || "Pilot";
- textEl.textContent = `Welcome back, ${name}!`;
-
- // PocketBase avatar URL pattern
- const avatarFile = record["avatar"];
- if (avatarFile) {
- avatarEl.src = `https://db.174bg.net/api/files/${record.collectionId}/${record.id}/${avatarFile}`;
- avatarEl.alt = name;
- avatarEl.hidden = false;
- } else {
- avatarEl.removeAttribute("src");
- avatarEl.alt = "";
- avatarEl.hidden = true;
- }
- }
-
- // ---------------------------------------------------------------------------
- // Required-info field definitions — map span IDs to PocketBase field names
- // ---------------------------------------------------------------------------
- const REQUIRED_INFO_FIELDS = [
- { id: "174bg-id", field: "id" },
- { id: "uee-citizen-record-id", field: "UEE_Citizen_Record_ID" },
- { id: "rsi-display-name", field: "RSI_Display_Name" },
- { id: "rsi-handle", field: "RSI_Handle" },
- { id: "joined-rsi-org", field: "joined_rsi_org" },
- { id: "user-email", field: "email" },
- { id: "174bg-branch", field: "Branch" },
- { id: "174bg-rank-number", field: "Rank_Number" },
- ];
-
- const RANK_NAMES = {
- Naval: [
- "Cadet",
- "Ensign",
- "Lieutenant",
- "Captain",
- "Commodore",
- "Admiral",
- ],
- Marine: [
- "Private",
- "Corporal",
- "Sergeant",
- "Major",
- "Commander",
- "General",
- ],
- Auxiliary: [
- "Trainee",
- "Technician",
- "Specialist",
- "Supervisor",
- "Chief",
- "Marshal",
- ],
- };
-
- function populateRequiredInfo() {
- const record = pb.authStore.record ?? {};
- let anyMissing = false;
-
- for (const { id, field } of REQUIRED_INFO_FIELDS) {
- const span = document.getElementById(id);
- const value = record[field];
- // Treat only null/undefined/empty-string as missing so legitimately
- // falsy values (e.g. Rank_Number 0, joined_rsi_org false) still show.
- if (value != null && value !== "") {
- span.textContent = value;
- span.style.color = "";
- } else {
- span.textContent = "MISSING";
- span.style.color = "red";
- anyMissing = true;
- }
- }
-
- // Derive rank name from branch + rank number
- const rankSpan = document.getElementById("174bg-rank-name");
- const branch = record["Branch"];
- const rankNumber = record["Rank_Number"];
- const rankName = RANK_NAMES[branch]?.[rankNumber];
- if (rankName) {
- rankSpan.textContent = rankName;
- rankSpan.style.color = "";
- } else {
- rankSpan.textContent = "MISSING";
- rankSpan.style.color = "red";
- anyMissing = true;
- }
-
- // Staff roles — optional list; show "none" when empty (not MISSING)
- const staffSpan = document.getElementById("174bg-staff-roles");
- const staffRoles = record["StaffRoles"];
- const staffList = Array.isArray(staffRoles)
- ? staffRoles
- : staffRoles
- ? [staffRoles]
- : [];
- if (staffList.length > 0) {
- staffSpan.textContent = staffList.join(", ");
- staffSpan.style.color = "";
- } else {
- staffSpan.textContent = "none";
- staffSpan.style.color = "";
- }
-
- if (anyMissing) {
- document.getElementById("profile-warning").hidden = false;
- } else {
- document.getElementById("profile-warning").hidden = true;
- }
- }
-
- let ROLES = [];
-
- function fetchRoles() {
- ROLES = [
- {
- branch: "Naval",
- value: "Ship Captain",
- text: "Ship Captain",
- link: "https://handbook.174bg.net/#ship-captain",
- },
- {
- branch: "Naval",
- value: "Pilot",
- text: "Pilot",
- link: "https://handbook.174bg.net/#pilot",
- },
- {
- branch: "Naval",
- value: "Helmsman",
- text: "Helmsman",
- link: "https://handbook.174bg.net/#helmsman",
- },
- {
- branch: "Naval",
- value: "Gunner",
- text: "Gunner",
- link: "https://handbook.174bg.net/#gunner",
- },
- {
- branch: "Marine",
- value: "Tactical Marine",
- text: "Tactical Marine",
- link: "https://handbook.174bg.net/#tactical-marine",
- },
- {
- branch: "Marine",
- value: "Heavy Marine",
- text: "Heavy Marine",
- link: "https://handbook.174bg.net/#heavy-marine",
- },
- {
- branch: "Marine",
- value: "Scout Marine",
- text: "Scout Marine",
- link: "https://handbook.174bg.net/#scout-marine",
- },
- {
- branch: "Marine",
- value: "Combat Medic",
- text: "Combat Medic",
- link: "https://handbook.174bg.net/#combat-medic",
- },
- {
- branch: "Marine",
- value: "Combat Engineer",
- text: "Combat Engineer",
- link: "https://handbook.174bg.net/#combat-engineer",
- },
- {
- branch: "Marine",
- value: "Warden",
- text: "Warden",
- link: "https://handbook.174bg.net/#warden",
- },
- {
- branch: "Auxiliary",
- value: "Engineer",
- text: "Engineer",
- link: "https://handbook.174bg.net/#engineer",
- },
- {
- branch: "Auxiliary",
- value: "Hazardous Materials Specialist",
- text: "Hazardous Materials Specialist",
- link: "https://handbook.174bg.net/#hazardous-materials-specialist",
- },
- {
- branch: "Auxiliary",
- value: "Medical Doctor",
- text: "Medical Doctor",
- link: "https://handbook.174bg.net/#medical-doctor",
- },
- {
- branch: "Auxiliary",
- value: "Quartermaster",
- text: "Quartermaster",
- link: "https://handbook.174bg.net/#quartermaster",
- },
- {
- branch: "Auxiliary",
- value: "Cargo Technician",
- text: "Cargo Technician",
- link: "https://handbook.174bg.net/#cargo-technician",
- },
- {
- branch: "Auxiliary",
- value: "Craftsman",
- text: "Craftsman",
- link: "https://handbook.174bg.net/#craftsman",
- },
- {
- branch: "Auxiliary",
- value: "Mining Technician",
- text: "Mining Technician",
- link: "https://handbook.174bg.net/#mining-technician",
- },
- {
- branch: "Auxiliary",
- value: "Salvage Operator",
- text: "Salvage Operator",
- link: "https://handbook.174bg.net/#salvage-operator",
- },
- {
- branch: "Auxiliary",
- value: "Expeditionary Analyst",
- text: "Expeditionary Analyst",
- link: "https://handbook.174bg.net/#expeditionary-analyst",
- },
- {
- branch: "Auxiliary",
- value: "Intelligence Agent",
- text: "Intelligence Agent",
- link: "https://handbook.174bg.net/#intelligence-agent",
- },
- ];
- }
-
- let rolesEditing = false;
- let rolesTbody;
- let rolesEditBtn;
- let rolesCancelBtn;
- let rolesStatus;
-
- // OtherJsonData may be stored as an object or a JSON string
- function getOtherJsonData() {
- const raw = pb.authStore.record?.OtherJsonData;
- if (!raw) return {};
- if (typeof raw === "string") {
- try {
- return JSON.parse(raw) || {};
- } catch {
- return {};
- }
- }
- return { ...raw };
- }
-
- function getFavouriteRole() {
- return getOtherJsonData().FavouriteRole ?? null;
- }
-
- function renderRoles(saved, favourite, editMode) {
- rolesTbody.querySelectorAll("tr:not([data-separator])").forEach((tr) => {
- const tds = tr.querySelectorAll("td");
- const prefTd = tds[1];
- const favTd = tds[2];
- const value = tr.dataset.roleValue;
- if (editMode) {
- const cb = document.createElement("input");
- cb.type = "checkbox";
- cb.checked = saved.includes(value);
- prefTd.replaceChildren(cb);
-
- const radio = document.createElement("input");
- radio.type = "radio";
- radio.name = "favourite-role";
- radio.value = value;
- radio.checked = favourite === value;
- radio.dataset.wasChecked = radio.checked ? "true" : "false";
- // Clicking an already-selected radio clears the favourite
- radio.addEventListener("click", () => {
- if (radio.dataset.wasChecked === "true") {
- radio.checked = false;
- radio.dataset.wasChecked = "false";
- } else {
- rolesTbody
- .querySelectorAll("input[name=favourite-role]")
- .forEach((r) => (r.dataset.wasChecked = "false"));
- radio.dataset.wasChecked = "true";
- }
- });
- favTd.replaceChildren(radio);
- } else {
- prefTd.textContent = saved.includes(value) ? "✅" : "❌";
- favTd.textContent = favourite === value ? "⭐" : "";
- }
- });
- }
-
- function buildRolesSection() {
- const container = document.getElementById("preferences");
- container.innerHTML = "";
-
- const heading = document.createElement("div");
- const title = document.createElement("b");
- title.textContent = "Role Preferences";
- rolesEditBtn = document.createElement("button");
- rolesEditBtn.type = "button";
- rolesEditBtn.textContent = "✏️ Edit";
- rolesCancelBtn = document.createElement("button");
- rolesCancelBtn.type = "button";
- rolesCancelBtn.textContent = "❌ Cancel";
- rolesCancelBtn.hidden = true;
- heading.append(title, " ", rolesEditBtn, " ", rolesCancelBtn);
-
- const table = document.createElement("table");
- const thead = document.createElement("thead");
- const headerRow = document.createElement("tr");
- for (const text of ["Role", "✅", "⭐"]) {
- const th = document.createElement("th");
- th.textContent = text;
- headerRow.appendChild(th);
- }
- thead.appendChild(headerRow);
-
- rolesTbody = document.createElement("tbody");
- let lastBranch = null;
- for (const role of ROLES) {
- if (role.branch !== lastBranch) {
- lastBranch = role.branch;
- const sep = document.createElement("tr");
- sep.dataset.separator = "true";
- const tdSep = document.createElement("td");
- tdSep.colSpan = 3;
- tdSep.textContent = role.branch ?? "";
- tdSep.style.cssText =
- "font-weight:600; padding-top:0.75rem; padding-bottom:0.25rem; border-bottom:none; opacity:0.6; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.05em";
- sep.appendChild(tdSep);
- rolesTbody.appendChild(sep);
- }
- const tr = document.createElement("tr");
- tr.dataset.roleValue = role.value;
- const tdName = document.createElement("td");
- if (role.link) {
- const a = document.createElement("a");
- a.href = role.link;
- a.textContent = role.text;
- a.target = "_blank";
- a.rel = "noopener noreferrer";
- tdName.appendChild(a);
- } else {
- tdName.textContent = role.text;
- }
- const tdCheck = document.createElement("td");
- const tdFav = document.createElement("td");
- tr.append(tdName, tdCheck, tdFav);
- rolesTbody.appendChild(tr);
- }
-
- table.append(thead, rolesTbody);
- rolesStatus = document.createElement("p");
- rolesStatus.style.cssText = "margin:0; font-size:0.85rem";
- container.append(heading, table, rolesStatus);
- }
-
- function loadRoles() {
- rolesEditing = false;
- rolesEditBtn.textContent = "✏️ Edit";
- rolesCancelBtn.hidden = true;
- renderRoles(
- pb.authStore.record?.RolePreferencesSelect ?? [],
- getFavouriteRole(),
- false,
- );
- }
-
- let oncallEditing = false;
- let oncallTbody;
- let oncallTzContainer;
- let oncallEditBtn;
- let oncallCancelBtn;
- let oncallStatus;
-
- const DAYS_OF_WEEK = [
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday",
- "Sunday",
- ];
-
- // Sunday-first, matching Date#getDay()/getUTCDay() and Intl's "weekday"
- const WEEKDAY_NAMES = [
- "Sunday",
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday",
- ];
- const WEEKDAY_INDEX = Object.fromEntries(
- WEEKDAY_NAMES.map((d, i) => [d, i]),
- );
-
- function pad2(n) {
- return String(n).padStart(2, "0");
- }
-
- function getBrowserTimeZone() {
- try {
- return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
- } catch {
- return "UTC";
- }
- }
-
- function listTimeZones() {
- if (typeof Intl.supportedValuesOf === "function") {
- try {
- return Intl.supportedValuesOf("timeZone");
- } catch {
- // fall through to the fallback list below
- }
- }
- return [
- "UTC",
- "America/Los_Angeles",
- "America/Denver",
- "America/Chicago",
- "America/New_York",
- "America/Sao_Paulo",
- "Europe/London",
- "Europe/Paris",
- "Europe/Berlin",
- "Europe/Moscow",
- "Africa/Johannesburg",
- "Asia/Dubai",
- "Asia/Kolkata",
- "Asia/Shanghai",
- "Asia/Tokyo",
- "Australia/Sydney",
- "Pacific/Auckland",
- ];
- }
-
- // Offset (ms) of `timeZone` from UTC at the instant `date` represents.
- function tzOffsetMs(date, timeZone) {
- const dtf = new Intl.DateTimeFormat("en-US", {
- timeZone,
- hourCycle: "h23",
- year: "numeric",
- month: "2-digit",
- day: "2-digit",
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- });
- const parts = {};
- for (const { type, value } of dtf.formatToParts(date)) {
- parts[type] = value;
- }
- const asUTC = Date.UTC(
- Number(parts.year),
- Number(parts.month) - 1,
- Number(parts.day),
- parts.hour === "24" ? 0 : Number(parts.hour),
- Number(parts.minute),
- Number(parts.second),
- );
- return asUTC - date.getTime();
- }
-
- // Formats `timeZone`'s UTC offset at `date` as "UTC+HH:MM"/"UTC-HH:MM".
- function formatUtcOffset(timeZone, date = new Date()) {
- const totalMinutes = Math.round(tzOffsetMs(date, timeZone) / 60000);
- const sign = totalMinutes < 0 ? "-" : "+";
- const abs = Math.abs(totalMinutes);
- return `UTC${sign}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`;
- }
-
- // Nearest same-or-later calendar date whose local weekday in `timeZone`
- // is `day` - used as a DST-correct anchor for the conversion below.
- function nextLocalDateFor(day, timeZone) {
- const dtf = new Intl.DateTimeFormat("en-US", {
- timeZone,
- year: "numeric",
- month: "2-digit",
- day: "2-digit",
- weekday: "long",
- });
- const parts = {};
- for (const { type, value } of dtf.formatToParts(new Date())) {
- parts[type] = value;
- }
- const diff = (WEEKDAY_INDEX[day] - WEEKDAY_INDEX[parts.weekday] + 7) % 7;
- const base = new Date(
- Date.UTC(
- Number(parts.year),
- Number(parts.month) - 1,
- Number(parts.day),
- ),
- );
- base.setUTCDate(base.getUTCDate() + diff);
- return base;
- }
-
- // Nearest same-or-later UTC calendar date whose UTC weekday is `day`.
- function nextUtcDateFor(day) {
- const now = new Date();
- const diff = (WEEKDAY_INDEX[day] - now.getUTCDay() + 7) % 7;
- const base = new Date(
- Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()),
- );
- base.setUTCDate(base.getUTCDate() + diff);
- return base;
- }
-
- // Converts a weekday + "HH:MM" wall-clock time in `timeZone` to the
- // equivalent UTC weekday + "HH:MM" (the day may shift due to the offset).
- function localDayTimeToUtc(day, time, timeZone) {
- const [hour, minute] = time.split(":").map(Number);
- const base = nextLocalDateFor(day, timeZone);
- const guess = Date.UTC(
- base.getUTCFullYear(),
- base.getUTCMonth(),
- base.getUTCDate(),
- hour,
- minute,
- );
- const offset = tzOffsetMs(new Date(guess), timeZone);
- const actual = new Date(guess - offset);
- return {
- day: WEEKDAY_NAMES[actual.getUTCDay()],
- time: `${pad2(actual.getUTCHours())}:${pad2(actual.getUTCMinutes())}`,
- };
- }
-
- // Converts a UTC weekday + "HH:MM" back to weekday + "HH:MM" wall-clock
- // time in `timeZone`.
- function utcDayTimeToLocal(day, time, timeZone) {
- const [hour, minute] = time.split(":").map(Number);
- const base = nextUtcDateFor(day);
- const instant = new Date(
- Date.UTC(
- base.getUTCFullYear(),
- base.getUTCMonth(),
- base.getUTCDate(),
- hour,
- minute,
- ),
- );
- const dtf = new Intl.DateTimeFormat("en-US", {
- timeZone,
- hourCycle: "h23",
- weekday: "long",
- hour: "2-digit",
- minute: "2-digit",
- });
- const parts = {};
- for (const { type, value } of dtf.formatToParts(instant)) {
- parts[type] = value;
- }
- return {
- day: parts.weekday,
- time: `${parts.hour === "24" ? "00" : parts.hour}:${parts.minute}`,
- };
- }
-
- // onCallSchedule is stored as JSON, UTC-normalized:
- // { timezone: "IANA/Zone", [utcDay]: { available, start, end } }
- function getOnCallRaw() {
- const raw = pb.authStore.record?.onCallSchedule;
- if (!raw) return {};
- if (typeof raw === "string") {
- try {
- return JSON.parse(raw) || {};
- } catch {
- return {};
- }
- }
- return { ...raw };
- }
-
- function getEffectiveTimezone(raw) {
- return raw.timezone || getBrowserTimeZone();
- }
-
- // Converts the stored UTC schedule to local wall-clock times, keyed by
- // local weekday, for display/editing.
- function utcScheduleToLocal(raw, timeZone) {
- const local = {};
- for (const day of DAYS_OF_WEEK) {
- const entry = raw[day];
- if (!entry?.available) continue;
- const start = utcDayTimeToLocal(day, entry.start, timeZone);
- const end = utcDayTimeToLocal(day, entry.end, timeZone);
- local[start.day] = {
- available: true,
- start: start.time,
- end: end.time,
- };
- }
- return local;
- }
-
- // Converts a local-keyed schedule (from the edit form) to UTC for
- // storage.
- function localScheduleToUtc(local, timeZone) {
- const utc = {};
- for (const day of DAYS_OF_WEEK) {
- const entry = local[day];
- if (!entry?.available) continue;
- const start = localDayTimeToUtc(day, entry.start || "00:00", timeZone);
- const end = localDayTimeToUtc(day, entry.end || "00:00", timeZone);
- utc[start.day] = { available: true, start: start.time, end: end.time };
- }
- return utc;
- }
-
- // Legacy records saved before timezone support has no `timezone` key -
- // those values are already local wall-clock times, so skip conversion.
- function getLocalOnCallSchedule() {
- const raw = getOnCallRaw();
- const timeZone = getEffectiveTimezone(raw);
- const schedule = raw.timezone ? utcScheduleToLocal(raw, timeZone) : raw;
- return { timeZone, schedule };
- }
-
- function renderOnCallTimezone(timeZone, editMode) {
- if (editMode) {
- const zones = listTimeZones();
- const options = zones.includes(timeZone) ? zones : [timeZone, ...zones];
- const now = new Date();
- const select = document.createElement("select");
- for (const tz of options) {
- const option = document.createElement("option");
- option.value = tz;
- option.textContent = `${tz} (${formatUtcOffset(tz, now)})`;
- option.selected = tz === timeZone;
- select.appendChild(option);
- }
- oncallTzContainer.replaceChildren(select);
- } else {
- oncallTzContainer.textContent = `${timeZone} (${formatUtcOffset(timeZone)})`;
- }
- }
-
- function getSelectedTimezone() {
- const select = oncallTzContainer.querySelector("select");
- return select ? select.value : getEffectiveTimezone(getOnCallRaw());
- }
-
- function renderOnCallSchedule(schedule, editMode) {
- oncallTbody.querySelectorAll("tr").forEach((tr) => {
- const day = tr.dataset.day;
- const entry = schedule[day] ?? {};
- const tds = tr.querySelectorAll("td");
- const availTd = tds[1];
- const startTd = tds[2];
- const endTd = tds[3];
-
- if (editMode) {
- const cb = document.createElement("input");
- cb.type = "checkbox";
- cb.checked = !!entry.available;
- availTd.replaceChildren(cb);
-
- const startInput = document.createElement("input");
- startInput.type = "time";
- startInput.value = entry.start ?? "";
- startInput.disabled = !cb.checked;
-
- const endInput = document.createElement("input");
- endInput.type = "time";
- endInput.value = entry.end ?? "";
- endInput.disabled = !cb.checked;
-
- cb.addEventListener("change", () => {
- startInput.disabled = !cb.checked;
- endInput.disabled = !cb.checked;
- });
-
- startTd.replaceChildren(startInput);
- endTd.replaceChildren(endInput);
- } else {
- availTd.textContent = entry.available ? "✅" : "❌";
- startTd.textContent =
- entry.available && entry.start ? entry.start : "—";
- endTd.textContent = entry.available && entry.end ? entry.end : "—";
- }
- });
- }
-
- function buildOnCallSection() {
- const container = document.getElementById("oncall");
- container.innerHTML = "";
-
- const heading = document.createElement("div");
- const title = document.createElement("b");
- title.textContent = "On-Call Schedule";
- oncallEditBtn = document.createElement("button");
- oncallEditBtn.type = "button";
- oncallEditBtn.textContent = "✏️ Edit";
- oncallCancelBtn = document.createElement("button");
- oncallCancelBtn.type = "button";
- oncallCancelBtn.textContent = "❌ Cancel";
- oncallCancelBtn.hidden = true;
- heading.append(title, " ", oncallEditBtn, " ", oncallCancelBtn);
-
- const hint = document.createElement("p");
- hint.style.cssText =
- "margin:0.25rem 0 0.75rem; font-size:0.85rem; color:var(--text-dim)";
- hint.textContent =
- "Let the group know which days and hours you're usually free to join operations.";
-
- const tzRow = document.createElement("div");
- tzRow.style.cssText =
- "display:flex; align-items:center; gap:0.5rem; margin-bottom:0.75rem";
- const tzLabel = document.createElement("b");
- tzLabel.textContent = "Timezone:";
- oncallTzContainer = document.createElement("span");
- tzRow.append(tzLabel, oncallTzContainer);
-
- const table = document.createElement("table");
- const thead = document.createElement("thead");
- const headerRow = document.createElement("tr");
- for (const text of ["Day", "Free", "From", "To"]) {
- const th = document.createElement("th");
- th.textContent = text;
- headerRow.appendChild(th);
- }
- thead.appendChild(headerRow);
-
- oncallTbody = document.createElement("tbody");
- for (const day of DAYS_OF_WEEK) {
- const tr = document.createElement("tr");
- tr.dataset.day = day;
- const tdDay = document.createElement("td");
- tdDay.textContent = day;
- const tdAvail = document.createElement("td");
- const tdStart = document.createElement("td");
- const tdEnd = document.createElement("td");
- tr.append(tdDay, tdAvail, tdStart, tdEnd);
- oncallTbody.appendChild(tr);
- }
-
- table.append(thead, oncallTbody);
- oncallStatus = document.createElement("p");
- oncallStatus.style.cssText = "margin:0.5rem 0 0; font-size:0.85rem";
- container.append(heading, hint, tzRow, table, oncallStatus);
- }
-
- function loadOnCallSchedule() {
- oncallEditing = false;
- oncallEditBtn.textContent = "✏️ Edit";
- oncallCancelBtn.hidden = true;
- const { timeZone, schedule } = getLocalOnCallSchedule();
- renderOnCallTimezone(timeZone, false);
- renderOnCallSchedule(schedule, false);
- }
-
- function memberLabel(record, id) {
- const m = record?.expand?.[id];
- return m ? m.RSI_Handle || m.name || m.id : (record?.[id] ?? "—");
- }
-
- async function loadLedger() {
- const container = document.getElementById("ledger");
- container.innerHTML = "";
-
- const heading = document.createElement("div");
- const title = document.createElement("b");
- title.textContent = "Ledger";
- heading.appendChild(title);
- container.appendChild(heading);
-
- const userId = pb.authStore.record?.id;
- if (!userId) return;
-
- let records;
- try {
- records = await pb.collection("ledger").getFullList({
- expand: "sender,recipient",
- sort: "-created",
- requestKey: null,
- });
- } catch (err) {
- console.error("Failed to load ledger:", err);
- const p = document.createElement("p");
- p.className = "empty";
- p.style.color = "red";
- p.textContent = `Failed to load ledger: ${err?.message ?? String(err)}`;
- container.appendChild(p);
- return;
- }
-
- if (records.length === 0) {
- const p = document.createElement("p");
- p.className = "empty";
- p.textContent = "No ledger entries.";
- container.appendChild(p);
- return;
- }
-
- const table = document.createElement("table");
- const thead = document.createElement("thead");
- const headerRow = document.createElement("tr");
- for (const text of ["Date", "Sender", "Recipient", "UEC", "Note"]) {
- const th = document.createElement("th");
- th.textContent = text;
- headerRow.appendChild(th);
- }
- thead.appendChild(headerRow);
-
- const tbody = document.createElement("tbody");
- for (const rec of records) {
- const tr = document.createElement("tr");
-
- const tdDate = document.createElement("td");
- tdDate.dataset.label = "Date";
- tdDate.textContent = rec.created
- ? new Date(rec.created).toLocaleString()
- : "—";
-
- const tdSender = document.createElement("td");
- tdSender.dataset.label = "Sender";
- tdSender.textContent = memberLabel(rec, "sender");
-
- const tdRecipient = document.createElement("td");
- tdRecipient.dataset.label = "Recipient";
- tdRecipient.textContent = memberLabel(rec, "recipient");
-
- const tdUec = document.createElement("td");
- tdUec.className = "uec";
- tdUec.dataset.label = "UEC";
- tdUec.textContent = Number(rec.uec ?? 0).toLocaleString();
-
- const tdNote = document.createElement("td");
- tdNote.dataset.label = "Note";
- tdNote.textContent = rec.note ?? "";
-
- tr.append(tdDate, tdSender, tdRecipient, tdUec, tdNote);
- tbody.appendChild(tr);
- }
-
- table.append(thead, tbody);
- container.appendChild(table);
- }
-
- const loginBtn = document.getElementById("login");
- const logoutBtn = document.getElementById("logout");
- const anonymousSection = document.getElementById("anonymous");
- const authedSection = document.getElementById("authed");
-
- function initAuthedUI() {
- fetchRoles();
- buildRolesSection();
- buildOnCallSection();
- populateWelcome();
- populateRequiredInfo();
- loadRoles();
- loadOnCallSchedule();
- loadLedger();
- }
-
- function updateAuthUI() {
- const loggedIn = pb.authStore.isValid;
- anonymousSection.style.display = loggedIn ? "none" : "";
- authedSection.style.display = loggedIn ? "flex" : "none";
- }
-
- // Verify the stored session is still valid server-side
- if (pb.authStore.isValid) {
- try {
- await pb.collection("members").authRefresh();
- } catch {
- pb.authStore.clear();
- }
- }
-
- // Complete OAuth redirect if returning from Discord
- const oauthParams = new URLSearchParams(window.location.search);
- const storedProvider = localStorage.getItem("pb_oauth_provider");
- const oauthStatus = document.getElementById("oauth-status");
-
- if (oauthParams.has("code") && oauthParams.has("state")) {
- if (!storedProvider) {
- oauthStatus.textContent =
- "Login error: OAuth state lost (localStorage empty). Please try again.";
- oauthStatus.style.color = "red";
- window.history.replaceState({}, "", window.location.pathname);
- } else {
- oauthStatus.textContent = "Completing login...";
- const provider = JSON.parse(storedProvider);
- localStorage.removeItem("pb_oauth_provider");
- const redirectUrl = window.location.origin + window.location.pathname;
- try {
- await pb
- .collection("members")
- .authWithOAuth2Code(
- provider.name,
- oauthParams.get("code"),
- provider.codeVerifier,
- redirectUrl,
- );
- oauthStatus.style.cssText =
- "color:green; background:#dfd; border:1px solid #080; border-radius:4px; padding:0.5rem";
- oauthStatus.textContent = "✅ Login successful!";
- window.history.replaceState({}, "", window.location.pathname);
- updateAuthUI();
- initAuthedUI();
- } catch (err) {
- console.error("OAuth callback failed:", err);
- const detail = [
- err?.message,
- err?.response ? JSON.stringify(err.response) : null,
- `code=${oauthParams.get("code")?.slice(0, 8)}…`,
- `state=${oauthParams.get("state")?.slice(0, 8)}…`,
- `redirect=${redirectUrl}`,
- `provider=${provider.name}`,
- ]
- .filter(Boolean)
- .join(" | ");
- oauthStatus.style.cssText =
- "color:red; background:#fdd; border:1px solid #c00; border-radius:4px; padding:0.5rem; font-family:monospace; font-size:0.8rem; white-space:pre-wrap; word-break:break-all";
- oauthStatus.textContent = `Login error — send this to your admin:\n${detail}`;
- window.history.replaceState({}, "", window.location.pathname);
- }
- }
- }
-
- if (!oauthParams.has("code") || !oauthParams.has("state")) {
- updateAuthUI();
- if (pb.authStore.isValid) {
- initAuthedUI();
- }
- }
-
- loginBtn.addEventListener("click", async () => {
- loginBtn.disabled = true;
- try {
- const methods = await pb.collection("members").listAuthMethods();
- const provider = methods.oauth2.providers.find(
- (p) => p.name === "discord",
- );
- if (!provider) throw new Error("Discord provider not found");
- const redirectUrl = window.location.origin + window.location.pathname;
- localStorage.setItem("pb_oauth_provider", JSON.stringify(provider));
- window.location.href =
- provider.authUrl + encodeURIComponent(redirectUrl);
- } catch (err) {
- console.error("Login failed:", err);
- loginBtn.disabled = false;
- }
- });
-
- logoutBtn.addEventListener("click", () => {
- pb.authStore.clear();
- updateAuthUI();
- });
-
- rolesEditBtn.addEventListener("click", async () => {
- if (!rolesEditing) {
- rolesEditing = true;
- rolesEditBtn.textContent = "💾 Save";
- rolesCancelBtn.hidden = false;
- renderRoles(
- pb.authStore.record?.RolePreferencesSelect ?? [],
- getFavouriteRole(),
- true,
- );
- } else {
- const selected = [
- ...rolesTbody.querySelectorAll("input[type=checkbox]:checked"),
- ].map((cb) => cb.closest("tr").dataset.roleValue);
- const favRadio = rolesTbody.querySelector(
- "input[name=favourite-role]:checked",
- );
- // Favourite must be one of the selected preferences
- let favourite = favRadio ? favRadio.value : null;
- if (favourite && !selected.includes(favourite)) favourite = null;
- rolesEditBtn.disabled = true;
- rolesCancelBtn.disabled = true;
- try {
- const prefs = selected;
- const other = getOtherJsonData();
- if (favourite) other.FavouriteRole = favourite;
- else delete other.FavouriteRole;
- await pb.collection("members").update(pb.authStore.record.id, {
- RolePreferencesSelect: prefs,
- OtherJsonData: other,
- });
- pb.authStore.record.RolePreferencesSelect = prefs;
- pb.authStore.record.OtherJsonData = other;
- rolesEditing = false;
- rolesEditBtn.textContent = "✏️ Edit";
- rolesCancelBtn.hidden = true;
- rolesStatus.textContent = "";
- renderRoles(selected, favourite, false);
- } catch (err) {
- console.error("Failed to save role preferences:", err);
- const msg = err?.response
- ? JSON.stringify(err.response)
- : (err?.message ?? String(err));
- rolesStatus.style.color = "red";
- rolesStatus.textContent = `Save failed: ${msg}`;
- } finally {
- rolesEditBtn.disabled = false;
- rolesCancelBtn.disabled = false;
- }
- }
- });
-
- rolesCancelBtn.addEventListener("click", () => {
- rolesEditing = false;
- rolesEditBtn.textContent = "✏️ Edit";
- rolesCancelBtn.hidden = true;
- renderRoles(
- pb.authStore.record?.RolePreferencesSelect ?? [],
- getFavouriteRole(),
- false,
- );
- });
-
- oncallEditBtn.addEventListener("click", async () => {
- if (!oncallEditing) {
- oncallEditing = true;
- oncallEditBtn.textContent = "💾 Save";
- oncallCancelBtn.hidden = false;
- const { timeZone, schedule } = getLocalOnCallSchedule();
- renderOnCallTimezone(timeZone, true);
- renderOnCallSchedule(schedule, true);
- } else {
- const timeZone = getSelectedTimezone();
- const local = {};
- oncallTbody.querySelectorAll("tr").forEach((tr) => {
- const day = tr.dataset.day;
- const tds = tr.querySelectorAll("td");
- const cb = tds[1].querySelector("input[type=checkbox]");
- const startInput = tds[2].querySelector("input[type=time]");
- const endInput = tds[3].querySelector("input[type=time]");
- if (cb.checked) {
- local[day] = {
- available: true,
- start: startInput.value || "00:00",
- end: endInput.value || "00:00",
- };
- }
- });
- oncallEditBtn.disabled = true;
- oncallCancelBtn.disabled = true;
- try {
- const utcSchedule = localScheduleToUtc(local, timeZone);
- const payload = { timezone: timeZone, ...utcSchedule };
- await pb.collection("members").update(pb.authStore.record.id, {
- onCallSchedule: payload,
- });
- pb.authStore.record.onCallSchedule = payload;
- oncallEditing = false;
- oncallEditBtn.textContent = "✏️ Edit";
- oncallCancelBtn.hidden = true;
- oncallStatus.textContent = "";
- renderOnCallTimezone(timeZone, false);
- renderOnCallSchedule(local, false);
- } catch (err) {
- console.error("Failed to save on-call schedule:", err);
- const msg = err?.response
- ? JSON.stringify(err.response)
- : (err?.message ?? String(err));
- oncallStatus.style.color = "red";
- oncallStatus.textContent = `Save failed: ${msg}`;
- } finally {
- oncallEditBtn.disabled = false;
- oncallCancelBtn.disabled = false;
- }
- }
- });
-
- oncallCancelBtn.addEventListener("click", () => {
- oncallEditing = false;
- oncallEditBtn.textContent = "✏️ Edit";
- oncallCancelBtn.hidden = true;
- const { timeZone, schedule } = getLocalOnCallSchedule();
- renderOnCallTimezone(timeZone, false);
- renderOnCallSchedule(schedule, false);
- });
- </script>
-</html>
diff --git a/174bg/manager/src/App.jsx b/174bg/manager/src/App.jsx
new file mode 100644
index 0000000..5f455bb
--- /dev/null
+++ b/174bg/manager/src/App.jsx
@@ -0,0 +1,182 @@
+import { useCallback, useEffect, useState } from "react";
+import { pb } from "./lib/pocketbase";
+import Welcome from "./components/Welcome.jsx";
+import RequiredInfo from "./components/RequiredInfo.jsx";
+import RolePreferences from "./components/RolePreferences.jsx";
+import OnCallSchedule from "./components/OnCallSchedule.jsx";
+import Ledger from "./components/Ledger.jsx";
+
+export default function App() {
+ const [record, setRecord] = useState(pb.authStore.record);
+ const [loggedIn, setLoggedIn] = useState(pb.authStore.isValid);
+ const [loginDisabled, setLoginDisabled] = useState(false);
+ const [oauthStatus, setOauthStatus] = useState({ text: "" });
+
+ const refreshFromStore = useCallback(() => {
+ setRecord(pb.authStore.record);
+ setLoggedIn(pb.authStore.isValid);
+ }, []);
+
+ useEffect(() => pb.authStore.onChange(refreshFromStore), [refreshFromStore]);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ async function init() {
+ if (pb.authStore.isValid) {
+ try {
+ await pb.collection("members").authRefresh();
+ } catch {
+ pb.authStore.clear();
+ }
+ }
+
+ const oauthParams = new URLSearchParams(window.location.search);
+ const storedProvider = localStorage.getItem("pb_oauth_provider");
+
+ if (oauthParams.has("code") && oauthParams.has("state")) {
+ if (!storedProvider) {
+ setOauthStatus({
+ text: "Login error: OAuth state lost (localStorage empty). Please try again.",
+ variant: "error-plain",
+ });
+ window.history.replaceState({}, "", window.location.pathname);
+ } else {
+ setOauthStatus({ text: "Completing login..." });
+ const provider = JSON.parse(storedProvider);
+ localStorage.removeItem("pb_oauth_provider");
+ const redirectUrl = window.location.origin + window.location.pathname;
+ try {
+ await pb
+ .collection("members")
+ .authWithOAuth2Code(
+ provider.name,
+ oauthParams.get("code"),
+ provider.codeVerifier,
+ redirectUrl,
+ );
+ setOauthStatus({ text: "✅ Login successful!", variant: "success" });
+ window.history.replaceState({}, "", window.location.pathname);
+ if (!cancelled) refreshFromStore();
+ } catch (err) {
+ console.error("OAuth callback failed:", err);
+ const detail = [
+ err?.message,
+ err?.response ? JSON.stringify(err.response) : null,
+ `code=${oauthParams.get("code")?.slice(0, 8)}…`,
+ `state=${oauthParams.get("state")?.slice(0, 8)}…`,
+ `redirect=${redirectUrl}`,
+ `provider=${provider.name}`,
+ ]
+ .filter(Boolean)
+ .join(" | ");
+ setOauthStatus({
+ text: `Login error — send this to your admin:\n${detail}`,
+ variant: "error",
+ });
+ window.history.replaceState({}, "", window.location.pathname);
+ }
+ }
+ } else if (!cancelled) {
+ refreshFromStore();
+ }
+ }
+
+ init();
+ return () => {
+ cancelled = true;
+ };
+ }, [refreshFromStore]);
+
+ const handleLogin = useCallback(async () => {
+ setLoginDisabled(true);
+ try {
+ const methods = await pb.collection("members").listAuthMethods();
+ const provider = methods.oauth2.providers.find((p) => p.name === "discord");
+ if (!provider) throw new Error("Discord provider not found");
+ const redirectUrl = window.location.origin + window.location.pathname;
+ localStorage.setItem("pb_oauth_provider", JSON.stringify(provider));
+ window.location.href = provider.authUrl + encodeURIComponent(redirectUrl);
+ } catch (err) {
+ console.error("Login failed:", err);
+ setLoginDisabled(false);
+ }
+ }, []);
+
+ const handleLogout = useCallback(() => {
+ pb.authStore.clear();
+ refreshFromStore();
+ }, [refreshFromStore]);
+
+ const handleRecordUpdate = useCallback(
+ (updated) => {
+ pb.authStore.save(pb.authStore.token, updated);
+ refreshFromStore();
+ },
+ [refreshFromStore],
+ );
+
+ let oauthStatusStyle = { fontSize: "0.9rem" };
+ if (oauthStatus.variant === "success") {
+ oauthStatusStyle = {
+ color: "green",
+ background: "#dfd",
+ border: "1px solid #080",
+ borderRadius: "4px",
+ padding: "0.5rem",
+ };
+ } else if (oauthStatus.variant === "error") {
+ oauthStatusStyle = {
+ color: "red",
+ background: "#fdd",
+ border: "1px solid #c00",
+ borderRadius: "4px",
+ padding: "0.5rem",
+ fontFamily: "monospace",
+ fontSize: "0.8rem",
+ whiteSpace: "pre-wrap",
+ wordBreak: "break-all",
+ };
+ } else if (oauthStatus.variant === "error-plain") {
+ oauthStatusStyle = { fontSize: "0.9rem", color: "red" };
+ }
+
+ return (
+ <>
+ <h1>174th Battle Group: Manager</h1>
+
+ {!loggedIn && (
+ <section id="anonymous">
+ <p>You are not logged in. Please log in to access this site:</p>
+ <button id="login" onClick={handleLogin} disabled={loginDisabled}>
+ Login with Discord
+ </button>
+ </section>
+ )}
+
+ {oauthStatus.text && (
+ <p id="oauth-status" style={oauthStatusStyle}>
+ {oauthStatus.text}
+ </p>
+ )}
+
+ {loggedIn && (
+ <section
+ id="authed"
+ style={{ display: "flex", flexDirection: "column", gap: "1em" }}
+ >
+ <Welcome record={record} />
+ <RequiredInfo record={record} />
+ <RolePreferences record={record} onUpdate={handleRecordUpdate} />
+ <OnCallSchedule record={record} onUpdate={handleRecordUpdate} />
+ <Ledger />
+ <div>
+ <button id="logout" onClick={handleLogout}>
+ Logout
+ </button>
+ </div>
+ </section>
+ )}
+ </>
+ );
+}
diff --git a/174bg/manager/src/components/Ledger.jsx b/174bg/manager/src/components/Ledger.jsx
new file mode 100644
index 0000000..247b343
--- /dev/null
+++ b/174bg/manager/src/components/Ledger.jsx
@@ -0,0 +1,73 @@
+import { useEffect, useState } from "react";
+import { pb } from "../lib/pocketbase";
+
+function memberLabel(record, id) {
+ const m = record?.expand?.[id];
+ return m ? m.RSI_Handle || m.name || m.id : (record?.[id] ?? "—");
+}
+
+export default function Ledger() {
+ const [records, setRecords] = useState(null);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ pb
+ .collection("ledger")
+ .getFullList({ expand: "sender,recipient", sort: "-created", requestKey: null })
+ .then((recs) => {
+ if (!cancelled) setRecords(recs);
+ })
+ .catch((err) => {
+ console.error("Failed to load ledger:", err);
+ if (!cancelled) setError(err);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ return (
+ <div id="ledger">
+ <div>
+ <b>Ledger</b>
+ </div>
+ {error && (
+ <p className="empty" style={{ color: "red" }}>
+ Failed to load ledger: {error?.message ?? String(error)}
+ </p>
+ )}
+ {!error && records && records.length === 0 && (
+ <p className="empty">No ledger entries.</p>
+ )}
+ {!error && records && records.length > 0 && (
+ <table>
+ <thead>
+ <tr>
+ <th>Date</th>
+ <th>Sender</th>
+ <th>Recipient</th>
+ <th>UEC</th>
+ <th>Note</th>
+ </tr>
+ </thead>
+ <tbody>
+ {records.map((rec) => (
+ <tr key={rec.id}>
+ <td data-label="Date">
+ {rec.created ? new Date(rec.created).toLocaleString() : "—"}
+ </td>
+ <td data-label="Sender">{memberLabel(rec, "sender")}</td>
+ <td data-label="Recipient">{memberLabel(rec, "recipient")}</td>
+ <td className="uec" data-label="UEC">
+ {Number(rec.uec ?? 0).toLocaleString()}
+ </td>
+ <td data-label="Note">{rec.note ?? ""}</td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ )}
+ </div>
+ );
+}
diff --git a/174bg/manager/src/components/OnCallSchedule.jsx b/174bg/manager/src/components/OnCallSchedule.jsx
new file mode 100644
index 0000000..725e5c8
--- /dev/null
+++ b/174bg/manager/src/components/OnCallSchedule.jsx
@@ -0,0 +1,186 @@
+import { useMemo, useState } from "react";
+import { pb } from "../lib/pocketbase";
+import {
+ DAYS_OF_WEEK,
+ formatUtcOffset,
+ getLocalOnCallSchedule,
+ listTimeZones,
+ localScheduleToUtc,
+} from "../lib/oncallTime";
+
+export default function OnCallSchedule({ record, onUpdate }) {
+ const [editing, setEditing] = useState(false);
+ const [timeZone, setTimeZone] = useState(null);
+ const [schedule, setSchedule] = useState({});
+ const [saving, setSaving] = useState(false);
+ const [status, setStatus] = useState({ text: "", error: false });
+
+ const display = useMemo(() => getLocalOnCallSchedule(record), [record]);
+ const zones = useMemo(() => listTimeZones(), []);
+
+ function startEdit() {
+ const { timeZone: tz, schedule: sched } = getLocalOnCallSchedule(record);
+ setTimeZone(tz);
+ setSchedule(sched);
+ setEditing(true);
+ setStatus({ text: "", error: false });
+ }
+
+ function cancelEdit() {
+ setEditing(false);
+ }
+
+ function toggleDay(day, available) {
+ setSchedule((prev) => {
+ const next = { ...prev };
+ if (available) {
+ next[day] = {
+ available: true,
+ start: prev[day]?.start ?? "",
+ end: prev[day]?.end ?? "",
+ };
+ } else {
+ delete next[day];
+ }
+ return next;
+ });
+ }
+
+ function setDayTime(day, key, value) {
+ setSchedule((prev) => ({
+ ...prev,
+ [day]: { ...prev[day], [key]: value },
+ }));
+ }
+
+ async function save() {
+ setSaving(true);
+ try {
+ const utcSchedule = localScheduleToUtc(schedule, timeZone);
+ const payload = { timezone: timeZone, ...utcSchedule };
+ const updated = await pb.collection("members").update(record.id, {
+ onCallSchedule: payload,
+ });
+ onUpdate(updated);
+ setEditing(false);
+ setStatus({ text: "", error: false });
+ } catch (err) {
+ const msg = err?.response
+ ? JSON.stringify(err.response)
+ : (err?.message ?? String(err));
+ setStatus({ text: `Save failed: ${msg}`, error: true });
+ } finally {
+ setSaving(false);
+ }
+ }
+
+ const shownTz = editing ? timeZone : display.timeZone;
+ const shownSchedule = editing ? schedule : display.schedule;
+ const tzOptions = zones.includes(shownTz) ? zones : [shownTz, ...zones];
+
+ return (
+ <div id="oncall">
+ <div>
+ <b>On-Call Schedule</b>{" "}
+ {!editing && (
+ <button type="button" onClick={startEdit}>
+ ✏️ Edit
+ </button>
+ )}
+ {editing && (
+ <>
+ <button type="button" disabled={saving} onClick={save}>
+ 💾 Save
+ </button>{" "}
+ <button type="button" disabled={saving} onClick={cancelEdit}>
+ ❌ Cancel
+ </button>
+ </>
+ )}
+ </div>
+ <p style={{ margin: "0.25rem 0 0.75rem", fontSize: "0.85rem", color: "var(--text-dim)" }}>
+ Let the group know which days and hours you're usually free to join operations.
+ </p>
+ <div style={{ display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "0.75rem" }}>
+ <b>Timezone:</b>
+ {editing ? (
+ <select value={timeZone} onChange={(e) => setTimeZone(e.target.value)}>
+ {tzOptions.map((tz) => (
+ <option key={tz} value={tz}>
+ {tz} ({formatUtcOffset(tz)})
+ </option>
+ ))}
+ </select>
+ ) : (
+ <span>
+ {shownTz} ({formatUtcOffset(shownTz)})
+ </span>
+ )}
+ </div>
+ <table>
+ <thead>
+ <tr>
+ <th>Day</th>
+ <th>Free</th>
+ <th>From</th>
+ <th>To</th>
+ </tr>
+ </thead>
+ <tbody>
+ {DAYS_OF_WEEK.map((day) => {
+ const entry = shownSchedule[day] ?? {};
+ return (
+ <tr key={day}>
+ <td>{day}</td>
+ <td>
+ {editing ? (
+ <input
+ type="checkbox"
+ checked={!!entry.available}
+ onChange={(e) => toggleDay(day, e.target.checked)}
+ />
+ ) : entry.available ? (
+ "✅"
+ ) : (
+ "❌"
+ )}
+ </td>
+ <td>
+ {editing ? (
+ <input
+ type="time"
+ value={entry.start ?? ""}
+ disabled={!entry.available}
+ onChange={(e) => setDayTime(day, "start", e.target.value)}
+ />
+ ) : entry.available && entry.start ? (
+ entry.start
+ ) : (
+ "—"
+ )}
+ </td>
+ <td>
+ {editing ? (
+ <input
+ type="time"
+ value={entry.end ?? ""}
+ disabled={!entry.available}
+ onChange={(e) => setDayTime(day, "end", e.target.value)}
+ />
+ ) : entry.available && entry.end ? (
+ entry.end
+ ) : (
+ "—"
+ )}
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ <p style={{ margin: "0.5rem 0 0", fontSize: "0.85rem", color: status.error ? "red" : "" }}>
+ {status.text}
+ </p>
+ </div>
+ );
+}
diff --git a/174bg/manager/src/components/RequiredInfo.jsx b/174bg/manager/src/components/RequiredInfo.jsx
new file mode 100644
index 0000000..67c6799
--- /dev/null
+++ b/174bg/manager/src/components/RequiredInfo.jsx
@@ -0,0 +1,66 @@
+import { RANK_NAMES } from "../lib/roles";
+
+const REQUIRED_INFO_FIELDS = [
+ { field: "id", label: "174BG ID" },
+ { field: "UEE_Citizen_Record_ID", label: "UEE Citizen Record ID" },
+ { field: "RSI_Display_Name", label: "RSI Display Name" },
+ { field: "RSI_Handle", label: "RSI Handle" },
+ { field: "joined_rsi_org", label: "Joined RSI Organisation" },
+ { field: "email", label: "Email" },
+ { field: "Branch", label: "Branch" },
+ { field: "Rank_Number", label: "Rank Number" },
+];
+
+export default function RequiredInfo({ record }) {
+ let anyMissing = false;
+
+ const rows = REQUIRED_INFO_FIELDS.map(({ field, label }) => {
+ const value = record?.[field];
+ // Treat only null/undefined/empty-string as missing so legitimately
+ // falsy values (e.g. Rank_Number 0, joined_rsi_org false) still show.
+ const missing = value == null || value === "";
+ if (missing) anyMissing = true;
+ return { field, label, value, missing };
+ });
+
+ const branch = record?.Branch;
+ const rankNumber = record?.Rank_Number;
+ const rankName = RANK_NAMES[branch]?.[rankNumber];
+ if (!rankName) anyMissing = true;
+
+ const staffRolesRaw = record?.StaffRoles;
+ const staffList = Array.isArray(staffRolesRaw)
+ ? staffRolesRaw
+ : staffRolesRaw
+ ? [staffRolesRaw]
+ : [];
+
+ return (
+ <div id="required-info">
+ <h2>Required Information</h2>
+ {rows.map(({ field, label, value, missing }) => (
+ <div key={field}>
+ <b>{label}:</b>{" "}
+ <span style={{ color: missing ? "red" : "" }}>
+ {missing ? "MISSING" : value}
+ </span>
+ </div>
+ ))}
+ <div>
+ <b>Rank Name:</b>{" "}
+ <span style={{ color: rankName ? "" : "red" }}>
+ {rankName ?? "MISSING"}
+ </span>
+ </div>
+ <div>
+ <b>Staff Roles:</b>{" "}
+ <span>{staffList.length > 0 ? staffList.join(", ") : "none"}</span>
+ </div>
+ {anyMissing && (
+ <div id="profile-warning">
+ ⚠️ Your profile is incomplete. Message an officer ASAP!
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/174bg/manager/src/components/RolePreferences.jsx b/174bg/manager/src/components/RolePreferences.jsx
new file mode 100644
index 0000000..ab3f282
--- /dev/null
+++ b/174bg/manager/src/components/RolePreferences.jsx
@@ -0,0 +1,171 @@
+import { Fragment, useState } from "react";
+import { pb } from "../lib/pocketbase";
+import { ROLES } from "../lib/roles";
+import { getOtherJsonData } from "../lib/otherJsonData";
+
+export default function RolePreferences({ record, onUpdate }) {
+ const [editing, setEditing] = useState(false);
+ const [selected, setSelected] = useState([]);
+ const [favourite, setFavourite] = useState(null);
+ const [saving, setSaving] = useState(false);
+ const [status, setStatus] = useState({ text: "", error: false });
+
+ const savedPrefs = record?.RolePreferencesSelect ?? [];
+ const savedFavourite = getOtherJsonData(record).FavouriteRole ?? null;
+
+ function startEdit() {
+ setSelected(savedPrefs);
+ setFavourite(savedFavourite);
+ setEditing(true);
+ setStatus({ text: "", error: false });
+ }
+
+ function cancelEdit() {
+ setEditing(false);
+ }
+
+ function toggleRole(value) {
+ setSelected((prev) =>
+ prev.includes(value)
+ ? prev.filter((v) => v !== value)
+ : [...prev, value],
+ );
+ }
+
+ function toggleFavourite(value) {
+ setFavourite((prev) => (prev === value ? null : value));
+ }
+
+ async function save() {
+ setSaving(true);
+ try {
+ const fav = favourite && selected.includes(favourite) ? favourite : null;
+ const other = getOtherJsonData(record);
+ if (fav) other.FavouriteRole = fav;
+ else delete other.FavouriteRole;
+
+ const updated = await pb.collection("members").update(record.id, {
+ RolePreferencesSelect: selected,
+ OtherJsonData: other,
+ });
+ onUpdate(updated);
+ setEditing(false);
+ setStatus({ text: "", error: false });
+ } catch (err) {
+ const msg = err?.response
+ ? JSON.stringify(err.response)
+ : (err?.message ?? String(err));
+ setStatus({ text: `Save failed: ${msg}`, error: true });
+ } finally {
+ setSaving(false);
+ }
+ }
+
+ const displayPrefs = editing ? selected : savedPrefs;
+ const displayFavourite = editing ? favourite : savedFavourite;
+
+ let lastBranch = null;
+
+ return (
+ <div id="preferences">
+ <div>
+ <b>Role Preferences</b>{" "}
+ {!editing && (
+ <button type="button" onClick={startEdit}>
+ ✏️ Edit
+ </button>
+ )}
+ {editing && (
+ <>
+ <button type="button" disabled={saving} onClick={save}>
+ 💾 Save
+ </button>{" "}
+ <button type="button" disabled={saving} onClick={cancelEdit}>
+ ❌ Cancel
+ </button>
+ </>
+ )}
+ </div>
+ <table>
+ <thead>
+ <tr>
+ <th>Role</th>
+ <th>✅</th>
+ <th>⭐</th>
+ </tr>
+ </thead>
+ <tbody>
+ {ROLES.map((role) => {
+ const branchHeader = role.branch !== lastBranch;
+ lastBranch = role.branch;
+ return (
+ <Fragment key={role.value}>
+ {branchHeader && (
+ <tr data-separator="true">
+ <td
+ colSpan={3}
+ style={{
+ fontWeight: 600,
+ paddingTop: "0.75rem",
+ paddingBottom: "0.25rem",
+ borderBottom: "none",
+ opacity: 0.6,
+ fontSize: "0.8rem",
+ textTransform: "uppercase",
+ letterSpacing: "0.05em",
+ }}
+ >
+ {role.branch ?? ""}
+ </td>
+ </tr>
+ )}
+ <tr>
+ <td>
+ {role.link ? (
+ <a href={role.link} target="_blank" rel="noopener noreferrer">
+ {role.text}
+ </a>
+ ) : (
+ role.text
+ )}
+ </td>
+ <td>
+ {editing ? (
+ <input
+ type="checkbox"
+ checked={selected.includes(role.value)}
+ onChange={() => toggleRole(role.value)}
+ />
+ ) : displayPrefs.includes(role.value) ? (
+ "✅"
+ ) : (
+ "❌"
+ )}
+ </td>
+ <td>
+ {editing ? (
+ <input
+ type="radio"
+ name="favourite-role"
+ checked={favourite === role.value}
+ onChange={() => {}}
+ onClick={() => toggleFavourite(role.value)}
+ />
+ ) : displayFavourite === role.value ? (
+ "⭐"
+ ) : (
+ ""
+ )}
+ </td>
+ </tr>
+ </Fragment>
+ );
+ })}
+ </tbody>
+ </table>
+ <p style={{ margin: 0, fontSize: "0.85rem", color: status.error ? "red" : "" }}>
+ {status.text}
+ </p>
+ </div>
+ );
+}
diff --git a/174bg/manager/src/components/Welcome.jsx b/174bg/manager/src/components/Welcome.jsx
new file mode 100644
index 0000000..da771cf
--- /dev/null
+++ b/174bg/manager/src/components/Welcome.jsx
@@ -0,0 +1,14 @@
+export default function Welcome({ record }) {
+ const name = record?.RSI_Handle || record?.name || "Pilot";
+ const avatarFile = record?.avatar;
+ const avatarUrl = avatarFile
+ ? `https://db.174bg.net/api/files/${record.collectionId}/${record.id}/${avatarFile}`
+ : null;
+
+ return (
+ <div id="welcome">
+ {avatarUrl && <img id="welcome-avatar" src={avatarUrl} alt={name} />}
+ <span id="welcome-text">Welcome back, {name}!</span>
+ </div>
+ );
+}
diff --git a/174bg/manager/src/lib/oncallTime.js b/174bg/manager/src/lib/oncallTime.js
new file mode 100644
index 0000000..e8afaf3
--- /dev/null
+++ b/174bg/manager/src/lib/oncallTime.js
@@ -0,0 +1,242 @@
+export const DAYS_OF_WEEK = [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+];
+
+// Sunday-first, matching Date#getDay()/getUTCDay() and Intl's "weekday"
+export const WEEKDAY_NAMES = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+];
+export const WEEKDAY_INDEX = Object.fromEntries(
+ WEEKDAY_NAMES.map((d, i) => [d, i]),
+);
+
+export function pad2(n) {
+ return String(n).padStart(2, "0");
+}
+
+export function getBrowserTimeZone() {
+ try {
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
+ } catch {
+ return "UTC";
+ }
+}
+
+export function listTimeZones() {
+ if (typeof Intl.supportedValuesOf === "function") {
+ try {
+ return Intl.supportedValuesOf("timeZone");
+ } catch {
+ // fall through to the fallback list below
+ }
+ }
+ return [
+ "UTC",
+ "America/Los_Angeles",
+ "America/Denver",
+ "America/Chicago",
+ "America/New_York",
+ "America/Sao_Paulo",
+ "Europe/London",
+ "Europe/Paris",
+ "Europe/Berlin",
+ "Europe/Moscow",
+ "Africa/Johannesburg",
+ "Asia/Dubai",
+ "Asia/Kolkata",
+ "Asia/Shanghai",
+ "Asia/Tokyo",
+ "Australia/Sydney",
+ "Pacific/Auckland",
+ ];
+}
+
+// Offset (ms) of `timeZone` from UTC at the instant `date` represents.
+export function tzOffsetMs(date, timeZone) {
+ const dtf = new Intl.DateTimeFormat("en-US", {
+ timeZone,
+ hourCycle: "h23",
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ });
+ const parts = {};
+ for (const { type, value } of dtf.formatToParts(date)) {
+ parts[type] = value;
+ }
+ const asUTC = Date.UTC(
+ Number(parts.year),
+ Number(parts.month) - 1,
+ Number(parts.day),
+ parts.hour === "24" ? 0 : Number(parts.hour),
+ Number(parts.minute),
+ Number(parts.second),
+ );
+ return asUTC - date.getTime();
+}
+
+// Formats `timeZone`'s UTC offset at `date` as "UTC+HH:MM"/"UTC-HH:MM".
+export function formatUtcOffset(timeZone, date = new Date()) {
+ const totalMinutes = Math.round(tzOffsetMs(date, timeZone) / 60000);
+ const sign = totalMinutes < 0 ? "-" : "+";
+ const abs = Math.abs(totalMinutes);
+ return `UTC${sign}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`;
+}
+
+// Nearest same-or-later calendar date whose local weekday in `timeZone`
+// is `day` - used as a DST-correct anchor for the conversion below.
+function nextLocalDateFor(day, timeZone) {
+ const dtf = new Intl.DateTimeFormat("en-US", {
+ timeZone,
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ weekday: "long",
+ });
+ const parts = {};
+ for (const { type, value } of dtf.formatToParts(new Date())) {
+ parts[type] = value;
+ }
+ const diff = (WEEKDAY_INDEX[day] - WEEKDAY_INDEX[parts.weekday] + 7) % 7;
+ const base = new Date(
+ Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day)),
+ );
+ base.setUTCDate(base.getUTCDate() + diff);
+ return base;
+}
+
+// Nearest same-or-later UTC calendar date whose UTC weekday is `day`.
+function nextUtcDateFor(day) {
+ const now = new Date();
+ const diff = (WEEKDAY_INDEX[day] - now.getUTCDay() + 7) % 7;
+ const base = new Date(
+ Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()),
+ );
+ base.setUTCDate(base.getUTCDate() + diff);
+ return base;
+}
+
+// Converts a weekday + "HH:MM" wall-clock time in `timeZone` to the
+// equivalent UTC weekday + "HH:MM" (the day may shift due to the offset).
+export function localDayTimeToUtc(day, time, timeZone) {
+ const [hour, minute] = time.split(":").map(Number);
+ const base = nextLocalDateFor(day, timeZone);
+ const guess = Date.UTC(
+ base.getUTCFullYear(),
+ base.getUTCMonth(),
+ base.getUTCDate(),
+ hour,
+ minute,
+ );
+ const offset = tzOffsetMs(new Date(guess), timeZone);
+ const actual = new Date(guess - offset);
+ return {
+ day: WEEKDAY_NAMES[actual.getUTCDay()],
+ time: `${pad2(actual.getUTCHours())}:${pad2(actual.getUTCMinutes())}`,
+ };
+}
+
+// Converts a UTC weekday + "HH:MM" back to weekday + "HH:MM" wall-clock
+// time in `timeZone`.
+export function utcDayTimeToLocal(day, time, timeZone) {
+ const [hour, minute] = time.split(":").map(Number);
+ const base = nextUtcDateFor(day);
+ const instant = new Date(
+ Date.UTC(
+ base.getUTCFullYear(),
+ base.getUTCMonth(),
+ base.getUTCDate(),
+ hour,
+ minute,
+ ),
+ );
+ const dtf = new Intl.DateTimeFormat("en-US", {
+ timeZone,
+ hourCycle: "h23",
+ weekday: "long",
+ hour: "2-digit",
+ minute: "2-digit",
+ });
+ const parts = {};
+ for (const { type, value } of dtf.formatToParts(instant)) {
+ parts[type] = value;
+ }
+ return {
+ day: parts.weekday,
+ time: `${parts.hour === "24" ? "00" : parts.hour}:${parts.minute}`,
+ };
+}
+
+export function getEffectiveTimezone(raw) {
+ return raw.timezone || getBrowserTimeZone();
+}
+
+// Converts the stored UTC schedule to local wall-clock times, keyed by
+// local weekday, for display/editing.
+export function utcScheduleToLocal(raw, timeZone) {
+ const local = {};
+ for (const day of DAYS_OF_WEEK) {
+ const entry = raw[day];
+ if (!entry?.available) continue;
+ const start = utcDayTimeToLocal(day, entry.start, timeZone);
+ const end = utcDayTimeToLocal(day, entry.end, timeZone);
+ local[start.day] = {
+ available: true,
+ start: start.time,
+ end: end.time,
+ };
+ }
+ return local;
+}
+
+// Converts a local-keyed schedule (from the edit form) to UTC for storage.
+export function localScheduleToUtc(local, timeZone) {
+ const utc = {};
+ for (const day of DAYS_OF_WEEK) {
+ const entry = local[day];
+ if (!entry?.available) continue;
+ const start = localDayTimeToUtc(day, entry.start || "00:00", timeZone);
+ const end = localDayTimeToUtc(day, entry.end || "00:00", timeZone);
+ utc[start.day] = { available: true, start: start.time, end: end.time };
+ }
+ return utc;
+}
+
+// onCallSchedule is stored as JSON, UTC-normalized:
+// { timezone: "IANA/Zone", [utcDay]: { available, start, end } }
+export function getOnCallRaw(record) {
+ const raw = record?.onCallSchedule;
+ if (!raw) return {};
+ if (typeof raw === "string") {
+ try {
+ return JSON.parse(raw) || {};
+ } catch {
+ return {};
+ }
+ }
+ return { ...raw };
+}
+
+// Legacy records saved before timezone support has no `timezone` key - those
+// values are already local wall-clock times, so skip conversion.
+export function getLocalOnCallSchedule(record) {
+ const raw = getOnCallRaw(record);
+ const timeZone = getEffectiveTimezone(raw);
+ const schedule = raw.timezone ? utcScheduleToLocal(raw, timeZone) : raw;
+ return { timeZone, schedule };
+}
diff --git a/174bg/manager/src/lib/otherJsonData.js b/174bg/manager/src/lib/otherJsonData.js
new file mode 100644
index 0000000..866e6fc
--- /dev/null
+++ b/174bg/manager/src/lib/otherJsonData.js
@@ -0,0 +1,13 @@
+// OtherJsonData may be stored as an object or a JSON string
+export function getOtherJsonData(record) {
+ const raw = record?.OtherJsonData;
+ if (!raw) return {};
+ if (typeof raw === "string") {
+ try {
+ return JSON.parse(raw) || {};
+ } catch {
+ return {};
+ }
+ }
+ return { ...raw };
+}
diff --git a/174bg/manager/src/lib/pocketbase.js b/174bg/manager/src/lib/pocketbase.js
new file mode 100644
index 0000000..ed0b2eb
--- /dev/null
+++ b/174bg/manager/src/lib/pocketbase.js
@@ -0,0 +1,3 @@
+import PocketBase from "pocketbase";
+
+export const pb = new PocketBase("https://db.174bg.net");
diff --git a/174bg/manager/src/lib/roles.js b/174bg/manager/src/lib/roles.js
new file mode 100644
index 0000000..1d02e37
--- /dev/null
+++ b/174bg/manager/src/lib/roles.js
@@ -0,0 +1,142 @@
+export const ROLES = [
+ {
+ branch: "Naval",
+ value: "Ship Captain",
+ text: "Ship Captain",
+ link: "https://handbook.174bg.net/#ship-captain",
+ },
+ {
+ branch: "Naval",
+ value: "Pilot",
+ text: "Pilot",
+ link: "https://handbook.174bg.net/#pilot",
+ },
+ {
+ branch: "Naval",
+ value: "Helmsman",
+ text: "Helmsman",
+ link: "https://handbook.174bg.net/#helmsman",
+ },
+ {
+ branch: "Naval",
+ value: "Gunner",
+ text: "Gunner",
+ link: "https://handbook.174bg.net/#gunner",
+ },
+ {
+ branch: "Marine",
+ value: "Tactical Marine",
+ text: "Tactical Marine",
+ link: "https://handbook.174bg.net/#tactical-marine",
+ },
+ {
+ branch: "Marine",
+ value: "Heavy Marine",
+ text: "Heavy Marine",
+ link: "https://handbook.174bg.net/#heavy-marine",
+ },
+ {
+ branch: "Marine",
+ value: "Scout Marine",
+ text: "Scout Marine",
+ link: "https://handbook.174bg.net/#scout-marine",
+ },
+ {
+ branch: "Marine",
+ value: "Combat Medic",
+ text: "Combat Medic",
+ link: "https://handbook.174bg.net/#combat-medic",
+ },
+ {
+ branch: "Marine",
+ value: "Combat Engineer",
+ text: "Combat Engineer",
+ link: "https://handbook.174bg.net/#combat-engineer",
+ },
+ {
+ branch: "Marine",
+ value: "Warden",
+ text: "Warden",
+ link: "https://handbook.174bg.net/#warden",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Engineer",
+ text: "Engineer",
+ link: "https://handbook.174bg.net/#engineer",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Hazardous Materials Specialist",
+ text: "Hazardous Materials Specialist",
+ link: "https://handbook.174bg.net/#hazardous-materials-specialist",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Medical Doctor",
+ text: "Medical Doctor",
+ link: "https://handbook.174bg.net/#medical-doctor",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Quartermaster",
+ text: "Quartermaster",
+ link: "https://handbook.174bg.net/#quartermaster",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Cargo Technician",
+ text: "Cargo Technician",
+ link: "https://handbook.174bg.net/#cargo-technician",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Craftsman",
+ text: "Craftsman",
+ link: "https://handbook.174bg.net/#craftsman",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Mining Technician",
+ text: "Mining Technician",
+ link: "https://handbook.174bg.net/#mining-technician",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Salvage Operator",
+ text: "Salvage Operator",
+ link: "https://handbook.174bg.net/#salvage-operator",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Expeditionary Analyst",
+ text: "Expeditionary Analyst",
+ link: "https://handbook.174bg.net/#expeditionary-analyst",
+ },
+ {
+ branch: "Auxiliary",
+ value: "Intelligence Agent",
+ text: "Intelligence Agent",
+ link: "https://handbook.174bg.net/#intelligence-agent",
+ },
+];
+
+export const RANK_NAMES = {
+ Naval: ["Cadet", "Ensign", "Lieutenant", "Captain", "Commodore", "Admiral"],
+ Marine: [
+ "Private",
+ "Corporal",
+ "Sergeant",
+ "Major",
+ "Commander",
+ "General",
+ ],
+ Auxiliary: [
+ "Trainee",
+ "Technician",
+ "Specialist",
+ "Supervisor",
+ "Chief",
+ "Marshal",
+ ],
+};
diff --git a/174bg/manager/src/main.jsx b/174bg/manager/src/main.jsx
new file mode 100644
index 0000000..ed1507f
--- /dev/null
+++ b/174bg/manager/src/main.jsx
@@ -0,0 +1,10 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App.jsx";
+import "./theme.css";
+
+createRoot(document.getElementById("root")).render(
+ <StrictMode>
+ <App />
+ </StrictMode>,
+);
diff --git a/174bg/manager/public/theme.css b/174bg/manager/src/theme.css
index 907a77e..907a77e 100644
--- a/174bg/manager/public/theme.css
+++ b/174bg/manager/src/theme.css
diff --git a/174bg/manager/vite.config.js b/174bg/manager/vite.config.js
new file mode 100644
index 0000000..ccadc66
--- /dev/null
+++ b/174bg/manager/vite.config.js
@@ -0,0 +1,9 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ outDir: "dist",
+ },
+});
diff --git a/174bg/manager/wrangler.jsonc b/174bg/manager/wrangler.jsonc
index 3285789..a6e5afe 100644
--- a/174bg/manager/wrangler.jsonc
+++ b/174bg/manager/wrangler.jsonc
@@ -4,7 +4,7 @@
"account_id": "424d9dabd39849818d9e22366a20e4d6",
"name": "174bg-manager",
"assets": {
- "directory": "./public/",
+ "directory": "./dist/",
"not_found_handling": "single-page-application",
},
"minify": true,