Major app refactor for V3.1.2.
Some checks failed
Build / build (push) Has been cancelled

This took way too long, but i finally fixed all of the bugs i created in making this update
This commit is contained in:
Trenton Zimmer 2026-03-18 12:57:52 -04:00
parent 2a18ef1f89
commit 93c9732094
24 changed files with 1102 additions and 384 deletions

View File

@ -1,7 +1,9 @@
VITE_APP_VERSION="3.1.1"
VITE_APP_VERSION="3.1.2"
VITE_API_URL="http://localhost:8000/"
VITE_API_KEY="your_api_key_should_be_here"
VITE_ASSET_PATH="/assets"
VITE_GAME_ASSET_PATH="https://cdn.phaseii.network/file/PhaseII/game-assets"
VITE_DISCORD_OAUTH_URL="https://discord.com/oauth2/authorize?client_id=947985989421395988&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2F%23%2Fprofile%2Fintegrate%2Fdiscord&scope=identify"
VITE_TACHI_OAUTH_URL="https://kamai.tachi.ac/oauth/request-auth?clientID=CIb72d1ff81fcfdc001ead8bde3a0ae1a7e0663a37"
VITE_TACHI_OAUTH_URL="https://kamai.tachi.ac/oauth/request-auth?clientID=CIb72d1ff81fcfdc001ead8bde3a0ae1a7e0663a37"
VITE_DISCORD_URL="https://discord.gg/mNmjpX33cd"
VITE_DOCS_URL="https://docs.phaseii.network"

View File

@ -1,7 +1,9 @@
VITE_APP_VERSION="3.1.1"
VITE_APP_VERSION="3.1.2"
VITE_API_URL="https://restfulsleep.phaseii.network"
VITE_API_KEY="your_api_key_should_be_here"
VITE_ASSET_PATH="https://cdn.phaseii.network/file/PhaseII/web-assets"
VITE_GAME_ASSET_PATH="https://cdn.phaseii.network/file/PhaseII/game-assets"
VITE_DISCORD_OAUTH_URL="https://discord.com/oauth2/authorize?client_id=947985989421395988&response_type=code&redirect_uri=https%3A%2F%2Fweb3.phaseii.network%2Fprofile%2Fintegrate%2Fdiscord&scope=identify"
VITE_TACHI_OAUTH_URL="https://kamai.tachi.ac/oauth/request-auth?clientID=CIce4260e1939ceed11c8e48ee857a3aef2a87ba56"
VITE_TACHI_OAUTH_URL="https://kamai.tachi.ac/oauth/request-auth?clientID=CIce4260e1939ceed11c8e48ee857a3aef2a87ba56"
VITE_DISCORD_URL="https://discord.gg/mNmjpX33cd"
VITE_DOCS_URL="https://docs.phaseii.network"

