diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9d1e4fd --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +.PHONY: all run runC build buildS buildC clean + +all: clean build run + +run: + @-node server/app.js + +runC: + @-npm run dev --prefix client + +build: buildC buildS + +buildC: + @-npm i --prefix client + @-npm run build --prefix client + +buildS: + @-npm i --prefix server + +clean: + @-rm -r client/dist + @-rm -r client/package-lock.json + @-rm -r client/node_modules + @-rm -r server/node_modules \ No newline at end of file diff --git a/client/README.md b/client/README.md index 7438bd5..dc075bf 100644 --- a/client/README.md +++ b/client/README.md @@ -1,8 +1,8 @@ -ZoomZoom Type - -An ExpressJS Typing Game - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +ZoomZoom Type + +An ExpressJS Typing Game + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/client/eslint.config.js b/client/eslint.config.js index ec2b712..5559bca 100644 --- a/client/eslint.config.js +++ b/client/eslint.config.js @@ -1,33 +1,33 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' - -export default [ - { ignores: ['dist'] }, - { - files: ['**/*.{js,jsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - parserOptions: { - ecmaVersion: 'latest', - ecmaFeatures: { jsx: true }, - sourceType: 'module', - }, - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...js.configs.recommended.rules, - ...reactHooks.configs.recommended.rules, - 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, -] +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/client/index.html b/client/index.html index efa5de9..e40bddd 100644 --- a/client/index.html +++ b/client/index.html @@ -1,12 +1,23 @@ - + ZoomZoom Type +
+ diff --git a/client/package-lock.json b/client/package-lock.json index 4a2c92f..d449ceb 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,11 +9,20 @@ "version": "0.0.0", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@popperjs/core": "^2.11.8", + "axios": "^1.9.0", + "bootstrap": "^5.3.5", + "random-words": "^2.0.1", "react": "^19.0.0", + "react-cookie": "^8.0.1", "react-dom": "^19.0.0", - "react-router-dom": "^7.4.0" + "react-router-dom": "^7.4.0", + "react-toastify": "^11.0.5", + "tailwindcss": "^4.0.17" }, "devDependencies": { "@eslint/js": "^9.21.0", @@ -24,7 +33,9 @@ "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.15.0", - "vite": "^6.2.2" + "sass": "^1.86.3", + "tailwindcss": "^4.1.3", + "vite": "^6.2.5" } }, "node_modules/@ampproject/remapping": { @@ -94,13 +105,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", - "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", "dev": true, "dependencies": { - "@babel/parser": "^7.26.10", - "@babel/types": "^7.26.10", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -110,12 +121,12 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.26.5", + "@babel/compat-data": "^7.26.8", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -192,25 +203,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", - "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, "dependencies": { - "@babel/types": "^7.26.10" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -250,30 +261,30 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz", - "integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -291,9 +302,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", - "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -303,266 +314,10 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", - "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", "cpu": [ "x64" ], @@ -575,138 +330,10 @@ "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "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.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", "dev": true, "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -743,9 +370,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, "dependencies": { "@eslint/object-schema": "^2.1.6", @@ -757,18 +384,18 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", - "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.15" @@ -813,9 +440,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", - "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", + "version": "9.25.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", + "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -831,12 +458,12 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.13.0", "levn": "^0.4.1" }, "engines": { @@ -847,7 +474,6 @@ "version": "6.7.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", - "license": "MIT", "engines": { "node": ">=6" } @@ -856,7 +482,28 @@ "version": "6.7.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", - "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz", + "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz", + "integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==", "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -868,7 +515,6 @@ "version": "6.7.2", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", - "license": "(CC-BY-4.0 AND MIT)", "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -880,7 +526,6 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", - "license": "MIT", "dependencies": { "prop-types": "^15.8.1" }, @@ -998,192 +643,95 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz", - "integrity": "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.36.0.tgz", - "integrity": "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.36.0.tgz", - "integrity": "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.36.0.tgz", - "integrity": "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.36.0.tgz", - "integrity": "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==", - "cpu": [ - "arm64" - ], + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "dev": true, + "hasInstallScript": true, "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.36.0.tgz", - "integrity": "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", "cpu": [ "x64" ], "dev": true, "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.36.0.tgz", - "integrity": "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.36.0.tgz", - "integrity": "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.36.0.tgz", - "integrity": "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.36.0.tgz", - "integrity": "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==", - "cpu": [ - "arm64" ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.36.0.tgz", - "integrity": "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.36.0.tgz", - "integrity": "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==", + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", "cpu": [ - "ppc64" + "x64" ], "dev": true, "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.36.0.tgz", - "integrity": "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==", - "cpu": [ - "riscv64" ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.36.0.tgz", - "integrity": "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.36.0.tgz", - "integrity": "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", "cpu": [ "x64" ], @@ -1194,9 +742,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.36.0.tgz", - "integrity": "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", "cpu": [ "x64" ], @@ -1206,45 +754,6 @@ "linux" ] }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.36.0.tgz", - "integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.36.0.tgz", - "integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.36.0.tgz", - "integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1259,9 +768,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "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, "dependencies": { "@babel/types": "^7.0.0" @@ -1278,25 +787,29 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" - }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1304,34 +817,33 @@ "dev": true }, "node_modules/@types/react": { - "version": "19.0.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz", - "integrity": "sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==", - "dev": true, + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", - "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", "dev": true, "peerDependencies": { "@types/react": "^19.0.0" } }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", - "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", + "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", "dev": true, "dependencies": { - "@babel/core": "^7.26.0", + "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" + "react-refresh": "^0.17.0" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -1398,12 +910,82 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bootstrap": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.5.tgz", + "integrity": "sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1414,11 +996,23 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "optional": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1446,6 +1040,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1456,9 +1062,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001706", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz", - "integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==", + "version": "1.0.30001715", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", + "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", "dev": true, "funding": [ { @@ -1491,6 +1097,29 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1509,6 +1138,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1546,8 +1186,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/debug": { "version": "4.4.0", @@ -1572,16 +1211,91 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.123", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz", - "integrity": "sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==", + "version": "1.5.141", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.141.tgz", + "integrity": "sha512-qS+qH9oqVYc1ooubTiB9l904WVyM6qNYxtOEEGReoZXw3xlqeYdFr5GclNzbkAufWgwWLEPoDi3d9MoRwwIjGw==", "dev": true }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", - "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", "dev": true, "hasInstallScript": true, "bin": { @@ -1591,38 +1305,37 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.1", - "@esbuild/android-arm": "0.25.1", - "@esbuild/android-arm64": "0.25.1", - "@esbuild/android-x64": "0.25.1", - "@esbuild/darwin-arm64": "0.25.1", - "@esbuild/darwin-x64": "0.25.1", - "@esbuild/freebsd-arm64": "0.25.1", - "@esbuild/freebsd-x64": "0.25.1", - "@esbuild/linux-arm": "0.25.1", - "@esbuild/linux-arm64": "0.25.1", - "@esbuild/linux-ia32": "0.25.1", - "@esbuild/linux-loong64": "0.25.1", - "@esbuild/linux-mips64el": "0.25.1", - "@esbuild/linux-ppc64": "0.25.1", - "@esbuild/linux-riscv64": "0.25.1", - "@esbuild/linux-s390x": "0.25.1", - "@esbuild/linux-x64": "0.25.1", - "@esbuild/netbsd-arm64": "0.25.1", - "@esbuild/netbsd-x64": "0.25.1", - "@esbuild/openbsd-arm64": "0.25.1", - "@esbuild/openbsd-x64": "0.25.1", - "@esbuild/sunos-x64": "0.25.1", - "@esbuild/win32-arm64": "0.25.1", - "@esbuild/win32-ia32": "0.25.1", - "@esbuild/win32-x64": "0.25.1" + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.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, "engines": { "node": ">=6" } @@ -1640,19 +1353,19 @@ } }, "node_modules/eslint": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", - "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", + "version": "9.25.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", + "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.23.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/js": "9.25.1", + "@eslint/plugin-kit": "^0.2.8", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -1712,9 +1425,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.19.tgz", - "integrity": "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==", + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", "dev": true, "peerDependencies": { "eslint": ">=8.40" @@ -1837,6 +1550,19 @@ "node": ">=16.0.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "optional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -1872,6 +1598,53 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, + "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==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1886,6 +1659,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -1895,6 +1676,41 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1919,6 +1735,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1928,6 +1755,50 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -1937,6 +1808,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", + "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -1983,6 +1860,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2095,7 +1982,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -2112,6 +1998,47 @@ "yallist": "^3.0.2" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2134,7 +2061,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -2154,17 +2080,31 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2252,11 +2192,23 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2271,6 +2223,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -2280,6 +2233,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2293,13 +2252,17 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2309,46 +2272,65 @@ "node": ">=6" } }, + "node_modules/random-words": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/random-words/-/random-words-2.0.1.tgz", + "integrity": "sha512-nZNJAmgcFmtJMTDDIUCm/iK4R6RydC6NvALvWhYItXQrgYGk1F7Gww416LpVROFQtfVd5TaLEf4WuSsko03N7w==", + "dependencies": { + "seedrandom": "^3.0.5" + } + }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "engines": { "node": ">=0.10.0" } }, + "node_modules/react-cookie": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-8.0.1.tgz", + "integrity": "sha512-QNdAd0MLuAiDiLcDU/2s/eyKmmfMHtjPUKJ2dZ/5CcQ9QKUium4B3o61/haq6PQl/YWFqC5PO8GvxeHKhy3GFA==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.6", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^8.0.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "dependencies": { - "scheduler": "^0.25.0" + "scheduler": "^0.26.0" }, "peerDependencies": { - "react": "^19.0.0" + "react": "^19.1.0" } }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/react-router": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.4.0.tgz", - "integrity": "sha512-Y2g5ObjkvX3VFeVt+0CIPuYd9PpgqCslG7ASSIdN73LwA1nNWzcMLaoMRJfP3prZFI92svxFwbn7XkLJ+UPQ6A==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.1.tgz", + "integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==", "dependencies": { - "@types/cookie": "^0.6.0", "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0", "turbo-stream": "2.4.0" @@ -2367,11 +2349,11 @@ } }, "node_modules/react-router-dom": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.4.0.tgz", - "integrity": "sha512-VlksBPf3n2bijPvnA7nkTsXxMAKOj+bWp4R9c3i+bnwlSOFAGOkJkKhzy/OsRkWaBMICqcAl1JDzh9ZSOze9CA==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.1.tgz", + "integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==", "dependencies": { - "react-router": "7.4.0" + "react-router": "7.5.1" }, "engines": { "node": ">=20.0.0" @@ -2381,6 +2363,31 @@ "react-dom": ">=18" } }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2391,12 +2398,12 @@ } }, "node_modules/rollup": { - "version": "4.36.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.36.0.tgz", - "integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "dev": true, "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.7" }, "bin": { "rollup": "dist/bin/rollup" @@ -2406,32 +2413,58 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.36.0", - "@rollup/rollup-android-arm64": "4.36.0", - "@rollup/rollup-darwin-arm64": "4.36.0", - "@rollup/rollup-darwin-x64": "4.36.0", - "@rollup/rollup-freebsd-arm64": "4.36.0", - "@rollup/rollup-freebsd-x64": "4.36.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.36.0", - "@rollup/rollup-linux-arm-musleabihf": "4.36.0", - "@rollup/rollup-linux-arm64-gnu": "4.36.0", - "@rollup/rollup-linux-arm64-musl": "4.36.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.36.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.36.0", - "@rollup/rollup-linux-riscv64-gnu": "4.36.0", - "@rollup/rollup-linux-s390x-gnu": "4.36.0", - "@rollup/rollup-linux-x64-gnu": "4.36.0", - "@rollup/rollup-linux-x64-musl": "4.36.0", - "@rollup/rollup-win32-arm64-msvc": "4.36.0", - "@rollup/rollup-win32-ia32-msvc": "4.36.0", - "@rollup/rollup-win32-x64-msvc": "4.36.0", + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" } }, + "node_modules/sass": { + "version": "1.87.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz", + "integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==", + "dev": true, + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, "node_modules/semver": { "version": "6.3.1", @@ -2472,7 +2505,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2501,6 +2533,73 @@ "node": ">=8" } }, + "node_modules/tailwindcss": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", + "integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==" + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "optional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.3.tgz", + "integrity": "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==", + "dev": true, + "license": "MIT" + }, "node_modules/turbo-stream": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", @@ -2518,11 +2617,18 @@ "node": ">= 0.8.0" } }, + "node_modules/universal-cookie": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-8.0.1.tgz", + "integrity": "sha512-B6ks9FLLnP1UbPPcveOidfvB9pHjP+wekP2uRYB9YDfKVpvcjKgy1W5Zj+cEXJ9KTPnqOKGfVDQBmn8/YCQfRg==", + "dependencies": { + "cookie": "^1.0.2" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2558,14 +2664,18 @@ } }, "node_modules/vite": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz", - "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==", + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", + "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", + "fdir": "^6.4.3", + "picomatch": "^4.0.2", "postcss": "^8.5.3", - "rollup": "^4.30.1" + "rollup": "^4.34.9", + "tinyglobby": "^0.2.12" }, "bin": { "vite": "bin/vite.js" @@ -2628,6 +2738,32 @@ } } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/client/package.json b/client/package.json index f6b1a67..51dd295 100644 --- a/client/package.json +++ b/client/package.json @@ -11,11 +11,20 @@ }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@popperjs/core": "^2.11.8", + "axios": "^1.9.0", + "bootstrap": "^5.3.5", + "random-words": "^2.0.1", "react": "^19.0.0", + "react-cookie": "^8.0.1", "react-dom": "^19.0.0", - "react-router-dom": "^7.4.0" + "react-router-dom": "^7.4.0", + "react-toastify": "^11.0.5", + "tailwindcss": "^4.0.17" }, "devDependencies": { "@eslint/js": "^9.21.0", @@ -26,6 +35,8 @@ "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.15.0", - "vite": "^6.2.2" + "sass": "^1.86.3", + "tailwindcss": "^4.1.3", + "vite": "^6.2.5" } } diff --git a/client/src/App.css b/client/src/App.css index 307d1e1..77c4a11 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -1,3 +1,442 @@ +/* Massive ass google font import link */ +@import url("https://fonts.googleapis.com/css2?family=Coral+Pixels&family=Galindo&family=Poiret+One&family=Space+Grotesk&family=Special+Gothic+Expanded+One&family=Style+Script&display=swap"); + +/*The color pallet for the application*/ +.theme-l5 { + color: #000 !important; + background-color: #ebf6ff !important; +} +.theme-l4 { + color: #000 !important; + background-color: #bedfff !important; +} +.theme-l3 { + color: #000 !important; + background-color: #7cc0ff !important; +} +.theme-l2 { + color: #fff !important; + background-color: #3ba0ff !important; +} +.theme-l1 { + color: #fff !important; + background-color: #0081f9 !important; +} +.theme-d1 { + color: #fff !important; + background-color: #0055a5 !important; +} +.theme-d2 { + color: #fff !important; + background-color: #004c93 !important; +} +.theme-d3 { + color: #fff !important; + background-color: #004281 !important; +} +.theme-d4 { + color: #fff !important; + background-color: #00396e !important; +} +.theme-d5 { + color: #fff !important; + background-color: #002f5c !important; +} + +.theme-accent { + color: #fff !important; + background-color: #b45f06; +} + +.theme-light { + color: #000 !important; + background-color: #ebf6ff !important; +} +.theme-dark { + color: #fff !important; + background-color: #002f5c !important; +} +.theme-action { + color: #fff !important; + background-color: #002f5c !important; +} + +.theme { + color: #fff !important; + background-color: #0060ba !important; +} +.text-theme { + color: #0060ba !important; +} +.error-text-theme { + color: #b45f06 !important; +} +.border-theme { + border-color: #0060ba !important; +} + +.hover-theme:hover { + filter: invert(1); +} +.hover-text-theme:hover { + filter: invert(1); +} +.hover-border-theme:hover { + filter: invert(1); +} + +.nav-anchor-visited:visited { + color: #fff !important; + text-decoration: none; +} +.nav-anchor-hover:hover { + filter: invert(1); + text-decoration: none; +} + +.nav-logo:hover, +:visited { + filter: invert(1); + text-decoration: none; +} +.nav-anchor-hover:hover { + filter: invert(1); + text-decoration: none; +} + +.nav-achor-hover:hover * { + filter: invert(1); +} + +.nav-logo:hover, +:visited { + color: #fff !important; +} + +.registerLink-hover:hover { + filter: invert(1); +} + +.footer-anchor-hover:hover { + filter: invert(1) !important; + text-decoration: none; +} + +.custom-accent-btn { + color: #fff !important; + background-color: #b45f06 !important; + border: none; + width: 160px; +} + +.custom-accent-btn:hover { + filter: invert(1); +} + +.custom-btn { + border: none; + width: 160px; +} + +.custom-btn:hover { + filter: invert(1); +} + +.custom-caret { + border-right: solid 1px black; +} + +@keyframes blink { + 50% { + opacity: 0; + } +} + +/* Themes for settings testing */ +.theme-blue1 { + color: white !important; + background-color: #002f5c !important; + border-color: white !important; + outline-color: white !important; +} + +.theme-green1 { + color: white !important; + background-color: #182c25 !important; + border-color: white !important; + outline-color: white !important; +} + +.theme-red1 { + color: white !important; + background-color: #de2a36 !important; + border-color: white !important; + outline-color: white !important; +} + +.theme-yellow1 { + color: black !important; + background-color: #ffee8c !important; + border-color: black !important; + outline-color: black !important; +} + +.accent-theme-blue1 { + color: white !important; + border-color: white !important; + background-color:#b45f06 !important; +} + +.btn-theme-blue1 { + color: white !important; + border-color: white !important; + background-color:#b45f06 !important; +} + +.btn-theme-blue1:hover { + color:black !important; + background-color:#c98032 !important; +} + +.accent-theme-green1 { + color: white !important; + border-color: white !important; + background-color: #306844 !important; +} + +.btn-theme-green1 { + color: white !important; + border-color: white !important; + background-color: #306844 !important; +} + +.btn-theme-green1:hover { + color: white !important; + background-color: #478b5f !important; +} + +.accent-theme-red1 { + color: black !important; + outline: black !important; + border-color: black !important; + background-color: #b4aba2 !important; +} + +.btn-theme-red1 { + color: black !important; + border-color: black !important; + background-color: #b4aba2 !important; +} + +.btn-theme-red1:hover { + color: black !important; + border-color: black !important; + background-color: #e7e0d9 !important; +} + +.accent-theme-yellow1 { + color: black !important; + border-color: black !important; + background-color: #8A804C !important; +} + +.btn-theme-yellow1 { + color: black !important; + border-color: black !important; + background-color: #8A804C !important; +} + +.btn-theme-yellow1:hover { + color: white !important; + border-color: white !important; + background-color: #615A35 !important; +} + +.icon-theme-blue1 { + color: white; +} + +.icon-theme-green1 { + color: white; +} + +.icon-theme-red1 { + color: white; +} + +.icon-theme-yellow1 { + color: black; +} + +.icon-theme-blue1:hover { + color: #b45f06; +} + +.icon-theme-green1:hover { + color: #306844; +} + +.icon-theme-red1:hover { + color: #b4aba2; +} + +.icon-theme-yellow1:hover { + color: #8A804C; +} + +.special-gothic-expanded-one-regular { + font-family: "Special Gothic Expanded One", sans-serif; + font-weight: 400; + font-style: normal; +} + +.coral-pixels-regular { + font-family: "Coral Pixels", serif; + font-weight: 400; + font-style: normal; +} + +.space-grotesk { + font-family: "Space Grotesk", sans-serif; + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; +} + +.style-script-regular { + font-family: "Style Script", cursive; + font-weight: 400; + font-style: normal; +} + +.galindo-regular { + font-family: "Galindo", sans-serif; + font-weight: 400; + font-style: normal; +} + +.poiret-one-regular { + font-family: "Poiret One", sans-serif; + font-weight: 400; + font-style: normal; +} +======= +/*profle page styling*/ +.profile-page { + width: 100%; +} + +.profile-page-header { + border-bottom: 3px solid white; + margin: 5px; + padding: 5px; + min-height:60px; + display: flex; + justify-content: space-between; +} + +.profile-page-header span { + margin-top: auto; + font-weight: bold; + font-size: 40px; +} +.edit-profile-button { + background-color: #007bff; /* Blue */ + color: white; + font-weight: bold; + padding: 10px 20px; + border: 2px solid black; + border-radius: 5px; + cursor: pointer; + transition: all 0.2s ease-in-out; + box-shadow: 2px 2px 0 black; +} + +.edit-profile-button:hover { + background-color: #0056b3; /* Darker blue on hover */ + transform: translate(-1px, -1px); + box-shadow: 3px 3px 0 black; +} + + +.profile-page-summary { + border-bottom: 3px solid white; + margin: 5px; + padding: 5px; + min-height: 100px; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 20px; + +} + +.profile-page-image { + width: 20%; +} + +.profile-page-summary-item { + background-color: #007bff; /* Blue */ + color: white; + font-weight: bold; + border: 2px solid black; + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + color: white; + height: 100px; + width: 100px; + text-align: center; +} + +.profile-page-details { + margin: 5px; + padding: 10px; + min-height: 100px; + border-radius: 10px; + overflow-x: auto; +} + +.profile-page-details span { + font-weight: bold; + margin-bottom: 10px; + display: inline-block; + margin-left: 5px; +} + +.profile-page-details table { + width: 100%; + border-collapse: collapse; + background-color: white; + border-radius: 8px; + overflow: hidden; + font-family: sans-serif; + font-size: 14px; +} + +.profile-page-details th, +.profile-page-details td { + padding: 10px; + text-align: center; + border-bottom: 1px solid #ddd; + color: black; +} + +.profile-page-details th { + background-color: #f4f4f4; + color: #333; + font-weight: bold; +} + +.profile-page-details tr:nth-child(even) { + background-color: #f9f9f9; +} + +.profile-page-details tr:hover { + background-color: #e0f7fa; +} +======= @tailwind base; @tailwind components; @tailwind utilities; @@ -27,4 +466,4 @@ .hover-border-theme:hover {border-color:#0060ba !important} .nav-anchor-visited:visited {color: #fff !important;} -.nav-anchor-hover:hover {color:#0060ba !important} \ No newline at end of file +.nav-anchor-hover:hover {color:#0060ba !important} diff --git a/client/src/App.jsx b/client/src/App.jsx index 0328b86..0d23ac4 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,25 +1,83 @@ -import { useState } from 'react' -import './App.css' +import { useState } from "react"; +import "./App.css"; import { Routes, Route } from "react-router-dom"; +import ClassicGame from "./components/ClassicGame.jsx"; +import QuoteGame from "./components/QuoteGame.jsx"; +import Settings from "./components/Settings.jsx"; +import GameModes from "./components/GameModes.jsx"; +//add imports here +//add imports here +import Footer from "./components/Footer.jsx"; +import { useCookies } from 'react-cookie'; import Home from './components/Home.jsx'; import NavBar from './components/NavBar.jsx'; import FaQ from './components/FaQ.jsx'; +import About from './components/About.jsx'; +import ProfilePage from './components/ProfilePage.jsx'; +import EditProfile from './components/EditProfile.jsx'; +import GamePage from './components/GamePage.jsx'; import Register from './components/Register.jsx'; +//Importing Bootstrap +import "bootstrap/dist/css/bootstrap.min.css"; +import "bootstrap/dist/js/bootstrap.bundle.min"; + +// Importing FontAwesome to allow for icons + +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faCarOn, faCrown, faKeyboard, faCircleInfo, faGear, faCircleUser } from "@fortawesome/free-solid-svg-icons"; +import { faGithub } from "@fortawesome/free-brands-svg-icons"; +import LogInPage from "./components/loginPage.jsx"; +import Leaderboard from "./components/LeaderBoard.jsx"; + +//Adding in the icons +library.add(faCarOn, faCrown, faKeyboard, faCircleInfo, faGear, faCircleUser, faGithub); + function App() { + //Settings States + const [theme, setTheme] = useState("theme-blue1"); + const [font, setFont] = useState("Arial"); + + //Adding the avg WPM state + const [avgWPM, setAvgWPM] = useState(0); + + //Adding in the cookie to handle user login state + const [cookie, setCookies, deleteCookie] = useCookies(["usr"]); return ( -
- - - - }/> - }/> - }/> - }/> - +
+ +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + }/> + + {/* Put the routes from Main into comments incase something breaks */} + {/* }/> */} + {/* }/> */} + {/* }/> */} + {/* }/> */} + {/* }/> */} + {/* }/> */} + {/* }/> */} + {/* }/> */} + {/* } /> */} + +
+
+
- ) + ); } -export default App +export default App; diff --git a/client/src/assets/logo.png b/client/src/assets/logo.png new file mode 100644 index 0000000..009c3ca Binary files /dev/null and b/client/src/assets/logo.png differ diff --git a/client/src/assets/sounds/dry-fart.mp3 b/client/src/assets/sounds/dry-fart.mp3 new file mode 100644 index 0000000..8092a40 Binary files /dev/null and b/client/src/assets/sounds/dry-fart.mp3 differ diff --git a/client/src/assets/sounds/oof.mp3 b/client/src/assets/sounds/oof.mp3 new file mode 100644 index 0000000..5bab911 Binary files /dev/null and b/client/src/assets/sounds/oof.mp3 differ diff --git a/client/src/assets/sounds/osu-hit-sound.mp3 b/client/src/assets/sounds/osu-hit-sound.mp3 new file mode 100644 index 0000000..e56f31d Binary files /dev/null and b/client/src/assets/sounds/osu-hit-sound.mp3 differ diff --git a/client/src/assets/sounds/slam.mp3 b/client/src/assets/sounds/slam.mp3 new file mode 100644 index 0000000..81bd3cb Binary files /dev/null and b/client/src/assets/sounds/slam.mp3 differ diff --git a/client/src/assets/sounds/vine-boom-sound-effect-full.mp3 b/client/src/assets/sounds/vine-boom-sound-effect-full.mp3 new file mode 100644 index 0000000..e832b67 Binary files /dev/null and b/client/src/assets/sounds/vine-boom-sound-effect-full.mp3 differ diff --git a/client/src/assets/sounds/wii-keyboard-click-3.mp3 b/client/src/assets/sounds/wii-keyboard-click-3.mp3 new file mode 100644 index 0000000..5e98d18 Binary files /dev/null and b/client/src/assets/sounds/wii-keyboard-click-3.mp3 differ diff --git a/client/src/components/About.jsx b/client/src/components/About.jsx index 024bf96..e511b5e 100644 --- a/client/src/components/About.jsx +++ b/client/src/components/About.jsx @@ -1,80 +1,86 @@ import React, { useEffect, useState, useRef } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import {FontAwesomeIcon} from "@fontawesome/react-fontawesome" +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" +import 'bootstrap/dist/css/bootstrap.min.css'; +import reactLogo from '../assets/logo.png' const About = () => { return ( - -
- {/* This will hold the logo and the Title */} -
- -

