diff --git a/frontend/package.json b/frontend/package.json index ab777d4..6a85373 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,32 +1,43 @@ { - "name": "test", + "name": "authlib-skin", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, "dependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@mui/material": "^6.1.7", + "@marsidev/react-turnstile": "^0.3.2", + "@mui/icons-material": "^5.16.7", + "@mui/material": "^5.16.7", + "ahooks": "^3.8.1", + "immer": "^10.1.1", + "jotai": "^2.10.1", + "mui-file-input": "^3.0.2", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "skinview3d": "^3.1.0", + "tilg": "^0.1.1" }, "devDependencies": { - "@eslint/js": "^9.13.0", + "@types/node": "^20.17.6", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react-swc": "^3.5.0", - "eslint": "^9.13.0", - "eslint-plugin-react-hooks": "^5.0.0", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "@vitejs/plugin-react-swc": "^3.7.1", + "eslint": "^8.57.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.14", - "globals": "^15.11.0", - "typescript": "~5.6.2", - "typescript-eslint": "^8.11.0", - "vite": "^5.4.10" + "rollup-plugin-visualizer": "^5.12.0", + "typescript": "^5.6.3", + "vite": "^5.4.11" } -} +} \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 8f6df62..c95fedd 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -14,49 +14,82 @@ importers: '@emotion/styled': specifier: ^11.13.0 version: 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@marsidev/react-turnstile': + specifier: ^0.3.2 + version: 0.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/icons-material': + specifier: ^5.16.7 + version: 5.16.7(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) '@mui/material': - specifier: ^6.1.7 - version: 6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.7 + version: 5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ahooks: + specifier: ^3.8.1 + version: 3.8.1(react@18.3.1) + immer: + specifier: ^10.1.1 + version: 10.1.1 + jotai: + specifier: ^2.10.1 + version: 2.10.2(@types/react@18.3.12)(react@18.3.1) + mui-file-input: + specifier: ^3.0.2 + version: 3.0.2(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/icons-material@5.16.7(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^6.28.0 + version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + skinview3d: + specifier: ^3.1.0 + version: 3.1.0 + tilg: + specifier: ^0.1.1 + version: 0.1.1(react@18.3.1) devDependencies: - '@eslint/js': - specifier: ^9.13.0 - version: 9.15.0 + '@types/node': + specifier: ^20.17.6 + version: 20.17.6 '@types/react': specifier: ^18.3.12 version: 18.3.12 '@types/react-dom': specifier: ^18.3.1 version: 18.3.1 + '@typescript-eslint/eslint-plugin': + specifier: ^6.21.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': + specifier: ^6.21.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.6.3) '@vitejs/plugin-react-swc': - specifier: ^3.5.0 - version: 3.7.1(vite@5.4.11) + specifier: ^3.7.1 + version: 3.7.1(vite@5.4.11(@types/node@20.17.6)) eslint: - specifier: ^9.13.0 - version: 9.15.0 + specifier: ^8.57.1 + version: 8.57.1 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.1) eslint-plugin-react-hooks: - specifier: ^5.0.0 - version: 5.0.0(eslint@9.15.0) + specifier: ^4.6.2 + version: 4.6.2(eslint@8.57.1) eslint-plugin-react-refresh: specifier: ^0.4.14 - version: 0.4.14(eslint@9.15.0) - globals: - specifier: ^15.11.0 - version: 15.12.0 + version: 0.4.14(eslint@8.57.1) + rollup-plugin-visualizer: + specifier: ^5.12.0 + version: 5.12.0(rollup@4.27.2) typescript: - specifier: ~5.6.2 + specifier: ^5.6.3 version: 5.6.3 - typescript-eslint: - specifier: ^8.11.0 - version: 8.14.0(eslint@9.15.0)(typescript@5.6.3) vite: - specifier: ^5.4.10 - version: 5.4.11 + specifier: ^5.4.11 + version: 5.4.11(@types/node@20.17.6) packages: @@ -303,49 +336,26 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.19.0': - resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/core@0.9.0': - resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.15.0': - resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.4': - resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.2.3': - resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} - engines: {node: '>=18.18.0'} + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - - '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} - engines: {node: '>=18.18'} + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} @@ -365,60 +375,74 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@mui/core-downloads-tracker@6.1.7': - resolution: {integrity: sha512-POuIBi80BZBogQkG4PQKIGwy4QFwB+kOr+OI4k7Znh7LqMAIhwB9OC00l6M+w1GrZJYj3T8R5WX8G6QAIvoVEw==} + '@marsidev/react-turnstile@0.3.2': + resolution: {integrity: sha512-NTTmQdVgSFTHZlFRw/6RwG2kveT6J57U8JdjhbSdKcSBEbhqoe9L3wPMbq8g31KCDOm2cYTu/LNavAPbCY1vgA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' - '@mui/material@6.1.7': - resolution: {integrity: sha512-KsjujQL/A2hLd1PV3QboF+W6SSL5QqH6ZlSuQoeYz9r69+TnyBFIevbYLxdjJcJmGBjigL5pfpn7hTGop+vhSg==} - engines: {node: '>=14.0.0'} + '@mui/core-downloads-tracker@5.16.7': + resolution: {integrity: sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==} + + '@mui/icons-material@5.16.7': + resolution: {integrity: sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@mui/material': ^5.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/material@5.16.7': + resolution: {integrity: sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==} + engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 '@emotion/styled': ^11.3.0 - '@mui/material-pigment-css': ^6.1.7 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@emotion/react': optional: true '@emotion/styled': optional: true - '@mui/material-pigment-css': - optional: true '@types/react': optional: true - '@mui/private-theming@6.1.7': - resolution: {integrity: sha512-uLbfUSsug5K0LVkv0PI6Flste3le8+6WSL2omdTiYde93P89Qr7pKr8TA6d2yXfr+Bm+SvD8/fGnkaRwFkryuQ==} - engines: {node: '>=14.0.0'} + '@mui/private-theming@5.16.6': + resolution: {integrity: sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==} + engines: {node: '>=12.0.0'} peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@types/react': optional: true - '@mui/styled-engine@6.1.7': - resolution: {integrity: sha512-Ou4CxN7MQmwrfG1Pu6EYjPgPChQXxPDJrwgizLXlRPOad5qAq4gYXRuzrGQ2DfGjjwmJhjI8T6A0SeapAZPGig==} - engines: {node: '>=14.0.0'} + '@mui/styled-engine@5.16.6': + resolution: {integrity: sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==} + engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.4.1 '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@emotion/react': optional: true '@emotion/styled': optional: true - '@mui/system@6.1.7': - resolution: {integrity: sha512-qbMGgcC/FodpuRSfjXlEDdbNQaW++eATh0vNBcPUv2/YXSpReoOpoT9FhogxEBNks+aQViDXBRZKh6HX2fVmwg==} - engines: {node: '>=14.0.0'} + '@mui/system@5.16.7': + resolution: {integrity: sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==} + engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@emotion/react': optional: true @@ -435,12 +459,12 @@ packages: '@types/react': optional: true - '@mui/utils@6.1.7': - resolution: {integrity: sha512-Gr7cRZxBoZ0BIa3Xqf/2YaUrBLyNPJvXPQH3OsD9WMZukI/TutibbQBVqLYpgqJn8pKSjbD50Yq2auG0wI1xOw==} - engines: {node: '>=14.0.0'} + '@mui/utils@5.16.6': + resolution: {integrity: sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==} + engines: {node: '>=12.0.0'} peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@types/react': optional: true @@ -460,6 +484,10 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@remix-run/router@1.21.0': + resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==} + engines: {node: '>=14.0.0'} + '@rollup/rollup-android-arm-eabi@4.27.2': resolution: {integrity: sha512-Tj+j7Pyzd15wAdSJswvs5CJzJNV+qqSUcr/aCD+jpQSBtXvGnV0pnrjoc8zFTe9fcKCatkpFpOO7yAzpO998HA==} cpu: [arm] @@ -631,6 +659,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@20.17.6': + resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -646,62 +677,78 @@ packages: '@types/react@18.3.12': resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} - '@typescript-eslint/eslint-plugin@8.14.0': - resolution: {integrity: sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/stats.js@0.17.3': + resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==} + + '@types/three@0.156.0': + resolution: {integrity: sha512-733bXDSRdlrxqOmQuOmfC1UBRuJ2pREPk8sWnx9MtIJEVDQMx8U0NQO5MVVaOrjzDPyLI+cFPim2X/ss9v0+LQ==} + + '@types/webxr@0.5.20': + resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/parser@8.14.0': - resolution: {integrity: sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/scope-manager@8.14.0': - resolution: {integrity: sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/type-utils@8.14.0': - resolution: {integrity: sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/types@8.14.0': - resolution: {integrity: sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.14.0': - resolution: {integrity: sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^7.0.0 || ^8.0.0 - '@typescript-eslint/utils@8.14.0': - resolution: {integrity: sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/visitor-keys@8.14.0': - resolution: {integrity: sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} '@vitejs/plugin-react-swc@3.7.1': resolution: {integrity: sha512-vgWOY0i1EROUK0Ctg1hwhtC3SdcDjZcdit4Ups4aPkDcB1jYhmo+RMYWY87cmXMhvtD5uf8lV89j2w16vkdSVg==} @@ -718,9 +765,19 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ahooks@3.8.1: + resolution: {integrity: sha512-JoP9+/RWO7MnI/uSKdvQ8WB10Y3oo1PjLv+4Sv4Vpm19Z86VUMdXh+RhWvMGxZZs06sq2p0xVtFk8Oh5ZObsoA==} + engines: {node: '>=8.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -728,6 +785,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} @@ -753,6 +814,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -781,6 +846,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -793,9 +861,24 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -804,46 +887,48 @@ packages: engines: {node: '>=12'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-plugin-react-hooks@5.0.0: - resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==} + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 eslint-plugin-react-refresh@0.4.14: resolution: {integrity: sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==} peerDependencies: eslint: '>=7' - eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint@9.15.0: - resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} @@ -877,9 +962,12 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + fflate@0.6.10: + resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -892,13 +980,16 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -907,6 +998,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -915,17 +1010,21 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} - globals@15.12.0: - resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} - engines: {node: '>=18'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -945,6 +1044,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -953,6 +1055,16 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + intersection-observer@0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -960,10 +1072,19 @@ packages: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -972,9 +1093,33 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jotai@2.10.2: + resolution: {integrity: sha512-DqsBTlRglIBviuJLfK6JxZzpd6vKfbuJ4IqRCz70RFEDeZf46Fcteb/FXxNr1UnoxR5oUy3oq7IE8BrEq0G5DQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1016,6 +1161,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1024,6 +1172,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + meshoptimizer@0.18.1: + resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -1031,13 +1182,27 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mui-file-input@3.0.2: + resolution: {integrity: sha512-58Jp3+f5MUXjhZjlLfYFOEOBICnLoFC4x0G1+y411JsGBjZQ1lgICv7KQVgP5aF+IRvhJ1vfI6KpbnmqwRKXoA==} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/icons-material': ^5.0.0 + '@mui/material': ^5.0.0 + '@types/react': ^18.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1050,6 +1215,13 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1074,6 +1246,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1100,6 +1276,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-bytes@6.1.1: + resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} + engines: {node: ^14.13.1 || >=16.0.0} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -1115,12 +1295,28 @@ packages: peerDependencies: react: ^18.3.1 + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-router-dom@6.28.0: + resolution: {integrity: sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.28.0: + resolution: {integrity: sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-transition-group@4.4.5: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -1134,6 +1330,13 @@ packages: regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1146,6 +1349,21 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup-plugin-visualizer@5.12.0: + resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rollup: + optional: true + rollup@4.27.2: resolution: {integrity: sha512-KreA+PzWmk2yaFmZVwe6GB2uBD86nXl86OsDkt1bJS9p3vqWuEQ6HnJJ+j/mZi/q0920P99/MVRlB4L3crpF5w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -1157,6 +1375,10 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} @@ -1170,6 +1392,16 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + skinview-utils@0.7.1: + resolution: {integrity: sha512-4eLrMqR526ehlZbsd8SuZ/CHpS9GiH0xUMoV+PYlJVi95ZFz5HJu7Spt5XYa72DRS7wgt5qquvHZf0XZJgmu9Q==} + + skinview3d@3.1.0: + resolution: {integrity: sha512-L+HXXAP4qYjLcY3YHasXKie9KXQpv/mPTMxgLOEd+hVQRdQkPs5xdWaKuOmlZY8UnyZzecQM7yrWRzgT/e7HZw==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1178,6 +1410,18 @@ packages: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1193,6 +1437,17 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + three@0.156.1: + resolution: {integrity: sha512-kP7H0FK9d/k6t/XvQ9FO6i+QrePoDcNhwl0I02+wmUJRNSLCUIDMcfObnzQvxb37/0Uc9TDT0T1HgsRRrO6SYQ==} + + tilg@0.1.1: + resolution: {integrity: sha512-0uHyTAUM0tJL792LeviRPFkJtCbF6Za3/hbbnRmWGUaicOhbJ0IpvBViXiXTF7nk6R0L6vve2XLesQzn5jEVng==} + peerDependencies: + react: ^18.0.0 || ^17.0.0 + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1203,24 +1458,25 @@ packages: peerDependencies: typescript: '>=4.2.0' + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.14.0: - resolution: {integrity: sha512-K8fBJHxVL3kxMmwByvz8hNdBJ8a0YqKzKDX6jRlrjMuNXyd5T2V02HIq37+OiWXvUUOXgOOGiSSOh26Mh8pC3w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -1264,10 +1520,29 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1482,29 +1757,19 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0)': + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': dependencies: - eslint: 9.15.0 + eslint: 8.57.1 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/config-array@0.19.0': - dependencies: - '@eslint/object-schema': 2.1.4 - debug: 4.3.7 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/core@0.9.0': {} - - '@eslint/eslintrc@3.2.0': + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 debug: 4.3.7 - espree: 10.3.0 - globals: 14.0.0 + espree: 9.6.1 + globals: 13.24.0 ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -1513,26 +1778,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.15.0': {} + '@eslint/js@8.57.1': {} - '@eslint/object-schema@2.1.4': {} - - '@eslint/plugin-kit@0.2.3': + '@humanwhocodes/config-array@0.13.0': dependencies: - levn: 0.4.1 - - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.6': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.1': {} - - '@humanwhocodes/retry@0.4.1': {} + '@humanwhocodes/object-schema@2.0.3': {} '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -1551,15 +1809,28 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@mui/core-downloads-tracker@6.1.7': {} + '@marsidev/react-turnstile@0.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - '@mui/material@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/core-downloads-tracker@5.16.7': {} + + '@mui/icons-material@5.16.7(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 - '@mui/core-downloads-tracker': 6.1.7 - '@mui/system': 6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@mui/material': 5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + '@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + '@mui/core-downloads-tracker': 5.16.7 + '@mui/system': 5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) '@mui/types': 7.2.19(@types/react@18.3.12) - '@mui/utils': 6.1.7(@types/react@18.3.12)(react@18.3.1) + '@mui/utils': 5.16.6(@types/react@18.3.12)(react@18.3.1) '@popperjs/core': 2.11.8 '@types/react-transition-group': 4.4.11 clsx: 2.1.1 @@ -1574,21 +1845,19 @@ snapshots: '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) '@types/react': 18.3.12 - '@mui/private-theming@6.1.7(@types/react@18.3.12)(react@18.3.1)': + '@mui/private-theming@5.16.6(@types/react@18.3.12)(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 - '@mui/utils': 6.1.7(@types/react@18.3.12)(react@18.3.1) + '@mui/utils': 5.16.6(@types/react@18.3.12)(react@18.3.1) prop-types: 15.8.1 react: 18.3.1 optionalDependencies: '@types/react': 18.3.12 - '@mui/styled-engine@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)': + '@mui/styled-engine@5.16.6(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 '@emotion/cache': 11.13.1 - '@emotion/serialize': 1.3.2 - '@emotion/sheet': 1.4.0 csstype: 3.1.3 prop-types: 15.8.1 react: 18.3.1 @@ -1596,13 +1865,13 @@ snapshots: '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) - '@mui/system@6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': + '@mui/system@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 - '@mui/private-theming': 6.1.7(@types/react@18.3.12)(react@18.3.1) - '@mui/styled-engine': 6.1.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) + '@mui/private-theming': 5.16.6(@types/react@18.3.12)(react@18.3.1) + '@mui/styled-engine': 5.16.6(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1) '@mui/types': 7.2.19(@types/react@18.3.12) - '@mui/utils': 6.1.7(@types/react@18.3.12)(react@18.3.1) + '@mui/utils': 5.16.6(@types/react@18.3.12)(react@18.3.1) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 @@ -1616,7 +1885,7 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 - '@mui/utils@6.1.7(@types/react@18.3.12)(react@18.3.1)': + '@mui/utils@5.16.6(@types/react@18.3.12)(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 '@mui/types': 7.2.19(@types/react@18.3.12) @@ -1642,6 +1911,8 @@ snapshots: '@popperjs/core@2.11.8': {} + '@remix-run/router@1.21.0': {} + '@rollup/rollup-android-arm-eabi@4.27.2': optional: true @@ -1752,6 +2023,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@20.17.6': + dependencies: + undici-types: 6.19.8 + '@types/parse-json@4.0.2': {} '@types/prop-types@15.7.13': {} @@ -1769,64 +2044,32 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 - '@typescript-eslint/eslint-plugin@8.14.0(@typescript-eslint/parser@8.14.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)': + '@types/semver@7.5.8': {} + + '@types/stats.js@0.17.3': {} + + '@types/three@0.156.0': + dependencies: + '@types/stats.js': 0.17.3 + '@types/webxr': 0.5.20 + fflate: 0.6.10 + meshoptimizer: 0.18.1 + + '@types/webxr@0.5.20': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.14.0(eslint@9.15.0)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 8.14.0 - '@typescript-eslint/type-utils': 8.14.0(eslint@9.15.0)(typescript@5.6.3) - '@typescript-eslint/utils': 8.14.0(eslint@9.15.0)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.14.0 - eslint: 9.15.0 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.7 + eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.14.0(eslint@9.15.0)(typescript@5.6.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.14.0 - '@typescript-eslint/types': 8.14.0 - '@typescript-eslint/typescript-estree': 8.14.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.14.0 - debug: 4.3.7 - eslint: 9.15.0 - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.14.0': - dependencies: - '@typescript-eslint/types': 8.14.0 - '@typescript-eslint/visitor-keys': 8.14.0 - - '@typescript-eslint/type-utils@8.14.0(eslint@9.15.0)(typescript@5.6.3)': - dependencies: - '@typescript-eslint/typescript-estree': 8.14.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.14.0(eslint@9.15.0)(typescript@5.6.3) - debug: 4.3.7 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - eslint - - supports-color - - '@typescript-eslint/types@8.14.0': {} - - '@typescript-eslint/typescript-estree@8.14.0(typescript@5.6.3)': - dependencies: - '@typescript-eslint/types': 8.14.0 - '@typescript-eslint/visitor-keys': 8.14.0 - debug: 4.3.7 - fast-glob: 3.3.2 - is-glob: 4.0.3 - minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: @@ -1834,26 +2077,78 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.14.0(eslint@9.15.0)(typescript@5.6.3)': + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0) - '@typescript-eslint/scope-manager': 8.14.0 - '@typescript-eslint/types': 8.14.0 - '@typescript-eslint/typescript-estree': 8.14.0(typescript@5.6.3) - eslint: 9.15.0 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.7 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) + debug: 4.3.7 + eslint: 8.57.1 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.7 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.3 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + eslint: 8.57.1 + semver: 7.6.3 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.14.0': + '@typescript-eslint/visitor-keys@6.21.0': dependencies: - '@typescript-eslint/types': 8.14.0 + '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 - '@vitejs/plugin-react-swc@3.7.1(vite@5.4.11)': + '@ungap/structured-clone@1.2.0': {} + + '@vitejs/plugin-react-swc@3.7.1(vite@5.4.11(@types/node@20.17.6))': dependencies: '@swc/core': 1.9.2 - vite: 5.4.11 + vite: 5.4.11(@types/node@20.17.6) transitivePeerDependencies: - '@swc/helpers' @@ -1863,6 +2158,19 @@ snapshots: acorn@8.14.0: {} + ahooks@3.8.1(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + dayjs: 1.11.13 + intersection-observer: 0.12.2 + js-cookie: 3.0.5 + lodash: 4.17.21 + react: 18.3.1 + react-fast-compare: 3.2.2 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + tslib: 2.8.1 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -1870,12 +2178,16 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 argparse@2.0.1: {} + array-union@2.1.0: {} + babel-plugin-macros@3.1.0: dependencies: '@babel/runtime': 7.26.0 @@ -1904,6 +2216,12 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@2.1.1: {} color-convert@2.0.1: @@ -1932,17 +2250,31 @@ snapshots: csstype@3.1.3: {} + dayjs@1.11.13: {} + debug@4.3.7: dependencies: ms: 2.1.3 deep-is@0.1.4: {} + define-lazy-prop@2.0.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.26.0 csstype: 3.1.3 + emoji-regex@8.0.0: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -1973,69 +2305,77 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.0.0(eslint@9.15.0): + eslint-config-prettier@9.1.0(eslint@8.57.1): dependencies: - eslint: 9.15.0 + eslint: 8.57.1 - eslint-plugin-react-refresh@0.4.14(eslint@9.15.0): + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): dependencies: - eslint: 9.15.0 + eslint: 8.57.1 - eslint-scope@8.2.0: + eslint-plugin-react-refresh@0.4.14(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.0: {} - - eslint@9.15.0: + eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.0 - '@eslint/core': 0.9.0 - '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.15.0 - '@eslint/plugin-kit': 0.2.3 - '@humanfs/node': 0.16.6 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 - '@types/estree': 1.0.6 - '@types/json-schema': 7.0.15 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.5 debug: 4.3.7 + doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 + file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 transitivePeerDependencies: - supports-color - espree@10.3.0: + espree@9.6.1: dependencies: acorn: 8.14.0 acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 4.2.0 + eslint-visitor-keys: 3.4.3 esquery@1.6.0: dependencies: @@ -2067,9 +2407,11 @@ snapshots: dependencies: reusify: 1.0.4 - file-entry-cache@8.0.0: + fflate@0.6.10: {} + + file-entry-cache@6.0.1: dependencies: - flat-cache: 4.0.1 + flat-cache: 3.2.0 fill-range@7.1.1: dependencies: @@ -2082,18 +2424,23 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@4.0.1: + flat-cache@3.2.0: dependencies: flatted: 3.3.1 keyv: 4.5.4 + rimraf: 3.0.2 flatted@3.3.1: {} + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true function-bind@1.1.2: {} + get-caller-file@2.0.5: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2102,11 +2449,29 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@11.12.0: {} - globals@14.0.0: {} + globals@13.24.0: + dependencies: + type-fest: 0.20.2 - globals@15.12.0: {} + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 graphemer@1.4.0: {} @@ -2122,6 +2487,8 @@ snapshots: ignore@5.3.2: {} + immer@10.1.1: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -2129,22 +2496,48 @@ snapshots: imurmurhash@0.1.4: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + intersection-observer@0.12.2: {} + is-arrayish@0.2.1: {} is-core-module@2.15.1: dependencies: hasown: 2.0.2 + is-docker@2.2.1: {} + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-number@7.0.0: {} + is-path-inside@3.0.3: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + isexe@2.0.0: {} + jotai@2.10.2(@types/react@18.3.12)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.12 + react: 18.3.1 + + js-cookie@3.0.5: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -2178,12 +2571,16 @@ snapshots: lodash.merge@4.6.2: {} + lodash@4.17.21: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 merge2@1.4.1: {} + meshoptimizer@0.18.1: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -2193,18 +2590,40 @@ snapshots: dependencies: brace-expansion: 1.1.11 - minimatch@9.0.5: + minimatch@9.0.3: dependencies: brace-expansion: 2.0.1 ms@2.1.3: {} + mui-file-input@3.0.2(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/icons-material@5.16.7(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@mui/icons-material': 5.16.7(@mui/material@5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.12)(react@18.3.1) + '@mui/material': 5.16.7(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + pretty-bytes: 6.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.12 + nanoid@3.3.7: {} natural-compare@1.4.0: {} object-assign@4.1.1: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2235,6 +2654,8 @@ snapshots: path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -2253,6 +2674,8 @@ snapshots: prelude-ls@1.2.1: {} + pretty-bytes@6.1.1: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -2269,10 +2692,24 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-fast-compare@3.2.2: {} + react-is@16.13.1: {} react-is@18.3.1: {} + react-router-dom@6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.21.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.28.0(react@18.3.1) + + react-router@6.28.0(react@18.3.1): + dependencies: + '@remix-run/router': 1.21.0 + react: 18.3.1 + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 @@ -2288,6 +2725,10 @@ snapshots: regenerator-runtime@0.14.1: {} + require-directory@2.1.1: {} + + resize-observer-polyfill@1.5.1: {} + resolve-from@4.0.0: {} resolve@1.22.8: @@ -2298,6 +2739,19 @@ snapshots: reusify@1.0.4: {} + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup-plugin-visualizer@5.12.0(rollup@4.27.2): + dependencies: + open: 8.4.2 + picomatch: 2.3.1 + source-map: 0.7.4 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.27.2 + rollup@4.27.2: dependencies: '@types/estree': 1.0.6 @@ -2330,6 +2784,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + screenfull@5.2.0: {} + semver@7.6.3: {} shebang-command@2.0.0: @@ -2338,10 +2794,32 @@ snapshots: shebang-regex@3.0.0: {} + skinview-utils@0.7.1: {} + + skinview3d@3.1.0: + dependencies: + '@types/three': 0.156.0 + skinview-utils: 0.7.1 + three: 0.156.1 + + slash@3.0.0: {} + source-map-js@1.2.1: {} source-map@0.5.7: {} + source-map@0.7.4: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-json-comments@3.1.1: {} stylis@4.2.0: {} @@ -2352,6 +2830,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + text-table@0.2.0: {} + + three@0.156.1: {} + + tilg@0.1.1(react@18.3.1): + dependencies: + react: 18.3.1 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -2360,33 +2846,29 @@ snapshots: dependencies: typescript: 5.6.3 + tslib@2.8.1: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.14.0(eslint@9.15.0)(typescript@5.6.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.14.0(@typescript-eslint/parser@8.14.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3) - '@typescript-eslint/parser': 8.14.0(eslint@9.15.0)(typescript@5.6.3) - '@typescript-eslint/utils': 8.14.0(eslint@9.15.0)(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - eslint - - supports-color + type-fest@0.20.2: {} typescript@5.6.3: {} + undici-types@6.19.8: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - vite@5.4.11: + vite@5.4.11(@types/node@20.17.6): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.27.2 optionalDependencies: + '@types/node': 20.17.6 fsevents: 2.3.3 which@2.0.2: @@ -2395,6 +2877,28 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + y18n@5.0.8: {} + yaml@1.10.2: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ef79242..b25320c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,14 +1,13 @@ - -import './App.css' -import { Button } from '@mui/material' +import { PageRoute } from '@/Route' function App() { return ( <> - ; + ) } + export default App diff --git a/frontend/src/Route.tsx b/frontend/src/Route.tsx new file mode 100644 index 0000000..83a2f18 --- /dev/null +++ b/frontend/src/Route.tsx @@ -0,0 +1,58 @@ +import { Routes, Route, createBrowserRouter, RouterProvider, Outlet } from "react-router-dom"; +import { ScrollRestoration } from "react-router-dom"; +import Login from '@/views/Login' +import Register from '@/views/Register' +import Profile from '@/views/profile/Profile' +import Textures from '@/views/profile/Textures' +import Security from '@/views/profile/Security' +import Layout from '@/views/Layout' +import UserAdmin from "@/views/admin/UserAdmin"; +import NeedLogin from "@/components/NeedLogin"; +import Index from "@/views/Index"; +import SendEmail from "@/views/SendEmail"; +import { sendForgotEmail, sendRegEmail } from "@/apis/apis"; +import Forgot from "@/views/Forgot"; + +const router = createBrowserRouter([ + { path: "*", Component: Root }, +]) + +function Root() { + return ( + <> + + }> + } /> + 404

} /> + } /> + } /> + } /> + } /> + } /> + + }> + } /> + } /> + } /> + + + }> + } /> + + + +
+ + + ) +} + + +export function PageRoute() { + return ( + <> + + + ) +} + diff --git a/frontend/src/apis/apis.ts b/frontend/src/apis/apis.ts new file mode 100644 index 0000000..b996595 --- /dev/null +++ b/frontend/src/apis/apis.ts @@ -0,0 +1,156 @@ +import type { tokenData, ApiUser, YggProfile, ApiConfig, List, UserInfo, EditUser } from '@/apis/model' +import { apiGet } from '@/apis/utils' +import root from '@/utils/root' + +export async function login(email: string, password: string, captchaToken: string) { + const v = await fetch(root() + "/api/v1/user/login", { + method: "POST", + body: JSON.stringify({ + "email": email, + "password": password, + "CaptchaToken": captchaToken + }) + }) + return await apiGet(v) +} + +export async function register(email: string, username: string, password: string, captchaToken: string, code: string) { + const v = await fetch(root() + "/api/v1/user/reg", { + method: "POST", + body: JSON.stringify({ + "Email": email, + "Password": password, + "Name": username, + "CaptchaToken": captchaToken, + "EmailJwt": code, + }) + }) + return await apiGet(v) +} + +export async function userInfo(token: string) { + if (token == "") return + const v = await fetch(root() + "/api/v1/user", { + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet(v) +} + +export async function yggProfile(uuid: string) { + if (uuid == "") return + const v = await fetch(root() + "/api/yggdrasil/sessionserver/session/minecraft/profile/" + uuid) + const data = await v.json() + if (!v.ok) { + throw new Error(data?.errorMessage) + } + return data as YggProfile +} + +export async function upTextures(token: string, textureType: 'skin' | 'cape', model: 'slim' | '', file: File) { + const f = new FormData() + f.set("file", file) + f.set("model", model) + + const r = await fetch(root() + "/api/v1/user/skin/" + textureType, { + method: "PUT", + body: f, + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet(r) +} + +export async function changePasswd(old: string, newpa: string, token: string) { + const r = await fetch(root() + "/api/v1/user/password", { + method: "POST", + body: JSON.stringify({ + "old": old, + "new": newpa + }), + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet(r) +} + +export async function getConfig() { + const r = await fetch(root() + "/api/v1/config") + return await apiGet(r) +} + +export async function changeName(name: string, token: string) { + const r = await fetch(root() + "/api/v1/user/name", { + method: "POST", + body: JSON.stringify({ + "name": name, + }), + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet(r) +} + +export async function ListUser(page: number, token: string, email: string, name: string) { + const u = new URL(root() + "/api/v1/admin/users") + u.searchParams.set("page", String(page)) + u.searchParams.set("email", email) + u.searchParams.set("name", name) + const r = await fetch(u.toString(), { + method: "GET", + headers: { + "Authorization": "Bearer " + token + } + }) + return await apiGet>(r) +} + +export async function editUser(u: EditUser, token: string, uid: string) { + const r = await fetch(root() + "/api/v1/admin/user/" + uid, { + method: "PATCH", + headers: { + "Authorization": "Bearer " + token + }, + body: JSON.stringify(u) + }) + return await apiGet(r) +} + +export async function sendRegEmail(email: string, captchaToken: string) { + const r = await fetch(root() + "/api/v1/user/reg_email", { + method: "POST", + body: JSON.stringify({ + "email": email, + "captchaToken": captchaToken + }) + }) + return await apiGet(r) +} + +export async function sendForgotEmail(email: string, captchaToken: string) { + const r = await fetch(root() + "/api/v1/user/forgot_email", { + method: "POST", + body: JSON.stringify({ + "email": email, + "captchaToken": captchaToken + }) + }) + return await apiGet(r) +} + + +export async function forgotPassWord(email: string, emailJwt: string, password: string) { + const r = await fetch(root() + "/api/v1/user/forgot", { + method: "POST", + body: JSON.stringify({ + "email": email, + "emailJwt": emailJwt, + "passWord": password, + }) + }) + return await apiGet(r) +} \ No newline at end of file diff --git a/frontend/src/apis/error.ts b/frontend/src/apis/error.ts new file mode 100644 index 0000000..58c962b --- /dev/null +++ b/frontend/src/apis/error.ts @@ -0,0 +1,8 @@ +export class ApiErr extends Error { + readonly code: number + + constructor(code: number, msg: string) { + super(msg) + this.code = code + } +} \ No newline at end of file diff --git a/frontend/src/apis/model.ts b/frontend/src/apis/model.ts new file mode 100644 index 0000000..5dc0828 --- /dev/null +++ b/frontend/src/apis/model.ts @@ -0,0 +1,66 @@ +export interface tokenData { + token: string + name: string + uuid: string +} + +export interface Api { + code: number + msg: string + data: T +} + +export interface List { + total: number + list: T[] +} + + +interface captcha { + type: string + siteKey: string +} + + +export interface ApiUser { + uid: string + uuid: string + is_admin: boolean +} + +export interface YggProfile { + name: string + properties: { + name: string + value: string + }[] +} + +export interface ApiConfig { + captcha: captcha + AllowChangeName: boolean + serverName: string + NeedEmail: boolean + AllowDomain: string[] + EmailReg: string + EmailRegMsg: string +} + +export interface UserInfo { + uid: number + uuid: string + is_admin: boolean + is_disable: boolean + email: string + reg_ip: string + name: string +} + +export interface EditUser { + email?: string + name?: string + password?: string + is_admin?: boolean + is_disable?: boolean + del_textures?: boolean +} \ No newline at end of file diff --git a/frontend/src/apis/utils.ts b/frontend/src/apis/utils.ts new file mode 100644 index 0000000..be66411 --- /dev/null +++ b/frontend/src/apis/utils.ts @@ -0,0 +1,10 @@ +import { ApiErr } from "./error" + +export async function apiGet(v: Response) { + type api = { data: T, msg: string, code: number } + const data = await v.json() as api + if (!v.ok) { + throw new ApiErr(data.code, data.msg) + } + return data.data +} diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/CaptchaWidget.tsx b/frontend/src/components/CaptchaWidget.tsx new file mode 100644 index 0000000..c24cfcc --- /dev/null +++ b/frontend/src/components/CaptchaWidget.tsx @@ -0,0 +1,65 @@ +import { Turnstile } from '@marsidev/react-turnstile' +import Button from '@mui/material/Button' +import { useRef, useState, memo, forwardRef, useImperativeHandle, useEffect } from 'react' +import type { TurnstileInstance } from '@marsidev/react-turnstile' +import Alert from '@mui/material/Alert'; +import Skeleton from '@mui/material/Skeleton'; +import { useRequest } from 'ahooks'; +import { getConfig } from '@/apis/apis'; + +interface prop { + onSuccess: ((token: string) => void) +} + +export type refType = { + reload: () => void +} + + +const CaptchaWidget = forwardRef(({ onSuccess }, ref) => { + const Turnstileref = useRef(null) + const [key, setKey] = useState(1) + const { data, error, loading } = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 600000, + loadingDelay: 200 + }) + + useImperativeHandle(ref, () => { + return { + reload: () => { + setKey(key + 1) + } + } + }) + useEffect(() => { + if (data?.captcha?.type != "turnstile") { + onSuccess("ok") + return + } + }, [data?.captcha?.type, onSuccess]) + + + if (error) { + console.warn(error) + return {String(error)} + } + if (!data && loading) { + return + } + + if (data?.captcha.type == "") { + return <> + } + + return ( + <> + + + + ) +}) + +const CaptchaWidgetMemo = memo(CaptchaWidget) + +export default CaptchaWidgetMemo \ No newline at end of file diff --git a/frontend/src/components/CheckInput.tsx b/frontend/src/components/CheckInput.tsx new file mode 100644 index 0000000..e323714 --- /dev/null +++ b/frontend/src/components/CheckInput.tsx @@ -0,0 +1,65 @@ +import TextField from '@mui/material/TextField'; +import { useState, useImperativeHandle, forwardRef } from 'react'; +import type { TextFieldProps } from '@mui/material/TextField'; +import { useControllableValue } from 'ahooks'; + +export type refType = { + verify: () => boolean +} + +type prop = { + checkList: { + errMsg: string + reg: RegExp + }[] +} & Omit, 'helperText'> + +export const CheckInput = forwardRef(({ required, checkList, ...textFied }, ref) => { + const [err, setErr] = useState(""); + const [value, setValue] = useControllableValue(textFied); + + + const check = (value: string) => { + if (required && (!value || value == "")) { + setErr("此项必填") + return false + } + for (const v of checkList) { + if (!v.reg.test(value)) { + setErr(v.errMsg) + return false + } + } + setErr("") + return true + } + + const verify = () => { + return check(value) + } + + useImperativeHandle(ref, () => { + return { + verify + } + }) + + const onChange = (event: React.ChangeEvent) => { + const value = event.target.value + setValue(value) + check(value) + } + + + + return +}) + +export default CheckInput \ No newline at end of file diff --git a/frontend/src/components/Loading.tsx b/frontend/src/components/Loading.tsx new file mode 100644 index 0000000..9b09deb --- /dev/null +++ b/frontend/src/components/Loading.tsx @@ -0,0 +1,15 @@ +import Backdrop from "@mui/material/Backdrop"; +import CircularProgress from "@mui/material/CircularProgress"; + +export default function Loading() { + return ( + <> + theme.zIndex.modal + 1 }} + open={true} + > + + + + ) +} diff --git a/frontend/src/components/NeedLogin.tsx b/frontend/src/components/NeedLogin.tsx new file mode 100644 index 0000000..b7b38b9 --- /dev/null +++ b/frontend/src/components/NeedLogin.tsx @@ -0,0 +1,41 @@ +import { userInfo } from "@/apis/apis"; +import { ApiErr } from "@/apis/error"; +import { token } from "@/store/store"; +import { useRequest } from "ahooks"; +import { useAtomValue } from "jotai"; +import { useEffect } from "react"; +import { useNavigate, Navigate } from "react-router-dom"; + + +export default function NeedLogin({ children, needAdmin = false }: { children: JSX.Element, needAdmin?: boolean }) { + const t = useAtomValue(token) + const navigate = useNavigate(); + const u = useRequest(() => userInfo(t), { + refreshDeps: [t], + cacheKey: "/api/v1/user" + t, + staleTime: 60000, + onError: e => { + if (e instanceof ApiErr && e.code == 5) { + navigate("/login") + } + console.warn(e) + } + }) + + useEffect(() => { + if (!u.data) return + if (!u.data.is_admin && needAdmin) { + navigate("/login") + } + if (u.data.uuid == "") { + navigate("/login") + } + }, [navigate, needAdmin, u.data]) + + if (!localStorage.getItem("token") || localStorage.getItem("token") == '""') { + return + } + + + return <> {children} +} \ No newline at end of file diff --git a/frontend/src/components/SkinViewUUID.tsx b/frontend/src/components/SkinViewUUID.tsx new file mode 100644 index 0000000..d68b300 --- /dev/null +++ b/frontend/src/components/SkinViewUUID.tsx @@ -0,0 +1,89 @@ +import { yggProfile } from "@/apis/apis"; +import { decodeSkin } from "@/utils/skin"; +import Skeleton from "@mui/material/Skeleton"; +import { useHover, useMemoizedFn, useRequest, useUnmount } from "ahooks"; +import { memo, useEffect, useRef, useState } from "react"; +import ReactSkinview3d, { ReactSkinview3dOptions } from "@/components/Skinview3d"; +import { SkinViewer, WalkingAnimation } from "skinview3d"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; + +interface prop { + uuid: string + width: number + height: number +} + +const SkinViewUUID = memo(function SkinViewUUID({ uuid, width, height }: prop) { + const [textures, setTextures] = useState({ skin: "", cape: "", model: "default" }) + const [err, setErr] = useState("") + + const SkinInfo = useRequest(() => yggProfile(uuid), { + cacheKey: "/api/yggdrasil/sessionserver/session/minecraft/profile/" + uuid, + onError: e => { + console.warn(e) + setErr(String(e)) + }, + refreshDeps: [uuid], + }) + + useEffect(() => { + if (!SkinInfo.data) return + const [skin, cape, model] = decodeSkin(SkinInfo.data) + setTextures({ cape: cape, skin: skin, model: model }) + }, [SkinInfo.data]) + + if (err != "") { + return {err} + } + return (<> + { + (SkinInfo.loading && !SkinInfo.data) ? + : (textures.skin != "" || textures.cape != "") ? ( + ) : + 还没有设置皮肤 + + } + ) +}) + + +const MySkin = function MySkin(p: ReactSkinview3dOptions) { + const refSkinview3d = useRef(null); + const skinisHovering = useHover(refSkinview3d); + const skinview3dView = useRef(null); + + useEffect(() => { + if (skinview3dView.current) { + skinview3dView.current.autoRotate = !skinisHovering + } + if (skinview3dView.current?.animation) { + skinview3dView.current.animation.paused = skinisHovering + } + }, [skinisHovering]) + + useUnmount(() => { + skinview3dView.current?.dispose() + }) + + const handelOnReady = useMemoizedFn(v => { + v.viewer.animation = new WalkingAnimation() + v.viewer.autoRotate = true + skinview3dView.current = v.viewer + }) + + return
+ +
+} + +export default SkinViewUUID \ No newline at end of file diff --git a/frontend/src/components/Skinview3d.tsx b/frontend/src/components/Skinview3d.tsx new file mode 100644 index 0000000..b4fdcc8 --- /dev/null +++ b/frontend/src/components/Skinview3d.tsx @@ -0,0 +1,101 @@ +import { memo, useEffect, useRef } from "react"; +import { SkinViewer, SkinViewerOptions } from "skinview3d"; + +// https://github.com/Hacksore/react-skinview3d/blob/master/src/index.tsx + +/** + * This is the interface that describes the parameter in `onReady` + */ +export interface ViewerReadyCallbackOptions { + /** + * The instance of the skinview3d + */ + viewer: SkinViewer; + /** + * The ref to the canvas element + */ + canvasRef: HTMLCanvasElement; +} + +export interface ReactSkinview3dOptions { + /** + * The class names to apply to the canvas + */ + className?: string; + /** + * The width of the canvas + */ + width: number | string; + /** + * The height of the canvas + */ + height: number | string; + /** + * The skin to load in the canvas + */ + skinUrl: string; + /** + * The cape to load in the canvas + */ + capeUrl?: string; + /** + * A function that is called when the skin viewer is ready + * @param {SkinViewer} instance callback function to execute when the viewer is loaded {@link SkinViewer} + * @example + * onReady((instance) => { + * console.log(instance) + * }) + */ + onReady?: ({ viewer, canvasRef }: ViewerReadyCallbackOptions) => void; + /** + * Parameters passed to the skinview3d constructor allowing you to override or add extra features + * @notes please take a look at the upstream repo for more info + * [bs-community/skinview3d](https://bs-community.github.io/skinview3d/) + */ + options?: SkinViewerOptions; +} + +/** + * A skinview3d component + */ +const ReactSkinview3d = memo(function ReactSkinview3d({ + className, + width, + height, + skinUrl, + capeUrl, + onReady, + options, +}: ReactSkinview3dOptions) { + const canvasRef = useRef(null); + const skinviewRef = useRef(); + + useEffect(() => { + if (!canvasRef.current) return + + const viewer = new SkinViewer({ + canvas: canvasRef.current, + width: Number(width), + height: Number(height), + ...options, + }); + + // handle cape/skin load initially + skinUrl && viewer.loadSkin(skinUrl, { model: options?.model ?? "auto-detect" }); + capeUrl && viewer.loadCape(capeUrl); + + skinviewRef.current = viewer; + + // call onReady with the viewer instance + if (onReady) { + onReady({ viewer: skinviewRef.current, canvasRef: canvasRef.current }); + } + + return () => viewer.dispose() + }, [capeUrl, height, onReady, options, skinUrl, width]); + + + return ; +}) + +export default ReactSkinview3d; \ No newline at end of file diff --git a/frontend/src/hooks/useTitle.ts b/frontend/src/hooks/useTitle.ts new file mode 100644 index 0000000..2ec60da --- /dev/null +++ b/frontend/src/hooks/useTitle.ts @@ -0,0 +1,16 @@ +import { getConfig } from '@/apis/apis' +import { useTitle as auseTitle, useRequest } from 'ahooks' +import { useEffect } from 'react' + +export default function useTitle(title: string) { + const { data, error } = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + }) + useEffect(() => { + error && console.warn(error) + }, [error]) + auseTitle(title + " - " + data?.serverName, { + restoreOnUnmount: true + }) +} \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css deleted file mode 100644 index 6119ad9..0000000 --- a/frontend/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bef5202..30c9a76 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,11 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' +import React from 'react' +import ReactDOM from 'react-dom/client' import App from './App.tsx' +import CssBaseline from '@mui/material/CssBaseline'; -createRoot(document.getElementById('root')!).render( - +ReactDOM.createRoot(document.getElementById('root')!).render( + + - , + ) diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts new file mode 100644 index 0000000..00bc090 --- /dev/null +++ b/frontend/src/store/store.ts @@ -0,0 +1,10 @@ +import { atom } from 'jotai' +import { atomWithStorage } from 'jotai/utils' + +export const token = atomWithStorage("token", "") +export const user = atomWithStorage("username", { + name: "", + uuid: "" +}) + +export const LayoutAlertErr = atom("") \ No newline at end of file diff --git a/frontend/src/utils/email.ts b/frontend/src/utils/email.ts new file mode 100644 index 0000000..2d88b7d --- /dev/null +++ b/frontend/src/utils/email.ts @@ -0,0 +1,3 @@ +export function checkEmail(email: string) { + return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email); +} \ No newline at end of file diff --git a/frontend/src/utils/root.ts b/frontend/src/utils/root.ts new file mode 100644 index 0000000..d5d67a1 --- /dev/null +++ b/frontend/src/utils/root.ts @@ -0,0 +1,6 @@ +export default function root() { + if (import.meta.env.VITE_APIADDR != "") { + return import.meta.env.VITE_APIADDR + } + return location.origin +} \ No newline at end of file diff --git a/frontend/src/utils/skin.ts b/frontend/src/utils/skin.ts new file mode 100644 index 0000000..53b9295 --- /dev/null +++ b/frontend/src/utils/skin.ts @@ -0,0 +1,17 @@ +import { YggProfile } from "@/apis/model"; + +export function decodeSkin(y: YggProfile) { + if (y.properties.length == 0) { + return ["", "", ""] + } + const p = y.properties.find(v => v.name == "textures") + if (!p?.value || p?.value == "") { + return ["", "", ""] + } + const textures = JSON.parse(atob(p.value)) + + const skin = textures?.textures?.SKIN?.url as string ?? "" + const cape = textures?.textures?.CAPE?.url as string ?? "" + const model = textures?.textures?.SKIN?.metadata?.model as string ?? "default" + return [skin, cape, model] +} \ No newline at end of file diff --git a/frontend/src/views/Forgot.tsx b/frontend/src/views/Forgot.tsx new file mode 100644 index 0000000..65ad9ec --- /dev/null +++ b/frontend/src/views/Forgot.tsx @@ -0,0 +1,120 @@ +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import TextField from '@mui/material/TextField'; +import { useTitle } from 'ahooks'; +import { useEffect, useState } from 'react'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import Loading from '@/components/Loading'; +import { produce } from 'immer'; +import { forgotPassWord } from '@/apis/apis'; +import { useNavigate } from 'react-router-dom'; + +export default function Forgot() { + const [err, setErr] = useState("") + useTitle("重设密码") + const [passerr, setPasserr] = useState("") + const [pass, setPass] = useState({ + pass1: "", + pass2: "", + }) + const [load, setLoad] = useState(false) + const [email, setEmail] = useState("") + const [code, setCode] = useState("") + const navigate = useNavigate(); + + useEffect(() => { + if (pass.pass1 != pass.pass2 && pass.pass2 != "") { + setPasserr("密码不相等") + return + } + setPasserr("") + }, [pass.pass1, pass.pass2]) + + const u = new URL(location.href) + + useEffect(() => { + setEmail(u.searchParams.get("email") ?? "") + setCode(u.searchParams.get("code") ?? "") + }, [u.searchParams]) + + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + setLoad(true) + forgotPassWord(email, code, pass.pass1).then(() => { + navigate("/") + }).catch(e => { + setErr(String(e)) + }).finally(() => { setLoad(false) }) + } + + + return ( + + + + + + + + 找回密码 + + + + + setPass(produce(v => { v.pass1 = p.target.value }))} + autoComplete="new-password" + /> + + + setPass(produce(v => { v.pass2 = p.target.value }))} + autoComplete="new-password" + /> + + + + + + + setErr("")} severity="error">{err} + + + {load && } + + ) +} \ No newline at end of file diff --git a/frontend/src/views/Index.tsx b/frontend/src/views/Index.tsx new file mode 100644 index 0000000..cc1cccc --- /dev/null +++ b/frontend/src/views/Index.tsx @@ -0,0 +1,9 @@ +import Profile from "@/views/profile/Profile" +import Login from "@/views/Login" + +export default function Index() { + if (localStorage.getItem("token") && localStorage.getItem("token") != '""') { + return + } + return +} \ No newline at end of file diff --git a/frontend/src/views/Layout.tsx b/frontend/src/views/Layout.tsx new file mode 100644 index 0000000..87555b8 --- /dev/null +++ b/frontend/src/views/Layout.tsx @@ -0,0 +1,291 @@ +import * as React from 'react'; +import { styled, useTheme } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import Drawer from '@mui/material/Drawer'; +import Toolbar from '@mui/material/Toolbar'; +import List from '@mui/material/List'; +import Divider from '@mui/material/Divider'; +import IconButton from '@mui/material/IconButton'; +import MenuIcon from '@mui/icons-material/Menu'; +import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import AppBar from '@mui/material/AppBar'; +import { Outlet } from 'react-router-dom'; +import { AccountCircle } from '@mui/icons-material'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import { LayoutAlertErr, token, user } from '@/store/store'; +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; +import Button from '@mui/material/Button'; +import { useNavigate } from "react-router-dom"; +import { useRequest, useMemoizedFn } from 'ahooks'; +import { getConfig, userInfo } from '@/apis/apis' +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import { memo } from 'react'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import PersonIcon from '@mui/icons-material/Person'; +import SecurityIcon from '@mui/icons-material/Security'; +import SettingsIcon from '@mui/icons-material/Settings'; +import { Link } from "react-router-dom"; +import GroupIcon from '@mui/icons-material/Group'; +import { ApiErr } from '@/apis/error'; + +const drawerWidth = 240; +const DrawerOpen = atom(false) + +const DrawerHeader = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + padding: theme.spacing(0, 1), + // necessary for content to be below app bar + ...theme.mixins.toolbar, + justifyContent: 'flex-end', +})); + +interface ListItem { + icon: JSX.Element + title: string + link: string +} + +const Layout = memo(function Layout() { + const theme = useTheme(); + const [err, setErr] = useAtom(LayoutAlertErr) + + return (<> + + + + + + + setErr("")} severity="error">{err} + + + + + + + + + ) +}) + +const MyToolbar = memo(function MyToolbar() { + const [nowUser, setNowUser] = useAtom(user) + const [anchorEl, setAnchorEl] = React.useState(null); + const navigate = useNavigate(); + const setToken = useSetAtom(token) + const setErr = useSetAtom(LayoutAlertErr) + const setOpen = useSetAtom(DrawerOpen) + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setErr(String(e)) + } + }) + + + const handleLogOut = useMemoizedFn(() => { + setAnchorEl(null); + setNowUser({ name: "", uuid: "" }) + setToken("") + navigate("/login") + }) + + + + return ( + <> + + {nowUser.name != "" && (<> + setOpen(true)} + > + + + ) + } + + + {server.data?.serverName ?? "皮肤站"} + + + {nowUser.name != "" && ( +
+ setAnchorEl(event.currentTarget)} + color="inherit" + > + + + setAnchorEl(null)} + > + 登出 + +
+ )} + {nowUser.name == "" && ( + + )} +
+ ) +}) + +const MyList = memo(function MyList(p: { list: ListItem[] }) { + + + return ( + <> + + {p.list.map(item => + + )} + + + ) +}) + +const MyListItem = function MyListItem(p: ListItem) { + const navigate = useNavigate(); + + const handleClick = () => { + navigate(p.link) + } + + return ( + + + + {p.icon} + + + + + ) +} + +const MyDrawer = function MyDrawer() { + const nowToken = useAtomValue(token) + const setErr = useSetAtom(LayoutAlertErr) + const theme = useTheme(); + const isLg = useMediaQuery(theme.breakpoints.up('lg')) + const [open, setOpen] = useAtom(DrawerOpen) + const navigate = useNavigate(); + + const userinfo = useRequest(() => userInfo(nowToken), { + refreshDeps: [nowToken], + cacheKey: "/api/v1/user" + nowToken, + staleTime: 60000, + onError: e => { + if (e instanceof ApiErr && e.code == 5) { + navigate("/login") + } + console.warn(e) + setErr(String(e)) + }, + }) + + const userDrawerList = React.useMemo(() => [ + { + icon: , + title: '个人信息', + link: '/profile' + }, + { + icon: , + title: '皮肤设置', + link: '/textures' + }, + { + icon: , + title: '账号设置', + link: '/security' + } + ] as ListItem[], []) + + const adminDrawerList = React.useMemo(() => [ + { + icon: , + title: '用户管理', + link: '/admin/user' + } + ] as ListItem[], []) + + + + return (<> + {userinfo.data && ( + setOpen(false)} + > + + setOpen(false)}> + {theme.direction === 'ltr' ? : } + + + + + {userinfo.data?.is_admin && ( + <> + + + )} + + )} + ) +} + +export default Layout \ No newline at end of file diff --git a/frontend/src/views/Login.tsx b/frontend/src/views/Login.tsx new file mode 100644 index 0000000..8d1127d --- /dev/null +++ b/frontend/src/views/Login.tsx @@ -0,0 +1,169 @@ +import * as React from 'react'; +import { useState } from 'react'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Link from '@mui/material/Link'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import { useSetAtom } from 'jotai'; +import { token, user } from '@/store/store' +import { getConfig, login } from '@/apis/apis' +import { Link as RouterLink, useNavigate } from "react-router-dom"; +import Loading from '@/components/Loading' +import CheckInput, { refType } from '@/components/CheckInput' +import useTitle from '@/hooks/useTitle'; +import CaptchaWidget from '@/components/CaptchaWidget'; +import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' +import { ApiErr } from '@/apis/error'; +import { useRequest } from 'ahooks'; + + + +export default function SignIn() { + const [err, setErr] = useState(""); + const [loading, setLoading] = useState(false); + const setToken = useSetAtom(token) + const setUserInfo = useSetAtom(user) + const checkList = React.useRef>(new Map()) + const navigate = useNavigate(); + useTitle("登录") + const captchaRef = React.useRef(null) + const [captchaToken, setCaptchaToken] = useState(""); + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setErr(String(e)) + } + }) + + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + const data = new FormData(event.currentTarget); + const postData = { + email: data.get('email')?.toString(), + password: data.get('password')?.toString(), + } + if (!Array.from(checkList.current.values()).map(v => v.verify()).reduce((p, v) => (p == true) && (v == true))) { + return + } + + if (loading) return + setLoading(true) + login(postData.email!, postData.password ?? "", captchaToken). + then(v => { + if (!v) return + setToken(v.token) + setUserInfo({ + uuid: v.uuid, + name: v.name, + }) + navigate("/profile") + }). + catch(v => { + captchaRef.current?.reload() + console.warn(v) + if (v instanceof ApiErr) { + switch (v.code) { + case 10: + setErr("验证码错误") + return + case 6: + setErr("密码或用户名错误") + return + case 9: + setErr("用户已被禁用") + return + } + } + setErr(String(v)) + }). + finally(() => setLoading(false)) + + }; + + + return ( + + + + + + + 登录 + + + { + dom && checkList.current.set("1", dom) + }} + checkList={[ + { + errMsg: "需为邮箱", + reg: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ + } + ]} + margin="normal" + fullWidth + id="email" + label="邮箱" + name="email" + autoComplete="email" + autoFocus + /> + + + + + + {server.data?.NeedEmail && + 忘记密码? + } + + + + {"注册"} + + + + + + + setErr("")} severity="error">{err} + + {loading && } + + ); +} \ No newline at end of file diff --git a/frontend/src/views/Register.tsx b/frontend/src/views/Register.tsx new file mode 100644 index 0000000..192f9dd --- /dev/null +++ b/frontend/src/views/Register.tsx @@ -0,0 +1,225 @@ +import * as React from 'react'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import Link from '@mui/material/Link'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import { Link as RouterLink } from "react-router-dom"; +import { getConfig, register } from '@/apis/apis' +import CheckInput, { refType } from '@/components/CheckInput' +import { useRef, useState } from 'react'; +import Alert from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import Loading from '@/components/Loading' +import { useNavigate } from "react-router-dom"; +import CaptchaWidget from '@/components/CaptchaWidget'; +import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' +import useTitle from '@/hooks/useTitle'; +import { ApiErr } from '@/apis/error'; +import { useSetAtom } from 'jotai'; +import { token, user } from '@/store/store'; +import { useRequest } from 'ahooks'; + +export default function SignUp() { + const [regErr, setRegErr] = useState(""); + const navigate = useNavigate(); + const [captchaToken, setCaptchaToken] = useState(""); + const captchaRef = useRef(null) + const [loading, setLoading] = useState(false); + useTitle("注册") + const setToken = useSetAtom(token) + const setUserInfo = useSetAtom(user) + const [code, setCode] = useState("") + const [email, setEmail] = useState("") + const [disableEmail, setDisableEmail] = useState(false) + + const u = new URL(location.href) + + React.useEffect(() => { + const e = u.searchParams.get("email") + if (!e || e == "") return + setEmail(e) + setDisableEmail(true) + }, [u.searchParams]) + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setRegErr(String(e)) + } + }) + + React.useEffect(() => { + if (!server.data || !server.data.NeedEmail) return + + const code = u.searchParams.get("code") + if (!code || code == "") { + navigate("/register_email") + return + } + setCode(code) + }, [server.data, u.searchParams, navigate]) + + + + const checkList = React.useRef>(new Map()) + + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + if (loading) return + const data = new FormData(event.currentTarget); + const d = { + password: data.get('password')?.toString(), + username: data.get("username")?.toString() + } + if (!Array.from(checkList.current.values()).map(v => v.verify()).reduce((p, v) => (p == true) && (v == true))) { + return + } + if (captchaToken == "") { + setRegErr("验证码无效") + return + } + setLoading(true) + register(email ?? "", d.username ?? "", d.password ?? "", captchaToken, code). + then(v => { + if (!v) return + setToken(v.token) + setUserInfo({ + uuid: v.uuid, + name: v.name, + }) + navigate("/profile") + }). + catch(v => { + captchaRef.current?.reload() + console.warn(v) + if (v instanceof ApiErr) { + switch (v.code) { + case 10: + setRegErr("验证码错误") + return + case 3: + setRegErr("邮箱已存在") + return + case 7: + setRegErr("用户名已存在") + return + } + } + setRegErr(String(v)) + }). + finally(() => setLoading(false)) + }; + + return ( + + + + + + + + 注册 + + + + + setEmail(v.target.value)} + autoComplete="email" + ref={(dom) => { + dom && checkList.current.set("1", dom) + }} + /> + + + { + dom && checkList.current.set("2", dom) + }} + checkList={[ + { + errMsg: "长度在 3-16 之间", + reg: /^.{3,16}$/ + } + ]} + required + fullWidth + name="username" + label="角色名" + autoComplete="username" + /> + + + { + dom && checkList.current.set("3", dom) + }} + checkList={[ + { + errMsg: "长度在 6-50 之间", + reg: /^.{6,50}$/ + } + ]} + required + fullWidth + label="密码" + type="password" + name="password" + autoComplete="new-password" + /> + + + + + + + + + + 登录 + + + + + + + setRegErr("")} severity="error">{regErr} + + {loading && } + + ); +} \ No newline at end of file diff --git a/frontend/src/views/SendEmail.tsx b/frontend/src/views/SendEmail.tsx new file mode 100644 index 0000000..6fb1126 --- /dev/null +++ b/frontend/src/views/SendEmail.tsx @@ -0,0 +1,181 @@ +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import CssBaseline from '@mui/material/CssBaseline'; +import Grid from '@mui/material/Grid'; +import Box from '@mui/material/Box'; +import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; +import Typography from '@mui/material/Typography'; +import Container from '@mui/material/Container'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import TextField from '@mui/material/TextField'; +import { useRequest, useTitle } from 'ahooks'; +import { getConfig } from '@/apis/apis'; +import { useEffect, useRef, useState } from 'react'; +import Snackbar from '@mui/material/Snackbar'; +import Alert from '@mui/material/Alert'; +import CaptchaWidget from '@/components/CaptchaWidget'; +import type { refType as CaptchaWidgetRef } from '@/components/CaptchaWidget' +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import { useNavigate } from "react-router-dom"; +import { ApiErr } from '@/apis/error'; +import Loading from '@/components/Loading'; + +export default function SendEmail({ title, anyEmail = false, sendService }: { title: string, anyEmail?: boolean, sendService: (email: string, captchaToken: string) => Promise }) { + const [err, setErr] = useState(""); + const [domain, setDomain] = useState(""); + const [email, setEmail] = useState("") + const captchaRef = useRef(null) + const [captchaToken, setCaptchaToken] = useState(""); + const [open, setOpen] = useState(false); + useTitle(title) + const navigate = useNavigate(); + const [helperText, setHelperText] = useState("") + const [loading, setLoading] = useState(false); + + const server = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 60000, + onError: e => { + console.warn(e) + setErr(String(e)) + } + }) + + useEffect(() => { + if (server.data?.AllowDomain.length != 0) { + setDomain(server.data?.AllowDomain[0] ?? "") + } + }, [server.data?.AllowDomain]) + + const emailonChange = (e: React.ChangeEvent) => { + setEmail(e.target.value) + if (e.target.value == "") { + setHelperText("邮箱不得为空") + } + setHelperText("") + } + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (email == "") { + setHelperText("邮箱不得为空") + } + const sendEmail = (() => { + if (!anyEmail && domain != "") { + return `${email}@${domain}` + } + return email + })() + + if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(sendEmail)) { + setHelperText("邮箱格式错误") + return + } + if (!anyEmail && server.data?.EmailReg && server.data?.EmailReg != "" + && !new RegExp(server.data?.EmailReg).test(sendEmail)) { + setHelperText(server.data?.EmailRegMsg ?? "邮箱不满足正则要求") + return + } + + if (server.data?.captcha.type != "" && captchaToken == "") { + return + } + setLoading(true) + sendService(sendEmail, captchaToken).then(() => setOpen(true)).catch(e => { + captchaRef.current?.reload() + console.warn(e) + if (e instanceof ApiErr) { + switch (e.code) { + case 10: + setErr("验证码错误") + return + case 11: + setErr("暂时无法对此邮箱发送邮件") + return + } + } + setErr(String(e)) + }).finally(() => setLoading(false)) + + } + + const handleClose = () => { + navigate("/") + } + + + return ( + + + + + + + + {title} + + + + + + { + server.data?.AllowDomain.length != 0 && !anyEmail && + + 域名 + + + } + + + + + + + + + + setErr("")} severity="error">{err} + + + 邮件已发送 + + 请到收件箱(或垃圾箱)点击验证链接以继续。 + + + + + + {loading && } + + ) +} \ No newline at end of file diff --git a/frontend/src/views/admin/UserAdmin.tsx b/frontend/src/views/admin/UserAdmin.tsx new file mode 100644 index 0000000..d7bd5f0 --- /dev/null +++ b/frontend/src/views/admin/UserAdmin.tsx @@ -0,0 +1,237 @@ +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import useTitle from '@/hooks/useTitle'; +import { useRequest } from 'ahooks'; +import { ListUser, editUser } from '@/apis/apis'; +import { useEffect, useRef, useState } from 'react'; +import { useAtomValue } from 'jotai'; +import { token } from '@/store/store'; +import TablePagination from '@mui/material/TablePagination'; +import Alert from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Box from '@mui/material/Box'; +import Chip from '@mui/material/Chip'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import { EditUser, UserInfo } from '@/apis/model'; +import { produce } from 'immer' +import Checkbox from '@mui/material/Checkbox'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import SkinViewUUID from '@/components/SkinViewUUID'; +import Loading from '@/components/Loading'; + +export default function UserAdmin() { + useTitle("用户管理") + const [page, setPage] = useState(1) + const nowtoken = useAtomValue(token) + const [err, setErr] = useState("") + const [email, setEmail] = useState("") + const [name, setName] = useState("") + const [open, setOpen] = useState(false); + const [row, setRow] = useState(null) + + + const handleOpen = (row: UserInfo) => { + setRow(row) + setOpen(true) + } + + const uq = new URLSearchParams("/api/v1/admin/users") + uq.set("page", String(page)) + uq.set("email", email) + uq.set("name", name) + + const { data, run } = useRequest(ListUser, { + cacheKey: uq.toString(), + debounceWait: 300, + onError: e => { + setErr(String(e)) + } + }) + useEffect(() => { + run(page, nowtoken, email, name) + }, [page, nowtoken, run, email, name]) + + + return (<> + + + + setEmail(v.target.value)} label="邮箱" variant="standard" /> + setName(v.target.value)} label="用户名" variant="standard" /> + + + + + + 邮箱 + 用户名 + 注册 ip + uuid + + + + + {data?.list.map((row) => ( + + {row.email} + {row.name} + {row.reg_ip} + {row.uuid} + + + ))} + +
+
+ setPage(page + 1)} + /> +
+ + setErr("")} severity="error">{err} + + + run(page, nowtoken, email, name)} /> + ); +} + +interface MyDialogProp { + open: boolean + setOpen: (b: boolean) => void + row: UserInfo | null + onUpdate: () => void +} + +function MyDialog({ open, row, setOpen, onUpdate }: MyDialogProp) { + const handleClose = () => { + setOpen(false) + } + const [erow, setErow] = useState({ + email: "", + name: "", + password: "", + is_admin: false, + is_disable: false, + del_textures: false, + }) + const [load, setLoad] = useState(false) + const nowToken = useAtomValue(token) + const [err, setErr] = useState("") + const editValue = useRef({}); + + useEffect(() => { + if (!row) return + setErow({ + email: row.email, + name: row.name, + password: "", + is_admin: row.is_admin, + is_disable: row.is_disable, + del_textures: false, + }) + editValue.current = {} + }, [row, open]) + + const handleOpen = () => { + if (load) return + setLoad(true) + editUser(editValue.current, nowToken, String(row?.uid)).then(() => [setOpen(false), onUpdate(), editValue.current = {}]).finally(() => setLoad(false)). + catch(e => setErr(String(e))) + } + + type StringKeys = { + [K in keyof T]: T[K] extends string ? K : never; + }[keyof T]; + + function handleSetValue(key: StringKeys>, value: string) { + setErow(produce(v => { + v[key] = value + editValue.current[key] = value + })) + } + + type BoolKeys = { + [K in keyof T]: T[K] extends boolean ? K : never; + }[keyof T]; + + function handleSetChecked(key: BoolKeys>, value: boolean) { + setErow(produce(v => { + v[key] = value + editValue.current[key] = value + })) + } + + + return (<> + + 修改用户信息 + + + handleSetValue('email', e.target.value)} + /> + handleSetValue('name', e.target.value)} + /> + handleSetValue('password', e.target.value)} + /> + + + handleSetChecked('is_admin', e.target.checked)} />} label="管理权限" /> + handleSetChecked('is_disable', e.target.checked)} />} label="禁用" /> + handleSetChecked('del_textures', e.target.checked)} />} label="清空材质" /> + + + + + + + + + + + {load && } + + setErr("")} severity="error">{err} + + ) +} \ No newline at end of file diff --git a/frontend/src/views/profile/Profile.tsx b/frontend/src/views/profile/Profile.tsx new file mode 100644 index 0000000..67e364e --- /dev/null +++ b/frontend/src/views/profile/Profile.tsx @@ -0,0 +1,69 @@ +import Card from '@mui/material/Card'; +import CardActions from '@mui/material/CardActions'; +import CardContent from '@mui/material/CardContent'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import CardHeader from '@mui/material/CardHeader'; +import { user } from '@/store/store'; +import { useAtomValue } from 'jotai'; +import { useNavigate } from 'react-router-dom'; +import Box from '@mui/material/Box'; +import useTitle from '@/hooks/useTitle'; +import SkinViewUUID from '@/components/SkinViewUUID'; +import root from '@/utils/root'; + +const Profile = function Profile() { + const navigate = useNavigate(); + const userinfo = useAtomValue(user) + + useTitle("个人信息") + + + return ( + <> + + + + + name + {userinfo.name} + uuid + {userinfo.uuid} + + {/* + + */} + + + + + + + + + + + + + + 本站 Yggdrasil API 地址 + {getYggRoot()} + + + + + + ) +} + +function getYggRoot() { + const u = new URL(root() + "/api/yggdrasil") + return u.toString() +} + +export default Profile \ No newline at end of file diff --git a/frontend/src/views/profile/Security.tsx b/frontend/src/views/profile/Security.tsx new file mode 100644 index 0000000..94f405c --- /dev/null +++ b/frontend/src/views/profile/Security.tsx @@ -0,0 +1,202 @@ +import Button from "@mui/material/Button"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CardHeader from "@mui/material/CardHeader"; +import TextField from "@mui/material/TextField"; +import { useEffect, useState } from "react"; +import { produce } from 'immer' +import { changeName, changePasswd, getConfig } from "@/apis/apis"; +import { useAtom, useAtomValue, useSetAtom } from "jotai"; +import { LayoutAlertErr, token, user } from "@/store/store"; +import Loading from "@/components/Loading"; +import { ApiErr } from "@/apis/error"; +import { useNavigate } from "react-router-dom"; +import useTitle from "@/hooks/useTitle"; +import Box from "@mui/material/Box"; +import { useRequest } from "ahooks"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogActions from "@mui/material/DialogActions"; + +export default function Security() { + useTitle("账号设置") + const setLayoutErr = useSetAtom(LayoutAlertErr) + + const { data } = useRequest(getConfig, { + cacheKey: "/api/v1/config", + staleTime: 600000, + onError: e => { + setLayoutErr(String(e)) + } + }) + + return (<> + + + {data?.AllowChangeName && } + + ) +} + +function ChangePasswd() { + const [pass, setPass] = useState({ + old: "", + pass1: "", + pass2: "", + }) + const [err, setErr] = useState("") + const [oldPassErr, setOldPassErr] = useState(false) + const [nowToken, setToken] = useAtom(token) + const [load, setLoad] = useState(false) + const setLayoutErr = useSetAtom(LayoutAlertErr) + const setUser = useSetAtom(user) + const navigate = useNavigate(); + + useEffect(() => { + if (pass.pass1 != pass.pass2 && pass.pass2 != "") { + setErr("密码不相等") + return + } + setErr("") + }, [pass.pass1, pass.pass2]) + + const handelClick = () => { + if (pass.pass1 != pass.pass2) return + if (load) return + setLoad(true) + changePasswd(pass.old, pass.pass1, nowToken) + .then(() => [navigate("/login"), setToken(""), setUser({ name: "", uuid: "" })]) + .catch(e => { + if (e instanceof ApiErr && e.code == 6) { + setOldPassErr(true) + return + } + setLayoutErr(String(e)) + }).finally(() => setLoad(false)) + } + + + return (<> + + + + setPass(produce(v => { v.old = p.target.value }))} + autoComplete="current-password" + /> + setPass(produce(v => { v.pass1 = p.target.value }))} + autoComplete="new-password" + /> + setPass(produce(v => { v.pass2 = p.target.value }))} + autoComplete="new-password" + /> + + + + {load && } + + ) +} + +function ChangeName() { + const [err, setErr] = useState("") + const [name, setName] = useState("") + const [open, setOpen] = useState(false) + const [load, setLoad] = useState(false) + const nowToken = useAtomValue(token) + const setUser = useSetAtom(user) + + const handelClick = () => { + if (name == "") return + setOpen(true) + } + + const handleClose = () => { + setOpen(false) + } + + const handleSubmit = () => { + if (load) return + setLoad(true) + changeName(name, nowToken).then(() => { + setName("") + setUser(v => { return { name: name, uuid: v.uuid } }) + }).catch(e => { + if (e instanceof ApiErr && e.code == 7) { + setErr("用户名已存在") + return + } + setErr(String(e)) + console.warn(e) + }).finally(() => [setLoad(false), setOpen(false)]) + } + + return (<> + + + + setName(v.target.value)} + autoComplete="username" + /> + + + + + + 确认修改后的用户名 + + + + {`用户名改为`} { {name} } {`?`} + + + + + + + + {load && } + ) +} \ No newline at end of file diff --git a/frontend/src/views/profile/Textures.tsx b/frontend/src/views/profile/Textures.tsx new file mode 100644 index 0000000..8240834 --- /dev/null +++ b/frontend/src/views/profile/Textures.tsx @@ -0,0 +1,133 @@ +import { useEffect, useState } from "react"; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import FormControl from "@mui/material/FormControl"; +import FormLabel from "@mui/material/FormLabel"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Radio from "@mui/material/Radio"; +import RadioGroup from "@mui/material/RadioGroup"; +import Button from "@mui/material/Button"; +import { CardHeader } from "@mui/material"; +import useTitle from "@/hooks/useTitle"; +import { MuiFileInput } from 'mui-file-input' +import Box from "@mui/material/Box"; +import ReactSkinview3d from '@/components/Skinview3d' +import { useUnmount } from "ahooks"; +import { useAtomValue, useSetAtom } from "jotai"; +import { LayoutAlertErr, token } from "@/store/store"; +import { upTextures } from "@/apis/apis"; +import Loading from "@/components/Loading"; +import Snackbar from "@mui/material/Snackbar"; + +const Textures = function Textures() { + const [redioValue, setRedioValue] = useState("skin") + useTitle("上传皮肤") + const [file, setFile] = useState(null) + const setErr = useSetAtom(LayoutAlertErr) + const [loading, setLoading] = useState(false) + const nowToken = useAtomValue(token) + const [ok, setOk] = useState(false) + const [skinInfo, setSkinInfo] = useState({ + skin: "", + cape: "", + model: "default" + }) + + useUnmount(() => { + skinInfo.skin && URL.revokeObjectURL(skinInfo.skin) + skinInfo.cape && URL.revokeObjectURL(skinInfo.cape) + }) + + useEffect(() => { + if (file) { + setSkinInfo(v => { + URL.revokeObjectURL(v.skin); + URL.revokeObjectURL(v.cape); + return { skin: "", cape: "", model: "" } + }) + const nu = URL.createObjectURL(file) + switch (redioValue) { + case "skin": + setSkinInfo({ skin: nu, cape: "", model: "default" }) + break + case "slim": + setSkinInfo({ skin: nu, cape: "", model: "slim" }) + break + case "cape": + setSkinInfo({ skin: "", cape: nu, model: "slim" }) + } + } + }, [file, redioValue]) + + + const onRadioChange = (_a: React.ChangeEvent, value: string) => { + setRedioValue(value) + } + const handleChange = (newFile: File | null) => { + setFile(newFile) + } + + const handleToUpload = () => { + if (!file || loading) return + setLoading(true) + const textureType = redioValue == "cape" ? "cape" : "skin" + const model = redioValue == "slim" ? "slim" : "" + upTextures(nowToken, textureType, model, file).then(() => setOk(true)).catch(e => [setErr(String(e)), console.warn(e)]). + finally(() => setLoading(false)) + } + + + + return (<> + + + + + + 类型 + + } label="Steve" /> + } label="Alex" /> + } label="披风" /> + +
+ +
+ +
+
+
+ + + + {file && } + + +
+ setOk(false)} + message="成功" + /> + {loading && } + ) +} + +export default Textures \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 2328e17..32659f3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,28 @@ -import { defineConfig } from 'vite' +import { defineConfig, type PluginOption } from 'vite' import react from '@vitejs/plugin-react-swc' +import { join } from "path"; +import { visualizer } from "rollup-plugin-visualizer"; -// https://vite.dev/config/ +// https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + visualizer() as PluginOption, + + react(), + ], + resolve: { + alias: { + '@': join(__dirname, "src") + }, + }, + build: { + rollupOptions: { + output: { + manualChunks: { + ['skinview3d']: ['skinview3d'], + ['@mui/material']: ['@mui/material'] + }, + }, + }, + }, })