211
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "phaseweb3",
"version": "3.1.0-RC",
"version": "3.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "phaseweb3",
"version": "3.1.0-RC",
"version": "3.1.1",
"dependencies": {
"@phosphor-icons/vue": "^2.2.1",
"@tailwindcss/vite": "^4.1.11",
@ -507,9 +507,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
"integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"dev": true,
"dependencies": {
"eslint-visitor-keys": "^3.4.3"
@ -534,13 +534,12 @@
}
},
"node_modules/@eslint/config-array": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/object-schema": "^2.1.6",
"@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@ -549,21 +548,22 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
"integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.17.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
@ -572,11 +572,10 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@ -584,7 +583,7 @@
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"js-yaml": "^4.1.1",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
@ -609,11 +608,10 @@
}
},
"node_modules/@eslint/js": {
"version": "9.32.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz",
"integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==",
"version": "9.39.2",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@ -622,23 +620,21 @@
}
},
"node_modules/@eslint/object-schema": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
"integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.15.1",
"@eslint/core": "^0.17.0",
"levn": "^0.4.1"
},
"engines": {
@ -777,7 +773,6 @@
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
@ -1384,8 +1379,7 @@
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true,
"license": "MIT"
"dev": true
},
"node_modules/@vitejs/plugin-vue": {
"version": "6.0.1",
@ -1666,12 +1660,12 @@
}
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
@ -2120,25 +2114,23 @@
}
},
"node_modules/eslint": {
"version": "9.32.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz",
"integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
"version": "9.39.2",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.15.0",
"@eslint/config-array": "^0.21.1",
"@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.32.0",
"@eslint/plugin-kit": "^0.3.4",
"@eslint/js": "9.39.2",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
@ -2197,14 +2189,13 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz",
"integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==",
"version": "5.5.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
"integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.11.7"
"prettier-linter-helpers": "^1.0.1",
"synckit": "^0.11.12"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@ -2228,16 +2219,15 @@
}
},
"node_modules/eslint-plugin-vue": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.3.0.tgz",
"integrity": "sha512-A0u9snqjCfYaPnqqOaH6MBLVWDUIN4trXn8J3x67uDcXvR7X6Ut8p16N+nYhMCQ9Y7edg2BIRGzfyZsY0IdqoQ==",
"version": "10.8.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.8.0.tgz",
"integrity": "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"natural-compare": "^1.4.0",
"nth-check": "^2.1.1",
"postcss-selector-parser": "^6.0.15",
"postcss-selector-parser": "^7.1.0",
"semver": "^7.6.3",
"xml-name-validator": "^4.0.0"
},
@ -2245,11 +2235,15 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
"@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
"@typescript-eslint/parser": "^7.0.0 || ^8.0.0",
"eslint": "^8.57.0 || ^9.0.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"vue-eslint-parser": "^10.0.0"
},
"peerDependenciesMeta": {
"@stylistic/eslint-plugin": {
"optional": true
},
"@typescript-eslint/parser": {
"optional": true
}
@ -2388,8 +2382,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true,
"license": "Apache-2.0"
"dev": true
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
@ -2471,9 +2464,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
@ -2490,10 +2483,9 @@
}
},
"node_modules/form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@ -3145,10 +3137,9 @@
}
},
"node_modules/minizlib": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
"license": "MIT",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"dependencies": {
"minipass": "^7.1.2"
},
@ -3162,21 +3153,6 @@
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"license": "MIT",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -3439,9 +3415,9 @@
}
},
"node_modules/postcss-selector-parser": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
@ -3483,11 +3459,10 @@
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
"integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-diff": "^1.1.2"
},
@ -3694,11 +3669,10 @@
}
},
"node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"version": "0.11.12",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
"integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
@ -3725,16 +3699,14 @@
}
},
"node_modules/tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
"license": "ISC",
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
"integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.0.1",
"mkdirp": "^3.0.1",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
},
"engines": {
@ -3918,16 +3890,15 @@
}
},
"node_modules/vue-eslint-parser": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz",
"integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==",
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.0.tgz",
"integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"eslint-scope": "^8.2.0",
"eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0",
"eslint-scope": "^8.2.0 || ^9.0.0",
"eslint-visitor-keys": "^4.2.0 || ^5.0.0",
"espree": "^10.3.0 || ^11.0.0",
"esquery": "^1.6.0",
"semver": "^7.6.3"
},
@ -3938,7 +3909,7 @@
"url": "https://github.com/sponsors/mysticatea"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0"
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0"
}
},
"node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": {

View File

@ -1,6 +1,6 @@
{
"name": "phaseweb3",
"version": "3.1.1",
"version": "3.1.2",
"scripts": {
"dev": "vite --host",
"build": "vite build",

View File

@ -47,5 +47,6 @@
"3.0.45": ["- (Bugfix) Fix Timeline not loading", "- (Major) Add a filter to the data in the timeline for private profiles", "- (Game) Add SDVX 6th chart", "- (Bugfix) Fix floats for jubeat difficulties"],
"3.1.0-RC": ["- (Feature) Add onboarding page", "- (Minor) Add BaseImage component", "- (Minor) Add BaseAccordion component, - (Admin) Finish admin event log page"],
"3.1.0": ["- (Major) WebUI V3 full release"],
"3.1.1": ["- (Admin) Add more admin stats for users", "- (Minor) Add new greetings"]
"3.1.1": ["- (Admin) Add more admin stats for users", "- (Minor) Add new greetings"],
"3.1.2": ["- (Major) Add initial code for oAuth pages", "- (Major) Add redirect to target page after login", "- (Minor) Add quick links to navbar", "- (Minor) Add new greetings", "- (Minor) Replace login page background", "- (Minor) Add animation to loading modal", "- (Bugfix) Refactor loading modal", "- (Bugfix) Refactor overlay layer", "- (Bugfix) Optimize main layouts", "- (Bugfix) Fix loading states around site", "- (Bugfix) Optimize news read api calls", "- (Minor) Add simple auth checkers to router, blocking access at webui level"]
}

View File

@ -1,7 +1,71 @@
<script setup>
import { computed, ref, watch } from "vue";
import { RouterView } from "vue-router";
import { useMainStore } from "@/stores/main.js";
import { useStyleStore } from "@/stores/style.js";
import OverlayLayer from "@/components/OverlayLayer.vue";
import LoadingModal from "@/components/Modal/LoadingModal.vue";
const mainStore = useMainStore();
const styleStore = useStyleStore();
const loading = computed(() => mainStore.activeRequests !== 0);
const saving = computed(() => mainStore.activeSavingRequests !== 0);
const errorCode = computed(() => mainStore.errorCode);
const isActive = computed(() => loading.value || saving.value);
const showModal = ref(false);
watch(isActive, (val) => {
if (val) {
showModal.value = true;
} else {
setTimeout(() => {
if (!isActive.value) {
showModal.value = false;
}
}, 250);
}
});
</script>
<template>
<RouterView :key="$route.fullPath" />
<div :class="{ dark: styleStore.darkMode }">
<OverlayLayer
:active="showModal"
:transparent="saving"
:z-index="showModal ? 'z-55' : '-z-150'"
>
<Transition name="modal-fade">
<LoadingModal
v-if="showModal"
:active="showModal"
:is-save="saving"
:error-code="errorCode"
/>
</Transition>
</OverlayLayer>
<RouterView
:key="$route.path"
:class="showModal && !saving ? 'hidden' : 'block'"
/>
</div>
</template>
<style>
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: all 0.15s ease-in;
}
.modal-fade-enter-from,
.modal-fade-leave-to {
transform: scale(0.9);
}
.modal-fade-enter-to,
.modal-fade-leave-from {
transform: scale(1);
}
</style>

View File

@ -1,5 +1,4 @@
<script setup>
import { defineProps } from "vue";
import { useRouter } from "vue-router";
import { PhArrowLineUpRight } from "@phosphor-icons/vue";
import CardBox from "@/components/CardBox.vue";

View File

@ -1,6 +1,4 @@
<script setup>
import { defineProps } from "vue";
defineProps({
musicData: {
type: Object,

View File

@ -34,9 +34,10 @@ const computedValue = computed({
},
});
const inputType = computed(() =>
props.type === "radio" ? "radio" : "checkbox",
);
const inputType = computed(() => {
if (props.type === "radio") return "radio";
return "checkbox";
});
</script>
<template>

View File

@ -1,7 +1,6 @@
<script setup>
import { useRouter } from "vue-router";
import CardBox from "@/components/CardBox.vue";
import OverlayLayer from "@/components/OverlayLayer.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import BaseButton from "@/components/BaseButton.vue";
import { useMainStore } from "@/stores/main";
@ -36,89 +35,87 @@ function hotReload() {
</script>
<template>
<OverlayLayer v-if="active" :transparent="isSave">
<CardBox
class="shadow-lg max-h-modal w-11/12 md:w-3/5 lg:w-2/5 xl:w-4/12 z-50 text-white/90"
<CardBox
class="shadow-lg max-h-modal w-11/12 md:w-3/5 lg:w-2/5 xl:w-4/12 z-50 text-white/90"
>
<div
v-if="!isFinished && !errorCode"
class="grid text-center justify-center grid-cols-1 gap-3"
>
<div
v-if="!isFinished && !errorCode"
class="grid text-center justify-center grid-cols-1 gap-3"
>
<template v-if="!mainStore.userCustomize.shrimpLinks">
<img
class="rounded-full place-self-center"
src="/icon.gif"
width="70"
/>
</template>
<template v-else>
<BaseIcon
:icon="PhShrimp"
size="70"
w="w-50"
color="text-pink-400"
class="m-5 animate animate-spin place-self-center"
/>
</template>
<h1 class="text-xl md:text-2xl">
<template v-if="!mainStore.userCustomize.shrimpLinks">
<img
class="rounded-full place-self-center"
src="/icon.gif"
width="70"
/>
{{ isSave ? `Submitting...` : `Loading...` }}
</template>
<template v-else>
<BaseIcon
:icon="PhShrimp"
size="70"
w="w-50"
color="text-pink-400"
class="m-5 animate animate-spin place-self-center"
/>
<span class="text-pink-200">
{{ isSave ? `Shrimping...` : `Krilling...` }}
</span>
</template>
<h1 class="text-xl md:text-2xl">
<template v-if="!mainStore.userCustomize.shrimpLinks">
{{ isSave ? `Submitting...` : `Loading...` }}
</template>
<template v-else>
<span class="text-pink-200">
{{ isSave ? `Shrimping...` : `Krilling...` }}
</span>
</template>
</h1>
<p class="text-lg md:text-xl">
<template v-if="!mainStore.userCustomize.shrimpLinks">
Please wait
</template>
<template v-else>
<span class="text-pink-400">Shrimply wait</span>
</template>
</p>
</div>
</h1>
<p class="text-lg md:text-xl">
<template v-if="!mainStore.userCustomize.shrimpLinks">
Please wait
</template>
<template v-else>
<span class="text-pink-400">Shrimply wait</span>
</template>
</p>
</div>
<div
v-if="isSave && isFinished && !errorCode"
class="grid text-center justify-center grid-cols-1 gap-3"
>
<div class="place-self-center">
<BaseIcon
:icon="PhCloudCheck"
class="text-green-700"
w="w-20"
:size="45"
/>
</div>
<h1 class="text-xl md:text-2xl">Saved!</h1>
<div
v-if="isSave && isFinished && !errorCode"
class="grid text-center justify-center grid-cols-1 gap-3"
>
<div class="place-self-center">
<BaseIcon
:icon="PhCloudCheck"
class="text-green-700"
w="w-20"
:size="45"
/>
</div>
<h1 class="text-xl md:text-2xl">Saved!</h1>
</div>
<div
v-if="errorCode"
class="grid text-center justify-center grid-cols-1 gap-3"
>
<div class="place-self-center">
<BaseIcon :icon="PhCloudX" class="text-red-500" w="w-20" :size="45" />
</div>
<h1 class="text-xl md:text-2xl">Uh Oh!</h1>
<h1 class="text-lg md:text-xl">
The server had some trouble processing your request at this time.
</h1>
<h1 class="text-lg md:text-xl">
Error: <span class="text-red-500">{{ errorCode }}</span>
</h1>
<div>
<BaseButton
type="submit"
color="info"
label="Reload"
:small="false"
@click="hotReload()"
/>
</div>
<div
v-if="errorCode"
class="grid text-center justify-center grid-cols-1 gap-3"
>
<div class="place-self-center">
<BaseIcon :icon="PhCloudX" class="text-red-500" w="w-20" :size="45" />
</div>
</CardBox>
</OverlayLayer>
<h1 class="text-xl md:text-2xl">Uh Oh!</h1>
<h1 class="text-lg md:text-xl">
The server had some trouble processing your request at this time.
</h1>
<h1 class="text-lg md:text-xl">
Error: <span class="text-red-500">{{ errorCode }}</span>
</h1>
<div>
<BaseButton
type="submit"
color="info"
label="Reload"
:small="false"
@click="hotReload()"
/>
</div>
</div>
</CardBox>
</template>

View File

@ -24,7 +24,7 @@ const isMenuNavBarActive = ref(false);
<template>
<nav
class="top-0 inset-x-0 fixed bg-gray-50 h-14 z-30 transition-position duration-150 ease-in-out w-screen lg:w-auto dark:bg-slate-900"
class="top-0 left-0 right-0 inset-x-0 fixed bg-gray-50 h-14 z-30 transition-position duration-150 ease-in-out w-full lg:w-auto dark:bg-slate-900"
>
<div class="flex lg:items-stretch" :class="containerMaxW">
<div class="flex flex-1 items-stretch h-14">
@ -44,7 +44,7 @@ const isMenuNavBarActive = ref(false);
</NavBarItemPlain>
</div>
<div
class="max-h-screen-menu overflow-y-auto lg:overflow-visible absolute w-screen top-14 left-0 bg-gray-50 shadow-lg lg:w-auto lg:flex lg:static lg:shadow-none dark:bg-slate-900"
class="max-h-screen-menu overflow-y-auto lg:overflow-visible fixed w-screen top-14 left-0 bg-gray-50 shadow-lg lg:w-auto lg:flex lg:static lg:shadow-none dark:bg-slate-900"
:class="[isMenuNavBarActive ? 'block' : 'hidden']"
>
<NavBarMenuList :menu="menu" @menu-click="menuClick" />

View File

@ -14,6 +14,10 @@ defineProps({
type: Boolean,
default: false,
},
active: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(["overlay-click"]);
@ -26,33 +30,20 @@ const styleStore = useStyleStore();
</script>
<template>
<div
:class="[type, zIndex]"
class="items-center flex-col justify-center md:h-full fixed inset-0"
>
<transition
enter-active-class="transition duration-150 ease-in"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition duration-150 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
<Transition name="overlay-fade">
<div
v-show="active"
:class="[type, zIndex]"
class="fixed inset-0 flex flex-col items-center justify-center bg-linear-to-tr dark:from-gray-700 dark:via-gray-900 dark:to-gray-700 transform"
>
<div
class="absolute inset-0 bg-linear-to-tr dark:from-gray-700 dark:via-gray-900 dark:to-gray-700 h-screen"
class="absolute inset-0"
:class="`${styleStore.overlayStyle} ${
transparent ? 'opacity-90' : 'opacity-100'
}`"
@click="overlayClick"
/>
</transition>
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="animate-fade-out"
>
<slot />
</transition>
</div>
</div>
</Transition>
</template>

View File

@ -0,0 +1,80 @@
export const applicationIntents = [
{
id: "network",
label: "Read Network",
tip: "View network news, public profiles, and public arcades",
},
{
id: "read_user",
label: "Read User",
tip: "Allows this app to read a user that has authenticated through it",
},
{
id: "update_user",
label: "Update User",
tip: "Allows this app to update a user that has authenticated through it",
},
{
id: "read_profiles",
label: "Read Profiles",
tip: "Allows this app to read the game profiles of a user that has authenticated through it",
},
{
id: "update_profiles",
label: "Update Profiles",
tip: "Allows this app to update the game profiles of a user that has authenticated through it",
},
{
id: "read_scores",
label: "Read Scores",
tip: "Allows this app to read the scores and records of a user that has authenticated through it",
},
{
id: "read_arcade",
label: "Read Arcades",
tip: "Allows this app to read the arcades of a user that has authenticated through it",
},
{
id: "update_arcade",
label: "Update Arcades",
tip: "Allows this app to update an arcade of a user that has authenticated through it",
},
{
id: "webhook",
label: "Webhooks",
tip: "Allows this app to add and remove webhooks for a user that has authenticated through it",
},
];
export const applicationWebhooks = [
{
id: "network.news",
type: "network",
short: "news",
label: "Network News",
tip: "Called when a new news post is uploaded",
},
{
id: "network.maintenance",
type: "network",
short: "maintenance",
label: "Network Maintenance",
tip: "Called when a maintenance window is posted",
},
{
id: "arcade.event",
type: "arcade",
short: "event",
label: "Arcade PCB Events",
tip: "Called when a PCB event is sent. Requires an arcade to be specified",
fields: ["arcadeId"],
},
{
id: "arcade.paseli",
type: "arcade",
short: "paseli",
label: "Arcade PASELI",
tip: "Called when a PASELI transaction completes. Requires an arcade to be specified",
fields: ["arcadeId"],
},
];

View File

@ -635,5 +635,25 @@
"author": "superaustin7",
"header": "Why does <username> need Konami Original Songs?",
"comment": "We ❤️ Pop'n Music!"
},
{
"author": "57z",
"header": "<username>, I once again am asking you to",
"comment": "submit welcome greetings."
},
{
"author": "Anonymous",
"header": "Welcome to StepManiaX, <username>!",
"comment": "Kyle Ward will sue"
},
{
"author": "Dadrewster569",
"header": "Konnichihello, <username>",
"comment": ""
},
{
"author": "superaustin7",
"header": "Welcome to jubeat, <username>",
"comment": "THAT WON'T MAKE IT IN TIME FOR THIS SCHEDULE."
}
]

View File

@ -10,13 +10,14 @@ import {
PhFilmSlate,
PhArchive,
PhGitMerge,
PhCode,
PhDiscordLogo,
PhTextAa,
} from "@phosphor-icons/vue";
import { ref, watch, onMounted, computed } from "vue";
import { ref, computed } from "vue";
import { useRouter, useRoute } from "vue-router";
import menuNavBar from "@/menuNavBar.js";
import { useMainStore } from "@/stores/main.js";
import { useStyleStore } from "@/stores/style.js";
import LoadingModal from "@/components/Modal/LoadingModal.vue";
// import EmailModal from "@/components/Modal/EmailModal.vue";
import UpdateModal from "@/components/Modal/UpdateModal.vue";
import WelcomeModal from "@/components/Modal/WelcomeModal.vue";
@ -26,75 +27,20 @@ import NavBarItemPlain from "@/components/NavBarItemPlain.vue";
import AsideMenu from "@/components/Menus/AsideMenu.vue";
import FooterBar from "@/components/FooterBar.vue";
import { gameData } from "@/constants";
// import BaseButton from "@/components/BaseButton.vue";
import BaseButton from "@/components/BaseButton.vue";
const DISCORD_URL = import.meta.env.VITE_DISCORD_URL;
const DOCS_URL = import.meta.env.VITE_DOCS_URL;
const router = useRouter();
const route = useRoute();
const mainStore = useMainStore();
onMounted(async () => {
try {
const validSession = await mainStore.loadUser();
if (!validSession) {
mainStore.deleteUserSession();
router.push({
name: "login",
});
}
} catch (error) {
console.error("Failed to check SessionID:", error);
mainStore.deleteUserSession();
router.push({
name: "login",
});
}
});
const loading = ref(mainStore.activeRequests !== 0);
const saving = ref(mainStore.activeSavingRequests !== 0);
const errorCode = ref(mainStore.errorCode);
const userLoaded = ref(mainStore.userLoaded);
const userArcades = ref(mainStore.userArcades);
watch(
() => mainStore.activeRequests !== 0,
(newValue) => {
loading.value = newValue;
},
);
watch(
() => mainStore.activeSavingRequests !== 0,
(newValue) => {
saving.value = newValue;
},
);
watch(
() => mainStore.errorCode,
(newValue) => {
errorCode.value = newValue;
},
);
watch(
() => mainStore.userLoaded,
(newValue) => {
userLoaded.value = newValue;
},
);
watch(
() => mainStore.userArcades,
(newValue) => {
userArcades.value = newValue;
},
);
const userLoaded = computed(() => mainStore.userLoaded);
const userCustomize = computed(() => mainStore.userCustomize);
const layoutAsidePadding = "xl:pl-60";
const styleStore = useStyleStore();
const isAsideMobileExpanded = ref(false);
const isAsideLgActive = ref(false);
@ -224,6 +170,14 @@ const menuAside = computed(() => {
label: "Changelog Archive",
});
if (mainStore.userAdmin) {
sideMenu.push({
label: "Developer Portal",
icon: PhCode,
to: "/developer",
});
}
return sideMenu;
});
</script>
@ -232,23 +186,12 @@ const menuAside = computed(() => {
<div
:key="route.fullPath"
:class="{
dark: styleStore.darkMode,
'overflow-hidden lg:overflow-visible': isAsideMobileExpanded,
}"
>
<LoadingModal
:active="loading || saving"
:is-save="saving"
:error-code="errorCode"
class="transition-opacity duration-300 ease-out"
:class="{
'opacity-100': loading || saving,
'opacity-0': !loading && !saving,
}"
/>
<template v-if="userLoaded">
<WelcomeModal class="transition-opacity duration-300 ease-out">
<UpdateModal class="transition-opacity duration-300 ease-out" />
<WelcomeModal>
<UpdateModal />
</WelcomeModal>
<!-- <EmailModal class="transition-opacity duration-300 ease-out" /> -->
</template>
@ -285,14 +228,26 @@ const menuAside = computed(() => {
<BaseIcon :icon="PhDotsThreeCircle" size="24" />
</NavBarItemPlain>
<div class="h-full flex place-items-center ml-4 gap-4">
<!-- <span>You can add up to 4 buttons here</span>
<BaseButton small label="QuickNav" color="info" />
<BaseButton small label="QuickNav" color="success" />
<BaseButton small label="QuickNav" color="warning" />
<BaseButton small label="QuickNav" color="danger" />
<span>They're sticky!</span> -->
</div>
<template v-if="!userCustomize.hideQuickLinks">
<div class="h-full flex place-items-center ml-4 gap-4">
<BaseButton
small
label="Discord"
:icon="PhDiscordLogo"
color="info"
:href="DISCORD_URL"
target="_blank"
/>
<BaseButton
small
label="Docs"
:icon="PhTextAa"
color="info"
:href="DOCS_URL"
target="_blank"
/>
</div>
</template>
</NavBar>
<AsideMenu
:is-aside-mobile-expanded="isAsideMobileExpanded"

View File

@ -1,53 +1,12 @@
<script setup>
import { useMainStore } from "@/stores/main.js";
import { useStyleStore } from "@/stores/style.js";
import { ref, watch } from "vue";
import LoadingModal from "@/components/Modal/LoadingModal.vue";
const mainStore = useMainStore();
const loading = ref(mainStore.isLoading);
const saving = ref(mainStore.isSaving);
const errorCode = ref(mainStore.errorCode);
const styleStore = useStyleStore();
watch(
() => mainStore.isLoading,
(newValue) => {
loading.value = newValue;
},
);
watch(
() => mainStore.isSaving,
(newValue) => {
saving.value = newValue;
},
);
watch(
() => mainStore.errorCode,
(newValue) => {
errorCode.value = newValue;
},
);
</script>
<script setup></script>
<template>
<div :class="{ dark: styleStore.darkMode }">
<LoadingModal
:active="loading || saving"
:is-save="saving"
:error-code="errorCode"
class="transition-opacity duration-300 ease-out"
:class="{
'opacity-100': loading || saving,
'opacity-0': !loading && !saving,
}"
<div class="bg-gray-950 dark:text-slate-100">
<div
class="hidden md:block absolute inset-0 z-0 bg-[radial-gradient(125%_125%_at_50%_10%,#000000_40%,#0d1a36_100%)]"
/>
<div class="bg-gray-950 dark:text-slate-100">
<div class="animated animatedFadeInUp fadeInUp">
<slot />
</div>
<div class="animated animatedFadeInUp fadeInUp">
<slot />
</div>
</div>
</template>

View File

@ -1,9 +1,11 @@
import { createRouter, createWebHistory } from "vue-router";
import { useMainStore } from "@/stores/main";
import Home from "@/views/HomeView.vue";
const routes = [
{
meta: {
requiresAuth: true,
title: "Dashboard",
},
path: "/",
@ -12,6 +14,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "News Archive",
},
path: "/news",
@ -20,6 +23,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "News Post",
},
path: "/news/:id",
@ -28,6 +32,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "WebUI Changelog",
},
path: "/changelog",
@ -60,6 +65,16 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Authorize an App",
},
path: "/profile/authorize",
name: "authorize_service",
component: () => import("@/views/Auth/AuthorizeView.vue"),
},
{
meta: {
requiresAuth: true,
title: "Profile",
},
path: "/profile",
@ -68,6 +83,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Customizations",
},
path: "/profile/customize",
@ -76,6 +92,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Integrations",
},
path: "/profile/integrate",
@ -84,6 +101,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Integration Callback",
},
path: "/profile/integrate/:service",
@ -92,6 +110,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Login Cards",
},
path: "/profile/cards",
@ -100,6 +119,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Claim a Profile",
},
path: "/profile/claim",
@ -108,6 +128,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Data Export",
},
path: "/profile/export",
@ -124,6 +145,7 @@ const routes = [
// },
{
meta: {
requiresAuth: true,
title: "Play Videos",
},
path: "/profile/videos",
@ -132,6 +154,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Game Images",
},
path: "/profile/content",
@ -140,6 +163,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "View User",
},
path: "/profiles/:id",
@ -151,6 +175,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "Admin Dashboard",
},
path: "/admin",
@ -159,6 +185,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "Onboarding",
},
path: "/admin/onboarding",
@ -167,6 +195,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "Auto-Onboarding",
},
path: "/admin/onboarding/:data",
@ -175,6 +205,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "Network Maintenance",
},
path: "/admin/maint",
@ -183,6 +215,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "Events",
},
path: "/admin/events",
@ -191,6 +225,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "Data API",
},
path: "/admin/api",
@ -199,6 +235,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "Arcades",
},
path: "/admin/arcades",
@ -207,6 +245,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "Users",
},
path: "/admin/users",
@ -215,6 +255,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "News",
},
path: "/admin/news",
@ -223,6 +265,8 @@ const routes = [
},
{
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "OTA Updates",
},
path: "/admin/ota",
@ -239,6 +283,7 @@ const routes = [
// },
{
meta: {
requiresAuth: true,
title: "Arcade Overview",
},
path: "/arcade/:id",
@ -247,6 +292,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Event Settings",
},
path: "/arcade/:id/events",
@ -258,6 +304,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Machine List",
},
path: "/arcade/:id/machines",
@ -269,6 +316,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "PASELI Transactions",
},
path: "/arcade/:id/paseli",
@ -280,6 +328,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Claim an Arcade",
},
path: "/arcade/claim",
@ -288,6 +337,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Game Overview",
},
path: "/games/:id/",
@ -299,6 +349,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "View Profile",
},
path: "/games/:game/profiles/:userId/",
@ -310,6 +361,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Edit Profile",
},
path: "/games/:game/edit",
@ -321,6 +373,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Rivals",
},
path: "/games/:game/rivals",
@ -332,6 +385,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Song Overview",
},
path: "/games/:game/song/:songId",
@ -343,6 +397,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "View Scores",
},
path: "/games/:game/scores/:userId",
@ -354,6 +409,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Network Scores",
},
path: "/games/:game/scores",
@ -365,6 +421,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Error",
},
path: "/:catchAll(.*)",
@ -373,6 +430,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "View Records",
},
path: "/games/:game/records/:userId",
@ -384,6 +442,7 @@ const routes = [
},
{
meta: {
requiresAuth: true,
title: "Network Records",
},
path: "/games/:game/records",
@ -395,11 +454,23 @@ const routes = [
},
{
meta: {
title: "Error",
requiresAuth: true,
requiresDev: true,
title: "Developer Portal",
},
path: "/:catchAll(.*)",
name: "ErrorPage",
component: () => import("@/views/ErrorView.vue"),
path: "/developer",
name: "Developer Portal",
component: () => import("@/views/Developer/PortalView.vue"),
},
{
meta: {
requiresAuth: true,
requiresDev: true,
title: "App Registration",
},
path: "/developer/register",
name: "App Registration",
component: () => import("@/views/Developer/RegisterView.vue"),
},
];
@ -411,4 +482,48 @@ const router = createRouter({
},
});
router.beforeEach(async (to) => {
const mainStore = useMainStore();
mainStore.errorCode = null;
if (to.meta.requiresAuth) {
const validSession = await mainStore.loadUser();
if (!validSession) {
mainStore.deleteUserSession();
return {
name: "login",
query: { redirect: to.fullPath },
};
}
}
if (to.meta.requiresAdmin) {
const validSession = await mainStore.loadUser();
if (!validSession || !mainStore.userAdmin) {
console.log("You're not an admin!");
window.alert("You're not an admin!");
return {
name: "dashboard",
};
}
}
if (to.meta.requiresDev) {
const validSession = await mainStore.loadUser();
if (!validSession || !mainStore.userAdmin) {
console.log("You're not a dev!");
window.alert("You're not a dev!");
return {
name: "dashboard",
};
}
}
return true;
});
export default router;

View File

@ -288,8 +288,9 @@ export async function APIUserReadNews(newsId) {
newsId: newsId,
});
mainStore.userLoaded = false;
await mainStore.loadUser();
const seen_news = await mainStore.userData.seen_news;
seen_news[newsId] = true;
return data;
} catch (error) {
console.log(`Error saving user!`, error);

View File

@ -0,0 +1,119 @@
<script setup>
import { useRouter } from "vue-router";
import { reactive } from "vue";
import { useMainStore } from "@/stores/main.js";
import CardBox from "@/components/CardBox.vue";
import BaseButton from "@/components/BaseButton.vue";
import LayoutGuest from "@/layouts/LayoutGuest.vue";
const router = useRouter();
const mainStore = useMainStore();
const form = reactive({
spinin: false,
});
const submit = async () => {
const response = await mainStore.createUserSession();
if (response) {
router.push("/");
}
};
const appInfo = {
name: "Edi",
image: "/edi512x512_2.png",
manager: "Lumen",
about:
"Edi is a DDR score tracking app that offers a direct connection to your PhaseII account.",
intents: ["user", "account", "webhook_scores"],
internal: false,
};
</script>
<template>
<LayoutGuest>
<div class="flex md:min-h-screen md:items-center md:justify-center">
<CardBox
class="w-full md:w-auto rounded-none md:rounded-xl md:drop-shadow-xl"
:class="form.spinin ? 'animate-spin' : 'animate-none'"
has-table
is-auth
>
<div
class="p-4 flex flex-col md:flex-row w-full space-y-2 md:space-y-0 md:space-x-4"
>
<div class="flex flex-col items-center text-wrap h-full md:mt-5">
<div class="flex flex-col items-center text-wrap">
<img src="/favicon.png" class="rounded-full shadow-lg mb-2" />
<h1 class="text-xl"><samp>PhaseII</samp></h1>
<button
class="text-sm text-gray-700 dark:text-white/75 hover:cursor-pointer"
@click="form.spinin = !form.spinin"
>
Spinnin' since 2021
</button>
</div>
<hr class="border-r my-1 w-full mb-4" />
<p class="text-lg relative bottom-0">Authorize App</p>
</div>
<div class="md:border-r" />
<form @submit.prevent="submit()">
<div class="flex flex-col items-center text-wrap">
<h1 class="text-lg md:text-xl mb-2">
Integrate with <span class="font-bold">{{ appInfo.name }}</span>
</h1>
<img
:src="appInfo.image"
width="75"
class="rounded-full shadow-lg mb-2"
/>
<p class="text-md max-w-md wrap-break-word text-center">
{{ appInfo.about }}
</p>
<hr class="border-r my-2 w-full" />
<h2 class="text-md md:text-lg">
<span class="font-bold">{{ appInfo.name }}</span> will have
access to:
</h2>
<ul class="text-sm md:text-md text-center">
<li v-for="intent of appInfo.intents" :v-key="intent">
- {{ intent }}
</li>
</ul>
</div>
<div class="flex flex-col gap-2 mt-4">
<BaseButton label="Authorize" color="success" type="submit" />
</div>
<hr class="border-t my-4 w-full" />
<div class="flex flex-col items-center">
<p
v-if="!appInfo.internal"
class="text-sm max-w-md wrap-break-word text-center mb-2"
>
Please note that
<span class="font-bold">{{ appInfo.name }}</span> is not a
<samp>PhaseII</samp> provided service, and that any issues
should be directed to the appropriate channels.
</p>
<p class="text-sm max-w-md wrap-break-word text-center">
<span class="font-bold">{{ appInfo.name }}</span> is managed by
<span class="font-bold">{{ appInfo.manager }}</span>
</p>
</div>
<hr class="border-t my-4 w-full" />
<div class="flex flex-col gap-2 my-4">
<h2>I changed my mind...</h2>
<BaseButton label="Go back" color="danger" to="/" />
</div>
</form>
</div>
</CardBox>
</div>
</LayoutGuest>
</template>

View File

@ -1,5 +1,5 @@
<script setup>
import { useRouter } from "vue-router";
import { useRouter, useRoute } from "vue-router";
import { PhUser, PhPassword } from "@phosphor-icons/vue";
import { reactive } from "vue";
import { useMainStore } from "@/stores/main.js";
@ -11,7 +11,9 @@ import BaseButton from "@/components/BaseButton.vue";
import LayoutGuest from "@/layouts/LayoutGuest.vue";
const router = useRouter();
const route = useRoute();
const mainStore = useMainStore();
const redirectPath = route.query.redirect || "/";
// Reactive form data
const form = reactive({
@ -33,7 +35,7 @@ const submit = async () => {
form.remember,
);
if (response) {
router.push("/");
router.push(redirectPath);
}
};
</script>

View File

@ -0,0 +1,97 @@
<script setup>
import { ref, onMounted, reactive } from "vue";
import { PhCloud, PhCode } from "@phosphor-icons/vue";
import { dashCode } from "@/constants/userData.js";
import SectionMain from "@/components/SectionMain.vue";
import BaseButton from "@/components/BaseButton.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import UserCard from "@/components/UserCard.vue";
import CardBox from "@/components/CardBox.vue";
import FormField from "@/components/FormField.vue";
import FormControl from "@/components/FormControl.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionTitleLine from "@/components/SectionTitleLine.vue";
const DOCS_URL = import.meta.env.VITE_DOCS_URL;
const copyToClipboard = (text) => {
navigator.clipboard
.writeText(text)
.then(() => {
alert("Copied to clipboard!");
})
.catch(() => {
alert("Failed to copy to clipboard!");
});
};
const appInfo = {
name: "Edi",
image: "/edi512x512_2.png",
manager: "Lumen",
about:
"Edi is a DDR score tracking app that offers a direct connection to your PhaseII account.",
intents: ["user", "account", "webhook_scores"],
webhooks: ["scores"],
useOAuth: true,
internal: false,
};
</script>
<template>
<LayoutAuthenticated>
<SectionMain>
<UserCard class="mb-6" use-small even-smaller />
<SectionTitleLine :icon="PhCode" title="Developer Portal" main />
<CardBox class="row-span-2 mb-6">
<h1 class="text-xl md:text-2xl">
Welcome to the <samp>PhaseII</samp> Developer Portal!
</h1>
<h2 class="text-lg">You're gonna do great 😄</h2>
<p>
If you're just now getting started, please read the
<a
class="text-blue-400 hover:text-blue-600"
:href="`${DOCS_URL}/developer`"
target="_blank"
>
developer docs
</a>
</p>
<hr class="border-r my-2 w-full" />
<p>Create or manage your applications below</p>
</CardBox>
<SectionTitleLine :icon="PhCloud" title="My Applications" main>
<BaseButton
label="Add Application"
color="success"
to="/developer/register"
/>
</SectionTitleLine>
<CardBox class="row-span-2 mb-6">
<div class="flex">
<div
class="flex flex-col justify-center p-4 rounded-xl bg-slate-700 hover:drop-shadow-xl transition-all"
>
<div class="w-full flex justify-center">
<img
:src="appInfo.image"
width="75"
class="rounded-full shadow-lg mb-2"
/>
</div>
<div class="mb-2 text-center">
<h1 class="text-lg md:text-xl font-bold">
{{ appInfo.name }}
</h1>
<h2 v-if="appInfo.useOAuth">OAuth Enabled</h2>
<h2>{{ appInfo.webhooks?.length || 0 }} Webhook(s)</h2>
</div>
</div>
</div>
</CardBox>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@ -0,0 +1,344 @@
<script setup>
import { reactive } from "vue";
import {
PhCloudArrowUp,
PhFileImage,
PhKey,
PhLinkSimple,
PhStorefront,
PhTextbox,
PhWebhooksLogo,
} from "@phosphor-icons/vue";
import SectionMain from "@/components/SectionMain.vue";
import UserCard from "@/components/UserCard.vue";
import PillTag from "@/components/PillTag.vue";
import CardBox from "@/components/CardBox.vue";
import FormField from "@/components/FormField.vue";
import FormCheckRadio from "@/components/FormCheckRadio.vue";
import FormFilePicker from "@/components/FormFilePicker.vue";
import FormControl from "@/components/FormControl.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionTitleLine from "@/components/SectionTitleLine.vue";
import BaseButton from "@/components/BaseButton.vue";
import { useMainStore } from "@/stores/main";
import { applicationIntents, applicationWebhooks } from "@/constants/developer";
const mainStore = useMainStore();
const appForm = reactive({
name: "",
about: "",
image: null,
useOAuth: false,
callbackUri: null,
intents: [],
useWebhooks: false,
webhookList: [],
});
const initWebhook = {
type: "network.news",
name: "",
endpoint: "",
authKey: "",
fields: {},
};
const newWebhook = reactive({
...initWebhook,
});
const sortedArcades = mainStore.userArcades.map((arcade) => ({
label: arcade.name,
id: arcade.id,
}));
function isValidUrl(value, { requireHttps = true } = {}) {
if (!value || typeof value !== "string") return false;
try {
const url = new URL(value);
if (!["http:", "https:"].includes(url.protocol)) return false;
if (requireHttps && url.protocol !== "https:") return false;
return true;
} catch {
return false;
}
}
function isValidAuthKey(key) {
return typeof key === "string" && key.length >= 40;
}
async function addWebhook() {
const tmpWebhook = JSON.parse(
JSON.stringify({
type: newWebhook.type,
name: newWebhook.name,
endpoint: newWebhook.endpoint,
authKey: newWebhook.authKey,
fields: newWebhook.fields,
}),
);
if (!tmpWebhook.name?.trim()) {
return alert("Webhook name is required.");
}
if (!isValidUrl(tmpWebhook.endpoint)) {
return alert("Webhook endpoint must be a valid HTTPS URL.");
}
if (!isValidAuthKey(tmpWebhook.authKey)) {
return alert("Auth key must be at least 40 characters.");
}
const webhookMeta = applicationWebhooks.find((w) => w.id === tmpWebhook.type);
if (webhookMeta?.fields?.includes("arcadeId")) {
if (!tmpWebhook.fields.arcadeId) {
return alert("Arcade selection is required.");
}
} else {
tmpWebhook.fields = {};
}
appForm.webhookList.push(tmpWebhook);
Object.assign(newWebhook, initWebhook);
}
function removeWebhook(authKey) {
appForm.webhookList = appForm.webhookList.filter(
(webhook) => webhook.authKey !== authKey,
);
}
async function requestApp() {
}
</script>
<template>
<LayoutAuthenticated>
<SectionMain>
<UserCard class="mb-6" use-small even-smaller />
<SectionTitleLine
:icon="PhCloudArrowUp"
title="Create an Application"
main
/>
<CardBox class="row-span-2 mb-6">
<PillTag color="info" label="General" class="mb-2" />
<FormField label="Name">
<FormControl
v-model="appForm.name"
:icon="PhTextbox"
name="name"
required
/>
</FormField>
<FormField label="About">
<FormControl
v-model="appForm.about"
name="about"
type="textarea"
required
/>
</FormField>
<FormField label="App Logo">
<FormFilePicker
v-model="appForm.image"
label="Image Upload"
accept="image/*"
:icon="PhFileImage"
/>
</FormField>
</CardBox>
<CardBox class="row-span-2 mb-6">
<PillTag color="warning" label="OAuth2.0" class="mb-2" />
<FormField label="Enable OAuth">
<FormCheckRadio
v-model="appForm.useOAuth"
name="useOAuth"
:model-value="appForm.useOAuth"
:input-value="appForm.useOAuth"
type="switch"
/>
</FormField>
<template v-if="appForm.useOAuth">
<FormField
label="Callback URI"
help="The URL that the user will be redirected to from the authorization page (ex: input of https://mycoolapp.my.domain/callback/url will redirect user to https://address/callback/url?code= )"
>
<FormControl
v-model="appForm.callbackUri"
:icon="PhLinkSimple"
name="callbackUri"
placeholder="https://my.domain/callback/url"
required
/>
</FormField>
<FormField
label="Intents"
help="Select what you'd like your app to do"
>
<template v-for="intent in applicationIntents" :key="intent.id">
<div class="grid md:flex justify-between">
<FormCheckRadio
v-model="appForm.intents"
:name="'intents'"
:label="intent.label"
:input-value="intent.id"
type="checkbox"
/>
<span
class="text-slate-400 hover:text-slate-200 font-thin transition-all delay-0"
>
{{ intent.tip ?? "" }}
</span>
</div>
<hr class="border-r my-3 w-full" />
</template>
</FormField>
</template>
</CardBox>
<CardBox class="row-span-2 mb-6">
<PillTag color="success" label="Webhooks" class="mb-2" />
<FormField
label="Enable Application Webhooks"
help="These are different from user webhooks!"
>
<FormCheckRadio
v-model="appForm.useWebhooks"
name="useWebhooks"
:model-value="appForm.useWebhooks"
:input-value="appForm.useWebhooks"
type="switch"
/>
</FormField>
<template v-if="appForm.useWebhooks">
<div class="grid md:grid-cols-2 gap-6">
<form class="h-full" @submit.prevent="addWebhook()">
<PillTag color="info" label="New Webhook" class="mb-4" />
<FormField
label="Webhook Type"
:help="
applicationWebhooks.find(
(webhook) => webhook.id === newWebhook.type,
)?.tip
"
>
<FormControl
v-model="newWebhook.type"
name="type"
:icon="PhWebhooksLogo"
:options="applicationWebhooks"
required
/>
</FormField>
<FormField label="Name">
<FormControl
v-model="newWebhook.name"
name="name"
:icon="PhTextbox"
required
/>
</FormField>
<FormField
label="API Endpoint"
help="The URL that the Webhook server will contact"
>
<FormControl
v-model="newWebhook.endpoint"
name="name"
placeholder="https://my.domain/endpoint/url"
:icon="PhLinkSimple"
required
/>
</FormField>
<FormField
label="API Auth Key"
help="Sent as headers x-api-key, x-webhook-key, authorization"
>
<FormControl
v-model="newWebhook.authKey"
name="authKey"
:icon="PhKey"
required
:minlength="40"
/>
</FormField>
<template
v-for="field of applicationWebhooks.find(
(webhookData) => webhookData.id === newWebhook.type,
)?.fields"
>
<template v-if="field === 'arcadeId'">
<FormField
label="Arcade"
help="The arcade that this Webhook will track"
>
<FormControl
v-model="newWebhook.fields.arcadeId"
name="arcadeId"
:icon="PhStorefront"
:options="sortedArcades"
required
/>
</FormField>
</template>
</template>
<BaseButton color="success" type="submit" label="Add Webhook" />
</form>
<div>
<PillTag color="warning" label="Queue" class="mb-4" />
<div class="grid gap-4">
<CardBox
v-for="webhook of appForm.webhookList"
:key="webhook.type"
color-prop="bg-slate-800 dark:bg-slate-800"
>
<div class="flex justify-between items-center">
<div class="m-[-5px]">
<PillTag
color="info"
:label="webhook.type"
class="mb-4"
small
/>
<h1 class="text-md font-bold">{{ webhook.name }}</h1>
<samp
class="text-sm font-mono p-1 bg-slate-900 rounded-lg"
>
{{ webhook.endpoint }}
</samp>
<h2
v-for="(value, field) of webhook.fields"
class="text-sm font-mono mt-2"
>
{{ field }}: {{ value }}
</h2>
</div>
<BaseButton
color="danger"
label="Remove"
@click="removeWebhook(webhook.authKey)"
/>
</div>
</CardBox>
</div>
</div>
</div>
</template>
</CardBox>
<BaseButton
:small="false"
label="Submit"
color="success"
@click="console.log(appForm)"
/>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@ -20,9 +20,7 @@ import { getGameInfo } from "@/constants";
import { dashCode } from "@/constants/userData";
import { getIIDXDan } from "@/constants/danClass";
import { formatSortableDate } from "@/constants/date";
import { useMainStore } from "@/stores/main";
const mainStore = useMainStore();
const $route = useRoute();
const $router = useRouter();
var gameID = null;
@ -44,15 +42,9 @@ const versionForm = reactive({
watch(
() => versionForm.currentVersion,
() => {
mainStore.continueLoad = true;
mainStore.isLoading = true;
mainStore.activeRequests = 1;
loadGame(versionForm.currentVersion, profiles.length != 0);
loadProfile();
musicIds.value = [];
mainStore.continueLoad = false;
mainStore.isLoading = false;
mainStore.activeRequests = 0;
},
);
@ -69,12 +61,8 @@ if (thisGame == null) {
}
onMounted(async () => {
mainStore.continueLoad = true;
await loadProfile();
await loadGame(null, profiles.length == 0);
mainStore.continueLoad = false;
mainStore.isLoading = false;
mainStore.activeRequests = 0;
});
async function loadGame(version, noUsers) {

View File

@ -147,6 +147,18 @@ async function revert() {
/>
</FormField>
<FormField
label="Hide QuickLinks"
help="Turns off the buttons in the navbar"
>
<FormCheckRadio
v-model="userCustomize.hideQuickLinks"
type="switch"
:input-value="userCustomize.hideQuickLinks ?? false"
name="hideQuickLinks"
/>
</FormField>
<div class="space-x-2">
<BaseButton type="submit" color="success" label="Save" />
<BaseButton