Zoom Zoom Type

+
+ + { /* About page header*/ } +
+

About Us

+

ZoomZoomType

+
+ -
- {/* Add the other items to the bar */} - - - -

Average WPM:

-
+ { /* About our group, how the idea started etc. */ } +
+
+ {/* Image on the left */} +
+
+ Logo +
+
-
- {/* Settings and Profile Icons on the far right */} - - + + {/* Text on the right — restored to original column with padding */} +
+
+

+ ZoomZoomType is an interactive web-based typing game + designed to challenge users to improve their typing + speed and accuracy. Players can race against the + clock — or against each other for the top spot — by typing given + passages as quickly and precisely as possible. + Built for both casual users looking to sharpen their + skills and competitive players aiming for the top of + the leaderboard, ZoomZoomType brings a fun, dynamic, + and fast-paced experience to the world of online typing + games. +

+
+
+
- - - + + - -
-

About Us

-

ZoomZoomType

-
+ { /* Another text section? */ } +
+
+
+

+ ZoomZoomType tracks detailed performance statistics like words + per minute (WPM) and accuracy, giving players a way to monitor + their progress and climb leaderboards. Built with a focus on + fast-paced gameplay and + community-driven competition. Whether your aiming for a + personal best or battling it out for the top spot, it + offers an engaging, competitive, and motivating environment. -

