diff options
| author | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2026-07-25 17:27:38 +0100 |
|---|---|---|
| committer | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2026-07-25 17:27:38 +0100 |
| commit | a85a54651070765d6254287369835d747be1c276 (patch) | |
| tree | 6598d2e36bf5a3770c9450e1acceefcfcb092842 | |
| parent | 0d001f0094f2784033be52fa2a28102c271ffb1e (diff) | |
| download | unnamed-group-a85a54651070765d6254287369835d747be1c276.tar unnamed-group-a85a54651070765d6254287369835d747be1c276.tar.gz unnamed-group-a85a54651070765d6254287369835d747be1c276.tar.bz2 unnamed-group-a85a54651070765d6254287369835d747be1c276.tar.xz unnamed-group-a85a54651070765d6254287369835d747be1c276.zip | |
manager is now using react
| -rw-r--r-- | 174bg/manager/.gitignore | 3 | ||||
| -rw-r--r-- | 174bg/manager/index.html | 18 | ||||
| -rw-r--r-- | 174bg/manager/package-lock.json | 1906 | ||||
| -rw-r--r-- | 174bg/manager/package.json | 18 | ||||
| -rw-r--r-- | 174bg/manager/public/index.html | 1163 | ||||
| -rw-r--r-- | 174bg/manager/src/App.jsx | 182 | ||||
| -rw-r--r-- | 174bg/manager/src/components/Ledger.jsx | 73 | ||||
| -rw-r--r-- | 174bg/manager/src/components/OnCallSchedule.jsx | 186 | ||||
| -rw-r--r-- | 174bg/manager/src/components/RequiredInfo.jsx | 66 | ||||
| -rw-r--r-- | 174bg/manager/src/components/RolePreferences.jsx | 171 | ||||
| -rw-r--r-- | 174bg/manager/src/components/Welcome.jsx | 14 | ||||
| -rw-r--r-- | 174bg/manager/src/lib/oncallTime.js | 242 | ||||
| -rw-r--r-- | 174bg/manager/src/lib/otherJsonData.js | 13 | ||||
| -rw-r--r-- | 174bg/manager/src/lib/pocketbase.js | 3 | ||||
| -rw-r--r-- | 174bg/manager/src/lib/roles.js | 142 | ||||
| -rw-r--r-- | 174bg/manager/src/main.jsx | 10 | ||||
| -rw-r--r-- | 174bg/manager/src/theme.css (renamed from 174bg/manager/public/theme.css) | 0 | ||||
| -rw-r--r-- | 174bg/manager/vite.config.js | 9 | ||||
| -rw-r--r-- | 174bg/manager/wrangler.jsonc | 2 |
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, |