-

- Text section regarding what we do as a group, - how we started the idea, yadda yadda yadda
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. - Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia - deserunt mollit anim id est laborum. -

-
- -
-
- Picture detailing service offered -
- -
-

- Another text section regarding what we do as a group, - how we started the idea, yadda yadda yadda
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. - Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia - deserunt mollit anim id est laborum. + ZoomZoomType was created as a collaborative group project for + our Human Interfce Computing course. Our team worked closely + together throught the planning, design, and development phases, + applying best practices in full-stack development, version control, + and agile methodology.

-
+
- ) } -export default About; \ No newline at end of file +export default About; diff --git a/client/src/components/ClassicGame.jsx b/client/src/components/ClassicGame.jsx new file mode 100644 index 0000000..47e38a6 --- /dev/null +++ b/client/src/components/ClassicGame.jsx @@ -0,0 +1,399 @@ +import React, { useState, useEffect, useRef, useMemo } from "react"; +import { useNavigate } from "react-router-dom"; +import { generate, count } from "random-words"; +import axios from "axios"; + +const NUMB_OF_WORD = 170; +const SECONDS = 60; + +const ClassicGame = ({ cookie, theme }) => { + //used placeholder for future gamemodes + const [mode, setMode] = useState("Random Lowercase Words"); + //place holder for ability to set time + const [time, setTime] = useState(60); + //future calculated wpm + const [wpm, setWpm] = useState(0); + //const [targetText, setTargetText] = useState("Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos"); + const [words, setWords] = useState([]); + //text user types + const [typedWord, setTypedWord] = useState(""); + //Game status + const [gameStatus, setGameStatus] = useState(false); + //IsTextGenerated? + const [textGenerated, setTextGenerated] = useState(true); + //iterator for tracking which word user is at in word array + const [it, setIt] = useState(0); + //correct characters typed + const [correctChars, setCorrectChars] = useState(0); + //defining input ref so when game starts, you can automatically type on keyboard without clicking it + const inputRef = useRef(null); + //current word iterator + const [currIt, setCurrIt] = useState(0); + //state for words per line so we can have dynamic screen sizes + const [wordsPerLine, setWordsPerLine] = useState(12); + //state for how many characters per line can exist for the purposes of resizing screen dynamically + const [charactersPerLine, setCharactersPerLine] = useState(0); + //reference for container containing typed words for the purposes of reszizing + const typingContainerRef = useRef(null); + //char reference used for calculating character width which is using for the purposes of resizing typing container + const charRef = useRef(null); + //state fortracking final wpm, never gets set to zero + const finalWpmRef = useRef(0); + + //Auxiliary functions to help with game + function generateWords() { + //generate list of words + let tempWords = generate(NUMB_OF_WORD); + //add a space to every word except for the last one + for (let i = 0; i < tempWords.length - 1; i++) { + tempWords[i] = tempWords[i] + " "; + } + setWords(tempWords); + } + + //got this from chatgpt + function formatTime(time) { + const minutes = Math.floor(time / 60); + const seconds = time % 60; + return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; + } + + const navigate = useNavigate(); + function handleClick(page) { + navigate(page); + } + + //handles starting the game from the start game button + function start() { + setGameStatus(true); + } + + async function postGameData(finalWpm) { + console.log("posting game data"); + //get user information + let userData = cookie.usr; + //store data in object + const data = { + userID: userData.userID, + wpm: finalWpm, + time: 60, + mode: 1, + }; + //post + try { + await axios.post("http://localhost:3000/api/postNewGame", data); + } catch (e) { + console.log("Error posting game data"); + } + } + + //checks if typed input matches current word + function handleTypedInput(event) { + //grab typed input + let typedPhrase = event.target.value; + + //add character to typed word + setTypedWord(typedPhrase); + if (typedPhrase === words[currIt]) { + //if game is running, allow characters to be recorded + if (gameStatus === true) { + //get correct characters typed so far + let charsTyped = correctChars; + //add current typedPhrase length to charsTyped + charsTyped += typedPhrase.length; + //set chars typed. This is used to calculate words per minute + setCorrectChars(charsTyped); + } + //increment the word + setCurrIt(currIt + 1); + //set input word to nothing + setTypedWord(""); + } + } + + //calculate words per minute which is characters per second divided by 5 + function calWPM(numChars, time) { + //convert characters to words + let words_ = numChars / 5; + //calculate wpm if + if (SECONDS - time != 0) { + //calculate seconds elapsed + let secondsElapsed = SECONDS - time; + //convert secondsElapsed to minutes elapsed + let minutesElapsed = secondsElapsed / 60; + //return wpm which is words/minutes + return words_ / minutesElapsed; + } + //return 0 if no time has passed + return 0; + } + + //useEffect hooks for game logic + //This hook generates initial text when page first loads + useEffect(() => { + generateWords(); + }, []); + + //This hook is responsible for generating text when the game starts and resetting it + useEffect(() => { + //checks if gameStatus is true and runs the text generation if it is true + if (gameStatus === true) { + //set text counter to 0 + setIt(0); + //set currIt counter to 0 + setCurrIt(0); + //set typed word to nothing + setTypedWord(""); + //if text is not generated, generate the text + if (textGenerated === false) { + generateWords(); + setTextGenerated(true); + } + //set wpm + setWpm(0); + //set counted characters + setCorrectChars(0); + } + //auto focus on input bar so user doesn't have to manually click it after game starts + if (gameStatus === true && inputRef.current) { + inputRef.current.focus(); + } + + //hook runs when game Status updates + }, [gameStatus]); + + //home responsible for managing the timer --will merge with first hook + //will also post scores when game ends + useEffect(() => { + if (gameStatus === true) { + //set time for timer to value of global variable SECONDS + let tempTime = SECONDS; + //set time to temp time before timer runs + setTime(tempTime); + //set timer that updates code roughly every second + const timer = setInterval(() => { + //decrement timer + tempTime = tempTime - 1; + //when timer hits zero, game is over + if (tempTime <= 0) { + //get final wpm + //disable the timer + clearInterval(timer); + //set gameStatus to false + setGameStatus(false); + //set textGenerated to false so new text can generate + setTextGenerated(false); + //get wpm + const finalWpm_ = finalWpmRef.current; + //check if user is logged in + if (cookie.usr) { + postGameData(finalWpm_).then(() => {}); + } + } + //set time to new time + setTime(tempTime); + }, 1000); + } + }, [gameStatus]); + + //useEffect loop for updating words per minute + useEffect(() => { + let tmpCorrectChars = correctChars; + let tmpWpm = calWPM(tmpCorrectChars, time); + if (tmpWpm != 0) { + finalWpmRef.current = Math.round(tmpWpm); + } + setWpm(Math.round(tmpWpm)); + //useeffect function runs when correctChars changes + }, [correctChars]); + + const handleResize = () => { + if (!typingContainerRef.current || !charRef.current) { + console.log("One or both refs are null"); + return; + } + + //get container padding + const containerElement = typingContainerRef.current; + const containerStyles = window.getComputedStyle(containerElement); + const containerPadding = parseFloat(containerStyles.paddingLeft) + parseFloat(containerStyles.paddingRight); + + //get the container width + const containerWidth = typingContainerRef.current.offsetWidth - containerPadding; + + //get the character width + const characterWidth = charRef.current.offsetWidth; + //calculate how many characters can fit within container + const charAmount = Math.floor(containerWidth / characterWidth); + //set characters per line + setCharactersPerLine(charAmount); + }; + + //use effect for handling screensize + useEffect(() => { + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + useEffect(() => { + handleResize(); // Run on initial render + }, []); + + useEffect(() => { + if (gameStatus) { + setTimeout(() => { + handleResize(); // Run after the game starts + }, 0); + } + }, [gameStatus]); + + //function for rendering the text, will be same across all games + const renderGame = useMemo(() => { + if (charactersPerLine === 0) { + return []; + } + //used to track indexes of all characters from 0 - n characters + let charIndex = 0; + //tracks the starting position of the current word we are typing + let currWordIndex = 0; + //set curr word index - must happen only once + let setCurrWordIndex = false; + //flag to mark all words typed after incorrect character red + let incorrectCharFound = false; + //current line + let line = []; + //set of lines + let lines = []; + + //grab characters per line + let charsPerLine = charactersPerLine; + let charsPerLineSoFar = 0; + let wordsOnCurrentLine = 0; + let visibleWords = words.slice(it, it + 85); + //unique keys for letters in lines. Coutns number of characters total + let k = 0; + + //set it forward if needed + if (currIt >= it + wordsPerLine) { + setIt(currIt); + } + + //iterate through all words from iterator marker onwards + let wordIt = 0; + while (wordIt < visibleWords.length && lines.length < 4) { + if (charsPerLineSoFar + visibleWords[wordIt].length <= charsPerLine) { + //add word length to charsPerLineSoFar + charsPerLineSoFar += visibleWords[wordIt].length; + //add every letter to line through rendering logic + for (let letterIt = 0; letterIt < visibleWords[wordIt].length; letterIt++) { + //rendering logic + //default rendering styles + let styling = {}; + let charSpan = ; + //if word has already been typed + if (wordIt + it < currIt) { + styling = { color: "white" }; + charSpan = ( + + {visibleWords[wordIt][letterIt]} + + ); + } else { + if (wordIt + it === currIt) { + if (setCurrWordIndex === false) { + currWordIndex = k; + setCurrWordIndex = true; + } + } + + //check if character has been typed + if (k - currWordIndex < typedWord.length) { + //if it has been typed, check if correct, if correct make it white + if (visibleWords[wordIt][letterIt] === typedWord[letterIt] && incorrectCharFound === false) { + styling = { color: "white", position: "relative" }; + } else { + styling = { color: "red", position: "relative" }; + incorrectCharFound = true; + } + } else { + styling = { color: "grey", position: "relative" }; + } + + //check whether to print cursor or not + if (k - currWordIndex === typedWord.length) { + charSpan = ( + + {visibleWords[wordIt][letterIt]} + + + ); + } else { + charSpan = ( + + {visibleWords[wordIt][letterIt]} + + ); + } + } + line.push(charSpan); + //increment unique key + k++; + } + //increment wordIt; + wordIt++; + //increment words on current line + wordsOnCurrentLine++; + } else { + //push the line of words into lines + lines.push(
{line}
); + //reset charsperlinesofar and line and wordsOnCurrentline + if (lines.length == 1) { + setWordsPerLine(wordsOnCurrentLine); + } + charsPerLineSoFar = 0; + line = []; + wordsOnCurrentLine = 0; + } + } + + return lines; + }, [words, it, typedWord, wordsPerLine, charactersPerLine]); + + return ( +
+
+
Mode: {mode}
+
Time: {formatTime(time)}
+
wpm: {wpm}
+
+ {/*Conditionally render game is game is running or not*/} + {gameStatus && ( +
+
+ a +
+
{renderGame}
+
+ )} + {gameStatus && handleTypedInput(event)} />} +
+ {gameStatus === false && ()} + +
+ {/*Hidden character reference used to calculating width of a character*/} +
+ ); +}; + +export default ClassicGame; diff --git a/client/src/components/EditProfile.jsx b/client/src/components/EditProfile.jsx new file mode 100644 index 0000000..99d12f8 --- /dev/null +++ b/client/src/components/EditProfile.jsx @@ -0,0 +1,191 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { useNavigate, useLocation, Navigate } from 'react-router-dom'; +import axios from 'axios'; + +//Importing what is needed to get the cookie +import { useCookies} from 'react-cookie'; + +async function gamePlayedDictExampleFunction(cookie) { + //Here is how you use the gamesPlayed endpoint to get overall wpm and games played + + //Make a variable to hold the returning dictionary + let gamesPlayedDict = null; + + console.log(`gamePlayedDictExampleFunction: cookie.userID: ${cookie.userID}`) + + //Put the axios call into an try/catch ==> Axios call needs to be in try catch + try { + gamesPlayedDict = await axios.get("http://localhost:3000/api/gamesPlayed", {params: {ID: cookie.userID}}) + } catch (e) { + console.log(`gamesPlayed API endpoint: ${e}`) + } + + //Print the values here + console.log(`gamePlayedDictExampleFunction: ${gamesPlayedDict.data}`); + console.log(`gamePlayedDictExampleFunction: gamesPlayed ${gamesPlayedDict.data.gamesPlayed}`); + console.log(`gamePlayedDictExampleFunction: wpmTotal ${gamesPlayedDict.data.wpmTotal}`); + + return gamesPlayedDict.data + +} + +const EditProfilePage = ({setWPM, deleteCookie}) => { + //Grab the cookie + const cookie = (useCookies(['usr'])[0]).usr; + + //Use the useNaivgate, allows for redirection + const navigate = useNavigate(); + + + //Use a state to handle new email update + const [newEmail, setNewEmail] = useState(""); + + const [newUsername, setUsername] = useState(""); + + const [newPassword, setPassword] = useState(""); + + + const [gamesPlayed, setGamesPlayed] = useState(0); + const [wpmTotal, setWpmTotal] = useState(0); + + + useEffect(() => { + if (cookie?.userID) { + gamePlayedDictExampleFunction(cookie).then(data => { + setGamesPlayed(data.gamesPlayed); + setWpmTotal(data.wpmTotal); + }); + } + }, [cookie]); // call once on mount or if cookie changes + + //Handles the email update + function handleEmailUpdateChange(e) { + setNewEmail(e.target.value); + } + + //Handles the username update + function handleUsernameUpdateChange(e) { + setUsername(e.target.value); + } + + //Handles the password update + function handlePasswordUpdateChange(e) { + setPassword(e.target.value); + } + + + + const handleDeleteAccount = async (e) => { + //TEST function for the delete button back end + //Seeing if can grab cookies in the express.js backend + //Have to send the cookie over + + //THIS IS HOW YOU DELETE AN ACCOUNT, this is tied to an button currently + + //Print the cookie out + console.log("The cookie grabbed: ", cookie); + console.log("The cookie ID grabbed: ", cookie.userID); + + let response = null + + try { + + response = await axios.delete("http://localhost:3000/api/deleteaccount", {data: {id: cookie.userID}}); + } catch (e) { + console.log("FAILED to reach handleDeleteAccount backend: ", e); + } + + //Now that its handled, set the state of the WPM to 0 + setWPM(0); + + + //Delete the cookie + deleteCookie('usr', {path: '/'}); + + //Redirect to the homepage + navigate('/'); + } + + + //Email Update handler example + const handleEmailUpdate = async (e) => { + //Prevent the default button behaviour + e.preventDefault(); + + console.log("Handling email update!"); + + let response = null; + + try { + response = await axios.post("http://localhost:3000/api/updateEmail", {id: cookie.userID, email: newEmail}); + } catch (e) { + console.log(`handleEmailUpdate: POST error: ${e}`); + } + + } + + //Username update handler example + const handleUsernameUpdate = async (e) => { + //Prevent the default + e.preventDefault(); + + console.log("Handling username update!"); + + let response = null; + + try { + response = await axios.post("http://localhost:3000/api/updateUsername", {id: cookie.userID, username: newUsername}); + } catch (e) { + console.log(`handleEmailUpdate: POST error: ${e}`); + } + } + + + //handle the Password update click + const handlePasswordUpdate = async (e) => { + //Prevent the default + e.preventDefault(); + + console.log("Handling password update!"); + + let response = null; + + try { + response = await axios.post("http://localhost:3000/api/updatePassword", {id: cookie.userID, password: newPassword}); + } catch (e) { + console.log(`handlePasswordUpdate: POST error: ${e}`); + } + } + + return ( +
+ + +
+

Total Games played: {gamesPlayed}

+

Total WPM: {wpmTotal}

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +

+ + +
+ ) +} + +export default EditProfilePage; \ No newline at end of file diff --git a/client/src/components/FaQ.jsx b/client/src/components/FaQ.jsx index 9810ecb..a89fa22 100644 --- a/client/src/components/FaQ.jsx +++ b/client/src/components/FaQ.jsx @@ -1,14 +1,14 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; - -const FaQ = () => { - - - return ( -
- FaQ -
- ) -} - +import React, { useEffect, useState, useRef } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; + +const FaQ = () => { + + + return ( +
+ FaQ +
+ ) +} + export default FaQ; \ No newline at end of file diff --git a/client/src/components/Footer.jsx b/client/src/components/Footer.jsx new file mode 100644 index 0000000..3dbffc8 --- /dev/null +++ b/client/src/components/Footer.jsx @@ -0,0 +1,16 @@ +import React, { useEffect, useState, useRef } from "react"; +import { Link } from "react-router-dom"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +const Footer = ({ theme }) => { + return ( + + ); +}; + +export default Footer; diff --git a/client/src/components/GameModes.jsx b/client/src/components/GameModes.jsx new file mode 100644 index 0000000..0c1f47f --- /dev/null +++ b/client/src/components/GameModes.jsx @@ -0,0 +1,36 @@ +import React, { useEffect, useState, useRef } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; + +const GameModes = ({theme}) => { + const navigate = useNavigate(); + function handleClick(page) { + navigate(page); + } + + return ( +
+

Gamemodes

+
+ +

The original typing experience.

+
+ +
+ +

Each word disappears as you begin to type it.

+
+ +
+ +

Memorize a line at a time!

+
+ +
+ +

Quote Mode: Type famous quotes.

+
+
+ ); +}; + +export default GameModes; diff --git a/client/src/components/GamePage.jsx b/client/src/components/GamePage.jsx new file mode 100644 index 0000000..ba9bff9 --- /dev/null +++ b/client/src/components/GamePage.jsx @@ -0,0 +1,7 @@ +import React, { useEffect, useState, useRef } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; + +const GamePage = () => {} + + +export default GamePage; \ No newline at end of file diff --git a/client/src/components/Home.jsx b/client/src/components/Home.jsx index 02ae67b..6ef9fb0 100644 --- a/client/src/components/Home.jsx +++ b/client/src/components/Home.jsx @@ -1,14 +1,29 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; - -const Home = () => { +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +const Home = ({theme}) => { + const navigate = useNavigate(); + function handleClick(page){ + navigate(page); + } return ( -
- Hello World +
+

+ Zoom Zoom Type +

+

+ Hone your typing skills solo or compete against others +

+
+
+ + +
- ) -} +
+ ); +}; -export default Home; \ No newline at end of file +export default Home; diff --git a/client/src/components/LeaderBoard.jsx b/client/src/components/LeaderBoard.jsx new file mode 100644 index 0000000..b038282 --- /dev/null +++ b/client/src/components/LeaderBoard.jsx @@ -0,0 +1,181 @@ +import React, { useEffect, useState } from "react"; +import axios from "axios"; + +const Leaderboard = ({theme}) => { + const gameModes = ["Classic", "Memorize", "Quotes", "Look-Ahead"]; + const [activeTab, setActiveTab] = useState("Classic"); + const [leaderboardData, setLeaderboardData] = useState({}); + const [loading, setLoading] = useState(false); + const [selectedQuote, setSelectedQuote] = useState(""); + const [quotesList, setQuotesList] = useState([]); + + useEffect(() => { + if (activeTab === "Quotes") { + fetchQuotesList(); + if (selectedQuote) { + fetchLeaderboard(selectedQuote); + } + } else { + fetchLeaderboard(); + } + }, [activeTab]); + + useEffect(() => { + if (activeTab === "Quotes" && selectedQuote) { + fetchLeaderboard(selectedQuote); + } + }, [selectedQuote, activeTab]); + + const fetchQuotesList = async () => { + try { + const response = await axios.get("http://localhost:3000/api/quotes"); + console.log(`fetchQuotes, response: ${response.data}`); + setQuotesList(response.data); + } catch (err) { + console.error("Error fetching quotes list:", err); + } + }; + + const fetchLeaderboard = async (quoteId = "") => { + setLoading(true); + try { + const response = await axios.get("http://localhost:3000/api/leaderboard", { + params: { mode: activeTab, quote: quoteId }, + }); + const key = activeTab === "Quotes" ? `${activeTab}-${quoteId}` : activeTab; + setLeaderboardData((prev) => ({ + ...prev, + [key]: response.data, + })); + } catch (err) { + console.error(`Error fetching leaderboard for ${activeTab}:`, err); + const key = activeTab === "Quotes" ? `${activeTab}-${quoteId}` : activeTab; + setLeaderboardData((prev) => ({ + ...prev, + [key]: [], + })); + } + setLoading(false); + }; + + const renderTable = () => { + const key = activeTab === "Quotes" ? `${activeTab}-${selectedQuote}` : activeTab; + const data = leaderboardData[key] || []; + + if (loading) { + return

Loading...

; + } + + if (data.length === 0) { + return

No data available for this mode.

; + } + + const cellStyle = { + border: "1px solid white", + padding: "8px", + textAlign: "center", + }; + + return ( +
+ + + + + + + + + + + {data.slice(0, 50).map((entry, index) => ( + + + + + + + ))} + +
RankUsernameWPMTime (s)
{index + 1}{entry.userUsername}{entry.wpm}{entry.time}
+
+ ); + }; + + return ( +
+
+
+ {/* Fixed Header and Tabs */} +

Leaderboard

+ + {/* Tab Buttons */} +
+
+ {gameModes.map((mode) => ( + + ))} +
+
+ +
+ {activeTab === "Quotes" ? ( + + ) : null} +
+ + {/* Scrollable Table Container - Fixed Height */} +
+ {(activeTab !== "Quotes" || selectedQuote) && renderTable()} +
+
+
+
+ ); +}; + +export default Leaderboard; diff --git a/client/src/components/LookAheadGame.jsx b/client/src/components/LookAheadGame.jsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/MemorizeGame.jsx b/client/src/components/MemorizeGame.jsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/NavBar.jsx b/client/src/components/NavBar.jsx index ebaf43a..7f7f693 100644 --- a/client/src/components/NavBar.jsx +++ b/client/src/components/NavBar.jsx @@ -1,13 +1,34 @@ -import React, { useEffect, useState, useRef } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import React, { useEffect, useState, useRef } from "react"; +import { Link } from "react-router-dom"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import NavBarUserDropDownMenu from "./NavBarUserDropDownMenu"; -const NavBar = () => { +const NavBar = ({ wpm, setWPM, cookie, deleteCookie, theme }) => { + return ( +
+
+
+ + +
Zoom Zoom Type
+ + + + + Average WPM: 

{wpm}

+
+
+ {/* Gap is here */} - return ( -
-
- ) -} + + {/*
+ + +
*/} +
+
+ ); +}; -export default NavBar; \ No newline at end of file +export default NavBar; diff --git a/client/src/components/NavBarUserDropDownMenu.jsx b/client/src/components/NavBarUserDropDownMenu.jsx new file mode 100644 index 0000000..dc35f77 --- /dev/null +++ b/client/src/components/NavBarUserDropDownMenu.jsx @@ -0,0 +1,51 @@ +import React, { useEffect, useState, useRef } from "react"; +import { Link } from "react-router-dom"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Navigate } from "react-router"; + +function NavBarUserDropDownMenu({ isLoggedIn, deleteCookie, wpm, setWPM, theme }) { + const [showDropdown, setShowDropdown] = useState(false); + + const onLogout = () => { + deleteCookie("usr", { path: "/" }); + setShowDropdown(false); + + //set the WPM to 0 after logout + setWPM(0); + + //Redirect to homepage + return ; + }; + + return ( +
+ + + + +
+
setShowDropdown(!showDropdown)} style={{ cursor: "pointer" }}> + +
+ + {showDropdown && ( +
+ {isLoggedIn ? ( + <> + setShowDropdown(false)}>Profile + setShowDropdown(false)}>Settings + setShowDropdown(false)}>Leader Board +
+ + + ) : ( + setShowDropdown(false)}>Login + )} +
+ )} +
+
+ ); +} + +export default NavBarUserDropDownMenu; diff --git a/client/src/components/ProfilePage.jsx b/client/src/components/ProfilePage.jsx new file mode 100644 index 0000000..2ad79c5 --- /dev/null +++ b/client/src/components/ProfilePage.jsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { useNavigate, useLocation, Navigate } from 'react-router-dom'; +import axios from 'axios'; + +import { useCookies} from 'react-cookie'; + +async function getProfileData(cookie) { + + return [ + { + RaceNumber:1, + Speed: 57, + Accuracy: 96.1, + Place: 2, + Date: "3/20/2025" + }, + + { + RaceNumber:2, + Speed: 64, + Accuracy: 97.8, + Place: 4, + Date: "4/23/2025" + }, + ]; +} + +const ProfilePage = () => { + + const cookie = (useCookies(['usr'])[0]).usr; + + + + const [statData, setStatData] = useState([]); + const [fullAverage, setFullAverage] = useState(0); + const [bestRace, setBestRace] = useState(0); + const [raceCount, setRaceCount] = useState(0); + + const navigate = useNavigate(); + const handleEditOnClick = () => { + navigate('/EditProfile'); + } + + useEffect(() => { + if (cookie?.userID) { + getProfileData(cookie).then(data => { + var sData = data; + + var rCount = sData.length; + var fa = 0; + var br = 0; + + for (var i = 0; i < rCount; i++) { + fa += sData[i].Speed; + if (sData[i].Speed > br) + br = sData[i].Speed; + } + + if (rCount > 0) + fa = fa/rCount; + + setStatData(sData); + setRaceCount(rCount); + setFullAverage(fa); + setBestRace(br); + }); + } + + + }, [cookie]); + + return ( +
+
+ Your Profile + +
+
+ +
+ {fullAverage} WPM +
+ Full Avg +
+
+ {bestRace} WPM +
+ Best Race +
+
+ {raceCount} +
+ Races +
+
+
+ Your Latest Race Results + + + + + + + + + + + + { + statData.sort((i1, i2) => i2.RaceNumber - i1.RaceNumber).map((item) => ( + + + + + + + + )) + } + +
RaceSpeedAccuracyPlaceDate
{item.RaceNumber}{item.Speed}{item.Accuracy}{item.Place}{item.Date}
+
+
+ ) +} + +export default ProfilePage; \ No newline at end of file diff --git a/client/src/components/QuoteGame.jsx b/client/src/components/QuoteGame.jsx new file mode 100644 index 0000000..5e61a26 --- /dev/null +++ b/client/src/components/QuoteGame.jsx @@ -0,0 +1,454 @@ +import React, { useState, useEffect, useRef, useMemo } from "react"; +import { useNavigate } from "react-router-dom"; +import { generate, count } from "random-words"; +import axios from "axios"; + +const NUMB_OF_WORD = 170; +const SECONDS = 60; + +const QuoteGame = ({ cookie }) => { + //used placeholder for future gamemodes + const [mode, setMode] = useState("Random Quote"); + //place holder for ability to set time + const [time, setTime] = useState(0); + //future calculated wpm + const [wpm, setWpm] = useState(0); + //const [targetText, setTargetText] = useState("Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos"); + const [words, setWords] = useState([]); + //text user types + const [typedWord, setTypedWord] = useState(""); + //Game status + const [gameStatus, setGameStatus] = useState(false); + //IsTextGenerated? + const [textGenerated, setTextGenerated] = useState(true); + //iterator for tracking which word user is at in word array + const [it, setIt] = useState(0); + //correct characters typed + const [correctChars, setCorrectChars] = useState(0); + //defining input ref so when game starts, you can automatically type on keyboard without clicking it + const inputRef = useRef(null); + //current word iterator + const [currIt, setCurrIt] = useState(0); + //state for words per line so we can have dynamic screen sizes + const [wordsPerLine, setWordsPerLine] = useState(12); + //state for how many characters per line can exist for the purposes of resizing screen dynamically + const [charactersPerLine, setCharactersPerLine] = useState(0); + //reference for container containing typed words for the purposes of reszizing + const typingContainerRef = useRef(null); + //char reference used for calculating character width which is using for the purposes of resizing typing container + const charRef = useRef(null); + //state fortracking final wpm, never gets set to zero + const finalWpmRef = useRef(0); + //state for tracking when to stop timer + const endTime = useRef(false); + //state for tracking quoteID + const [quoteID, setQuoteID] = useState("none"); + + //Auxiliary functions to help with game + async function getRandomQuote() { + try { + //get the quote from the server + const response = await axios.get("http://localhost:3000/api/randomQuote"); + //turn it into text + const quote = response.data.quote; + setQuoteID(response.data.quoteID); + //parse the words + const parsedQuote = quote.split(" ").map((word, index, array) => { + return index < array.length - 1 ? word + " " : word; + }); + setWords(parsedQuote); + console.log(parsedQuote); + } catch (error) { + console.error("Error fetching random quote from server"); + //turn it into text + const quote = "The Quote does not exist but it will exist soon."; + //parse the words + const parsedQuote = quote.split(" ").map((word, index, array) => { + return index < array.length - 1 ? word + " " : word; + }); + setWords(parsedQuote); + setQuoteID(response.data.quoteID); + } + } + + //got this from chatgpt + function formatTime(time) { + const minutes = Math.floor(time / 60); + const seconds = time % 60; + return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; + } + + const navigate = useNavigate(); + function handleClick(page) { + navigate(page); + } + + //handles starting the game from the start game button + function start() { + setGameStatus(true); + endTime.current = false; + } + + async function postGameData(finalWpm) { + console.log("posting game data"); + //get user information + let userData = cookie.usr; + //store data in object + //grab time + let timeTaken = time; + const data = { + userID: userData.userID, + wpm: finalWpm, + time: timeTaken, + mode: 3, + quoteID: quoteID, + }; + + //post + try { + await axios.post("http://localhost:3000/api/postNewGame", data); + } catch (e) { + console.log("Error posting game data"); + } + } + + //checks if typed input matches current word + function handleTypedInput(event) { + //grab typed input + let typedPhrase = event.target.value; + + //add character to typed word + setTypedWord(typedPhrase); + if (typedPhrase === words[currIt]) { + //if game is running, allow characters to be recorded + if (gameStatus === true) { + //get correct characters typed so far + let charsTyped = correctChars; + //add current typedPhrase length to charsTyped + charsTyped += typedPhrase.length; + //set chars typed. This is used to calculate words per minute + setCorrectChars(charsTyped); + } + //increment the word + setCurrIt(currIt + 1); + //set input word to nothing + setTypedWord(""); + + //check if user has completed all words + if (currIt + 1 === words.length) { + //end game + endGame(); + } + } + } + + function endGame() { + setGameStatus(false); + endTime.current = true; + const finalWpm_ = finalWpmRef.current; + + if (cookie.usr) { + postGameData(finalWpm_).then(() => { + console.log("Game data posted successfully."); + }); + } + } + + //calculate words per minute which is characters per second divided by 5 + function calWPM(numChars, time) { + //convert characters to words + let words_ = numChars / 5; + //calculate wpm if + if (time != 0) { + //calculate seconds elapsed + let secondsElapsed = time; + //convert secondsElapsed to minutes elapsed + let minutesElapsed = secondsElapsed / 60; + //return wpm which is words/minutes + return words_ / minutesElapsed; + } + //return 0 if no time has passed + return 0; + } + + //useEffect hooks for game logic + //This hook generates initial text when page first loads + useEffect(() => { + getRandomQuote(); + }, []); + + //This hook is responsible for generating text when the game starts and resetting it + useEffect(() => { + //checks if gameStatus is true and runs the text generation if it is true + if (gameStatus === true) { + //set text counter to 0 + setIt(0); + //set currIt counter to 0 + setCurrIt(0); + //set typed word to nothing + setTypedWord(""); + //if text is not generated, generate the text + if (textGenerated === false) { + getRandomQuote(); + setTextGenerated(true); + } + //set wpm + setWpm(0); + //set counted characters + setCorrectChars(0); + } + //auto focus on input bar so user doesn't have to manually click it after game starts + if (gameStatus === true && inputRef.current) { + inputRef.current.focus(); + } + + //hook runs when game Status updates + }, [gameStatus]); + + //home responsible for managing the timer --will merge with first hook + //will also post scores when game ends + useEffect(() => { + if (gameStatus === true) { + //set time for timer to value of global variable SECONDS + let tempTime = 0; + //set time to temp time before timer runs + setTime(tempTime); + //set timer that updates code roughly every second + const timer = setInterval(() => { + //increment timer + tempTime = tempTime + 1; + //when timer hits zero, game is over + if (endTime.current === true) { + //disable the timer + clearInterval(timer); + + // //set gameStatus to false + // setGameStatus(false); + // //set textGenerated to false so new text can generate + // setTextGenerated(false); + // //get wpm + // const finalWpm_ = finalWpmRef.current; + // //check if user is logged in + // if(cookie.usr){ + // postGameData(finalWpm_).then(() => { + // }); + // } + } + //set time to new time + setTime(tempTime); + }, 1000); + } + }, [gameStatus]); + + //useEffect loop for updating words per minute + useEffect(() => { + let tmpCorrectChars = correctChars; + let tmpWpm = calWPM(tmpCorrectChars, time); + if (tmpWpm != 0) { + finalWpmRef.current = Math.round(tmpWpm); + } + setWpm(Math.round(tmpWpm)); + //useeffect function runs when correctChars changes + }, [correctChars]); + + const handleResize = () => { + if (!typingContainerRef.current || !charRef.current) { + console.log("One or both refs are null"); + return; + } + + //get container padding + const containerElement = typingContainerRef.current; + const containerStyles = window.getComputedStyle(containerElement); + const containerPadding = parseFloat(containerStyles.paddingLeft) + parseFloat(containerStyles.paddingRight); + + //get the container width + const containerWidth = typingContainerRef.current.offsetWidth - containerPadding; + + //get the character width + const characterWidth = charRef.current.offsetWidth; + //calculate how many characters can fit within container + const charAmount = Math.floor(containerWidth / characterWidth); + //set characters per line + setCharactersPerLine(charAmount); + }; + + //use effect for handling screensize + useEffect(() => { + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + useEffect(() => { + handleResize(); // Run on initial render + }, []); + + useEffect(() => { + if (gameStatus) { + setTimeout(() => { + handleResize(); // Run after the game starts + }, 0); + } + }, [gameStatus]); + + //function for rendering the text, will be same across all games + const renderGame = useMemo(() => { + if (charactersPerLine === 0) { + return []; + } + //used to track indexes of all characters from 0 - n characters + let charIndex = 0; + //tracks the starting position of the current word we are typing + let currWordIndex = 0; + //set curr word index - must happen only once + let setCurrWordIndex = false; + //flag to mark all words typed after incorrect character red + let incorrectCharFound = false; + //current line + let line = []; + //set of lines + let lines = []; + + //grab characters per line + let charsPerLine = charactersPerLine; + let charsPerLineSoFar = 0; + let wordsOnCurrentLine = 0; + let visibleWords = words.slice(it, it + 85); + //unique keys for letters in lines. Coutns number of characters total + let k = 0; + + //set it forward if needed + if (currIt >= it + wordsPerLine) { + setIt(currIt); + } + + //iterate through all words from iterator marker onwards + let wordIt = 0; + while (wordIt < visibleWords.length && lines.length < 4) { + if (charsPerLineSoFar + visibleWords[wordIt].length <= charsPerLine) { + //add word length to charsPerLineSoFar + charsPerLineSoFar += visibleWords[wordIt].length; + //add every letter to line through rendering logic + for (let letterIt = 0; letterIt < visibleWords[wordIt].length; letterIt++) { + //rendering logic + //default rendering styles + let styling = {}; + let charSpan = ; + //if word has already been typed + if (wordIt + it < currIt) { + styling = { color: "white" }; + charSpan = ( + + {visibleWords[wordIt][letterIt]} + + ); + } else { + if (wordIt + it === currIt) { + if (setCurrWordIndex === false) { + currWordIndex = k; + setCurrWordIndex = true; + } + } + + //check if character has been typed + if (k - currWordIndex < typedWord.length) { + //if it has been typed, check if correct, if correct make it white + if (visibleWords[wordIt][letterIt] === typedWord[letterIt] && incorrectCharFound === false) { + styling = { color: "white", position: "relative" }; + } else { + styling = { color: "red", position: "relative" }; + incorrectCharFound = true; + } + } else { + styling = { color: "grey", position: "relative" }; + } + + //check whether to print cursor or not + if (k - currWordIndex === typedWord.length) { + charSpan = ( + + {visibleWords[wordIt][letterIt]} + + + ); + } else { + charSpan = ( + + {visibleWords[wordIt][letterIt]} + + ); + } + } + line.push(charSpan); + //increment unique key + k++; + } + //increment wordIt; + wordIt++; + //increment words on current line + wordsOnCurrentLine++; + } else { + //push the line of words into lines + lines.push(
{line}
); + //reset charsperlinesofar and line and wordsOnCurrentline + + if (lines.length === 1) { + setWordsPerLine(wordsOnCurrentLine); + } + charsPerLineSoFar = 0; + line = []; + wordsOnCurrentLine = 0; + } + } + + //Push remaining characters onto lines + if (line.length > 0) { + lines.push(
{line}
); + + //set wordsPerLine if this is the first line + if (lines.length === 1) { + setWordsPerLine(wordsOnCurrentLine); + } + } + + return lines; + }, [words, it, typedWord, wordsPerLine, charactersPerLine]); + + return ( +
+
+
Mode: {mode}
+
Time: {formatTime(time)}
+
wpm: {wpm}
+
+ {/*Conditionally render game is game is running or not*/} + {gameStatus && ( +
+
+ a +
+
{renderGame}
+
+ )} + {gameStatus && handleTypedInput(event)} />} +
+ {gameStatus === false && ()} + +
+ {/*Hidden character reference used to calculating width of a character*/} +
+ ); +}; + +export default QuoteGame; diff --git a/client/src/components/Settings.jsx b/client/src/components/Settings.jsx new file mode 100644 index 0000000..c055807 --- /dev/null +++ b/client/src/components/Settings.jsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState, useRef } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; + +/* + The settings page (according to the mockup) has the following approach + - Header + - Settings header + - Settings blocks (keyboard layout, font, theme) + - Block header + - Block buttons + - Buttons + - Button content + - Footer (if we got it) +*/ + +// SettingsBlock takes a title and however many buttons inside +// Dynamically creates a segment for each section of the settings +const SettingsBlock = ({ title, context, buttons, theme, onButtonClick }) => ( +
  • +
    +
    +

    {title}

    +
    +
    +

    {context}

    +
    +
    +
      + {buttons.map((button, index) => ( +
    • + +
    • + ))} +
    +
  • +); + +const Settings = ({ font, setFont, theme, setTheme }) => { + // so... i think I need to get the thing to (on click) get the Title info (setting block), and then the button content. + // I can do that with a function I think + const handleButtonClick = (blockID, buttonID) => { + switch (blockID) { + case "Keyboard Layout": + // i dont have anything for this right now. + break; + case "Font": + setFont(buttonID); + break; + case "Theme": + setTheme(buttonID); + break; + default: + break; + } + }; + + // JSON format used to create setting blocks + // title: Title of the settings segment + // context: Small description of the segment + // buttons: Options inside of the segment + // content: The text presented in the button + const blockData = [ + /* might save this for later. */ + /*{ + title: "Keyboard Layout", + context: "Changes the keyboard format (only while the game is running)", + buttons: [ + { content: "QWERTY" }, + { content: "Colemak" }, + { content: "Dvorak" }, + { content: "Workman" }, + { content: "QWERTZ" }, + ], + },*/ + { + title: "Font", + context: "Style up the website with a funky font! (does not effect play area)", + buttons: [ + { content: "Arial" }, + { content: "Consolas" }, + { content: "Comic Sans MS" }, + { content: "Times New Roman" }, + { content: "Special Gothic Expanded One" }, + { content: "Coral Pixels" }, + { content: "Space Grotesk" }, + { content: "Style Script" }, + { content: "Galindo" }, + { content: "Poiret One" }, + ], + }, + { + title: "Theme", + context: "Changes the theme colors of the website.", + buttons: [ + { content: "theme-blue1" }, + { content: "theme-green1" }, + { content: "theme-red1" }, + { content: "theme-yellow1" }, + /*{ content: "theme-d5" }, + { content: "theme-d4" }, + { content: "theme-d3" }, + { content: "theme-d2" }, + { content: "theme-d1" }, + { content: "theme-l5" }, + { content: "theme-l4" }, + { content: "theme-l3" }, + { content: "theme-l2" }, + { content: "theme-l1" },*/ + ], + }, + ]; + + // Once blockData is established, we create the elements + // blockData maps the block data into each block + return ( +
    +
    +

    Settings

    +
    +
      + {blockData.map((block, index) => ( + + ))} +
    +
    + ); +}; + +export default Settings; diff --git a/client/src/components/loginPage.jsx b/client/src/components/loginPage.jsx new file mode 100644 index 0000000..7375d0f --- /dev/null +++ b/client/src/components/loginPage.jsx @@ -0,0 +1,149 @@ +import React, { useEffect, useState, useRef } from "react"; +import { useNavigate, useLocation, redirect, Navigate, Route } from "react-router-dom"; +import axios from "axios"; + +const LogInPage = ({ updateAvgWPM, cookie, setLoginCookie, theme }) => { + //Set up the navigate object + const navigate = useNavigate(); + + const [loginError, setLoginError] = useState(false); + const [formValue, setFormValue] = useState({ password: "", email: "" }); + + function handleFormEmailChange(e) { + setFormValue({ password: formValue.password, email: e.target.value }); + } + + function handleFormPasswordChange(e) { + setFormValue({ password: e.target.value, email: formValue.email }); + } + + //Component to display the login errors if present + function LoginErrorMessage({ error }) { + if (error) { + return ( +

    Either the username or password is incorrect or an account does not exist under the provided email, please try again

    + ); + } else { + return null; + } + } + + //Handles the login button click + const handleLogin = async (e) => { + //Prevent the default action of submission, will do with axios + e.preventDefault(); + + //Clear the error message state since trying to log in again + setLoginError(false); + + console.log("handling the login!!!"); + console.log(`formValue.email: ${formValue.email}`); + console.log(`formValue.password: ${formValue.password}`); + + //Now take the information and submit it to the backend with axios + let response = null; + + try { + response = await axios.post("http://localhost:3000/api/login", formValue); + } catch { + //Now make response be an {} with status 401 + //Force it to be a fail HTTP status code + response = { status: 401 }; + } + + //Now check in on the response code + if (response.status == 200) { + //Good + console.log("Login successful"); + + const email = formValue.email; + console.log(`formvalue.email: ${formValue.email}`); + + console.log(response.data.userName); + + //Make the JSON to put into the cookie, so usrID, usrName + //The login query already sends back all three to use! + const cookieJSON = { + userUsername: response.data.userName, + userID: response.data.userID, + userEmail: response.data.userEmail, + }; + + console.log(cookieJSON); + + //Grab the avg WPM + const userAvgWPM = await axios.get("http://localhost:3000/api/getAvgWPM", { params: { email } }); + + console.log(userAvgWPM); + + //Set the WPM global state + updateAvgWPM(userAvgWPM.data.avgWPM); + + console.log(userAvgWPM.data.avgWPM); + + console.log(`userAvgWPM: {userAvgWPM}`); + + //make the cookie + setLoginCookie("usr", JSON.stringify(cookieJSON), { path: "/" }); + + //Redirect to the homepage + return ; + } else { + //clear the fields + + //Make the error message show by modifying the state + setLoginError(true); + } + }; + + // + // Checking if the user is logged in, if so, redirect to profile + // + const loginCookie = cookie.usr ? JSON.stringify(cookie.usr) : null; + + if (loginCookie == null) { + return ( +
    +
    +
    +
    +

    Welcome to Zoom Zoom Type!

    +

    Please login below

    + + {/* Throwing in a DIV to ensure the input boxes dont grow with the error message being shown */} +
    + +
    + +
    + + +
    + +
    + +
    + + +
    + +
    + +
    + +

    Need an account?{" "}Register Here

    +
    +
    +
    +
    +
    + ); + } else { + //User is already logged in, now redirect to the homepage + return ; + } +}; + +export default LogInPage; diff --git a/client/src/main.jsx b/client/src/main.jsx index b877058..2747051 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,11 +1,13 @@ -import React from 'react' -import { createRoot } from 'react-dom/client' -import App from './App.jsx' -import { BrowserRouter } from "react-router-dom" +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.jsx"; +import { BrowserRouter } from "react-router-dom"; +import { CookiesProvider } from "react-cookie"; - -createRoot(document.getElementById('root')).render( - - - -) +createRoot(document.getElementById("root")).render( + + + + + +); diff --git a/client/vite.config.js b/client/vite.config.js index 8b0f57b..bce19db 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -1,7 +1,8 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], -}) +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + hmr: true, +}) diff --git a/server/DB/db.js b/server/DB/db.js new file mode 100644 index 0000000..39b4500 --- /dev/null +++ b/server/DB/db.js @@ -0,0 +1,24 @@ +//This will run queries from the db and return the results +const sqlite = require('better-sqlite3'); +const path = require('path'); +const db = new sqlite(path.resolve(__dirname, 'zoomzoomtypeDB.db'), {fileMustExist: true}); + +function query(sql, params=[]) { + return db.prepare(sql).all(params); +} + +function deleteQuery(sql, params){ + db.prepare(sql).run(params); + +} + +//Done for all queries that do not return any data ONLY!!! +function noReturnQuery(sql, params) { + db.prepare(sql).run(params); +} + +module.exports = { + query, + deleteQuery, + noReturnQuery +} \ No newline at end of file diff --git a/server/DB/queries.js b/server/DB/queries.js new file mode 100644 index 0000000..69da565 --- /dev/null +++ b/server/DB/queries.js @@ -0,0 +1,419 @@ +const db = require('./db'); +const {check, validationResult } = require('express-validator'); + + +// +// READ/GET +// + +//Assumed to already be sanitized by the backend before being sent to query +function getPasswordByEmail(email) { + if (email == null) { + //No email was given, so return false + return null; + } + + //There is an email, so query the database to check + result = db.query('SELECT userPassword FROM users WHERE userEmail == ?;', [email]); + + //Return the first result (only result) and the userPassword value from the returned dictionary + pass = null; + + try { + //Try to grab the password from the result + pass = result[0].userPassword; + } catch { + //No password was found, so set null + pass = null; + } + + return pass; +} + + +function getUserIDByEmail(email) { + if (email == null) { + //No email was given, so return false + return null; + } + + //There is an email, so query the database to check + result = db.query('SELECT userID FROM users WHERE userEmail == ?;', [email]); + + //Return the first result (only result) and the userPassword value from the returned dictionary + id = null; + + try { + //Try to grab the password from the result + id = result[0].userID; + } catch { + //No password was found, so set null + id = null; + } + + return id; +} + + +function getUserNameByID(userID) { + //Check if userID is null + if (userID == null) { + return; + } + + + //Now execute the query + result = db.query("SELECT userUsername FROM users WHERE userID = ?;", [ userID ]); + + usrName = null; + + try{ + usrName = result[0].userUsername; + }catch{ + throw "getUserNameByID: Could not get the user's username from the SQL Database Query"; + } + + return usrName; + +} + +function getAvgWPMByUserID(userID) { + //check if the userID is null + if (userID == null) { + return; + } + + //Now query the db based on the userID + result = db.query("SELECT wpmTotal/gamesPlayed as average FROM avgWPM WHERE userID = ?;", [userID]); + + avg = null; + + try{ + avg = result[0].average; + }catch{ + throw "getAvgWPMByUserID: Could not get average from the SQL Database Query"; + } + + return avg; + +} + +function getGamesPlayedAndWPMByUserID(userID) { + + //check for null userID + if (userID == null) { + throw "getGamesPlayedAndWPMByUserID: userID can not be null"; + } + + //Good to go, now grab the users WPM and games played by their userID + result = db.query("SELECT wpmTotal, gamesPlayed FROM avgWPM WHERE userID = ?;", [userID]); + + //Will hold the games played and the over all WPM + gamesPlayed = 0; + wpmTotal = 0; + + console.log(result[0].gamesPlayed); + console.log(result[0]); + + try { + gamesPlayed = result[0].gamesPlayed; + wpmTotal = result[0].wpmTotal; + } catch{ + throw `getGamesPlayedAndWPMByUserID: could not either assign gamesPlayed or wpmTotal: ${e}`; + } + + return result[0]; +} + +function getProfileDataByID(userID) { + //Ensure the userID is not null + if (userID == null) { + throw "getProfileDataByID: userID can not be null"; + } + + //Now that the userID is not null, the query can be executed + //THIS IS A LONG QUERY!!!!!! + let result = db.query(`SELECT + ranked.rank AS ranking, + ranked.wpm, + CASE + WHEN ranked.mode = 1 THEN 'Classic' + WHEN ranked.mode = 2 THEN 'Memorize' + WHEN ranked.mode = 3 THEN 'Quote' + WHEN ranked.mode = 4 THEN 'Look-Ahead' + END AS mode, + ranked.time, + ranked.dateOfGame, + ranked.accuracy + FROM ( + SELECT + leaderBoardID, + userID, + wpm, + mode, + time, + quoteID, + dateOfGame, + accuracy, + RANK() OVER (ORDER BY wpm DESC) AS rank + FROM leaderBoard + ) AS ranked + LEFT JOIN quotes ON ranked.quoteID = quotes.quoteID + WHERE ranked.userID = ?;`, [userID]); + + console.log(result); + + return result; +} + +function getLeaderBoardResults(gameMode) { + //Will hold the top 10 results for the mode + top10 = null; + + //Default is classic (1) + let gameModeNumber = 1 + + //Sets the game mode number, this will be passed to the DB query + switch (gameMode) { + case "Classic": + gameModeNumber = 1; + break; + case "Memorize": + gameModeNumber = 2; + break; + case "Quotes": + gameModeNumber = 3; + break; + case "Look-Ahead": + gameModeNumber = 4; + break; + } + + console.log(`getLeaderBoardResults: gameMode: ${gameMode} | gameModeNumber: ${gameModeNumber}`); + + //Now we need to make the DB call based on the game mode, if the game mode is not 3 + if (gameMode != 3) { + top10 = db.query("SELECT users.userUsername, wpm, time FROM leaderBoard JOIN users ON leaderBoard.userID = users.userID WHERE mode = ? ORDER BY wpm DESC LIMIT 10;", [gameModeNumber]); + } + + return top10; +} + + +function getAllQuotes() { + //Will hold the results + let results = null; + + + //Now send query over + result = db.query("SELECT * FROM quotes;"); + + console.log(`getAllQuotes result: ${result[0]}`); + console.log(`getAllQuotes result: ${result[1]}`); + + //Now return it + return result; +} + +function getQuoteLeaderBoardByID(quoteID) { + console.log("Getting quote leaderboard by the quoteID"); + + //Now make the DB query to get the leaderboard based on quote ID + let result = null; + + result = db.query("SELECT users.userUsername, wpm, TIME FROM leaderBoard JOIN users ON leaderBoard.userID = users.userID JOIN quotes ON leaderBoard.quoteID = quotes.quoteID WHERE mode = 3 AND leaderBoard.quoteID = ? ORDER by wpm DESC LIMIT 10;", [quoteID]); + + return result; +} + +function getRandomQuote() { + console.log("Getting random quote and quote ID"); + + let result = db.query("SELECT * FROM quotes ORDER BY RANDOM() LIMIT 1;", []); + result = result[0]; + + console.log(`getAllQuotes: result = ${result}`); + + return result; +} + + +// +// UPDATE +// +function updateAvgWPMByUserID(userID, gamesWPM){ + if (userID == null){ + console.log("updateAvgWPMByUserID: userID can not be null"); + return null; + } + + //variables to hold the new values to write to the database + newGamesPlayed = 0; + newResultWPM = 0; + + //Get the current WPM and game count + result = db.query("SELECT wpmTotal, gamesPlayed from avgWPM WHERE userID = ?;", [userID]); + + + //Now assign the query results to each respective updated variable + try{ + newResultWPM = result[0].wpmTotal; + } catch { + throw "updateAvgWPMByUserID: Could not get wmpTotal from the SQL Database Query"; + } + + console.log(`updateAvgWPMByUserID, userID: ${userID} | wpmTotal: ${newResultWPM}`); + + try { + newGamesPlayed = result[0].gamesPlayed; + } catch { + throw "updateAvgWPMByUserID: Could not get gamesPlayed from the SQL Database Query"; + } + + + + //Update the values + newGamesPlayed++; + newResultWPM += gamesWPM; + + //Now write an update query + db.noReturnQuery("UPDATE avgWPM SET wpmTotal = ?, gamesPlayed = ? WHERE userID = ?;", [newResultWPM, newGamesPlayed, userID]); +} + + +function updateUserNameByID(ID, newUserName) { + console.log("Updating username by ID with DB"); + + + //Check for NULL ID + if (ID == null) { + throw "updateUserNameByID: ID can not be null"; + } else if (newUserName == null) { + throw "updateUserNameByID: newUserName can not be null" + } + + //Now make the update query + db.noReturnQuery("UPDATE users SET userUsername = ? WHERE userID = ?;", [newUserName, ID]); +} + +function updateEmailByID(ID, newEmail) { + console.log("Updating email by ID with DB"); + + //Check for NULL id and email + if (ID == null) { + throw "updateEmailByID: ID can not be null"; + + } else if (newEmail == null) { + throw "updateEmailByID: newEmail can not be null"; + } + + + + //Make call to DB to update info + db.noReturnQuery("UPDATE users SET userEmail = ? WHERE userID = ?;", [newEmail, ID]); +} + +function updatePasswordByID(ID, newPassword) { + console.log("Updating password by ID with DB"); + + //Check for NULL id and email + if (ID == null) { + throw "updatePasswordByID: ID can not be null"; + + } else if (newPassword == null) { + throw "updatePasswordByID: newPassword can not be null"; + } + + + + //Make call to DB to update info + db.noReturnQuery("UPDATE users SET userPassword = ? WHERE userID = ?;", [newPassword, ID]); +} + + +function addScoreToDatabase(userID, wpm, mode, time, quoteID=null) { + //Send this to the database, this is any mode + //BUT NOT QUOTE (3) + if (quoteID == null && mode != 3) { + db.noReturnQuery("INSERT INTO leaderBoard (userID, wpm, mode, time) VALUES (?, ?, ?, ?);", [userID, wpm, mode, time]); + } else { + //This is quoted insert! + db.noReturnQuery("INSERT INTO leaderBoard (userID, wpm, mode, time, quoteID) VALUES (?, ?, ?, ?, ?);", [userID, wpm, mode, time, quoteID]); + } +} + +// +// CREATE +// + +function createUserAccount(userName, userEmail, password) { + //Password is already assumed to be confirmed to be the same + + //Null checks + if (userName == null) { + throw "createUserAccount: userName can not be null"; + } else if (userEmail == null) { + throw "createUserAccount: userEmail can not be null"; + } else if (password == null) { + throw "createUserAccount: password can not be null"; + } + + //Create the user account + db.noReturnQuery("INSERT INTO users (userUsername , userEmail , userPassword) VALUES (?, ?, ?);", [userName, userEmail, password]); + + //Holds the userID of the new account to make an entry into the avgWPM table + userID = null; + + userIDResult = db.query("SELECT userID FROM users WHERE userEmail = ?", [userEmail]); + + //Try to assign it to the userID variable + try { + userID = userIDResult[0].userID; + } catch { + throw "createUserAccount: Can not grab the userID of the newly created account from the database"; + } + + //Now that its assigned, create a new entry with 0's for wpmTotal and gamesPlayed to it for the user + db.noReturnQuery("INSERT INTO avgWPM (wpmTotal, userID, gamesPlayed) VALUES (?, ?, ?);", [0, userID, 0]); + +} + +// +// DELETE +// + +function deleteUserByID(ID) { + + //Check for NULL ID + if (ID == null) { + throw "deleteUserByID: ID can not be null"; + } + + //Now good to execute the query + try { + db.deleteQuery("DELETE FROM users WHERE userID = ?;", [ID]); + } catch (e) { + console.log(`deleteUserByID: ${e}`); + } + +} + +module.exports = { + getPasswordByEmail, + getUserIDByEmail, + getAvgWPMByUserID, + updateAvgWPMByUserID, + getUserNameByID, + addScoreToDatabase, + getGamesPlayedAndWPMByUserID, + getLeaderBoardResults, + deleteUserByID, + createUserAccount, + updatePasswordByID, + updateEmailByID, + updateUserNameByID, + getQuoteLeaderBoardByID, + getAllQuotes, + getRandomQuote, + getProfileDataByID, +} \ No newline at end of file diff --git a/server/DB/zoomzoomtypeDB.db b/server/DB/zoomzoomtypeDB.db new file mode 100644 index 0000000..1a3e3af Binary files /dev/null and b/server/DB/zoomzoomtypeDB.db differ diff --git a/server/DB/zoomzoomtypeDB.db.sql b/server/DB/zoomzoomtypeDB.db.sql new file mode 100644 index 0000000..6713a83 --- /dev/null +++ b/server/DB/zoomzoomtypeDB.db.sql @@ -0,0 +1,25 @@ +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "avgWPM" ( + "avgwpmid" INTEGER NOT NULL UNIQUE, + "avgwmp" INTEGER NOT NULL, + "userID" INTEGER NOT NULL, + "gamesPlayed" INTEGER NOT NULL, + PRIMARY KEY("avgwmp" AUTOINCREMENT), + CONSTRAINT "userID" FOREIGN KEY("userID") REFERENCES "" +); +CREATE TABLE IF NOT EXISTS "leaderBoard" ( + "leaderBoardID" INTEGER NOT NULL UNIQUE, + "userID" INTEGER NOT NULL, + "wpm" INTEGER NOT NULL, + PRIMARY KEY("leaderBoardID" AUTOINCREMENT), + CONSTRAINT "userID" FOREIGN KEY("userID") REFERENCES "" +); +CREATE TABLE IF NOT EXISTS "users" ( + "userID" INTEGER NOT NULL UNIQUE, + "userEmail" TEXT NOT NULL UNIQUE, + "userPassword" TEXT NOT NULL, + "userUsername" TEXT NOT NULL, + PRIMARY KEY("userID" AUTOINCREMENT) +); +INSERT INTO "users" ("userID","userEmail","userPassword","userUsername") VALUES (1,'test@test.com','12345','test'); +COMMIT; diff --git a/server/DB/zoomzoomtypeDB.sqbpro b/server/DB/zoomzoomtypeDB.sqbpro new file mode 100644 index 0000000..6016e48 --- /dev/null +++ b/server/DB/zoomzoomtypeDB.sqbpro @@ -0,0 +1,2 @@ +
    INSERT INTO users (userEmail, userPassword, userUsername) +VALUES ("ehager2@kent.edu", "12345", "eh");
    diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..f9fa7e2 --- /dev/null +++ b/server/app.js @@ -0,0 +1,34 @@ +const express = require('express') +const app = express(); +const path = require('path'); +const routes = require("./routes") +var cookieParser = require('cookie-parser') + +//Define the port for the API +const port = 3000; + +//Make the first endpoint +app.use('/api', routes); + +//Use the JSON parser, this is needed +app.use(express.json()); + +//Add the middleware to process cookies from the browser +app.use(cookieParser()); + +//Serve the static files from the client folder +app.use(express.static(path.join(__dirname + '/../client/dist'))); + + + +//Serve the files from the client folder to client +//Used for fallback non api routing /{*splat} is equivalent to saying * routes in express +//Needed for react router to work when reloading pages +app.get('/{*splat}', (req, res) => { + res.sendFile(path.join(__dirname, '../client/dist/index.html')); +}); + +//Run the server +app.listen(port, () => { + console.log(`Server running at http://localhost:${port}`); +}); \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..28990c2 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,1346 @@ +{ + "name": "server", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "axios": "^1.8.4", + "better-sqlite3": "^11.9.1", + "cookie-parser": "^1.4.7", + "cookie-session": "^2.1.0", + "express": "^5.1.0", + "express-validator": "^7.2.1" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/better-sqlite3": { + "version": "11.9.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.9.1.tgz", + "integrity": "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cookie-session": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.1.0.tgz", + "integrity": "sha512-u73BDmR8QLGcs+Lprs0cfbcAPKl2HnPcjpwRXT41sEV4DRJ2+W0vJEEZkG31ofkx+HZflA70siRIjiTdIodmOQ==", + "dependencies": { + "cookies": "0.9.1", + "debug": "3.2.7", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cookie-session/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-validator": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", + "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "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==" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", + "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", + "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..e8e9791 --- /dev/null +++ b/server/package.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "axios": "^1.8.4", + "better-sqlite3": "^11.9.1", + "cookie-parser": "^1.4.7", + "cookie-session": "^2.1.0", + "express": "^5.1.0", + "express-validator": "^7.2.1" + } +} diff --git a/server/routes.js b/server/routes.js new file mode 100644 index 0000000..5b4b279 --- /dev/null +++ b/server/routes.js @@ -0,0 +1,372 @@ +const express = require('express'); +const router = express.Router(); +const session = require('cookie-session'); +const queries = require('./DB/queries'); +const {check, validationResults, matchedData} = require('express-validator'); +const { query } = require('./DB/db'); + + +//Add middleware to the router, so the form data can be processed +router.use(express.urlencoded({extended: true})); +router.use(express.json()); + + + +//Now define the routes + +// +// POST requests +// +router.post('/login', (req, res, next) => { + console.log(req.body.email); + //console.log(req.body.password); + + //sanatize, ensure its not empty, trim white space, escape and normalize the email + check('email').notEmpty().trim().isEmail().escape().normalizeEmail() + + + + //Grab the data + const data = matchedData(req); + + console.log(`req: ${req.body.email}`); + console.log(`Data: ${data.body}`); + + + //connect and query the db + correctPassword = queries.getPasswordByEmail(req.body.email); + console.log("matchedData email: " + matchedData(req).email); + + //Create a cookie since the log in is correct + if (req.body.password === correctPassword) { + console.log("Password is correct!!"); + + //grab the usr id + const usrID = queries.getUserIDByEmail(req.body.email); + + //grab the usr name + const usrName = queries.getUserNameByID(usrID); + + console.log(usrName); + + //Send a HTTP 200 status for OK and redirect to the homepage + res.status(200).send({"userID": usrID, 'userName': usrName, 'userEmail': req.body.email}); + } else { + console.log("Password is incorrect, or no account exists"); + //Send a HTTP 401 - Unauthorized access response + res.sendStatus(401); + } + + +}); + +//The call for this would look like: const response = await axios.post("http://localhost:3000/api/createAccount", formData); +router.post("/createAccount", (req, res) => { + console.log("Creating an account!!!"); + + //Grab the email, password and username + let email = null; + let password = null; + let userName = null; + + if (req.body.email) { + email = req.body.email + } else { + throw "createAccount: email can not be null" + } + + if (req.body.password) { + password = req.body.password + } else { + throw "createAccount: password can not be null" + } + + if (req.body.userName) { + userName = req.body.userName + } else { + throw "createAccount: userName can not be null" + } + + + //Send the query + try { + queries.createUserAccount(userName, email, password); + } catch (e) { + console.log(`createAccount ERROR: ${e}`); + res.sendStatus(500); + } + + //Send an OK HTTP response, account was created + res.sendStatus(200); +}); + + + +//Post call will look like const response = await axios.post("http://localhost:3000/api/postNewGame", {userID: 1, wpm: 45, time: 4.0, mode: 1, quoteID: 1}) + +router.post("/postNewGame", (req,res) => { + //Will need the userID, wpm, time, and game mode int + console.log(`postNewGame : ${req.body}`); + console.log(`postNewGame : ${req.body.userID}`); + console.log(`postNewGame : ${req.body.wpm}`); + + + //Grab the userID, wpm, time and game mode int + const userID = req.body.userID; + const wpm = req.body.wpm; + const time = req.body.time; + const mode = req.body.mode; + const quoteID = req.body.quoteID; + + //Enusre no null's are given + if (userID == null) { + throw "postNewGame: userID can not be null"; + } else if (wpm == null) { + throw "postNewGame: wpm can not be null"; + } else if (time == null) { + throw "postNewGame: time can not be null"; + } else if (mode == null) { + throw "postNewGame: mode can not be null"; + } else if (mode == 3 && quoteID == null) { + throw "postNewGame: quoteID can not be null when the quotes game mode is played"; + } + + + //Now make the db call to the leader board + if (mode == 3) { + //Only for the quotes game mode + queries.addScoreToDatabase(userID, wpm, mode, time, quoteID); + + } else { + //Any other game mode other than Quotes + queries.addScoreToDatabase(userID, wpm, mode, time); + } + + + //Now make a call to update the users profile total WPM and Games played + queries.updateAvgWPMByUserID(userID, wpm); + + //Sent the call to the db properly! + res.sendStatus(200); + +}); + + +// +// GET +// +router.get('/logout', (res, req) => { + //Check if there is a session + currentLoggedInUser = req.session; + + if (currentLoggedInUser){ + //There is a user to log out, log them out + currentLoggedInUser = null; + + //200 = successful + req.redirect(200, "http://localhost:5173/") + } else { + + //Now redirect to the login page + req.redirect(401, "http://localhost:5173/"); + } +}); + +//Command will look like const result = await axios.get("http://localhost:3000/api/profileData", {"userID": user_id_here}); +router.get("/profileData", (req, res) => { + //Need to grab the userID from the request + const userID = req.body.userID; + + //Ensure the useID is retrieved, this ensures the user is also logged in too + if (userID == null) { + throw "profileData: userID can not be null or has not been provided in the request's body"; + } + + + + //Now execute the query + let result = queries.getProfileDataByID(userID); + + console.log(result); + + + //Send an HTTP 200 OK and the data back + res.status(200).send(result); +}); + +router.get("/getAvgWPM", (res, req) => { + //Grab the users email + userEmail = res.query.email; + + //Query the DB for the user id + userID = queries.getUserIDByEmail(userEmail); + + //Now get the avg WMP from the database + avgWPM = queries.getAvgWPMByUserID(userID); + + //Send the response back + req.send({'avgWPM': avgWPM}); +}); + +router.get("/gamesPlayed", (req, res) => { + + //Grab the users ID from the request + const userID = req.query.ID + + console.log(`gamesPlayer: userID - ${userID}`); + + //Now query to grab the number of games played & total WPM + gamesPlayed = queries.getGamesPlayedAndWPMByUserID(userID); + + console.log(`gamesPlayed in router: ${gamesPlayed}`); + console.log(`gamesPlayed in router: ${gamesPlayed.gamesPlayed}`); + + //Send back the dictionary that is {gamesPlayed, wpmTotal} + res.status(200).send(gamesPlayed) +}); + + +//Call will be like this: const result = axios.get("http://localhost:3000/api/randomQuote") +//A dictonary +router.get("/randomQuote", (req, res) => { + console.log("Grabbing a random quote"); + + let result = null; + + result = queries.getRandomQuote(); + + + //Now print it out and see the format of the result + console.log(`randomQuote: ${result.quoteID}`); + console.log(`randomQuote: ${result.quote}`); + + + res.status(200).send(result); +}); + +// +// Profile commands +// + +router.delete('/deleteaccount', (req,res) => { + console.log("Going to delete account"); + + //Now grab the cookie with Express Cookies + //going to delete the cookie after making the DB call to remove the collumn + //The ID will be pulled from the cookie + console.log("The cookie: ", req.body.id); + + const userIDToDelete = req.body.id; + + //Now make the call to the db + queries.deleteUserByID(userIDToDelete); + + + //Delete was good, so send a 200 OK + res.sendStatus(200); +}); + +router.post('/updateUsername', (req, res) => { + console.log("Going to update the username"); + + //Grab the username and ID from the form + const newUserName = req.body.username; + const userID = req.body.id; + + console.log(`routes.js new username: ${newUserName}`); + + //Now grab the cookie + console.log("The updateUsername cookie: ", req.body.id); + + //Send request to db + queries.updateUserNameByID(userID, newUserName); + + + //Send a HTTP-200 to say everything was OK + res.sendStatus(200); + +}); + +router.post('/updatePassword', (req, res) => { + console.log("Going to update the password"); + + //Grab the username and ID from the form + const newPassword = req.body.password; + const userID = req.body.id; + + console.log(`routes.js new password: ${newPassword}`); + + //Now grab the cookie + console.log("The updatePassword cookie: ", req.body.id); + + //Send request to db + queries.updatePasswordByID(userID, newPassword); + + + //Send a HTTP-200 to say everything was OK + res.sendStatus(200); + + +}); + +router.post('/updateEmail', (req, res) => { + console.log("Going to update the email"); + + //Grab the cookie for the ID + const userIDToUpdateEmail = req.body.id; + + //Grab the email from the form component to send to the DB + const newUserEmail = req.body.email; + + console.log(`newUserEmail: ${newUserEmail}`); + console.log(`userIDToUpdateEmail: ${userIDToUpdateEmail}`); + + + //Now make the db call + queries.updateEmailByID(userIDToUpdateEmail, newUserEmail); + + //Send an HTTP 200 - OK, signaling the request was executed successfully + res.sendStatus(200); +}); + + +router.get("/leaderboard", (req, res) => { + console.log("Grabbing leaderboard info"); + + //Grab the game mode + const gameMode = req.query.mode; + + console.log(`gameMode: ${gameMode}`); + + //Now send it to the DB for processing + const result = queries.getLeaderBoardResults(gameMode); + + res.status(200).json(result); +}); + +router.get("/quotes", (req,res) => { + console.log("Grabbing the quotes"); + + //Now grab all of the quotes in the DB + const result = queries.getAllQuotes(); + + res.status(200).send(result); +}); + +router.get("/quoteleaderboard", (req, res) => { + console.log("in /quoteleaderboard"); + + //Now pull the quoteID from the request + const quoteID = req.query.quote + + console.log(`The quote id to get the leaderboard for: ${quoteID}`); + + //Now take that and send it to the DB to get the quote leaderboard + results = queries.getQuoteLeaderBoardByID(quoteID); + + res.status(200).send(results); +}); + + +//export the module to import into the app +module.exports = router; \ No newline at end of file